hello,大家好,本篇文章主要是继指针基础篇之后的进阶篇。博主初学指针时也是对指针的进阶使用深感头疼,特意码字数日力求解决指针,让大家在读了博主的文章后,能彻悟指针。同时呢我们在基础篇已经知道指针的概念,但指针真正的作用可不止于此,接下来让我们一起谈谈指针的高阶主题学习
文章大纲介绍
本文主要讲指针与数组,函数的组合使用,主要的内容有以下知识点:
- 指针与数组
- 指针数组与数组指针
- 数组传参和指针传参
- 函数指针数组搭配使用
- 回调函数以及qsort函数
一.数组与函数
我们来看一个实例:
1.1指向数组的指针
#include <stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
printf("%p\n", arr);
printf("%p\n", &arr[0]);
return 0; }
结果如下:  解读代码: 数组名表示的是数组首元素的地址。(2种情况除外,博主在数组章节有提到) 也就是说,我们可以这样写代码:
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
int *p = arr;
也就是说,我们可以通过指针来访问数组,那么我们要如何将指向首元素的指针后移找到数组的其他数据呢?
1.2指针访问数组
我们看下图,理解分析:  结论: p+i 其实计算的是数组 arr 下标为i的地址。
1.3指针访问数组实例:
来看实例:
int main()
{
int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, };
int *p = arr;
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
for (i = 0; i<sz; i++)
{
printf("%d ", *(p + i));
}
return 0;
}
结果如下:  我们可以看到利用指针我们也完成了我们的目的。
二.指针数组与数组指针
也许我们早就听过指针数组与数组指针,那么他们是什么呢? 
2.1指针数组的定义
我们知道int arr[10] 代表一个存放10个整形数据的数组,那如果我们将其改成int*arr[10] 他会不会代表存放10个指针的数组呢? 答案是YES,同时我们把这种存放指针的数组称之为指针数组。 类似的对比我们整形数组就很好想了。  那么,他的使用是什么样的呢?我们继续往下看。
2.2指针数组的使用实例
我们举个实例,来看看他的使用,代码如下:
#include <stdio.h>
int main() {
int arr[4][4] = { 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 };
int* p2[4];
printf("\n使用指针数组的方式访问二维数组arr\n");
for (int k = 0; k < 4; ++k) {
p2[k] = arr[k];
}
for (int i = 0; i < 4; ++i)
{
for (int j = 0; j < 4; ++j)
{
printf("arr[%d][%d]=%d\t", i, j, *(p2[i] + j));
}
printf("\n");
}
return 0;
}
看看运行结果:  是不是很神奇,我们利用指针完成了对二维数组的访问,当然指针数组的作用不止于此,接下来让博主带着大家进行走进数组与指针。
2.3数组指针的定义
整形指针: int * pint; 能够指向整形数据的指针。 浮点型指针: float * pf; 能够指向浮点型数据的指针。 那数组指针应该是:能够指向数组的指针。
接着我们来看c语言中数组指针的定义:
int (*p)[10];
解释: p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个 指针,指向一个数组,叫数组指针。
这里要注意:[]的优先级要高于号的,所以必须加上()来保证p先和结合。
2.4数组指针与指向数组的指针的混淆
当我们读了他的定义后,有人就有疑问了:
数组指针不是直接可以用 指针指向数组首地址进行访问么,
其实呀这个不叫数组指针,他只是是一个普通指针,指向的是数组首元素。 而数组指针,指向的是数组所在的内存空间,譬如:
char a[4];
char (*pa)[4] = &a;
在这里,pa指向的不是a的首元素地址,而是a的整个内存空间,如果你对pa做自加移动操作,移动步长是4个char字符长度,而不是1个。 而如果是
char a[4];
char *pa = &a;
在这里,pa指向的是a的首元素地址,如果你对pa做自加移动操作,移动步长是1个char字符长度,而不是4个。
2.5数组指针的使用实例
#include <stdio.h>
void print_arr1(int arr[3][5], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
void print_arr2(int(*arr)[5], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
int main()
{
int arr[3][5] = { 0,1,2,3,4,5,6,7,8,9 };
print_arr1(arr, 3, 5);
print_arr2(arr, 3, 5);
return 0;
}
我们可以看到两种方式都成功打印出了数组: 
三.数组传参与指针传参
3.1一维数组传参(含指针数组)
一个代码读懂一维数组传参:
#include <stdio.h>
void test(int arr[10])
{}
形参用一个数组接受。因为实参是arr,即数组首元素的地址,这里的arr[]只是便于理解。
其实本质上传递的是地址。
void test(int *arr)
{}
我们知道arr是数组首元素的地址,自然可以通过指针来接收传参
void test2(int *arr[20])
{}
我们知道arr2是指针数组,自然形参可以用指针数组来接收
void test2(int **arr)
{}
我们知道arr2是指针数组,数组存放的是指针,那我们在接受传参时,必须用指向指针的指针来接收,即二级指针
int main()
{
int arr[10] = {0};
int *arr2[20] = {0};
test(arr);
test2(arr2);
return 0;
}
总结: 数组传参,形参可以是数组也可以是指针
3.2二维数组传参
void test(int arr[3][5])
{}
void test(int arr[][5])
{}
二维数组传参,函数形参的设计只能省略第一个[]的数字。
对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
void test(int *arr)
{}
指针接收二维数组传参的首行地址
void test(int (*arr)[5])
{}
实参是一个一维数组的地址,所以能用一个数组指针类型去接收。数组指针指的是指向数组的指针,依然为一个指针,存放的是整个数组的地址。
int main()
{
int arr[3][5] = {0};
test(arr);
二维数组首元素是第一行,所以二维数组传参传的是第一行的地址,而不是首元素地址。
return 0;
}
3.3指针传参
相对来说,指针传参并不难,我们直接看下列代码: 一级指针:
#include <stdio.h>
void print(int *p, int sz)
{
int i = 0;
for(i=0; i<sz; i++)
{
printf("%d\n", *(p+i));
}
}
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9};
int *p = arr;
int sz = sizeof(arr)/sizeof(arr[0]);
print(p, sz);
return 0;
}
运行结果如下:  二级指针:
#include <stdio.h>
void test(int** ptr)
{
printf("num = %d\n", **ptr);
}
int main()
{
int n = 10;
int* p = &n;
int** pp = &p;
test(pp);
test(&p);
return 0;
}
结果如下: 
四。函数指针
我们知道,函数也有地址,既然是地址,我们也是可以通过指针访问的
4.1函数指针
函数名与&函数名都代表函数的地址,并无区别
void test()
{
printf("hehe\n");
}
void (*pfun1)();
回答是:pfun1可以存放。pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void。 总结: 指针函数的声明类似函数,通俗来讲:返回类型 (*名称)(参数){} 请读者思考这段代码,进行思考:
void (*signal(int , void(*)(int)))(int);
4.2函数指针数组
定义: 我们把函数的地址存到一个数组中,那这个数组就叫函数指针数组.
`int (*parr1[10])();
parr1 先和 [] 结合,说明 parr1是数组,数组的内容是 int (*)() 类型的函数指针。
4.3函数指针数组实例
我们来看看没有使用函数指针数组的简易计算器的实现:
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a*b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
do
{
printf( "*************************\n" );
printf( " 1:add 2:sub \n" );
printf( " 3:mul 4:div \n" );
printf( "*************************\n" );
printf( "请选择:" );
scanf( "%d", &input);
switch (input)
{
case 1:
printf( "输入操作数:" );
scanf( "%d %d", &x, &y);
ret = add(x, y);
printf( "ret = %d\n", ret);
break;
case 2:
printf( "输入操作数:" );
scanf( "%d %d", &x, &y);
ret = sub(x, y);
printf( "ret = %d\n", ret);
break;
case 3:
printf( "输入操作数:" );
scanf( "%d %d", &x, &y);
ret = mul(x, y);
printf( "ret = %d\n", ret);
break;
case 4:
printf( "输入操作数:" );
scanf( "%d %d", &x, &y);
ret = div(x, y);
printf( "ret = %d\n", ret);
break;
case 0:
printf("退出程序\n");
breark;
default:
printf( "选择错误\n" );
break;
}
} while (input);
return 0;
}
我们可以看的出来代码长且繁琐,接下来我们利用函数指针数组进行优化:
```c
#include <stdio.h>
int add(int a, int b) {
return a + b; }
int sub(int a, int b) {
return a - b; }
int mul(int a, int b) {
return a*b; }
int div(int a, int b) {
return a / b; }
int main()
{
int x, y;
int input = 1;
int ret = 0;
int(*p[5])(int x, int y) = { 0, add, sub, mul, div };
while (input)
{
printf( "*************************\n" );
printf( " 1:add 2:sub \n" );
printf( " 3:mul 4:div \n" );
printf( "*************************\n" );
printf( "请选择:" );
scanf( "%d", &input);
if ((input <= 4 && input >= 1))
{
printf( "输入操作数:" );
scanf( "%d %d", &x, &y);
ret = (*p[input])(x, y);
}
else
printf( "输入有误\n" );
printf( "ret = %d\n", ret);
}
return 0;
}
程度解决了代码的繁琐程度,非常实用。
4.3指向函数指针数组的指针
解读:
指向函数指针数组的指针是一个指针,指针指向一个数组 ,那么这个数组的元素都是函数指针
void (*pfun)() = test;
void (*pfunArr[5])();
pfunArr[0] = test;
void (*(*ppfunArr)[5])() = &pfunArr;
return 0;
关于这个指针的话,博主认为大家了解即可,使用的相对来说较少。接下来,我们要进行压轴知识点的学习了,可以说,他也是个非常重要的知识点。接下来,我们一起学习——回调函数。
五.回调函数
定义如下:
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个 函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数 的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进 行响应。
5.1 qsort函数
我们看看c标准库对qsort函数参数的定义:   大家自行阅读参数以及了解参数的意义
1.qsort是一个包含在<stdlib.h>头文件下的库函数,进行快速排序 2.qsort函数可以对所有类型的数据进行排序,一个函数解决所有类型的排序问题,不需要根据不同的类型些不同的函数,提高效率 3.在使用qsort库函数之前我们需要写一个比较函数,这个需要针对各个类型写单独的比较函数,这个比较函数只需要比较两个元素之间的大小就可以,若A>B则返回1,A<B返回-1,A=B返回0,之后调用sqort函数,将比较后的返回值传入,进行排序,得到最终的排序结果结果。
5.2回调函数实例
#include <stdio.h>
int int_cmp(const void * p1, const void * p2) {
return (*( int *)p1 - *(int *) p2);
}
int main()
{
int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
int i = 0;
qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);
for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++)
{
printf( "%d ", arr[i]);
}
printf("\n");
return 0;
}
6.总结
指针的进阶学习就到此结束,博主还会进行关系c语言的重难点以及分享许多比较实用的知识,如有疑问,私聊或者评论区告诉博主,博主力争帮大家解决问题,那么同时欢迎关注博主,一起与博主学习c语言 码字不易,求个三连,博主将与你们同在~
|