1 nullptr 空指针字面量,强类型std::nullptr_t,可以隐式转换为任何指针类型。
1 2 3 4 5 #ifndef __cplusplus #define NULL ((void *)0) #else #define NULL 0 #endif
C++中,NULL 是字面量 0,而不是(void *)0
1 2 3 4 5 6 7 8 9 class A {};int main () { A *p1 = nullptr ; A *p2 = NULL ; A *p3 = 0 ; }
C++中,NULL是为兼容C代码而定义的,C++11前,C++代码推荐使用0代替NULL(0即是空值,false,空指针)。 C++代码中大量使用NULL,一方面是习惯,另一方面NULL比0更方便阅读,让人看上去就知道变量、参数是指针类型。
1 2 3 4 5 this ->ptr = 0 ;this ->ptr = NULL ;func (0 , 0 );func (0 , NULL );
C++98/03中,模板函数调用时,0作为无类型值,无法进行类型推导(往往当成int)。C++11新增nullptr就是为了更好地支持泛型(模板)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <cstddef> template <class T >constexpr T clone (const T& t) { return t; } void g (int *) {}int main () { g (nullptr ); g (NULL ); g (0 ); g (clone (nullptr )); }
作为强类型的nullptr还可以解决如下二义性问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <cstddef> #include <iostream> void f (int *) { std::cout << "f(int *) called." << std::endl; }void f (int ) { std::cout << "f(int) called." << std::endl; }void g (char *) { std::cout << "g(char *) called." << std::endl; }void g (char ) { std::cout << "g(char) called." << std::endl; }int main () { f (nullptr ); f (0 ); g (nullptr ); }
总结:C++11后,使用强类型的nullptr替代NULL/0;
2 initialization/std::initializer_list 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include <cstddef> #include <iostream> class A {public : A () : p (nullptr ) {} A (int ) : p (nullptr ) {} A (long ) : p{nullptr } {} A (double ) : A () {} private : char *p; }; class A2 {private : char *p = nullptr ; char *p2{nullptr }; }; int main () { int i1 (0 ) ; int i2 = 0 ; int i3{0 }; int *pi = new int {0 }; A a{0 }; std::atomic<int > ai1{0 }; std::atomic<int > ai2 (0 ) ; }
总结: C++11后新的初始化方式使代码更简洁,统一。
3 auto/decltype auto在C++11前就是一个关键字, 变量前加auto(或不加)表示自动存储期, C++11起用作自动类型推导。
C++11里auto用于推导变量的类型, C++14里可以推导函数返回类型, 其规则同模板实参推导规则。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 { auto x = 0 ; } { auto x = 0u ; } { auto x{0 }; } { } { auto x = {0 }; } { const auto & x = 0 ; } { std::map<int , int > map; for (auto x : map) { } for (auto & x : map) { } for (const auto & x : map) { } map<string, int > map2; for (const std::pair<std::string, int >& kv : map2) ; for (const std::pair<const std::string, int >& kv : map2) ; for (const auto & kv : map2) ; } { std::map<std::string, int > data{{"abc" , 1 }, {"bcd" , 2 }}; for (const auto & [k, v] : data) { std::cout << k << ": " << v << std::endl; } } { auto x = new int {0 }; } { auto *x = new int {0 }; } { } { int x{0 }; int & y = x; auto z = y; } { const int x{0 }; volatile int y{0 }; auto z1 = x; auto z2 = x; } { const int x{0 }; auto & y = x; } { int x[]{0 , 1 , 2 }; auto y = x; } { int x[]{0 , 1 , 2 }; auto & y = x; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 void f () {}template <typename T, typename U>auto add (T t, U u) -> decltype (t + u) { return t + u; } int main () { int x{0 }; decltype (x) y; decltype (f) func; decltype (0.1 ) f; auto g = [](int a, int b) { return a + b; }; decltype (g) func2 = g; std::cout << func2 (1 , 2 ) << std::endl; const int i{0 }; decltype (i) j = i; int a{0 }; int & b = a; decltype (b) c = a; auto ret = add (1.0 , 2.1 ); }
总结: auto/decltype 有助于简化代码, 但细节很多(*, &, const, volatile), 将其理解为占位符而不是一种新类型。
4 lambda Lambda 表达式: 可以捕获变量的匿名函数对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 std::vector<int > data (10 ) ;std::generate (data.begin (), data.end (), [n = 0 ]() mutable { return n++; }); typedef void (*func) () ;int main () { func f = []() { std::cout << "func1" << std::endl; }; f (); int x = 100 ; std::function<void ()> f2 = [x]() { std::cout << "func2, x = " << x << std::endl; }; f2 (); std::function<void ()> f3 = std::bind ([](int x) { std::cout << "func3, x = " << x << std::endl; }, 200 ); f3 (); }
总结:lambda可以简化代码,可以用来实现闭包(包括operator(), std::function, std::bind 等)。
5 Rvalue reference/std::move 5.1 左值/右值
一般而言,左值出现在等号左边,右值出现在等号右边,左值可以取地址,右值不能取地址。
1 2 3 4 5 6 7 8 9 10 11 a++ // 右值 ++a // 左值 &a // r a, b // a是左值,b是右值 "hello" // 字符串字面量,左值 nullptr 0 true // 其他字面量,右值 this // 右值 string() // 右值 枚举项 // 右值 变量的名字,不论其类型(右值引用),由其名字构成的表达式仍是左值表达式
5.2 左值引用/右值引用
C++支持引用(创建对象的别名),C++11起,支持右值引用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 std::string s; std::string& s2 = s; const std::string& s3 = s; void f (const std::string& s) ; int & n = 42 ; const int & n = 42 ; int && n = 42 ; std::string s1 = "hello, " ; std::string s2 = "world." ; string&& s3 = s1 + s2; std::cout << s3 << std::endl; struct A { ~A () { std::cout << "~A()" << std::endl; } }; int main () { A&& a = A (); std::cout << "here" << std::endl; } int main () { A a = A (); std::cout << "here" << std::endl; }
5.3 std::move
1 2 3 4 5 6 template <typename _Tp>_GLIBCXX_NODISCARD constexpr typename std::remove_reference<_Tp>::type&& move (_Tp&& __t ) noexcept { return static_cast <typename std::remove_reference<_Tp>::type&&>(__t ); }
5.4 移动构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 struct A { A () {} A (const A&) { std::cout << "A(const A&)" << std::endl; } A (A&&) { std::cout << "A(A&&)" << std::endl; } }; int main () { std::vector<A> vc; vc.emplace_back (A ()); } int main () { std::string s ("hello" ) ; std::string s2 = std::move (s); std::cout << std::boolalpha << s.empty () << std::endl; std::cout << s2 << std::endl; }
5.5 std::forward
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 T& & => T& T&& & => T& T& && => T& T&& && => T&& template <typename _Tp>_GLIBCXX_NODISCARD constexpr _Tp&&forward(typename std::remove_reference<_Tp>::type& __t ) noexcept { return static_cast <_Tp&&>(__t ); } struct A { void f () & { std::cout << "&" << std::endl; } void f () && { std::cout << "&&" << std::endl; } }; template <typename T>void Print (T&& t) { t.f (); std::move (t).f (); std::forward<T>(t).f (); } int main () { A a; Print (a); Print (A ()); } for (auto && x: f ());
总结:移动语义节省拷贝构造的开销,是升级至C++11最重要的特性之一;没有移动语义,就没有unique_ptr(C++11前只能用auto_ptr);std::move/std::forward 不移动/转发任何东西,只做cast。
6 forEach 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 int arr[]{0 , 1 , 2 };for (int & x : arr) { x++; } { int arr[3 ] = {0 , 1 , 2 };{ int (&__range1)[3 ] = arr;for (int * __begin1 = __range1, *__end1 = __range1 + 3L ; __begin1 != __end1; ++__begin1) { int & x = *__begin1; x++; } }
总结:用于遍历,方便,更可读。
7 constexpr 修饰变量或函数,用于常量表达式,(要求编译器)进行编译期求值。
1 2 3 4 5 template <class T > class numeric_limits {public : static constexpr T max () noexcept {} };
总结:扩展了常量表达式的范围,显示要求编译器。
8 raw string literal 原始字符串字面量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 std::cout << R"dxxx( hello, world. )dxxx" << std::endl; std::cout << R"(ABC 123)" << std::endl;std::cout << R"( { "k1" : true, "k2" : "str" } )" << std::endl;std::regex word_regex (R"(\w+)" ) ;
总结:避免转义字符,json、regex、换行时好用。
9 variadic templates 可变模版参数
1 2 3 4 5 6 7 8 9 void print () { std::cout << std::endl; }template <class T , class ... Args>void print (const T& arg0, const Args&...args) { std::cout << arg0 << " " ; print (args...); } print (1 , 1.0 , true , "hello" );
1 2 3 4 5 6 template <class ... Args>void print (const Args&...args) { (void )std::initializer_list<int >{([](auto i) { std::cout << i << " " ; }(args), 0 )...}; std::cout << std::endl; }
总结:强大,优美!
10 lang
noexcept
thread_local
static_assert
…
library
容器:std::array, std::unordered_map, std::unordered_set, std::unordered_multiset, std::unordered_multimap, std::forward_list
智能指针:std::unique_ptr, std::shared_ptr, std::weak_ptr
正则表达式库:regex
并发:std::thread, std::atomic, std::mutex, std::condition_variable
…
Reference