> 开场
相信很多同学都在行指针和列指针这堂课上听懵了吧,希望这篇随笔会对你有所帮助,
德尔菲神庙是古希腊世界中最nb的一所神庙,古希腊人认为它是”世界的脐带“,它是供奉太阳神阿波罗的主要圣地(阿波罗是光明、预言、音乐、医药和理性之神),因为阿波罗是nb的预言之神,所以古希腊人会带着各种各样的问题来到德尔菲神庙祈求神谕。
或许是阿波罗不希望来的人上来就是一通瞎问乱问,有一句箴言被刻在德尔菲神庙的入口处,"认识你自己"。
这个终极哲学问题,其实可以落地为一个非常实用的原则:每当“听不懂”时,我们首先要做的,正是践行“认识你自己”——思考“我为什么会听不懂”。
> 为什么会听不懂?
我相信指针和数组很多同学都会用,但一个很普遍的现象是——很多同学一到"指针和数组"这一块就蒙圈了,为什么呢?笔者拙见:其原因在于我们没学好数组和指针。
指针中的 * 到底是什么 有何作用? 在一维数组和二维数组中是否有所不同?
为什么一维数组的 * (Array+i) 是一个值,而二维数组的 * (Array+i)是一个地址?
讲课,笔者自然是没那个资格,故只在此分享一些通过Deepseek搜索的知识所整理的心得,以及对这俩问题的一些见解。
> What is 星号?
哈哈,博客后台的这个Markdown编辑器还挺难用的,此段中大部分的 * 就用中文"星号"代替了。
回归正题,什么是星号?
很多人编程时第一次接触星号是把它当乘法运算的运算符号用,这母庸置疑,在学习"指针"这部分知识后,我们对星号的认知得到了进一步拓展。星号还是那个星号,但它变得更高端了,从一个乘法运算符变成了一个"多功能运算符",何意味?
首先,在变量声明中, * 用于指明变量是指针类型
例如
int a = 10;
int *ptr; // 声明一个指向整数的指针
ptr = &a; // ptr 指向变量a的地址int **pptr; // 声明指向指针的指针
pptr = &ptr; // pptr 指向ptr的地址
这部分研究的再深一点就会涉及"多级指针",因为接下来的内容和多级指针没关系,而且笔者不会多级指针,所以我们着重研究星号的另一个用途——作为解引用运算符,获取指针指向的值。
先理解解引用这个概念,在一维数组中,解引用的含义非常简单直接:获取指针所指向的那个具体的数据值。
例如
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr; // p指向数组的第一个元素printf("初始状态:\n");
printf("*p = %d\n", *p); // 10 - 第一个元素的值p++; // 指针移动到下一个元素
printf("\n第一次 p++ 后:\n");
printf("*p = %d\n", *p); // 20 - 第二个元素的值p++; // 再次移动指针
printf("\n第二次 p++ 后:\n");
printf("*p = %d\n", *p); // 30 - 第三个元素的值
"这很好理解呀,给*p当值用就完事了",笔者之前也是这样想的,但这种想法沿袭到对二维数组的理解中会造成毁灭性的后果。
例如
int a[3][4] = {{1,2,3,4}, {5,6,7,8}, {9,10,11,12}};// 灾难开始的地方:
printf("%d", *a); // 等等,这输出的是地址,不是值!
printf("%d", *(a+1)); // 又是地址?!
如此,二维数组中的 * ( * (a+i)+j) 让人难以理解也就说得通了用刚才的想法去理解:既然*p是值,那么*(a+i)也应该是个值,可*(*(a+i)+j)又是什么鬼?!
哈哈哈哈,看起来面对二维数组,我们应转变我们的思维,笔者拙见:要用"降维"的思维去理解二维数组
int arr2D[3][4];
// 逐步分析类型变化:
arr2D // int(*)[4] (指向包含4个int的数组)
arr2D + 1 // int(*)[4] (还是行指针)
*(arr2D + 1) // int* (降为一维数组指针)
*(arr2D + 1) + 2 // int* (元素指针)
*(*(arr2D + 1) + 2) // int (最终的值)
看上面这几行代码,和上午课上的那个差不多吧?(11.25 周二上午10:00~11:30)笔者惭愧,上午讲这段的时候没听懂,晚上已经忘干净了(悲)。
无妨,让我们重新将其整理一遍
第一行: arr2D
指向这个二维数组的第0行(也就是首行)的地址,此处与一维数组的区别是一维数组中 arr2D 指向的是第0个元素(首元素)
第二行: arr2D+1
指向第1行(首行的下一行)的地址,此处不可以以一维数组的思维去理解了,在二维数组中 arr2D+1 是在行数上 +1
第三行: * (arr2D + 1)
此处的*起解引用作用,它将 arr2D+1 降维,此时它从一个二维地址降维成了一个一维地址,再具体一点说, * (arr2D+1) 此时是这个一位数组的首元素的"地址",
有的同学可能会疑惑,这和一维数组能扯上什么关系啊???别急,你可以看着下面这个二维数组去理解
int arr2D[3][4] = {{1, 2, 3, 4},{5, 6, 7, 8}, {9, 10, 11, 12}// 创建3×4二维数组};
此时的 arr2D+1 指向内容为{5, 6, 7, 8}的一维数组的首元素5,而(arr2D+1)指向该数组的首元素5的地址
Q:为什么这个一维数组里面是{5, 6, 7, 8}?
A:前面我们将arr2D变成了arr2D+1,此时是第二行的首地址,而我们通过将第二行降维处理后,二维数组的第二行{5, 6, 7, 8}此时可看作一维数组, * (arr2D+1) 就是这个一维数组首元素的地址,这是一个降维操作,地址降维后得到的仍是地址 不要用前文提到的一维数组的思维去理解 * (arr2D+i)
也就是说,第三行把"指向数组的指针"降维成了"指向整数的指针"。
第四行: * (arr2D + 1) + 2
如果你能理解降维这一操作,那这一步你也能理解了,第四步将这个指向{5, 6, 7, 8}首元素 5 的地址的指针向右偏移了两位 现在指向 7 ,仍为地址。
第五行: * ( * (arr2D + 1) + 2)
此处在第四行的* (arr2D + 1) + 2外面加了一层*() , 即对其地址再次解引用,二维数组经两次解引用后得到的是值,over。
以上观点目前仅适用于二维数组,不要贸然将其应用于指针数组,笔者无法保证会不会出错。