深入理解指针(4)
1. 字符指针变量在指针的类型中我们知道有⼀种指针类型为字符指针 char* ;⼀般使⽤:int main() { char ch w; char *pc ch; *pc w; return 0; }还有⼀种使⽤⽅式如下int main() { const char* pstr hello bit.;//这⾥是把⼀个字符串放到pstr指针变量⾥了吗 printf(%s\n, pstr); return 0; }代码 const char* pstr hello bit.; 特别容易让同学以为是把字符串 hello bit 放 到字符指针 pstr ⾥了但是本质是把字符串 hello bit. ⾸字符的地址放到了pstr中。上⾯代码的意思是把⼀个常量字符串的⾸字符 h 的地址存放到指针变量 pstr 中。《剑指offer》中收录了⼀道和字符串相关的笔试题我们⼀起来学习⼀下#include stdio.h int main() { char str1[] hello bit.; char str2[] hello bit.; const char *str3 hello bit.; const char *str4 hello bit.; if(str1 str2) printf(str1 and str2 are same\n); else printf(str1 and str2 are not same\n); if(str3 str4) printf(str3 and str4 are same\n); else printf(str3 and str4 are not same\n); return 0; }这⾥str3和str4指向的是⼀个同⼀个常量字符串。C/C会把常量字符串存储到单独的⼀个内存区域 当⼏个指针指向同⼀个字符串的时候他们实际会指向同⼀块内存。但是⽤相同的常量字符串去初始 化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同str3和str4相同。2. 数组指针变量2.1 数组指针变量是什么之前我们学习了指针数组指针数组是⼀种数组数组中存放的是地址指针。数组指针变量是指针变量还是数组答案是指针变量。我们已经熟悉• 整形指针变量 int * pint; 存放的是整形变量的地址能够指向整形数据的指针。• 浮点型指针变量 float * pf; 存放浮点型变量的地址能够指向浮点型数据的指针。那数组指针变量应该是存放的应该是数组的地址能够指向数组的指针变量。下⾯代码哪个是数组指针变量int * p1[10];int (*p2)[10];思考⼀下p1,p2分别是什么数组指针变量int (*p)[10];解释p先和*结合说明p是⼀个指针变量然后指针指向的是⼀个⼤⼩为10个整型的数组。所以p是 ⼀个指针指向⼀个数组叫数组指针。这⾥要注意[]的优先级要⾼于*号的所以必须加上来保证p先和*结合。2.2 数组指针变量怎么初始化数组指针变量是⽤来存放数组地址的那怎么获得数组的地址呢就是我们之前学习的 数组名int arr[10] {0};arr;//得到的就是数组的地址如果要存放个数组的地址就得存放在数组指针变量中如下int(*p)[10] arr;我们调试也能看到 arr 和 p 的类型是完全⼀致的。数组指针类型解析int (* p) [10] arr; | | | | | | | | p指向数组的元素个数 | p是数组指针变量名 p指向的数组的元素类型3. ⼆维数组传参的本质有了数组指针的理解我们就能够讲⼀下⼆维数组传参的本质了。过去我们有⼀个⼆维数组的需要传参给⼀个函数的时候我们是这样写的#include stdio.h void test(int a[3][5], int r, int c) { int i 0; int j 0; for(i0; ir; i) { for(j0; jc; j) { printf(%d , a[i][j]); } printf(\n); } } int main() { int arr[3][5] {{1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7}}; test(arr, 3, 5); return 0; }这⾥实参是⼆维数组形参也写成⼆维数组的形式那还有什么其他的写法吗⾸先我们再次理解⼀下⼆维数组⼆维数组其实可以看做是每个元素是⼀维数组的数组也就是⼆维 数组的每个元素是⼀个⼀维数组。那么⼆维数组的⾸元素就是第⼀⾏是个⼀维数组。如下图所以根据数组名是数组⾸元素的地址这个规则⼆维数组的数组名表⽰的就是第⼀⾏的地址是⼀ 维数组的地址。根据上⾯的例⼦第⼀⾏的⼀维数组的类型就是 int [5] 所以第⼀⾏的地址的类 型就是数组指针类型 int(*)[5] 。那就意味着⼆维数组传参本质上也是传递了地址传递的是第⼀ ⾏这个⼀维数组的地址那么形参也是可以写成指针形式的。如下#include stdio.h void test(int (*p)[5], int r, int c) { int i 0; int j 0; for(i0; ir; i) { for(j0; jc; j) { printf(%d , *(*(pi)j)); } printf(\n); } } int main() { int arr[3][5] {{1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7}}; test(arr, 3, 5); return 0; }总结⼆维数组传参形参的部分可以写成数组也可以写成指针形式。4. 函数指针变量4.1 函数指针变量的创建什么是函数指针变量呢根据前⾯学习整型指针数组指针的时候我们的类⽐关系我们不难得出结论函数指针变量应该是⽤来存放函数地址的未来通过地址能够调⽤函数的。那么函数是否有地址呢我们做个测试#include stdio.h void test() { printf(hehe\n); } int main() { printf(test: %p\n, test); printf(test: %p\n, test); return 0; }输出结果如下test: 005913CAtest: 005913CA确实打印出来了地址所以函数是有地址的函数名就是函数的地址当然也可以通过 函数名 的⽅ 式获得函数的地址。如果我们要将函数的地址存放起来就得创建函数指针变量咯函数指针变量的写法其实和数组指针 ⾮常类似。如下void test() { printf(hehe\n); } void (*pf1)() test; void (*pf2)() test; int Add(int x, int y) { return xy; } int(*pf3)(int, int) Add; int(*pf3)(int x, int y) Add;//x和y写上或者省略都是可以的函数指针类型解析int (* pf3) (int x, int y) | | ------------ | | | | | pf3指向函数的参数类型和个数的交代 | 函数指针变量名 pf3指向函数的返回类型 int (*) (int x, int y) //pf3函数指针变量的类型4.2 函数指针变量的使⽤通过函数指针调⽤指针指向的函数。#include stdio.h int Add(int x, int y) { return xy; } int main() { int(*pf3)(int, int) Add; printf(%d\n, (*pf3)(2, 3)); printf(%d\n, pf3(3, 5)); return 0; }输出结果584.3 两段有趣的代码代码1(*(void (*)())0)();代码解析1. 上述代码其实是⼀次函数调⽤调⽤的是0地址处的⼀个函数这个函数没有参数没有返回值。2. 代码中的 void (*)() 是函数指针类型 (void (*)())0 类型放在括号中意思是强制类型转 化是将0这个整型值强制类型转化成这种函数指针类型也就是说0被当做函数的地址了。3. *(void (*)())0 前⾯加⼀个 * 就是调⽤0地址处的这个函数根据函数指针的类型能知 道这个函数没有参数也没有返回值。代码2void (*signal(int , void(*)(int)))(int);代码解析1. 上述代码是⼀次函数的声明。2. 声明的函数名字叫 signal 函数的参数有2个第⼀个是int类型第⼆个是函数指针类 型: void(*)(int) 。函数的返回值也是函数指针 void(*)(int) 。两段代码均出⾃《C陷阱和缺陷》这本书4.3.1 typedef关键字 typedef是⽤来类型重命名的可以将复杂的类型简单化。⽐如你觉得 unsigned int 写起来不⽅便如果能写成 uint 就⽅便多了那么我们可以使⽤typedef unsigned int uint;//将unsigned int 重命名为uint如果是指针类型能否重命名呢其实也是可以的⽐如将 int* 重命名为 ptr_t ,这样写typedef int* ptr_t;但是对于数组指针和函数指针稍微有点区别⽐如我们有数组指针类型 int(*)[5] ,需要重命名为 parr_t 那可以这样写1 typedef int(*parr_t)[5]; //新的类型名必须在*的右边函数指针类型的重命名也是⼀样的⽐如将 void(*)(int) 类型重命名为 pf_t ,就可以这样写typedef void(*pfun_t)(int);//新的类型名必须在*的右边那么要简化代码2可以这样写:typedef void(*pfun_t)(int);pfun_t signal(int, pfun_t);5. 函数指针数组数组是⼀个存放相同类型数据的存储空间我们已经学习了指针数组⽐如int * arr[10];//数组的每个元素是int*那要把函数的地址存到⼀个数组中那这个数组就叫函数指针数组那函数指针的数组如何定义呢int (*parr1[3])();int *parr2[3]();int (*)() parr3[3];答案是parr1parr1 先和 [] 结合说明parr1是数组数组的内容是什么呢是 int (*)() 类型的函数指针。6. 转移表函数指针数组的⽤途转移表举例计算器的⼀般实现#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( 0:exit \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); break; default: printf(选择错误\n); break; } } while (input); return 0; }使⽤函数指针数组的实现#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 }; //转移表 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 ((input 4 input 1)) { printf( 输⼊操作数 ); scanf( %d %d, x, y); ret (*p[input])(x, y); printf( ret %d\n, ret); } else if(input 0) { printf(退出计算器\n); } else { printf( 输⼊有误\n ); } }while (input); return 0; }