C++ Cookbook

目录

1 对象初始化

  • 总是使用花括号进行初始化{},原因:当隐式转换精度变小时编译器将会报错。
double pi = 3.1415926536;
int a(pi), b = pi; //丢失精度
// int c{pi}, d = {pi}; //编译报错:good!

2 指针和引用

2.1 常量指针和引用

  • 指向常量的指针没有规定其所指对象必须是一个常量(该值仍可以通过其他方式改变)。引用也一样。

2.2 const 指针

int val = 0;
int val2 = 1;
int *const pi = &val; //指向整形的常量指针 top-level const
pi = &val2; // error: assignment of read-only variable ‘pi’

const int *pci = &val; //指向常量整形的指针 low-level const
*pci = 2; // error: assignment of read-only location ‘* pci’

3 表达式

3.1 输入输出求值顺序

int i{0};
std::cout << i << " " << ++i << "\n"; //未定义

3.2 递增递减运算符

std::cout << *iter++ << "\n"; //先解引用后递增迭代器,常规简洁写法,记住。
// 等价于
std::cout << *iter << "\n";
iter++;

3.3 sizeof 运算符

sizeof 运算符返回一条表达式或一个类型名字所占的字节数

sizeof(type)
sizeof expr
  • sizeof 并不实际计算其运算对象的值,编译期即可
  • 对 string 或 vector 对象使用,仅返回该类型固定部分的大小

4 函数

4.1 局部静态变量

在程序第一次经过对象定义语句时初始化,直到程序结束才销毁。

auto f() {
  static auto cst = 0; // 仅执行一次
  return ++cst;
}
  • 编译器保证局部静态变量线程安全,典型实现:Meyer's singleton

4.2 数组形参

void f(const int *);
void f(const int[]); //函数意图显而意见
void f(const int[10]); //10 并不起作用,应通过额外的参数传数组长度
void f(int (&arr)[10]); //数组引用形参

4.3 可变参数形参

  • initializer_list

    同类型多参数

    void f(initializer_list<int> args);
    f({1, 2, 3});
    
  • 可变参数模板
    template<typename... Args>
    void f(const Args& ... rest);
    

4.4 列表初始化返回值

vector<string> process() {
  return {"a", "b", "cd"};
}

4.5 尾置返回类型

auto f(int i) -> int(*)[10];//返回指针,指向含有 10 个整数的数组

4.6 constexpr 函数

能用于常量表达式的函数

  • 函数的返回值及所有形参类型必须是字面值类型
  • 函数体有且只有一条 return 语句
constexpr int num() { return 42; }
  • 编译时直接用结果值 42 替换对 num 的函数调用

4.7 函数指针

bool (*pf)(int a);
bool fn(int a) { return a == 1; }
pf = fn;                  // & is optional
pf(2);                    // (*pf)(2) is optional
void usepf(bool fn(int)); // bool (*fn)(int) is optional
  • 返回函数指针
    using fn = bool(int);     //函数类型
    using pf = bool (*)(int); //函数指针类型
    pf returnFn();            // OK
    fn returnFn();            // Wrong
    fn *returnFn();           // OK
    auto returnFn() -> bool (*)(int)
    
  • 成员函数指针

    三种形式:

    auto a = std::mem_fn<int&()>(&X::get); //std::mem_fn 生成指向成员指针的包装对象
    auto b  = [] (X& x) {return x.get();};
    auto c = std::bind(&X::get, &x);
    

4.8 std::bind

接受一个可调用对象,绑定若干参数,生成一个新的可调用对象。(函数柯里化)

  • 引用类型参数需用 std::ref 包装,常量引用使用 std::cref。

4.9 运算符函数化

标准库提供了内置类型的函数形式的运算符,如 plus<int>, negate<int>等。主要用于给算法传递这些函数,例如:

sort(svec.begin(), svec.end(), greater<string>());

4.10 std::function

为了统一各种不同可调用对象之间(lambda,函数指针,重载调用运算符的类等)的同一调用形式(相同的函数签名)

5

5.1 隐式 inline

在类定义中声明的成员函数为隐式 inline 函数

5.2 可变数据成员(mutable)

使用关键字 mutable 定义 const 成员函数可以改变的数据成员,比如用于 const 函数调用计数

class Test {
 public:
  void fn() const { count++; }; //count 可以被改变

 private:
  mutable int count = 0;
};

fn 是一个对于外部来说的逻辑 const 函数,它所改变的内部状态不为外部“所知”(非接口可获取,内部缓存)

5.3 explicit 构造

抑制构造函数(拷贝和移动构造函数不算在内)定义的隐式转换

5.4 聚合类

  • 要求所有成员都为 public
  • 成员无默认初始化
  • 没有基类,没有 virtual
struct Data {
  int id;
  string name;
};
Data data{1, "steve"};

5.5 字面值常量类

  • 数据成员都是字面值类型的聚合类
  • 非聚合类
    • 数据成员必须是字面值类型
    • 数据成员初始值定义必须使用常量表达式
    • 至少有一个 constexpr 构造函数
    • 必须使用析构函数的默认定义

5.6 运算符重载

  • 递增递减运算符
    MyClass operator++(); //前置版本
    MyClass operator++(int); //后置版本
    
  • 类型转换重载
    operator target_type() const; //隐式转换
    explicit operator target_type() const; //显示转换
    

5.7 虚析构函数

虚析构函数是为了解决基类的指针指向派生类对象,并用基类的指针删除派生类对象时产生的子类资源不释放的问题。

5.8 dynamic_cast

从父类转换成子类,使用 dynamic_cast 会在运行时检查转换是否有效,而 static_cast 不会

5.9 强制调用基类虚函数

p_base->Base::fn();//使用作用域指定

5.10 虚继承

菱形结构

class A {}; //虚基类,被 D 继承了两次
class B : public virtual A {}; //声明虚继承
class C : public virtual A {}; //声明虚继承
class D : public B {};
class D : public C {};
  • D 中只有一个 A 的基类部分, 通过在 object 的 base class table 存放 base class 指针实现
  • 在派生类的构造中,虚基类总是先于非虚基类构造

5.11 嵌套类和局部类

  • 嵌套类:定义在某类内部,与外层类为相互独立的类型
  • 局部类:定义在函数内部,作用域很小,通常为小型数据类

6 类型

6.1 类型别名

以下两个语句等价:

typedef double wages;
using wages = double;

尽量使用 using,支持带模板的类型别名

6.2 Template Type Deduction.

template<typename T>
void f(ParamType param); // ParamType often contains adornments, such as const/& ...

f(expr);
int x = 42;
const int cx = x;
const int &rx = x;
  • Case 1: reference or pointer (obvious and intuitive)

    1. If expr's type is a reference, ignore the reference part.
    2. Then pattern-match expr's type against ParamType to determine T.
    template<typename T>
    void f(T &param);
    
    f(x); // T -> int, ParamType -> is int &
    f(cx); // T -> const int, ParamType -> is const int &
    f(rx); // T -> const int, ParamType -> is const int &, rule 1: rx's reference-ness is ignored
    f(5); // wrong 不接受右值
    
    template<typename T>
    void f(const T &param);
    f(x); // T -> int, ParamType -> const int &
    f(cx); // T -> int, ParamType -> const int &
    f(rx); // T -> int, ParamType -> const int &
    
  • Case 2: universal reference

    Declared like rvalue references in a non-template function.

    1. If expr is an lvalue, both T and ParamType are deduced to be lvalue references.
    2. If expr is an rvalue, the Case 1 rule applies.
    template <typename T>
    void f(T &&param);
    
    f(x);  // x is lvalue, T -> int &, ParamType -> int &&& -> int &
    f(cx); // T -> const int &, ParamType -> const int &&& -> const int &
    f(rx); // T -> const int &, ParamType -> const int &&& -> const int &
    f(42); // x is rvalue, T -> int, ParamType -> int &&
    
  • Case 3: neither reference nor pointer (just pass-by-value)

    1. If expr's type is a reference, ignore the reference part.
    2. If, after ignoring expr's reference-ness, expr is const, ignore that, too. If it's volatile, also ignore that.
    template<typename T>
    void f(T param);
    
    f(x); // T -> int, ParamType -> int
    f(cx); // T -> int, ParamType -> int
    f(rx); // T -> int, ParamType -> int
    
    const char* const ptr = {"42"}; // ptr is const pointer to const char
    f(ptr); // T -> const char *, ParamType -> const char *; the right const is ignored.
    // i.e. a modifiable pointer to a const char
    
  • Case 4: array

    decay into pointers when pass-by-value

    const char array[] = {"42"};
    
    template<typename T>
    void f(T param);
    f(array); // T -> const char *, ParamType -> const char *
    
    template<typename T>
    void f2(T &param);
    f2(array); // T -> const char [2], ParamType -> const char (&)[2]
    
    // practical array template example
    template<typename T, std::size_t N>
    constexpr std::size_t arraySize(T (&)[N]) noexcept {
      return N;
    }
    int values[] = {2, 3, 5, 7};
    int mappedValues[arraySize(values)];
    std::array<int, arraySize(values)> mappedValues2;
    
  • Case 5: function

    decay into pointers when pass-by-value

    void someFunc(int, double);
    
    template<typename T>
    void f1(T param);
    
    template<typename T>
    void f2(T& param);
    
    f1(someFunc); // T -> void (*)(int, double)
    f2(someFunc); // T -> void(int, double); ParamType -> void (&)(int, double)
    

6.3 auto

auto deduction just like template type deduction.

  • auto plays the role of T in the template
  • the type specifier for the variable acts as ParamType

    auto x = 42; // case3; auto -> int
    const auto cx = x; // case3; auto -> int; TypeSpecifier -> const int
    
    const auto &cx = x; // case1; auto -> int; TypeSpecifier -> const int &
    
    auto &&uref1 = x; // case2; auto -> int &; TypeSpecifier -> int &&& -> int &
    auto &&uref2 = cx; // case2: auto -> const int &; TypeSpecifier -> const int &&& -> const int &
    auto &&uref3 = 42; // case2: rvalue; auto -> int; TypeSpecifier -> int &&
    
    const char name[] = "beep";
    auto arr1 = name; // auto -> const char *; TypeSpecifier -> const char *
    auto &arr2 = name; // auto -> const char [4]; TypeSpecifier -> const char (&)[]
    
    void somefn(int);
    auto func1 = somefn; // auto -> void (*)(int); TypeSpecifier -> void (*)(int)
    auto &func2 = somefn; // auto -> void(int); TypeSpecifier -> void (&)(int)
    
  • Special Cases for auto

    auto assumes that a braced initializer represents a std::initializer_list, but template type deduction doesn't.

    // Special Cases
    auto x1 = 27; // auto -> int
    auto x2(27); // auto -> int
    int x3 = { 27 }; // auto -> std::initializer_list<int>; value is {27}
    int x4{ 27 };
    
    //  If such a type can't be deduced, the code will be rejected
    auto x5 = { 1, 2, 3.0 }; // error!!
    auto x6 = { 2, 3, 5 }; // OK!
    
    template<typename T>
    void f1(T param);
    f1({ 2, 3, 5}); // error!
    
    template<typename T>
    void f2(std::initializer_list<T> initList);
    f1({ 2, 3, 5}); // OK!
    
  • auto in C++14

    C++14 permits auto to indicate that a function's return type should be deduced, and C++14 lambdas may use auto in parameter declarations. However, these uses of auto employ template type deduction, not auto type deduction.

6.4 decltype

  • decltype almost always yields the type of a variable or expression without any modifications.

    const int i = 0; // decltype(i) is const int
    
    bool f(const Widget& w);
    // decltype(w) is const Widget&
    // decltype(f) is bool(const Widget&)
    // decltype(f(w)) is bool
    
  • One exception: For lvalue expressions of type T other than names, decltype always reports a type of T&.

    int x = 0;
    decltype((x)) y = x;
    // the expression (x) is defined to be an lvalue, decltype((x)) is therefore int&.
    
    decltype(auto) f() {
      int x = 0;
      return (x); // returns int&
    }
    
  • The primary use for decltype is declaring function templates where the function's return type depends on its parameter types. (C++11)

    std::vector<int> container;
    
    template<typename Container, typename Index>
    auto AccessInC11(Container& c, Index i)
        -> decltype(c[i]) {
      return c[i];
    }
    
    // For functions with an auto return type specification,
    // compilers employ template type deduction. return type will be int, not int& as below.
    template<typename Container, typename Index>
    auto AccessInC14(Container& c, Index i) {
      return c[i];
    }
    
    // C++14 refined 1
    // auto specifies that the type is to be deduced, and decltype says
    // that decltype rules should be used during the deduction.
    template<typename Container, typename Index>
    decltype(auto) AccessC14Refined1(Container& c, Index i) {
      return c[i];
    }
    
    // C++14 refined final
    template<typename Container, typename Index>
    decltype(auto) AccessC14RefinedFinal(Container& c, Index i) {
      return std::forward<Container>(c)[i];
    }
    
    // C++11 final
    template<typename Container, typename Index>
    auto AccessC11RefinedFinal(Container& c, Index i)
        -> decltype(std::forward<Container>(c)[i])
    {
      return std::forward<Container>(c)[i];
    }
    
  • decltype(auto) example

    Widget w;
    const Widget& cw = w;
    
    auto myWidget1 = cw; // auto type deduction: myWidget1's type is Widget
    decltype(auto) myWidget2 = cw; // decltype type deduction: myWidget2's type is const Widget&
    

6.5 Diagnostics

  • Compile Time
    template <typename T>
    class TypeDisplayer;
    
    int x = 0;
    const int *y = &x;
    
    TypeDisplayer<decltype(x)> xType; // aggregate ‘TypeDisplayer<int> yType’ has incomplete type ...
    TypeDisplayer<decltype(y)> yType; // aggregate ‘TypeDisplayer<const int*> yType’ has incomplete type ...
    
  • Runtime
    int x = 0;
    const int &y = x;
    
    std::cout << typeid(x).name() << "\n"; // not reliable
    std::cout << typeid(y).name() << "\n";
    
    // instead, use boost
    #include <boost/type_index.hpp>
    template<typename T>
    void f(const T& param) {
      using boost::typeindex::type_id_with_cvr;
      std::cout << type_id_with_cvr<T>().pretty_name() << "\n";
      std::cout << type_id_with_cvr<decltyp(param)>().pretty_name() << "\n";
    }
    // "with_cvr", cvr means const, volatile, reference
    

6.6 引用折叠

引用折叠只能应用于间接创建的引用的引用,如类型别名或模板参数

  • X& &, X& &&, X&& &都折叠成 X&
  • X&& &&折叠成 X&&

6.7 类型转换

  • 隐式转换
    int i = 5;
    double d = 3.14;
    int ival = d + i; // 隐式转换
    
  • 显式转换
    • static_cast
      auto d = static_cast<double>(i);
      void *p;
      auto pd = static_cast<double*>(p);
      
    • dynamic_cast

      多态指针转换,可通过返回值判断转换是否成功

    • const_cast

      去掉 const 修饰

      const char *pc;
      char *p = const_cast<char *>(pc)
      

      常常用于函数重载的情况

    • reinterpret_cast

      用来处理无关类型之间的转换,字面意思“重新解释(类型的比特位)”,不安全避免使用。

6.8 运行时类型识别 typeid

  • typeid 用于指针类型,始终返回该指针静态编译时的类型
  • typeid 作用于在继承体系中的类时,是否返回静态编译时类型取决于类是否包含虚函数

7 容器

7.1 数组

  • 声明

    int *ptrs[10]; //指针数组,指针都指向一个 int 对象
    int &refs[10]; //语法错误:不存在引用的数组
    int (*pArray)[10]; //指向一个 int 数组
    int (&rArray)[10]; //引用一个 int 数组
    
    using int_array = int[10];
    int_array *pArray; //指向一个 int 数组,更简洁
    
  • 使用数组初始化 vector

    int array[] = {1,5,3,6,9};
    std::vector<int> vec(std::begin(array), std::end(array));
    
  • 多维数组

        int array[2][3] = {2,5,7,5,3,1};
        for (auto &row: array)
    for (auto col: row)
        std::cout << col << std::endl;
    
    • 2 行 3 列数组,使用 for range 时,外层循环要用引用,为了避免数组被自动转成指针

7.2 iterator

  • C++11 引入 cbegin 和 cend,调用他们返回一个 const_iterator,支持只读访问。
  • C++11 引入全局函数 begin 和 end,定义在 iterator 头文件中。

        int test[] = {1,5,6,8,3,4};
        for (auto it = std::begin(test);
       it != std::end(test); it++) {
    std::cout << *it << std::endl;
        }
    
  • 迭代器循环体内不能执行改变迭代器容量的操作,如执行会使迭代器失效。

7.3 C 风格字符串

常用方法 strlen, strcmp, strcat, strcpy

  • c_str

    string 对象成员方法,返回 const char*,指向其内部的内容。

    std::string s("hello world!");
    auto c = s.c_str();
    s = "hello"; // cout << c print 'hello'
    

7.4 顺序容器

  • vector
  • list
  • forward_list
  • deque
  • array
  • string

7.5 关联容器

  • 有序容器
  • 无序容器
    std::unordered_map<std::string, int> h{{"a", 2}, {"b", 4}};
    std::cout << h.bucket_count() << "\n";     //桶个数
    std::cout << h.max_bucket_count() << "\n"; //最大桶个数
    std::cout << h.bucket_size(1) << "\n";     //第 1 个桶内的元素个数
    std::cout << h.load_factor() << "\n";      //每个桶的平均元素数量
    std::cout << h.max_load_factor() << "\n"; //试图维护的每个桶的平均元素数量
    h.rehash(20); //桶个数设为大于等于 n,且桶个数>size/max_load_factor
    h.reserve(50);//使得 h 可以保存 n 个元素且不必 rehash
    

7.6 遍历删元素

  std::vector<int> ivec{7, 2, 6, 5, 5, 6, 1};
  std::cout << ivec.capacity() << "\n";
  for (auto it = ivec.begin(); it != ivec.end();) {
    if (*it == 6) {
it = ivec.erase(it);
    } else {
++it;
    }
  }

7.7 容器适配器

  • stack(默认基于 deque)
  • queue(默认基于 deque)
  • priority_queue(默认基于 vector)

7.8 常用泛型算法

  • 算法形式
    • alg(beg, end, other args);
    • alg(beg, end, dest, other args);
    • alg(beg, end, beg2, other args);
    • alg(beg, end, beg2, end2, other args);

  • find, accumlate, equal, all, any, none, for_each, count, mismatch, search


  • fill, copy, move, replace, generate, remove, unique, reverse, rotate, shuffle

    • XXX_copy 会讲结果额外拷贝到新的集合中,即 non-inplace
  • 容器自定的算法通常性能更好

    如 list, forward_list 的 sort, merge, remove, reverse 和 unique

7.9 非常规迭代器

  • insert iterator
    • back_inserter
      vector<int> vec;
      auto it = back_inserter(vec);
      *it = 42; //类似于 push_back(42)
      

      常用于泛型算法的输出迭代器

    • front_inserter

      类似于 push_front

    • inserter

      指定插入位置的迭代器

  • stream iterator
    • istream_iterator
      std::istream_iterator<int> ist(cin), eof; //默认构造创建一个 eof 值
      vector<int> vec;
      std::transform(ist, eof, std::back_inserter(vec),
         [](auto a) { return a * 2; });
      
    • ostream_iterator
      vector<int> vec{9, 5, 5, 3, 5, 7, 8, 2};
      std::ostream_iterator<int> ost(cout);
      std::transform(vec.begin(), vec.end(), ost, [](auto a) { return a * 2; });
      
  • reverse iterator

    rbegin, rend

  • move iterator
  • local_iterator

    无序容器的桶(bucket)迭代器

8 智能指针

8.1 动态数组

  • unique_ptr
        {
    std::unique_ptr<int[]> array(new int[10]);
        } // 超出作用域自动调用 delete []释放
    
  • shared_ptr

    默认不支持 delete[]释放,需自定义删除器

        {
    std::shared_ptr<int[]> array(new int[10], [](int *p) { delete[] p; });
        }
    

8.2 allocator 类

用于分离 内存分配对象构造

  • 内存分配
    • allocate
    • deallocate
  • 对象构造与析构
    • construct
    • destroy
  • 未初始化内存的拷贝
    • uninitialized_copy
    • uninitialized_fill

9 移动语义

9.1 std::move & std::forward

  • std::move 实际只做右值转换,不做任何 move 的事情
  • std::forward 当参数是通过右值初始化时, std::forward 将其转化成右值,若为左值则返回原参数类型。

        void process(const int &n);
        void process(int &&n);
    
        template<typename T>
        void forward_test(T &&param) {
    process(std::forward<T>(param));//因 param 本身是一个左值,可以通过 std::forward 进行转换
        }
    
        int a = 0;
        forward_test(a); //lvalue->process
        forward_test(std::move(a)); // rvalue->process, n in process initialized by rvalue
    

    即当用于 universal reference 时,forward 会保持实参类型的所有细节,可以实现完美转发。

9.2 universal reference

当"T&&"引用遇上类型推断时,则为 universal 引用;其余时候为右值引用

template<typename T>
void f(T &&param); // param is universal reference

int &&var1 = 1;
auto &&var2 = var1; // var2 is universal reference

10 lambda

10.1 C++14 lambda hints

auto func = [data = std::move(data)] {...}; // C++14 init capture
auto func = [](auto &&x) { return normalize(std::forward<decltype(x)>x); }; // C++14 forward lambda

10.2 mutable lambda

  int i = 42;
  auto func = [i]() mutable { return ++i; } // lambda 函数体内能改变 i 的值,默认按值传递不允许

  //类似生成如下类
  class Func {
   public:
    Func(int i) : i_(i) {}
    int operator()() {
return ++i_;
    }

   private:
    int i_;
  };

如没有 mutable 修饰,则生成的 operator()会是 const 函数

11 模板

11.1 非类型参数模板

template<int N, int M>
bool less(const char (&p1)[N], const char (&p2)[M]); // 实例化时自动推断 N, M

11.2 模板参数的静态成员

默认情况 C++假定通过作用域运算符访问的名字是成员,非类型。可以使用 typename 显式的告诉编译器。

T::size_type * p; //编译器并不知道是变量定义还是成员变量乘以 p
typename T::size_type *p; //OK, 只能使用 typename,不能用 class

11.3 显示实例化

为了避免相同类型重复实例化,使用 extern 声明外部定义

  • extern template declaration: 声明
  • template declaration: 定义只能有一个

11.4 模板返回值

template <typename It>
auto fn(It begin, It end) -> typename remove_reference<decltype(*begin)>::type {
  return *begin
}
  • *begin 返回容器元素的引用
  • 使用 remove_reference 对类型去引用
  • 尾部 typename 显示告知编译器这里需要的是类型而非成员

11.5 标准类型转换模板

remove_reference, add_const, add_lvalue_reference, add_rvalue_reference, remove_pointer, add_pointer, make_signed, make_unsigned, remove_extent, remove_all_extents

11.6 可变参数模板

可不同类型多参数,使用 typename…来指定多个类型的列表,对于参数包,我们只能做两件事情

  • 获取参数包大小
template<typename T, typename... Args>
void foo(const T &t, const Args & ... args) {
  std::cout << sizeof...(Args) << "\n"; // 类型参数的数目
  std::cout << sizeof...(args) << "\n"; // 函数参数的数目
}
  • 扩展(expand)

    扩展的意思是将一个包分解为构成的元素

    template<typename T, typename... Args>
    void f(const T& t, const Args&... args) {
      while (sizeof...(args)) { //扩展生成函数参数列表
        std::cout << t << "\n";
        f(args...); //扩展生成实参列表
      }
    }
    
    template<typename T>
    void print(const T &t) {
      std::cout << t << "\n";
    }
    
    template<typename... Args>
    void f2(const Args&... args) {
      print(args)...; //对每一个 args 调用 print
    }
    
  • 编写可变参数函数例子
        template<typename T>
        ostream &print(ostream &os, const T &t) {
    return os << t;
        }// 该函数用于对 rest 的最后一次调用终止递归
    
        template<typename T, typename... Args>
        ostream &print(ostream &os, const T &t, const Args&... rest) {
    os << t << ", ";
    return print(os, rest...) //递归,每次调用自己打印剩余 rest 中的第一个
        }
    
  • emplace_back 的实现

    结合可变参数及 forward 机制实现 emplace_back

        template<typename... Args>
        void SomeVector::emplace_back(Args &&... args) {
    chk_n_alloc(); //如果需要重新分配 SomeVector 的内存空间
    alloc.construct(first_free++, std::forward<Args>(args)...);
        }
    

11.7 模板特例化

模板特例化指为原模板中的每个模板参数都提供实参,本质上接管了编译器的工作

template<>
int compare(const char* const &p1, const char* const &p2) {
  return strcmp(p1, p2);
}
  • 特例化本质是实例化一个模板,非重载;不影响函数匹配
  • 类模板可以部分特例化
        template <typename T> struct remove_reference { typedef T type; };
    
        template <typename T> struct remove_reference<T &> {
    typedef T type;
        }
    
        template <typename T>
        struct remove_reference<T &&> {
    typedef T type;
        }
    

12 辅助调试

  • NDEBUG 宏
  • assert 宏

12.1 辅助变量

__func__, __FILE__, __LINE__, __TIME__, __DATE__

13 输入输出流

13.1 缓冲区控制

  • endl: 输出一个换行符并刷新缓冲区
  • flush: 直接刷新缓冲区
  • ends: 输出一个'\0' 并刷新缓冲区
  • unitbuf: 设定输出流为每次写入直接刷新缓冲区

13.2 tie 关联输出流

例如标准输入流 cin 会关联标准输出流 cout,cin 会刷新 cout 的状态

cin.tie(&cout);

14 string

14.1 例子:特定字符搜索

string name = "r1b5";
string numbers = "0123456789";
string::size_type pos = 0;
while ((pos = name.find_first_of(numbers, pos)) != string::npos) {
  std::cout << name[pos] << "\n";
  ++pos;
}

14.2 数值转换

  • std::to_string
  • stoXXX

15 new & delete

  • 可重载 new 和 delete 操作符以控制 内存分配
  • 无法改变 运算符 new 和 delete 的行为
    • 运算符 new 的操作:1) 调用 operator new 获取内存空间 2) 在内存空间中使用构造函数构造对象
    • 运算符 delete:1) 调用析构销毁对象 2) 调用 operator delete 回收内存空间

15.1 placement new

int *p = new (nothrow) int; // placement new, 不抛出异常,创建失败则返回 nullptr

也可以是如下形式:

new (place_address) type

place_address 指定一个事先分配好内存的指针

  • placement new 类似于 allocator 的 construct 成员

15.2 标准库 allocator

  • allocate
  • deallocate
  • construct
  • destroy

16 异常

16.1 构造函数处理异常

需要声明成函数 try 语句块,这样才能捕获构造函数的初始化过程

Foo::Foo(int i) try : i_(i) {}

17 标准库

17.1 regex

17.2 random

std::uniform_int_distribution<> u(0, 9);
std::default_random_engine e; // e{some_seed}
std::cout << e.min() << " " << e.max() << "\n";
for (std::size_t i = 0; i < 10; ++i) {
  std::cout << u(e) << "\n";
}

18 不可移植特性

18.1 算术类型大小不一致

18.2 位域

using Bit = unsigned int; //位域必须是整型,通常使用无符号整形
class A {
  Bit a: 2; //占两位
  Bit b: 4;
  Bit c: 8;
  Bit d: 50; // a+b+c=16+d>64 d start with another unsigned int block
}

为了实现存储压缩,a,b,c 压缩在同一个 unsigned int 内

18.3 volatile

volatile 告诉编译器,不应该对其声明的对象进行优化。

  • 打破编译器根据有限的上下文对某变量是不会发生改变的假设,从而作出错误的编译优化。

18.4 extern

extern 链接指示器可声明其他语言编写的函数,也可以指示 C++代码为其他语言所用

extern "C" size_t strlen(const char *);
extern "C" {
#include <string.h>
  int acFn(int a);
  //...
}

18.5 __cplusplus

与 C 编译器共享编译同一个源文件

#ifdef __cplusplus
extern "C"
#endif // __cplusplus
int strcmp(const char *, const char *);

19 类对象

19.1 内存布局

  • 一般继承
    vptr 虚函数表
    data member 数据成员
  • 多重继承
    base1 vptr
    base1 data member
    base2 vptr
    base2 data member
    derived data member
  • 多重继承(虚继承)
    base vptr
    derived_level1_a vptr
    derived_level1_a vbptr(虚继承 base)
    derived_level1_a data member
    derived_level1_b vptr
    derived_level1_b vbptr(虚继承 base)
    derived_level1_b data member
    derived_level2 data member