1. *&修饰符位置: 应当紧靠变量名

Example:

  1. char *name;
  2. int *x, y; //此处一不会被认为是int指针
  3. //错误示范:
  4. int* x, y; //容易误认为x和y都是int指针

2. 不可将浮点变量用“==”或“!=”与任何数字比较

无论是 float 还是 double 类型的变量,都有精度限制。所以一定要避免将浮点变量用“==”或“!=”与数字比较,应该设法转化成“>=”或“<=”形式。
假设浮点变量的名字为 x,应当将

  1. if (x == 0.0) // 隐含错误的比较

转化为

  1. if ((x>=-**EPSINON**) && (x<=**EPSINON**))

其中 EPSINON 是允许的误差(即精度)。

3. 在多重循环中,应当将最长的循环放在最内层

在多重循环中,如果有可能,应当将最长的循环放在最内层,最短的循环放在最外层,以减少 CPU 跨切循环层的次数。

  1. for (col=0; col<5; col++ ) //外循环5次
  2. {
  3. for (row=0; row<100; row++) //内循环100次
  4. {
  5. sum = sum + a[row][col];
  6. }
  7. }

4. 如果函数返回值是一个对象,要考虑 return 语句的效率

正确示例:

return String(s1 + s2);

错误示例:

String temp(s1 + s2); return temp;

上述代码将发生三件事。首先, temp 对象被创建,同时完成初始化;然后拷贝构造函数把 temp 拷贝到保存返回值的外部存储单元中;最后, temp 在函数结束时被销毁(调用析构函数)。
然而“创建一个临时对象并返回它”的过程是不同的,编译器直接把临时对象创建并初始化在外部存储单元中,省去了拷贝和析构的化费,提高了效率。

5. 内存三种分配方式

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

    6. 野指针

    指针 p 被 free 以后其地址仍然不变(非 NULL),只是该地址对应的内存是垃圾, p 成了“野指针”(指向“垃圾”内存的指针)。如果此时不把 p 设置为 NULL,会让人误以为 p 是个合法的指针。 当使用if(p != NULL)时,if语句并没有作用:

    1. char *p = (char *) malloc(100);
    2. strcpy(p, hello”);
    3. free(p); // p 所指的内存被释放,但是 p 所指的地址仍然不变
    4. if(p != NULL) // 没有起到防错作用
    5. {
    6. strcpy(p, world”); // 出错
    7. }

    7. 慎用内联函数

    内联函数可以直接替换函数调用,省去了函数调用的开销,提供函数的执行效率,但并不能乱用! 内联是以代码膨胀(复制)为代价,如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少;每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。
    以下情况不宜使用内联:

  • 如果函数体内的代码比较长,使用内联将导致内存消耗代价较高。

  • 如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大

    8. 构造函数和析构函数

    对于任意一个类 A, C++编译器将自动为 A 产生四个缺省的函数,如 :

  • A(void); // 缺省的无参数构造函数

  • A(const A &a); // 缺省的拷贝构造函数
  • ~A(void); // 缺省的析构函数
  • A & operate =(const A &a); // 缺省的赋值函数

“缺省的拷贝构造函数”和“缺省的赋值函数”均采用“浅拷贝”而非“深拷贝”的方式来实现,倘若类中含有指针变量,这两个函数注定将出错。所以对于包含指针变量的类,一定要实现拷贝构造函数和赋值函数。
如果我们实在不想编写拷贝构造函数和赋值函数,又不允许别人使用编译器生成的缺省函数,怎么办? 偷懒的办法是:将拷贝构造函数和赋值函数声明为私有函数

  1. class A
  2. {
  3. private:
  4. A(const A &a); // 私有的拷贝构造函数
  5. A & operate =(const A &a); // 私有的赋值函数
  6. };

9. 非内部数据类型的成员对象使用初始化列表初始化

类的数据成员的初始化可以采用初始化表或函数体内赋值两种方式,这两种方式的 效率不完全相同。非内部数据类型的成员对象应当采用第一种方式初始化,以获取更高的效率。内部数据类型的数据成员而言,两种初始化方式的效率几乎没有区别。

  1. class A
  2. {
  3. A(void); // 无参数构造函数
  4. A(const A &other); // 拷贝构造函数
  5. A & operate =( const A &other); // 赋值函数
  6. };
  7. class B
  8. {
  9. public:
  10. B(const A &a); // B 的构造函数
  11. private:
  12. A m_a; // 成员对象
  13. };
  14. //方式1:
  15. B::B(const A &a) : m_a(a)
  16. {
  17. }
  18. //方式2:类 B 的构造函数在函数体内用赋值的方式将成员对象 m_a 初始化。
  19. //实际上 B 的构造函数干了两件事:先暗地里创建 m_a对象(调用了 A 的无参数构造函数),再调用类 A 的赋值函数,将参数 a 赋给 m_a。
  20. B::B(const A &a)
  21. {
  22. m_a = a;
  23. }

10. 若在逻辑上 B 是 A 的“一种”,并且 A 的所有功能和属性对 B 而言都有意义,则允许 B 继承 A 的功能和属性。

11. 若在逻辑上 A 是 B 的“一部分”(a part of),则不允许 B 从 A 派生,而是要用 A 和其它东西组合出 B。

12. 对于非内部数据类型的输入参数,应该将“值传递”的方式改为“const 引用传递”,目的是提高效率。例如将 void Func(A a) 改为 void Func(const A &a)。

13. const成员函数

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

  1. class Stack
  2. {
  3. public:
  4. void Push(int elem);
  5. int Pop(void);
  6. int GetCount(void) const; // const 成员函数
  7. private:
  8. int m_num;
  9. int m_data[100];
  10. };