日记大全

日记大全 > 句子大全

高观点下的C语言入门

句子大全 2019-08-30 02:11:01
相关推荐

C语言,是大多数科班出身的程序员的第一门语言,也是大多数底层技术爱好者的主要语言。

在计算机领域,底层技术就是核心技术。

C语言刚入门时,其实还是很别扭的,因为它的设计与高中数学教育形成的思维惯式不太一样。

1,相等==和赋值=的区分,

在数学里,一般不需要特别的符号来区分相等和赋值,人们可以根据上下文的语义知道表示的是什么。

x=1的同时也就暗含了x==1,在给人看时不需要特意说明这点。

但在电脑程序里需要区分,因为电脑的理解力比人差得多,也就需要在程序设计时添加更多的细节。

比较x是否等于1,和让x等于1,是两种不同的运算。

必须给电脑明确指出,它才能正确运行。

因此,我们需要两个“不同但又类似”的符号,作为这两个关联场景的运算符,C语言选择了=用于赋值,==用于相等。

不等,则使用了!=,因为ascii码里没有数学上那个不等号,如果使用ascii码之外的符号就会需要额外的输入法软件,这对编程语言是不可接受的。

没有哪个程序员在编程时想切换输入法,除非它要输入非英文字符串。

之所以不让==表示赋值,而让=继续符合人们中学时学的通常语义,是因为赋值比相等在程序里出现的次数更多。

比较运算可以是>、>=、 < 、<= 、!= 、!,==出现的次数其实不多。

但赋值,包含扩展的+=、-=之类的,出现的次数特别多,可以少打一个符号。

对使用频率高的词汇使用较短的编码,是所有语言的惯例。也是符合“信息论”的编码原则的。

“我”,在汉语里的笔画算比较少的,而且结构很醒目。

英语则是I,法语是Je,这种高频次的第一人称代词,一般都是简短+单音节+元音结尾的词。

第三人称的“是”,也是另一个高频词汇,英语是is,法语是est,例如多邻国的名句:C’est une fille anglaise.

汉语是典型分析语,语义表达不依赖动词形式变化,所有人称的“是”都一样,但这个字也笔画简单而结构显眼。

书法好的可以把它写地龙飞凤舞,大气磅礴。

回到C语言,另外,如果赋值使用了==,那么频次不低的+=就变成了+==,三个字符不但会给词法解析带来额外负担,还会让程序员多打一个字符:(

程序员都是懒人,否则他们就不会把重复的活儿给电脑做了:(

+=在循环语句里用于变量的自增,使用频率仅低于++。

2,从0开始计数,

这也是很不符合惯例的设计。

人们数数是从1开始,0在数学史上出现也是较晚的,比1晚的多。

但是在二维数组中,如果从1开始计数,那么2行16列的数组,16是第1行最后1个元素,17是第2行第1个元素。

可是,16/16=1余0,17/16=1余1,它们的数组索引与它们与数组大小的除法结果就不一致了:

“商+1”才是行号,但余数是0时商又不需要+1,这在编程时会额外消耗程序员的脑力,而且这种计算细节很容易出错。

另外,就是索引值与元素在数组中的实际偏移量不一致。

第1个元素与数组首地址的偏移量是0,而不是1,&a[1]对应的是a+1 - 1 = a+0。

就跟公元1世纪不是1xx年,而是xx年一样,算起来别扭又烧脑。

如果让索引号和偏移量一致,那么0号位的内存就浪费了。

这在内存稀缺的上世纪70年代,是不可接受的。

最后,为了让数组索引与除法/和模运算%的结果一致,就从0开始计数了。

模运算%的结果一致叫做“同余”,线性同余方程组的求解,最早可以追溯到中国古代数学著作“孙子算经”。

宋代大数学家秦九韶在“数学九章”中把这个解法叫做“大衍求一术”,现代的抽象代数里叫做“中国剩余定理”。

秦九韶的解法,同时也是这个定理的“构造性证明”。

构造性证明,比存在性和唯一性的证明要牛,因为它同时给出了求解步骤。

中国古代在代数学领域还是很牛的:(

几何学上可能古希腊牛一些,毕竟欧几里德比较牛,“几何原本”,“古希腊三大难题”。

但古希腊三大“几何”难题,最后都是靠“代数”否决的:(

3,变量,

变量的出现伴随着方程的诞生,可以追溯到一元一次方程的出现,久得估计没法考证了。

秦九韶解线性同余方程组的中国剩余定理,肯定比这个晚了至少上千年。

方程确定的是隐函数,函数确定的自然是显函数。

4,函数,

C语言的函数和数学上的函数有点区别,它是一个处理某个功能的代码模块,可以被另一个函数调用。

它也和数学上的函数一样有参数(自变量),也有返回值(结果),这可能是它也叫做函数的原因。

它也是从参数空间(定义域)到返回值空间(值域)的一一映射:在代码没有BUG的情况下同样的参数该算出同样的结果来。

调用时需要传参数,参数和返回值在不需要的时候也可以没有,void f()这种。

整个程序的入口函数就是main函数,它被操作系统加载程序时调用,Linux的这个API是execve()。

5,指针,

指针,是C语言的压轴章节。

这章之后,就是文件读写的API了,严格来说不属于编程语言范畴,而是属于IO库的范畴。

“去书架上把C primer plus借来”。

书架就是“指针”,是C primer plus这本书存放的位置。

C primer plus就是指针的内容,即书架上存的是什么。

内存就是计算机的“书架”,内存里的值就是这个“书架”上的“书”。

对于电脑来说要尽量细化,所以给内存按字节编号,指针就是这类编号。

还可以给书里的章节编号,那么书架就是二级指针,书是一级指针,章节是指针的内容。

地址,在日常生活中也是常见的。

寄快递需要有地址和收货人,去某地址找某个收货人,把快件交给他之后,他的总体重量增加了1公斤,就是*p += 1。

6,递归,

递归,就是利用“函数栈”保存上下文状态的迭代。

递归比迭代难理解,但在编程上比迭代容易实现,因为它不需要手动保存上下文状态。

按照数学归纳法,当k = 0的时候条件成立,当k = n的时候也成立,当k=n+1的时候也成立,计算k = 10的结果。

从k=0开始,根据迭代公式,一直迭代到k=10,每一步都要保存k跌代到哪里了,结果是什么。

k和结果就是上下文,手动管理它们的存放就是迭代,把它们写在函数参数里,让程序在函数调用的过程里自动管理,就是递归。

参数,也是函数的局部变量,局部变量的管理由编译器实现,实际上简化了程序员的工作难度。

二叉树遍历时,开一个动态数组,不断把遍历到的节点按遍历顺序存进去,同时记录哪个分支遍历过了,哪个分支没有遍历,迭代到哪里了,实际是比较麻烦的。

但是,用函数栈来存储状态,就只需要改变每次迭代的参数就可以了,代码就简单得多。

递归难理解,可能在于它是反向迭代,大多数场景下的都是从10跌代到0,截至条件与日常经验是反过来的。

倒着数数,相对比较别扭。

7,字符和字节,

单字节并不足以表示全球主要语言的所有常用字符,char作为1个字节的变量同时表示字符,是ascii码时代的遗迹。

那个时候字符和字节的语义是一致的。

unicode的出现比较晚,所以C后来又添加了wchar类型,但char已经在各种代码里用惯了。

char既当字符变量,又当单字节变量,char*还作为缓冲区的指针,又当作字符串,差别只在于结尾0。

然后就有了著名的缓冲区溢出漏洞:(

不在字符串的string类型中记录长度,而是靠结尾的哨兵0,最后不得不搞出了各种snprintf函数。

每次strcpy、sprintf时都要在心里默算缓冲区够不够。

优点就是vsnprintf()函数的实现比较简单,因为字符和整数可以直接运算,

x % 10 + ‘0’就是数字要打印的字符。

char和char*字符串的使用,在C里是特别容易出BUG的地方。

本来,字符应该是无符号的,但是在char同时充当了有符号的单字节整数的情况下,就导致了char型的0x80在编译器扩展时成了负数。

unsigned char型的0x80是正的。

char在与int计算时需要注意它的最高位是0还是1,因为编译器会做符号扩展。

阅读剩余内容
网友评论
相关内容
拓展阅读
最近更新