🤡博客主页:醉竺
🥰本文专栏:《C语言深度解剖》《精通C指针》
😻欢迎关注:感谢大家的点赞评论+关注,祝您学有所成!
✨✨💜💛想要学习更多C语言深度解剖点击专栏链接查看💛💜✨✨
目录
前情提示:
1. 指针是什么
1.1 指针的内存布局
1.2 指针解引用
2. 指针和数组
2.1 数组的内存布局
2.2 理解 &a[0] 和 &a 的区别
2.3 指针和数组的恩怨
2.4 以指针的形式访问和以下标的形式访问
2.5 a 和&a的区别
3. 指针数组和数组指针
3.1 指针数组和数组指针的内存布局
3.2 int(*)[10] p2 也许应该这么定义数组指针
4. 多维数组和多级指针
4.1 二维数组
4.1.1 基本概念
4.1.2 基本内存布局
4.1.3 空间布局
5. 数组参数和指针参数
5.1 一维数组传参
5.2 一级指针传参
5.3 二维数组参数和二级指针参数
6. 函数指针
7. 函数指针数组
8. 函数指针数组指针
9. 强烈推荐两个专栏
前情提示:
在之前的两个专栏中——《C语言深度解剖》https://blog.csdn.net/weixin_43382136/category_12344236.html
《精通C指针》https://blog.csdn.net/weixin_43382136/category_12659166.html我已经由浅入深地讲解了指针。无论您是在阅读本文之前还是之后,都可以查阅以下文章以获得更全面的理解:
指针就是这么简单https://blog.csdn.net/weixin_43382136/article/details/137881292?spm=1001.2014.3001.5501https://blog.csdn.net/weixin_43382136/article/details/137881292?spm=1001.2014.3001.5501
数组指针、指针数组和数组指针数组https://blog.csdn.net/weixin_43382136/article/details/138248557?spm=1001.2014.3001.5501https://blog.csdn.net/weixin_43382136/article/details/138248557?spm=1001.2014.3001.5501
函数指针、函数指针数组、指向函数指针数组的指针、回调函数https://blog.csdn.net/weixin_43382136/article/details/138322410?spm=1001.2014.3001.5501https://blog.csdn.net/weixin_43382136/article/details/138322410?spm=1001.2014.3001.5501本文将从宏观角度审视指针的难点和易错点,若您已认真学习过上述内容,阅读本文将更为轻松。
1. 指针是什么
在回答这个问题之前,我想先问几个问题?
1. 如何看待下面代码中的a变量?
结论:
同样一个a变量,在不同的应用场景中,a本身的含义是不同的。
本质区别就是 左值和右值的区别。
变量的空间:左值; 变量的内容:右值;
重新理解变量:
定义一个变量,本质是在内存中根据类型来进行开辟空间。有了空间,就必须具有地址来标识空间,来方便CPU进行寻址。有了空间,就可以把数据保存起来。
2. 什么是指针?
指针就是地址!那么地址本质是什么呢?地址是数据,那么数据可不可以被保存在变量空间里面呢?当然可以。
3. 有没有指针变量这个概念?
保存指针(地址)数据的变量就叫做指针变量
4. 指针 和 指针变量又有何不同?我们口语中的"定义一个指针"究竟是什么意思?我们该如何理解这种说法?
- 严格意义上,指针和指针变量是不同的,指针就是地址值,而指针变量是一个变量,要在特定区域开辟空间,要用来保存地址数据,还可以被取地址。(先分开)
- 但是,我们经常在口语化表达的时候,又经常将这两个概念混合,具体原因无从考证,不过个人认为与最早的C语言资料(书, 文档之类)的翻译有关。然后,书与书之间互相借鉴,形成了这样的说法。
- 同时,简化说法,也更符合人的表达习惯,估计老外也是这么想的。(在关联)
- 那么我们以后怎么认为呢?我们分开理解,但是依旧关联使用。自己使用的时候,混合使用可以。和别人讨论,最好明确概念。
结论:指针就是地址,指针变量是一个变量,变量内部保存指针(地址)数据。
为什么要有指针?
回答一个问题:为何每间宿舍都要有门牌号呢?
结论:提高查找效率。
类比到计算机中:
CPU在内存中寻址的基本单位是多大?
在32位机器下,最多能够识别多大的物理内存?
既然CPU寻址按照字节寻址,但是内存又很大,所以,内存可以看做众多字节的集合。
其中,每个内存字节空间,相当于一个学生宿舍,字节空间里面能放8个比特位,就好比同学们住的八人间,每个人是一个比特位。
每间宿舍都有门牌号就等价于每个字节空间对应的地址,即该空间对应的指针。
那么,为何要存在指针呢?为了CPU寻址的效率。如果没有,该怎么找在字节空间中的数据呢?
究竟该如何理解编址?
1.1 指针的内存布局
1.2 指针解引用
*p完整理解是,取出p中的地址,访问该地址指向的内存单元(空间或者内容)(其实通过指针变量访问,本质是一种间接寻址的方式)
口诀:对指针解引用,就是指针指向的目标。所以*p,就是a
2. 指针和数组
2.1 数组的内存布局
概念:数组是具有相同数据类型的集合。
先看下面的例子:
int a[5];
所有人都明白这里定义了一个数组,其包含了5个int型的数据。我们可以用a[0]、a[1]等来访问数组里面的每一个元素,那么这些元素的名字就是a[0]、a[1]....吗?看看下图内容:
如上图所示,当我们定义一个数组a时,编译器根据指定的元素个数和元素类型分配确定大小(元素类型大小×元素个数)的一块内存,并把这块内存的名字命名为a。
先看下面一段代码:
我们发现,先定义的变量,地址是比较大的,后续依次减小。
这是为什么呢?
a,b,c都在main函数中定义,也就是在栈上开辟的临时变量。而a先定义意味着,a先开辟空间,那么a就先入栈,所以a的地址最高,其他类似。
运行结果:
2.2 理解 &a[0] 和 &a 的区别
看下面的例子:
口诀:对指针+1,本质是加上其所指向类型的大小。
2.3 指针和数组的恩怨
很多初学者弄不清指针和数组到底有什么样的关系,我现在就告诉你:它们之间没有任何关系,只是它们经常穿着相似的衣服来逗你玩罢了。
指针就是指针,指针变量在 32 位系统下,永远占 4 字节,其值为某一个内存的地址。指针可以指向任何地方,但是不是任何地方你都能通过这个指针变量访问到呢?
数组就是数组,其大小与元素的类型和个数有关;定义数组时必须指定其元素的类型和个数;数组可以存任何类型的数据,但不能存函数。
既然它们之间没有任何关系,那为何很多人经常把数组和指针混淆,甚至很多人认为指针和数组是一样的呢?这就与市面上C语言的书有关了,很少有书把这个问题讲得透彻,讲得明白。
2.4 以指针的形式访问和以下标的形式访问
可以理解成:
[] 是对 *() 的缩写
结论:指针和数组指向或者表示一块空间的时候,访问方式是可以互通的,具有相似性。但是具有相似性,不代表是一个东西或者具有相关性。
2.5 a 和&a的区别
结论: &a叫做数组的地址,a做右值叫做数组首元素的地址,本质是类型不同,进而进行+-计算步长不同
3. 指针数组和数组指针
3.1 指针数组和数组指针的内存布局
初学者总是分不出指针数组和数组指针的区别,其实这很好理解。
指针数组:首先它是一个数组,数组的元素都是指针,数组占多少字节由数组本身决定。它是“储存指针的数组”的简称。
数组指针:首先它是一个指针,它指向一个数组。在32位系统下永远是占4字节,至于它指向的数组占多少字节并不知道。它是“指向数组的指针”的简称。
下面到底哪个是数组指针,哪个是指针数组呢?
(A)int *p1[10];
(B) int(*p2)[10];
这里需要明白一个符号之间的优先级问题。“ [ ] ”的优先级比“ * ”要高,p1 先与“ [ ] "结合,构成一个数组的定义,数组名为 p1,int * 修饰的是数组的内容,即数组的每个元素。那现在我们清楚,这是一个数组,其包含 10 个指向 int 类型数据的指针,即指针数组。至于 p2 就更好理解了,这里“() ” 的优先级比“ [ ] ”高,“ * ”号和 p2 构成一个指针的定义,指针变量名为 p2, int 修饰的是数组的内容,即数组的每个元素。数组在这里并没有名字,是个匿名数组。那现在我们清楚 p2 是一个指针,它指向一个包含10个int类型数据的数组,即数组指针。
我们看下图进行理解
3.2 int(*)[10] p2 也许应该这么定义数组指针
很多小伙伴学不会数组指针或者指针数组等比较复杂的指针有时候并不是怪自己!而是C语言在复杂类型的设计上确实不太 “优雅”!
这里有个有意思的话题值得探讨一下:平时我们定义指针都是在数据类型后面加上指针变量名,这个数组指针 p2 的定义怎么不是按照这个语法来定义的呢?也许我们应该这样来定义p2:
int(*)[10] p2;
int(*)[10] 确实是指针类型,p2是指针变量。其实数组指针的原型确实就是这样子的,只不过C语言的设计风格,把指针变量p2前移了,与“ * ” 号紧挨着在一个小括号里。你私下完全可以这么理解,有助于判断复杂数据类型的学习,只不过编译器语法不通过罢了。
4. 多维数组和多级指针
4.1 二维数组
4.1.1 基本概念
几乎大部分书中所画的二维数组,都是矩阵样子,具体可以参考书中的图,但是,现在我们要在这里澄清,书中的图,最多只能称之为示意图,并非真的内存布局图。
可以想象一些问题:如果按照书中矩阵样子画二维数组的话,那么三维数组,四维数组又该如何画呢?
4.1.2 基本内存布局
运行结果:
结论:二维数组在内存地址空间排布上,也是线性连续且递增的
4.1.3 空间布局
以它为例:char a[3][4] = { 0 };
5. 数组参数和指针参数
5.1 一维数组传参
说明:如果把数组作为函数的参数,那么编译器会进行优化。 编译器会将数组参数转换成 指针类型的变量,用于接收数组的首元素的地址。
C语言中,当一维数组作为函数参数的时候,编译器总是把它解析成一个指向其首元素类型的指针。
所以说一维数组当做函数参数的时候,数组元素的个数可以忽略不写,因为降维成了指向其首元素类型的指针了,已经指向了这块数组空间了,可以进行访问了,传参时的个数已经不重要了。
5.2 一级指针传参
1. 能否把指针变量本身传递给一个函数
因为指针变量,也是变量,在传参上,它也必须符合变量的要求,进行临时拷贝!
2.无法把指针变量本身传递给一个函数
这很像孙悟空拔下一根猴毛变成自己的样子去忽悠小妖怪,与其类似,fun函数实际运行时,用到的都是_p2这个变量而非p2本身。如此,我们看下面的例子:
5.3 二维数组参数和二级指针参数
前面详细分析了二维数组和二级指针,那它们作为参数时与不作为参数时又有什么区别呢?看例子:
void fun(char a[3][4]);
我们按照上面的分析,完全可以把 a[3][4] 理解为一个一维数组 a[3],其每个元素都是一个含有4个char类型数据的数组。上面的规则:“C语言中,当一维数组作为函数参数的时候,编译器总是把它解析成一个指向其首元素类型的指针。”在这里同样适用,也就是说我们可以把这个函数声明改写为:
结论:
任何维度的数组,传参的时候,都要发生降维,降维成指向其首元素类型的指针
那么,二维数组,内部“元素”是一维数组!那么降维成指向一维数组的指针
6. 函数指针
顾名思义,函数指针就是函数的指针。它是一个指针,指向一个函数。看例子:
1. (*(void (*) ()) 0) ()-这是什么
7. 函数指针数组
8. 函数指针数组指针
注意,这里的 pf 和 第 7 章节的 pf 就完全是两码事了。4第 7 章节的 pf 并非指针,而是一个数组名;这里的 pf 确实是实实在在的指针。这个指针指向 :
一个包含了3个元素的数组;这个数组里面存的是指向函数的指针;这些指针指向一些返回值类型为指向字符的指针,参数为一个指向字符的指针的函数。这比第 7 章节的函数指针数组更拗口。其实你不用管这么多,明白这是一个指针就ok了,其用法与前面讲的数组指针没有差别。
9. 强烈推荐两个专栏
《C语言深度解剖》https://blog.csdn.net/weixin_43382136/category_12344236.htmlhttps://blog.csdn.net/weixin_43382136/category_12344236.html
《精通C指针》https://blog.csdn.net/weixin_43382136/category_12659166.htmlhttps://blog.csdn.net/weixin_43382136/category_12659166.html