C程序设计语言 The C Programming Language
第1章 导言
1.1
1、C语言提供的转义字符序列还包括:\t表示制表符;\b表示回退符;\”表示双引号;\\表示反斜杠本身。
1.2
2、我们建议每行只书写一条语句,并在运算符两边各加上一个空格字符。
3、printf的转换说明%6.1f表明一个待打印的数至少占6个字符宽,且小数点后面有一位数字。
4、%%表示百分号本身。
1.4
5、#define 名字 替换文本。其中,替换文本可以是任何字符序列,而不仅限于数字。
1.5
6、文本流是由多行字符构成的字符序列,而每行字符则由0个或多个字符组成,行末是一个换行符。
7、getchar函数从文本流读入下一个输入字符,并将其作为结果值返回。
8、转换说明%ld告诉printf函数其对应的参数是long类型。
9、在兼有值与赋值两种功能的表达式中,赋值结合次序是由右至左。
10、由&&或||连接的表达式由左至右求值,并保证在求值过程中只要能够判断最终的结果为真或假,求值就立即终止。
1.10
11、在每个需要访问外部变量的函数中,必须声明相应的外部变量,此时说明其类型。声明时可以用extern语句显式声明,也可以通过上下文隐式声明。
12、某些情况下可以省略extern声明。在源文件中,如果外部变量的定义出现在使用它的函数之前,那么在那个函数中就没有必要使用extern声明。
13、如果程序包含在多个源文件中,而某个变量在file1文件中定义、在file2和file3文件中使用,那么在文件file2与file3中就需要使用extern声明来建立该变量与其定义之间的联系。
第2章 类型、运算符与表达式
2.1
14、下划线”_”被看做是字母,通常用于命名较长的变量名,以提高其可读性。库例程的名字通常以下划线开头。
2.3
15、可以用’\OOO’表示任意的字节大小的位模式。其中,OOO代表1~3个八进制数字(0…7)。这种位模式还可以用’\xhh’表示,其中,hh是一个或多个十六进制数字(0…9,a…f,A…F)。
16、标准库strlen(s)可以反悔字符串参数s的长度,但长度不包括末尾的”\0”。
17、在没有显式说明的情况下,enum类型中的第一个枚举名的值为0,第二个为1,以此类推。如果只指定了部分枚举名的值,那么未指定值的枚举名的值将依着最后一个指定值向后递增。如:
enum months { JAN = 1, FEB, MAR, APR, MAY, JUN, JUL, AUG,SEP, OCT, NOV, DEC };
2.7
18、C语言没有指定char类型的变量是无符号变量还是带符号变量。当把一个char类型的值转换为int类型的值时,其结果有没有可能为负整数?对于不同的机器,其结果也不同,这反映了不同机器结构之间的区别。
19、为了保证程序的可移植性,如果要在char类型的变量中存储非字符数据,最好指定signed或unsigned限定符。
20、当把double类型转换为float类型时,是进行四舍五入还是截取取决于具体的实现。
2.9
21、当对signed类型的带符号值进行右移时,某些机器将对左边空出的部分用符号位填补(即“算术移位”),而另一些机器则对左边空出的部分用0填补(即“逻辑移位”)。
22、表达式x&~077与机器字长无关,它比形式为x&0177700的表达式要好,因为后者假定x是16位的数值。这种可移植的形式并没有增加额外开销,因为~077是常量表达式,可以在编译时求值。
2.10
23、expr1 op = expr2等价于expr1 = (expr1) op (expr2)。它们的区别在于,前一种形式expr1只计算一次。注意,在第二种形式中,expr2两边的圆括号是必不可少的,例如,x *= y + 1的含义是x = x * (y + 1)。
24、在求对二的补码时,表达式x &= (x – 1)可以删除x中最右边值为1的一个二进制位。
2.11
25、在表达式expr1 ? expr2 : expr3中,首先计算expr1,如果其值不等于0,则计算expr2的值,并以该值作为条件表达式的值,否则计算expr3的值,并以该值作为条件表达式的值。expr2与expr3中只能有一个表达式被计算。
26、例:每行打印10个元素,每列之间用一个空格隔开,每行用一个换行符结束(包括最后一行):
for( i = 0; i < n; i++)
printf(“%6d%c”,a[i], ( i % 10 == 9 || i == n – 1) ? ‘\n’ : ‘ ‘);
第3章 控制流
3.4
27、switch语句的每一个分支都由一个或多个整数值常量或常量表达式标记。如果某个分支与表达式的值匹配,则从该分支开始执行。
3.6
28、itoa函数是atoi函数的逆函数,它把数字转换为字符串。
3.8
29、标号的命名同变量命名的形式相同,标号的后面要紧跟一个冒号。标号可以位于对应的goto语句所在函数的任何语句的前面。标号的作用域是整个函数。
第4章 函数与程序结构
4.2
30、标准库中包含类似功能的atof函数,在头文件<stdlib.h>中声明。
4.3
31、默认情况下,外部变量与函数具有下列性质:通过同一个名字对外部变量的所有引用(即使这种引用来自于单独编译的不同函数)实际上都是引用同一个对象(标准中把这一性质称为外部链接)。
32、如果两个函数必须共享某些数据,而这两个函数互不调用对方,这种情况下最方便的方式便是把这些共享数据定义为外部变量。
33、程序不能确定它已经读入的输入是否足够,除非超前多读入一些输入:每当程序多读入一个字符时,就把它压回输入中,对代码其余部分而言就好像没有读入该字符一样。
4.4
34、外部变量或函数的作用域从声明它的地方开始,到其所在的(待编译的)文件的末尾结束。
35、另一方面,如果要在外部变量的定义之前使用该变量,或者外部变量的定义与变量的使用不在同一个源文件中,则必须在相应的变量声明中强制性地使用关键字extern。
36、在一个源程序的所有源文件中,一个外部变量只能在某个文件中定义一次,而其他文件可以通过extern声明来访问它(定义外部变量的源文件中也可以包含对该外部变量的extern声明)。外部变量的初始化只能出现在其定义中。
4.6
37、用static声明限定外部变量与函数,可以将其后声明的对象的作用域限定为被编译源文件的剩余部分。
38、通常情况下,函数名字是全局可访问的,对整个程序的各个部分而言都可见。但是,如果把函数声明为static类型,则该函数名除了对该函数声明所在的文件可见外,其他文件都无法访问。
39、static类型的内部变量是一种只能在某个特定函数中使用但一直占据存储空间的变量。
4.7
40、register声明告诉编译器,它所声明的变量在程序中使用频率较高。其思想是,将register变量放在机器的寄存器中,这样可以使程序更小、执行速度更快。
41、register声明只适用于自动变量以及函数的形式参数。
42、无论寄存器变量实际上是不是存放在寄存器中,它的地址都是不能访问的。
4.10
43、我们考虑将一个数作为字符串打印的情况。使用递归,函数首先调用它自身打印前面的(高位)数字,然后再打印后面的数字。
4.11
44、#include命令:如果文件名用引号引起来,则在源文件所在位置查找该文件;如果在该位置没有找到文件,或者如果文件名是用尖括号<与>括起来的,则将根据相应的规则查找该文件。
45、#define指令占一行,替换文本是#define指令行尾部的所有剩余部分内容,但也可以把一个较长的宏定义分为若干行,这时需要在待续的行末尾加上一个反斜杠符\。
46、宏定义中也可以使用前面出现的宏定义。替换只对记号进行,对括在引号中的字符串不起作用。
47、如果对各种类型的参数的处理是一致的,则可以将同一个宏定义应用于任何数据类型,而无需针对不同的的数据类型需要定义不同的函数。
48、可以通过#undef指令取消名字的宏定义,这样做可以保证后续的调用,而不是宏调用。
49、如果在替换文本中,参数名以#作为前缀则结果将被扩展为由实际参数替换该参数的带引号的字符串。例如:#define dprint(expr) printf(#expr“ = %g\n”,expr) 使用语句dprint(x/y);调用该宏时,该宏将被扩展为:printf(“x/y” “= %g\n”, x/y);
50、如果替换文本中的参数与##相邻,则该参数将被实际参数替换,##与前后的空白符将被删除。例如:#define paste(front,back) front## back 因此,宏调用paste(name,1)的结果将建立记号name1。
51、在#if语句中可以使用表达式defined(名字),该表达式的值遵循下列规则:当名字已经定义时,其值为1;否则,其值为0。
52、C语言专门定义了两个预处理器语句#ifdef与#ifndef,它们用来测试某个名字是否已经定义。
第5章 指针与数组
5.1
53、机器的一个字节可存放一个char类型的数据,两个相邻的字节存储单元可存储一个short类型的数据,而4个相邻的字节存储单元可存储一个long类型的数据。指针是能够存放一个地址的一组存储单元(通常是两个或4个字节)。
54、地址运算符&只能应用于内存中的对象,即变量与数组元素。它不能作用于表达式、常量或register类型的变量。
55、int *ip该声明语句表明表达式*ip的结果是int类型。
56、double *dp, atof(char *)表明,在表达式中,*dp和atof(s)的值都是double类型。
57、一元运算符*和&的优先级比算术运算符的优先级高。如,*ip += 1将ip所指对象的值加1,它等同于++*ip或(*ip)++语句的执行结果。语句(*ip)++中的圆括号是必需的,否则,该表达式将对ip进行加一运算,而不是对ip指向的对象进行加一运算,这是因为,类似于*和++这样的一元运算符遵循从右至左的结合顺序。
5.3
58、pa + i是数组元素a[i]的地址,*(pa + i)引用的是数组元素a[i]的内容。pa + i指向pa所指向的对象之后的第i个对象。
59、数组名所代表的就是该数组最开始的一个元素的地址。
60、对数组元素a[i]的引用也可以写成*(a + i)这种形式。相应地,如果pa是一个指针,那么,在表达式中也可以在它的后面加下标。pa[i]与*(pa + i)是等价的。简而言之,一个通过数组和下标实现的表达式可等价地通过指针和偏移量实现。
61、在函数定义中,形式参数char s[];和char *s;是等价的。
5.4
62、指针与整数之间不能相互转换,但0是唯一的例外:常量01可以赋值给指针,指针也可以和常量0进行比较。
63、如果指针p和q指向同一个数组的成员,那么它们之间就可以进行类似于==、!=、<、>=的关系比较运算。
64、任何指针与0进行相等或不等的比较运算都有意义。但是,指向不同数组的元素的指针之间的算术或比较运算没有定义。
65、在计算p+n时,n将根据p指向的对象的长度按比例缩放,而p指向的对象的长度则取决于p的声明。
66、如果p和q指向相同数组中的元素,且p<q,那么q-p+1就是位于p和q指向的元素之间的元素的数目。
5.5
67、字符串常量占据的存储单元数比双引号内的字符数大1。
68、下面两个定义之间有很大的差别:
char amessage[] = “now is the time”;
char *pmessage = “now is the time”;
数组中的单个字符可以进行修改,但amessage始终指向同一个存储位置。另一方面,pmessage是一个指针,其初值指向一个字符串常量,之后它可以被修改以指向其他地址,但如果视图修改字符串的内容,结果是没有定义的。
69、表达式同’\0’的比较是多余的,因为只需要判定表达式的值是否为0即可。
void strcpy(char *s, char *t)
{
while (*s++ = *t++);
}
70、*--p在读取指针p指向的字符之前先对p执行自减运算。
5.7
71、可以将逻辑表达式用做数组的下标。
72、二维数组实际上是一种特殊的一维数组,它的每个元素也是一个一维数组。
73、如果将二维数组作为参数传递给函数,那么在函数的参数声明中必须指明数组的列数。函数调用时传递的是一个指针,它指向由行向量构成的一维数组。
74、int (*daytab)[13];这种声明形式表明参数是一个指针,它指向具有13个整形元素的一维数组。因为方括号[]的优先级高于*的优先级,所以上述声明中必须使用圆括号。
5.10
75、调用主函数main时,它带有两个参数。第一个参数(习惯上称为argc,用于参数计数)的值表示运行程序时命令行中参数的数目;第二个参数(称为argv,用于参数向量)是一个指向字符串数组的指针,其中每个字符串对应一个参数。我们通常用多级指针处理这些字符串。
76、按照C语言的约定,argv[0]的值是启动该程序的程序名。
77、ANSI标准要求argv[argc]的值必须为一空指针。
78、也可以将printf语句写成下列形式:
printf((argc > 1) ? “%s “ : “%s”, *++argv);这就说明,printf的格式化参数也可以是表达式。
79、*++argv是一个指向参数字符串的指针,因此(*++argv)[0]是它的第一个字符(另一种有效形式是**++argv)。
5.11
80、在调用函数qsort的语句中,strcmp和numcmp的函数的地址。因为它们是函数,所以前面不需要加上取地址运算符&,同样的原因,数组名前面也不需要&运算符。
81、指针数组参数的类型为通用指针类型void *。由于任何类型的指针都可以转换为void *类型,并且在将它转换回原来的类型时不会丢失信息,所以,调用qsort函数时可以将参数强制转换为void *类型。
82、int (*comp)(void *, void *) 它表明comp是一个指向函数的指针,该函数具有两个void*类型的参数,其返回值类型为int。comp的使用和其声明是一致的,comp是一个指向函数的指针,*comp代表一个函数。下列语句是对该函数进行调用:(*comp)(v[i], v[left])。
83、(int (*)(void *, void *))strcmp 表示函数指针的强制类型转换。将strcmp转换为返回值为int,参数类型为两个void *类型指针的函数。
第6章 结构
6.1
84、关键字struct后面的名字是可选的,称为结构标记。
85、struct声明定义了一种数据类型。在标志结构成员表结束的右花括号之后可以跟一个变量表,这与其他基本类型的变量声明是相同的。例如:struct { … } x,y,z;
6.2
86、结构类型的参数和其他类型的参数一样,都是通过值传递的。
87、在所有运算符中,下面4个运算符的优先级最高:结构运算符”.”和
”->”、用于函数调用的”()”以及用于下标的”[]”,因此,它们同操作数之间的结合也最紧密。例如,对于结构声明
struct {
int len;
char *str;
} *p;
表达式++p->len将增加len的值,而不是增加p的值,这是因为,其中隐含的括号关系是++(p->len)。可以使用括号改变结合次序。例如:
(++p)->len将先执行p的加1操作,在对len执行操作;而(p++)->len则先对len执行操作,然后再将p加1(该表达式中的括号可以省略)。
同样的道理,*p->str读取的是指针str所指向的对象的值;*p->str++先读取指针str所指向的对象的值,然后再将str加1(与*s++相同);
(*p->str)++将指针str指向的对象的值加1;*p++->str先读取指针str指向的对象的值,然后再将p加1。
6.3
88、C语言提供了一个编译时一元运算符sizeof,它可用来计算任一对象的长度。
89、#define NKEYS (sizeof keytab / sizeof keytab[0])
使用这种方法,即使类型变了,也不需要改动程序。
6.4
90、两个指针之间的加法运算是非法的。
91、不要认为结构的长度等于各成员长度的和。因为不同的对象有不同的对齐要求,所以,结构中可能会出现未命名的“空穴”。
6.5
92、一个包含其自身实例的结构是非法的。
6.7
93、声明typedef int Length;将Length定义为与int具有同等意义的名字。类型Length可用于类型生命吗,类型转换等,它和类型int完全相同。
94、声明typedef char *String;将String定义为与char*或字符指针同义。
95、由于typedef是由编译器解释的,因此它的文本替换功能要超过预处理器的能力。例如:typedef int (*PFI)(char *, char *);该语句定义了类型PFI是“一个指向函数的指针,该函数具有两个char *类型的参数,返回值类型为int”。
6.8
96、联合类型:读取的类型必须是最近一次存入的类型。
97、联合只能用其第一个成员类型的值进行初始化。
6.9
98、位字段,或简称字段,是“字”中相邻位的集合。“字”是单个的存储单元,它同具体的实现有关。
99、struct {
unsigned intis_keyword : 1;
unsigned intis_extern : 1;
unsigned intis_static : 1;
} flags;
这里定义了一个变量flags,它包含3个一位的字段。冒号后的数字表示字段的宽度(用二进制位数表示)。
100、字段可以不命名,无名字段(只有一个冒号和宽度)起填充作用。特殊宽度0可以用来强制在下一个字边界上对齐。
101、字段不是数组,并且没有地址,因此对它们不能使用&运算符。
第7章 输入与输出
7.2
102、函数printf的返回值为打印的字符数。
103、函数printf在字符%和转换字符中间可能一次包含下列组成部分:
符号,用于指定被转换的参数按照左对齐的形式输出。
数,用于指定最小字段宽度。转换后的参数将打印不小于最小字段宽度的字段。如果有必要,字段左边(如果使用左对齐的方式,则为右边)多余的字符位置用空格填充以保持最小字段宽。
小数点,用于将字段宽度和精度分开。
数,用于指定精度,即指定字符串中要打印的最大字符数、浮点数小数点后的位数、整型最少输出的数字数目。
字母h或l,字母h表示将整数作为short类型打印,字母l表示将整数作为long类型打印。
104、在转换说明中,宽度或精度可以用星号*表示,这时,宽度或精度的值通过转换下一参数(必须为int型)来计算。例如,为了从字符串s中打印最多max个字符,可以使用下列语句:printf(“%.*s”,max,s);
7.3
105、函数printf的正确声明形式为:int printf(char *fmt, …)
其中,省略号表示参数表中参数的数量和类型是可变的。省略号只能在出现在参数表的尾部。
106、标准头文件<stdarg.h>中包含一组宏定义,它们对如何遍历参数表进行了定义。va_list类型用于声明一个变量,该变量将依次引用各参数。我们将该变量称为ap,意思是“参数指针”。宏va_start将ap初始化为指向第一个无名参数的指针。在使用ap之前,该宏必须被调用一次。每次调用va_arg,该函数都将返回一个参数,并将ap指向下一个参数。va_arg使用一个类型名来决定返回的对象类型、指针移动的步长。最后,必须在函数返回之前调用va_end,以完成一些必要的清理工作。
7.4
107、成功匹配并赋值的输入项的个数将作为scanf的函数值返回,所以,该函数的返回值可以用来确定已匹配的输入项的个数。如果到达文件的结尾,该函数将返回EOF。下一次调用scanf函数将从上一次转换的最后一个字符的下一个字符开始继续搜索。
108、输入函数sscanf,它用于从一个字符串(而不是标准输入)中读取字符序列:
Int sscanf(char *string, char *format, arg1, arg2, …)
它按照格式参数format中规定的格式扫描字符串string,并把结果分别保存到arg1、arg2、…这些参数中。这些参数必须是指针。
格式串通常都包含转换说明,用于控制输入的转换。格式串可能包含下列部分:
空格或制表符,在处理过程中将被忽略。
普通字符(不包括%),用于匹配输入流中下一个非空白符字符。
转换说明,依次由一个%、一个可选的赋值禁止符*、一个可选的数值(指定最大字段宽度)、一个可选的h、l或L字符(指定目标对象的宽度)以及一个转换字符组成。
109、转换说明中如果有赋值禁止字符*,则跳过该输入字段,不进行赋值。输入字段定义为一个不包括空白符的字符串,其边界定义为到下一个空白符或达到指定的字段宽度。
7.5
110、FILE像int一样是一个类型名,而不是结构标记。它是通过typedef定义的。
111、类似于getchar和putchar,getc和putc是宏而不是函数。
112、文件指针stdin与stdout都是FILE*类型的对象。但它们是常量,而非变量,因此不能对它们赋值。
7.6
113、按照惯例,返回值0表示一切正常,而非0返回值通常表示出现了异常情况。
114、在主程序main中,语句return expr等价于exit(expr)。
第8章 UNIX系统接口
8.1
115、在UNIX操作系统中,所有的外围设备(包括键盘和显示器)都被看作是文件系统中的文件,因此,所有的输入/输出都要通过读文件或写文件完成。也就是说,通过一个单一的接口就可以处理外围设备和程序之间的所有通信。
8.2
116、将char类型的变量转换为unsigned char类型可以消除符号扩展问题。
8.5
117、只供标准库中其他函数所使用的名字以下划线开始,因此一般不会与用户程序中的名字冲突。
8.6
118、在UNIX系统中,目录就是文件,它包含了一个文件名列表和一些指示文件位置的信息。
附录A 参考手册
A.2.5
119、在C语言的某些实现中,还有一个扩展的字符集,它不能用char类型表示。扩展集中的常量要以一个前导符L开头(例如L’x’),称为宽字符常量。这种常量的类型为wchar_t。
A.2.6
120、与字符常量一样,扩展字符集中的字符串字面值也以前导符L表示,如L”…”。宽字符字符串字面值的类型为“wchar_t类型的数组”。
A.7.1
121、对于某类型T,如果某表达式或子表达式的类型为“T类型的数组”,则此表达式的值是指向数组第一个对象的指针,并且此表达式的类型将被转换为“指向T类型的指针”。如果此表达式是一元运算符&或sizeof,则不会进行转换。
A.7.4
122、sizeof运算符应用于数组时,其值为数组中字节的总数。
A.7.18
123、由逗号分隔的两个表达式的求值次序为从左到右,并且左表达式的值被丢弃。
A.8.1
124、函数内部的extern声明表明,被声明的对象的存储空间定义在其他地方。
A.8.2
125、volatile用于强制某个实现屏蔽可能的优化。
A.8.3
126、若指向某一结构的指针被强制转换为指向该结构第一个成员的指针类型,则结果将指向该结构的第一个成员。
127、如果指向某一联合的指针被强制转换为指向一个成员的指针类型,则结果将指向该成员。
A.8.6
128、对于多维数组来说,只有第一维可以缺省。
A.12.3
129、当某个标识符在某个扩展中被替换后,再次扫描并再次遇到此标识符时不再对其执行替换,而是保持不变。
130、即使执行宏扩展后得到的最终结果以#打头,也不认为它是预处理命令。
131、给定下列定义:#define cat(x, y) x ## y
宏调用cat(cat(1, 2), 3)没有定义:##阻止了外层调用的参数的扩展。因此,它将生成下列记号串:cat(1, 2)3
如果再引入第二层的宏定义,如下所示:#define xcat(x, y) cat(x, y)
我们就可以得到正确的结果。xcat(xcat(1, 2), 3)将生成123,因为xcat自身的扩展不包含##运算符。
附录B 标准库
B.1
132、文本流是由文本行组成的序列,每一行包含0个或多个字符,并以’\n’结尾。二进制流是由未经处理的字节构成的序列。
B.1.2
133、printf在%与转换字符之间可以包括标志:
- 指定被转换的参数在其字段内左对齐
+ 指定在输出的参数在其字段内左对齐
空格 如果第一个字符不是正负号,则在其前面加上一个空格
0 对于数值转换,当输出长度小于字段宽度时,添加前导0进行填充。