《C语言程序设计现代方法》笔记

来源:互联网 发布:剑三丐帮正太捏脸数据 编辑:程序博客网 时间:2024/04/27 19:26


《C Programming: A modern Approach, Second Edition》,不错哟,重新学习一下C语言,做此笔记,持续不定期更新

1、对C程序来说,通常包含下列3个步骤(往往是自动实现的,因此人们往往会发现这项工作不是太艰巨):

    • 预处理。首先程序被交给预处理器(preprocessor),执行以#开头的命令(通常称为指令),给程序添加内容,也可以对程序进行修改。
    • 编译。修改后的程序现在可以进入编译器(compiler),编译器把程序翻译成机器指令(即目标代码)。这样的程序还是不可以运行的。
    • 链接。在最后一个步骤中,链接器(linker)把由编译器产生的目标代码和所需的其它附加代码整合在一起,这样才最终产生了完全可执行的程序。


2、多年以来,C语言一直缺少适当的布尔类型,这个问题在C99中得到了解决,它提供了_Bool类型,只能赋值为0或1,存储非零值会导致变量赋值为1;C99还提供了一个头文件<stdbool.h>,使得操作布尔值更加容易。

/**Name : pun.c*Purpose: Prints a bad pun*Author: Albert,K.N.King*/#include <stdio.h>//#include <stdbool.h>int main(void) /*Beginning of main program*/{//bool flag = true;_Bool flag = 1;if(flag) printf("To C, or not to C ,that is the question!\n");return 0;}

[scwangj@LB270108 simple]$ gcc test.c -o test[scwangj@LB270108 simple]$ ./testTo C, or not to C ,that is the question![scwangj@LB270108 simple]$

3、我们把预处理器执行的命令称为指令(directive),程序运行时执行的命令称为语句(statement)。


4、关于标识符,你可能想不到的:

    • 由一个下划线和一个大写字母开头、或者由两个下划线开关的标识符,是为标准库保留的标识符。程序不允许为任何目的使用这种形式的标识符。
    • 由一个下划线开头的标识符,被保留用作具有文件作用域的标识符和标记。除非在函数内部声明,否则不应该使用这类标识符。
    • 在标准库中所有具有外部链接的标识符,被保留用作具有外部链接的标识符。特别是所有标准库函数的名字都被保留。因此,即使文件没有包含<stdio.h>,也不应该定义名为printf的外部函数,因为在标准库中已经有一个同名的函数了。


5、main函数的结尾可以用return 0, 也可以使用exit(0),二者都是终止程序,完全等价。exit函数包含在头文件<stdlib.h>中。


6、包含小数点但却不以f结尾的常量是double型的,它比float型的值存储的更加精确更大。如果不加f,编译器可能会生成一条警告信息,告诉你存储到float型变量中的数可能超出了该变量的取值范围。


7、关于printf. C语言编译器不会检测格式串中转换说明的数量是否和输出项的数量相匹配。如

printf("%d,%d\n",i); 将正确显示变量i的值,接着显示另一个(无意义)整数值。

printf("%d\n",i,j);会显示变量i的值,但是不显示变量j的值。

    C语言也不检测转换说明是否适合要显示项的数据类型。如下面的printf函数,其中int型变量i和float型变量x的顺序旋转错误:

printf("%f, %d\n",i,x); 因为printf函数必须服从于格式串,所以它将如实地显示一个float型值,接着是是一个int型值。可是这两个值都将是无意义的。


8、关于scanf. scanf函数本质上是一种“模式匹配”函数,试图把输入的字符组与转换说明相匹配。在寻找数据的起始位置时,它会忽略空白字符(包括空格符、水平和垂直制表符、换页符和换行符)。当scanf函数遇到一个不可能属于当前项的字符时,它会把此字符“放回原处”,以便在扫描下一个输入项或者下一次调用scanf函数时再次读入。如下面4个数的排列(用☼表示换行符):

1-20.3-4.0e2☼

我们使用如下的scanf函数调用:

scanf("%d%d%f%f", &i, &j, &x, &y);

那么,第一个非空的输入字符是1,因为整数可以从1开始,所以scanf接着读取下一个字符,即- ,该字符不能出现在整数内,所以把1存入变量i,把字符-放回原处;随后scanf读取字符- , 2, 0, . ,因为不能包含小数点,所以scanf把-20存入变量j中,把小数点放回原处;接下来读取字符. , 3 , -, 因为浮点数不能在数字后面有负号,所以scanf把0.3存入变量x中,把字符-放回原处;最后scanf把-4.0x10^3存入变量y中,把换行符放回原处。

问与答:

我不能理解scanf函数如何把字符“放回原处”,并在以后再次读取?

用户从键盘输入时,程序并没有读取输入,而是把用户的输入放在一个隐藏的缓冲区中,由scanf函数来读取。scanf 函数把字符放回缓冲区中供后续读取是非常容易的。


9、当运算符/和运算符%用于负操作数时,其结果难以确定。最好避免编写依赖于由实现定义的行为的程序。


10、自增运行符和自减运算符。++i 意味着“立即自增1”,而i++则意味着“现在先用i的原始值,稍后再自增1”,我们可以放心的假设i将在下一条语句执行前进行自增。


11、根据C标准,类似c = (b = a + 2) - (a = 1); 和 j = i * i++; 这样的语句都会导致“未定义的行为(undefine behavior)” ,当程序中出现未定义的行为时,后果是不可预料的。不同的编译器给出的编译结果可能是不同的,但这还不是唯一可能发生的事情:首先程序可能无法通过编译,就算通过了编译也可能无法运行,即使可以运行也可能崩溃、不稳定或者产生无意义的结果。换句话说,应该像躲避瘟疫一样避免未定义的行为。

12、“悬空else”的问题。C语言中,else子句应该属于离它最近的且还未和其它else匹配的if语句。如

if (y != 0) if(x != 0)result = x / y;elseprintf("Error: y is equal to 0 !\n");

它正确的缩进格式应如下:

if (y != 0) if(x != 0)result = x / y;elseprintf("Error: y is equal to 0 !\n");
可以把内层的if语句用花括号括起来:

if (y != 0) {if(x != 0)result = x / y;elseprintf("Error: y is equal to 0 !\n");}

13、关于条件表达式

问:如果i是int型变量,而f是float型变量,那么条件表达式(i > 0 ? i : f) 是哪一种类型的值?

答:如问题中出现的那样,当in型和float型的值混合在一个条件表达式中时,表达式的类型为float型。如果i > 0 为真,那么变量i转换为float型后的值就是表达式的值。


14、关于goto语句。goto语句不是天生的魔鬼,只是通常它有更好的替代方法;使用过多的goto语句的程序会迅速退化成“垃圾代码”,因为控制可以随意跳来跳去。由于 goto语句既可以向前跳又可以向后跳,所以使得程序难以外阅读,而break语句只向后跳,continue只向前跳;goto语句使程序难以修改,因为它可能会使某段代码用于多种不同的目的。另C99不允许goto语句绕过变长数组的声明,因为在程序的执行过程中,遇到变长数组声明时,通常就为该变长数组分配内存空间了,用goto语句绕过变长数组的声明可能会导致程序对未分配空间的数组中元素进行访问。


15、关于字符串。

    • C语言允许对指针取下标,因此可以对字符串字面量取下标:char ch; ch = "abc"[1]; 这样ch的值将是b。
    • char digit_to_hex_char (int digit) { return "0123456789ABCDEF"[digit]; } 这个函数把0-15的数转换成等价的十六进制的字符形式。
    • 不要在需要字符串的时候使用字符(反之亦然)。如printf("\n");是合法的,而printf('\n');却是非法的。
    • 当声明用于存放字符串的字符数组时,要始终保证数组的长度比字符串的长度多一个字符,这是因为C语言规定每个字符串都要以空字符结尾,否则会出现不可预先的后果;字符串的长度取决于空字符的位置,而不是取决于用于存放字符串的字符数组的长度。


16、关于scanf和gets函数读取字符串。

    • scafn函数会跳过空白字符,然后读入字符并存储,直到遇到空白字符为止。scanf函数始终会在字符串末尾存储一个空字符。
    • 用scanf函数读入字符串永远不会包含空白字符。因此,scanf函数通常不会读入一整行输入。换行符会使scanf函数停止读入,空格符或制表符也会产生同样的结果。为了一次读入一整行输入,可以使用gets函数,它同样会在字符串末尾存储一个空字符。
    • gets不同于scanf函数的地方:
      • gets函数不会在开始读字符串之前跳过空白字符(scanf 函数会跳过)
      • gets函数会持续读入直至找到换行符才停止(scanf函数会在任意空白字符处停止)。此外,gets函数会忽略掉换行符,不会把它存储到数组中,用空字符代替换行符。


17、