- C++的一些版本 - C++98(1.0) - C++03 - C++11(2.0) - C++14 ## Variadic Templates 可变参数模板 ```cpp void printX() {} template void printX(const T& firstArg, const Types&... args) { cout << firstArg << endl; printX(args...); } ``` > 通过`sizeof...(args)`可以知道args中包括多少个参数 通过可变参数模板,可以帮助我们使用递归做一些操作 ```cpp // 可变参数模板的用例 #include template inline void hash_combine(size_t& seed. const T& val)// 函数_4 { seed ^= std::hash()(val) + 0x9e3779b9 + (seed<<6) + (seed >> 2); } template inline void hash_val(size_t& seed, const T& val){// 函数_3 hash_combine(seed, val); } template inline void hash_val(size_t& seed, const T& val, const Types&... args)// 函数_2 { hash_combine(seed, val); // 根据args...的参数个数来执行 函数_2 或者 函数_3 hash_val(seed, args... ); } template inline size_t hash_val(const Types&... args)// 函数_1 { size_t seed = 0; // 虽然 函数_1 和 函数_2 都符合执行条件,但是 函数_2 相比而言更特化,所以执行 函数_2 hash_val(seed, args); return seed; } class CustomerHash{ public: std::size_t operator()(const Customer& c) const{ // 因为第一个fname不是size_t类型,所以执行的是 函数_1 hash_val return hash_val(c.fname, c.lname, c.no); } } ``` > 这里用args配合递归不停的切分参数 ```cpp template class tuple; template<> class tuple<> {}; // 作为中止点 template class tuple : private tuple { typedef tuple inherited; public: tuple() {} tuple(Head v, Tail... vtail) : m_head(v), inherited(vtail) {} typename Head::type head() {return m_head;} inherited& tail() {return *this;} protected: Head m_head; } // tuple t(41, 6.3, "Niko"); // t.head() -> 41 // t.tail() -> tuple(6.3, "Niko") // t.tail()->head() -> 6.3 ``` > 这里最上面有个空的 tuple<>,是作为递归继承的中止点 > 这里用递归继承的方式做出了tuple ## 模板中的空格 ```cpp vector > // 全版本支持 vector> // C++11之后没有空格也行 ``` > 因为老版本一些编译器会将`>>`识别为操作符,所以需要加空格 > C++11之后不加空格也行 ## nullptr ans std::nullptr_t `nullptr` 指的是 空指针,C++11允许用`nullptr`代替0或者`NULL` ```cpp void f(int); void f(void*); f(0); // 运行 f(int) f(NULL); // 运行 f(int),如果是其他数,就不清楚具体调用哪个了 f(nullptr); // 云心 f(void*) ``` ```cpp #if defined(__cplusplus) && __cplusplus >= 201103L #ifndef __GXX_NULLPTR_T #define __GXX_NULLPTR_T typedef decltype(nullptr) nullptr_t; ``` ## auto ```cpp auto i = 20; // i 的 类型是 int double f(); auto d = f(); // d 的 类型是 double vector v; auto pos = v.begin(); // pos 的 类型是 vector::iterator auto l = [](int x) -> bool { // ... }; // l 的类型是 lambda表达式 ``` > 使用`auto`主要是用在数据类型 **太长** 或者 **太复杂** 的情况 (比如迭代器、lambda的类型) > 倒也不是什么情况都用`auto` ```cpp // 标准库中对auto的使用 #if __cplusplus >= 201103L inline auto operator- (const reverse_iterator<_IteratorL> &__x, const reverse_iterator<_IteratorR> &__y) -> decltype(__y.base() - __x.base()) #else inline typename reverse_iterator<_IteratorL>::difference_type operator- (const reverse_iterator<_IteratorL>& __x, const reverse_iteratror<_IteratorR>& __y) #endif ``` ## unifor initialization 一致性初始化 初始化可能发生在小括号、中括号、赋值中 ```cpp Rect r1 = {3, 4, 5}; // 大括号 Rect r1(3, 4, 5); // 小括号 int values[5] = {1, 3, 4, 5}; // 赋值 ``` C++11引入**一致性初始化**——大括号(虽然用以前的也没错) ```cpp int values[]{1, 3, 4, 5}; vector v{1, 3 ,4 ,5}; complex c{3.0, 4.0}; ``` > 变量之后直接加上大括号即表示初始化 当编译器看到`{t1, t2, t3...}`便做出一个`initializer_list`,他关联至一个`array`,调用函数(比如构造函数)时`arry`中的元素被逐个分解传给函数;如果调用函数的参数就是`initializer_list`,就不会注意分解而是直接整个传给函数 比如:`vector v{1, 3 ,4 ,5};` 中的初始化参数 `{1, 3 ,4 ,5}`转换成 `initlializer_list`,而这个关联`array`容器 ## initalizer_list 初始化列表 ```cpp int i; // 没有被初始化 int j{}; // j被初始化为0 int *p; // p没有被初始化 int *q{}; // q被初始化为nullptr ``` ```cpp int x1(4.3); // ok int x2 = 4.3; // ok int x3 {4.3}; // 部分平台Error,部分平台warning int x4 = {5.3}; // 部分平台Error,部分平台warning char c1{7}; // ok char c2{9999}; // 部分平台Error,部分平台warning std::vector v1{1, 3, 4, 5}; // ok std::vector v2{1.0, 3.0, 4.0, 5.0}; // 部分平台Error,部分平台warning ``` > 初始化列表的作用,还可以帮助检查一些错误 ```cpp void print(std::initlializer_list vals) { for(auto p = vals.begin(), p != vals.end(); ++p){ std::cout << *p << std::endl; } } print({1, 3, 4, 5}); ``` > 证明`{t1, t2, t3...}`被转换成`initializer_list` ```cpp class P{ public: P(int a, int b){ std::cout << "(int ,int)" << std::endl; } P(initializer_list initlist){ std::cout << "initializer_list" << std::endl; } }; P p(7, 7); // 输出 (int ,int) P p{7, 7}; // 输出 initializer_list P r{7, 5, 4}; // 输出 initializer_list P p = {7, 7}; // 输出 initializer_list ``` > 如果不存在第二个`initializer_list`的构造函数,则第三个对象 r 将创建失败 ## explicit ```cpp /** * non-explicit-one-argument 只要一个实参就够了的构造函数 * 下面例子中:没有写oprator * (Fraction, int)的操作符重载也不会报错,因为编译器将4通过一个参数的构造变成Fraction(4, 1)类型 * 当构造函数前加上explicit关键字时,编译器不会进行隐式构造函数将4变成Fraction(4, 1) * * —————————————————————————————————————————————————————————————————————————————— * 如果构造函数不存在explicit,且有operator double()转换函数,编译器报错,因为存在二义性即两种方式都可以行得通且没有优先级这么一说 * 如果构造函数存在explicit,且有operator double()转换函数,编译器不报错,因为只有一条路可走,将Fraction转换成double计算 * 如果构造函数不存在explicit,且没有operator double()转换函数,编译器不报错,因为只有一条路可走,将4转换成Fraction计算 **/ class Fraction { public: // 这里构造函数只需要一个参数其实也够 Fraction(int num, int den = 1) : m_numerator(num), m_denominator(den) {} //explicit Fraction(int num, int den = 1) : m_numerator(num), m_denominator(den) {} Fraction operator*(const Fraction &f) { return Fraction(f.m_numerator * this->m_numerator, f.m_denominator * this->m_denominator); } std::string getString() { char numerator[10]; char denominator[10]; memset(numerator, 0, sizeof(numerator)); memset(denominator, 0, sizeof(denominator)); itoa(m_numerator, numerator, 10); itoa(m_denominator, denominator, 10); return std::string(std::string(numerator) + std::string("/") + std::string(denominator)); } private: int m_denominator; // 分母 int m_numerator; // 分子 }; int main() { Fraction f(3, 5); Fraction f2 = f * 4; std::cout << f2.getString() << std::endl; system("pause"); return 0; } ``` > explicit告诉编译器,不能发生相应的隐式类型转换,只能以显示的方式进行类型转换 ## range-based for statement ```cpp // for(decl : coll){ // statement // } for(int i : {2, 3, 4, 5, 6}){ cout << i << endl; } vector vec; //... for(auto elem : vec){ cout << elem << endl; } for(auto &elem : vec){ elem *= 3; } // range-based for 的底层实现类似 for(auto _pos = coll.begin(), _end = coll.end(); _pos != _end; ++pos){ decl = *_pos; // 对值的操作 } // 或者 for(auto _pos = begin(coll), _end = end(coll); _pos != _end; ++pos){ decl = *_pos; // 对值的操作 } ``` **下面的写法会报错** ```cpp class C{ public: explicit C(const string& s); // 禁止string隐式转换成C // ... } vector vs; for(const C& elem : vs){ cout << elem << endl; // 报错,因为字符串不能隐式的转换成C } ``` ## =default, =delete 如果自行定义一个构造函数,那么编译器不会再给一个默认构造函数 如果强制加上 `=default` 就可以重新获得并使用默认构造函数 ```cpp class Zoo { public: Zoo(int i1, int i2) : d1(i1), d2(i2) {} Zoo(const Zoo&) =delete; // 不允许拷贝构造 Zoo(Zoo &&) =default; // 使用编译器给的 Zoo& operator=(const Zoo &) =default; Zoo& operator=(const Zoo&&) =delete; virtual ~Zoo(){} private: int d1, d2; }; ``` ```cpp class Foo{ public: Foo(int i) : _i(i) {} Foo() = default; // 构造函数可以有多个,这里希望编译器提供默认构造 Foo(const Foo& x) : _i(x._i) {} // Foo(const Foo&) = default; // Error 拷贝构造函数不可被重载 // Foo(const Foo&) = delete; // Error 拷贝构造函数不可被重载 Foo& operator=(const Foo& x) {_i = x._i; return *this;} // Foo& operator=(const Foo& x) = default; // Error 赋值不可被重载 // Foo& operator=(const Foo& x) = delete; // Error 赋值不可被重载 void func1() = default; // Error 不可作为default void func2() = delete; // ok 不过如果不需要可以不写这个函数… ~Foo() = default; // ~Foo() = delete // 可以new不可delete }; ``` - `=default` 除了默认成员函数(构造、析构、拷贝、赋值等)之外,其他函数使用编译报错(很明显,其他函数也没有所谓的默认) - `=delete`可以用在任何函数上(`=0`只能用于`virtual`函数) > 一般而言,如果成员对象中存在指针,建议考虑深拷贝、浅拷贝的问题,需要自定义拷贝、赋值函数 ## Alias Templat (template typedef)化名模板 ```cpp template using Vec = std::vector>; Vec coll; // = std::vector coll; ``` ## decltype 使用该关键字可以找到表达式的参数类型 ```cpp map coll; decltype(coll)::value_type elem; // C++11可以通过对象使用 map::value_type elem; // C++11之前必须指明类型使用 template auto add(T1 x, T2 y) -> decltype(x+y); // 通过decltype动态设置返回参数类型 decltype(x+y) add(T1 x, T2 y); // 这样写报错,因为decltype先不知道x、y是什么,在后面的函数参数中才知道 auto cmp = [](const Person& p1, const Person& p2){ // 比较大小 }; std::set coll(cmp); ``` ## lambda表达式 ```cpp []{ std::cout << "hello world" << std::endl; }(); // 输出 hello world auto l = []{ std::cout << "hello world" << std::endl; }; l();// 输出 hello world [导入器](参数) mutable 异常 -> 返回类型 { 函数体 } ``` > 导入器,取用的外部变量(传值、传引用) > 参数,正常函数参数 > mutable 是否可以修改值 > 异常 一些特殊操作抛出异常 ```cpp { int id = 0; // 值传递 auto f = [id]() mutable { std::cout << "id : " << id << std::endl; ++id;// 如果没有mutable,则id不可执行++操作 }; id = 42; f(); // 输出 id : 0 f(); // 输出 id : 1 f(); // 输出 id : 2 std::cout << id << std::endl; // 输出 id : 42 } { int id = 0; // 引用传递 auto f = [&id](int param) { std::cout << "id : " << id << std::endl; ++id; ++param; }; id = 42; f(7); // 输出 id : 42 f(7); // 输出 id : 43 f(7); // 输出 id : 44 std::cout << id << std::endl; // 输出 id : 45 } { int id = 0; auto f = [id]() { std::cout << "id : " << id << std::endl; ++id; }; // 报错 没有mutable 不可更改 id = 42; f(); f(); f(); std::cout << id << std::endl; } // 一个 = 表示 其他所有对象都是值传递, id为引用传递 auto f = [=, &id]{ // ... }; ```