导读笔记

条款一: 视C++为一个语言联邦

  • C, C++以C为基础,基础语法结构、预处理、数据类型、数组、指针等等都来源于c;

  • 面对对象的C++,为了改进C对面向对象的支持,增加面向对象的部分,类、封装、继承、多态、virtual函数【late binding基础】

  • C++ 模版编程, 泛型编程,模版元编程

  • STL, 标准模版库, 是一个内置的标准库,实现了通用的容器,迭代器,常用算法以及函数对象等等内容;

对于大部分人来说,包括我自己接触到的C++程序员, 大部分是使用第一部分和第二部分以及少量的第四部分,通常, 对于大部分内置的类型,传值pass by value 通常比 pass by reference高效;而二对于用户自定义的类型,这时候才能显现出以const 引用来传递参数的高效性;

注意⚠️:

在STL中, 迭代器和函数对象std::function都是建立在C指针基础之上的, 所以对于STL中的迭代器和函数对象,值传递同样非常高效!!具体见条款20

条款二: 用const enum inline替换#define

  • const enum inline可以进行更多的语法检查, C++中const的内置类型,在大部分编译处理上会以常量进行替换,也就是个立即数,而不是变量,间接的介绍了内存变量;当然这只是很少的一部分;
  • 宏的替换性质, 会导致某些数据在生成的代码中存在多份拷贝,而const不会

第二条中提到了一个让我思考了一会儿的问题, 当我们定义一个某个类型专有的常量定义时, #define没法做到, 因为它没有scope的概念, 而利用const可以做到;所以有了

private:
  const type name;// 第一种声明
  static const type name;// 第二种声明

这样的写法, 但是这里有个问题, 既然是const的, 那么为什么我每一个类对象都要有一份变量name, 所以出现了所有类共有的一个对象,通过static const 来实现;在条款二中,进一步提到, 如果要禁止对这个变量取地址, 那么下面的做法更合适

private:
    enum {Name = 5 };
    //using Name

这样做1. 他是私有的,它等同于常量、不能取地址或reference、不会有多份【即不会有多份内存分配】; 条款二中称之为enum hack;

条款三: 尽可能的使用const

从左到右,const修饰的是与他最相近的类型

const char* p; // 从左往右读, 遇到第一个类型char, 所以p指针指向一个const char值;
char const *p //同上, 从左到右, 与const最近的一个类型是char;
char * const p; //第一个类型*, 表明是指针类型, 所以, const修饰的是指针本身;内容可变哦;
//那么下面的东西就好理解了
const char * const p;
const char const *p;
char const * const p;
  • STL中迭代器就是T的抽象; 所以生命一个const的迭代器就像声明一个指针为const 一样的效果,也就是说这个迭代器本身不可以背改变, 但是它指向的值是可以被改变的; `T const p就类似于const std::vector p; 如果你需要一个指向内容不被改变的迭代器,请使用const_iterator..比如说这样std::vector::const_iterator cIter...`;

让const 成员函数委托给 none-const 成员不是一件好事, 不应该这样做;

const T& f() const {
    //....
    const_cast(*this).f();// bad style
}
T& f() {
    // dothings
}

因为const成员函数的声明相当于想世界宣布,我内部不会改变对象的任何非static的东西【bitwise const】结果;你在内部却干了不该干的事情, 也许现实世界存在, 但是对于程序,恐怕会给外部造成假象而产生错误;

所以条款三种指出:当const 和 non-const成员函数右着实际性等价的时候, 使用non-const版本调用const版本来避免代码重复,不过注意⚠️,一定要记得进行static_cast和const_cast的转译,不然是个死循环的递归调用!

条款四:make sure that object are initialized before they're used

对于成员变量, 应该确保它们在构造函数被运行前被初始化;也就是在C++提供的初始化列表member initialization list;我们应该知道, 如果在构造函数中进行成员函数的赋值对!就是赋值,而非初始化!一个明显的例子就是引用成员变量,如果在构造函数对其赋值,编译器是不会让你通过的;另一个就是推荐使用STL替代C part of c++来避免未初始化的魔咒;

必须要在初始化列表中进行初始化的:

  • 基类的构造函数
  • const成员
  • 引用成员变量
  • 没有无参构造函数的对象成员

non-local static 与 local static

首先,static的变量会在main开始之前就初始化, 在main结束后才会被析构

  • non-local static指非成员函数内的static变量
  • local static就是指函数内的static变量

⚠️因为C++没有对定义在不同编译单元的non-local static变量规定初始化顺序,所以如果你有一个non-local static变量的初始化依赖另一个,比如像下面这个这样:

//in a file
static A a(b); // 'b' is a static B* b; 

//in another file
static B b;

因为C++没有non-local static的构造顺序;所以我们不能确保构造a时,b已经被构造;解决办法,通过一个local static来包裹这些static的变量,估计很多人都很熟悉,对,就是单例模式中的那个;

static A a(get_local_b());

const B* get_local_b() {
    static B b;
    return &b;
}

这样就完美的解决了这个问题, 由这个带来的好处还有,如果你从来没有调用这个函数,那么b对象永远都不会被调用,是不是太完美了!

拷贝构造 和 拷贝赋值

需要注意对于类对象而言=不一定就是发生 拷贝赋值copy assignment

class A;
class B;

B b0;  //default构造函数的调用过程
B b1;
B b2 = b1; //这里不是拷贝赋值, 而是拷贝构造!
B b3(b0) // 显式的拷贝构造调用过程;

b1 = b0; // 这里才是拷贝赋值运算符被调用的地方!

explicit 修饰的构造函数明确的告诉编译器: 禁止一切隐式的转换过程;

results matching ""

    No results matching ""