第2章
环境:翻译环境: 源代码转化成可执行的机器指令。
执行环境:用于实际执行代码。
翻译:源文件-〉目标文件-〉可执行文件(通过链接器将多个目标文件捆绑在一起)
编译过程:预处理器-〉源代码经过解析产生目标代码(这个过程中是绝大多数错误和警告产生的地方)-〉优化器(就是对目标代码进行进一步优化,使效率更高)
执行:首先,程序被加载到内存,那些不是存储在栈中的未被初始化的变量将在这个时候被初始化;然后,程序的执行便开始了,负责处理一些日常事务,如收集命名行参数以便使程序能够访问他们,并开始调用main函数;现在,开始执行程序代码,在绝大多数机器里,程序将使用一个运行时堆栈,用于存储函数的局部变量和返回地址。程序同时也使用静态内存,存储与静态内存中的变量在整个程序执行过程中将一直保持不变。最后,程序终止,正常终止的话,是main函数返回。
2.2词法规则
三字母符 ??
转义序列 /
注释:注意1.别把代码给错误的注释掉了2.别在结尾写成了*?
标志符:
编程风格:
空行用于分隔不同的逻辑代码段,它们是按照功能分段的。
If和相关语句的括号是这些语句的一部分,而不是他所测试的表达式的一部分。
在绝大多数的操作符使用中,中间都隔以空格,复杂的操作符中省略空格,这样有助于显示表达式的分组。
绝大多数注释都是称快出现的,因为这样比较能引起注意。
在函数的定义中,返回类型出现于独立的一行中,而函数的名字则在下一行的起始处。
第三章 数据
1.长整型至少和整型一样长,整型至少和短整型一样长。
2.可移植性问题:把存储与char变量的值,限制在signed char 和unsigned char 两者的交集之中,这样可以获得最大程度的可移植性,又不牺牲效率。并且只有当char 类型显示声明为signed 或 unsigned 时,才对它执行算术运算。
3.使用字符常量所产生的都是正确的值,所以它能提高程序的可移植性。
4.枚举类型:如果没有对一个值赋值,那么它的值是前面这个值的数+1
5.指针:选择NUL作为字符串的终止符的原因是因为它是一个可打印的字符。
如果你想修改字符串请先把它存在字符数组中。字符串常量的直接值是一个指针,而不是这些字符本身。
3.2基本声明
声明简单数组,c数组另一个值得关注的地方是,编译器并不检查程序对数组下标的引用是否在数组的合法范围之内。如果下标值是从那些已知正确的值计算而来,那么就无需检查它的值,如果一个用作下标的值是根据某种方法从用户输入的数据产生而来的,那么使用它之前必须进行检测,确保它位于有效的范围之内。
第五章 操作符
注意:1.注意检查if 和while中=和==的区别。
2.断路求值
3.条件操作符的优势
4.逗号表达式的使用场合和它的一个小技巧
5.下标引用:总是从0开始,不对下标进行有效性检验
6.避免混合使用整形值和布尔值
7.左值和右值的区别:左值必须应该指定一个固定的位置,左值意味着一个位置,而右值意味着一个值
8.操作符的优先级是决定相邻操作符之间谁先计算,谁后计算
9.如果顺序会对导致结果产生区别,最好使用临时变量
第六章 指针
1.在对指针进行间接访问之前,必须先对指针进行初始化
2.NULL指针给了一种方法,表示某个特定的指针目前没有指向任何东西,对NULL指针进行间接引用是非法的
3.如果已经知道指针将被初始化为何种地址,就将该指针初始化为该地址,如果没有将指针初始化为NULL指针
第七章 函数
1. 函数中 传值调用和传址调用的区别
2. 黑盒不怎么明白
3. 递归函数的两个条件:(1).有限制条件(2).每次操作后越来越接近这个限制条件
4. 递归函数运行设计一些开销:参数必须压到堆栈中,为局部变量分配内存空间
第八章 数组
1. 数组名是指向某种类型的指针常量,表示数组第一个元素的地址
2. 下标绝不会比指针更有效率,指针有时会比下标更有效率
3. 当你根据某个固定的数目在一个数组中移动时,使用指针代码将比使用下标产生更加有效率的代码,当这个增量是1,并且机器具有地址自动增量模型时,这点表现更加突出。
4. 声明为寄存器的指针比静态内存和堆栈中的指针效率更高
5. 如果你可以通过测试一些已经初始化并经过调整的内容来判断循环是否终止,那么你就不需要一个单独的计数器。
6. 函数中的形式参数传递的是数组时,不指定数组大小的原因是因为数组传递的时候是以指针的形式传递的。
7. 多位数组的名称表示的是,指向第一个数组的指针
8. max[3,4]等价于max[3]
9. 多维数组中初始化时,记得要把花括号加上(更容易区分,还有就是可以给缺少元素的初始化为0)
10. 只要有可能函数的形式参数都应该声明为const
第九章 字符串
1.
第十章 结构和联合
1. 如果你想在多个元文件中使用同一类型的数据结构,你应该把标签生命或typedef形式的声明放在一个头文件中。
2. 结构成员可以是标量,数组,指针甚至是其他结构
3. 下标操作和点操作具有相同的优先级,都是从左到右进行操作。
4. 指针对结构内的数据的调用,采用两种操作符,点操作和-〉操作。
5. struct sample{
int a ;
sample *b;
};是合法的
struct sample{
int a ;
sample b;
};是不合法的
Typedef struct sample{
int a ;
sample *b;
} sample;是合法的
Typedef struct {
int a ;
sample *b;
} sample;是非法的
6. 注意结构的不完整声明
第十一章 动态内存分配
1. malloc 函数用来实现从内存中提取一块合适的内存,并向该程序返回一个指向这个内存的指针。这块内存现在并没有进行任何初始化。当一块以前使用的内存没有使用的时候,程序调用free函数将它归还给内存池供以后使用。
2. malloc函数当没有内存可以分配时,就会返回一个null指针,所以对null指针的判断很重要。
3. malloc和calloc的区别:一是,后者在返回指针之前先把内存初始化为0;二是,calloc包括需要元素的数量和每个元素的字节数,根据这个值,它能计算出到底需要分配多少内存。
4. realloc用于修改原先已经分配好的内存大小。可以扩大也可以缩小,扩大时,前面的存储内容不变,后面的不被初始化;缩小时,尾部的内存便被砍掉。如果原先的内存不能改变大小,该函数将会重新分配一块新的内存,将原先的内存中的内容复制过来。因此使用realloc 后就不能在使用指向旧内存的指针了,而是应该使用realloc 返回的的新指针。
5. 如果偶尔调用了malloc,程序将由于语法错误而无法编译,在alloc中必须加入#undef指令,这样他才能调用malloc 而不至于出现语法错误。
6. 操作内存时超出了分配内存的边界。
7. 传递给free的指针必须是malloc alloc,realloc 返回的指针,让他释放一个非动态分配的内存可导致程序立即终止或在晚些时候终止。试图释放动态内存的一部分内存也会出现同样的错误。动态分配的内存必须整块释放。
8. 不要使用已经被释放的内存。
9. 分配的内存在使用完毕后不进行释放将会产生内存泄露。一个持续分配却一点都不是放内存的程序最终将耗尽可用的内存。
10. 动态分配内存一个常见的用途就是为那些运行时才知道长度的数组分配内存空间。
第十二章 使用结构和指针
1. 在链表中,每个节点包含一个链表下一个节点的指针,连表最后一个指针字段的值为null
2. 为了记住链表的起始位置,可以使用一个根指针(root pointer),跟指针指向链表的第一个节点。。注意跟指针只是一个指针,它不包含任何数据。
3. 事实上链表的节点可以存储在内存的各个地方
4. 链表中如果你想遍历其他的节点,你只能从根节点开始。可以对链表进行排序
5.语句提炼 :如果语句对if语句执行没有影响我们可以将这个语句提前,如果语句在if执行后对这条语句没有影响,可以将这条语句放到后面。
6.双链表插入逻辑的提炼
7.不要仅仅以代码的大小衡量代码的效率
第十三章:高级指针话题
1. 指针的多层间接访问
2. 函数指针
3. 函数只能返回标量值,不能返回数组
4. 对函数指针进行操作之前,必须把它初始化为指向某个函数,函数指针的初始化也可以通过赋值操作进行完成;在函数指针的初始化之前,具有函数的原型是很重要的
5. 函数指针的两个作用:函数指针作为参数传递给另外一个函数,用作转移表
6. 回调函数
7. 把具体操作和选择操作份开始一个良好的程序设计方案
8. 命令行参数
9. 当一个字符串常量出现在一个表达式中时,它的值是个指针常量,便一起把这些字符的一份拷贝存储在内存的一个位置,并存储一个指向第一个字符的指针。*“xyz”表示的是x
10. 字符串常量的指针是“指向字符的指针“
11. 神秘函数
12. 如果没有必要,避免使用多层间接访问
13. 使用转移表时应该检验下标的有效性
14. 不同寻常的代码应该加上相应的注释
第十四章 预处理器
1. c与处理器要做的事情:删除注释,插入#include包含的内容文件的内容,定义和#define指令定义的符号以及确定代码的部分内容是否应该根据一些条件编译 指令进行编译
2. 如果定义的stuf比较长可以分开成几行,不过除了最后一行外其他的几行都应该在后面加上/,并且不要加上;号
3. 所有对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免由于宏被替换时被不必要的操作符和临近的操作符之间不可预料的相互作用
4. 宏不可以出现递归
5. 宏适于类型无关的
6. 具有副作用的宏
7. 宏的命名约定:一种方式是 都大写,
8. #undef 用于移出一个宏定义
9. 命令行的定义
第十五章 输入输出函数
1. ascn编码器并未被禁止在它们的库函数的基础上添加新的函数,但是标准函数必须根据标准所定义的方式执行。如果你关心可移植性只要避免使用费标准函数就行了。
2. perror 以一种简单,统一的方式报告错误
3. 良好的编程实践要求任何可能产生错误的操作,都应该在执行之后进行检查,确定它是否成功。
4. 注意只有当库函数失败时,errno才能被设置,当函数成功运行时,errno的值不会被修改。
5. exit 函数中的参数和main中的参数状态是一致的,用于提示程序是否正常完成,这个函数没有返回值,当exit结束时,程序已经消失,所以他无返回值而言。
6. 标准i/o函数库还引用了缓存i/o的概念,提高了绝大多数程序的效率
7. 这个函数库存在两个缺点:1。它在某种特定的类型的机器上实现的,并没有对其他不同特性的机器多作考虑。2.设计这发现上述问题后,试图去修正,但是只要他们这么作了这个函数库就不标准了,程序的可移植性就会降低。
8. 使用标准 输入输出时,这种缓存坑引起混淆,只有当他们与交互设备并无联系时,才会进行完全缓存。
9. 事实上,如果程序失败,缓存奴输出可能不被写入,这就可能使得关于程序出现错误的位置不正确,这个的解决方法是在用于调适的printf后面加上fflush, fflush迫使缓存区的内容立即写入,不管他立即已满
10. 标准错误就是错误信息写入的地方
11. EOF所选择的实际值比一个字符要多几位
12. 打开流和关闭流,对关闭流是否进行检验的标准是:问两个问题,操作成功应该执行什么 ,操作失败应该执行什么 ;如果答案一样的话,可以不进行检验否则进行检验。
13. fget fput是真正的函数,但是getc putc getchar putchar 都是定义的宏
14. ungetc的用法
15. 未格式化的i/o操作
16. 二进制数据避免了在数值转换为字符串过程中所涉及到的开销和精度损失,但是这些机巧只能将数据被另外一个数据顺序读取时才能使用。
17. fflush迫使一个输出流的缓存区内的数据进行物理写入,不管他是否已满。
18. 随机访问是通过读取和写入先前定位到文件中需要的位置来实现。
19. 改变流缓存
20. 流错误函数,临时文件函数tmpfile,文件操纵函数
第十六章 标准库函数
1. 整型函数库:算术<stdlib.h>取绝对值,除法运算(对整型的运算包含商和余数,返回一个结构),随机数的<stdlib.h>其中有个小技巧:使用每一天的时间作为随机数产生的种子 ;字符串转换<stdlib.h>将字符串转换为数值
2. 浮点型函数库,<math.h>包含了剩余数学函数的声明,这些函数的绝大多数返回值都是double型,注意区别定义域错误和范围错误;包含三角函数,双曲函数,对数和指数函数,浮点形式,幂函数,底数,顶数,绝对值和余数<math.h>转换为double型的字符串转换函数,(书上标记的是在<stdlib.h>中,本人认为是笔误应该在<math.h>中)
3. 日期和时间函数:<time.h>处理器时间,当天时间其中有一个difftime函数用来计算两个时间的差值
4. 非本地跳转<setjmp.h>
5. 信号