一.二级指针
我们知道但凡变量都有地址,指针变量也不例外,那么访问指针变量地址的指针称为二级指针,记作int** p,这个表达式这样理解,*赋予p一个指针的身份,而这个指针指向int*类型的地址(这样理解有助于后面的理解),看一行代码示例
我们发现有两个地址,第一个是指针p指向的a的地址,第二个是p本身的地址。
二.指针数组
1.指针数组的概念
指针数组,简单来说就是用来存放指针(地址)的数组。代码示例
代表arr4[]数组中存放的元素是int*指针类型,然后前一卷我们讲到数组名一般都是数组首元素地址,所以arr4[]中相当于存放了三个数组首元素的地址。
2.指针数组模拟二维数组
那么指针数组有什么用呢,我们不妨来看,但我们得到数组首元素地址后是不是可以借此访问整个数组中的元素,看一下代码
这两个代码本质是一样的。(另外在插入一个知识点,我们学指针之前遍历数组都是用的下标类似于arr[i],但其实编译器在执行这个代码的时候会将其自动转换为*(arr+i)的形式。)
那么指针数组模拟出的二维数组本质上并不是一个二维数组,只不过表达出的形式一样。
三.字符指针变量
先看一组代码
这组代码什么意思?是把字符串“abc”赋给指针,可指针只能用来接收地址,那是把整个字符串的地址赋给指针吗?可char*在x86下只有4个字节的空间,显然放不下。来看一下这个代码的运行结果。 看到这个结果就知道了原来它是把该字符串中的首字符地址赋给了指针p。
所以你不妨将这个字符串想象成一个字符数组,“abc”看成一个数组名,前面我们提到数组名一般情况下代表首元素地址,这样是不是理解的更清晰啦。那么接下来我们就看一下如何访问这个字符串中的其他字符,有两种方式
前两种本质一样可以类比成arr[i]/*(arr+i),后两种本质一样相当于int* p=arr;然后用*(p+i)访问。
那我们能不能更改这个字符串中的字符元素呢
显然不能,那有的人又问这个char*被定义成了常变量肯定不能,把const删了行不行,再看代码
显然也不行,当把字符串赋给字符型指针变量时,该字符型指针变量和该字符串就具有常属性,此时字符串称为常量字符串,不论加不加const限制都不能进行更改。
那么下面再看一组代码
这个代码的输出结果是多少,三秒钟思考,来上结果!
为什么是这样的结果嘞,前两个加字符数组,str是它们的数组名,而if里面比较数组名,实际就是在比较它们首元素的地址,而每次创建一个字符数组都要新创建地址,所以它们首元素的地址当然不一样。而后两个又为什么一样呢?后两个代码的意思是把相同字符串首元素的地址赋给不同的指针,相当于两个指针访问的都是a元素的地址,那有的人又问,为什么后两个代码“abc”不能创建两次?注意后两个是常量字符串,而常量字符串如果内容相同则只用创建一次。
所以我们在比较字符串内容是否相同时用到的strcmp函数传入的形参就是const char*类型。
四.数组指针
1.概念
前面我们提到了有一个概念叫指针数组(一个存放指针的数组,记作int* parr[]),而数组指针是什么呢?其实就是访问一个数组的指针,记作int (*parr)[],理解就是*赋给parr一个指针的身份,让它访问一个整形数组,为什么要加括号呢,因为根据运算符的优先级,如果不加括号,parr就会先与[]结合,那不就变成指针数组了吗。
通过这个代码我们不难发现,数组指针访问的使整个数组的地址。
2.数组指针初始化
3.用数组指针访问整个一维数组(不建议这么用)
先来解释一下这个代码的意思,此时数组指针p中存放的是整个数组的地址,也就是&arr,那么当我对p解引用时就得到*p=arr,arr是数组首元素的地址然后再前移后移,但是这样写还不如直接用指针,有点多此一举,所以不建议这样用,但注意我这里说的是一维数组。
4.二维数组传参的本质
前面我们讲到一维数组传参的本质就是传入首元素的地址,所以形参可以写成int arr[],也可以写成int* arr。那类比一维数组,二维数组传参的本质也是传入数组首元素,只不过首元素是二维数组第一行中的元素,代码如下
五.函数指针
1.概念
类比数组指针可知,函数指针用来访问一个函数的地址,记作(返回类型) (*p)(形参类型),比如int (*p)(int,int)(可以不写形参变量名,但必须要有形参类型),代码如下
就拿刚才的函数来举个例子,定义一个函数指针用来访问函数Test的地址,找到Test的返回类型和形参类型,然后这个函数指针变量的类型就为void(* )(int(* )[3], int, int),然后这个函数指针的变量名为p。
2.函数名
数组名是数组首元素的地址,函数名也可以作为函数的地址。
3.函数指针的应用
为了方便演示,接下来将函数改为求和函数
其实函数调用的本质是调用函数的地址,所以当用函数指针调用的时候有以上三种情况:
第一种把函数的地址赋给指针,即将&Sum赋给指针,在对指针p1解引用得到Sum,然后调用。
第二种直接把函数名给指针,因为函数名就是函数的地址,这时候不论是否对指针p2进行解引用,都可以成功调用函数。
那如果是这样函数指针应该怎么写呢?三秒思考时间
想对了吗【好奇一下】
六.typedef关键字
接下来看两段代码
1.(*(void(*)())0)(),啥意思,懵了吗?
解释一下,首先把这个代码拆分一下:void(*)()是函数指针,把指针类型放在一个整型前并且加上括号,就是将0强制转换成函数指针类型,*(void(*)()0)是对指针解引用得到一个函数地址,然后调用,且这个函数返回值为空,形参为空。
2.void(* signal(int, void(* )(int)))(int),是不是更懵了
还是把这个代码进行拆分
先看这个signal(int, void(* )(int))什么意思,是不是定义了一个signal函数,一个形参为int类型,一个形参为函数指针类型(且这个函数指针指向一个返回值为void,形参为int类型的函数)。
再看最外层,把signal(int, void(* )(int))当成一个整体,所以整个代码的意思就是函数signal的返回类型是一个函数指针(这个函数指针指向的函数返回值为void,形参为int类型)
3.所以这样的代码十分不利于理解,所以就发明了一个关键字——typedef,示例代码如下
相当于可以对一个已知的变量名进行重命名,所以我们就可以利用这个关键字化简上述复杂的代码,示例如下:
这样就看的非常简便。