内容提要
-
指针
回顾
变量指针和指针变量:
-
指针变量:本质上是一个变量,存放的是地址值(指针值)
-
变量指针:本质上是一个指针,可以通过&获取
数组指针和指针数组:
-
指针数组:本质上是数组,存放一组指针的数组
-
数组指针:本质上是指针,是一个指向整个数组的指针
字符指针:
-
本质上是一个指针变量,指向char类型数据
如何使用单层for循环遍历一个二维数组:
-
用一个指针变量,默认指向第0行第0列,然后使用for循环,通过指针偏移实现元素的遍历
int main(){int arr[2][3] = {10,20,30,100,200,300};int *p = &arr[0][0];int len = sizeof(arr) / sizeof(arr[0][0]);int rows = sizeof(arr) / sizeof(arr[0]);// for (; p < arr + rows; p++) // 方式1,会报警告,不影响使用// for (; p < &arr[0][0] + len; p++) // 方式2for (; p < arr[0] + len; p++) // 方式3{printf("%-6d",*p);}printf("\n");return 0;}// 一维数组 arr-------数组的首地址// 二维数组 int a[2][2] = {1,2,3,4};// &a----------------整个数组的地址(1,2,3,4)// a---&a[0]---------首行元素的地址(1,2)// a[0]---&a[0][0]---首个元素的地址(1)
使用数组指针遍历二维数组
-
实现方式:
int arr[2][3] = {10,20,30,100,200,300};int (*p)[3] = arr;int rows = sizeof(arr)/sizeof(arr[0]);int cols = sizeof(arr[0])/sizeof(arr[0][0]);for (int i = 0; i < rows; i++){for(int j = 0; j < cols; j++){// 下标法遍历printf("%-6d",arr[i][j]);printf("%-6d",p[i][j]);// 指针法遍历printf("%-6d",*(*(arr + i) + j));printf("%-6d",*(*(p + i) + j));// 混合法遍历printf("%-6d",*(arr + i) + j));printf("%-6d",*(p[i] + j));}}printf("\n");
C语言实现字符串变量的两种方式:
-
字符数组实现字符串变量
char arr[] = "hello";
-
字符指针实现字符串变量
chae *arr = "hello";
函数传参相关:
-
数组、指针在传参中的配套
-
形参和实参都是数组
-
实参是数组,形参是指针
-
形参和实参都是指针
-
实参是指针,形参是数组
注意:
字符数组和字符指针在函数传参的时候,需要注意:
被调函数中不能修改传进来的字符串
-
指针
函数指针与指针函数
函数指针
定义
函数指针本质上是指针,它是函数的指针(定义了一个指针,变量中存储了函数的地址)。函数都有一个入口地址,所谓指向函数的指针,就是指向函数的入口地址。这里函数名就代表入口地址。
函数指针存在的意义:
-
让函数多了一种调用方式
-
函数指针可以做为形参,可以形式调用(回调函数)
语法:
返回值类型 (*变量名)(形参列表);
举例:
int (*p)(int a,int b);
函数指针的初始化
①定义的同时赋值
// 函数指针需要依赖于函数,先有函数,再有指针// 定义一个普通的函数int add(int a,int b) { return a + b; }// 定义一个函数指针,并初始化// 观察:函数指针的返回类型和依赖函数的返回类型一致,函数指针的参数个数、类型和依赖的函数一致int (*p)(int a,int b) = add; // 赋值一定要注意:函数名不能带()
②先定义,后赋值
// 函数指针需要依赖于函数,先有函数,再有指针// 定义一个普通的函数int add(int a,int b) { return a + b; }// 定义一个函数指针,并初始化// 观察:函数指针的返回类型和依赖函数的返回类型一致,函数指针的参数个数、类型和依赖的函数一致// int (*p)(int a,int b); // 写法1int (*p)(int, int); // 写法2 // 给函数指针赋值p = add;
注意:
函数指针指向的函数要和函数指针定义的返回值类型、形参列表对应,否则编译报错
函数指针是指针,但不能指针运算,如p++等,没有实际意义
函数指针作为形参,可以形成回调
函数指针作为形参,函数调用时的实参只能是与之对应的函数名,不能带小括号()
函数指针的形参列表中的变量名可以省略
案例
-
需求:求a,b两个数的最大值
-
代码:
/*** 定义一个函数,实现求两个数中的最大值* @param int a, int b* @return 最大值*/ int get_max(int a, int b) {return a > b ? a : b; }int main(int argc,char *argv[]) {// 定义测试数据int a = 3, b = 4, c;// 直接调用函数c = get_max(a,b);printf("%d,%d中的最大值是%d\n",a,b,c);// 定义一个函数指针int (*p)(int, int) = get_max;// 间接调用函数:方式1c = p(a,b);printf("%d,%d中的最大值是%d\n",a,b,c);// 间接调用函数:方式2c = (*p)(a,b);printf("%d,%d中的最大值是%d\n",a,b,c);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() {// 要求使用callback_1打印输出100handle(100,callback_1);// 要求使用callback_2打印输出200handle(200,callback_2);return 0; }
指针函数
定义
本质上是函数,这个函数的返回值类型是指针,这个函数称之为指针函数。
语法:
// 数据类型* 函数名(形参列表) // 写法1 数据类型 *函数名(形参列表) // 写法2 {函数体;return 指针变量; }
举例:
int *get(int a) {int *b = &a;return b; }int main() {int *a = get(5);printf("%d\n",*a); }
注意:
在函数中不要直接返回一个局部变量的地址,因为函数调用完毕后,局部变量会被回收,使得返回的地址就不明确,此时返回的指针就是野指针。
解决方案:
如果非要访问,可以给这个局部变量添加(定义的时候添加)static
,可以延长它的生命周期,从而避免野指针(尽量少用,因为存在内存泄漏)
演示案例:
int *add(int a, int b) {static int sum; // 提升sum的生命周期sum = a + b;return ∑ }int main() {int *res = add(5,3);printf("%d\n",*res);return 0; }
案例
-
需求:有若干个学生,每个学生有4门成绩,要求在用户输入学生序号(参数:int id)后,能输出该学生的全部成绩(返回值:float scores[4]),用指针函数来实现
-
代码:
/*** 定义一个函数,要求输入学生序号,返回学生所有成绩* @param all: 所有的学生信息* @param id: 学生序号(从0开始)* @return float scores[4]*/ float *search(float (*all)[4],int id) {// 定义指针变量用来接收查询到的某个学生的所有成绩float *pt;pt = *(all + id); // 行偏移return pt; // 返回行地址 }int main(int argc,char *argv[]) {// 准备一个二维数组,存储若干个学生的成绩float scores[][4] = {{60,70,80,89},{66,44,55,88},{90,98,87,100}};int m;printf("请输入学生序号(0~2):\n");scanf("%d",&m);printf("第%d个学生的成绩:\n",m);// 用来接收第m个学生的成绩float *p;p = search(scores,m);// 遍历成绩for (;p < scores[m] + 4; p++){printf("%5.2f\t",*p);}// for (int i = 0; i < 4; i++)// {// printf("%5.2f\t",*(p+i));// }printf("\n");return 0; }
二维数组访问方式
访问方式 | 获取列值 | 获取列地址 | 等价性说明 |
---|---|---|---|
数组下标 | arr[i][j] | &arr[i][j] | 直接通过行、列下标访问元素 |
双重指针解引用 | *(*(arr + i) + j) | *(arr + i) + j | 通过指针算术逐层解引用访问元素 |
单行指针解引用 | *(arr[i] + j) | arr[i] + j | 先定位行,再通过指针偏移访问元素 |