2016/2/23 14:41:06
《嗨翻C语言》,本书分为三个部分。
本书分为三个部分:第 1 章到第 4 章是基础知识,包括基本语法、指针、字符串、小工具和源文件;第 5 章到第 8 章为进阶内容,有结构、联合、数据结构、堆、函数指针、动/静态链接;最后四章是高级主题,内容涵盖了系统调用、进程间通信、网络编程和多线程 。(加粗部分的笔记在下篇)
以下笔记没有严格按照章节进行整理,因为书中在多个章节可能提到同一类知识点,比如(略)
说明 在此笔记中,不记录不同 C 标准的差异,具体细节查看原书;不记录不同 OS 下编写 C 时的差异,具体细节查看原书或者 google。笔记中只对不同标准、不同 OS 下共性的语言原理、语法细节做出整理。
此上篇整理了 1-190 项标注和笔记。
课外 三种C标准 ANSI C 始于 20 世纪 80 年代后期,适用于最古老的代码;1999 年开始的 C99 标准有了很大的改进;在 2011 年发布的最新标准 C11 中……想知道编译器支持哪种标准,可以查看编译器的文档。
“指定初始化器”按名设置结构和联合字段,它属于 C99 标准;
C99 标准支持“指定初始化器”,C++ 不支持。
在代码中间的位置声明新变量,只有 C99 和 C11 标准才允许这样做,在 ANSI C(在此特指 C89)中,必须在函数的顶部声明局部变量;
更多的区别:1. 在早期的 ANSI C 标准中,main()
函数可以是 void
类型。但是在 C99 中 main
函数的返回类型必须是 int
。2. ANSI C标准没有用来表示真和假的值,C语言把0这个值当做假处理,把非0的任何值当做真处理。C99标准则允许在程序中使用true和false关键字。但编译器还是会把他们当做1和0这两个值来处理。
扩展阅读:
gcc gcc 是 *inux 下的一款编译器,上述三种标准它都支持。
ps 这种说法太笼统,请阅读 GCC对C标准的支持 、GCC的默认C标准 、哪个版本的gcc才支持c11 、有哪些支持C11标准的编译器 )
在不指定C标准的情况下,GCC默认使用GNU C,所以如果你想让编译器遵循C99标准,需要使用 -std=99 选项。
gcc是到了4.7,才真正支持c11的。
windows下使用gcc 虽然在本书中作者提供了以下两种方法,但是在后续的介绍中也多有提及其不足之处。而且在接触之后,我还是放弃了 Cygwin,转而在虚拟机上安装了 debian。
如在Windows操作系统上使用gcc(GNU编译器套装),有两种选择:一种是 Cygwin ,它可以完全模拟UNIX环境,自然也就包括了gcc;如果你只是想创建能够在Windows下运行的程序,MinGW 可能更符合你的需要。
摘两条在书中描述的 Cygwin 项目不完美的地方:
一些Cygwin的gcc版本允许修改字符串字面值,不会报错,但这样做常常是错的。
Cygwin的很多版本中,在多个信号的发送顺序和接收顺序问题上,做了不恰当的假设。
其他 面向对象是一种对抗软件复杂性的技术。
基础知识 表达式
在C语言中,几乎每样东西都有返回值:表达式 x = 4
本身也有一个值,这个值是4,即赋给x的值。
Q1:这个赋值表达式能否进行再次赋值 ?例如 (x=3)=4
A1:在C中编译无法通过,类似3=4
;在C++中正常编译,运行,类似x=4
。
注意:以下这种再次赋值是成立的。因为无论 y=4
(C语言) 还是 y=x
(C++) 都是成立的。
1 2 3 4 5 6 7 8 9 10 11 int main () { int x = 9 ; int y = 8 ; y = x = 4 ; printf ("x is %d, y is %d\n" , x, y); return 0 ; }
关键是理解 C/C++ 中左值的概念,而且 C 和 C++ 中是不一样的。
使用 switch
语句的好处之一是,可以用下落逻辑 在不同的分支之间复用代码。
存储器 如果真的想玩转C语言,就需要理解C语言如何操纵存储器。掌握指针和存储器寻址对成为一名地道的C程序员很重要。
函数(例如 main() 函数)中声明变量,计算机会把它保存在一个叫栈(Stack)的存储器区段中;函数以外的地方声明变量,计算机则会把它保存在存储器的全局量段(Globals);堆用于动态存储。
存储器的分布图:参考位置#1022、位置#1322、位置#1448
存储器是进程的
sizeof
是运算符,好比+
、&
,它不是库函数。程序是在编译期间计算 sizeof
的。sizeof
运算告知某样东西在存储器中占多少字节,既可以对数据类型使用,也可以对某条数据使用。
指针
指针就是存储器中某条数据的地址
指针做了两件事:避免副本和共享数据。
指针是一种间接形式 的地址(怎么理解?)
*
运算符可以读取存储器地址中的内容。*
运算符还可以设置存储器地址中的内容。
字符串
指向字符串字面值(string literal)的指针变量不能用来修改字符串的内容
在存储器的非只读区域创建了字符串的副本,就可以修改它的字母
如果你想把指针设成字符串字面值,必须确保使用了 const
关键字: 1 2 3 char *card = "JQK" ;card[1 ] = 'A' ;
编译错误,比在运行时崩溃好太多了! 1 2 3 const char *s = "some string" ;s[0 ] = 'S' ;
加不加 const
,字符串字面值都是只读的,const
修饰符表示,一旦你试图用 const
修饰过的变量去修改数组,编译器就会报错 1 2 3 4 char masked_raider[] = "Alive" ;const char *jimmy = masked_raider;
数组
数组的索引值是一个偏移量
数组变量好比指针……
1 char quote[] = "Cookies make you fat" ;
计算机为字符串的每一个字符以及结束字符 \0
在栈上分配空间,并把首字符的地址和 quote 变量关联起来。函数传参时传给函数的是指针。
数组变量与指针又不完全相同(区别:重点理解2、3点)
sizeof(数组)
是……数组的大小;sizeof(指针)
返回4或8。
数组的地址……是数组的地址;指针的地址是另一个地址。
数组变量不能指向其他地方。 计算机会为数组分配存储空间,但不会为数组变量分配任何空间。
指针退化
把数组赋给指针变量,指针变量只会包含数组的地址信息,而对数组的长度一无所知,相当于指针丢失了一些信息。我们把这种信息的丢失称为退化。
结构(结构体)
结构可以像数组那样在结构中保存字段,但读取时只能按名访问。
Q1:数组变量就是一个指向数组的指针,那么结构变量是一个指向结构的指针吗?
A1:不是,结构变量是结构本身的名字。 数组变量的地址是数组变量自身;结构变量的地址是指针,不是自身。
为结构变量赋值相当于叫计算机复制数据。
重点在函数传参时 很可能浪费较多存储资源;而数组作为函数参数传的是指针。
用 typedef
为结构创建别名。用 typedef
定义结构时可以省略结构名,只写类型名。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 struct call_phone { int cell_no; const char *wallpaper; float minutes_of_charge; }; typedef struct call_phone { int cell_no; const char *wallpaper; float minutes_of_charge; } phone; typedef struct { int cell_no; const char *wallpaper; float minutes_of_charge; } phone;
关于“对齐”,可以查看原文 或者进一步从网上学习
在C语言中,参数按值传递给函数。
作者要表达的就是字面意思!@但这句话在国内出版物中经常作为前半句出现—“值传参和“指针传参”,以至于之前理解都有偏差。其实“指针传参”还是按值传递给函数的。
联合
定义一种叫“量”的数据类型,然后根据特定的数据决定要保存个数、重量还是容积。
每次创建结构实例,计算机都会在存储器中相继摆放字段,联合则不同。当定义联合时,计算机以其中最大的字段分配空间,然后由你决定里面保存什么值。
1 2 3 4 5 typedef union { short count; float weight; float volume; } quantity;
指定初始化器(designated initializer)按名设置结构和联合字段,它属于C99标准
1 2 quantity q = {.weight=1.5 }; phone p= {.cell_no=15210 , .minutes_of_charge=1.2 };
联合提供了一种让你创建支持不同数据类型的变量 的方法。
联合经常和结构一起用。创建联合相当于创建新的数据类型。
编译器不会记录你在联合中设置或读取过哪些字段。
Q1:可以在 union
中保存任何字段(count
、weight
或者 volume
)的值,这些不同类型的值保存在存储器中相同的位置……既然如此,你怎么知道我保存的是 float
还是 short
?要是我保存了 float
字段,却读取了 short
字段呢?
A1:解决方法:只要用枚举或其他东西记录一下就行了。
枚举 (略)
位字段
位字段(bitfield)的初衷是节省存储器空间:真/假的值只需要一位就能表示;月份等小范围的数字……
只有当多个位字段出现在同一个结构中,才能节省空间。
如果编译器发现结构中只有一个位字段,还是会把它填充成一个字,这就是为什么位字段总是组合在一起。
可以用位字段指定一个字段有多少位。位字段应当声明为 unsigned int
1 2 3 4 5 6 7 8 9 10 11 12 typedef struct { unsigned int first_visit:1 ; unsigned int come_agin:1 ; unsigned int fingers_lost:4 ; unsigned int shark_attack:1 ; unsigned int days_a_week:3 ; } survey;
Q1:为什么C语言不支持二进制字面值?
A1:因为二进制字面值占了很大空间(质疑? ),而且十六进制通常写起来更快。
递归
分而治之 小工具 问:什么是小工具?——其实就是其字面意思,重点是要有这个概念。
操作系统都有小工具。类Unix的操作系统为完成工作会大量使用工具,Windows用的少一些。
C语言小工具执行特定的小任务,例如读写文件、过滤数据。如果要完成更复杂的任务,可以把多个工具链接在一起。
小工具是一个C程序,它做一件事情并把它做好。
当你想解决一个大问题时,可以把它分解成一连串的小问题,然后针对每个小问题写一个小工具。
问:为什么小工具要使用标准输入和标准输出?
答:有了它们,就可以轻易用管道将小工具们串连起来。
问:如果两个程序用管道相连,第二个程序要不要等第一个程序执行完后才能开始运行?
答:不需要,两个程序可以同时运行,第一个程序一发出数据,第二个程序马上就可以处理。
输入输出
使用 scanf()
时要小心:限制 scanf()
能读入的字符数
1 2 3 4 5 6 7 8 9 int main () { char card_name[3 ]; puts ("输入牌名:" ); scanf ("%2s" , card_name); return 0 ; }
如果忘了限制 scanf()
读取字符串的长度,用户就可以输入远远超出程序空间的数据,多余的数据会写到计算机还没有分配好的存储器中
如果你要向 fgets()
函数传递数组变量,就用 sizeof
;如果只是传指针,就应该输入你想要的长度。
操作系统控制数据如何进出标准输入、标准输出。
scanf()
和 printf()
函数,只管从标准输入读数据,向标准输出写数据
进程有一只耳朵(标准输入)和两张嘴(标准输出和标准错误)
printf()
其实只是 fprintf()
函数的特例,
可以用 >
重定向标准输出,2>
重定向标准错误。
用管道连接输入与输出
创建自己的数据流
命令行参数
使用多个源文件
动态存储