内容提要
- 指针
- 函数指针与指针函数
- 二级指针
指针
函数指针与指针函数
函数指针
定义
函数指针本质上是指针,是一个指向函数的指针。函数都有一个入口地址,所谓指向函数的指针,就是指向函数的入口地址。(这里的函数名就代表入口地址)
函数指针存在的意义:
- 让函数多了一种调用方式
- 函数指针可以作为形式,可以形式调用(回调函数)
语法:
返回值类型(*指针变量名)(形参列表);
举例
int (*p)(int a, int b);
函数指针的初始化
①定义的同时赋值
// 函数指针需要依赖于函数,先有函数,后有指针// 定义一个普通函数
// 普通函数
int add(int a, int b){return a + b};// 定义一个函数指针,并初始化
// 观察,函数指针的返回类型和指向函数的返回类型一致,函数的形参列表个数、类型、顺序跟指向函数的形参列表一致
int (*p)(int a, int b) = add; // 函数指针p指向函数add,这里的add不能带(),add就是该函数的入口地址
②先定义后赋值
// 函数指针需要依赖于函数,先有函数,后有指针// 定义一个普通函数
// 普通函数
int add(int a, int b){return a + b};// 定义一个函数指针,并初始化
// 观察,函数指针的返回类型和指向函数的返回类型一致,函数的形参列表个数、类型、顺序跟指向函数的形参列表一致
int (*p)(int, int) = add; //形参列表的参数名可以省略p = add; //此时是将add的入口地址赋值给指针
注意:
1.函数指针指向的函数要和函数指针定义的返回值类型,形参列表对应,否则编译报错2.函数指针是指针,但不能指针运算,如p++等,没有实际意义
3.函数指针作为形参,可以形成回调
4.函数指针作为形参,函数调用时的实参只能是与之对应的函数名,不能带小括号()
5.函数指针的形参列表中的变量名可以省略
注意:函数不能作为函数的形参,但是指向函数的函数指针是可以作为函数的形参的。
案例
- 需求:求a,b两个数的最大值
- 代码:
/**
* 定义一个函数,求两个数的最大值
**/
int ger_max(int a, int b)
{return a > b ? a : b;
}int main()
{// 定义测试数据int a = 3, b = 4, max;// 直接调用函数max = get_max(a,b);printf("%d,%d中的最大值是%d\n",a,b,max);// 定义一个函数指针int (*p)(int,int) = get_max;// 间接调用函数:方式1max = p(a,b); //直接将指针名作为函数名printf("%d,%d中的最大值是%d\n",a,b,max);// 间接调用函数:方式2max = (*p)(a,b); //直接将指针名作为函数名printf("%d,%d中的最大值是%d\n",a,b,max);return 0;
}
回调函数
定义
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针作为参数传递给另一个函数,当这个指针备用来调用其所指向的函数时。我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
为什么要用回调函数
因为可以把调用者与被调用者分开,所以调用者不关心谁是被调用者。它只需知道存在一个具有特定原型和限制条件的被调用函数。
简而言之,回调函数就是允许用户把需要调用的方法的指针作为参数传递给一个函数,以便该函数在处理相似事件的时候可以灵活的使用不同的方法。
/**
*回调函数1
*/
int callback_1(int a)
{printf("hello, this is callback 1:a=%d\n", a),return a;
}/**
*回调函数2
*/
int callback_2(int b)
{printf("hello, this is callback_2:b=%d\n", b);return b;
}/**
*实现回调函数(函数的参数是函数指针)
*/
int handle(int x, int(*callback)(int))
{printf("日志:开始执行任务!\n");int res = callback(x);printf("日志:执行结果:%d\n", res);printf("日志:结束执行任务!\n");
}int main(int argc,char *argv[])
{handle(100,callback_1);handle(200,callback_2); return 0;
}
指针函数
定义
本质上是函数,这个函数的返回值类型是指针,整个函数称之为指针函数。
int *p
:普通指针。int (*p)[3]
:数组指针。int *p[]
:指针数组。int (*p)()
:函数指针。int *p()
:指针函数。
语法:
// 写法1
数据类型* 函数名(形参列表)
{函数体;return 指针变量;
}
// 写法2
数据类型 *函数名(形参列表)
{函数体;return 指针变量;
}
举例:
int *get(int a)
{int *p = &a;return 0;
}
int main()
{int *a= get(5);printf("%d\n",*a);
}
注意:
在函数中不要直接返回一个局部变量的地址,因为函数调用完毕后,局部变量会被回收,使得返回的地址就不明确,此时返回的指针就是野指针。
解决方案:
如果非要访问,可以给这个局部变量添加(定义的时候添加)static
,可以延长它的生命周期,从而避免野指针(尽量少用,因为存在内存泄漏)
演示案例:
int *add(int a, int b)
{static int sum; // 使用static修饰局部变量,会提升生命周期,但是作用域不会发生改变,不建议sum = a + b;return ∑ // 执行完return 作为函数作用域的布局变量sum的空间被释放
}int main()
{int *res = add(5,3); // 接收到了地址,但是地址对应的空间已经被释放printf("%d\n", *res);return 0;
}
案例
- 需求:有若干个学生,每个学生有4门成绩,要求在用户输入学号(int id)后,能输出该学生的全部成绩(float scores[4]),用指针函数实现。
/**
* 定义一个函数,要求输入学生序号,返回该学生所有成绩
* @param all:所有人的成绩
* @param id:要检索学生的序号
* @return id:对应血行的成绩数组(指针)
*/
float* search(float (*all)[4], int id)
{// 定义一个指针变量,用来接受查询到的某个学生的所有成绩float *pt;pt = *(all + id); //行偏移return pt; // 赋值运算中 float pt[4] == float *pt;
}int main()
{// 准备一个二维数组,存储3个学生的成绩float scores[][4] = {{60,70,80,90},{66,77,88,99},{61,71,81,91}};// 定义一个变量,用来接收学生序号int m;printf("请输入学生序号(0~2):\n");scanf("%d", &m);printf("第%d个学生的成绩:\n", m);// 创建一个指针,用来接收成绩float *p = search(scores, m);// 遍历成绩for(; p < scores[m] + 4; p++){printf("%5.2f\t", *p);}printf("\n");return 0;
}
二级指针
定义
二级指针(多重指针)用于存储一级指针的地址,需要两次解引用才能访问原始数据,其他多级指针的用法类似,实际开发中最常见的多级指针是二级指针
int a = 10; // a是普通变量
int *p = &a; // 一级指针(p指向a,p存储的是a的地址)
int **w = &p; // 二级指针(w指向p,w存储的是p的地址)
int ***x = &w // 三级指针(x指向w,x存储的是w的地址)
语法
数据类型 **指针变量名 = 指针数组的数组名 | 一级指针的地址
特点
①与指针数组的等效性二级指针与指针数组等效性,但与二维数组不等效。二维数组名是数组指针类型,如int (*)[3]
,而非二级指针。
// 指针数组
int arr[] = {11,22,33};
int* arr_[] = {&arr[0], &arr[1], &arr[2]}; // 正确的指针数组的定义// 二级指针接受指针数组
char* str[3] = {"abc","aaa034","12a12"}; // str存储的是三个字符串的首地址
char **p = str;
②与二维数组的差异二维数组名是数组指针类型,直接赋值给二级指针会导致类型不匹配
int arr[2][3] = {{1,3,5}{11,33,55}};
int (*p)[3] = arr; //arr这个数组名就是数组指针类型int **k = arr; // 编译报错,arr类型 int(*)[3] 不兼容 k类型 int**
解引用
①字符型二级指针可直接遍历字符串数组,类似一维数组
void fun1()
{// 定义一个字符类型的指针数组char *arr[] = {"orange","apple","banana","kiwi"};int len = sizeof(arr) / sizeof(arr[0]);for (int i = 0; i <len; i++){printf("%s\n",arr[i]);}printf("\n");
}void fun2()
{char *arr[] = {"orange","apple","banana","kiwi"};int len = sizeof(arr) / sizeof(arr[0]);// 二级指针等效于指针数组char **p = arr;for(int i = 0; i < len; i++){printf("%s\n",p[i]); //下标法printf("%s\n",*(p+i)); //指针法}printf("\n");
}void fun3()
{char *arr[] = {"orange","apple","banana","kiwi"};int len = sizeof(arr) / sizeof(arr[0]);// 二级指针等效于指针数组char **p;// 定义循环变量int i = 0;// 遍历指针数组do{p = arr + i; // p = arr ----> p = arr + 0printf("%s\n", *p);i++;}while(i < len);
}int main()
{fun1();fun2();fun3();return 0;
}
②其他类型的二级指针需要两次解引用访问数据,常用于操作指针数组
int main()
{// 普通的一维数组int arr1[] = {11,22,33,44,55,66};// 创建一个指针数组int *arr[] = {&arr1[0],&arr1[1],&arr1[2],&arr2[3],&arr1[4],&arr1[5]};// 用一个二级指针接收指针数组int **p = arr;// 遍历数组for(int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++){printf("%-6d", *p[i]); //下标法printf("%-6d", **(p + i)); // 指针法 等价于*(*(p+i))}pritnf("\n");return 0;
}
总结
①二级制指针与指针数组等效,可简化指针数组的遍历操作
②二维数组名是数组指针类型(如: int(*)[3]
),与二级指针( int**)
类型不兼容。
③操作非字符型二级指针时,须通过两次解引用访问实际数据。