说明
本文档性质为个人学习笔记。
在阅读《C Primer Plus(第六版)中文版》时,记录下的需要备忘的知识点,主要关注在C语言的特点和使用中细节上。
第17章 高级数据表示 未做记录,因为暂时用不到那里。记录的要点主要是提醒备查所以不会过于详细。
第三章 数据:数据类型关键字
P64 用%d显示float类型的值,其值不会被转换成int类型
P65 通常,退格不会擦除退回所经过的字符,但有些实现是擦除的
P65 关于缓冲区刷新输出
C标准明确规定了何时把缓冲区中的内容发送到屏幕:当缓冲区满、遇到换行字符或需要输入的时候(从缓冲区把数据发送到屏幕或文件被称为刷新缓冲区)。
第四章 printf()和scanf()
P75 sizeof运算符的圆括号使用
运算对象是类型时,圆括号必不可少,但是对于特定量,可有可无。尽管如此,还是建议所有情况下都使用圆括号。
第五章 表达式和语句
P122 副作用、序列点和完整表达式的概念
副作用(side effect)是对数据对象或文件的修改。
a = 100;
上述表达式的主要作用是对右侧项进行求值,副作用是将a变量的值设为100。
如果将常量换成函数,可能比较好理解一些:
int retnum(void)
{
return 100;
}
b = retnum();
此时函数retnum完成的任务为表达式的主要目的,将变量b设置为100是其副作用。
P128 在ANSI C之前,C使用的是函数声明,而不是函数原型
函数声明只指明了函数名和返回类型,没有指明参数类型。为了向下兼容,C现在仍然允许这样的形式:
void pound();/*ANSI C之前的函数声明 */
第六章 循环
P156 逗号运算符
逗号运算符并不局限在for循环中使用,它有两个其他性质:
首先,它保证了被它分隔的表达式从左往右求值(换言之,逗号是一个序列点,所以逗号左侧项的所有副作用都在程序执行逗号右侧项之前发生)。
其次,整个逗号表达式的值是右侧项的值。
第七章 C控制语句:分支和跳转
P192 求值顺序
apples = (5 + 3) * (9 + 6);
C把先计算哪部分的决定权留给编译器的设计者,以便针对特定系统优化设计,但是对于逻辑运算符是个例外,C保证逻辑表达式的求值顺序是从左往右。
&&和||运算符都是序列点,所以程序在从一个运算对象执行到下一个运算对象之前,所有的副作用都会生效。
而且C一旦发现某个元素让整个表达式无效,便立即停止求值。
P207 带多重选择的switch语句
形式:
switch (expression)
{
case label1 : statement1//使用break调出switch
case label2 : statement2
default : statement3
}
一般注解:
程序根据expression的值跳转至相应的case标签处,然后执行剩下的所有语句,除非执行到break语句进行重定向。
expression和case标签都必须是整数值(包括char类型),标签必须是常量或完全由常量组成的表达式。
如果没有case标签与expression的值匹配,控制则转至标有default的语句(如果有的话),否则,将将转至执行紧跟在switch语句后面的语句。
第九章 函数
P248 定义带形式参数的函数
ANSI C要求在每个变量前都声明其类型。
void function(int x, y, z); /* 无效的函数头 */
void function(int x, int y, int z);/* 有效的函数头 */
P255:这里的变量名是假名,不必与函数定义的形式参数名一致。
ANSI C也接受ANSI C之前的形式,但将其视为废弃不用的形式:
void show_n_char(char, num)
char ch;
int num;
这里,圆括号中只有参数名列表,而参数的类型在后面声明。
P252 没有返回值的return
return;
这条语句会导致终止函数,并把控制权交给主调函数,因为return后面没有任何表达式,所以没有返回值,只有在void函数中才会用到这种形式。
P253 函数的前置声明可以放在主调函数外面,也可以放在主调函数里面
函数原型都声明在使用函数之前。
P255 无参数或未指定参数的函数原型
假设有下面的函数原型:
void print_name();
一个支持ANSI C的编译器会假定用户没有用函数原型来声明函数,它将不会检查参数。
为了表明函数确实没有参数,应该在圆括号中使用void关键字:
void print_name(void);
另外,ANSI C允许使用部分原型。
P257 每级递归的递归变量都属于本级递归私有
P262 main()也可以被自己或其它函数递归调用,尽管很少这样做
P271 *和指针名之间的空格可有可无
通常,程序员在声明时使用空格,在解引用变量时省略空格。
第十章 数组和指针
P291 声明数组形参
因为数组名是该数组元素的首地址,作为实际参数的数组名要求形式参数是一个与之匹配的指针。只有在这种情况下,C才会把int ar[]和int * ar解释成一样。也就是说,ar是指向int的指针,由于函数原型可以省略参数名,所以下面4种原型都是等价的:
int sum(int *ar, int n);
int sum(int *, int);
int sum(int ar[], int n);
int sum(int [], int);
但是,在函数定义中不能省略参数名。
P299 对形式参数使用const
为了保护数组中的数据,ANSI C提供了一种预防手段。如果函数的意图不是修改数组中的数据内容,那么在函数原型和函数定义中声明形式参数时应使用关键字const。
第十一章 字符串和字符串函数
P326 静态字符串在运行时的修改是未定义的
char * word = "frame";
word[1] = "l";
编译器可能允许这样做,但是对当前C标准来说,这样的行为是未定义的。例如,这样的语句可能导致内存访问错误。原因是,编译器可以使用内存中的一个副本来表示所有完全相同的字符串字面量。
P332 puts()和fputs()的输出
再次提醒读者注意,puts()函数会在待输出字符串末尾添加一个换行符,而fputs()不会这样做。
P338 puts()函数的停止位置
puts()函数在遇到空字符时就停止输出,所以必须确保有空字符。
P342 字符串常量的串联特性
P353 strcpy()的其他属性
strcpy()还有两个有用的属性:
第一,strcpy()的返回类型是char *,该函数返回的是第一个参数的值,即第一个字符的地址。
第二,第1个参数不必指向数组的开始。这个属性可用于拷贝数组的一部分。
P358 请注意,那些使用const关键字的函数原型表明,函数不会更改字符串
P358 size_t类型
size_t类型是sizeof运算符返回的类型。
C规定sizeof运算符返回一个整整数类型,但并未指明是哪种整数类型,所以size_t在一个系统中可以是unsigned int,而在另一个系统中可以是unsigned long。
string.h头文件针对特定系统定义了size_t,或者参考有其他有size_t定义的头文件。
P364 main()函数的参数
P366 具有错误检测功能的strtol()函数
strtol()函数最多可以转换三十六进制,‘a’~'z’字符都可以用作数字。
P368 fgets()函数获取一行输入,用于代替已被废弃使用的gets()
第十二章 存储类别、链接和内存管理
P376 C变量有三种链接属性
外部链接、内部链接或无链接。
一些程序员把“内部链接的文件作用域”简称为“文件作用域”,把“外部链接的文件作用域”简称为“全局作用域”或“程序作用域”。
P376 C对象的存储期
C对象有4种存储期:静态存储期、线程存储期、自动存储期和动态分配存储期。
如果对象有静态存储期,那么它在程序执行期间一直存在。文件作用域变量具有静态存储期,无论是内部链接还是外部链接。
线程存储期用于并发程序设计,程序执行可以被分为多个线程。具有线程存储期的对象,从被声明到线程结束一直存在。以关键字_Thread_local声明一个对象时,每个线程都获得该变量的私有备份。
块作用域的变量通常都具有自动存储期。
变长数组稍有不同,变长数组的存储期从声明处到块的末尾,而不是从块的开始处到块的末尾。
P377 auto说明符
auto是存储类别说明符(storage-class specifier)。auto关键字在C++中的用法完全不同,如果编写C/C++兼容的程序,最好不要使用auto作为存储类别说明符。
P380 使用register存储类别说明符以便请求将变量存储在CPU的寄存器中
P382 静态变量和外部变量在程序被载入内存时已经执行完毕
P382 不能在函数的形参中使用static
P384 外部变量的初始化
与自动变量不同的是,如果未初始化外部变量,它们会被自动初始化为0。这一原则也适用于外部定义的数组元素。
与自动变量的情况不同,只能使用常量表达式初始化文件作用域变量。
P385 定义和声明
int tern = 1;
main()
{
extern int tern;
...
}
上述程序中,第一次声明被称为定义式声明(defining declaration),第二次声明被称为引用式声明(referencing declaration)。关键字extern表明该声明不是定义,因为它指示编译器去别处查询其定义。
extern int tern = 1;
main()
{
...
}
如果是这样的写法,这样的声明不会引起分配存储空间。因此不要用extern创建外部定义,只用它来引用现有的外部定义。
P387 C语言中的存储类别说明符
C语言具有6个存储类别说明符:auto、register、static、extern、_Thread_local、typedef。typedef关键字与任何内存存储无关,把它归于此类有一些语法上的原因。
在绝大多数情况下,不能在声明中使用多个存储类别说明符,所以这意味着不能使用多个存储类别说明符作为typedef的一部分。唯一例外的是_Thread_local,它可以和static或extern一起使用。
P389 除非使用static关键字,否则一般函数声明都默认为extern
P400 calloc()函数的特性
calloc()函数把块中的所有位设置为0。(注意,在某些硬件系统中,不是把所有位设置为0来表示浮点值0。)
P405 const和volatile的同时使用
可以同时使用const和volatile限定一个值。只能在声明中同时使用这两个限定符,它们的顺序不重要,如下图所示:
volatile const int loc;
const volatile int * ploc;
第十三章 文件输入/输出
P416 main()函数中的return和exit()
根据ANSI C的规定,在最初调用的main()中使用return和exit()的效果相同。但如果main()在一个递归程序中,exit()仍然糊终止程序,即使在除main()以外其它函数中调用exit()也能结束整个程序。
P421 程序打开文件数量限制
程序都是单独打开或关闭每个文件。同时打开的文件数量是有限的,这个限制取决于系统和实现,范围一般是10~20。
P435 数据保存方式的选择
如果要在不损失精度的前提下保存或恢复数值数据,请使用二进制模式以及fread()和fwrite()函数。
如果打算保存文本信息并创建能在普通文本编辑器查看的文本,请使用文本模式和函数(如gets()和fprintf())。
第十四章 结构和其他数据
P454 结构其他特性
现在的C允许把一个结构赋值给另一个结构,但是数组不能这样做。
现在的C(包括ANSI C),函数不仅能把结构本身作为参数传递,还能把结构作为返回值返回。
P467 记录和字段
存储在一个结构中的整套信息被称为记录(record),单独的项被称为字段(field)。
P474 枚举类型
可以用枚举类型(enumerated type)声明符号名称来表示整形常量。实际上enum常量是int类型,因此只要能使用int类型的地方就可以使用枚举类型。枚举类型的目的是提高程序的可读性。
C枚举类型的一些特性并不适用于C++。例如,C允许枚举变量使用++运算符,但是C++标准不允许。
默认情况下,枚举列表中的常量都被赋予0、1、2等。
在枚举声明中,可以为枚举常量指定整数值。如果只给一个枚举常量赋值,没有对后面的枚举常量赋值,那么后面的常量会被赋予后续的值。
枚举类型只能在内部使用。
P477 共享名称空间
C语言使用名称空间()标识程序中的各部分。名称空间是分类别的。在特定作用域中的结构标记、联合标记和枚举标记都共享相同的名称空间,该名称空间和普通变量使用的空间不同。
所以下面的代码不会产生冲突:
struct rect
{
double x;
double y;
};
int rect;
第十五章 位操作
P499 切换位
切换位指的是打开已关闭的位或关闭已打开的位。可以使用按位异或运算符(^)切换位。
假设b是一个位(1或0),如果b为1,则1b为0;如果b为0,则1b为1。另外无论b为1还是0,0^b均为b。
P505 位字段
P516 C有两种访问位的方法:按位运算符和在结构中创建位字段
第十六章 C预处理器和C库
P522 编译器将用一个空格字符替换每一条注释
P523 宏定义还可以包含其他宏(一些编译器不支持这种嵌套功能)
P525 预处理器可能将替换体解释为记号或字符串
P525 ANSI标准中,如果出现重复宏定义,只有新旧定义完全相同才允许重定义
P527 必要时使用足够多的圆括号来确保运算和结合的正确顺序
一般而言,不要在宏中使用递增或递减运算符,以免出现如下所示的未定义语句:
++x*++x;
P528 C允许在字符串中包含宏参数。
在函数宏的替换体中,#号作为一个预处理运算符,可以把记号转换成字符串。这个过程称为字符串化(stringizing)。
P529 变参宏:…和__VA_ARGS__
P535 如果宏通过头文件引入,那么#define在文件中的位置取决于#include指令的位置
P538 预处理运算符defined
如果它的参数使用#define定义过,则返回1;否则返回0.
#if defined (VAX)
P552 通用工具库的atexit()函数
atexit()注册函数列表中的函数,当调用exit()时会执行这些函数。
ANSI保证,在这个列表中至少可以放32个函数。exit()执行这些函数的顺序与列表中函数的顺序相反,即最后添加的函数最先执行。
注意,main()函数结束时会隐式调用exit()。
P555 指针赋值时的强制类型转换,在C中是可选的,在C++中是必须的
P558 memcpy()和memmove()
两函数原型为:
void *memcpy(void * restrict s1, const void * restrict s2, size_t n);
void *memmove(void *s1, const void *s2, size_t n);
编译器不会在本不该使用memcpy()时禁止你使用,作为程序员,在使用该函数时有责任确保两个区域不重叠。
P560 可变参数:stdarg.h
————2020-1-9@燕卫博————