文章目录
前言
继续!
一、字符指针变量
在指针的类型中我们知道有一种指针类型为字符指针char*
一般使用:
还有一种使用方式如下:
代码 const char* pstr = “hello bit.”; 特别容易令人误解为把字符串 hello bit. 放到字符指针 pstr 里面,但是本质是把字符串 hello bit. 首字符的地址放到 pstr 中
1.我们可以把字符串想象成一个字符数组,但是这个数组是不能被修改的
2.当常量字符串出现在表达式中的时候,它的值是第一个字符的地址,以下两种写法等价
有一道关于字符串相关的笔试题,如下:
请给我你认为的输出
答案是第一个是not,第二个是yes
我们来思考原因,首先下面那个字符串被const修饰,不会被修改,内容一样,从内存利用率来说,没有必要存多份,只存一份,大家公用即可,而第一个则不然
二、数组指针变量
数组指针变量是什么?
指针数组:是数组,是存放指针的数组
整型指针:是指针,指向整型的指针
数组指针变量:是指针变量,用来存放数组的地址,是能够指向数组的指针变量
为了让你更好地区分,告诉我下面两个分别是什么
答案是前者是指针数组,后者是数组指针
更准确地说,p1是数组,数组10个元素,每个元素的类型是int*
p2是指针,是指向数组的指针,而所指向的数组有10个元素,每个元素的类型是int
有点绕,你再看看如下:
通过括号, * 先跟 parr 结合,代表 parr 是一个指针(必须要加括号, [ ] 的优先级大于 * )
数组指针变量怎么初始化
数组指针变量是用来存放数组地址的,那怎么获得数组的地址呢?其实就是我们之前所学的&数组名
如果要存放,那就得存放到数组指针变量当中,即:
int (*parr)[10] = &arr;
通过调试,我们也能看到 &arr 和 p的类型是完全一致的
三、二维数组传参的本质
有了数组指针的理解,我们就能够讲以下二维数组传参的本质了
过去我们有一个二维数组需要传参给一个函数的时候,我们是这样写的:
那二维数组可以写成指针吗?可以!
这需要我们再次理解一下二维数组,二维数组可以看做是每个元素是一维数组的数组,也就是二维数组的每个元素是一个一维数组,那么二维数组的首元素就是一行,是个一维数组
所以,根据数组名是数组首元素的地址这个规划,二维数组的数组名表示的就是第一行的地址,是一维数组的地址,根据上面的例子,第一行的一维数组的类型是int[5],所以第一行的地址的类型就是数组指针类型int (*p)[5],那就意味着二维数组传参本质上也是传递了地址,传递的是第一行在这个一维数组的地址,那么形参也是可以写成指针形式的,如下:
四、函数指针变量
函数指针变量的创建
什么是函数指针变量呢?
还是类比,它还是个指针变量,是用来存放函数的地址的变量,未来能通过地址调用函数的
来个测试:
输出结果如下:
确实打印出来了地址,所以函数是有地址的,函数名就是函数的地址,当然也可以通过 &函数名 的方式获得函数的地址
跟数组区别的是,函数前面不管加不加取址符 & 都表示函数的地址
int (*pf)(int, int) = &Add; // pf就是函数指针变量
int ret1 = Add(4,9);
int ret2 = (*pf)(4,9); // 调用
函数指针变量的使用
如上,注意函数指针变量后面的括号加上x,y即(int x, int y)也是可以的
五、两段有趣的代码
代码1
(* ( void ( * )( ) ) 0 )( );
我们发现void(*)()是函数指针类型,而这个类型括号抱起来再跟上0,是强制类型转换,然后再调用
1.把0这个整数值,强制类型转换成一个函数的地址,这个函数没有参数,返回类型是void
2.去调用0地址处的函数!
此段代码出自《C陷阱与缺陷》:
代码2
void (* signal(int, void(*)(int) ) )(int);
这段代码又是什么意思?哈哈,认真思考,这两句代码是本篇最难的部分了~
先来想signal是啥意思,这里没有传参,也没和 * 混在一起,说明它本身是个函数
而一个函数,参数有了,那么还差返回类型,我们把函数名和参数去掉,最后就发现剩下了void( * )(int)
所以,signal函数的参数有2个,第一个是int类型,第二个是函数指针类型,该指针指向的函数参数是int,返沪类型为void
signal函数的返回类型是这种类型的void(*)(int)函数指针
该指针指向的函数参数是int,返回类型是void
typedef关键字
typedef关键字是用来简化类型名的
比如说定义一个无符号整数num
unsigned int num; // 太麻烦
这时候,我们可以重命名
typedef unsigned int uint;
typedef int* ptr_t;
typedef int(* parr_t)[5]; // 简化指针类型int (*)[5]
注意,要修改数值指针类型和函数指针类型的时候,名字放里面
对于前面的void (* signal(int, void(*)(int) ) )(int);
我们把 void ( * )(int) 重命名一下
typedef void( * pf_t)(int);
所以上述代码可以简化为
pf_t signal(int, pf_t)(int);
六、函数指针数组
如果我们要设计一个计算器,要求对整型进行四种运算,显然第一想到的是switch…case语句,可这样代码过于冗杂,我们可以尝试把四种运算都放在一个数组里面,这个数组就是函数指针数组
跟函数指针变量相比,就是在名字后面加上了[ ],我们成功的将函数的地址放进数组里,这意味着我们可以更加高效的管理函数了!
总结
回调函数预告,我保证下一章你会感慨函数调用的奇妙!