第一章 文件结构
- 头文件由三部分构成:
- 头文件开头处的版权和版本声明;
- 预处理块;
- 函数和类结构声明等;
- 【规则 1-2-1】为了防止头文件被重复引用,应当用 ifndef/define/endif 结构产生预处理块。
- 【建议 1-2-1】头文件中只存放“声明”而不存放“定义”。
- 定义文件由三部分组成:
- 定义文件开头处的版权和版本声明;
- 对一些头文件的引用;
- 程序的实现体(包括数据和代码)。
第二章 程序的版式
- 【规则 2-1-1】在每个类声明之后、每个函数定义结束之后都要加空行。在一个函数体内,逻揖上密切相关的语句之间不加空行,其它地方应加空行分隔。
- 【建议 2-2-1】尽可能在定义变量的同时初始化该变量(就近原则)
- 【规则 2-6-1】应当将修饰符 * 和 & 紧靠变量名
- 【规则 2-7-6】注释的位置应与被描述的代码相邻,可以放在代码的上方或右方,不可放在下方。
- 定义类时应主张将public类型的函数写在前面,而将private类型的数据写在后面,即“以行为为中心”。
第三章 命名规则
- 【规则 3-1-1】标识符应当直观且可以拼读,可望文知意,不必进行“解码”。
- 【规则 3-1-2】标识符的长度应当符合“ min-length && max-information”原则。
- 【规则 3-1-4】程序中不要出现仅靠大小写区分的相似的标识符。
- 【规则 3-1-5】程序中不要出现标识符完全相同的局部变量和全局变量,尽管两者的作用域不同而不会发生语法错误,但会使人误解。
- 简单Windows程序命名规则:
- 【规则 3-2-1】类名和函数名用大写字母开头的单词组合而成。
- 【规则 3-2-2】变量和参数用小写字母开头的单词组合而成。
- 【规则 3-2-3】常量全用大写的字母,用下划线分割单词。
- 【规则 3-2-4】静态变量加前缀 s_(表示 static)。如果不得已需要全局变量,则使全局变量加前缀 g_(表示 global)。
- 【规则 3-2-6】类的数据成员加前缀 m_(表示 member),这样可以避免数据成员与成员函数的参数同名。
- /*简单Unix应用程序命名规则:
- NUL*///请忽略它-_-|
第四章 表达式和基本语句
- 运算符的优先级与结合律如下所示。注意一元运算符 + - * 的优先级高于对应的二元运算符。
- 【规则 4-1-1】如果代码行中的运算符比较多,用括号确定表达式的操作顺序,避免使用默认的优先级。
- if语句中各种类型变量与零值比较:
- bool变量:【规则 4-3-1】不可将布尔变量直接与 TRUE、 FALSE 或者 1、 0 进行比较。(应为if(flag),if(!flag))
- 整型变量:【规则 4-3-2】应当将整型变量用“ ==”或“! =”直接与 0 比较。
- 浮点变量:【规则 4-3-3】不可将浮点变量用“ ==”或“! =”与任何数字比较。(浮点数有精度限制,应为if ((x>=-EPSINON) && (x<=EPSINON)),其中 EPSINON 是允许的误差(即精度))
- 指针变量:【规则 4-3-4】应当将指针变量用“ ==”或“! =”与 NULL 比较。
- 为防止将if(p==NULL)写成if(p=NULL),可以将其写成if(NULL==p)。
- 【建议 4-4-1】在多重循环中,如果有可能,应当将最长的循环放在最内层,最短的循环放在最外层,以减少 CPU 跨切循环层的次数。
- 【建议 4-4-2】如果循环体内存在逻辑判断,并且循环次数很大,宜将逻辑判断移到高质量 C++/C 编程指南,循环体的外面。(循环“流水线”作业)
- 【规则 4-5-1】不可在 for 循环体内修改循环变量,防止 for 循环失去控制。
- 【建议 4-5-1】建议 for 语句的循环控制变量的取值采用“半开半闭区间”写法。
- switch语句中,【规则 4-6-1】每个 case 语句的结尾不要忘了加 break,否则将导致多个分支重叠(除非有意使多个分支重叠)。
- 【规则 4-6-2】不要忘记最后那个 default 分支。即使程序真的不需要 default 处理,也应该保留语句 default : break; 这样做并非多此一举,而是为了防止别人误以为你忘了 default 处理。
第五章 常量
- 常量是一种标识符, 它的值在运行期间恒定不变。 C 语言用 #define 来定义常量 (称为宏常量)。 C++ 语言除了 #define 外还可以用 const 来定义常量(称为 const 常量)。
- const 与 #define 的比较
- const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误(边际效应)。
- 有些集成化的调试工具可以对 const 常量进行调试,但是不能对宏常量进行调试。
- 【规则 5-2-1】在 C++ 程序中只使用 const 常量而不使用宏常量,即 const 常量完全取代宏常量。
- 不能在类声明中初始化 const 数据成员。const 数据成员的初始化只能在类构造函数的初始化表中进行。
- 怎样才能建立在整个类中都恒定的常量呢?别指望 const 数据成员了,应该用类中的枚举常量来实现(是不是只能定义为整型常量?!见下一条)。
eg:class A
{…
enum { SIZE1 = 100, SIZE2 = 200}; // 枚举常量
int array1[SIZE1];
int array2[SIZE2];
}; - 枚举常量不会占用对象的存储空间,它们在编译时被全部求值。枚举常量的缺点是:它的隐含数据类型是整数,其最大值有限,且不能表示浮点数(如 PI=3.14159)。
第六章 函数设计
- 【规则 6-1-3】如果参数是指针,且仅作输入用,则应在类型前加 const,以防止该指针在函数体内被意外修改。
- 【规则 6-1-4】如果输入参数以值传递的方式传递对象,则宜改用“ const &”方式来传递,这样可以省去临时对象的构造和析构过程,从而提高效率。
- 【建议 6-1-2】尽量不要使用类型和数目不确定的参数。这种风格的函数在编译时丧失了严格的类型安全检查。C 标准库函数 printf 是采用不确定参数的典型代表,其原型为:int printf(const chat *format[, argument]…);
- 【规则 6-2-1】不要省略返回值的类型。
- C 语言中,凡不加类型说明的函数,一律自动按整型处理。这样做不会有什么好处,却容易被误解为 void 类型。
- C++语言有很严格的类型安全检查,不允许上述情况发生。
- 【规则 6-2-2】函数名字与返回值类型在语义上不可冲突。违反这条规则的典型代表是 C 标准库函数 getchar。
- 【规则 6-2-3】不要将正常值和错误标志混在一起返回。正常值用输出参数获得,而错误标志用 return 语句返回。
- 【建议 6-2-1】有时候函数原本不需要返回值,但为了增加灵活性如支持链式表达,可以附加返回值。例如字符串拷贝函数 strcpy 的原型:char *strcpy(char *strDest, const char *strSrc);
- 【建议 6-2-2】如果函数的返回值是一个对象,有些场合用“引用传递”替换“值传递”可以提高效率。而有些场合只能用“值传递”而不能用“引用传递”,否则会出错。
- 【规则 6-3-1】在函数体的“入口处”,对参数的有效性进行检查。
- 【规则 6-3-2】在函数体的“出口处”,对 return 语句的正确性和效率进行检查。
注意事项如下:- return 语句不可返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁。
- 要搞清楚返回的究竟是“值”、“指针”还是“引用”。
- 如果函数返回值是一个对象,要考虑 return 语句的效率。
eg:return String(s1 + s2);
这是临时对象的语法,表示“创建一个临时对象并返回它”。不要以为它与“先创建一个局部对象 temp 并返回它的结果”是等价的,如
String temp(s1 + s2);
return temp;
实质不然,上述代码将发生三件事。首先, temp 对象被创建,同时完成初始化;然后拷贝构造函数把 temp 拷贝到保存返回值的外部存储单元中;最后, temp 在函数结束时被销毁(调用析构函数)。然而“创建一个临时对象并返回它”的过程是不同的,编译器直接把临时对象创建并初始化在外部存储单元中,省去了拷贝和析构的化费,提高了效率。
- 【建议 6-4-4】不仅要检查输入参数的有效性,还要检查通过其它途径进入函数体内的变量的有效性,例如全局变量、文件句柄等。
- 断言 assert 是仅在 Debug 版本起作用的宏,它用于检查“不应该”发生的情况。为了不在程序的 Debug 版本和 Release 版本引起差别, assert 不应该产生任何副作用。所以 assert 不是函数,而是宏。
- 【规则 6-5-1】使用断言捕捉不应该发生的非法情况。不要混淆非法情况与错误情况之间的区别,后者是必然存在的并且是一定要作出处理的。
- 指针与引用:
- 引用被创建的同时必须被初始化(指针则可以在任何时候被初始化)。
- 不能有 NULL 引用,引用必须与合法的存储单元关联(指针则可以是 NULL)。
- 一旦引用被初始化,就不能改变引用的关系(指针则可以随时改变所指的对象)。
- 引用的主要功能是传递函数的参数和返回值。 C++语言中,函数的参数和返回值的传递方式有三种:值传递、指针传递和引用传递。
- “引用传递”的性质象“指针传递”,而书写方式象“值传递”。
第七章 内存管理
- 内存分配方式有三种:
- 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量, static 变量。
- 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
- 从堆上分配,亦称动态内存分配。程序在运行的时候用 malloc 或 new 申请任意多少的内存,程序员自己负责在何时用 free 或 delete 释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。
- 常见的内存错误及其对策如下:
- 内存分配未成功,却使用了它。
- 内存分配虽然成功,但是尚未初始化就引用它。
- 内存分配成功并且已经初始化,但操作越过了内存的边界。
- 忘记了释放内存,造成内存泄露。
- 释放了内存却继续使用它。
有三种情况:- 程序中的对象调用关系过于复杂,实在难以搞清楚某个对象究竟是否已经释放了内存,此时应该重新设计数据结构,从根本上解决对象管理的混乱局面。
- 函数的 return 语句写错了,注意不要返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁。
- 使用 free 或 delete 释放了内存后,没有将指针设置为 NULL。导致产生“野指针”。(空悬指针?)
- 【规则 7-2-1】用 malloc 或 new 申请内存之后,应该立即检查指针值是否为 NULL。防止使用指针值为 NULL 的内存。
- 【规则 7-2-2】不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。
- 【规则 7-2-3】避免数组或指针的下标越界,特别要当心发生“多 1”或者“少 1”操作。
- 【规则 7-2-4】动态内存的申请与释放必须配对,防止内存泄漏。
- 【规则 7-2-5】用 free 或 delete 释放了内存之后,立即将指针设置为 NULL,防止产生“野指针”。
- 指针与数组:
- 数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。数组名对应着(而不是指向)一块内存,其地址与容量在生命期内保持不变,只有数组的内容可以改变。
- 指针可以随时指向任意类型的内存块,它的特征是“可变”,所以我们常用指针来操作动态内存。指针远比数组灵活,但也更危险。
- 不能对数组名进行直接复制与比较。应该用标准库函数 strcpy 进行复制,用标准库函数 strcmp进行比较。
- 用运算符 sizeof 可以计算出数组的容量(字节数)。但是 sizeof(指针)的值却是 4。C++/C 语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。
- 当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。
- 如果函数的参数是一个指针,不要指望用该指针去申请动态内存。如果非得要用指针参数去申请内存, 那么应该改用 “指向指针的指针”,或者用函数返回值来传递动态内存。
- free和delete只是把指针所指的内存给释放掉,但并没有把指针本身干掉。指针的地址仍然不变(非 NULL),只是该地址对应的内存是垃圾。此时如果不将指针设为NULL,那么if(NULL==p)语句将不起作用。
- 我们发现指针有一些“似是而非”的特征:
- 指针消亡了,并不表示它所指的内存会被自动释放。
- 内存被释放了,并不表示指针会消亡或者成了 NULL 指针。
- “野指针”不是 NULL 指针,是指向“垃圾”内存的指针。成因主要有两种:
- 指针变量没有被初始化。
- 指针 p 被 free 或者 delete 之后,没有置为 NULL。
- 指针操作超越了变量的作用范围。
- malloc/free和new/delete:前者是库函数,对于自定义的对象,不能自动调用其构造和析构函数;后者是关键字,可以调用构造函数和析构函数。对于内部数据类型两者是等价的。
- 如果用 free 释放“ new 创建的动态对象”,那么该对象因无法执行析构函数而可能导致程序出错。 如果用 delete 释放“ malloc 申请的动态内存”,理论上讲程序不会出错,但是该程序的可读性很差。
- malloc、free、new、delete:
- malloc 返回值的类型是 void *,所以在调用 malloc 时要显式地进行类型转换,将void * 转换成所需要的指针类型。
- malloc 函数本身并不识别要申请的内存是什么类型,它只关心内存的总字节数。
- 为什么 free 函数不象 malloc 函数那样复杂呢?这是因为指针 p 的类型以及它所指的内存的容量事先都是知道的,语句 free(p)能正确地释放内存。如果 p 是 NULL 指针,那么 free 对 p 无论操作多少次都不会出问题。如果 p 不是 NULL 指针,那么 free 对 p连续操作两次就会导致程序运行错误。
- new 内置了 sizeof、类型转换和类型安全检查功能。对于非内部数据类型的对象而言, new 在创建动态对象的同时完成了初始化工作。如果对象有多个构造函数,那么 new 的语句也可以有多种形式。
- 如果用 new 创建对象数组,那么只能使用对象的无参数构造函数。
eg:Obj *objects = new Obj[100]; // 创建 100 个动态对象
不能写成Obj *objects = new Obj[100](1);// 创建 100 个动态对象的同时赋初值 1 - 在用 delete 释放对象数组时,留意不要丢了符号‘ []’。
第八章 C++函数的高级特性
- 对比于 C 语言的函数, C++增加了重载 ( overloaded)、 内联 ( inline)、 const 和 virtual四种新机制。其中重载和内联机制既可用于全局函数也可用于类的成员函数, const 与virtual 机制仅用于类的成员函数。
- 如果 C++程序要调用已经被编译后的 C 函数,C++提供了一个 C 连接交换指定符号 extern“ C”来解决这个问题。
- 注意并不是两个函数的名字相同就能构成重载。全局函数和类的成员函数同名不算重载,因为函数的作用域不同。全局函数被调用时应加‘ ::’标志。
- 成员函数的重载、覆盖与隐藏:
- 成员函数被重载的特征:
- 相同的范围(在同一个类中);
- 函数名字相同;
- 参数不同;
- virtual 关键字可有可无。
- 覆盖是指派生类函数覆盖基类函数,特征是:
- 不同的范围(分别位于派生类与基类);
- 函数名字相同;
- 参数相同;
- 基类函数必须有 virtual 关键字。
- “隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
- 如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无 virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
- 如果派生类的函数与基类的函数同名, 并且参数也相同, 但是基类函数没有 virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。
- 成员函数被重载的特征:
- 【规则 8-3-1】参数缺省值只能出现在函数的声明中,而不能出现在定义体中。
- 【规则 8-3-2】如果函数有多个参数,参数只能从后向前挨个儿缺省,否则将导致函数调用语句怪模怪样。
- 运算符与普通函数在调用时的不同之处是:对于普通函数,参数出现在圆括号内;而对于运算符,参数出现在其左、右侧。
- 如果运算符被重载为全局函数,那么只有一个参数的运算符叫做一元运算符,有两个参数的运算符叫做二元运算符。
如果运算符被重载为类的成员函数,那么一元运算符没有参数,二元运算符只有一个右侧参数,因为对象自己成了左侧参数。 - 在 C++运算符集合中,有一些运算符是不允许被重载的。这种限制是出于安全方面的考虑,可防止错误和混乱。(sizeof,::,.,等等)
- 不能改变 C++内部数据类型(如 int,float 等)的运算符。
- 不能重载‘ .’,因为‘ .’在类中对任何成员都有意义,已经成为标准用法。
- 不能重载目前 C++运算符集合中没有的符号,如#,@,$等。原因有两点,一是难以理解,二是难以确定优先级。
- 对已经存在的运算符进行重载时,不能改变优先级规则,否则将引起混乱。
- 在 C 程序中,可以用宏代码提高执行效率。宏代码本身不是函数,但使用起来象函数。预处理器用复制宏代码的方式代替函数调用, 省去了参数压栈、生成汇编语言的 CALL调用、返回参数、执行 return 等过程,从而提高了速度。使用宏代码最大的缺点是容易出错,预处理器在复制宏代码时常常产生意想不到的边际效应。使用宏代码还有另一种缺点:无法操作类的私有数据成员。
- 对于任何内联函数,编译器在符号表里放入函数的声明(包括名字、参数类型、返回值类型)。如果编译器没有发现内联函数存在错误,那么该函数的代码也被放入符号表里。在调用一个内联函数时,编译器首先检查调用是否正确(进行类型安全检查,或者进行自动类型转换,当然对所有的函数都一样)。如果正确,内联函数的代码就会直接替换函数调用,于是省去了函数调用的开销。
- 所以在 C++ 程序中,应该用内联函数取代所有宏代码,“断言 assert”恐怕是唯一的例外。 assert 是仅在 Debug 版本起作用的宏,它用于检查“不应该”发生的情况。为了不在程序的 Debug 版本和 Release 版本引起差别, assert 不应该产生任何副作用。 如果 assert 是函数, 由于函数调用会引起内存、 代码的变动, 那么将导致 Debug版本与 Release 版本存在差异。 所以 assert 不是函数, 而是宏。
- 关键字 inline 必须与函数定义体放在一起才能使函数成为内联,仅将 inline 放在函数声明前面不起任何作用。所以说, inline 是一种“用于实现的关键字”,而不是一种“用于声明的关键字”。
- 定义在类声明之中的成员函数将自动地成为内联函数。(类内声明,外部定义的函数不是内联的?)
- 以下情况不宜使用内联:
- 如果函数体内的代码比较长,使用内联将导致内存消耗代价较高。
- 如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大。
- 类的构造函数和析构函数容易让人误解成使用内联更有效。要当心构造函数和析构函数可能会隐藏一些行为,如“偷偷地”执行了基类或成员对象的构造函数和析构函数。所以不要随便地将构造函数和析构函数的定义体放在类声明中。
第九章 类的构造函数、析构函数与赋值函数
- 每个类只有一个析构函数和一个赋值函数,但可以有多个构造函数(包含一个拷贝构造函数,其它的称为普通构造函数)。
- 既然能自动生成函数,为什么还要程序员编写?
原因如下:- 如果使用“缺省的无参数构造函数”和“缺省的析构函数”,等于放弃了自主“初始化”和“清除”的机会, C++发明人 Stroustrup 的好心好意白费了。
- “缺省的拷贝构造函数”和“缺省的赋值函数”均采用“位拷贝”而非“值拷贝”的方式来实现,倘若类中含有指针变量,这两个函数注定将出错。
- 构造函数有个特殊的初始化方式叫“初始化表达式表”(简称初始化表)。初始化表位于函数参数表之后,却在函数体 {} 之前。这说明该表里的初始化工作发生在函数体内的任何代码被执行之前。
- 构造函数初始化表的使用规则:
- 如果类存在继承关系,派生类必须在其初始化表里调用基类的构造函数。
- 类的 const 常量只能在初始化表里被初始化,因为它不能在函数体内用赋值的方式来初始化。
- 类的数据成员的初始化可以采用初始化表或函数体内赋值两种方式,这两种方式的效率不完全相同。
- 非内部数据类型的成员对象应当采用第一种方式初始化,以获取更高的效率。(第二种方式会先创建其对象,调用无参构造函数,再调用其赋值函数)。
- 对于内部数据类型的数据成员而言,两种初始化方式的效率几乎没有区别,但后者的程序版式似乎更清晰些。
- 构造从类层次的最根处开始,在每一层中,首先调用基类的构造函数,然后调用成员对象的构造函数。析构则严格按照与构造相反的次序执行,该次序是唯一的,否则编译器将无法自动执行析构过程。成员对象初始化的次序完全不受它们在初始化表中次序的影响,只由成员对象在类中声明的次序决定。
- 缺省的拷贝构造函数以“位拷贝”方式执行,以string类为例,将造成三个错误:
- 一是 b.m_data 原有的内存没被释放,造成内存泄露;
- 二是b.m_data 和 a.m_data 指向同一块内存, a 或 b 任何一方变动都会影响另一方;
- 三是在对象被析构时, m_data 被释放了两次。
- 拷贝构造函数和赋值函数非常容易混淆,常导致错写、错用。拷贝构造函数是在对象被创建时调用的,而赋值函数只能被已经存在了的对象调用。
- 类 String 拷贝构造函数与普通构造函数的区别是:在函数入口处无需与 NULL 进行比较,这是因为“引用”不可能是 NULL,而“指针”可以为 NULL。
- string类拷贝构造函数四部曲:
- 检查自赋值;
- 释放原有空间;
- 重新分配空间并拷贝;函数 strlen 返回的是有效字符串长度,不包含结束符‘\0’。函数 strcpy 则连‘\0’一起复制。
- 返回本对象的引用。目的是为了实现象 a = b = c 这样的链式表达。注意不要将 return *this 错写成 return this 。
- 偷懒的办法处理拷贝构造函数与赋值函数:只需将拷贝构造函数和赋值函数声明为私有函数,不用编写代码。
- 基类的构造函数、析构函数、赋值函数都不能被派生类继承。如果类之间存在继承关系,在编写上述基本函数时应注意以下事项:
- 派生类的构造函数应在其初始化表里调用基类的构造函数。
- 基类与派生类的析构函数应该为虚(即加 virtual 关键字)。如果析构函数不为虚,将不会调用派生类的析构函数。
- 在编写派生类的赋值函数时,注意不要忘记对基类的数据成员重新赋值。即调用基类的赋值函数。(因为不能直接操作基类的私有成员)
第十章 类的继承与组合
- 继承:若在逻辑上 B 是 A 的“一种”,并且 A 的所有功能和属性对 B 而言都有意义,则允许 B 继承 A 的功能和属性。
- 组合:若在逻辑上 A 是 B 的“一部分” ( a part of),则不允许 B 从 A 派生,而是要用 A 和其它东西组合出 B。
第十一章 其他编程经验
- 使用 const 提高函数的健壮性:const不止能定义const常量,更大的魅力是它可以修饰函数的参数、返回值,甚至函数的定义体。
- const修饰函数参数:
- 如果参数作输出用,不论它是什么数据类型,也不论它采用“指针传递”还是“引用传递”,都不能加 const 修饰,否则该参数将失去输出功能。
- const 只能修饰输入参数:
- 如果输入参数采用“指针传递”,那么加 const 修饰可以防止意外地改动该指针,起到保护作用。
- 如果输入参数采用“值传递”,由于函数将自动产生临时变量用于复制该参数,该输入参数本来就无需保护,所以不要加 const 修饰。
- 对于非内部数据类型的参数而言,象 void Func(A a) 这样声明的函数注定效率比较底。因为函数体内将产生 A 类型的临时对象用于复制参数 a,而临时对象的构造、复制、析构过程都将消耗时间。
- const 修饰函数的返回值
- 如果给以“指针传递”方式的函数返回值加 const 修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加 const 修饰的同类型指针。eg:const char * GetString(void);
- 如果函数返回值采用“值传递方式”,由于函数会把返回值复制到外部临时的存储单元中,加 const 修饰没有任何价值。
- 如果返回值不是内部数据类型,将函数 A GetA(void) 改写为 const A & GetA(void)的确能提高效率。但此时千万千万要小心,一定要搞清楚函数究竟是想返回一个对象的“拷贝”还是仅返回“别名”就可以了,否则程序会出错。
- 函数返回值采用“引用传递”的场合并不多,这种方式一般只出现在类的赋值函数中,目的是为了实现链式表达。
- const成员函数:任何不会修改数据成员的函数都应该声明为 const 类型。const 成员函数的声明看起来怪怪的: const 关键字只能放在函数声明的尾部,大概是因为其它地方都已经被占用了。
- const修饰函数参数:
- 【建议 11-3-2】变量(指针、数组)被创建之后应当及时把它们初始化,以防止把未被初始化的变量当成右值使用。
【完】
欢迎关注我的微信公众号:共同商讨,共同进步!