目录
前言
指针,可谓是C语言学习过程中不可或缺的一部分,同时也是学习难度相对较高的一个部分,接下来就跟着小鸥来见识一下指针的奥妙吧!
内存和地址粗理解
首先,想要理解指针就要先知道内存和地址的概念,简单来说内存其实就是用来存放地址的空间,在存储数据时,内存会被分解为一个个的单元格,每个单元格的大小是一个字节(Byte)。
而地址就像现实中的地址一样,我们每一个人都有着自己的居住地址,正是得益于地址的存在才让我们在找人时更加方便。而在C语言中给地址取了一个新的名字——指针 ,只是不同于现实世界,不同的两个人可能会有相同的地址,但在内存中地址都是相互独立的,不同的数据地址一定是不同的。
而指针其实就是一种特殊的数据类型,所以指针变量就是指针,指针就是指针变量,它存放的是其他数据的地址。
初识指针
取地址操作符和解引用操作符
& ——取地址操作符,顾名思义就是取出其操作数的地址的操作符
int* p就是最简单的指针类型,这里的int和*要分开理解:*代表的意思是,p是一个指针,int则代表其存放的地址是一个int类型的数据的地址。
由打印可知使用%p打印出p的值即为a变量的地址(x86环境下地址更短)
* ——解引用操作符,又称间接访问操作符,只能对指针类型变量使用
上图可知*p的打印结果就是原本a的值;而在对*p进行重新赋值20后,打印a的值时结果也成了20,这说明*p的本质就是a变量,对*p进行修改会直接导致a的值改变。
就好比p是一个人,他用&知道了a家的地址,而你拿着*(万能锁)去找p要到了a家的地址,这样你就可以直接不通过a的同意就进入a的房间,从而达到直接修改a的目的(即间接访问)。
注意
解引用操作符——*,和创建指针变量时使用的*意义不同,创建指针变量时的*表示这个变量是一个指针,而这个指针的类型就是其指向的数据的类型加一个*,比如int* p,int*就是p变量的数据类型。
指针类型的大小
和其他数据类型一样,指针也有自己的数据类型大小,而这主要是由机器来决定的,在x32的环境下地址就有32个bit位,即4个字节大小;而在x64的环境下,地址就有64个bit位,即8个字节大小。(在VS环境下打印时选择x86即为x32环境,此时打印的地址长度更短)
切记!
无论什么类型的指针,大小都是4/8个字节,是由硬件决定的,不存在int*类型指针比char*类型指针大的说法。
指针的运算
1.指针+-整数
由上图的结果可知,指针类型+-整数得到的结果仍然是指针,即“指针+-整数=指针”。
而从相差的结果不难看出:指针+-整数跳过的大小为指针所指向的数据类型大小*整数个字节
即int*类型指针+n跳过sizeof(int)*n个字节;char*类型指针+n跳过sizeof(char)*n个字节,如下图图解
2.指针-指针
其实由上文的指针+-整数=指针就不难推出指针-指针=整数的结果,但是切记没有指针+指针,只存在指针-指针=整数。
指针-指针的时若两个指针指向同一块空间,列如同一数组的两个元素,则这个差值就是他们之间的元素个数。
可用于模拟实现strlen函数:
当两个指针不属于同一个数组时,相减得到的差值是两个地址之间的差值,而单位由指针指向的数据的数据类型决定,而当两个指针变量指向的数据类型不同时,差值的单位就由减号前边的指针指向的数据类型决定,如下图:
3.指针的关系运算
指针可以直接比较大小
指针类型
1.二级指针
二级指针就是存放一级指针地址的指针,而int*,char*,double*等,只有一个*的就是一级指针
结合上图,可知二级指针于一级指针的不同就在于它指向的是指针,而不是其他数据类型而已,图中的二级指针int** ppi,ppi就是它的变量名,int*表示ppi指向的数据是一个int*类型的数据,第二个*则和一级指针创建时的*一样,表示这个变量是一个指针,ppi的类型就int**
2.void*指针
又名泛型指针,viod*指针表示的就是没有具体类型的指针,一般用于接收不知道具体类型的指针时使用。但需注意的是,由于viod*指针没有具体类型,所以无法进行指针的+-等运算,且不能直接进行解引用。
3.数组指针变量
a.数组名的理解
想要理解数组指针变量首次就得理解清楚数组名的含义:
由上图的地址差值不难看出,arr与&arr[0]所包含的意义是相同的,也就是说,数组名就等于数组首元素的地址,结合上文中指针+-整数的结论,说明他们的数据类型都是int*,而&arr虽然一开始打印的地址是首元素的地址,但在+1后直接跳过40个字节,这说明&arr的数据类型此时不可能是int*,那它究竟代表了什么呢?由此我们便引出了数组指针变量。
注意两个例外
1.&数组名;2.sizeof(数组名)。这两种情况中,数组名代表的不再是首元素的地址,而是代表的整个数组的地址,此时它的数据类型应该是一个数组指针,而不是数组元素对应的指针类型,下文会解释。
b.数组指针变量:
上文中&arr+1跳过了40个字节,而图中的数组是一个包含10个int类型元素的数组,而10*sizeof(int)=40;这说明&arr+1其实就是跳过了整个数组的长度,其实&arr的数据类型就是我们的数组指针,如下图
图中parr就是一个数组指针
c.理解数组指针变量以及区别指针数组:
由图中可知,数组指针变量是一个指针,它指向的是整个数组的地址,其数据类型就是int (*) [10]而parr则是这个数组指针的变量名,而图中将()去掉后又引出了指针数组,指针数组就是一个元素都为指针的数组,是一个数组,要注意区分。
4. 字符指针变量
字符指针变量就是指向字符的指针,主要区分字符型数组和直接指向常字符串的字符型指针
由上图可以知道,当字符型指针指向一个字符串时,其包含的地址就是该字符串首个元素的地址,这和数组名的含义有类似,但并不完全相同,下面我们通过一段代码来认识一下他们的不同点:
5.函数指针变量
函数指针变量就是指向函数地址的指针变量声明如下
结合图分析可知,函数指针变量的构成是:函数的返回类型 (*变量名)(参数类型);去掉pf这个变量名,剩下的部分就是这个函数指针变量的数据类型,这和上文数组指针变量相同(所有的数据类型都可以这样推理出来)。
6.结构体指针变量
结构体指针的创建
可见结构体指针变量也和其他指针创建方法相似,struct S*就是ps的数据类型
指针相关数组
1.指针数组
a.指针数组认识及使用
指针数组已经在上文数组指针变量中提及,指针数组就是数组里的元素都是指针的一种数组,而其元素的指针类型可以是任意的。
使用指针数组实现二维数组效果:
b.数组名及下标引用操作符的本质
二维数组本质
首先我们要先理解二维数组的数组名及其含义
上图中结果结合上文中一维数组名的理解可以知道,arr是二维数组首元素的地址,而二维数组的首元素就是数组中的第一列的所有数组成的一维数组,二维数组就是多个一维数组组成的,所以arr的数据类型,其实就是一个数组指针,指向的是二维数组中的第一个一维数组的地址。
下标引用操作符和解引用操作符的关系
从二维数组的理解的解释图中的绿字就可以窥见下标引用操作符和解引用操作符的关系:
以上文指针数组中的二维数组为例:
由图中结果可知三种表达方式的结果都相同,说明三者是等价的
得出结论:
下标引用操作符本质就是对它的两个操作数——一个数组名和一个整型(下标)进行指针+整数的求和后的和再解引用的操作
2.函数指针数组(转移表)
函数指针数组,指的是一个指针数组,里面的每个元素是一个函数指针,指向每一个函数的地址,这样就可以通过这个数组,来取得函数的地址,从而调用函数。而这种使用函数指针数组得到函数地址,再进行调用的方式,就叫做转移表。
由图中可知函数指针数组的创建方式,但须注意的是函数指针数组中的每个函数的返回类型、参数类型都要相同。
总结
总的来说,指针涉及的知识点较多,虽然想要彻底弄明白实有一定难度,但只要坚持学习就总有一天能拿下这篇江山!
本篇文章主要总结了指针的重要知识点,相信如果你对本篇提及的内容已经熟练掌握,那你在指针上的理解也已经不差了,还有一些细枝末节尚未提及,有兴趣的读者可以关注我接下来的文章内容,或者直接另寻高人指点,CSDN高手云集,总有适合你的高人。
后记
小鸥也还在持续学习当中,如果对你有帮助,那是我作为本篇作者的荣幸,但文中一定还有许多不足之处,恳请各路神仙大佬多多海涵并指点一二,好让小鸥也能在学习之路上沐浴各位大佬的春光啊——
在此,感谢各位读者的光临,咱们下期再见!