【C语言】指针(3)——更多指针类型:数组指针、函数指针;回调函数
目录1.字符指针变量一道字符串相关的题2.数组指针变量2.1数组指针变量是什么数组指针变量2.2数组指针变量的初始化3.二维数组传参的本质总结二维数组传参形参可以写成指针形式也可以写成数组形式。4.函数指针变量4.1函数指针变量的创建创建函数指针变量的方法函数指针类型解析4.2函数指针变量的使用4.3两段有趣的代码4.3.1typedef关键字普通类型指针类型数组指针类型函数指针类型5.函数指针数组函数指针数组的定义方法6.转移表1.字符指针变量在指针的类型中有一种指针类型为字符指针char*一般使用方法# includestdio.h int main() { char ch w; char* pc ch; *pc w; return 0; }还有一种使用方法#includestdio.h int main() { const char* pstr hello.; // 定义了一个常量字符串 printf(%s\n, pstr); return 0; }代码const char* pstr hello.;的本质是把字符串hello.的首地址放入了pstr中。上述代码的意思是把一个常量字符串的首字符h的地址存放到指针变量pstr中。一道字符串相关的题#includestdio.h int main() { char str1[] hello.; char str2[] hello.; const char* str3 hello.; const char* str4 hello.; if (str1 str2) { printf(str1 and str2 are same.\n); } else { printf(str1 and str2 are not same.\n); } if (str3 str4) { printf(str1 and str2 are same.\n); } else { printf(str1 and str2 are not same.\n); } return 0; }这道题的输出结果是从运行结果来看可以看出str3和str4的地址是相同的指向同一个字符串。这是因为C/C会把常量字符串存储到一个单独的内存区域当几个指针指向同一个字符串的时候他们实际上会指向同一个内存相同的常量字符串没必要存两次因为常量字符串不能修改但是用同样的字符串去初始化不同的数组时就会开辟不同的内存块所以str1和str2不同st23和str4相同。2.数组指针变量2.1数组指针变量是什么我们知道指针数组是一种数组数组中存放的是指针那么数组指针变量是指针变量还是数组答案数组指针是一个指针变量。整形指针变量int* pint;存放的是整型变量的地址是能够指向整形数据的指针浮点型指针变量float* pf;存放浮点型变量的地址是能够指向浮点型数据的指针。同样的数组指针变量应该是存放数组的地址能够指向数组的指针变量。思考下面两行代码哪个是数组指针变量int* p1[10]; int (*p2)[10];第一行是指针数组存放指针的数组第二行是数组指针存放数组的指针数组指针变量int (*p2)[10];p先和*结合说明p是一个指针p后面的[10]说明指针指向的是一个大小为10个整型的数组。p是一个指针指向一个数组就是数组指针。注意[]的优先级比*高所以要在*p两边加上()保证p先和*结合。2.2数组指针变量的初始化指针变量用来存放数组地址那么如何获取数组的地址呢可以使用数组名int arr[10]; int (*p)[10] arr;存放数组的地址需要存放在数组指针中int (*p)[10] arr; | | | | | | | | p指向的数组的元素个数 | 数组指针的变量名 p指向的数组中存放的元素类型3.二维数组传参的本质之前我们需要完成二维数组传参操作时是这样写的#includestdio.h void test(int arr[3][5], int r, int c) { int i 0; int j 0; for (i 0; i r; i) { for (j 0; j c; j) { printf(%d , arr[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。二维数组传参本质上也是传递了地址传递的是第一行这个一维数组的地址。那么形参也可以写成指针形式如下#includestdio.h void test(int (*arr)[5], int r, int c) { int i 0; int j 0; for (i 0; i r; i) { for (j 0; j c; j) { printf(%d , *(*(arr 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; }总结二维数组传参形参可以写成指针形式也可以写成数组形式。4.函数指针变量4.1函数指针变量的创建函数指针变量是用来存放函数地址的未来通过地址能够调用函数。总结二维数组传参形参可以写成指针形式也可以写成数组形式。 4.函数指针变量 4.1函数指针变量的创建 函数指针变量是用来存放函数地址的未来通过地址能够调用函数。通过函数结果可以发现函数名和函数名都是函数的地址。要将函数的地址存放起来就需要创建函数指针变量。创建函数指针变量的方法#includestdio.h void test() { ; } void (*pf1)() test; void (*pf2)() test; int Add(int x, int y) { return x y; } int (*pf3)(int, int) Add; int (*pf4)(int x, int y) Add; // 形参中的x和y写不写都可以函数指针类型解析int (*pf4)(int x, int y) Add; | | ------------ | | | | | pf4指向的函数的参数类型和个数 | 函数指针变量名 pf4指向的函数的返回类型 int (*) (int x, int y) // pf4函数指针变量的类型4.2函数指针变量的使用通过函数指针调用指针指向的函数#includestdio.h int Add(int x, int y) { return x y; } int main() { int (*pf)(int, int) Add; printf(%d\n, (*pf)(2, 3)); printf(%d\n, pf(3, 5)); return 0; }运行结果4.3两段有趣的代码代码1(*(void(*)())0)();这句话的意思是将0强制类型转换成void(*)()类型的函数指针再对其解引用。假设0地址处放着无参返回类型为void的函数最终调用0地址处的函数。代码2void (*signal(int,void(*)(int)))(int);相当于一个函数声明声明了一个返回类型为void(*)(int)的signal(int,void(*)(int))函数4.3.1typedef关键字typedef是用来给类型重命名的给类型起别名可以将复杂的类型简单化。普通类型比如如果感觉unsigned int写起来不方便就可以重命名为uinttypedef unsigned int uint; // 将unsigned重命名为uint指针类型例如将int*重命名为ptr_ttypedef int* ptr_t;数组指针类型例如将int(*)[5]重命名为parr_ttypedef int(*parr_t)[5];这里新的类型名必须在*的右边函数指针类型例如将void(*)(int)类型重命名为pf_ttypedef void(*pfun_t)(int); // 新的类型名必须在*的右边可以简化上述的代码2typedef void(*pfun_t)(int); pfun_t signal(int,pfun_t);5.函数指针数组数组是一个存放相同类型数据的存储空间之前我们学习了指针数组int *arr[10]; // 数组的每一个元素都是int*那么同理把函数的地址存到一个数组中这个数组就叫函数指针数组。函数指针数组的定义方法int (*parr1[3])();parr1先和[]结合说明parr1是数组数组的内容是int(*)()类型的函数指针。6.转移表函数指针的用途转移表一般情况下我们要完成一个计算器是这样写#includestdio.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:menu \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; }可以发现switch语句中的语句存在重复使用函数指针数组实现#includestdio.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:menu \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; }7.回调函数回调函数就是一个通过函数指针调用的函数如果把函数的指针地址作为参数传给另一个函数当这个指针被用来调用其所指向的函数时被调用的函数就是回调函数。回调函数不是由该函数的实现方直接调用而是在特定的事件或条件发生时由另外的一方调用的用于对该事件或条件进行相应。在深入理解指针3中最后的计算器代码中有很多重复出现的冗余代码因为这些重复代码只有调用函数的部分是不同的所以可以把调用的函数的地址以参数的形式传递过去使用函数指针接收函数指针指向什么函数就调用什么函数使用的就是回调函数的功能。#includestdio.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:menu \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; }上述代码中switch语句基本都是重复代码我们使用回调函数对代码进行修改#includestdio.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; } void calc(int(*pf)(int, int)) { int ret 0; int x, y; printf(请输入操作数); scanf(%d %d, x, y); ret pf(x, y); printf(ret %d\n, ret); } 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:menu \n); printf(*************************\n); printf(请选择); scanf(%d, input); switch (input) { case 1: calc(add); break; case 2: calc(sub); break; case 3: calc(mul); break; case 4: calc(div); break; case 0: printf(退出程序\n); break; default: printf(选择错误\n); break; } } while (input); return 0; }8.qsortqsort是一个库函数底层使用的快速排序qsort的定义和参数void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void *));它包含四个参数base: 指向待排序数组的第一个元素的指针。nitems: 数组中的元素数量。size: 数组中每个元素的大小以字节为单位。compar: 比较函数的指针用于比较两个元素。比较函数应当返回一个整数表示比较结果。8.1使用qsort排序整型数据#includestdio.h #include cstdlib 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; }8.2使用qsort排序结构数据#includestdio.h #include cstdlib #includestring struct Stu { char name[20]; int age; }; // 假设按照年龄来比较 int cmp_stu_by_age(const void* e1, const void* e2) { return ((struct Stu*)e1)-age - ((struct Stu*)e2)-age; } // strcmp是库函数是专门用来比较两个字符串大小的 // 假设按照名字来比较 int cmp_stu_by_name(const void* e1, const void* e2) { return strcmp(((struct Stu*)e1)-name,((struct Stu*)e2)-name); } // 按照年龄排序 void test2() { struct Stu s[] { {zhangsan,20},{lisi,30},{wangwu,15} }; int sz sizeof(s) / sizeof(s[0]); qsort(s, sz, sizeof(s[0]), cmp_stu_by_age); } // 按照名字排序 void test3() { struct Stu s[] { {zhangsan,20},{lisi,30},{wangwu,15} }; int sz sizeof(s) / sizeof(s[0]); qsort(s, sz, sizeof(s[0]), cmp_stu_by_name); } int main() { test2(); test3(); return 0; }9.qsort的模拟实现使用回调函数模拟实现qsort采用冒泡排序void*的指针是无具体类型的指针它的作用是接受任何类型的地址#includestdio.h int int_cmp(const void* p1, const void* p2) { return (*(int*)p1 - *(int*)p2); } void _swap(void* p1, void* p2, int size) { int i 0; for (i 0; i size; i) { char tmp *((char*)p1 i); *((char*)p1 i) *((char*)p2 i); *((char*)p2 i) tmp; } } void bubble(void* base, int count, int size, int(*cmp)(const void*, const void*)) { int i 0; int j 0; for (i 0; i count - 1; i) { for (j 0; j count - i - 1; j) { if (cmp((char*)base j * size, (char*)base (j 1) * size) 0) { _swap((char*)base j * size, (char*)base (j i) * size, size); } } } } int main() { int arr[] { 1,3,5,7,9,2,4,6,8,0 }; int i 0; bubble(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; }完