针对在校大学生的C语言入门学习——高级语法
针对在校大学生的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(int(*p)[3], int len);int main()
{int arr[2][3] = {1,2,3,4,5,6};printArr(arr, 2);//分行打印arr数组return 0;
}void printArr(int(*p)[3], int len)
{int i,j;for(i = 0;i < len;i++)//遍历指针p指向的数组{for(j = 0;j < 3;j++)//遍历每个元素数组{printf("%d ", p[i][j]);}printf("\n");}
}
- 当我想在函数中传递一个多维数组的时候,我们就需要使用数组指针。难道定义int类型的指针做参数不行吗?当然不行,我们上面聊多维数组的时候讲过,arr+1偏移的可不是4个字节,所以不能用int类型指针。
- 给大家一个经验,定义一个指针指向一个数组时,指针要定义成数组的元素类型。如果数组的元素是int类型,那就定义int类型指针;如果数组的元素是数组类型,那就定义数组指针;如果数组的元素是函数指针,那就定义二级函数指针!说远了,越说越想笔试题了。
- 再有,在C语言中传递一个数组时,我们必须传递两个参数。第一是数组的首地址,第二是数组的长度。如果是字符串的话传递一个首地址就可以了,因为字符串有字符\0做结尾。
函数指针
- 函数指针的用处就更大了!函数指针是C语言实现多态的必要语法!你们没有看错,就是多态。很多人都认为多态是面向对象语言才有的,其实在有面向对象语言之前就有面向对象的思想了。C语言虽然是面向过程的语言,但是想做稍微大型一些的项目,面向对象的思想是少不了的。
- 什么是多态?我对多态的描述——通过参数传递逻辑,实现在不改变代码框架的情况下执行不同的逻辑。这个描述同样适用于C++、java等面向对象的语言。对于一个数组,如果我既想做升序排序,又想做降序排序,应该怎么做呢?初级方法是把排序写两遍,下面代码我以冒泡排序为例:
void printArr(int *p, int len);
void sortUp(int *p, int len);
void sortDown(int *p, int len);int main()
{int arr[10] = {21,3,43,5,67,89,9,4,13,2};sortUp(arr, 10);printArr(arr, 10);sortDown(arr, 10);printArr(arr, 10);return 0;
}void sortUp(int *p, int len)
{int i,j;for(i = 0;i < len-1;i++){for(j = 0;j < len-1-i;j++){if(p[j] > p[j+1]){int t = p[j];p[j] = p[j+1];p[j+1] = t;}}}
}void sortDown(int *p, int len)
{int i,j;for(i = 0;i < len-1;i++){for(j = 0;j < len-1-i;j++){if(p[j] < p[j+1]){int t = p[j];p[j] = p[j+1];p[j+1] = t;}}}
}void printArr(int *p, int len)
{int i;for(i = 0;i < len;i++){printf("%d ", p[i]);}printf("\n");
}
- 你会发现sortUp和sortDown两个函数里只有if判断条件不同。这样大量重复的代码非常不利于项目的维护和扩展。所以如果可以把if里面的逻辑通过参数的形式传递进来,那不就可以避免大量重复代码了吗!请看下面使用函数指针的代码:
void printArr(int *p, int len);
void sort(int *p, int len, int(*cmp)(int,int));
int cmpUp(int a, int b);
int cmpDown(int a, int b);int main()
{int arr[10] = {21,3,43,5,67,89,9,4,13,2};sort(arr, 10, cmpUp);printArr(arr, 10);sort(arr, 10, cmpDown);printArr(arr, 10);return 0;
}void sort(int *p, int len, int(*cmp)(int,int))
{int i,j;for(i = 0;i < len-1;i++){for(j = 0;j < len-1-i;j++){if(cmp(p[j], p[j+1])){int t = p[j];p[j] = p[j+1];p[j+1] = t;}}}
}int cmpUp(int a, int b)
{return a > b;
}int cmpDown(int a, int b)
{return a < b;
}void printArr(int *p, int len)
{int i;for(i = 0;i < len;i++){printf("%d ", p[i]);}printf("\n");
}
- 通过sort函数的参数cmp,分别调用了cmpUp和cmpDown函数,实现了逻辑传递。这样使代码得到了很大的优化。
结构体
- 结构体的语法是很容易理解的,但是什么时候使用结构体呢?比如我们现在来开发一款格斗类游戏,人物的属性包括攻击力、防御、HP等简单元素,那么我们应该用什么类型来表示一个人物呢?你会发现C语言的基本数据类型没有能够表达这么复杂信息的。但是我们可以用三个int类型来分别表示三个属性,不过这样的话对于三个变量的管理有事大问题,因为从语法来说三个变量之间是没有任何联系的。这个时候就可以使用结构体来把这三个属性都封装到一个类型中。
struct Hero
{int act;int def;int hp;
};int main()
{struct Hero superMan;superMan.act = 100;superMan.def = 50;superMan.hp = 500;return 0;
}
- 结构体的好处就是将一些逻辑上相关的变量封装成一个类型,这样编程的时候无论是从逻辑上还是可读性上都有更加准确表达。
枚举
- 枚举类型是非常常用但是又经常在学习中被忽略的类型。当我们需要一些逻辑上相关,但是值又不能重复的常量时,我们往往使用枚举。
enum Debuff
{dizziness,//眩晕 toxic, //中毒freezing //冰冻
};
void showDebuff(enum Debuff db);int main()
{showDebuff(dizziness);return 0;
}void showDebuff(enum Debuff db)
{switch (db) {case dizziness: printf("眩晕了\n");break;case toxic:printf("中毒了\n");break;case freezing:printf("冰冻了\n");break;}
}
- 我可以用0、1、2这些常量来分别表示三种状态,但是常量本身是没有意义的。写成宏定义又体现不出三种状态的逻辑关联性。所以定义成枚举既易读又好写。
- 关于语法就聊到这里。前面的文章中我也说过,学习编程最好的方式就是写代码。说来容易,对于新手而言写代码是需要辅导的。我不能辅导大家写代码,但是我可以把我写代码的思路一步一步的写出来给大家做参考。后面我会给大家献上《扫雷》和《学生管理系统》两个练习。注意,我给的不仅仅是代码,将是我编写代码的整个思路。
针对在校大学生的C语言入门学习——高级语法相关推荐
- 大学生学习c语言方法,针对在校大学生的C语言入门学习——函数
为什么要使用函数如果你没有接受过系统的训练就能主动想要封装函数,说明你在编程上是一个天才,千万不要辜负自己的天赋!函数在语法上并不难理解,但是什么时候使用函数才合理呢? 我们不妨来做一个猜谜游戏--& ...
- c语言10个人 三向成绩,C语言入门学习精华:这样学习C语言最有效
C语言入门学习精华:这样学习C语言最有效 c语言死了吗? 本材料描述了使用C语言的高级技能,并努力将您的C语言能力从"基本"提升到"高级".然而,学习态度比学习 ...
- c语言入门自学免费app,C语言入门学习最新版下载-C语言入门学习app手机版v1.0.2 安卓版-腾飞网...
C语言入门学习app手机版是一款c语言编程自学软件,零基础也可以学习,里面有海量教学视频,针对c语言不同程度的讲解都囊括其中.随时随地学习编程都可以,不用担心自己没有基础.还支持在手机上敲代码编程哦. ...
- c语言入门自学手机版,c语言入门自学app下载-C语言入门学习 安卓版v1.0.2-PC6安卓网...
C语言入门学习app是一款C语言零基础自学软件.C语言入门自学app提供海量精品学习资源,从小白入门到基础进阶都有,帮你轻松学习编程. 软件介绍 C语言入门学习app是一款专业的编程入门学习App,致 ...
- c语言入门自学手机版,C语言入门学习app下载-C语言入门学习app最新版下载 V1.0.2-友情手机站...
C语言入门学习app是一款0基础自学软件,这里有着丰富C语音相关课程学习,大家在这里是可以便捷搜索查找,随时都是可以找到适合感兴趣课程学习,都是一些优质课程知识提供大家,学员在这里是可以高效学习,海恩 ...
- c语言入门自学手机版,C语言入门学习软件下载-C语言入门学习手机版v1.0.2 - 起点软件园...
C语言入门学习是一款最新推出上线于安卓手机平台的专业编程学习软件,C语言入门学习app收录了海量入门视频课程,清晰易懂的详细讲解专为刚入门学习C语言的小白量身打造,不同章节都有相应的习题,根据习题成绩 ...
- 郑州计算机c语言培训机构,c语言入门学习选郑州哪家计算机专业学校
C语言是一门稀缺的贵族语言,会C语言的被IT界高看,C语言的地位很高,学C语言确实难,但是有正确的方法成为C程序员是非常有效的,会C语言相当于拥有了一定地位,未来非常有前景,那么如何入门C语言呢?选择 ...
- R语言入门学习——Rstudio的安装与学习
R语言入门学习--Rstudio的安装与学习 1.Rstudio下载 2.Rstudio安装 3.Rstudio功能和简单程序 1.Rstudio下载 下载地址:https://rstudio.com ...
- lua语言入门学习(二)lua语言的特点及注意事项
lua语言入门学习 lua语言的特点及注意事项 文章目录 lua语言入门学习 lua语言的学习地址 一.lua语言特点 1.注释 2.全局变量和局部变量 3.lua数据类型 4.字符串类型 5.多变量 ...
- lua语言入门学习(一)搭建基本的环境并实现运行
lua语言入门学习 本篇文章用来记录自己写lua语言的过程 文章目录 lua语言入门学习 前言 一.lua语言是什么? 二.使用步骤 1.搭建环境 2.开始运行 总结 前言 最近来到了一家网络游戏公司 ...
最新文章
- DNA提取方法对浮游生物群落研究结果的影响
- 理解卷积神经网络?看这篇论文就够了
- 联赛前(伪)数据结构专题总结
- c++17(33)-数值上下限、无穷、非数、中文字符串
- python源码精要(6)-CPython编译标志
- 软件构造学习笔记-第十一周
- php中带?错误,参考-此错误在PHP中意味着什么?
- 简单python脚本实例-对Python实现简单的API接口实例讲解
- Android入门(9)AudioRecord和AudioTrack类的使用【转】http://blog.sina.com.cn/s/blog_6309e1ed0100j1rw.html...
- 知识图谱—知识存储—仅用neo4j搭建简单的金融知识图谱
- 女朋友突然天天加班,用python写个定位脚本,直接抓到现场
- 输入学生成绩评定等级:90-100为A,80-89为B,60-79为C,小于60为D
- 机器学习决策树ID3
- 阿里云服务器如何进行快照备份
- simulink他励直流电动机串电阻启动仿真
- 字库点阵数据的竖置横排和横置横排区别
- 服务器端脚本语言和客户端脚本语言
- r语言 读服务器数据,R语言数据实战 | 安装R语言
- js html 渐变透明度,JavaScript动画之透明度渐变
- 你的线性代数魔法书,极度易吸收:想让图像怎么动,它就怎么动 | 免费