针对在校大学生的C语言入门学习——高级语法
- 对于一门语言,什么才算是高级语法呢?个人认为是一些比较难以理解的语法。之所以难以理解,是因为这类语法往往都是为了处理一些高级的问题,而这些高级问题对于没有开发经验的新手来说是从未遇到过的。比如我跟一个南方的学生聊东北的铁锅炖有多么多么的好吃,任我说的唾沫横飞他也不会真的明白有多好吃,因为他从来没吃过。
- 今天想跟大家聊的一些语法并非嵌入式笔试题中经常问到的一些难题,因为很多笔试题没有实际意义。这里顺便提一下,大家如果找工作的话,需要像应付升学考试一样刷题才行,因为笔试题真的很偏!我想聊的是在开发中实实在在经常使用的一些语法。并且在这篇文章之后我还想带大家做两个综合练习来实践我们聊过的所有语法,然后这一系列文章就算完成了。
多维数组
-
在多维数组这里我重点想聊二维数组。大家先不要着急跳过这部分内容,因为很多人真的不了解什么才是二维数组,更何况多维数组。
-
我问过很多同学什么是二维数组,得到的答案往往是表示行列的数组。那么三维数组呢?四维五维数组呢?我要郑重的强调,数组本身只是一段连续的内存,不表示任何含义。大家经常说的二维数组表示行列,那只是开发者赋予它的逻辑意义。就像一杯水,你可以用来浇花、用来解渴,但你却不能说浇花和解渴就是水。
-
下面我定义一个二维数组来分析一下它的性质。
int arr[2][3] = {
{
1,2,3},{
4,5,6}};
- 接下来我使用一个更加本质的描述来解释这个定义——arr是一个包含两个元素的一维数组,数组的每个元素是一个有三个int类型元素的一维数组。
- 上图是对这个数组的内存分析。arr[0]是一个数组,数组元素分别是1、2、3,访问方式分别是arr[0][0]、arr[0][1]、arr[0][2]。数组arr[1]同理。既然arr[0]和arr[1]分别是两个数组,那arr[0]和arr[1]难道分别是两个地址吗?没错,他们是地址!arr[0][0]可以写成*(arr[0]+1)或者*(*(arr+0)+1),这种写法教材里都会有,数组操作的本质就是地址偏移和间接运算。
- 如果打印arr[0]、arr[1]、&arr[0][0]、&arr[1][0],你会发现arr[0]和&arr[0][0]的地址值是一样的,arr[1]和&arr[1][0]的地址值是一样的。既然值一样,可以用&arr[0][0]代替arr[0]吗?答案是否定的,因为他们不是一个类型的地址,你可以输出arr[0]+1和&arr[0][0]+1试试看,他们偏移一个单位的值是不一样的。前者偏移12字节,后者偏移4字节。你还会发现arr[0]+1的值和arr[1]是一样的,很正常,因为数组是连续的。
- 也许有的同学看到这里脑子很乱,这是正常的,毕竟我们现在聊的是C语言的高级语法,需要我们付出更多的耐心去研究。我给二维数组的定义是——一个以一维数组为元素的一维数组。说到这里你再去翻看以前学习过的一维数组和二维数组的初始化方式,你会明白为什么二维数组初始化的时候第二维的长度是不可省的,因为第二维的长度表示的是数组元素类型!
- 至于多维数组,我们可以将我对二维数组的定义无限递归就可以了,其本质是一样的。为什么三维数组以上的数组很少见呢?道理很简单,数组的每一维都和上一维有着逻辑的从属关系,也就是我们常说的耦合性。二维数组的逻辑就已经很复杂了,如果真的有一个逻辑需要使用到三维以上的数组,那我们可以重新审视一下我们的编程思路是不是出现了问题。因为给逻辑加上一个维度,其复杂度可不是加一那么简单。
复杂声明
- 关于复杂声明前面我简单的介绍过了,今天就详细的来聊聊。说到复杂声明那我不得不请出我的C语言恩师丹尼斯理查(因为在老爷爷的书里学到很多东西,所以就不要脸的隔空自拜老爷爷为恩师了)老爷爷的《C程序设计语言》,看看C语言之父是怎么说的。
- 怎么样,有没有被老爷爷的光环照耀到!关于复杂声明,英文描述要比中文描述简单易懂得多。如果英文不是很好的同学,那我就来给大家别扭的翻译一下吧。
char **argv;
//argv:指针指向指针指向char。
/*
我的翻译:argv是一个指向指针的指针,所指向的指针指向char类型。就是我们常说的二级指针。
*/
int (*daytab)[13];
//daytab:指针指向13个int类型元素的数组。
/*
我的翻译:daytab是一个指针,指向一个13个元素的数组,该数组的元素类型是int。就是我们常说的数组指针。
*/
int *daytab[13];
//daytab:13个指向int类型指针元素的数组。
/*
我的翻译:daytab是一个13个元素的数组,数组的元素类型是int类型的指针。就是我们常说的指针数组。
*/
void *comp();
//comp:返回指向void类型指针的函数。
/*
我的翻译:comp是一个函数,函数的返回值类型是指向void类型的指针。就是我们常说的指针函数。
*/
void(*comp)();
//comp:指向返回值是void类型的函数的指针。
/*
我的翻译:comp是一个指针,指向的是一个返回值类型是void,参数列表是空的函数。就是我们常说的函数指针。
*/
char(*(*x())[])();
/*
太难了,我直接翻译吧!首先x是一个函数,函数的返回值是一个指向数组的指针,这个数组的元素类型是指向返回值是char类型函数的指针。这个由于太复杂了,所以并没有人给它起名字。
*/
char(*(*x[3])())[5];
/*
直接来了!首先x是一个3个元素的数组,数组的元素是指向函数的指针,函数的返回值是数组指针,所指向的数组是5个char类型的元素。
*/
- 用中文翻译这个实在是太累人了,而且不直观。建议大家好好理解好好体会英文描述。另外作为新人不要去特意记忆“数组指针”、“指针数组”、“函数指针”、“指针函数”这些概念。因为C语言本来没有这些名称,都是国内的大神为了简化语言自己起的名字。咱们把复杂声明搞清楚就可以了。
- 复杂声明的最后给大家做个总结:表示身份的符号有* () [],其中*表示指针,出现在标识符左边,()表示函数出现在标识符右边,[]表示数组出现在标识符右边。分析复杂声明的时候按照从标识符开始“先右后左,由内而外”的顺序分析。用来包括标识符的()是提升优先级的。
数组指针
- 复杂声明之后大家应该明白什么是数组指针了。但是有什么用呢?大家来看下面代码:
void printArr