《C和指针》全书"警告与编程提示"总结笔记

来源:互联网 发布:西安电子大学网络教育 编辑:程序博客网 时间:2024/05/18 02:27
 ↑
全书18章:
1-6章:快速上手、基本概念、数据、语句、操作符和表达式、指针
7-12章:函数、数组、字符串和字节、结构和联合、动态内存分配、结构和指针
13-18章:高级指针、预处理器、输入和输出函数、标准函数库、抽象数据类型、运行时环境

>>第1章丶快速上手


头文件 stdlib.h 中定义了 EXIT_SUCCESS 和 EXIT_FAILURE 符号。
EXIT_SUCCESS == 0;
EXIT_FAILURE == 1;
/* 从屏幕获取字符(伪代码) */int ch;while ((ch = getchar ()) != EOF && ch != '\n'){    ch = getchar ();}
EOF实际是一个整型值,位数比字符类型要多,把ch声明为整型可以防止从输入读取的字符意外的被解释为EOF。

警告的总结:
1. 在scanf函数的标量函数前未加&取地址字符;
2. 机械的把printf函数的格式代码照搬于scanf函数;
3. 在应该使用&&或者==的地方误用了单个的&或=;

编程提示的总结:
1. 使用#include指令避免重复声明;
2. 使用#define指令给常量取名;
3. 在#include文件中放置函数原型声明;
4. 在使用下标前先检查他们的值;
5. 在while和if表达中蕴含赋值操作;
6. 如何表写一个空循环体/无限循环(死循环);
7. 始终要进行检查,确保数组不越界;

>>第2章丶基本概念


警告的总结:
1. 字符串常量的字符被错误地解释为三个字母词;
2. 编写的糟糕的注释可能会意外的中止语句;
3. 注释的不适当结束。

编程提示的总结:
1. 良好的程序风格和文档将使程序更容易阅读和维护。

>>第3章丶数据


头文件 limits.h 中说明了各种不同的整数类型特点:
字符    SCHAR_MIN    SCHAR_MAX    UCHAR_MAX(unsigned)
短整型  SHRT_MIN     SHRT_MAX     USHRT_MAX
整型    INT_MIN      INT_MAX      UINT_MAX
长整型  LONG_MIN     LONG_MAX     ULONG_MAX

警告的总结:
1. 在声明指针变量时容易采用误导的写法;
2. 误解指针声明中初始化的含义。

编程提示的总结:
1. 为了保持最佳的可移植性,把字符的值限制在有符号和无符号字符范围的交集之内,或者不要在字符上执行算术运算;
2. 用她们在使用时最自然的形式来表示字面值;
3. 不要把整型值和枚举值混在一起;
4. 不要依赖隐式声明;
5. 在定义类型的新名字时,使用typedef而不是#define;
6. 用const声明其值不会修改的变量;
7. 使用名字变量而不是字面值常量;
8. 不要在嵌套的代码块之间使用相同的变量名;
9. 除了实体的具体定义位置之外,在它的其他声明位置都使用extern关键字。

>>第4章丶语句


警告的总结:
1. 编写不会产生任何结果的表达式;
2. 确信在if语句中的语句列表前后加上花括号;
3. 在switch语句中,执行流意外地从case顺延到下一个case。

编程提示的总结:
1. 在一个没有循环体的循环中,用一个分号表示空语句,并让它独占一行;
2. for循环的可读性比while循环强,因为它把用于控制循环的表达式收集起来放一个地方;
3. 在每个switch语句中都使用default子句。

>>第5章丶操作符和表达式

/* 统计一个二进制数中1出现的个数 */#include <stdio.h>int count_one_bits (unsigned int value){int ones;//for (ones = 0; value != 0; value = value >> 1) {  /* 写法效果相同 */    for (ones = 0; value != 0; value >>=  1) {/* 如果最低位的值为1,计数增1 *///if (value % 2 != 0)        if ( (value & 1) != 0 )ones++;}return ones;}int main (void){int i = 0x10011001;int count = 0;printf ("i = %x\n", i);count = count_one_bits (i);printf ("i的二进制中bit1的个数为:%d个。\n", count);return 0;}
$: count_one_bits
i = 10011001
i的二进制中bit1的个数为:4个。

逗号操作符:
将多个表达式分隔开来,自左向右逐个进行求职,整个逗号表达式的值就是最后那个表达式的值。
    if (b+1, c/2, d>0) // 如果d>0那么整个表达式的值为真。

下标引用等效写法:
    array [index]
    *(array + index)

警告的总结:
1. 有符号值的右移位操作是不可移植的;
2. 移位操作的位数是个负值;
3. 连续赋值中各个变量的长度不一;
4. 误用=而不是==进行相等比较;
5. 误用|代替||,以及&代替&&;
6. 在不同的用于表示布尔值的非零值之间进行比较;
7. 表达式赋值的位置并不决定表达式计算的精度;
8. 编写结果依赖于求值顺序的表达式。

编程提示的总结:
1. 使用复合赋值符可以使程序更易于维护;
2. 使用条件操作符代替if语句可以简化表达式;
3. 使用逗号操作来消除多余的代码;
4. 不要混用整型和布尔型值。

>>第6章丶指针


硬件仍通过地址访问内存地址。
int *p;...*p = 12;// 错误!!p没有被初始化,如果是静态的,自动被初始化为0;如果是自动变量,不会被初始化,编译器没办法知道12这个值将存储于内存的什么位置。*&a = 12; // 合法。*和&是逆操作,但没有人会类似此情况使用。*100 = 25; // 错误!!将25存储于100的地址位置,100为整型,*只能操作指针类型*(int *)100 = 25; // 合法。(int *)100表达了地址100的位置。

警告的总结:
1. 错误地对一个未初始化的指针变量进行解引用;
2. 错误地对一个NULL指针进行解引用;
3. 向函数错误地传递NULL指针;
4. 未检测到指针表达式的错误,从而导致不可预料的结果;
5. 对一个指针进行减法运算,使它非法地指向了数组第1个元素的前面的内存位置。

编程提示的总结:
1. 一个值应该只具有一种意思;
2. 如果指针并不指向任何有意义的东西,就把它设置为NULL。

>>第7章丶函数


ADT:抽象数据类型(黑盒设计)

警告的总结:
1. 错误地在其他函数的作用域内编写函数原型;
2. 没有为那些返回值不是整型的函数编写原型;
3. 把函数原型和旧式风格的函数定义混合使用;
4. 在va_arg中使用错误的参数类型,导致未定义的结果。

编程提示的总结:
1. 在函数原型中使用参数明,可以给使用该函数的用户提供更多的信息;
2. 抽象数据类型可以减少程序对模块实现细节的依赖,从而提高程序的可靠性;
3. 当递归定义清晰的优点可以补偿它的效率开销时,就可以使用这个工具。

>>第8章丶数组


#define ARRAY_SIZE(arr)  (sizeof(arr) / sizeof((arr)[0]))
// 常用的直接取到数组长度的宏定义ARRAY_SIZE, 内核中可查看到常用。

警告的总结:
1. 当访问多维数组的元素时,误用逗号分隔下标;
2. 在一个指向未指定长度的数组的指针上执行指针运算。

编程提示的总结:
1. 一开始就编写良好的代码显然比依赖编译器来修正劣质代码更好;
2. 源代码的可读性几乎总是比程序的运行时效率更为重要;
3. 只要有可能,函数的指针形参都应该声明为const;
4. 在有些环境中,使用register关键字提高程序的运行时效率;
5. 在多维数组的初始值列表中使用完整的多层花括号能够提高可读性。

>>第9章丶字符串和字节


中文所占的strlen字符串长度是3个英文字符的字符长度。
如:strlen ("字") == strlen ("abc") == 3;

字符分类函数:iscntrl / isspace / isdigit / islower / isupper / isalpha / isalnum / ispunct / isgraph / isprint

内存操作:memcpy / memmove / memcmp / memchr / memset

警告的总结:
1. 应该使用有符号数的表达式中使用strlen函数;
2. 在表达式中混用有符号数和无符号数;
3. 使用strcpy函数把一个长字符串复制到一个较短的数组中,导致溢出;
4. 使用strcat函数把一个字符串添加到一个数组中,导致数组溢出;
5. 把strcmp函数的返回值当做布尔值进行测试;
6. 把strcmp函数的返回值1和-1进行比较;
7. 使用并非以NULL字节结尾的字符串序列;
8. 使用strncpy函数产生不一NULL字节结尾的字符串;
9. 把strncpy函数和strxxx族函数混用;
10. 忘了strtok函数将会修改它所处理的字符串;
11. strtok函数是不可再入的。

编程提示的总结:
1. 不要试图自己编写功能相同的函数来取代函数库;
2. 使用字符分类和转换函数可以提高函数的移植性。

>>第10章丶结构和联合


警告的总结:
1. 具有相同成员列表的结构声明产生不同类型的变量;
2. 使用typedef为一个自引用的结构定义名字时应该小心;
3. 向函数传递结构参数是低效的。

编程提示的总结:
1. 把结构标签和结构的typedef声明放在头文件中,当源文件需要这些声明时就可以通过#include指令把他们包含进来;
2. 结构成员的最佳排列形式并不一定就是考虑边界对齐而浪费内存空间最少的那种排列形式。

>>第11章丶动态内存分配


malloc / free 分配/释放内存,不会初始化;
calloc 分配内存,并将内存初始化为0;
realloc 修改已分配的内存块的大小;

警告的总结:
1. 不检查从malloc函数返回的指针是否为NULL;
2. 访问动态分配的内存之外的区域;
3. 向free函数传递一个并非由malloc函数返回的指针;
4. 在动态内存被释放之后再访问它。

编程提示的总结:
1. 动态内存分配有助于消除程序内部存在的限制;
2. 使用sizeof计算数据类型的长度,提高程序的可移植性。

>>第12章丶结构和指针


警告的总结:
1. 落到链表尾部的后面;
2. 使用指针时应格外小心,因为C并没有对他们提供安全网;
3. 从if语句中提炼语句可能会改变测试结果。

编程提示的总结:
1. 消除特殊情况使代码更易于维护;
2. 通过提炼语句消除if语句中的重复语句;
3. 不要仅仅根据代码的大小评估它的质量。

>>第13章丶高级指针


警告的总结:
1. 对一个未初始化的指针执行间接访问操作;
2. 在转移表中使用越界下标。

编程提示的总结:
1. 如果并非必要,避免使用多层间接访问(多级指针);
2. 把void *强制转换为其他类型的指针时必须小心;
3. 破坏性的命令行参数处理方式使你以后无法再次进行处理;
4. 不寻常的代码时钟应该加上一条注释,描述它的目的和原理。

>>第14章丶预处理器


警告的总结:
1. 不要在一个宏定义的末尾加上分号;
2. 在宏定义中使用参数,但忘了在他们周围加上括号;
3. 忘了在整个宏定义的两边加上括号。

编程提示的总结:
1. 避免用#define指令定义可以用函数实现的很长序列的代码;
2. 在那些对表达式求值的宏中,每个宏参数出现的地方都应该加上括号,并且在整个宏定义两边也加上括号;
3. 避免使用#define宏创建一种新语言;
4. 采用命名约定,使程序员很容易看出某个标识符是否未#define宏;
5. 只要合适就应该使用文件包含,不必担心它的额外开销;
6. 头文件只应该包含一组函数或数据的声明;
7. 把不同集合的声明分离到不同的头文件中可以改善信息隐藏;
8. 嵌套的#inlcude文件使我们很难判断源文件之间的依赖关系。

>>第15章丶输入输出函数


警告的总结:
1. 忘了在一条调试用的printf语句后面跟一个fflush调用;
2. 不检查fopen函数的返回值;
3. 改变文件的位置将丢弃任何被退回到流的字符;
4. 在使用fgets时指定太小的缓冲区;
5. 使用gets的输入溢出缓冲区且未被检测到;
6. 使用任何scanf系列函数时,格式代码和参数指针类型不匹配;
7. 在任何scanf系列函数的每个非数组、非指针参数前忘了加上&符号;
8. 注意在使用scanf系列函数转换double、long double、short和long整型时,在格式代码中加上合适的限定符;
9. sprintf函数的输入溢出了缓冲区且未检测到;
10. 混淆printf和scanf格式代码;
11. 使用任何printf系列函数时,格式代码和参数类型不匹配;
12. 在有些长整数长于普通整数的机器上打印长整数值时,忘了在格式代码中指定l修改符;
13. 使用自动数组作为流的缓冲区时应多加小心;

编程提示的总结:
1. 在可能出现错误的场合,检查并报告错误;
2. 操纵文本行而无需顾及他们的外部表示形式这个能力有助于提高程序的可移植性;
3. 使用scanf限定符提高可移植性;
4. 当你打印长整数时,即使所使用的机器并不需要,坚持使用l修改符可以提高可移植性。

>>第16章丶标准函数库


算术 <stdlib.h>
int ads (int value); // 返回参数的绝对值
long int labs (long int value);
div_t div (int, numerator, int denominator); // 返回商和余数到结构体div_t中
ldiv_t ldiv (long int numer, long int denom);

随机数 <stdlib.h>
int rand (void); // 从随机数种子中产生随机数
void srand (unsigned int seed);  // 随机数种子

字符串转换整数 <stdlib.h>
int atoi (char const *string);
long int atol (char const *string);
long int strtol (char const *string, char **unused, int base);
unsigned long int strtoul (char const *string, char **unused, int base);
double atof (char const *string);
double strtod (char const *string, char **unused);

<math.h>
三角函数: sin () / cos () / tan () / asin () / acos () / atan () / atan2 ()
双曲函数: sinh () / consh () / tanh ()
对数和指数函数: exp () / log () / log10 () / frexp () / ldexp () / modf ()
幂: pow () / sqrt ()
底数、定数、绝对值、余数: floor () / ceil () / fabs () / fmod ()

<time.h>
日期和时间: clock () / time () / ctime () / difftime () / asctime () / gmtime () / localtime () / strftime () / mktime ()

警告的总结:
1. 忘了包含math.h头文件可能导致数学函数产生不正确的结果;
2. clock函数可能只产生处理器时间的近似值;
3. time函数的返回值并不一定是以秒为单位的;
4. tm结构中月份的范围并不是从1到12;
5. tm结构中的年是从1900年开始计数的年数;
6. 从异步信号的处理函数中调用exit或abort函数是不安全的;
7. 当每次信号发生时,你必须重新设置信号处理函数;
8. 避免exit函数的多重调用。

编程提示的总结:
1. 对信号进行处理将导致程序的可移植性变差;
2. 使用断言可以简化程序的调试。

>>第17章丶经典抽象数据类型


警告的总结:
1. 使用断言检查内存是否分配成功是危险的;
2. 数组形式的二叉树节点位置计算公式假定数组的下标从1开始;
3. 把数据封装于对它进行操纵的模块可以防止用户不正确的访问数据;
4. 与类型无关的函数没有类型检查,所以应该小心,确保传递正确类型的数据;

编程提示的总结:
1. 避免使用具有副作用的函数可以使程序更容易理解;
2. 一个模块的接口应该避免暴露它的实现细节;
3. 将数据类型参数化,使它更容易修改;
4. 只有模块对外公布的接口才应该是公用的;
5. 使用断言来防止非法操作;
6. 几个不同的实现使用同一个通用接口使模块具有更强的可互换性;
7. 复用现存的代码而不是对它进行改写;
8. 迭代比尾部递归效率更高。

>>第18章丶运行时环境


警告的总结:
1. 是链接器而不是编译器决定外部标识符的最大长度;
2. 你无法链接由不同编译器产生的程序。

编程提示的总结:
1. 使用stdarg实现可变参数列表;
2. 改进算法比优化代码更有效率;
3. 使用某种环境特有的技巧会导致程序不可移植。



2017.07.29
全书完... ...
原创粉丝点击