《C专家编程(笔记).pdf》由会员分享,可在线阅读,更多相关《C专家编程(笔记).pdf(6页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、第一章:一:尽量不要在你的代码中使用无符号类型,以免增加不必要的复杂性,尤其是不要仅仅因为无符号数不存在负值(如年龄,国债)而用它来表示数量。只有在使用位段和二进制掩码时,才可以使用无符号数,应该在表达式中使用强制类型转换,使操作数均为有符号数或无符号数。这样就不必由编译器来选择结果的类型。二:当执行算术运算时:操作数的类型如果不同,就会发生转换,数据类型一般朝着浮点精度更高,长度更长的方向转变。整型数如果转换为 signed 不会丢失信息,就转换为 signed,否则转换为 unsigned。一个L的NUL用于结束一个字符串。ASCALL字符中零的位模式被称为NUL。两个的 NULL 用于表
2、示什么也不指向,(空指针)第二章:这不是 BUG,而是语言特性2.1 多做之过:这些特性包括容易出错的 switch 语句,相邻字符串常量的自动连接和缺省全局范围。C 语言中,几乎从来不进行运行时错误检查-对进行解除引用操作的指针有效性检查大概是唯一的例外。无效的指针可能成为程序员的噩梦,人们很容易用一个无效的指针来引用内存。在所有的虚拟内存体系结构里,一旦一个指针进行解除引用操作时,所引用的内存超出内存地址的虚拟地址空间,操作系统就会结束这个进程。但是DOS 并不支持虚拟内存,即使内存访问失败,它也无法立即捕获这种情况。C 语言的理念,程序员应该知道自己正在干什么,而且保证自己的所做所为是正
3、确的。各个 case 和 default 的顺序是可以任意的,但习惯总是把 default 放在最后。switch 存在的一些问题是,其中之一是它对 case 可能出现的值太过放纵了,例如可以在 switch 的做好括号后声明一些变量,从而进行一些局部存储的分配,在最初的编译器中,这是一个技巧-绝大多数用于处理复杂复合语句的代码都可以被复用。switch 语句缺省采用”fall through“,在 97%的情况下都是错误的。break 语句事实上跳出的是最近的那层循环语句或 switch 语句。字符串常量的自动合并意味着字符串数组在初始化时,如果不小心漏掉了一个逗号,编译器不会发出错误信息,
4、而是悄无声息的合并在一起。在最后一个字符串末尾的逗号并不是打错字,而是从早期的 C 语法中继承下的东西,不管存在是否有意义,ANSI对它的解释是是语言自动生成容易些。太多的缺省可见性定义函数时,在缺省情况下名字是全局可见的,可以再名字前面加一个冗余的 extern 关键字,也可以不加,效果一样的。如果想限制这个函数的访问,就必须加个 static 关键字。2.2 误做之过:C 语言中属于“误做之过”的特性,就是语言中有误导性质或是不适当的特性,这些特性有些跟 C 语言的简介有关,有些则更操作符的优先级有关。C 语言存在的一个问题就是它太简洁了,仅增加,修改或删除一个字符就会使程序成另外一个仍然
5、有效却全然不同的程序。更糟糕的是,许多符号是被”重载的“在不同的上下文环境有不同的意思。当 sizeof 的操作数是个类型名时,两边必须加上括号(这常常使人认为他是一个函数),但操作数如果是一个变量则不必加括号。你让一个符号所表达的意思越多,编译器就越难检测到这个符号在你的使用中所存在的异常情况。2.3 少做之过属于少做之过的特性就是语言应该提供但未提供的特性。C 语言有最大一口策略,这种策略表示如果下个标记有超过一种的解释方案,编译器将选取最长的字符序列方案。第三章 声明参数按照从右到左的次序压倒堆栈中,这种说法过于简单了,参数在传递时首先尽可能地存放到寄存器中(追求速度)。一个 int 型
6、变量跟只包含一个 Int 型成员的结构变量 S 在参数传递时可能完全不同,一个 int型参数一般会被传递到寄存器中,而结构变量 s 在参数则很可能被传递到堆栈中。结构体:在结构中放置数组,如 struct s_tagint a100;现在可以把数组当做第一等级的类型,用赋值语句拷贝整个数组,一传值的方式传递到函数,或者把它作为函数的返回类型。在典型的情况下并不需要频繁的对整个数组进行赋值操作。但是如果需要这样做,可以通过放入结构中实现。C 语言声明的优先级A 声明从它的名字开始读取,然后按照优先级一次读取。B 优先级从高到低一次是:1 声明中被括号括起来的2 后缀符号()【】3 前缀符号*不要
7、在一个 typedef 中放入几个声明器,千万不要把 typedef 嵌到声明的中间部分。不要为了方便起见对结构使用 typedef,这样做唯一的好处是能使你不必书写 struct 关键字,但这个关键字可以向你提示一些信息,你不应该把它省掉。typedef int x10,#define x int10的区别正确思考这个问题的方法是把 typedef 看成是一种彻底的“封装”类型-在声明它之后不能再往里面增加别的东西。它和宏的区别体现在两个方面。首先,可以用其他类型说明符对宏类型进行扩展,但对 typedef 所定义的类型名却不能这样做。如下所示:#define peach intunsign
8、ed peach i;/没问题typedef int peachunsigned peach i;/错误非法。其次:在连续几个变量的声明中,用 typedef 定义的类型能够保证声明中所有的变量均为同一种类型,而用#define 定义的类型则无法保证。extern 对象声明告诉编译器对戏那个的类型和名字,对象的内存分配在别处进行。由于并未在声明中为数组分配内存,所以并不需要提供关于数组长度的信息。并且 extern int*i;extern int i;是不一样的。第四章 数组和指针出现在赋值符号左边的符号有时被称为左值(由于它位于“左手边”或“表示地点”),出现在赋值符号右边的符号有时则被称
9、为右值,编译器为每个变量分配一个地址(左值),这个地址在编译时可知,而且该变量在运行时一直保存于这个地址,相反存储于该变量中的值只有在运行时才可知。如果需要用到变量中存储的值,编译器发出指令从指定地址读入变量的值,并将它存于寄存器。char*p=abcdef;char p=abcdef;前者编译器告知 p 是一个纸箱字符的指针(相反数组的定义告诉编译器 p 是一个字符序列)pi表示从 p 所指向的地址开始,前进一步,每步都是一个字符,既然把 p 声明为指针,那么不管 p 原先是定义为指针还是数组,都会按照上面所示的三个步骤进行操作;。前者间接访问数据,首先取得指针的内容,把它作为地址,然后从这
10、个地址提取数据。如果指针有一个小标1就把指针的内容更加上一作为地址,从中去数据后者 直接访问数据,a1指示简单地一 a+1 为地址取数据。第五章;链接器收集模块准备执行的三个阶段的规范名称是连接-编辑,载入,动态链接。如果函数库的一份拷贝时可执行文件的物理组成部分,那么我们称之为静态链接;如果可执行文件只是包含了文件名,让载入器在运行时能够寻找程序所需要的库函数,那么我们称之为动态链接。静态链接的模块被连接编辑并载入运行,动态链接的模块被连接编辑后载入,并在运行时进行连接以便运行。程序执行时,在 main()函数被调用前,运行时载入器把共享的数据对象载然入到进程的地址空间。外部函数被真正调用之
11、前,运行时载入器不解析它们,所以即使链接了函数库,如果没有实际调用,也不会带来而外开销。即使是在静态链接中,整个文件也没有被全部载如到可执行文件中,所装入的只是所需要的函数。动态链接时是一种动态链接,这意味着程序在运行时必须能够找到它们所需要的库函数,连接器吧库文件名或路径名植入可执行文件做到这一点,这意味着,库函数的路径不能够随意移动,动态链接可以从两个方面提高性能:一:动态链接可执行文件比功能相同的链接的体积小。二:所有动态库连接到某个特定的函数库的可执行文件在运行时共享该函数库的一个单独拷贝。操作系统内核保证映射到内存中的库函数可以被所有使用它的进程共享。这就提供了更好的 I/O 和交换
12、空间利用率,节省了物理内存,提高了系统的整体性能。静态库被称为 archive,它们通过 ar(用于 archive 的实用工具)来创建和更新。动态链接库由链接编辑器 ld 创建根据约定,动态库的文件扩展名为“.so”,表示“shared object共享对象”。涉及到 UNIX 的部分没有看*第六章:运行时数据结构a.out-汇编程序和链接编辑输出格式 它不是胡编程序输出而是连接器输出。编译器设计者通过不存储为使用的信息来提高速度,其他的优化措施包括把信息保存在寄存器而不是堆栈中,尽管我们谈到了将过程活动记录压到堆栈中,但是过程活动记录并不一定要存在于堆栈中。事实上,尽可能地把过程活动记录的
13、内容更放到寄存器中会使函数调用的速度更快,效果更好。第七章 对内存的思考就像堆栈能够根据需要自动增长一样,数据段也包含了一个对象,用于完成这项工作,这就是堆,它用于动态分配的存储。用 malloc 函数,callvoc 函数与 malloc 函数类似,但它在返回指针之前先把分配好的内存的内容清零,realloc 函数把内存拷贝到别的地方然后将指向新地址的指针返回给你。这在动态增长的表很有用。alloca()分配在栈上的动态内存,它分配的内存会自动释放。这并不适合那些比创建它们的函数生命周期更长的结构。数据对齐的意思是数据项只能存储在地址是数据项大小的整数倍的内存位置上。编译器通过自动分配和填充
14、数据(在内存中)来对齐。当然在磁盘或磁带上并没有这样的对齐要求,所以程序员对它们可以很愉快地不必关心数据对齐,但是当他们把一个 char 指针转换为 Int 指针时,就会出现神秘的总线错误。段错误或段违规是由于内存管理单元(负责支持虚拟内存的硬件)的异常所致,该异常则通常是由于解除引用一个未初始化或非法值的指针引起的。如果指针引用一个并不位于你的地址空间的地址,操作系统便会进行干涉。如 int*p=0;*p=17;一个微妙之处是,导致指针具有非法的值通常不是由于编程错误引起的,和总线错误不同,段错误更像是一个间接的症状而不是错误的原因。一个更糟糕的微妙之处是,如果未初始化的指针恰好具有未对其的
15、值(对于指针所要访问的数据而言),它将会产生总线错误,而不是段错误。通常导致段错误的几个直接原因:解除引用一个包含非法值的指针。解除引用一个空指针在未得到正确的权限时进行访问。用完了堆栈或堆空间下面的说法过于简单,但在绝大多数框架的绝大多数情况下,总线错误意味着 Cpu 对进程引用内存的一些做法不满,而段错误则是 MMU 对进程引用内存的一席情况发出抱怨。第八章 为什么程序员无法分清楚万圣节和圣诞节ANSI C 函数原型的目的是使 C 语言成为一种更加可靠的语言。建立原型就是为了消除一种普通(但很难发现)的错误,就是形参和实参类型不匹配。ANSI C 的函数原型就是才用一种新的函数声明形式,把
16、参数的类型也包含在声明之中。函数的定义也作为相应的改变以匹配声明。在 ANSIC 中如果使用了新风格的函数定义,编译器就不会假定参数是准确声明的,于是便不进行类型提升,并据此产生代码。不要在函数的声明和定义中混用新旧两种风格。复杂的类型转换可以按下面的 3 个步骤编写(1)一个对象的声明,它的类型就是想要转换的结果类型(2)删除标示符(以及任何如 extern 之类的存储限定符),并把剩余的内容放在一对括号里。(3)把第二步产生的内容放在需要进行类型转换的对象的左边。第九章;再论数组对编译器而言,一个数组就是一个地址,一个指针就是一个地址的地址。什么时候数组和指针时相同的规则一:表达式中的数组
17、名(与声明不同)被编译为一个指向该数组的第一个元素的指针规则二:下标总是与指针的偏移量相同规则三:在函数参数的声明中,数组名被编译成当做指向该数组的第一个元素的指针。规则一个规则二合起来理解,就是对数组下标的引用总是可以写成“一个指向数组的起始地址的指针加偏移量”。你只要记住在表达式中,指针和数组时可以互换的,因为它们在编译器里最终形式都是指针,并且都可以进行取下标操作,就像加法一样,取下标操作的操作数是可以交换的这就是为什么 a6=6a都是正确的。在处理一维数组时,指针并不见得比数组块,C 语言吧数组下标改写为指针偏移量的根本原因是指针和偏移量是底层硬件所使用的基本模型。之所以要把传递给函数
18、的数组参数转换为指针时出于效率的考虑,要拷贝整个数组的效率时太低。不管程序员实际缩写的是哪种形式,函数并自动知道指针所指向的数组的范围,所以要有个约定,如数组一 NUL 结尾或另一个附加参数表示结束。注意有一样操作只能在指针里进行二无法再数组中进行,那就是修改它的值。数组名是不可以修改的左值,它的值不能改变的。多维数组:定义和引用多维数组唯一的方法就是使用数组的数组。尽管 C 语言把数组的数组当作多维数组,在思维模式中,吧数组当做是一种向量(即某种对象的一维数组,它的元素可以使另一个数组),就能极大的简化编程语言中这个复杂的领域。在 C 语言的多维数组,最右边的下标是最先变换的,这个约定被称为
19、“行主序”。语言中,多维数组的最大用途就是存储多个字符串。第十章再论数组数组名被改写成一个指针参数规则并不是递归定义的。数组的数组会被改写为“数组的指针”而不是指针的指针。实参所匹配的形式参数数组的数组char c810;char(*)10数组的指针指针数组char*c15char*c指针的指针数组指针char(*c)64char(*)64不改变指针的指针char*Cchar*不改变二维数组要复杂一些,数组被改写为指向数组第一行的指针。现在需要两个约定,其中一个用于提示每行的结束,另一个用于提示所有行的结束。提示单行的结束可以使用一维数组的两种方法,增加一个额外的参数表示大小,赋予数组最后一个
20、元素一个特殊的值,这个特殊的值不会作为数组元素出现。提示所有行结束也可以这样。使用指针向函数传递一个多维数组语言没有办法表达“这个数组的边界在不同的调用中可以变化”,我们能够采用的最后的方法是把 arrayxy这样的形式改写为一个一维数组 arrayx+1,它的元素类型是指向 arrayy的指针。这样就改变了问题的性质,而改变后的问题是我们已经解决了的。在数组最后的那个元素 arrayx+1里存储一个 null 指针,提示数组的结束。二维或更多维的数组无法再 C 语言中用作一般形式的参数。你无法向函数传递一个普通的多维数组。可以系那个函数传递预先确定长度的特殊数组。方法一 f(int arra
21、y1020)尽管这是最简单的方法,但同时也是作用最小的,因为它迫使函数只处理 10 行 20 列的 int型数组,注意多维数组最主要的一维的长度不必显示写明,所有函数都必须知道数组其他维的确切长度和数组的基地址。方法二 f(int array20)方法三 我们可以采用的这种方法是放弃二维数组,把它的结构改为以个 Hiffe 向量。也就是说,创建一个一维数组,数组中的元素是指向其他东西的指针。注意:只有把二维数组改为以指向向量的指针数组的前提下才可以这样做。Hiffe 向量的这种数据结构美感在于,它允许任意的字符串指针数组传递给函数,但必须是指针数组,而且必须是指向字符串的指针数组。不要在一行代码里实现太多的功能。