C++高质量编程
C++高质量编程
C++编程,对于开发者,都可以写上二段,但是真正能写出高质量的代码估计还是比较少,同样我也是学习者,本文作为平时学习日志吧。随时都会更新……
1、const 数据成员只在某个对象生存期内是常量,而对于整个类而言却是可变的,
因为类可以创建多个对象,不同的对象其const 数据成员的值可以不同
第6 章 函数设计
1、函数接口的两个要素是参数和返回值。
避免函数有太多的参数,参数个数尽量控制在5 个以内。
尽量不要使用类型和数目不确定的参数。
2、在函数体的“入口处”,对参数的有效性进行检查。
很多程序错误是由非法参数引起的,我们应该充分理解并正确使用“断言”(assert)来防止此类错误。
断言assert 是仅在Debug 版本起作用的宏,它用于检查“不应该”发生的情况。
assert((pvTo != NULL) && (pvFrom != NULL));
3、在函数体的“出口处”,对return 语句的正确性和效率进行检查。
如果return 语句写得不好,函数要么出错,要么效率低下。
(1)return 语句不可返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁。
4、引用与指针的比较
引用的一些规则如下:
(1)引用被创建的同时必须被初始化(指针则可以在任何时候被初始化)。
(2)不能有NULL 引用,引用必须与合法的存储单元关联(指针则可以是NULL)。
(3)一旦引用被初始化,就不能改变引用的关系(指针则可以随时改变所指的对象)
引用的主要功能是传递函数的参数和返回值。
C++语言中,函数的参数和返回值的传递方式有三种:值传递、指针传递和引用传递。
5、指针能够毫无约束地操作内存中的如何东西,尽管指针功能强大,但是非常危险。
第7 章 内存管理
1、内存分配方式有三种:
(1) 从静态存储区域分配。
内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static 变量。
(2) 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。
栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
(3) 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc 或new 申请任意多少的内存,
程序员自己负责在何时用free 或delete 释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。
2、常见的内存错误及其对策
(1)内存分配未成功,却使用了它。
常用解决办法是,在使用内存之前检查指针是否为NULL。
如果指针p 是函数的参数,那么在函数的入口处用assert(p!=NULL)进行检查。
如果是用malloc 或new 来申请内存,应该用if(p==NULL)或if(p!=NULL)进行防错处理。
(2)内存分配虽然成功,但是尚未初始化就引用它。
所以无论用何种方式创建数组,都别忘了赋初值,即便是赋零值也不可省略,不要嫌麻烦。
(3)内存分配成功并且已经初始化,但操作越过了内存的边界。
(4)忘记了释放内存,造成内存泄露。
含有这种错误的函数每被调用一次就丢失一块内存。动态内存的申请与释放必须配对,
程序中malloc 与free 的使用次数一定要相同,否则肯定有错误(new/delete 同理)。
(5)释放了内存却继续使用它。
(1)程序中的对象调用关系过于复杂,实在难以搞清楚某个对象究竟是否已经释放了内
存,此时应该重新设计数据结构,从根本上解决对象管理的混乱局面。
(2)函数的return 语句写错了,注意不要返回指向“栈内存”的“指针”或者“引用”,
因为该内存在函数体结束时被自动销毁。
(3)使用free 或delete 释放了内存后,没有将指针设置为NULL。导致产生“野指针”。
3、用malloc 或new 申请内存之后,应该立即检查指针值是否为NULL。防止使用指针值为NULL 的内存。
不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。
避免数组或指针的下标越界,特别要当心发生“多1”或者“少1”操作。
动态内存的申请与释放必须配对,防止内存泄漏。
用free 或delete 释放了内存之后,立即将指针设置为NULL,防止产生“野指针”。
4、指针与数组的对比
C++/C 程序中,指针和数组在不少地方可以相互替换着用。
数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。数组名对应着(而不是指向)一块内存。
指针可以随时指向任意类型的内存块,它的特征是“可变”,所以我们常用指针来操作动态内存。
char *p = “world”;指针p 指向常量字符串“world”(位于静态存储区,内容为world),常量字符串的内容是不可以被修改的。
从语法上看,编译器并不觉得语句p[0]= ‘X’有什么不妥,但是该语句企图修改常量字符串的内容而导致运行错误。
5、C++/C 语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。
注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。
6、指针参数是如何传递内存的?
如果函数的参数是一个指针,不要指望用该指针去申请动态内存。
7、编译器总是要为函数的每个参数制作临时副本,指针参数p 的副本是 _p,编译器使 _p = p。
如果函数体内的程序修改了_p 的内容,就导致参数p 的内容作相应的修改。这就是指针可以用作输出参数的原因。
在本例中,_p 申请了新的内存,只是把_p 所指的内存地址改变了,但是p 丝毫未变。所以函数GetMemory
并不能输出任何东西。事实上,每执行一次GetMemory 就会泄露一块内存,因为没有用free 释放内存。
8、如果非得要用指针参数去申请内存,那么应该改用“指向指针的指针“。
由于“指向指针的指针”这个概念不容易理解,我们可以用函数返回值来传递动态内存。
这里强调不要用return 语句返回指向“栈内存”的指针,因为该内存在函数结束时自动消亡。
9、发现指针p 被free 以后其地址仍然不变(非NULL),只是该地址对应的内存是垃圾,p 成了“野指针”。
如果此时不把p 设置为NULL,会让人误以为p 是个合法的指针。
10、动态内存会被自动释放吗?
(1)指针消亡了,并不表示它所指的内存会被自动释放。
(2)内存被释放了,并不表示指针会消亡或者成了NULL 指针。
11、“野指针”不是NULL 指针,是指向“垃圾”内存的指针。
“野指针”的成因主要有两种:
(1)指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL 指针,它的缺省值是随机的,
它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。
(2)指针p 被free 或者delete 之后,没有置为NULL,让人误以为p 是个合法的指针。
(3)指针操作超越了变量的作用范围。
12、有了malloc/free 为什么还要new/delete ?
1、 malloc 与free 是C++/C 语言的标准库函数,new/delete 是C++的运算符。
2、对于非内部数据类型的对象而言,光用maloc/free 无法满足动态对象的要求。对象
在创建的同时要自动执行构造函数, 对象在消亡之前要自动执行析构函数。
13、内存耗尽怎么办?
如果在申请动态内存时找不到足够大的内存块,malloc 和new 将返回NULL 指针,
宣告内存申请失败。通常有三种方式处理“内存耗尽”问题。
(1)判断指针是否为NULL,如果是则马上用return 语句终止本函数。
(2)判断指针是否为NULL,如果是则马上用exit(1)终止整个程序的运行。
(3)为new 和malloc 设置异常处理函数。
14、int *p = (int *) malloc(sizeof(int) * length);
如果p 是NULL 指针,那么free 对p 无论操作多少次都不会出问题。如果p 不是NULL 指针,那么free 对p
连续操作两次就会导致程序运行错误。
int *p2 = new int[length];因为new 内置了sizeof、类型转换和类型安全检查功能。
15、我的经验教训是:
(1)越是怕指针,就越要使用指针。不会正确使用指针,肯定算不上是合格的程序员。
(2)必须养成“使用调试器逐步跟踪程序”的习惯,只有这样才能发现问题的本质。
第8 章 C++函数的高级特性
1、对比于C 语言的函数,C++增加了重载(overloaded)、内联(inline)、const 和virtual四种新机制。
其中重载和内联机制既可用于全局函数也可用于类的成员函数,const 与virtual 机制仅用于类的成员函数。
2、成员函数被重载的特征:
(1)相同的范围(在同一个类中);
(2)函数名字相同;
(3)参数不同;
(4)virtual 关键字可有可无。
覆盖是指派生类函数覆盖基类函数,特征是:
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有virtual 关键字。
3、运算符重载
在C++语言中,可以用关键字operator 加上运算符来表示函数,叫做运算符重载。
Complex Add(const Complex &a, const Complex &b);
Complex operator +(const Complex &a, const Complex &b);
运算符与普通函数在调用时的不同之处是:对于普通函数,参数出现在圆括号内;而对于运算符,参数出现在其左、右侧。
4、函数内联
C++ 语言支持函数内联,其目的是为了提高函数的执行效率(速度)。
定义在类声明之中的成员函数将自动地成为内联函数.
内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。
每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。
第9 章 类的构造函数、析构函数与赋值函数
1、根据经验,不少难以察觉的程序错误是由于变量没有被正确初始化或清除造成的,而初始化和清除工作很容易被人遗忘。
第11 章 其它编程经验
1、const 更大的魅力是它可以修饰函数的参数、返回值,甚至函数的定义体。
2、对于非内部数据类型的输入参数,应该将“值传递”的方式改为“const 引用传递”,目的是提高效率。
例如将void Func(A a) 改为void Func(const A &a)。
对于内部数据类型的输入参数,不要将“值传递”的方式改为“const 引用传递”。否则既达不到提高效率的目的,
又降低了函数的可理解性。例如void Func(int x) 不应该改为void Func(const int &x)。
3、任何不会修改数据成员的函数都应该声明为const 类型。
4、变量(指针、数组)被创建之后应当及时把它们初始化,以防止把未被初始化的变量当成右值使用。
雨,静静的飘扬;
心,慢慢的行走;
程序人生,人生迈进。