了解指针4

📅 发布时间:2026/7/14 19:25:22 👁️ 浏览次数:
了解指针4
深入理解指针(4) — 内容总结1. 字符指针变量char*1.1 指向单个字符intmain(){charchw;char*pcch;*pcw;return0;}1.2 指向字符串字面量#includestdio.hintmain(){//加cosnt是为了不让该字符串里的内容若是修改代码会崩溃constchar*pstrhello bit.;// pstr 存的是字符串首字符 h 的地址printf(%c\n,*pstr);// 输出首字符 hprintf(%s\n,pstr);// 输出 hello bit.return0;}注意字符串字面量存储在只读数据区所以pstr最好用const char*修饰防止通过pstr修改时程序崩溃。1.3 经典题字符数组 vs 字符指针#includestdio.hintmain(){charstr1[]hello bit.;charstr2[]hello bit.;constchar*str3hello bit.;constchar*str4hello bit.;if(str1str2)// str1和str2的地址不一样{printf(str1 and str2 are same\n);}else{printf(str1 and str2 are not same\n);// ✅ 输出这行}if(str3str4){printf(str3 and str4 are same\n);// ✅ 输出这行}else{printf(str3 and str4 are not same\n);}return0;}结果str1 and str2 are not samestr3 and str4 are same原因str1 和 str2 是两个独立的数组分别占据内存中不同的位置。str1 str2 比较的是两个数组的起始地址也就是数组名被隐式转换为指针。虽然它们的内容完全相同但地址不同所以比较结果为假不相等。“hello bit.” 是一个字符串常量存储在常量区只读数据段。编译器为了节省内存会将内容相同的字符串常量优化为只存一份。str3 和 str4 都指向同一块内存地址。str3 str4 比较的是两个指针存储的地址值它们相同所以比较结果为真相等。结论变量存储位置比较结果str1、str2各自在栈上开辟独立的字符数组不相同地址不同str3、str4都指向只读数据区的同一字符串字面量相同编译器优化指向同一地址2. 数组指针变量2.1 数组指针 vs 指针数组分辨技巧int*p1[10];// 指针数组p1 先与 [] 结合 → 10 个 int* 的数组//指向首元素int(*p2)[10];// 数组指针p2 先与 * 结合 → 指向含 10 个 int 的数组的指针指向数组的指针变量 p1是数组类型是指针所有是指针数组p2是指针指向数组所有是数组指针口诀[]优先级高于*。不加括号 → 数组加括号 → 指针。2.2 数组指针的初始化intarr[10]{0};int(*p)[10]arr;// p 指向整个数组int (*)[10] 类型关键区别arr表示数组首元素的地址类型为int*而arr表示整个数组的地址类型为int (*)[10]。虽然两者的数值可能相同但指针类型和步长不同。图解int (*p)[10] arr; │ │ │ └── p 指向一个包含 10 个 int 的数组 └────── p 是一个指针关键arr的类型是int (*)[10]数组指针与普通指针int*不同。3. 二维数组传参的本质二维数组作为函数参数时会退化为指向第一行一维数组的指针。3.1 传统写法数组形式#includestdio.hvoidtest(inta[3][5],intr,intc){inti0;intj0;for(i0;ir;i){for(j0;jc;j){printf(%d ,a[i][j]);}printf(\n);}}intmain(){intarr[3][5]{{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}};test(arr,3,5);return0;}3.2 本质写法数组指针形式#includestdio.h// int a[3][5] 等价于 int (*p)[5]voidtest(int(*p)[5],intr,intc)//int (*p)[5]是指向第一个数组的地址{inti0;intj0;for(i0;ir;i){for(j0;jc;j){printf(%d ,*(*(pi)j));// 等价于 p[i][j]}printf(\n);}}intmain(){intarr[3][5]{{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}};test(arr,3,5);//传的是一维数组的指针return0;}核心理解表达式含义p指向第 0 行一个int[5]p i指向第 i 行跳过一个int[5]即 20 字节*(p i)得到第 i 行的数组名int[5]也就是该数组首元素的地址*(p i) j第 i 行中第 j 个元素的地址*(*(p i) j)第 i 行第 j 个元素的值结论int a[3][5]作为参数时退化为int (*)[5]指向含 5 个 int 的数组的指针而非二级指针int**。4. 函数指针变量4.1 函数地址函数名和函数名都是函数的地址#includestdio.hvoidtest(){printf(hehe\n);}intmain(){printf(test: %p\n,test);// 输出 test: 005913CAprintf(test: %p\n,test);// 输出 test: 005913CAreturn0;}结论test和test数值相同都是函数的入口地址。4.2 函数指针的定义与使用voidtest(){printf(hehe\n);}void(*pf1)()test;// ✅ 写法一void(*pf2)()test;// ✅ 写法二等价intAdd(intx,inty){returnxy;}int(*pf3)(int,int)Add;// 或者int(*pf3)(intx,inty)Add;// x、y 可写可不写Add和Add都是函数地址函数指针拆解int (*pf3) (int x, int y) ↑ ↑ ↑ | | └── 参数列表 | └──────────── pf3 是指针 └───────────────── 返回值类型为 int调用方式两种等价#includestdio.hintAdd(intx,inty){returnxy;}intmain(){int(*pf3)(int,int)Add;//(*pf3)AddAddpf3printf(%d\n,(*pf3)(2,3));// 输出 5注意一定要把*pf3括起来printf(%d\n,pf3(3,5));// 输出 8等价写法return0;}4.3 两个经典考验例1(*(void(*)())0)();拆解步骤步骤解释0整数 0(void (*)())0将 0 强制转换为函数指针类型无参返回 void*(void (*)())0解引用得到该函数(*(void (*)())0)()调用地址为 0 的那个函数本质调用一个地址为 0 的函数危险操作会导致段错误。例2void(*signal(int,void(*)(int)))(int);拆解步骤步骤解释signal函数名signal(int, void(*)(int))signal 函数有两个参数一个int一个函数指针参数为 int、返回 voidvoid (*...)(int)signal 的返回类型是函数指针参数为 int、返回 void4.4 用typedef简化函数指针typedefunsignedintuint;// 给类型起别名//unsigned int unittypedefint*pint;// 将 int* 起别名为 pint//int*pinttypedefint(*parr_t)[5];// 数组指针类型起别名//int(*)[5] parr_ttypedefvoid(*pfun_t)(int);// 函数指针类型起别名//void (*)(int) pfun_t//pfun_t p2 test;//可定义为 pfun_t(类型) p2(函数指针变量的名字) test(函数)// 简化例2typedefvoid(*pfun_t)(int);pfun_tsignal(int,pfun_t);// 简洁明了和#define的区别// 使用 typedeftypedefchar*STRING;STRING p1,p2;// p1 和 p2 都是 char* 类型两个都是指针✅// 使用 #define#defineSTRINGchar*STRING p1,p2;// 展开后char* p1, p2; 只有 p1 是指针p2 是 char❌5. 函数指针数组int*arr[10];// 整型指针数组int(*parr1[3])();// 函数指针数组3 个函数指针返回 int、无参int*parr2[3]();// ❌ 错误函数不能返回数组int(*)()parr3[3];// ❌ 语法错误int (*parr1[3])();拆解int (*parr1[3])(); ↑ └── parr1 先与 [3] 结合 → 数组 再与 * 结合 → 数组中存放的是指针 再与 () 结合 → 指针指向函数返回 int、无参结论parr1是一个包含 3 个元素的数组每个元素是int (*)()类型的函数指针。6. 转移表 — 函数指针数组的应用函数指针数组可用于替换冗长的switch-case实现转移表。6.1 传统写法switch-case 冗长#includestdio.hintadd(inta,intb){returnab;}intsub(inta,intb){returna-b;}intmul(inta,intb){returna*b;}intdiv(inta,intb){returna/b;}intmain(){intx,y;intinput1;intret0;do{printf(*************************\n);printf( 1:add 2:sub \n);printf( 3:mul 4:div \n);printf( 0:exit \n);printf(*************************\n);printf(请选择);scanf(%d,input);switch(input){case1:printf(请输入两个操作数);scanf(%d %d,x,y);retadd(x,y);printf(ret %d\n,ret);break;case2:printf(请输入两个操作数);scanf(%d %d,x,y);retsub(x,y);printf(ret %d\n,ret);break;case3:printf(请输入两个操作数);scanf(%d %d,x,y);retmul(x,y);printf(ret %d\n,ret);break;case4:printf(请输入两个操作数);scanf(%d %d,x,y);retdiv(x,y);printf(ret %d\n,ret);break;case0:printf(退出程序\n);break;default:printf(输入错误\n);break;}}while(input);return0;}6.2 转移表写法函数指针数组简洁优雅#includestdio.hintadd(inta,intb){returnab;}intsub(inta,intb){returna-b;}intmul(inta,intb){returna*b;}intdiv(inta,intb){returna/b;}intmain(){intx,y;intinput1;intret0;// 转移表函数指针数组下标直接对应菜单编号int(*p[5])(intx,inty){0,add,sub,mul,div};//创建函数指针数组把函数指针存到数组里do{printf(*************************\n);printf( 1:add 2:sub \n);printf( 3:mul 4:div \n);printf( 0:exit \n);printf(*************************\n);printf(请选择);scanf(%d,input);if(input4input1){printf(请输入两个操作数);scanf(%d %d,x,y);ret(*p[input])(x,y);// 通过函数指针调用printf(ret %d\n,ret);}elseif(input0){printf(退出程序\n);}else{printf(输入错误\n);}}while(input);return0;}对比总结方式代码量可扩展性switch-case~60 行每加一个运算多 6 行繁琐转移表~40 行只需在数组中加一个函数指针简洁优雅核心思路用函数指针数组的下标替代switch分支通过(*p[input])(x, y)一行调用对应的函数。总结本讲是函数指针的深入内容重点掌握字符指针char*区分数组与字面量str1 ! str2但str3 str4数组指针int (*p)[10][]优先级高于*加括号才是指针二维数组传参退化int a[3][5]→int (*p)[5]不是二级指针函数指针int (*pf)(int, int)函数名就是地址typedef可简化复杂声明函数指针数组int (*arr[3])()存放多个函数指针转移表用函数指针数组替代switch-case代码更简洁