概述
前言
从2021/4/16日开始学习c高级,5月16日结束,刚好1个月,这个月每天下班后和周末,都在坚持学习,前段时间因工作需要到外地出差,出差期间也没停止我的学习,每个章节我有自己的笔记,本文就是学完后一个总结,一是怕之前学的东西忘记,再回忆一遍加深记忆,另外也算是为这一个月的学习画上一个句号,说实话学完后心里感觉空唠唠的,不静下心来细想,好像不知道到底学到了什么,写这篇总结的另外一个目的就是,细数这个月的过往,以求心安。
之前看课程简介时印象最深刻的是说很多同学学习完c高级后有一种脱胎换骨的感觉,这点我到没有这方面的体会,可能是我现在的工作更偏向于硬件,软件编写得并不多,没有在工作中应用,才无法体会到那种感觉吧,又或许c功底的提升我自己不知道吧,看视频学习期间,我每看完一个老师的视频,都会凭着自己的理解和记忆,把课程中的示例代码自己写一遍,基本都问题不大,功能实现也差不了太多,有时候卡住了,简单瞄一眼老师的代码,就能完成课程代码功能。
自己能力提升了多少我不知道但是自我感觉还是收获还是颇丰,在一次与朋友讨论c问题时我才意识到,我考虑c的思想已经被改变,大脑首先居然就是从内存管理的角度切入。学习c高级前一直觉得指针很神秘,内心带有畏难心理。学完后觉得也就这么回事。下面分章节说说自己的收获或一些感悟吧。
内存这个大话题
这个章节学习过程中,印象很深刻的就是老师说的那句“懂得从内存角度分析c语言,那么你就成功了一半”,学完c高级后我感觉我的思想已经随课程改变,我的大脑和老师视乎一样,看待c问题时,首先就是从内存出发,这一点我深有体会,举个例子,有一次在和同事聊天时,同事说他最近在学习python,谈到两个字符串组合的方法,我首先想到的就是分配2段内存,分别存放两段字符串,组合时再开辟新的一段内存,分别将原来的2个内存的全部拷贝过来,我同事当时就评价说搞硬件的和搞软件的思维不一样,搞硬件的就喜欢用硬件思维考虑问题,就如操作内存,他说实际在python语言中只要对两个字符串相加就可以了(这一点我不知道对不对,但c肯定是不行的),举这个例子我并不是想讨论这个问题本身,而是想说思维的变化,换之前我最多会想到数组,但是肯定不会扯到内存。
数据类型
这章节我收获最大的也是老师所讲的核心,就是弄清楚了到底什么是数据类型,说到底我们定义的各种类型的变量最终无非就是放在存储器中,之前没考虑过这个问题,就单纯的认为这就是c的游戏规则,不问出处,遵守即可,然而学完这个章节后我才明白其实不同数据类型是规定的编译器在对不同类型变量进行内存大小的分配和这段内存的解析方法进行定义。存储器说白了就是一个有很多格子的仓库而已,场地给你了,仓库里放什么,你要占用多大面积,以及货物怎么堆放它都不管,你自己看着办。而数据类型就是定义占用场地大小,或者说是占用几个格子,和这片多个格子的组合方法,存放的货物也许是数字也许是字符,也可能是代码。从这里开始就引入了指针,从内存的管理中引入指针也让我感觉到是最顺理成章的,毫无违和感。因为我实在是想不到能有其它什么更好的办法来访问内存,指针当然是不二之选。
位操作
位操作这个章节是在学习裸机的时候一起学的,现在回想一下,位操作天生就是用来操作硬件的,帅呆了,之前我写单片机程序都是与和或一个特定数字,别说别人看,我自己写的时候都要计算半天,调试程序遇到问题时,首先就怀疑是特定数字自己计算错误,心里没有一点底气。学完本章节中; 置1 用 |=1<<x;置0用 &=~(<<x) , 非0转1用 !!x;感觉好极了,就想说一句“不装了,摊牌了”课程中还讲到了些稍微复杂点的位操作实现,这里就不一一讲述了,实际也用得不多,用到的时候再去回忆吧。
指针
指针、指针、指针重要的事情说三百遍,指针才是c的精髓,因为编译器喜欢用它,很多语法糖的底层,编译器都是用指针在处理,任何类型的指针都是一个占用4字节的整形变量,不同类型的指针只是对指向对象的解析方法不同,如p++到底该移动几个字节(地址),还得提一下,一个地址对应一个字节,这也该说1百遍,确很少有人说,这个章节的核心我认为是左值右值的含义,特别是数组做右值时的含义。
数组右值
右值 a[ 0 ] –指数组的首元素首地址;
右值 &a —数组的起始地址,虽然实际地址同上,但意义不一样;
右值 a ——指数组的首元素首地址,与a[0]完全一样;
别看简单的这么3行,弄混了几代人啊,有的工程师可能怕是在咽气的那一刻都还不能区分这这3种表达,编译时报错总是傻傻摸不着头脑,有木有。
另外要提一下的是数组不能越界写入,如果出现越界写入,编译器会将越界部分的数据丢掉。
数组的指针实现
该章节还讲到了,数组的指针实现方法,哈哈哈哈哈,想想都觉得好嗨啊。
用普通指针指向数组,访问a[1] 等效于 *(a+1);用了这么多年的a[ x ],真没想过编译器在内存操作中是怎么实现的。原来编译器把他还原成了指针,[ x ]只是c的语法糖而已。
一维数组指针
定义一维数组指针 (*P)[5]= &Table,而数组指针访问Table[1]时,则是*(*p + 1),&Table指针指向的不是内部元素,而是指向的整个数组外壳,*p是访问外壳里面的其中一个元素的地址(就如同指向一个变量地址),要获取地址的值当然还得加一个*,即*(*p+1)了。恍然大悟吧,指针的世界怎么就这么妙啊。
二维数组指针
明白一维数组指针,那么二维数组指针就不难明白了,二维数组指针的定义时 (*p)[2][3];指针访问 a[1][2] 则是,*(*(*P+1)+2);*P进入第1维度,*p+1则是第一维度的第二个元素数组,这个时候就可以看成是一个一维数组了就再加*再加*呗。p=&Table就是指针指向的是二维数组的外壳,呵呵,二维数组里面是2个1维数组啊,*P向内进入1层,指到了内部的一维数组的外壳,**P就到了一维数组内部的元素的地址了,***P就把地址的内容取出来了呗;
咋看都觉得好复杂啊,指针+指针+指针;是什么鬼啊,虽然看起来很屌,其实就是增强版的一维数组指针啊;
额!好吧,说起来容易,其实我在学这个的时候也是被绕晕在厕所过的。擦屁股的纸都差点找不到了。
sizeof
sizeof这个常用的这个关键字又有谁能想到是在编译时就计算出了对象的大小,并赋值了了,而不是在程序运行过程中去计算的,之前还觉得这玩意儿效率低,还特意避开他,自己算好了再放进去。现在只想说,兄弟你想多了。还有sizeof算的不是数据个数哦,而是占用字节长度。又有谁会注意到这个问题。也许只有翻车了才会恍然大悟吧。
传值传址
除数组外,都是传值调用,函数传参时是新建变量拷贝数据值,要是你传了个结构体,甭提效率有多低了并且函数内的操作不影响原始数据,想要提高效率和修改原始数据还是让指针漫天飞吧,它真的能给你节省好多全局变量。
之前有个项目,指针指向了结构体,折磨了得我头昏脑涨啊,编译器就是不给面子,不是警告就是错误,*p 和 . 都不好使,还是一个老家伙跟我一起折腾了半天后说用->试试才解决,虽然解决了,但是我真不知道为啥要这样,学了这个章节才知道,嗦嘎,指针访问结构体时要用箭头 ->。
高级指针
真的很高级?低调点,其实没那么高,但是确实有点难,我认为是c高级课程中最难的的一个章节,好在老师将得细,不然又白瞎了。上来就是数组指针、指针数组。
函数指针
函数原型:int func(int a,char b) 函数指针int (*PT) (int,char);怎么样是不是感觉函数指针和函数原型很像了,没错就是很像,直接把函数原型拷贝,再加个(*PT),再把形参的变量删除了,就完事了。
函数指针指向函数PT=func;访问函数就可以痛痛快快的int a=PT了。
然鹅,函数指针前加上typdef就又不一样了,PT就不是指针了,而是一个函数指针类型了,就不能直接等于func了,既然是类型当然还要定义量啊,所以还得PT p定义一个函数指针变量,函数访问就得使用int a= p了。
二重指针
int a=5,*p1,*p2;p1=&a;p2=&p1;**p2就等于5了,重点是第二重指针一定要指向指针的地址。别把上面的数组指针扯过来,他们不一样。不过非要扯在一起,那也行,都是一层一层的剥,(如果你愿意一层一层一层剥开我的心,你会发现,你会诧异,你是我最压抑最深处的秘密)还学毛啊,跟我一起唱起来。
堆栈 & bbs段
经常听到堆栈,一直都是一知半解的,鬼知道这玩意儿是干毛的。直到学习了本章节,才知道堆栈就是扯淡的,堆是堆,栈是栈,2种不同的东西,挂嘴边的堆栈其实就是栈,不用整得这么高大上,不就是用来存放数据的吗:
堆内存:比较连续的一段内存,编译器不会主动分配,他藏着掖着的,只有用户自己使用malloc才能申请到。
栈内存:存放局部变量的,据说操作系统会给每一个程序分配一段内存作为栈,程序内所有函数共用,函数结束,栈内存归还,而且还是不洗碗的归还,所以大家用之前都自己洗呗,因为系统分配给栈的空间有限,所以别搞大面积的局部变量,不然溢出了就完犊子了。
bbs :第一次听说bbs段是在裸机中,当时没太懂,提到bbs段就不得不提数据段,两者都是用来存放静态局部变量和全局变量,bbs用来存放为0的静态局部变量和为0的全局,非0的全局和静态局部变量则放在数据段。按道理来说,两则完全可以混在一起用啊,为啥要分开了,搞事情啊,呵呵,你太天真了,我们使用的大部分变量起始初始值都为0,如果编译器逐个清零效率太低了,但是也不能全部直接清零,因为偶尔有那么一个他不是0,所以编译器这家伙将两者分开存放,bbs段数据内容就不拷贝了,直接全部清零,效率老高了。至于数据段,反正也不多,逐个去赋值一遍也耽误不了太多时间。bbs与数据段与程序相伴一生,不会在程序运行过程中被释放,所以还是要少使用,没错我就是指的要少使用全局变量和静态局部变量,占着茅坑不一坨接一坨的拉屎,关键是程序移植性还差,要用大片的全局,就直接malloc向堆要吧,堆好说话则了,用完记得free还哦。释放了就行了。
字符串
字符串,你大爷的,难住了我半个硬件工程师的职业生涯,”字符串” ‘字符’ 单双引号就是弄不清啊。主要是单片机用字符串太少,其实字符串就是一段连续的内存组合解析,当然可以用语法糖的数组,也可以使用指针,目的就是为了操作连续内存呗。