高质量C++摘录

早上10点爬起来看~

---------------------------------------

不要把程序中的复合表达式与“真正的数学表达式”混淆。
例如:
if (a < b < c) // a < b < c 是数学表达式而不是程序表达式
并不表示
if ((a<b) && (b<c))
而是成了令人费解的
if ( (a<b)<c )


浮点变量与零值比较
【规则4-3-3】不可将浮点变量用“==”或“!=”与任何数字比较。
千万要留意,无论是float 还是double 类型的变量,都有精度限制。所以一定要
避免将浮点变量用“==”或“!=”与数字比较,应该设法转化成“>=”或“<=”形式。
假设浮点变量的名字为x,应当将
if (x == 0.0) // 隐含错误的比较转化为
if ((x>=-EPSINON) && (x<=EPSINON))
其中EPSINON 是允许的误差(即精度)。


指针变量与零值比较
?? 【规则4-3-4】应当将指针变量用“==”或“!=”与NULL 比较。
指针变量的零值是“空”(记为NULL)。尽管NULL 的值与0 相同,但是两者意义不
同。假设指针变量的名字为p,它与零值比较的标准if 语句如下:
if (p == NULL) // p 与NULL 显式比较,强调p 是指针变量
if (p != NULL)
不要写成
if (p == 0) // 容易让人误解p 是整型变量
if (p != 0)
或者
if (p) // 容易让人误解p 是布尔变量
if (!p)


内存分配方式有三种:
(1) 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的
整个运行期间都存在。例如全局变量,static 变量。
(2) 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函
数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集
中,效率很高,但是分配的内存容量有限。
(3) 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc 或new 申请任意
多少的内存,程序员自己负责在何时用free 或delete 释放内存。动态内存的生存
期由我们决定,使用非常灵活,但问题也最多。


【规则7-2-1】用malloc 或new 申请内存之后,应该立即检查指针值是否为NULL。
防止使用指针值为NULL 的内存。
【规则7-2-2】不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右
值使用。
【规则7-2-3】避免数组或指针的下标越界,特别要当心发生“多1”或者“少1”
操作。
【规则7-2-4】动态内存的申请与释放必须配对,防止内存泄漏。
【规则7-2-5】用free 或delete 释放了内存之后,立即将指针设置为NULL,防止
产生“野指针”。


char a[] = "hello world";
char *p = a;
cout<< sizeof(a) << endl; // 12 字节
cout<< sizeof(p) << endl; // 4 字节

void Func(char a[100])
{
cout<< sizeof(a) << endl; // 4 字节而不是100 字节
}


注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。示例
7-3-3(b)中,不论数组a 的容量是多少,sizeof(a)始终等于sizeof(char *)。

不要用return 语句返回指向“栈内存”的指针
char *GetMemory3(int num)
{
char *p = (char *)malloc(sizeof(char) * num);
return p;  //堆内存指针
}
void Test3(void)
{
char *str = NULL;
str = GetMemory3(100);
strcpy(str, "hello");
cout<< str << endl;
free(str);
}


我们发现指针有一些“似是而非”的特征:
(1)指针消亡了,并不表示它所指的内存会被自动释放。
(2)内存被释放了,并不表示指针会消亡或者成了NULL 指针。


别看free 和delete 的名字恶狠狠的(尤其是delete),它们只是把指针所指的内存给
释放掉,但并没有把指针本身干掉。
用调试器跟踪示例7-5,发现指针p 被free 以后其地址仍然不变(非NULL),只是
该地址对应的内存是垃圾,p 成了“野指针”。如果此时不把p 设置为NULL,会让人误
以为p 是个合法的指针。

判断指针是否为NULL,如果是则马上用exit(1)终止整个程序的运行。


(1)越是怕指针,就越要使用指针。不会正确使用指针,肯定算不上是合格的程序员。
(2)必须养成“使用调试器逐步跟踪程序”的习惯,只有这样才能发现问题的本质。


如果C++程序要调用已经被编译后的C 函数,该怎么办?
假设某个C 函数的声明如下:
void foo(int x, int y);
该函数被C 编译器编译后在库中的名字为_foo,而C++编译器则会产生像_foo_int_int
之类的名字用来支持函数重载和类型安全连接。由于编译后的名字不同,C++程序不能
直接调用C 函数。C++提供了一个C 连接交换指定符号extern“C”来解决这个问题。
例如:
extern “C”
{
void foo(int x, int y);
 // 其它函数
}
或者写成
extern “C”
{
#include “myheader.h”
 // 其它C 头文件
}


本来仅仅区别重载与覆盖并不算困难,但是C++的隐藏规则使问题复杂性陡然增加。
这里“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual
关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual
关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。


void Foo(int x=0, int y=0); // 正确,缺省值出现在函数的声明中
void Foo(int x=0, int y=0) // 错误,缺省值出现在函数的定义体中
{

}
为什么会这样?我想是有两个原因:一是函数的实现(定义)本来就与参数是否有
缺省值无关,所以没有必要让缺省值出现在函数的定义体中。二是参数的缺省值可能会
改动,显然修改函数的声明比修改函数的定义要方便。

运算符与普通函数在调用时的不同之处是:对于普通函数,参数出现在圆括号内;
而对于运算符,参数出现在其左、右侧。

如果运算符被重载为全局函数,那么只有一个参数的运算符叫做一元运算符,有两
个参数的运算符叫做二元运算符。
如果运算符被重载为类的成员函数,那么一元运算符没有参数,二元运算符只有一
个右侧参数,因为对象自己成了左侧参数。


不能被重载的运算符
在C++运算符集合中,有一些运算符是不允许被重载的。这种限制是出于安全方面
的考虑,可防止错误和混乱。
(1)不能改变C++内部数据类型(如int,float 等)的运算符。
(2)不能重载‘.’,因为‘.’在类中对任何成员都有意义,已经成为标准用法。
(3)不能重载目前C++运算符集合中没有的符号,如#,@,$等。原因有两点,一是难以
理解,二是难以确定优先级。
(4)对已经存在的运算符进行重载时,不能改变优先级规则,否则将引起混乱。


如下风格的函数Foo 则成为内联函数:
void Foo(int x, int y);
inline void Foo(int x, int y) // inline 与函数定义体放在一起
{

}
所以说,inline 是一种“用于实现的关键字”,而不是一种“用于声明的关键字”。
一般地,用户可以阅读函数的声明,但是看不到函数的定义。尽管在大多数教科书中内
联函数的声明、定义体前面都加了inline 关键字,但我认为inline 不应该出现在函数
的声明中。这个细节虽然不会影响函数的功能,但是体现了高质量C++/C 程序设计风格
的一个基本原则:声明与定义不可混为一谈,用户没有必要、也不应该知道函数是否需
要内联。


“缺省的拷贝构造函数”和“缺省的赋值函数”均采用“位拷贝”而非“值拷贝”
的方式来实现,倘若类中含有指针变量,这两个函数注定将出错。

所以更加严格的继承规则应当是:若在逻辑上B 是A 的“一种”,并且A 的所有功
能和属性对B 而言都有意义,则允许B 继承A 的功能和属性。

为了提高效率,可以将函数声明改为void Func(A &a),因为“引用传递”仅借用
一下参数的别名而已,不需要产生临时对象。但是函数void Func(A &a) 存在一个缺点:
“引用传递”有可能改变参数a,这是我们不期望的。解决这个问题很容易,加const
修饰即可,因此函数最终成为void Func(const A &a)。
以此类推,是否应将void Func(int x) 改写为void Func(const int &x),以便
提高效率?完全没有必要,因为内部数据类型的参数不存在构造、析构的过程,而复制
也非常快,“值传递”和“引用传递”的效率几乎相当。


const 成员函数
任何不会修改数据成员的函数都应该声明为const 类型。如果在编写const 成员函
数时,不慎修改了数据成员,或者调用了其它非const 成员函数,编译器将指出错误,
这无疑会提高程序的健壮性。


【规则11-2-1】不要一味地追求程序的效率,应当在满足正确性、可靠性、健壮性、
可读性等质量因素的前提下,设法提高程序的效率。
【规则11-2-2】以提高程序的全局效率为主,提高局部效率为辅。
【规则11-2-3】在优化程序的效率时,应当先找出限制效率的“瓶颈”,不要在无关
紧要之处优化。
【规则11-2-4】先优化数据结构和算法,再优化执行代码。
【规则11-2-5】有时候时间效率和空间效率可能对立,此时应当分析那个更重要,
作出适当的折衷。例如多花费一些内存来提高性能。
【规则11-2-6】不要追求紧凑的代码,因为紧凑的代码并不能产生高效的机器码。

---------------------------------------------

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值