《Effective C++:改善程序与设计的55个具体做法》阅读笔记 1——让自己习惯C++

如果哪一节看不懂,就去看链接相应的博客,这里面总结的比较好。

1.让自己习惯C++

本章就是最基本的一些东西。

Item 1: 将 C++ 视为 federation of languages(语言联合体)

C++可以视为四种语言联合体,这四个部分是:类c语言、面向对象、模板、stl

面向对象和模板中一般使用传引用
c语言和stl中一般使用传值
可这是为什么呢?

Item 2: 用consts, enums和inlines取代#defines

#defines定义的常量会在预处理阶段被替换,这样如果在编译时发生错误,只会提示因为宏所定义的值导致了错误,我们并不一定知道导致错误的值是通过宏定义的。解决方法:

1.consts取代#defines:

  • constant(常量)比使用#define能产生更小的代码。因为将宏替换成值,每一处宏使用的位置都是一个值的拷贝。
  • 字符串常量的表达两种表达:
const char * const authorName = "Scott Meyers";
const std::string authorName("Scott Meyers");    // 此种表达更可取
  • 类属常量(static const):只要你不去取类属常量的地址 ,你可以只声明并使用它,而不提供它的定义。
    关于类的声明和定义,可参考:“类的声明和定义、extern

2.enum取代#defines:

  • 如果你不希望人们得到你的整型族常量的 pointer或引用,enum(枚举)就是强制约束这一点的好方法。
  • enum取代#defines不会产生不必要的内存分配,但是consts取代#defines会产生不必要的内存分配

3.inline function(内联函数)的 template(模板)代替宏定义的函数:

  • 由于宏只是进行简单地替换,所以使用宏来定义函数有数不清的缺点,可能出现意想不到的错误,如:
#define add(x, y) x + y
5 * add(2, 3) // 使用5 * add(2, 3)时,被替换为5 * 2 + 3,值为13,而非5 * (2 + 3),值为25。
  • inline function(内联函数)的 template(模板)代替宏定义的函数:
#define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b)) 
// 替换如下:
template<typename T> inline void callWithMax(const T& a, const T& b) 
{ 
    f(a > b ? a : b); 
}

如果能用上面的方法替代宏,就尽量少用宏。

Item 3: 只要可能就用const

只要是不需要修改的变量都设为const,包括函数的形参、函数的返回值等。

1.小知识:

  • 如果const出现在星号左边,则指针指向的内容为常量;如果const出现在星号右 ,则指针自身为常量。以下两种写法是等价的,因为const都在星号左边:
const Widget *pw;
Widget const *pw;
  • 迭代器本身为const,迭代器指向内容为const:
const std::vector::iterator iter = vec.begin();  // 迭代器本身不能改变,迭代器指向东西可以改变
std::vector::const_iterator cIter = vec.begin(); // 迭代器本身可以改变,迭代器指向东西不能改变

2.成员函数

  • 提升一个 C++ 程序的性能的基本方法:函数写成const函数,并以传引用的方式给const函数传递参数和返回值。【我没理解错吧】

  • 添加了const成员函数和非const成员函数之间是重载关系,而不是重写。如char& operator[] (std::size_t position)const char& operator[] (std::size_t position) const。如果类中同时存在const和非const的同名同参数成员函数,const对象调用的是const成员函数,非const对象调用的是非const成员函数。

  • operator []的返回类型导致的错误:
    重载operator []的返回类型为const char&,不能使用赋值操作th[0]='x';,因为对常量进行赋值操作,当然会出错。
    重载operator []的返回类型为char而不是引用,不能使用赋值操作th[0]='x';,因为tb[0]返回的只是一个临时对象,对临时对象进行赋值操作,当然会出错。

  • const成员函数,分为两种观点:
    bitwise constness:const成员函数不可以更改对象内任何non-static成员变量。
    logical constness:const成员函数可以修改它所处理的对象内的某些bits,但必须在客户端侦测不出的情况下才可以这么做。使用关键字mutable修饰的数据成员,可以在const成员函数中被改变。

  • 非const成员函数和const成员函数的代码重复问题:
    非const成员函数和const成员函数实现的功能几乎完全一致,也就是说两者的代码有很高的重复性。
    解决方法:char& operator[] (std::size_t position)中调用const char& operator[] (std::size_t position) const,具体如下:在char& operator[] (std::size_t position)中利用static_cast将this强制转换为const类型,利用const类型的this就可以调用函数const char& operator[] (std::size_t position) const,最后利用const_cast将函数operator[]返回的const char&类型数据的const去除掉。
    【注】const char& operator[] (std::size_t position) const中调用char& operator[] (std::size_t position),这是不合理的,因为const函数中不能改变对象的逻辑状态。

Item 4:确定对象被使用前已先被初始化

  • 保证初始化与不用保证初始化:
    C part of C++ (见条款1):初始化可能招致运行期成本,那么就不保证发生初始化,如array不保证初始化。
    non-C parts of C++:一般要保证初始化,如vector要保证初始化

成员初始化列表

  • 成员变量的初始化和赋值:
    自定义类:在进入构造函数内部之前,各个成员变量会调用自己默认构造函数进行初始化。如果在构造函数内部又进行了赋值操作,那么就会导致初始化后立即进行赋值操作,这就将初始化操作给浪费了。使用成员初始化列表,就可以避免这样的浪费。同理,std::string encrypted(password);优于std::string encrypted; encrypted=password;

内置类型:不保证一定在进入构造函数之前获取初值,初始化和赋值的成本相同。即使是内置类型,使用成员初始化列表也是个好习惯,比如成员变量是const或references,它们就一定需要被初始化,而不是赋值。

综上,不管是自定义类还是内置类型,使用成员初始化列表进行初始化都是一个好习惯。所以请将所有的成员变量都放在成员初始化列表上。

【当有很多构造函数的时候,为了降低代码的重复,我们还是会使用赋值操作代替成员初始化列表】:
如果有很多个构造函数,那么每个构造函数中将会有很多重复的代码,如相同的初始化列表就要写很多次,那么一般将成员变量的赋值操作写在一个private函数中,然后各个构造函数调用这个private函数,即用赋值操作来替代初始化列表。(当然还是尽量使用成员初始化列表)

【注】成员变量的初始化次序是由声明次序决定的,而不是由初始化列表的次序决定的。

跨编译单元之初始化次序问题

问题:如果某编译单元内的某个non-local static 对象的初始化动作使用了另一编译单元内的某个non-local static 对象,它所用到的这个对象可能尚未被初始化,因为C++对“定义于不同编译单元内的non-local static对象”的初始化次序并无明确定义。【就是说a.cpp使用到了b.cpp中的一个变量,但是g++ a.cpp b.cpp并不能保证b.cpp中的变量先被初始化,C++并为对初始化顺序有明确规定】

  • non-local static对象:函数内的static对象称为local static对象,其他地方定义的static称为non-local static对象( 也就是说该对象是global或位于namespace作用域内,抑或在class内或file 作用域内被声明为static)。
    static相关内容,请参考链接
  • 编译单元(translation unit):一个源文件为一个编译单元

【为什么不同源文件(不同编译单元)的static类型数据可以互相使用?】
答:链接中提到的——static修饰的全局变量是不可以在其他文件中使用的,但是从《Effective C++》的内容可以看出,这里的non-local static并不包括static修饰的全局变量,它包括的是全局非static变量(global或位于namespace作用域内)和class内作用域内被声明为static的变量,应该不包括file 作用域内被声明为static的变量(原文中应该写错了)

为免除“跨编译单元之初始化次序”问题,请以reference-returning函数返回的local static对象替换non-local static对象。具体说明可以参考原文,或者链接中的“4. 不同编译单元内定义的non-local static 对象”。

reference-returning函数如果被经常调用可设置为inline。
在多线程中,静态变量的初始化次序可能会有问题。最好在多线程开启前的单线程阶段,手工调用所有reference-returning函数进行初始化。

posted @ 2022-10-02 15:41  好人~  阅读(51)  评论(0编辑  收藏  举报