【C语言】指针的入门介绍

本文涉及的产品
对象存储 OSS,20GB 3个月
对象存储 OSS,恶意文件检测 1000次 1年
对象存储 OSS,内容安全 1000次 1年
简介: 指针是c语言的重中之重

# 前言 指针是c语言的重中之重 --- # (一)指针初阶: --- ### 一 : 什么是指针 **其实指针有两层含义:** 1.**指针是地址**是一层含义 2.当地址或者指针需要存起来的时候,我们需要一个**指针变量**,**这个指针变量是存放地址的**,我们经常说的“指针“,”指针“,**其实是指针变量,指针变量也被称为指针** --- ### 二 : 什么是野指针 > **概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)** 简单的说:你在大街上看见一条狗,那个狗没有栓链子,没有主人,我们称它为野狗,指针也是一样,不知道指向内存的什么地址,就被称为野指针。 #### 什么情况下会造成野指针呢? **1 . 指针未初始化** ```c #include int main() { int* p;//局部变量指针未初始化,默认为随机值 *p = 20; return 0; } ``` 上面的代码意思可以理解为:你看见了一个酒店,你进去了,随便找了个房间就睡觉了,并没有去前台取得自己的一个房卡。 **2 . 指针越界访问** ```c #include int main() { int arr[10] = {0}; int *p = arr; int i = 0; for(i=0; i<=11; i++) { //当指针指向的范围超出数组arr的范围时,p就是野指针 *(p++) = i; } return 0; } ``` 这里画一个图表示一下: ![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/20210613112538397.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ2ODc0MzI3,size_16,color_FFFFFF,t_70) arr数组就给了10个空间,只到下标九,但是上面的for循环循环了11次,可以得出已经访问超过我们的arr数组大小了所以会出现指针越界访问的情况。 ![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/20210613112906666.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ2ODc0MzI3,size_16,color_FFFFFF,t_70) **3 . 指针指向的空间释放** ```c int* test() { int a = 10; //(1)第一次进来创建a 出去test函数 空间给操作系统 return &a; } int main() { int *p= test(); *p = 20; //(2)还是第一次的地址 ,但是出来了第一次的地址已经还回去了, //(3)这时候进行赋值,会造成非法访问 p野指针 return 0; } ``` 来个例子:你和你女朋友分手了,但是你还有女朋友的电话,你就三番两次打电话给她,但是她已经不是你女朋友了,你这样就属于非法骚扰了..... --- #### 如何避免野指针 **1.指针初始化** > 要养成好的编程习惯,当变量不知道赋什么的话,可以先赋个0,或者相对应类型的值 **int a = 0;** 指针不知道应该初始化成什么值的时候,建议直接初始化为NULL。 ```c #include int main() { int* p = NULL; //NULL要引用头文件 return 0; } ``` **2.小心越界** > **C语言本身不会检查数组的越界,要我们自己保存头脑清晰** --- **3.指针指向的空间释放及时置NULL** > **要使用的代码被释放了,及时置NULL** **4.指针使用之前检查有效性** 先来看看下面的代码可以运行起来不? ```c int main() { int* p = NULL; *p = 10; return 0; } ``` ![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/20210624143102560.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ2ODc0MzI3,size_16,color_FFFFFF,t_70) 答案是:不可以的,为什么呢? NULL其实是一个地址位置是在0处的一个地址,这个地址在操作系统里,没有分配给用户,所以不可以把10放进去赋值。 ![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/20210624143257284.png) 那么怎么规避呢?可以在使用之前先判断一下 ```c int main() { int* p = NULL; if (p!= NULL) //不是空才继续使用,增加代码的健壮性 { *p = 10; } return 0; } ``` > 上面的方法讲完就不会遇见野指针了吗?其实不然,你要写BUG谁也难不住,教的是怎么避免少写,养成好习惯!!! --- ### 三 : 指针的运算 - 指针+- 整数 - 指针-指针 - 指针的关系运算 #### 1.指针的加减整数运算 代码包含指针加减整数和指针的关系运算 ```c #define N_VALUES 5 float values[N_VALUES]; float *vp; //指针+-整数;指针的关系运算 for (vp = &values[0]; vp < &values[N_VALUES];) { *vp++ = 0; } ``` 上面的代码大家可以试着看一下,下面有解析: **解析:** ![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/2021062414522525.png) 定义了一个值为5 --- ![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/20210624145257659.png) 定义了一个数组长度为5,下面是名为values的数组 ![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/20210624145420737.png) ![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/20210624145502547.png) 指针变量 vp --- for (vp = &values[0]; vp < &values[N_VALUES] ; ) { *vp++ = 0; } ![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/20210624145757166.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ2ODc0MzI3,size_16,color_FFFFFF,t_70) --- vp < &values[N_VALUES] 小于第5个元素 ![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/20210624150035953.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ2ODc0MzI3,size_16,color_FFFFFF,t_70) --- *vp++ = 0; vp指向的是第一个数组下标为0的,条件是小于数组第5个元素就停止,所以最后代码全部运行起来,**就是把数组0-4的下标全部赋为0** ![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/20210624150632906.png) --- >随着数组下标的增长, 地址其实是由低到高变化的 ![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/20210624150957390.png) --- #### 2.指针 - (减)指针 问:下面代码显示的是什么? ```c int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; printf("%d\n",&arr[9] -&arr[0]); return 0; } ``` ![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/20210624152714587.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ2ODc0MzI3,size_16,color_FFFFFF,t_70) **答案:9** **解析:** > **指针减指针 得到的两个指针之间的元素个数** > **指针和指针相减的前提是:两个指针指向的同一个空间** &arr[9] 和&arr[0] 分别指向的是,他们之间有9个元素,所以是9 ![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/20210624153944817.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ2ODc0MzI3,size_16,color_FFFFFF,t_70) --- > **指针和指针相减的前提是:两个指针指向的同一个空间** ```c int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; char c[5]; printf("%d\n",&arr[9] -&c[0]);//e return 0; } ``` 上面代码块是错误的。 --- #### 3.使用指针减指针模仿写一个strlen 我们都知道指针减指针就可以得到元素个数,那么来看一下接下来的模仿strlen函数的思路吧! ![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/20210626163319780.png) 找到a的地址,和 \0的地址(每一个字符串后面都有一个隐藏的\0),让它们相减就可以求出字符串的长度了 下面放代码: ```c int my_strlen(char* str) { char* start = str; //保存一开始的地址 start 里面是 a的地址 while (*str != '\0') // 如果str不会最后一个\0就不会停下来 { str++; //一直加到\0 } return str - start; // '\0' - 'a' 指针减指针 } int main() { int a = my_strlen("abc"); printf("%d\n",a); // 打印3 return 0; } ``` --- ### 四 : 指针和数组 > 想学好指针,你必须先知道数组名是什么!! > **数组名是数组首元素地址** 代码来验证一下: ```c int main() { int arr[10] = { 0 }; printf("%p\n",arr); //数组名是首元素地址 printf("%p\n",&arr[0]); //取第一个元素的地址 return 0; } ``` **果然地址是一样的!** ![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/20210626170513494.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ2ODc0MzI3,size_16,color_FFFFFF,t_70) --- 那么数组名是一个地址,是不是也可以拿指针来接收 ```c int arr[10] = { 0 }; int* p = arr; ``` 看下面的代码: **既然arr是首元素地址 ,但是可以拿p指针来接收, 是不是 (arr == p)呢?** 跑一下下面的代码: ```c int main() { int arr[10] = { 0 }; int* p = arr; int i = 0; for ( i = 0; i < 10; i++) { printf("%p <==> %p <==> %p \n",&arr[i] , p+i ,arr+i); //p+i 访问第i个元素地址 } return 0; } ``` 结果:**好家伙3个地址一模一样,所以上面的问题是成立的** ![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/2021062617192838.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ2ODc0MzI3,size_16,color_FFFFFF,t_70) 有了上面的例子,我们可以推导出来一个更加有趣的代码例子: **(加法支持加法交换律)** ```bash int arr[10] = {1,2,3,4,5,6,7,8,9,10}; int* p = arr; arr[2] <==> *(arr+2) <==> *(2+arr) <==> 2[arr] *(p+2) <==> *(2+P) <==> 2[p] <==> p[2] ``` 以上都是可以支持打印出来正确的数字,**但是为什么呢?** > [ ]是一个操作符 arr[ 2] , arr 和 2 是两个操作数 **其实 arr [ 2 ] 这种写法编译器最终会把它转换为** *(arr+2) ,**arr表首元素地址 加上2 就是跳过2个元素 解引用就得到最终的结果了** --- ### 五 : 二级指针 > **指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里?** 看看下面代码: ```c int main() { int a = 10; //为什么要int*呢 int *pa = &a; //a是int类型 *pa表示pa是个指针变量 所以是int* int* *ppa = &pa; //int* 是因为pa是int*类型 *ppa 表示ppa是个指针 return 0; } ``` 有个a变量,在内存开辟一个内存空间,地址是0x11223344 int a = 10; ![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/20210627154445374.png) int* pa = &a; **把a的地址放在pa的内存空间里面**。 pa的地址是**0x44332211** ![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/20210627154735789.png) int** ppa = &pa; **把pa的地址放在ppa的内存空间里面**。 ppa的地址是**0x22334411**![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/20210627155931357.png) 为什么叫二级指针呢,因为指向的有两层关系,**比如我现在要修改 a 的值要怎么修改呢?** \**ppa == *(*pa) == 把*pa解引用 自然可以修改到值 ![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/20210627161012703.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ2ODc0MzI3,size_16,color_FFFFFF,t_70) --- ### 六 : 指针数组 > **指针数组是指针还是数组?** > **答案:是数组。是存放指针的数组。** **int arr1[5]:** 整形数组 -存放整形的数组就是整形数组 ![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/20210627162128340.png) **char arr2[6]:** 字符数组 - 存放的是字符 ![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/20210627162219448.png) int* arr3[5]:整形指针数组: **arr3是一个数组,有五个元素,每个元素是一个整形指针。** ![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/20210627162320370.png) --- # (二)指针进阶: ### 一 : 字符指针 在指针的类型中我们知道有一种指针类型为字符指针 char*, 一般是这样使用的: ```c int main() { char ch = 'w'; char *pc = &ch; *pc = 'w'; return 0; } ``` 还有一种使用方式如下: **把字符串放在字符指针变量中。** ```c int main() { char* pstr = "hello world";//这里是把一个字符串放到pstr指针变量里了吗? printf("%s\n", pstr); return 0; } ``` **那么出现一个问题**:char* 就算是在64位的机器是最多也是存8个字节啊,**把hello world放进去可以放的下吗?** 其实:关于上面的问题,解释是:**本质上是把字符串的首字符地址存储在了pstr中**,可以代码验证一下:如果是第一个字符那打印出来的是 h ![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/20210627171027811.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ2ODc0MzI3,size_16,color_FFFFFF,t_70) 果然一模一样!! --- 来一个练题: ```c #include int main() { char str1[] = "hello bit."; char str2[] = "hello bit."; char* str3 = "hello bit."; 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; } ``` 输出什么? > 答案:![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/20210706145925575.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ2ODc0MzI3,size_16,color_FFFFFF,t_70) 为什么呢?来看下面的解析 首先我们可以看到可以有4个变量,先看一下str1 和str2 . ![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/20210706150502329.png) 它们是两个不相同的数组,所以分别指向**不同的地址**。 来看看str3和str4,因为str3 和str4存放的是**常量字符**,常量字符是不可以被改变的,所以在内存存放的是同一个内容,**2个变量同时指向同一个地址**一起使用,所以应该是下图所示: ![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/20210706151042633.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ2ODc0MzI3,size_16,color_FFFFFF,t_70) 所以最后就是上面的那个结果,对于这道题目的最大争议就是 **str3 和str4存的变量是不是常量变量不可以被修改**,我们可以去编译器看一下: ![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/20210706151401791.png) **可以看见这里是报错的,所以说我们上面所述的如实。** --- ### 二: 指针数组 > **指针数组本质上是数组,数组中存放的是指针(地址)** 应用场景: ```c int main() { int a[3] = { 1,2,3 }; int b[] = { 4,5,6 }; int* c[3] = {a,b}; int i = 0; for ( i = 0; i < 2; i++) { int j = 0; for ( j = 0; j < 3; j++) { printf("%d ",c[i][j]); } printf("\n"); } return 0; } ``` --- ### 三 : 数组指针 > 数组指针是指针?还是数组? 答案是:指针。 下面代码哪个是数组指针? ```c int *p1[10]; int (*p2)[10]; //p1, p2分别是什么? ``` 解释: > p1是指针数组 > p2是数组指针, > 解释:p2先和*结合,说明p2是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个 指针,指向一个数组,叫数组指针。 这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合 --- #### &数组名VS数组名 对于下面的数组: > int arr[10]; arr 和 &arr 分别是啥? 我们知道arr是数组名,数组名表示数组首元素的地址。 那&arr数组名到底是啥? 我们看一段代码: ```c #include int main() { int arr[10] = {0}; printf("%p\n", arr); printf("%p\n", &arr); return 0; } ``` ![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/20210706164824141.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ2ODc0MzI3,size_16,color_FFFFFF,t_70) 可见数组名和&数组名打印的地址是一样的。 难道两个是一样的吗? 我们再看一段代码: ```c #include int main() { int arr[10] = { 0 }; printf("arr = %p\n", arr); printf("&arr= %p\n", &arr); printf("arr+1 = %p\n", arr+1); printf("&arr+1= %p\n", &arr+1); return 0; } ``` ![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/20210706164956797.png) > 根据上面的代码我们发现,其实&arr和arr,虽然值是一样的,但是意义应该不一样的。 实际上: **&arr 表示的是数组的地址**,而不是数组首元素的地址。(细细体会一下) **数组的地址+1,跳过整个数组的大小**,所以 &arr+1 相对于 &arr 的差值是40. **数组名是数组首元素的地址** 但是有2个例外: 1.sizeof(数组名) - **数组名表示整个数组,计算的是整个数组的大小,单位是字节** 2.&数组名 - **数组名表示整个数组 ,取出的是整个数组的地址** --- ### 四 : 一维数组传参、二维数组传参 **数组的传参,使用以下形式都可以** #### (1):一维数组传参 ```c #include void test(int arr[])//ok {} //不在[]里面写10,是因为传过来的是首元素地址,并不会真的开辟一个10个数组的大小,可省,写了也不影响 void test(int arr[10])//ok {} //同上 void test(int *arr)//ok {} //传过来的是首元素地址,所以可以拿1级指针。 void test2(int *arr[20])//ok {} void test2(int **arr)//ok {} int main() { int arr[10] = {0}; int *arr2[20] = {0}; test(arr); test2(arr2); } ``` 这里解释一下为什么 void test2(int **arr) 也可以: ![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/20210711175832223.png) arr里面的类型都是1级指针,所以放在2级指针存放没有什么不合适。 --- #### (2):二维数组传参 **二维数组,首元素是第一行地址** ```c void test(int arr[3][5])//ok {} //实参是2维数组, void test(int arr[][])//no {} //可以省略行,不可以省略列 void test(int arr[][5])//ok {} //只省略了行,没有省略列 //总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。 //因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。 //这样才方便运算。 void test(int *arr)//no {} //这个是传过来第一行的地址 void test(int* arr[5])//ok? {} void test(int (*arr)[5])//ok? {} //arr是一个指针,指向的是5个int类型,实参传过来的是首元素地址,也就是第一行地址,第一行地址是int类型,所以可以 void test(int **arr)//ok {} //因为传过来的压根不是首元素地址,是第一行地址,是一维数组的地址,所以不可以拿二维指针接收。 int main() { int arr[3][5] = {0}; test(arr); } ``` #### (3):一级指针传参 ```c #include void print(int *p, int sz) { int i = 0; for(i=0; i 当一个函数的参数部分为一级指针的时候,函数能接收什么参数? 比如: ```c void test1(int *p) {} int a = 10; int* pa = &a; //test1函数能接收什么参数? //答: test1(&a); test1(a); void test2(char* p) {} char a='w' ; char* pa = &a; //test2函数能接收什么参数? //答: test2(&a); test2(a); ``` --- #### (4):二级指针传参 列: ```c #include void test(int** ptr) { printf("num = %d\n", **ptr); } int main() { int n = 10; int*p = &n; int **pp = &p; int * arr[10] = {0}; test(pp); //传二级指针变量 可以 test(&p);//传一级指针变量的地址 可以 test(arr);//传存放一级指针的数组 可以 return 0; } ``` --- ### 五 : 函数指针 > 函数指针:**指向函数的指针! 存放函数地址的指针。** 先看一段代码: 打印test函数的地址,和取地址打印test函数的地址,它们一样吗? ```c #include void test() { printf("hehe\n"); } int main() { printf("%p\n", test); printf("%p\n", &test); return 0; } ``` 看结果:发现他们的地址都是一样的。 ![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/2021071515545490.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ2ODc0MzI3,size_16,color_FFFFFF,t_70) 现在出来一个结论: > 数组 arr != &arr > **一个是数组首元素的地址 和整个数组的地址** > 函数 test == &test > **函数不存在什么首元素地址,函数名即是取函数地址** 问:那我们的函数的地址要想保存起来,怎么保存?来个列子: ```c int add(int x, int y) { return x + y; } int main() { int ret = add(3, 5); int (*pret) (int, int) = &add; //存放函数的指针 //返回类型 (*变量名) (函数类型) int prt1 = (*pret)(3, 5); int prt2 = pret(3,5); int pt = add(3, 5); //ok 通过函数名 printf("%d\n", prt1); // ok printf("%d\n", prt2); //ok printf("%d\n", pt); //ok return 0; } ``` 上面代码输出结果: ![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/20210715170847198.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ2ODc0MzI3,size_16,color_FFFFFF,t_70) > 上面那么多代码:其实发现**prt1** 和 **prt2** 一个加了*,一个没有加*,但是结果都是一样的,**其实那颗\* 是一个摆设**,只是让我们更加好理解,你前面也可以加5颗\* ,最后的结果是一样的 。 ![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/2021071517121652.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ2ODc0MzI3,size_16,color_FFFFFF,t_70) 函数指针语法: **返回类型 (\[*]变量名) (函数类型)** 自己尝试写写下面列子: ```c void test(char* str) { } int main() { pt = &test; //pt该怎么写? return 0; } ``` 结果:void (*pt) (char\*) --- 阅读两段有趣的代码:请读懂这两段代码的意思? ```c //代码1 (*(void (*)())0)(); //代码2 void (*signal(int , void(*)(int)))(int); ``` 解析代码1:(整个代码的意思是:**调用了0地址处的函数**,**该参数返回类型是void**) ,一步一步来看: > (1)void (*)() **是一个函数指针类型:** > (2)**(** void (\*)() **)** 0 **:把0强制转换类型,被解释是一个函数地址** > (3)**(**\* void (\*)() **)** 0 **:对0地址处进行了解引用操作** > (4)**(**\* void (\*)() **)** 0() **:调用了0地址处的函数** 本题出自:《C陷阱和缺陷》 ![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/20210716160911243.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ2ODc0MzI3,size_16,color_FFFFFF,t_70) 解析代码2:整个代码是 **函数参数的声明** void (*signal(int , void(*)(int)))(int); **把这个代码拆分开,好理解点** ![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/20210716164327204.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ2ODc0MzI3,size_16,color_FFFFFF,t_70) > (1)signal先和( )结合,说明signal是函数名 > (2)signal函数的第一个参数是int类型,第二个参数是函数指针, > 该函数指针,指向一个参数为int,返回类型是void > (3)signal函数的返回类型也是一个函数指针 > 该函数指针,指向一个参数为int,返回类型是void > **singal是一个函数声明** 代码2太复杂,如何简化:其实我们可以把 void(*)(int) 这个类型,使用typedef > typedef void(*pfun_t)(int); > 简化前: void (*signal(int , void(*)(int)))(int); > 简化后:pfun_t signal(int, pfun_t); --- ### 六 : 函数指针数组 > 我们都知道数组是存放同类型数据的存储空间,那我们已经学习了指针数组, 比如:int *arr[10]; //数组的每个元素是int* 那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢? ```c int (*parr1[10])() ``` 怎么理解这个代码:首先parr1先和[ ]结合说明是一个数组 ```c int (*)() ``` 去掉中间的内容,就是int (*)()类型的函数指针。 问:**那么函数指针数组有什么用呢?** 函数指针数组的用途:转移表 使用案列:**未使用函数指针数组,代码冗余** ```c #include 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("*************************\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; } ``` 使用案列:**使用函数指针数组,代码简洁** ```c int add(int x, int y) { return x + y; } int sub(int x, int y) { return x - y; } int mul(int x, int y) { return x * y; } int div(int x, int y) { return x / y; } void menu() { printf("************"); printf("***1.ADD***\n"); printf("***2.SUB***\n"); printf("***3.MUL***\n"); printf("***4.DIV***\n"); printf("***0.exit***\n"); } int main() { int input = 0; int ret = 0; int x, y; int(*pfArr[5])(int , int ) = { NULL, add,sub,mul,div}; do { menu(); printf("请选择菜单"); scanf("%d", &input); if (input>=1 && input<= 4) { printf("请输入2个数字"); scanf("%d %d",&x , &y); ret = (*pfArr[input])( x , y); //找到执行函数的地址,必须要参数一样的才可以这样使用。 printf("ret = %d\n", ret); } else { printf("输入错误\n"); } } while (input); return 0; } ``` --- ### 七 : 指向函数指针数组的指针 > 指向函数指针数组的指针是一个 **指针** 指针指向一个 **数组** ,数组的元素都是 **函数指针** ```c int(*P) (int, int) //函数指针 int(*P2[5]) (int, int) //函数指针数组 int(*p(*P3)[5])(int ,int ) =&p2 ;//指向函数指针数组的指针 ``` ```c void test(const char* str) { printf("%s\n", str); } int main() { //函数指针pfun void (*pfun)(const char*) = test; //函数指针的数组pfunArr void (*pfunArr[5])(const char* str); pfunArr[0] = test; //指向函数指针数组pfunArr的指针ppfunArr void (*(*ppfunArr)[10])(const char*) = &pfunArr; return 0; } ``` 这部分做个了解,不必太深入。 --- ### 七 : 回调函数 > 回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。 还是上面的 计算器代码: ```c int add(int x, int y) { return x + y; } int sub(int x, int y) { return x - y; } int mul(int x, int y) { return x * y; } int div(int x, int y) { return x / y; } int calu(int(*(*pf)(int,int))) //把函数地址当参数 { int x, y; printf("输入操作数:"); scanf("%d %d", &x, &y); return (*pf)(x, y); } int main() { int input = 1; int ret = 0; do { printf("*************************\n"); printf(" 1:add 2:sub \n"); printf(" 3:mul 4:div \n"); printf("*************************\n"); printf("请选择:"); scanf("%d", &input); switch (input) { case 1: ret = calu(add); //把函数地址传过去 printf("ret = %d\n", ret); break; case 2: ret = calu(sub); printf("ret = %d\n", ret); break; case 3: ret = calu(mul); printf("ret = %d\n", ret); break; case 4: ret = calu(div); printf("ret = %d\n", ret); break; case 0: printf("退出程序\n"); break; default: printf("选择错误\n"); break; } } while (input); return 0; } ``` --- #### (1):模拟实现qsort函数
相关实践学习
借助OSS搭建在线教育视频课程分享网站
本教程介绍如何基于云服务器ECS和对象存储OSS,搭建一个在线教育视频课程分享网站。
相关文章
|
4天前
|
安全 C语言
【C语言】如何规避野指针
【C语言】如何规避野指针
22 0
|
4天前
|
C语言
C语言:数组和指针笔试题解析(包括一些容易混淆的指针题目)
C语言:数组和指针笔试题解析(包括一些容易混淆的指针题目)
|
4天前
|
C语言
c语言指针总结
c语言指针总结
15 1
|
3天前
|
存储 安全 编译器
C语言详解指针(指针海洋的探索,将传值与传址刻在心里)
C语言详解指针(指针海洋的探索,将传值与传址刻在心里)
7 0
|
3天前
|
C语言
C语言——入门分支与循环
C语言——入门分支与循环
3 0
|
4天前
|
C语言
C语言(指针详解)重点笔记:指针易错点,都是精华
C语言(指针详解)重点笔记:指针易错点,都是精华
6 0
|
4天前
|
存储 C语言
C语言指针讲解(适用于初学者)
C语言指针讲解(适用于初学者)
6 0
|
4天前
|
算法 测试技术 容器
【刷题】双指针入门
经过这四道题目的洗礼,我大概对双指针有了初步印象,接下来我会继续努力!!!
44 13
【刷题】双指针入门
|
4天前
|
算法 C++
【C++入门到精通】智能指针 shared_ptr循环引用 | weak_ptr 简介及C++模拟实现 [ C++入门 ]
【C++入门到精通】智能指针 shared_ptr循环引用 | weak_ptr 简介及C++模拟实现 [ C++入门 ]
16 0
|
4天前
|
安全 算法 数据安全/隐私保护
【C++入门到精通】智能指针 shared_ptr 简介及C++模拟实现 [ C++入门 ]
【C++入门到精通】智能指针 shared_ptr 简介及C++模拟实现 [ C++入门 ]
13 0