《从问题到程序》第五章读书笔记
这章主要从五个方面介绍,分别为数值类型、函数和标准库函数、函数定义和程序的函数分解、c程序的结构与变量及预处理。主要是对函数整体的理解以及常用的规范进行说明。
一、数值类型
1.实数类型和整数类型
除了实数类型之外的数值类型都是整数类型。C语言将字符类型也看作整数类型可以作为整数参加运算。各种整数类型都分为带符号与无符号的两种,带符号类型表示一定范围内的正数和负数,符号类型的值都不小于0。
2.字符类型
在一般 C 语言系统里,一个字符占一个字节,其中存着字符的编码。字符类型主要用于存储文字信息和输入输出。如果把字符当作整数参加运算,所用的就是字符的编码(这是一个整数)。
3.整数类型
(1)无符号整数类型的一个特点是算术运算以对应类型的表示范围为模进行。当计算结果超出类型的表示范围时,以取模后的余数作为计算结果。 假定 unsigned 用 16 位表示,表示范围是 0~65535。 如果计算结果超出这个范围,就以得到的结果除以 65536 的余数作为结果。例如 234+65500 的结果将是 198。其他无符号类型的情况也一样。
(2)由于类型问题,计算中可能出现隐含的类型转换动作。 C 语言规定, 当各种小整数类型(short、 unsigned short 类型,各种 char 类型)的数据出现在表达式之中,计算之前先将它们转换为 int 类型的值后再参与运算,这一过程称为整数提升。在基本类型相同时, C 语言认为无符号类型是比同样有符号类型更大的类型。举例说,如果要做下面计算:2365U + 18764,首先要从整型值 18764 转换生成一个无符号整数的对应值,然后用这个新值参与计算。
(3)基本数据类型的选择
二、函数和标准库函数
C 标准库函数完成一些最常用的基本功能, 包括基本输入和输出、 文件操作、 存储管理,以及其他一些常用功能函数, 如数学函数、 数据值的类型转换函数等。
1.字符分类函数
应当在程序前部用#include 命令包含系统头文件 ctype.h。在这个头文件里还说明了两个字母大小写转换函数:
2.随机数生成函数
要使用标准库的随机数生成功能, 程序前部应包含系统文件 stdlib.h, 这个文件里描述了与随机数生成有关的函数:
int rand(void)
这是一个无参函数,每次调用将得到一个新随机整数, 其值在 0 和系统定义的符号常量RAND_ MAX 之间。不同系统里的 RAND_ MAX 可能不同,一般系统中用 32767。
void srand(unsigned seed)
这个函数用参数 seed 的值重新设置种子值, 即为生成下一个随机数而保存的一个整数值(由它出发递推,取得下一个随机数)。默认的初始种子值是 1。
三、函数定义和程序的函数分解
(1)函数调用
(2)函数封装
(3)参数传递
(4)过时的函数定义形式与原型形式
1.在写无参函数的原型时,参数表必须写成(void),不能简单写成(),因为这一形式将被看着是过时原型形式。
2.我们应坚持的正确原则是: 1)如果使用库函数,那么就必须在源文件前部用#include命令包含必要的头文件(xxx.h 文件)。 2) 对所有未能在使用前给出定义的函数(无论它是定义在本文件后面, 还是在其他源文件里),都应给出正确完整的原型说明。 3)把原型说明写在源文件最前面(不要写在函数内部),以使函数的定义点和所有使用点都能“看到”同一个原型说明。如果坚持了这些原则,就能避免函数调用与定义不一致的错误.
四、C 程序结构与变量
1.外部变量
外部定义、 外部说明是指写在源程序文件表层的定义和说明(不在函数体内)。函数定义是一种外部定义, 写在外层(不在函数体内) 的原型说明是一种外部说明。 一个外部定义或者说明总从它出现的位置开始起作用,其作用范围一直延续到文件结束。
2.静态局部变量
书中举了个小问题实际提出了对另一种变量的需求,这种变量的作用域应是局部的,定义在函数体里,从而保证信息的隐蔽性,避免其他函数无意的越权访问; 而其存在期应是全局的,因此可以跨越函数的不同调用,在两次调用间传递信息。 此外,这种变量的初始化只应进行一次, 使变量值能在函数的不同调用间保持。 C 语言的静态局部变量就是这样的。 静态局部变量的定义位置与其他局部变量一样,另用关键字 static 指明其特殊性。静态局部变量的性质就是上面三条:局部作用域、全程存在期、一次初始化。
void format(void) {
static int m = 0;
if (++m == 10) {
putchar('
');
m = 0;
}
else putchar(' ');
}
五、预处理
预处理命令以独立的预处理命令行的形式出现。 # 符号是特殊引导符号,如果源程序里某行的第一个非空格符号是 #, 那么这行就是预处理命令行。
1.宏定义
由 #define 开始的行称为宏定义命令行。 宏定义行有两种形式:
2.宏定义与函数调用的区别
(1) 宏定义和调用并不考虑类型问题。
(2)在执行方面, 宏调用将在程序加工中被在现场展开, 不会形成运行中的调用动作
3.带参宏有几个值得特别注意的问题。
(1)有些宏展开后会引起对参数的多次计算,而从宏调用的形式上完全看不到这种情况。 例如上面定义的 min,展开之后总会有一个参数表达式被计算两次,有时这种情况会引来奇怪后果。请看下面调用:
z = min(n++, m++);
展开的结果是:
z = ((n++) < (m++) ? (n++) : (m++))
无论变量 n 和 m 在语句执行前的情况如何,总会有一个变量做了两次增量操作。这个情况在程序正文中完全看不到,很可能成为程序里难以发现的错误。
(2)人们通常都在带参宏的替代正文中写许多括号,把各参数的出现都括起,也把整个段括起,以防展开后由于运算符的优先级而引起问题。假设定义了下面的求平方宏:
#define square(x) x * x
表面上看它完全正确,但在特定环境下它却可能出问题。考虑下面调用:
z = square(x + y);
宏展开后得到的是:
z = x + y * x + y;
这显然不可能是写程序的人所希望的。
3.条件编译命令
注:
一个简单猜数游戏,根据书中的讲解亲自实现了一下: