浅显易懂很适合入门,如果代码不强行翻译成中文就更好了。
出版时间:2013 年 9 月
章节:完整的C程序长啥样?
计算机会从main()函数开始运行程序。它的名字很重要:如果没有一个叫main()的函数,程序就无法启动。
当计算机在运行程序时,它需要一些方法来判断程序是否运行成功,计算机正是通过检查main()函数的返回值来做到这一点。如果让main()函数返回0,就表明程序运行成功;如果让它返回其他值,就表示程序在运行时出了问题。
当调用printf()时,可以包含任意数量的参数,但确保每个参数都要有一个对应的%格式符。
章节:程序工作了!
C语言比其他大多数语言的抽象层次更低,因此它不提供字符串,而是用了相似的东西来代替:以字符为元素的数组。
章节:别在字符串的尽头掉下去
在如今的很多语言中,计算机会时刻记录数组的大小,但C语言比大多数语言更低层,它无法确切地知道数组有多长,如果C语言想在屏幕上显示字符串,它就需要知道什么时候会到达字符数组的尾部,为此C语言加入了哨兵字符。
为什么字符要从0开始编号?为什么不是1?字符的索引值是一个偏移量:它表示当前要引用的这个字符到数组中第一个字符之间有多少字符。
单引号通常用来表示单个字符,而双引号通常用来表示字符串。
字符串字面值是常量。
C语言采取不同的方式在存储器中保存字符串字面值。总线错误意味着程序无法更新那一块存储器空间。
在C语言中,“等号”(=)用来赋值(assignment),而“双等号”用来检查两个值是否相等。
章节:做事情
C语言中大部分命令都是语句。简单的语句是一些动作,它们做事情,或告诉我们事情。你已经见过定义变量的语句、从键盘读取输入的语句以及向屏幕显示数据的语句。
章节:用break语句退出循环……
break非常有用,因为它有时是结束循环最简单有效的方法,但应该避免滥用break,因为它们会降低代码的可读性。
章节:……用 continue 继续循环
通常情况下,函数都需要包含一条return语句,但只要把函数的返回类型声明为void,没有return语句也无妨。
章节:C代码包含指针
指针就是存储器中某条数据的地址。
在函数调用时,可以只传递一个指针,而不用传递整份数据。
章节:深入挖掘存储器
每当声明一个变量,计算机都会在存储器中某个地方为它创建空间。如果在函数(例如main()函数)中声明变量,计算机会把它保存在一个叫栈(Stack)的存储器区段中;如果你在函数以外的地方声明变量,计算机则会把它保存在存储器的全局量段(Globals)。
章节:运行代码时,计算机在想什么
sizeof(指针)在32位操作系统中返回4,在64位操作系统中返回8。
编译器会把运算符编译为一串指令;而当程序调用函数时,会跳到一段独立的代码中执行。
编译器可以在编译时确定存储空间的大小。
章节:数组变量与指针又不完全相同
指针变量是一个用来保存存储器地址的变量,那数组变量呢?如果对数组变量使用&运算符,结果是数组变量本身。
章节:为什么数组从0开始
数组变量可以用作指针,这个指针指向数组的第一个元素,也就是说除了方括号表示法,还可以用*运算符读取数组的第一个元素。
表达式drinks[i]和*(drinks + i)是等价的,这解释了为什么数组要从索引0开始,所谓索引,其实就是为了找到元素的地址单元,指针需要加上的那个数字。
章节:致命处方案件
在编译器生成可执行文件时,编译器会根据变量的类型,用变量的大小乘以指针的增量或减量。
章节:用scanf()输入数字
怎么把数据输进数值字段呢?传递一个指向数值变量的指针就行了。
章节:scanf()会导致缓冲区溢出
缓冲区溢出很有可能会导致程序出错,这种情况通常被称为段错误或abort trap,不管出现什么错误消息,程序都会崩溃。
章节:fgets()配合sizeof一起使用
如果需要输入由多个字段构成的结构化数据,可以使用scanf();而如果想要输入一个非结构化的字符串,fgets()将是你的不二之选。
章节:字符串字面值不能更新
为了防止修改,字符串字面值通常保存在只读存储器中。
章节:如果想修改字符串,就复制它
如果你想把指针设成字符串字面值,必须确保使用了const关键字。
章节:神奇子弹案件
如果在变量声明中看到*,说明变量是指针。
可以将char指针声明成为const char *,以防代码用它修改字符串。
章节:把存储器保存在大脑里
栈:这是存储器用来保存局部变量的部分。每当调用函数,函数的所有局部变量都在栈上创建。它之所以叫栈是因为它看起来就像堆积而成的栈板:当进入函数时,变量会放到栈顶;离开函数时,把变量从栈顶拿走。奇怪的是,栈做起事来颠三倒四,它从存储器的顶部开始,向下增长。
堆用于动态存储。
章节:使用string.h
stdio.h提供了标准输入/输出函数,如printf和scanf。
章节:使用strstr()函数
strstr()函数会在第一个字符串中查找第二个字符串,如果找到,它会返回第二个字符串在存储器中的位置
章节:该审查代码了
程序总是首先运行main()函数。
可以在一个C程序中创建多个函数,但计算机总是先运行main()。
章节:可以用 < 重定向标准输入……
可以使用 < 操作符从文件中读取数据。
用 > 重定向标准输出。
章节:做一件事并把它做好
只做一件简单的事。
章节:bermuda工具
不同操作系统实现管道的方法不同,可能用存储器,也可能用临时文件。我们只要知道它从一端接收数据,在另一端发送数据就行了。
< 会把文件内容发送到流水线中第一个进程的标准输入, > 会捕获流水线中最后一个进程的标准输出。
章节:创建自己的数据流
fopen()函数接收两个参数:文件名和模式。共有三种模式,分别是w(写文件)、r(读文件)与a(在文件末尾追加数据)。
最早FILE是用宏定义的,而宏的名字通常都要大写。
章节:由库代劳
为了避免歧义,可以用--隔开参数和选项,比如set_temperature -c -- -4。getopt()看到--就会停止读取选项。
章节:声明与定义分离
声明只是一个函数签名:一条包含函数名、形参类型与返回类型的记录。
章节:创建第一个头文件
严格意义上讲,编译器只完成编译的步骤,即把C源代码转化为汇编语言。但宽泛地讲,编译是将C源代码转化为可执行文件的整个过程,这个过程由很多阶段组成,而gcc允许你控制这些阶段。gcc会预处理和编译代码。
章节:编译的幕后花絮
一旦有了全部的目标代码,就需要像拼“七巧板”那样把它们拼在一起,构成可执行程序。当某个目标代码的代码调用了另一个目标代码的函数时,编译器会把它们连接在一起。同时,链接还会确保程序能够调用库代码。最后,程序会写到一个可执行程序文件中,文件格式视操作系统而定,操作系统会根据文件格式把程序加载到存储器中运行。
章节:在程序中包含encrypt.h
为了防止两个源文件中的同名变量相互干扰,变量的作用域仅限于某个文件内。如果你想共享变量,就应该在头文件中声明,并在变量名前加上extern关键字。
章节:make需要知道什么?
make编译的文件叫目标(target)。严格意义上讲,make不仅仅可以用来编译文件。
章节:用makefile向make描述代码
生成方法都必须以tab开头。
章节:用结构创建结构化数据类型
struct是structured data type(结构化数据类型)的缩写。有了结构,就可以像下面这样把不同类型的数据写在一起,封装成一个新的大数据类型。
struct fish { const char *name; const char *species; int teeth; int age; };
章节:使用“.”运算符读取结构字段
当把一个结构变量赋给另一个结构变量,计算机会复制结构的内容。如果结构中含有指针,那么复制的仅仅是指针的值。
章节:结构中的结构
计算机按字从存储器中读取数据,如果某个字段跨越了多个字,CPU就必须读取多个存储器单元,并以某种方式把读到的值合并起来。
章节:(t).age和t.age
t->age表示“由t指向的结构中的age字段”,也就是说happy_birthday()函数还能这么写:
void happy_birthday(turtle *a) { a->age = a->age + 1; printf("Happy Birthday %s! You are now %i years old!\n", a->name, a->age); }
计算机先对“点”运算符求值,然后对*运算符求值。
章节:枚举变量保存符号
有时你不想保存数字或文本,而是想保存一组符号。如果你想记录一周中的某一天,只想保存MON-DAY、TUESDAY、WEDNESDAY……这些符号。你不需要保存文本,因为一共只有七种不同的取值。
章节:有时你想控制某一位
每一个十六进制数对应一个长度为4的二进制数。只要知道数字0到15的二进制形式,就能很快地在心中完成转换。
章节:位字段的位数可调
以用位字段(bitfield)指定一个字段有多少位。如果你有一连串的位字段,计算机会放在一起,以节省空间,也就是说如果有8个1位的位字段,计算机就会把它们保存在一个字节中。
假设想在某个结构中保存月份(0到11的值),就可以用一个4位的位字段来保存,为什么?因为4位可以保存0到15,而3位只能保存0到7。
二进制字面值占了很大空间,而且十六进制通常写起来更快。
4位可以保存0到二进制数1111(也就是15)的值,但3位最大只能保存二进制数111(也就是7)。
章节:用C语言创建岛屿……
在C语言中,NULL的值实际上为0,NULL专门用来把某个指针设为0。
章节:用堆进行动态存储
栈是存储器用来保存局部变量的区域。数据保存在局部变量中,一旦离开函数,变量就会消失。
章节:首先,用malloc()获取空间
想象程序在运行时突然发现有大量数据要保存,程序想申请一个大容量储物柜来保存数据,在C语言中,可以用一个叫malloc()的函数来申请。你告诉malloc()需要多少存储器,它就会要求操作系统在堆中分配这点空间,然后malloc()会返回一个指针,指向堆上新分配的空间。
章节:有用有还
使用栈的时候,你无需操心归还存储器,因为这个过程是自动进行的。每当你离开函数,局部变量就会从栈中清除。
章节:调用free()释放存储器
malloc()函数分配空间并给出一个指向这块空间的指针。你需要用这个指针访问数据,用完以后,需要用free()函数释放存储器,就像把储物柜的钥匙还给服务员,好让别人能接着用。
章节:用malloc()申请存储器……
申请存储器的函数叫malloc(),是memory allocation(存储器分配)的意思。
章节:用strdup()修复代码
字符串字面值位于存储器的只读区域,该区域专门用来分配常量。
章节:函数名是指向函数的指针……
在C语言中,函数名也是指针变量。当你创建了一个叫go_to_warp_speed(int speed)函数的同时也会创建了一个叫go_to_warp_speed的指针变量,变量中保存了函数的地址。
章节:如何用数组解决刚才的问题?
用“返回类型(*变量名)(参数类型)”来声明新的函数指针。
章节:你的函数如何做到这点?
预处理器在编译阶段之前运行,它会做很多事情,包括把头文件包含进代码。
章节:一种平台一个叫法
绝大部分操作系统都支持动态库,它们的工作方式也大抵相同,但称呼却大相径庭。在Windows中,动态库通常叫动态链接库,后缀名是.dll;在Linux和Unix上,它们叫共享目标文件,后缀名.so;而在Mac上,它们就叫动态库,后缀名.dylib。
章节:岂止是安全问题
什么是内核?在大部分计算机上,系统调用就是操作系统内核中的函数。什么是内核?虽然你从来没在屏幕上看到过它,但内核其实一直都在那里控制计算机。内核是计算机中最重要的程序,它主管三样东西:
- 进程:只有当内核把程序加载到存储器时程序才能运行。内核创建进程,并确保它们得到了所需资源。内核同时也会留意那些变得贪得无厌或者已经崩溃的进程。
- 存储器:计算机所能提供的存储器资源是有限的,因此内核必须小心翼翼地分配每个进程所能使用的存储器大小。内核还能把部分存储器交换到磁盘从而增加虚拟存储器空间。
- 硬件:内核利用设备驱动与连接到计算机上的设备交互。你的程序在不了解键盘、屏幕和图形处理器的情况下就能使用它们,因为内核会代表你与它们交涉。
系统调用是程序用来与内核对话的函数。
章节:用fork()克隆进程
fork()会克隆当前进程。新建副本将从同一行开始运行相同程序,变量和变量中的值完全一样,只有进程标识符(PID)和原进程不同。
章节:waitpid()函数
重定向输入、输出,然后让进程相互等待,进程间通信就这么简单。
新文件总是按序加入描述符表,如果第一个空的描述符是4号,你的文件就会用它。
章节:…定时器发出SIGALRM信号
每次调用alarm()函数都会重置定时器,也就是说如果把闹钟调到10秒,但过一会儿又把它设为了10分钟,那么闹钟信号10分钟以后才会触发,第一个10秒计时就失效了。
章节:BLAB:服务器连接网络四部曲
在使用套接字与客户端程序通信前,服务器需要历经四个阶段:绑定(Bind)、监听(Listen)、接受(Accept)和开始(Begin),首字母缩写为BLAB。
章节:套接字不是传统意义上的数据流
端口号从0开始一直到65535,首先你需要决定用小号码(1024以下)还是大号码。很多计算机中,只有超级用户或管理员才有资格使用1024号以下的端口,因为小号的端口留给了一些知名服务,如网页服务器和邮件服务器。操作系统只允许管理员使用这些端口,防止普通用户启动一些多余的服务。
章节:……进程不是唯一答案
难道每当想要同时做几件事时都得创建进程吗?不见得,有以下几个原因。
- 创建进程要花时间。
- 有的机器新建进程只要花一丁点时间。虽然时间很短,但还是需要时间。如果你想要执行的任务才用几十毫秒,每次都创建进程就很低效。
- 共享数据不方便。
- 当创建子进程时,子进程会自动包含父进程所有数据的副本。但这些只是副本,如果子进程想把数据发回父进程,就需要借助管道之类的东西。
章节:用互斥锁来管理交通
假设你有两个线程,它们都想得到互斥锁A和B。倘若第一个线程得到了A,第二个线程得到了B,这两个线程就会陷入死锁。因为第一个线程无法得到B,第二个线程无法得到A,它俩都停滞不前。
章节:Allegro能做什么?
通过定时器,你就能创建一个按固定帧率(FPS)刷新屏幕的程序,比如每秒60帧。
章节:用static定义私有变量或函数
static关键字用来控制变量或函数的作用域,防止其他代码以意想不到的方式访问你的数据或函数。