其实,这本书适合没事的时候翻翻,随便打开哪一页,应该都可以看下去的。总体来说,写的比较欢脱,不像《C和指针》和《C陷阱和缺陷》的章节标题那么严肃,有时候还以为自己在看科技杂志。不过,理解书里面的内容反而需要有较高的C语言基础,内容结构比较松散,如果觉得没有逻辑性,可能是语言基础还掌握的不够。
比较喜欢第1章关于C语言的介绍,前世今生什么的。
2015年5月1日 星期五 天气 雨
第1章 C:穿越时空的迷雾
- C语言的史前阶段:BCPL->B->New B->早期C
- C语言的早期体验(评:历史原因造成了现在C语言的一些语言特性)
- 标准I/O库和C预处理器
- K&R C
- 今日之ANSI C
- 它很棒,但符合标准吗:不可移植代码,坏代码,可移植代码
- 编译限制,ANSI C标准对一个能够成功编译的程序的最小长度做了限制
- ANSI C标准的结构,与K&R C相比,最重要的新特性就是原型
第2章 这不是Bug,而是语言特性f
- 编程语言的细节,Fortran语言经典错误,DO 10 I=1.10和DO 10 I=1,10的区别。
分析编程语言缺陷的一种方法是把所有的缺陷归类为3类:不该做的做了,该做的没做,该做但做的不合适。
- 多做之过
- switch语句中的fall through
- 相邻字符串常量被自动合并
- 太多的缺省可见性,即默认的函数作用域过宽
- 误做之过,语言中有误导性质或是不适当的特性
- 重载,许多符号甚至关键字被重载
- 有些运算符的优先级是错误的(ANSI C标准由于种种历史原因,没有在优先级方面采取措施)
- 少做之过,语言应该提供但未能提供的特性
- 空格问题
- 注释风格
- 编译器日期被破坏
- lint程序绝不应该被分离出来,lint程序寻找bug,早用,勤用
第3章 分析C语言的声明
- 只有编译器才会喜欢的语言,声明
- 声明如何形成,至少一个类型说明符+有且只有一个声明器+零个或更多声明器+一个分号
- 优先级规则,括号内的->后缀操作符->前缀操作符,按照这个顺序优先级从高到低
- 通过图表分析C语言声明
- typedef可以成为你的朋友,为一种类型引入新名字,而不是为变量分配内存
- typedef int x[10]和#define x int[10]的区别
- 可以用其他类型说明对宏类型名进行扩展
#define peach int unsigned peach i; /*没问题*/ typedef int banana; unsigned banana i;/*错误,非法*/
-
-
连续几个变量声明中,typedef保证所有变量为同一种类,#define定义则无法保证
-
- typedef struct foo {..foo;}的含义,不同的命名空间内使用同一个名字,bad habit
第4章 令人震惊的事实,数组和指针并不相同
- 数组并非指针
- 我的代码无法运行
-
/*file 1*/ int mango [100]; /*file 2*/ extern int *mango;
- 什么是声明,什么是定义
- 声明,描述其他地方创建的对象
- 定义,确定对象类型并为其分配内存,只能出现在一个地方
- 使声明和定义匹配,指针的外部声明与数组不匹配,代码当然没办法运行
- 指针和数组的区别
指针 | 数组 |
保存数据的地址 | 保存数据 |
间接访问数据,首先取指针内容,把它作为地址,然后从这个地址取地址。如果指针有一个下标[I],就把指针的内容加上I作为地址,从中提取数据 |
直接访问数据,a[I]只是简单的以a+I为地址取得数据 |
通常用于动态数据结构 | 通常用于存储固定数目且数据类型相同的元素 |
相关的函数为malloc().free() | 隐式分配和删除 |
通常指向匿名数据 | 自身即为数据名 |
第5章 对链接的思考
- 函数库、链接和载入
C预处理器——>前端(语义和语法分析)——>后端(代码生成器)——>优化器——>汇编程序——>链接-载入器,编译器通常分割成几个更小的程序
-
- 静态链接,函数库的一份拷贝是可执行文件的物理组成部分
- 动态链接,可执行文件只包含了文件名,让载入器运行时自行寻找所需要的函数库
- 动态链接的优点,体积小,共享数据库
- 函数库链接的5个特殊秘密
- 动态库文件扩展为“so”,静态为“a“
- 通过-lthread选项,告诉编译器连接到libthread.so
- 编译器期望在确定的目录找到库
- 观察头文件,确认所使用的函数库
- 与提取动态库中的符合相比,静态库中的符号提取的方法限制更严
- 警惕interpositioning,通过编写与库函数同名的函数来取代该库函数的行为
- 产生链接器报告
第6章 运动的诗章:运行时数据结构
运行时数据结构包括栈,活动记录,数据和堆
- a.out及其传说,assembler output,汇编程序和链接编辑输出格式
- 段,segments,在unix中段表示一个二进制文件相关的内容块。文件中有三个段:文本段,数据段和BSS段
- OS在a.out文件里干了些什么,可执行文件中段在内存如何分布(自己画一下内存中的分布图)
- C语言运行时系统在a.out里干了些什么,C语言怎么组织正在运行的程序的数据结构(堆栈,活动记录,数据,堆等)
堆栈段的三个主要作用:为局部变量提供存储空间,进行函数调用时存储与此相关的一些维护性信息,用作暂时存储区
- 当函数被调用时发生了什么:过程活动记录(自己画过程活动记录的规范描述)
- auto和static,函数内变量定义为static被保存在数据段而不是堆栈,就算退出函数,变量值依然保持。
- 控制线程
- setjmp和longjmp,主要用于错误恢复
- UNIX中的堆栈段,当进程需要更多空间是,堆栈会自动生长
- MS-DOS中的堆栈段,在可执行文件建立时,堆栈大小必须确定,不能再运行时增长
- 有用的C语言工具:用于检查源代码的工具,用于检查可执行文件的工具,用于帮助调试的工具,性能优化辅助的工具
2015年5月5日 星期二 9:43
第7章 对内存的思考
- Intel 80x86系列,向后兼容
- Intel 80x86内存模型以及它的工作原理
- 在UNIX中,段是一块以二进制形式出现的相关内容
- 在内存模式中,段是内存模型设计的结果,一块64KB的内存区域
- 在OS中,段与操作系统的内存管理有关
- 虚拟内存,通过“页”形式组织,页是OS在disk和memory之间移动的单位,一般为几K字节
- Cache存储器 ,全写法cache和写回法cache
- 数据段和堆,像堆栈段能够根据需要自动增长一样,数据段也包含了一个对象,用于完成这项工作,这就是堆。堆用于动态分配的存储,通过malloc等库函数获得内存。
- 内存泄露
C语言通常不使用垃圾收集器,自动确认并回收不再使用的内存块。
堆经常出现两种类型的错误:
-
- 释放或改写仍在使用的内存
- 未释放不再使用的内存
- 总线错误,总线错误大多是因为未对齐的读或写引起的,段错误是由于内存管理单元异常,通常由于解引用一个未初始化/非法值的指针引起
第8章 为什么程序员无法分清万圣节和圣诞节
看了之后有点不知所云,还是理解不够深,好零碎的感觉
第9章 再论数组
- 什么时候数组和指针相同?
- 为什么会发生混淆
- 作为函数的形式参数时,数组和指针可互换
- 表达式中数组名(与声明不同)被compiler当做一个指向该数组第一个元素的指针
- C语言把数组下表作为指针的偏移量
- 为什么C语言把数组形参当做指针,传值调用,传址调用?
- 数组片段的下标
通过向函数传递一个指向数组任意元素的指针,这样传递给函数的就是从该元素之后的数组片段
- 数组和指针可交换性的总结
- 用a[i]这样的形式对数组进行访问总是被编译器“改写”或解释为像*(a+i)这样的指针访问
- 指针始终是指针,绝不可以改写为数组。你可以用下标形式访问指针,一般都是指针作为函数参数的时候,而且你知道实际传递给函数的是一个数组
- 在特定的上下文中,也就是作为函数的参数,一个数组的声明可以被看成一个指针。作为函数参数的数组(就是在一个函数调用中)始终会被编译器修改成为一个指向数组第一个元素的指针
- 因此,当把一个数组定义为函数参数时,可以选择把它定义为数组,也可以定义为指针。不管何种选择,在函数内部事实上获得的都是一个指针
- 在其他情况中,定义和声明必须匹配。
- C语言的多维数组
- 尽管术语上称作多维数组,但C语言实际上只支持“数组的数组”
- int apricot[2][3][4]在内存中的定位(可以自己画一下)
- 内存中数组如何布局:“行主序”,数组最右边下标最先变化
- 对多维数组的初始化
第10章 再论指针
- 多维数组的内存分布,以线性形式排列在内存中,array[i][j]——>*(*(array+i)+j)
- 指针数组就是Iliffe向量,声明一个一维指针数组,每个指针指向一个字符串来取得类似于二维字符数组的效果.char *pea[4];char pine[4][5];
- 在锯齿状数组上使用指针,字符串指针数组,右端长度不一
- 向函数传递一个一维数组,需要长度约束,有两种方法
- 增加额外参数表示元素数目,argc的作用
- 赋予数组最后一个元素一个特殊的值,该值不会作为正常元素出现在数组中
- 使用指针向函数传递一个多维数组
/*二维或更多维数组无法在C语言中用作一般形式的参数*/ my_function(int my_array[10][20]); my_function(int my_array[][20]);
my_function(int (*my_array)[20]); my_function(char ** my_array); /*字符串指针*/对于多维数组作为参数传递的支持缺乏是C语言存在的一个内在限制。这使得用C语言编写某些特定类型的程序非常困难,如数值分析算法。
- 使用指针从函数返回一个数组
/*先声明函数*/ int (*paf()) [20]; /*定义函数*/ int (*paf()) [20]{ int (*pear)[20]; pear=calloc(20,sizeof(int)); if(!pear)longjmp(error,1); return pear; } /*调用函数*/ int (*result)[20]; result=paf(); (*result)[3]=12;
- 使用指针创建和使用动态数组
- 使用malloc()函数得到一个指向一大块内存的指针,然后像引用数组一样引用内存
- realloc()对一个现在的内存块大小进行重新分配,同时不会丢失原来块的内容
第11章 你懂得C,所以C++不在话下
- 初识OOP,面向对象编程:封装,继承,多态(动态绑定?) 抽象——取事物的本质特性
- 隐藏不相关细节
- 向外界提供“黑盒子”接口
- 把一个复杂的系统分解成几个相互独立的组成部分
- 重用和共享代码
- 封装——把相关的类型、数据和函数组合在一起
- 类把代码和相关数据封装(捆绑)在一起
- 展示一些类,类<-->对象,类型<-->变量
- 访问控制,public,private,protected后面可以跟一大串声明,friend和virtual只能用于一条声明
- 声明,正常的C语言声明,包括函数,类型或数据
- 如何调用成员函数,点操作符.
- 继承——复用已定义的操作
- 多重继承
- 重载,作用于不同类型的同一操作具有相同的名字
- C++如何进行操作符重载
- C++输入/输出,cout<<"the value is:"<<i<<endl;多个I/O操作数链接在一起
- 多态——运行时绑定
- 支持相关的对象具有不同的成员函数(但原型相同),并允许对象与适当的成员函数进行运行时绑定。virtual关键字告诉编译器该成员函数是多态的。
- 多态是指一个函数或操作符只有一个名字,但它可以用于几个不同的派生类类型的能力
- 解释
- 当相应派生类的成员函数取代基类的同类函数是,C++要求你必须提前通知compiler,方法就是在可能被取代的基类成员函数前加上virtual关键字
- C++如何表现多态
- vptr指针实现虚函数,vptr指针指向一个叫做vtbl的函数指针向量(称为虚函数表,也称为V表)。每个类都有这样一个向量,类中每个虚函数在该向量中都有一条记录
- 具体可参看别人的一篇博客,对于虚函数在内存中分配等问题分析的非常透彻 http://blog.jobbole.com/86843/
- C++的其他要点
- 异常,在错误处理时改变程序的控制流
- 模板,支持参数化类型 ,类和对象的关系<---->模板和函数的关系
template<class T> T min(T a,T b) { return (a>b)?a:b; }
- 内联inline
- new和delete操作符,new可以真正地建立一个对象,malloc()函数只分配内存。
- 传引用调用,call-by-reference。