《高质量C++/C编程指南(林锐)》学习总结

1. 不可以将布尔变量直接和TRUE、FALSE、1、0进行比较。应该用if(flag)或者if(!flag)。

    整型变量直接用”==“或者”!=“与0进行比较。

浮点变量不可以直接用”==“或者“!=”与数字比较。

指针变量用“==”和“!=”与NULL直接进行比较。

2. 可以使用诸如if(NULL == P)来避免if(NULL=P)类错误。

3. 在有循环嵌套的时候,尽量将长循环放在最内层,这样可以提高效率。

4. 可以使用#define来定义宏常量,用const来定义const常量。const常量有数据类型,宏常量没有数据类型。编译器可以对const常量进行类型安全检查。在C++中,const常量 完全取代宏常量。

5. C语言中,函数参数传递有值传递和指针传递两种方式;C++语言中,函数参数传递有值传递、指针传递以及引用传递。如果函数无参数,使用void进行填充。

6. 使用宏断言assert对程序进行检查。assert仅在Debug版本起作用。Debug版本主要用于内部调试,Release版本主要发行给用户使用。

7. return语句不可以返回“栈内存”的“指针”或者“引用”,因为该部分内存在函数体结束时会被自动销毁。

8. 内存分配方式,如全局变量、static变量等在程序编译时就已经分配好的内存在静态存储区域。如函数内局部变量等存储单元在栈上分配内存。如用malloc或new申请的动态内 存从堆上分配。

9. 内存容量计算。使用sizeof计算数组容量是可以的,若使用sizeof识别指针变量所指向的容量大小,只能得到一个指针变量的字节数,而不是其所指的内存容量。另外,当数组 作为函数的参数进行传递时,该数组自动退化为同类型的指针。

10. 当指针p被free掉之后,其地址仍然不变,但是该地址对应的内存是垃圾,p变成“野指针”,此时需要把p设置为NULL。

11. inline是一种“用于实现的关键字”,而不是一种“用于声明的关键字”。定义在类声明中的成员函数将自动地成为内联函数。内联可以提高执行效率,但是以代码膨胀为代价,要 权衡执行效率和内存空间。

12. C++编译器编译任意一个类时,会自动产生四个缺省函数:无参数构造函数,拷贝构造函数,析构函数,赋值函数。

const和define的比较
在C++中const 和#define 都能够定义常量。但是前者与后者相比,有一些优点。
1.const定义的常量有数据类型,而#define定义的常量没有数据类型。编译器会对前者进行安全类型检查,对后者编译时只是做简单的字符替换替换。
没有安全检查,而且在替换的时候经常出现意想不到的错误,唯一能做的是不要吝啬()。
2.有些集成化调试工具会对const常量进行调试,但是不能对宏常量进行调试。
//不能再类声明中初始化const数据成员,因为类的对象为被创建时,编译器不知道SIZE是多少。
class A
{
           const int SIZE = 100;
           int array[SIZE];
};
 
//const数据成员的初始化只能在类构造函数的初始化列表中进行。
class A
{
public:
           //构造函数
          A( int size )
                   :SIZE( size)
          {}
 
public:
          A( int size);
           const int SIZE;
};
 
int main()
{
           A a(100);
           A b(200);
          system( "pause");
           return 0;
}


//在整个类中建立恒定的常量,可以用枚举类型
class A
{
           enum{SIZE1=100,SIZE2=200};
 
           int array1[SIZE1];
           int array2[SIZE2];
};

对于赋值函数,应当使用“引用传递”的方式返回String对象,如果用值传递的话,return *this 语句会把*this 拷贝到返回值的外部存储单元中,增加了不必要的开销,降低了赋值函数的效率。

对于相加函数,应当使用值传递方式返回String对象,如果改为“引用传递”,那么返回值是一个局部变量的引用,局部变量temp在出了函数作用域时会被自动销毁,导致引用无效。

如果函数返回值是一个对象,则应考虑return 语句的效率
return  String( s1 + s2) ; //这是临时对象的语法,表示传进一个临时对象并返回它。

String temp(s1+s2)
return temp;
首先,temp对象被创建,同时完成初始化,然后拷贝构造函数把temp拷贝到返回外部存储单元中,最后temp在函数结束时被销毁(调用析构函数),它与上面是不同的,上面的过程中,编译器直接调用构造函数把临时对象创建并初始化在外部存储单元中,省去了拷贝构造和析勾函数的过程,提高了效率。

引用和指针的比较:
引用时C++的产物,他相当于个者个东西起了个别名,实质上还是这个东西。
引用的规则如下:
1.引用被创建的同时必须初始化(指针则可以再任何时候都能被初始化)。
2.不能有NULL引用,必须引用合理的存储单元。
3.一旦引用被初始化,就不能改变引用关系(指针可以改变引用的关系)。

#include<iostream>
using namespace std;
int main()
{
           char a[] = "hello" ;
          a[0] = 'X';
          cout << a << endl;
           char *p = "hello" ;   //指针p指向常量字符串
          p[0] = 'X';               //运行时会崩溃
          cout << p << endl;
          system( "pause");
           return 0;
}

头文件的结构:

【建议】 头文件中只存放“声明”而不存放“定义”
在 C++ 语法中,类的成员函数可以在声明的同时被定义,并且自动成为内联函数。这虽然会带来书写上的方便,但却造成了风格不一致,弊大于利。建议将成员函数的定义与声明分开,不论该函数体有多么小。

【建议】 不提倡使用全局变量,尽量不要在头文件中出现象 extern int value 这类声明。  

空行:

【规则】 在每个类声明之后、每个函数定义结束之后都要加空行

【规则】 在一个函数体内,逻揖上密切相关的语句之间不加空行,其它地方应加空行分隔。

代码行:

【规则】 一行代码只做一件事情,如只定义一个变量,或只写一条语句。这样
的代码容易阅读,并且方便于写注释。

【规则】 if、 for、 while、 do 等语句自占一行,执行语句不得紧跟其后。不论执行语句有多少都要加{}。这样可以防止书写失误。

【规则】 应当将修饰符 * 和 & 紧靠变量名 如:int  *x, y; // 此处 y 不会被误解为指针。

注释:

【规则】 边写代码边注释,修改代码同时修改相应的注释,以保证注释与代码的一致性。不再有用的注释要删除。

【规则】 注释应当准确、易懂,防止注释有二义性。错误的注释不但无益反而有害。

命名规则:

【规则】 静态变量加前缀 s_(表示 static) 、

布尔变量与零值比较

【规则】 不可将布尔变量直接与 TRUE、 FALSE 或者 1、 0 进行比较。

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

循环语句的效率

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

                                                    

【建议】 如果循环体内存在逻辑判断,并且循环次数很大,宜将逻辑判断移到 循环体的外面。示例 4-4(c)的程序比示例 4-4(d)多执行了 N-1 次逻辑判断。并且由于前者老要进行逻辑判断,打断了循环“流水线”作业,使得编译器不能对循环进行优化处理,降低了效率。如果 N 非常大,最好采用示例 4-4(d)的写法,可以提高效率。如果 N 非常小,两者效率差别并不明显,采用示例 4-4(c)的写法比较好,因为程序更加简洁。

【规则】 每个 case 语句的结尾不要忘了加 break,否则将导致多个分支重叠(除非有意使多个分支重叠)。

【规则】不要忘记最后那个 default 分支。 即使程序真的不需要 default 处理,也应该保留语句 default : break; 这样做并非多此一举,而是为了防止别人误以为你忘了 default 处理。

有关goto 语句

很多人建议废除 C++/C 的 goto 语句,以绝后患。但实事求是地说,错误是程序员自
己造成的,不是 goto 的过错。goto 语句至少有一处可显神通,它能从多重循环体中咻地一下子跳到外面,用不着写很多次的 break 语句;

就象楼房着火了,来不及从楼梯一级一级往下走,可从窗口跳出火坑。所以我们主
张少用、慎用 goto 语句,而不是禁用。

const 与 #define 的比较

【规则】 尽量使用含义直观的常量来表示那些将在程序中多次出现的数字或字符串。

#define

MAX 100

/* C 语言的宏常量 */

const int

MAX = 100;

// C++ 语言的 const 常量

const float

PI = 3.14159; // C++ 语言的 const 常量

【规则】 在 C++ 程序中只使用 const 常量而不使用宏常量,即 const 常量完全取代宏常量(const 常量有数据类型)。

【规则】 需要对外公开的常量放在头文件中,不需要对外公开的常量放在定义文件的头部。为便于管理,可以把不同模块的常量集中存放在一个公共的头文件中。
【规则】 如果某一常量与其它常量密切相关,应在定义中包含这种关系,而不应给出一些孤立的值。例如:
const float RADIUS = 100;
const float DIAMETER = RADIUS * 2; 

使用 const 提高函数的健壮性

注意:所以很多 C++程序设计书籍建议:“Use const whenever you need” const 只能修饰输入参数,如果参数作输出用,不论它是什么数据类型,也不论它采用“指针传递”还是“引用传递”,都不能加 const 修饰,否则该参数将失去输出功能。

1、如果输入参数采用“指针传递”,那么加 const 修饰可以防止意外地改动该指针,起到保护作用 。

2、如果输入参数采用“值传递”,由于函数将自动产生临时变量用于复制该参数,该输入参数本来就无需保护,所以不要加 const 修饰

3、对于非内部数据类型的参数而言,象 void Func(A a) 这样声明的函数注定效率比较低。因为函数体内将产生 A 类型的临时对象用于复制参数 a,而临时对象的构造、复制、析构过程都将消耗时间。为了提高效率,可以将函数声明改为 void Func(A &a), 因为“引用传递”仅借用一下参数的别名而已,不需要产生临时对象。但是函数 void Func(A &a) 存在一个缺点:“引用传递”有可能改变参数 a,这是我们不期望的。解决这个问题很容易,加 const修饰即可,因此函数最终成为 void Func(const A &a)。

4、以此类推,是否应将 void Func(int x) 改写为 void Func(const int &x),以便提高效率?完全没有必要,因为内部数据类型的参数不存在构造、析构的过程,而复制也非常快, “值传递”和“引用传递”的效率几乎相当。

5、任何不会修改数据成员的函数都应该声明为 const 类型 。

注意:不能在类声明中初始化 const 数据成员。 const 数据成员的初始化只能在类构造函数的初始化表中进行

函数参数的规则

【规则】 参数的书写要完整,不要贪图省事只写参数的类型而省略参数名字。

【规则】 如果参数是指针,且仅作输入用,则应在类型前加 const,以防止该指针在函数体内被意外修改。

例如:void StringCopy(char *strDestination,const char *strSource);

【规则】 如果输入参数以值传递的方式传递对象,则宜改用“const &”方式来传递,这样可以省去临时对象的构造和析构过程,从而提高效率。

注意:函数 getchar的原型为int getchar(void);

【建议】 如果函数的返回值是一个对象,有些场合用“引用传递”替换“值传递”可以提高效率。

【规则】 在函数体的“入口处”,对参数的有效性进行检查。很多程序错误是由非法参数引起的,我们应该充分理解并正确使用“断言”(assert)来防止此类错误,assert 可以帮助我们找到发生错误的原因。

【规则】 在函数体的“出口处”,对 return 语句的正确性和效率进行检查。 return 语句不可返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁。例如
char * Func(void)
{
char str[] = “hello world”; // str 的内存位于栈上

return str; // 将导致错误
}

【建议 6-4-3】 尽量避免函数带有“记忆”功能。 建议尽量少用 static 局部变量,除非必需。

引用:

一旦引用被初始化,就不能改变引用的关系(指针则可以随时改变所指的对象)。
以下示例程序中,k 被初始化为 i 的引用。语句 k = j 并不能将 k 修改成为 j 的引
用,只是把 k 的值改变成为 6。由于 k 是 i 的引用,所以 i 的值也变成了 6。
int i = 5;
int j = 6;
int &k = i;
k = j; // k 和 i 的值都变成了 6;

内存管理:

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

计算内存容量

用运算符 sizeof 可以计算出数组的容量(字节数)。示例下图 7-3-3(a)中,sizeof(a)的值是 12(注意别忘了’\0’)。指针 p 指向 a,但是 sizeof(p)的值却是 4。这是因为sizeof(p)得到的是一个指针变量的字节数,相当于 sizeof(char*),而不是 p 所指的内存容量。C++/C 语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。

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

【规则】如果函数的参数是一个指针,不要指望用该指针去申请动态内存。如果非得要用指针参数去申请内存, 那么应该改用“指向指针的指针” 由于“指向指针的指针”这个概念不容易理解,我们可以用函数返回值来传递动态内存。这种方法更加简单,见示例 7-4-3  

用函数返回值来传递动态内存这种方法虽然好用,但是常常有人把 return 语句用错
了。这里强调不要用 return 语句返回指向“栈内存”的指针,因为该内存在函数结束时
自动消亡,见示例 7-4-4。

注意:指针变量在创建的同时应当被初始化,要么将指针设置为 NULL,要么让它指向合法的内存。例如
char *p = NULL;
char *str = (char *) malloc(100)

malloc/free 的使用要点

函数 malloc 的原型如下:
void * malloc(size_t size);
例如:用 malloc 申请一块长度为 length 的整数类型的内存,程序如下:
int *p = (int *) malloc(sizeof(int) * length);

我们应当把注意力集中在两个要素上: “类型转换”和“sizeof”。malloc 返回值的类型是 void *,所以在调用 malloc 时要显式地进行类型转换,将void * 转换成所需要的指针类型。

注意:  C++时用new/delete来管理内存的,C 程序只能用 malloc/free 管理动态内存(C++语言中之所以还保留malloc/free是因为有时会调用C写的库)。

new/delete 的使用要点

运算符 new 使用起来要比函数 malloc 简单得多,例如:

int *p1 = (int *)malloc(sizeof(int) * length);

int *p2 = new int[length];

这是因为 new 内置了 sizeof、类型转换和类型安全检查功能。 

参数的缺省值

【规则】 参数缺省值只能出现在函数的声明中,而不能出现在定义体中。
例如:
void Foo(int x=0, int y=0); // 正确,缺省值出现在函数的声明中
void Foo(int x=0, int y=0) // 错误,缺省值出现在函数的定义体中
{

}

如何在派生类中实现类的基本函数

注意:如果类存在继承关系,派生类必须在其初始化表里调用基类的构造函数

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

其他一些建议

【规则】 先优化数据结构和算法,再优化执行代码 。

【建议】 当心数据类型转换发生错误。尽量使用显式的数据类型转换(让人们知道发生了什么事),避免让编译器轻悄悄地进行隐式的数据类型转换。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值