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 可变参数形参
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.6 运算符重载
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)
- If expr's type is a reference, ignore the reference part.
- Then pattern-match expr's type against ParamType to determine T.
template<typename T> void f(T ¶m); 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 ¶m); 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.
- If expr is an lvalue, both T and ParamType are deduced to be lvalue references.
- If expr is an rvalue, the Case 1 rule applies.
template <typename T> void f(T &¶m); 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)
- If expr's type is a reference, ignore the reference part.
- 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 ¶m); 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.
autoplays the role of T in the templatethe 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
autoautoassumes that a braced initializer represents astd::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!
6.4 decltype
decltypealmost 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,
decltypealways 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
decltypeis 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)exampleWidget 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.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
- 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; });
- istream_iterator
- reverse iterator
rbegin, rend
- move iterator
- local_iterator
无序容器的桶(bucket)迭代器
8 智能指针
8.1 动态数组
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 &¶m) { 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 &¶m); // 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); }
- 特例化本质是实例化一个模板,非重载;不影响函数匹配
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