《高质量的C/C++编程指南》读书笔记

林锐:《高质量的C/C++编程》 知错就改;经常温故而知新;坚持学习,天天向上。 阅读详情

第一章 文件结构

  1. 头文件由三部分构成:
    1. 头文件开头处的版权和版本声明;
    2. 预处理块;
    3. 函数和类结构声明等;
  2. 【规则 1-2-1】为了防止头文件被重复引用,应当用 ifndef/define/endif 结构产生预处理块。
  3. 【建议 1-2-1】头文件中只存放“声明”而不存放“定义”。
  4. 定义文件由三部分组成:
    1.  定义文件开头处的版权和版本声明;
    2.  对一些头文件的引用;
    3. 程序的实现体(包括数据和代码)。

第二章 程序的版式

  1. 【规则 2-1-1】在每个类声明之后、每个函数定义结束之后都要加空行。在一个函数体内,逻揖上密切相关的语句之间不加空行,其它地方应加空行分隔。
  2. 【建议 2-2-1】尽可能在定义变量的同时初始化该变量(就近原则)
  3. 【规则 2-6-1】应当将修饰符 * 和 & 紧靠变量名
  4. 【规则 2-7-6】注释的位置应与被描述的代码相邻,可以放在代码的上方或右方,不可放在下方。
  5. 定义类时应主张将public类型的函数写在前面,而将private类型的数据写在后面,即“以行为为中心”。

第三章 命名规则

  1. 【规则 3-1-1】标识符应当直观且可以拼读,可望文知意,不必进行“解码”。
  2. 【规则 3-1-2】标识符的长度应当符合“ min-length && max-information”原则。
  3. 【规则 3-1-4】程序中不要出现仅靠大小写区分的相似的标识符。
  4. 【规则 3-1-5】程序中不要出现标识符完全相同的局部变量和全局变量,尽管两者的作用域不同而不会发生语法错误,但会使人误解。
  5. 简单Windows程序命名规则:
    1. 【规则 3-2-1】类名和函数名用大写字母开头的单词组合而成。
    2. 【规则 3-2-2】变量和参数用小写字母开头的单词组合而成。
    3. 【规则 3-2-3】常量全用大写的字母,用下划线分割单词。
    4. 【规则 3-2-4】静态变量加前缀 s_(表示 static)。如果不得已需要全局变量,则使全局变量加前缀 g_(表示 global)。
    5. 【规则 3-2-6】类的数据成员加前缀 m_(表示 member),这样可以避免数据成员与成员函数的参数同名。
  6. /*简单Unix应用程序命名规则:
    1. NUL*///请忽略它-_-|

第四章 表达式和基本语句

  1. 运算符的优先级与结合律如下所示。注意一元运算符 + - * 的优先级高于对应的二元运算符。
  2. 【规则 4-1-1】如果代码行中的运算符比较多,用括号确定表达式的操作顺序,避免使用默认的优先级。
  3. if语句中各种类型变量与零值比较:
    1.  bool变量:【规则 4-3-1】不可将布尔变量直接与 TRUE、 FALSE 或者 1、 0 进行比较。(应为if(flag),if(!flag))
    2. 整型变量:【规则 4-3-2】应当将整型变量用“ ==”或“! =”直接与 0 比较。
    3. 浮点变量:【规则 4-3-3】不可将浮点变量用“ ==”或“! =”与任何数字比较。(浮点数有精度限制,应为if ((x>=-EPSINON) && (x<=EPSINON)),其中 EPSINON 是允许的误差(即精度))
    4. 指针变量:【规则 4-3-4】应当将指针变量用“ ==”或“! =”与 NULL 比较。
    5. 为防止将if(p==NULL)写成if(p=NULL),可以将其写成if(NULL==p)。
  4. 【建议 4-4-1】在多重循环中,如果有可能,应当将最长的循环放在最内层,最短的循环放在最外层,以减少 CPU 跨切循环层的次数。
  5. 【建议 4-4-2】如果循环体内存在逻辑判断,并且循环次数很大,宜将逻辑判断移到高质量 C++/C 编程指南,循环体的外面。(循环“流水线”作业)
  6. 【规则 4-5-1】不可在 for 循环体内修改循环变量,防止 for 循环失去控制。
  7. 【建议 4-5-1】建议 for 语句的循环控制变量的取值采用“半开半闭区间”写法。
  8. switch语句中,【规则 4-6-1】每个 case 语句的结尾不要忘了加 break,否则将导致多个分支重叠(除非有意使多个分支重叠)。
  9. 【规则 4-6-2】不要忘记最后那个 default 分支。即使程序真的不需要 default 处理,也应该保留语句 default : break; 这样做并非多此一举,而是为了防止别人误以为你忘了 default 处理。

第五章 常量

  1. 常量是一种标识符, 它的值在运行期间恒定不变。 C 语言用 #define 来定义常量 (称为宏常量)。 C++ 语言除了 #define 外还可以用 const 来定义常量(称为 const 常量)。
  2. const 与 #define 的比较
    1. const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误(边际效应)。
    2. 有些集成化的调试工具可以对 const 常量进行调试,但是不能对宏常量进行调试。
  3. 【规则 5-2-1】在 C++ 程序中只使用 const 常量而不使用宏常量,即 const 常量完全取代宏常量。
  4. 不能在类声明中初始化 const 数据成员。const 数据成员的初始化只能在类构造函数的初始化表中进行。
  5. 怎样才能建立在整个类中都恒定的常量呢?别指望 const 数据成员了,应该用类中的枚举常量来实现(是不是只能定义为整型常量?!见下一条)。
    eg:class A
    {…
    enum { SIZE1 = 100, SIZE2 = 200}; // 枚举常量
    int array1[SIZE1];
    int array2[SIZE2];
    };
  6. 枚举常量不会占用对象的存储空间,它们在编译时被全部求值。枚举常量的缺点是:它的隐含数据类型是整数,其最大值有限,且不能表示浮点数(如 PI=3.14159)。

第六章 函数设计

  1. 【规则 6-1-3】如果参数是指针,且仅作输入用,则应在类型前加 const,以防止该指针在函数体内被意外修改。
  2. 【规则 6-1-4】如果输入参数以值传递的方式传递对象,则宜改用“ const &”方式来传递,这样可以省去临时对象的构造和析构过程,从而提高效率。
  3. 【建议 6-1-2】尽量不要使用类型和数目不确定的参数。这种风格的函数在编译时丧失了严格的类型安全检查。C 标准库函数 printf 是采用不确定参数的典型代表,其原型为:int printf(const chat *format[, argument]…);
  4. 【规则 6-2-1】不要省略返回值的类型。
    1. C 语言中,凡不加类型说明的函数,一律自动按整型处理。这样做不会有什么好处,却容易被误解为 void 类型。
    2. C++语言有很严格的类型安全检查,不允许上述情况发生。
  5. 【规则 6-2-2】函数名字与返回值类型在语义上不可冲突。违反这条规则的典型代表是 C 标准库函数 getchar。
  6. 【规则 6-2-3】不要将正常值和错误标志混在一起返回。正常值用输出参数获得,而错误标志用 return 语句返回。
  7. 【建议 6-2-1】有时候函数原本不需要返回值,但为了增加灵活性如支持链式表达,可以附加返回值。例如字符串拷贝函数 strcpy 的原型:char *strcpy(char *strDest, const char *strSrc);
  8. 【建议 6-2-2】如果函数的返回值是一个对象,有些场合用“引用传递”替换“值传递”可以提高效率。而有些场合只能用“值传递”而不能用“引用传递”,否则会出错。
  9. 【规则 6-3-1】在函数体的“入口处”,对参数的有效性进行检查。
  10. 【规则 6-3-2】在函数体的“出口处”,对 return 语句的正确性和效率进行检查。
    注意事项如下:
    1. return 语句不可返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁。
    2. 要搞清楚返回的究竟是“值”、“指针”还是“引用”。
    3. 如果函数返回值是一个对象,要考虑 return 语句的效率。
      eg:return String(s1 + s2);
      这是临时对象的语法,表示“创建一个临时对象并返回它”。不要以为它与“先创建一个局部对象 temp 并返回它的结果”是等价的,如
      String temp(s1 + s2);
      return temp;
      实质不然,上述代码将发生三件事。首先, temp 对象被创建,同时完成初始化;然后拷贝构造函数把 temp 拷贝到保存返回值的外部存储单元中;最后, temp 在函数结束时被销毁(调用析构函数)。然而“创建一个临时对象并返回它”的过程是不同的,编译器直接把临时对象创建并初始化在外部存储单元中,省去了拷贝和析构的化费,提高了效率。
  11. 【建议 6-4-4】不仅要检查输入参数的有效性,还要检查通过其它途径进入函数体内的变量的有效性,例如全局变量、文件句柄等。
  12. 断言 assert 是仅在 Debug 版本起作用的宏,它用于检查“不应该”发生的情况。为了不在程序的 Debug 版本和 Release 版本引起差别, assert 不应该产生任何副作用。所以 assert 不是函数,而是宏。
  13. 【规则 6-5-1】使用断言捕捉不应该发生的非法情况。不要混淆非法情况与错误情况之间的区别,后者是必然存在的并且是一定要作出处理的。
  14. 指针与引用:
    1. 引用被创建的同时必须被初始化(指针则可以在任何时候被初始化)。
    2. 不能有 NULL 引用,引用必须与合法的存储单元关联(指针则可以是 NULL)。
    3. 一旦引用被初始化,就不能改变引用的关系(指针则可以随时改变所指的对象)。
  15. 引用的主要功能是传递函数的参数和返回值。 C++语言中,函数的参数和返回值的传递方式有三种:值传递、指针传递和引用传递。
  16. “引用传递”的性质象“指针传递”,而书写方式象“值传递”。

第七章 内存管理

  1. 内存分配方式有三种:
    1. 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量, static 变量。
    2. 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
    3. 从堆上分配,亦称动态内存分配。程序在运行的时候用 malloc 或 new 申请任意多少的内存,程序员自己负责在何时用 free 或 delete 释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。
  2. 常见的内存错误及其对策如下:
    1. 内存分配未成功,却使用了它。
    2. 内存分配虽然成功,但是尚未初始化就引用它。
    3. 内存分配成功并且已经初始化,但操作越过了内存的边界。
    4. 忘记了释放内存,造成内存泄露。
    5. 释放了内存却继续使用它。
      有三种情况:
      1. 程序中的对象调用关系过于复杂,实在难以搞清楚某个对象究竟是否已经释放了内存,此时应该重新设计数据结构,从根本上解决对象管理的混乱局面。
      2. 函数的 return 语句写错了,注意不要返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁。
      3. 使用 free 或 delete 释放了内存后,没有将指针设置为 NULL。导致产生“野指针”。(空悬指针?)
  3. 【规则 7-2-1】用 malloc 或 new 申请内存之后,应该立即检查指针值是否为 NULL。防止使用指针值为 NULL 的内存。
  4. 【规则 7-2-2】不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。
  5. 【规则 7-2-3】避免数组或指针的下标越界,特别要当心发生“多 1”或者“少 1”操作。
  6. 【规则 7-2-4】动态内存的申请与释放必须配对,防止内存泄漏。
  7. 【规则 7-2-5】用 free 或 delete 释放了内存之后,立即将指针设置为 NULL,防止产生“野指针”。
  8. 指针与数组:
    1. 数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。数组名对应着(而不是指向)一块内存,其地址与容量在生命期内保持不变,只有数组的内容可以改变。
    2. 指针可以随时指向任意类型的内存块,它的特征是“可变”,所以我们常用指针来操作动态内存。指针远比数组灵活,但也更危险。
  9. 不能对数组名进行直接复制与比较。应该用标准库函数 strcpy 进行复制,用标准库函数 strcmp进行比较。
  10. 用运算符 sizeof 可以计算出数组的容量(字节数)。但是 sizeof(指针)的值却是 4。C++/C 语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。
  11. 当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。
  12. 如果函数的参数是一个指针,不要指望用该指针去申请动态内存。如果非得要用指针参数去申请内存, 那么应该改用 “指向指针的指针”,或者用函数返回值来传递动态内存。
  13. free和delete只是把指针所指的内存给释放掉,但并没有把指针本身干掉。指针的地址仍然不变(非 NULL),只是该地址对应的内存是垃圾。此时如果不将指针设为NULL,那么if(NULL==p)语句将不起作用。
  14. 我们发现指针有一些“似是而非”的特征: 
    1. 指针消亡了,并不表示它所指的内存会被自动释放。
    2. 内存被释放了,并不表示指针会消亡或者成了 NULL 指针。
  15. “野指针”不是 NULL 指针,是指向“垃圾”内存的指针。成因主要有两种:
    1. 指针变量没有被初始化。
    2. 指针 p 被 free 或者 delete 之后,没有置为 NULL。
    3. 指针操作超越了变量的作用范围。
  16. malloc/free和new/delete:前者是库函数,对于自定义的对象,不能自动调用其构造和析构函数;后者是关键字,可以调用构造函数和析构函数。对于内部数据类型两者是等价的。
  17. 如果用 free 释放“ new 创建的动态对象”,那么该对象因无法执行析构函数而可能导致程序出错。 如果用 delete 释放“ malloc 申请的动态内存”,理论上讲程序不会出错,但是该程序的可读性很差。
  18. malloc、free、new、delete:
    1. malloc 返回值的类型是 void *,所以在调用 malloc 时要显式地进行类型转换,将void * 转换成所需要的指针类型。
    2. malloc 函数本身并不识别要申请的内存是什么类型,它只关心内存的总字节数。
    3. 为什么 free 函数不象 malloc 函数那样复杂呢?这是因为指针 p 的类型以及它所指的内存的容量事先都是知道的,语句 free(p)能正确地释放内存。如果 p 是 NULL 指针,那么 free 对 p 无论操作多少次都不会出问题。如果 p 不是 NULL 指针,那么 free 对 p连续操作两次就会导致程序运行错误。
    4. new 内置了 sizeof、类型转换和类型安全检查功能。对于非内部数据类型的对象而言, new 在创建动态对象的同时完成了初始化工作。如果对象有多个构造函数,那么 new 的语句也可以有多种形式。
    5. 如果用 new 创建对象数组,那么只能使用对象的无参数构造函数。
      eg:Obj *objects = new Obj[100]; // 创建 100 个动态对象
      不能写成Obj *objects = new Obj[100](1);// 创建 100 个动态对象的同时赋初值 1
    6. 在用 delete 释放对象数组时,留意不要丢了符号‘ []’。

第八章 C++函数的高级特性

  1. 对比于 C 语言的函数, C++增加了重载 ( overloaded)、 内联 ( inline)、 const 和 virtual四种新机制。其中重载和内联机制既可用于全局函数也可用于类的成员函数, const 与virtual 机制仅用于类的成员函数。
  2. 如果 C++程序要调用已经被编译后的 C 函数,C++提供了一个 C 连接交换指定符号 extern“ C”来解决这个问题。
  3. 注意并不是两个函数的名字相同就能构成重载。全局函数和类的成员函数同名不算重载,因为函数的作用域不同。全局函数被调用时应加‘ ::’标志。
  4. 成员函数的重载、覆盖与隐藏:
    1. 成员函数被重载的特征:
      1. 相同的范围(在同一个类中);
      2. 函数名字相同; 
      3. 参数不同; 
      4. virtual 关键字可有可无。
    2. 覆盖是指派生类函数覆盖基类函数,特征是:
      1. 不同的范围(分别位于派生类与基类);
      2. 函数名字相同; 
      3. 参数相同; 
      4. 基类函数必须有 virtual 关键字。
    3. “隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
      1. 如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无 virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
      2. 如果派生类的函数与基类的函数同名, 并且参数也相同, 但是基类函数没有 virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。
  5. 【规则 8-3-1】参数缺省值只能出现在函数的声明中,而不能出现在定义体中。
  6. 【规则 8-3-2】如果函数有多个参数,参数只能从后向前挨个儿缺省,否则将导致函数调用语句怪模怪样。
  7. 运算符与普通函数在调用时的不同之处是:对于普通函数,参数出现在圆括号内;而对于运算符,参数出现在其左、右侧。
  8. 如果运算符被重载为全局函数,那么只有一个参数的运算符叫做一元运算符,有两个参数的运算符叫做二元运算符。 
    如果运算符被重载为类的成员函数,那么一元运算符没有参数,二元运算符只有一个右侧参数,因为对象自己成了左侧参数。
  9. 在 C++运算符集合中,有一些运算符是不允许被重载的。这种限制是出于安全方面的考虑,可防止错误和混乱。(sizeof,::,.,等等)
    1. 不能改变 C++内部数据类型(如 int,float 等)的运算符。 
    2. 不能重载‘ .’,因为‘ .’在类中对任何成员都有意义,已经成为标准用法。 
    3. 不能重载目前 C++运算符集合中没有的符号,如#,@,$等。原因有两点,一是难以理解,二是难以确定优先级。 
    4. 对已经存在的运算符进行重载时,不能改变优先级规则,否则将引起混乱。
  10. 在 C 程序中,可以用宏代码提高执行效率。宏代码本身不是函数,但使用起来象函数。预处理器用复制宏代码的方式代替函数调用, 省去了参数压栈、生成汇编语言的 CALL调用、返回参数、执行 return 等过程,从而提高了速度。使用宏代码最大的缺点是容易出错,预处理器在复制宏代码时常常产生意想不到的边际效应。使用宏代码还有另一种缺点:无法操作类的私有数据成员。
  11. 对于任何内联函数,编译器在符号表里放入函数的声明(包括名字、参数类型、返回值类型)。如果编译器没有发现内联函数存在错误,那么该函数的代码也被放入符号表里。在调用一个内联函数时,编译器首先检查调用是否正确(进行类型安全检查,或者进行自动类型转换,当然对所有的函数都一样)。如果正确,内联函数的代码就会直接替换函数调用,于是省去了函数调用的开销。
  12. 所以在 C++ 程序中,应该用内联函数取代所有宏代码,“断言 assert”恐怕是唯一的例外。 assert 是仅在 Debug 版本起作用的宏,它用于检查“不应该”发生的情况。为了不在程序的 Debug 版本和 Release 版本引起差别, assert 不应该产生任何副作用。 如果 assert 是函数, 由于函数调用会引起内存、 代码的变动, 那么将导致 Debug版本与 Release 版本存在差异。 所以 assert 不是函数, 而是宏。 
  13. 关键字 inline 必须与函数定义体放在一起才能使函数成为内联,仅将 inline 放在函数声明前面不起任何作用。所以说, inline 是一种“用于实现的关键字”,而不是一种“用于声明的关键字”。
  14. 定义在类声明之中的成员函数将自动地成为内联函数。(类内声明,外部定义的函数不是内联的?)
  15. 以下情况不宜使用内联: 
    1. 如果函数体内的代码比较长,使用内联将导致内存消耗代价较高。 
    2. 如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大。
  16. 类的构造函数和析构函数容易让人误解成使用内联更有效。要当心构造函数和析构函数可能会隐藏一些行为,如“偷偷地”执行了基类或成员对象的构造函数和析构函数。所以不要随便地将构造函数和析构函数的定义体放在类声明中。

第九章 类的构造函数、析构函数与赋值函数

  1. 每个类只有一个析构函数和一个赋值函数,但可以有多个构造函数(包含一个拷贝构造函数,其它的称为普通构造函数)。
  2. 既然能自动生成函数,为什么还要程序员编写?
    原因如下:
    1. 如果使用“缺省的无参数构造函数”和“缺省的析构函数”,等于放弃了自主“初始化”和“清除”的机会, C++发明人 Stroustrup 的好心好意白费了。 
    2. “缺省的拷贝构造函数”和“缺省的赋值函数”均采用“位拷贝”而非“值拷贝”的方式来实现,倘若类中含有指针变量,这两个函数注定将出错。
  3. 构造函数有个特殊的初始化方式叫“初始化表达式表”(简称初始化表)。初始化表位于函数参数表之后,却在函数体 {} 之前。这说明该表里的初始化工作发生在函数体内的任何代码被执行之前。
  4. 构造函数初始化表的使用规则:
    1. 如果类存在继承关系,派生类必须在其初始化表里调用基类的构造函数。
    2. 类的 const 常量只能在初始化表里被初始化,因为它不能在函数体内用赋值的方式来初始化。
    3. 类的数据成员的初始化可以采用初始化表或函数体内赋值两种方式,这两种方式的效率不完全相同。
      1. 非内部数据类型的成员对象应当采用第一种方式初始化,以获取更高的效率。(第二种方式会先创建其对象,调用无参构造函数,再调用其赋值函数)。
      2. 对于内部数据类型的数据成员而言,两种初始化方式的效率几乎没有区别,但后者的程序版式似乎更清晰些。
  5. 构造从类层次的最根处开始,在每一层中,首先调用基类的构造函数,然后调用成员对象的构造函数。析构则严格按照与构造相反的次序执行,该次序是唯一的,否则编译器将无法自动执行析构过程。成员对象初始化的次序完全不受它们在初始化表中次序的影响,只由成员对象在类中声明的次序决定。
  6. 缺省的拷贝构造函数以“位拷贝”方式执行,以string类为例,将造成三个错误:
    1. 一是 b.m_data 原有的内存没被释放,造成内存泄露;
    2. 二是b.m_data 和 a.m_data 指向同一块内存, a 或 b 任何一方变动都会影响另一方;
    3. 三是在对象被析构时, m_data 被释放了两次。
  7. 拷贝构造函数和赋值函数非常容易混淆,常导致错写、错用。拷贝构造函数是在对象被创建时调用的,而赋值函数只能被已经存在了的对象调用。
  8. 类 String 拷贝构造函数与普通构造函数的区别是:在函数入口处无需与 NULL 进行比较,这是因为“引用”不可能是 NULL,而“指针”可以为 NULL。
  9. string类拷贝构造函数四部曲:
    1. 检查自赋值;
    2. 释放原有空间;
    3. 重新分配空间并拷贝;函数 strlen 返回的是有效字符串长度,不包含结束符‘\0’。函数 strcpy 则连‘\0’一起复制。
    4. 返回本对象的引用。目的是为了实现象 a = b = c 这样的链式表达。注意不要将 return *this 错写成 return this 。
  10. 偷懒的办法处理拷贝构造函数与赋值函数:只需将拷贝构造函数和赋值函数声明为私有函数,不用编写代码。
  11. 基类的构造函数、析构函数、赋值函数都不能被派生类继承。如果类之间存在继承关系,在编写上述基本函数时应注意以下事项:
    1. 派生类的构造函数应在其初始化表里调用基类的构造函数。
    2. 基类与派生类的析构函数应该为虚(即加 virtual 关键字)。如果析构函数不为虚,将不会调用派生类的析构函数。
    3. 在编写派生类的赋值函数时,注意不要忘记对基类的数据成员重新赋值。即调用基类的赋值函数。(因为不能直接操作基类的私有成员)

第十章 类的继承与组合

  1. 继承:若在逻辑上 B 是 A 的“一种”,并且 A 的所有功能和属性对 B 而言都有意义,则允许 B 继承 A 的功能和属性。
  2. 组合:若在逻辑上 A 是 B 的“一部分” ( a part of),则不允许 B 从 A 派生,而是要用 A 和其它东西组合出 B。

第十一章 其他编程经验

  1. 使用 const 提高函数的健壮性:const不止能定义const常量,更大的魅力是它可以修饰函数的参数、返回值,甚至函数的定义体。
    1. const修饰函数参数:
      1. 如果参数作输出用,不论它是什么数据类型,也不论它采用“指针传递”还是“引用传递”,都不能加 const 修饰,否则该参数将失去输出功能。
      2. const 只能修饰输入参数:
        1. 如果输入参数采用“指针传递”,那么加 const 修饰可以防止意外地改动该指针,起到保护作用。
        2. 如果输入参数采用“值传递”,由于函数将自动产生临时变量用于复制该参数,该输入参数本来就无需保护,所以不要加 const 修饰。
        3. 对于非内部数据类型的参数而言,象 void Func(A a) 这样声明的函数注定效率比较底。因为函数体内将产生 A 类型的临时对象用于复制参数 a,而临时对象的构造、复制、析构过程都将消耗时间。
    2. const 修饰函数的返回值
      1. 如果给以“指针传递”方式的函数返回值加 const 修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加 const 修饰的同类型指针。eg:const char * GetString(void);
      2. 如果函数返回值采用“值传递方式”,由于函数会把返回值复制到外部临时的存储单元中,加 const 修饰没有任何价值。
      3. 如果返回值不是内部数据类型,将函数 A GetA(void) 改写为 const A & GetA(void)的确能提高效率。但此时千万千万要小心,一定要搞清楚函数究竟是想返回一个对象的“拷贝”还是仅返回“别名”就可以了,否则程序会出错。
      4. 函数返回值采用“引用传递”的场合并不多,这种方式一般只出现在类的赋值函数中,目的是为了实现链式表达。
    3. const成员函数:任何不会修改数据成员的函数都应该声明为 const 类型。const 成员函数的声明看起来怪怪的: const 关键字只能放在函数声明的尾部,大概是因为其它地方都已经被占用了。
  2. 【建议 11-3-2】变量(指针、数组)被创建之后应当及时把它们初始化,以防止把未被初始化的变量当成右值使用。
【完】
欢迎关注我的微信公众号:共同商讨,共同进步!

《高质量C++/C编程指南》读书笔记与实践 函数指针是指向函数的指针变量,它保存了一个函数的地址。使用函数指针可以在运行时选择要执行的函数,提供了额外的灵活性和动态性。// 声明一个指向函数的指针,并初始化为function的地址funcPtr();// 通过函数指针调用functionreturn 0;在这个例子中,我们定义了一个名为function的普通函数,然后声明了一个函数指针funcPtr,将function的地址赋值给该函数指针。通过函数指针调用函数就像是直接调用函数一样。 阅读详情

相关推荐

金蝶云星空-协同开发云-安装开发环境

金蝶协同开发云就是不需要每一个开发都在自己的本机安装开发环境,自己安装可容易出错,包括源代码管理也一并解决了,大大减少了开发人员的工作量和降低了上手的难度。

qq_33881408的博客 5596

高质量C++/C编程指南

1 文件结构 每个C++/C程序通常分为两个文件。一个文件用于保存程序的声明(declaration),称为头文件。另一个文件用于保存程序的实现(implementation),称为定义(definition)文件。C++/C程序的头文件以“.h”为后缀,C程序的定义文件以“.c”为后缀,C++程序的定义文件通常以“.cpp”为后缀。 头文件的结构与作用 头文件主要有两个作用: 通过头...

aodia2015的博客 1001

高通camx架构学习(零)—— 深入理解Camera 硬件抽象层

从上面的定义可以看出,主要是通过hw_module_t 代表了模块,通过其open方法用来打开一个设备,而该设备是用hw_device_t来表示,其中除了用来关闭设备的close方法外,并无其它方法,由此可见谷歌定义的HAL接口,并不能满足绝大部分HAL模块的需要,所以谷歌想出了一个比较好的解决方式,那便是将这两个基本结构嵌入到更大的结构体内部,同时在更大的结构内部定义了各自模块特有的方法,用于实现模块的功能,这样,一来对上保持了HAL的统一规范,二来也扩展了模块的功能。

图像学习之旅 1668

高质量C/C++编程指南

本文档参考《高质量C++ C编程指南》及自己的心得编写,如有侵权,立刻删除!定义文件有三部分内容:(1)定义文件开头处的版权和版本声明;(2)对一些头文件的使用;(3)程序的实现体(包括数据和代码)假设定义文件的名称为header.cpp,定义文件的结构参见如下://版权和版本说明见2.1.1,此处省略#include "header.h" //引用头文件...//全局函数的实现体...//类成员函数的实现体...

weixin_72852519的博客 3534

海康视觉平台VisionMaster使用学习笔记(1)

双击图像源设置图像源的属性,像素格式分为Mono8和RDB24分别对应导入的图像是彩色还是黑白。打开相机驱动软件MVS,对连接的相机进行属性上的配置,先对相机的IP进行对应的设置。将自动触发的使能打开,保存配置好的相机属性,默认保存到用户集1中。点击MVS软件中的触发按钮,查看相机是否进行正常的触发拍照。打开VM软件添加一个图像源,选择图像源选项拖出图像源。回到VM软件中将选择相机选项选择到配置好参数的相机。取图方式也可以调整为相机取图,将图像源改为相机。双击相机进行连接后对相机的属性进行配置。

A567852的博客 1万+

基于转录逆转的药物重定向 GPS 模型

GPS是一个基于分子结构预测基因表达扰动的开源平台,由三个模块组成:RCL(在LINCS扰动数据上训练抗噪MLP)、GPS4Drugs(预测新化合物的基因表达谱并进行逆转评分)、MolSearch(基于MCTS的分子优化)。平台使用Morgan指纹和Gene Ontology注释作为特征输入,将基因表达扰动离散为三分类标签。训练数据来自LINCS Phase I的18,746个化合物和978个landmark基因,支持四细胞系独立训练,可用于药物重定向与化合物筛选。

听雨草堂 83

万字长文【C++】高质量编程指南

内联函数,编译器在符号表里放入函数的声明,包括名字,参数类型和返回值类型,如果编译器没有发现内联函数存在错误,那么该函数的代码也被放入符号表里,在调用一个内联函数时,编译器首先检查调用是否正确,进行类型安全检查,或者进行自动类型转换,如果正确,内联函数的代码就会直接替换函数的调用,于是省去了函数调用的开销。编译器总是要为函数的每个参数制作临时副本,指针参数p的副本是 _p,编译器 使 _p =p,如果函数体内的程序修改了 _p的内容,就导致参数p的内容作相应的修改,这就是指针可以作为输出参数的原因。

Ljq0704的博客 1555

UWB:FiRa Consortium UCI Generic Technical Specification v1.1.0(2)- 核心控制消息;会话管理;测距会话管理

测距是UWB最核心的应用。为了方便应用调用测距接口,FIRA统一了UCI命令格式,这样各个厂商可以互相兼容。

Ankie资深技术项目经理 1277

《高质量C/C++编程指南》读书笔记22.1.13

头文件:保存程序的声明(declaration) 定义(definition) 文件:保存程序的实现(implementation) 版权和版本的声明位于头文件和定义文件的开头。 空行 在每个类声明之后、每个函数定义结束之后都要加空行。 在一个函数体内,逻揖上密切相关的语句之间不加空行,其它地方应 加空行分隔。 代码行 if、for、while、do 等语句自占一行,执行语句不得紧跟其后。不论 执行语句有多少都要加{}。这样可以防止书写失误。 尽可能在定义变量的同时初始化该变量(就近原则)。

亿点点细节的博客 253

Android 13 SPRD 源码默认打开多用户实际无效果问题修复

【代码】Android 13 SPRD 源码默认打开多用户实际无效果问题修复。

m0_60898338的博客 449

从被秒封到稳过Cloudflare!Canvas/WebGL/WebRTC多维度浏览器指纹隐身全实战

反爬和反反爬,本质上是一场持续的博弈,没有100%的隐身,只有不断升级的伪装。网上很多教程只给了个基础的噪声代码,却没讲透反爬的检测逻辑,导致你用了还是被封。这套方案的核心,不是简单的API重写,而是站在反爬的角度,模拟真实用户的所有特征,保证所有指纹的一致性、合理性、完整性。目前这套方案我用了半年,跑了上千万次请求,稳过国内国外99%的主流反爬平台,没有出现过一次因为指纹关联导致的批量封禁。大家有什么更狠的反爬场景,或者更好的伪装技巧,欢迎评论区一起交流。

专注于Python爬虫开发,分享爬虫技巧、项目实战与反爬经验,使用Scrapy、BeautifulSoup等工具,解决数据抓取难题。 1193

【读书笔记】《高质量C/C++编程指南》

文件结构、程序版式、命名规则、表达式与基本语句、常量、函数设计、内存管理、C++高级特性

phoenixFlyzzz的博客 2821

【教程4>第6章>第15节】 基于I+jQ复数信号的LS信道估计和均衡实现1——matlab仿真分析,QPSK为例

在基于QPSK调制的通信系统中,LS信道估计和均衡是用于补偿信道失真、提高信号传输质量的重要技术。以下将详细介绍基于I+jQ复数信号的 LS 信道估计和均衡的实现原理与过程。可以看到,通过线性插值之后,QPSK信号的星座图可以模糊的看到四个星座点(图2),通过非线性插值之后,可以清晰的看到QPSK的四个星座点(图3)。可以看到,信道均衡显著提高了通信系统的可靠性和有效性。

FPGA/MATLAB学习教程/源码/项目合作开发 657

[转载]<<高质量c/c++编程指南>>读书笔记

一个strcpy函数的代码 能考查三个方面 (1) 编程风格 (2) 出错处理 (3) 算法复杂度分析(用于提供性能) 定义编程老手和编程高手 定义1:能长期稳定地编写出高质量程序的程序员称为编程老手 定义2:能长期稳定地编写出高难度、高质量的程序与称为编程高手 第一章 文件结构 1.1 版权和版本的声明 版权和版本的声明位于头文件和定义文件的开头,主要内容有: (1) 版权信息 (2...

weixin_30432579的博客 350

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

高质量C/C++编程指南读书笔记(1)

最近在读高质量C/C++编程指南,我只将一些我自己平时没有注意的东西记录下来。 这本书挺好的,推荐大家阅读。以下是我的读书笔记: 1.1版权和版本的声明 example: /************************************************* Copyright (C),fmh File name: xxx.cpp Author: fmh      Ve

Sean 683

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

<<高质量c/c++编程指南>>读书笔记1

一个strcpy函数的代码 能考查三个方面 (1) 编程风格 (2) 出错处理 (3) 算法复杂度分析(用于提供性能) 定义编程老手和编程高手 定义1:能长期稳定地编写出高质量程序的程序员称为编程老手 定义2:能长期稳定地编写出高难度、高质量的程序与称为编程高手   第一章 文件结构 1.1 版权和版本的声明 版权和版本的声明位于头文件和定义文件的开头,主要内容有:

孤独的糖三角 3882

高质量C/C++编程指南(读书笔记)

第1章 为防止头文件被重复引用,应当用 ifndef/define/endif 结构产生预处理块 使用 #include 引用标准库头文件,使用 #include"filename.h" 引用非标准库头文件 头文件中只存放“声明”,不存放“定义” 第2章 应在关联性不强的语句间添加空行 一行代码只做一件事情 if、for、while、do 等语句...

失落的世界 124

高质量C/C++编程指南读书笔记(2)

7.2.1用malloc或者new申请内存之后,应该立即检查指针值是否为 NULL。防止使用指针值为NULL的内存。 7.2.3动态内存的申请与释放必须配对,防止内存泄漏。 7.2.4用free或delete释放了内存之后,立即将指针设置为NULL,防止 产生“野指针”。 7.7野指针的成因: (1)指针变量没有被初始化。 (2)指针p被free或者delete之后,没有置为NULL

Sean 830

【备战2014笔面试】读书笔记-高质量C/C++编程指南(1)

第一章。头文件 【规则1-2-1】为了防止头文件被重复引用,应当用 ifndef/define/endif 结构产生预 处理块。 【规则1-2-2】用 #include 格式来引用标准库的头文件(编译器将从 标准库目录开始搜索)。 【规则1-2-3】用 #include “filename.h” 格式来引用非标准库的头文件(编译器 将从用户的工作目录开始搜索)。 //

906

高质量C/C++编程指南--读书笔记七 7、8章

第七章 内存管理 一、内存管理 1、内存使用规则:(P45) (1)、用malloc 或 new 申请内存之后,应该立即检查指针值是否为NULL 。防止使用指针值为 NULL 的内存。 (2)、不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。 (3)、避免数组或指针的下标越界,特别要当心发生“多1” 或者 “少1” 操作。 (4)、动态内存的申请和释放必须配...

weixin_30348519的博客 112

高质量C/C++编程指南--读书笔记一 1、2章

第一章 文件结构 一、知识点 1、头文件编写规则:(P12) (1)、为了防止头文件被重复引用,应当用ifndef/ define/ endif 结构产生预处理块。 (2)、用#include <filename.h>格式引用标准库的头文件(编译器将从标准库目录开始搜索)。 (3)、用#include "filename.h"格式引用非标准库的头文件(编译器将从用户的...

weixin_30621919的博客 129

高质量C/C++编程指南--读书笔记五 5、6章

第五章 常量 一、知识点 1、const与#define的比较:(P33) (1)、const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误。 (2)、有些集成化的调试工具可以对const常量进行调试,但是不能对宏常量进行调试。 2、类中的const数据成员只在某个...

weixin_30246221的博客 106

高质量C/C++编程指南--读书笔记三 3、4章

第三章 命名规则 一、知识点 1、命名规则:(P23) (1)、在Windows中应用程序的标识符通常采用“大小写”混排的方式,如AddChild。而Unix应用程序的标识符通常采用“小写加下划线”的方式,如add_child。 (2)、全局函数的名字应当使用“动词” 或者“ 动词+ 名词”。类的成员函数应当只用“动词”,被省略掉的名词就是对象本身。 2、简单的Wind...

weixin_30407099的博客 114

【备战2014笔面试】读书笔记-高质量C/C++编程指南(2)

第十章  类的继承与组合 继承 如果A 是基类,B 是A 的派生类,那么B 将继承A 的数据和函数。例如: class A {  public:        void Func1(void);        void Func2(void); };   class B : public A {  public:       

706
上一篇: 《C陷阱与缺陷》读书笔记
下一篇: 自己实现简易的内存泄露检测工具VLD
SmartBrave
博客等级 码龄12年 25粉丝 69原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值