编写高质量代码_改善C++程序的150个建议 读书笔记

这几天看了下这本书《编写高质量代码_改善C++程序的150个建议》,觉的蛮有收获的,再次记录下自己以前不清晰的知识点,以供学习。

编写符合标准的main函数

C语言标准规定了main函数的两种标准形式:


int main(void)

int main(int argc, char* argv[])

C++03标准中给出了两种main函数的定义方式


int main()

int main(int argc, char *argv[])

上述标准都要求main函数返回一个整型数值

extern C的作用

extern C的作用是当程序被C++编译器编译时,让后续的链接器以C方式来寻找函数,方便C++程序调用C程序。

注释风格

C++风格注释形如:// ....,推荐使用这样的注释。但是,头文件说明和函数默认参数的注释,还是用C风格(/**/)的较好。

不要写与编译器依赖紧密的代码

例如:printf("a %d %d", p(), q()),p和 q函数的执行前后顺序与编译器实现相关,应当避免此类代码。类似的还有 c = p() * q() * r()

尽量用const,enum,inline代替#define

inline关键字用在函数调用展开,在类声明中定义并且实现的函数自动为内联函数。如果需要将其他函数定义为内联函数,则需要在函数实现头声明此关键字,才让编译器尝试去内联,至于具体是否内联,还要求函数体满足一定条件才行,总体原则是短小精悍。

在使用define的场合,注意用()来保护宏函数。例如:#define MAX(a, b) ((a) > (b) ? (a) : (b))

struct在C和C++下的异同

C语言的struct不允许定义函数程序,而C++语言下的struct可以。

成员初始化

const成员变量只能通过"成员初始化列表"来进行初始化。在C++中允许声明一个函数但不无函数体实现。

类中成员变量如果是自定义类型,那么它的构造是先于类本身的构造函数,其构造顺序与在类中定义的顺序一致。

RAII手法:将资源的申请和释放操作包裹到类中的构造和析构函数中,隐式地保护资源。

所有数据成员一律为private类型。如果派生类需要用到,那么在用到的时候再将其改为protect类型,否则,一律声明为private类型,对外隐藏。
在具体声明时,可以按类型来多段声明,比如私有控件,来一个private,私有数据来另外一个private。

派生类初始化

在继承体系中,对象的构造是从最根处开始的。

虚函数相关

当类中至少包含一个虚函数时,才需要将其析构函数设置为虚函数。
在派生类被正确地构造出来之前,调用派生类的虚函数是没有意义的。
不要在构造/析构函数中调用虚函数。

虚函数需要配合继承机制,它生效于运行期,属于OOP范畴,适用于接口相同,逻辑不同的代码应用于不同的场合,实现了接口与实现的分离。

在C++中,还有一种成为pimpl idiom的手法,来更加彻底的分离接口和实现,大体实习思路是将对象中全部的数据成员封装在另一个实现类中,在本类中通过类成员指针或者是智能指针来引用。对外暴露的接口在内部都转发给实现类处理。

模板在编译器生效,它的本意是让不同类型的数据作用在相同的代码逻辑,属于GP范畴,体现为算法的普适性。

基类中虚函数的保护级别:

  • protect:标明派生类一定要重写该虚函数
  • private:标明派生类可以修改基类虚函数,如果无特殊要求的话,可以复用基类虚函数
  • public:禁止将虚函数声明为public权限

接口设计

设计目标:能覆盖完整功能并且达到接口最小化

以行为为中心的类设计,对外的public函数放在前面,需要继承的protect虚函数紧随其后,再后面是private的虚函数、普通函数以及成员变量。

语法的背后含义是语义,接口设计要有明确的语义,不可模棱两可、职责不清。

拷贝相关

如果类中无动态分配的资源类型,那么默认的浅拷贝可以满足要求。一旦有指针类型,那么,就必定有动态申请的资源,在拷贝时,需要重写赋值运算符,在目标类中重新申请空间,并且将原动态资源内容原封不动的拷贝过来。

如果重写派生类的赋值操作符,那么需要调用基类的赋值操作符,完整的进行赋值,而不是只赋值派生类数据,不赋值基类数据。

一般,只有在类宏成员变量动态申请了资源,才需要重写赋值操作符和拷贝构造函数。考虑到A = A这种情况,还需要重写==操作符,一般来说,重写的==,那么!=也顺带写上,保持相关操作的一致性。


A& A::operator=(const A& rhs)

{
    if(this == &rhs)
        return *this;
        
    parent::operator=(rhs)
    
    // 派生类成员赋值
    
    return *this;
    
}

重载相关

overloading:具有相同函数名,但是不同参数形式的函数集合

overriding:在类中,派生类重写基类提供的虚函数,建议使用 override关键字来声明,在派生类中,不要用virtual来标明此为虚函数。

Hiding:派生类中重写和基类一样签名的非虚函数

模板相关

模板的声明和定义需要放在同一个头文件中,如果分离声明和实现,则外部在使用时,需要包含实现的cpp文件,这违反惯用法。

.inl文件可以将模板头文件和复杂模板定义分离,一般将其在头文件尾部进行包含。

异常相关

如底层发生异常,则需要逐级上报,直到有能力处理此异常的层级来处理。如果程序都没处理,则会被C++系统捕获并终止程序运行。异常可以将发生错误和处理错误分离。

一般以传值来抛出异常,以 const 引用来捕获异常,不涉及到异常对象的清理工作,无对象切割问题,如本层级处理后还需要继续抛出异常,可调用throw来。

智能指针

优先使用shared_ptr,它内部工作原理是引用计数,线程安全,支持扩展,推荐使用。

不要在stl容器中存储auto_ptr对象,因为容器元素是允许拷贝构造和赋值的,而auto_ptr是不能满足这一条件的。

STL相关

多用算法调用,少用手写循环。

杂项优化

不要返回局部变量的引用。

双重循环时,短循环放在外面,长循环放在里面。

优化常规框架:profiling --> 算法 --> 数据结构 --> 实现细节。

代码的主要功能是供给别人阅读,其次才是让编译器编译,让计算机执行,除了修改代码外,还有很多不用修改代码就能带来的优化,作为一个有追求的程序员,多了解代码之外的优化和设计方法是很有必要的。

猜你喜欢

转载自www.cnblogs.com/cherishui/p/9853282.html