1. 语言特性 1.1 常量 nullptr代替NULL 传统C++会把NULL,0视为同一个东西,有些定义((void*)0),有些会定义为0,但是有问题:
C++不允许void * 隐式类型转换,void* 0
0会给C++重载特性带来混乱
constexpr 明确声明函数或者对象在编译期会成为常量表达式, C++14开始,constexpr函数可以使用局部变量、循环、分支等简单语句。
1 2 3 4 5 6 constexpr int fibonacci (const int n) { if (n == 1 ) return 1 ; if (n == 2 ) return 1 ; return fibonacci(n-1 ) + fibonacci(n-2 ); }
字面量: C++14带来很多新的字面量:
1 2 3 4 5 6 7 8 9 10 11 using namespace std ::literals::complex_literals;std ::cout << "i * i = " << 1 i * 1 i << std ::endl ;using namespace std ::literals::chrono_literals;this_thread::sleep_for(500 ms); using namespace std ::literals::string_literals;std ::cout << "hello world" s.substr(0 , 5 );#include <bitset> cout << bitset <9>(mask) << endl ;
1.2 变量及其初始化 C++17 可以将变量放在语句内(C++17) :
1 2 3 4 if (const std ::vector <int >::iterator itr = std ::find(vec.begin(), vec.end(), 3 );itr != vec.end()) { *itr = 4 ; }
初始化列表(C++11)
1 2 3 4 5 6 7 std ::vector <int > vec;MagicFoo(std ::initializer_list <int > list ) { for (std ::initializer_list <int >::iterator it = list .begin(); it != list .end(); it++) { vec.push_back(*it); } }
几乎可以在所有初始化对象的地方使用大括号而不是小括号。当一个构造函数没有标成 explicit 时,你可以使用大括号不写类名来进行构造:
1 2 3 4 Obj getObj () { return {1.0 }; }
和Obj(1.0)唯一区别在于{1.0}拒绝窄转换,只允许调用Obj(double).
结构化绑定(C++17)
类数据成员的默认初始化(C11):
1 2 3 4 5 6 7 8 9 10 11 12 class Complex {public : Complex() {} Complex(float re) : re_(re) {} Complex(float re, float im) : re_(re) , im_(im) {} private : float re_{0 }; float im_{0 }; };
内联变量(C++17) C++17 引入了内联(inline)变量的概念,允许在头文件中定义内联变量,然后像内联函数一样,只要所有的定义都相同,那变量的定义出现多次也没有关系。对于类的静态数据成员,const 缺省是不内联的,而 constexpr 缺省就是内联的。
1 struct magic { static inline const int number = 42 ;};
1.3 类型推导 auto - 自动类型推断 - C++ 20开始可以函数传参 - C++14开始可以用于返回值(包括decltype)
auto实际使用规则类似于函数模板参数推导 - auto a = expr
意味着用expr去匹配一个假想的 template <typename T> f(T)
函数模板,结果为值类型 - const auto& a = expr
意味着expr去匹配一个 template <typename T> f(const T&)
结果为常左值引用类型 - auto&& a = expr;
意味着 template <typename T> f(T&&)
函数模板
即根据类型推导规则,auto 是值类型,auto& 是左值引用类型,auto&& 是转发引用(可以是左值引用,也可以是右值引用)。
decltype
decltype(变量名) 可以获得变量名的精确类型
decltype(表达式) -> 获得表达式的引用
如果是个纯右值(prvalue),结果仍然是值类型
尾返回类型(C++11)
1 2 3 4 template <typename T, typename U>auto add2(T x, U y) -> decltype(x + y) { return x + y; }
返回值推导(C++14):
1 2 3 4 template <typename T, typename U>auto add3 (T x, U y) { return x + y; }
decltype(auto) (C++14) 写auto时要确定引用还是值类型,decltype(auto)可以根据表达式通用地决定返回的是值类型还是引用类型。
1 2 3 decltype (auto ) look_up_string () { return lookup1(); }
类模板实参推导 (CTAD)(C++17 起)
1 2 std ::pair pr{1 , 42 };std ::array a{1 ,2 ,3 };
1.4 控制流 if constexpr (C++17) 允许代码中声明常量表达式的判断:
1 2 3 4 5 6 7 8 9 #include <iostream> template <typename T>auto print_type_info (const T& t) { if constexpr (std ::is_integral<T>::value) { return t + 1 ; } else { return t + 0.001 ; } }
区间for(C++11)
1 2 3 for (auto element : vec) {}
1.5 模板 模板的哲学在于将问题丢到编译期去处理,大幅度优化运行时的性能,C++的黑魔法之一。
外部模板 为了解决重复实例化的问题,C++11 引入外部模板:
1 2 template class std : :vector <bool >; extern template class std : :vector <double >;
using 语法用作类型别名(C++11)
1 2 3 4 5 6 7 8 9 10 11 12 typedef int (*process) (void *) ;using NewProcess = int (*)(void *);template <typename T>using TrueDarkMagic = MagicType<std ::vector <T>, std ::string >;int main () { TrueDarkMagic<bool > you; }
变长参数模板(C++11) 参数是可变的,但是如何解包(把类型拿下来)有以下处理:
递归模板函数(C++11),缺点在需要定义一个终止递归的函数
变参模板展开(C++17)
初始化列表展开
1 2 3 4 5 6 7 8 9 10 11 template <typename T0>void printf1 (T0 value) { std ::cout << value << std ::endl ; } template <typename T, typename ... Ts>void printf1 (T value, Ts... args) { std ::cout << value << std ::endl ; printf1(args...); }
变参模板展开(C++17),在一个函数中完成printf的编写
1 2 3 4 5 template <typename T0, typename ... T>void printf2 (T0 t0, T... t) { std ::cout << t0 << std ::endl ; if constexpr (sizeof ...(t) > 0 ) printf2 (t...) ; }
初始化列表展开
1 2 3 4 5 6 7 template <typename T, typename ... Ts>auto printf3 (T value, Ts... args) { std ::cout << value << std ::endl ; (void ) std ::initializer_list <T>{([&args] { std ::cout << args << std ::endl ; }(), value)...}; }
1.6 面向对象 委托构造(C++11) 构造函数可以调用另一个构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <iostream> class Base {public : int value1; int value2; Base() { value1 = 1 ; } Base(int value) : Base() { value2 = value; } }; int main () { Base b (2 ) ; std ::cout << b.value1 << std ::endl ; std ::cout << b.value2 << std ::endl ; }
继承构造(C++11) 使用using 引入继承构造的概念
1 2 3 4 5 #include <iostream> class Subclass : public Base {public : using Base::Base; };
显式虚函数重载(C++11
override 通知编译器进行重载
final
函数 final 表示
类 final 表示拒绝重载
default
delete
枚举类
1 2 3 enum class new_enum : unsigned int { value1, value2, value3 = 100 , value4 = 100 };
2. 运行时强化 2.1 Lambda 表达式 捕获方式:
值捕获 [value]
引用捕获 [&value]
隐式捕获
表达式捕获 C++ 14
Lambda 泛型
2.2 函数包装器
1 2 3 std ::function<int (int )> func2 = [&](int value)->int { return 1 + value; };
2.3 移动和右值 2.3.1 值类别 值类别和值类型的差别:
值类别 value category :左右值
值类型/引用类型 value type :C++中,只有引用和指针是引用类型,Java中原生类型是值类型,类属于引用类型,Python中都是引用类型
lvalue : 有标识符,可取地址的表达式,函数变量名,返回左值引用的表达式,字符串字面量
rvalue :包括prvalue 纯右值和xvalue将亡值
prvalue : 纯右值,传统右值:没有标识符,不可以取地址的表达式,例如++x,除了字符串字面量之外的字面量,prvalue如果绑定到一个引用上,可以延长生命周期
xvalue :将亡值,可以看成有名字的右值(有标识符)
glvalue :现在的左值范围,lvalue和xvalue
2.3.2 移动 std::move
: 无条件地将实参强制类型转换为右值,产生一个xvalue
原理: static_cast 到type &&
1 2 3 4 5 6 7 8 9 10 template <typename _Tp>constexpr typename std ::remove_reference<_Tp>::type&&move(_Tp&& __t ) noexcept { return static_cast <typename std ::remove_reference<_Tp>::type&&>(__t ); }
移动的意义: string res = string("hello") + name + ".";
在C++之前是完全不推荐的,但是有了移动之后过程:
调用 string(const *)
,生成Hello临时对象,复制一次
调用 operator+(string&&, const string&)
直接在1临时对象上操作,name复制一次
调用 operator+(string&&, const string&)
在后面追加操作
临时对象2析构
临时对象1析构
对于实际内存布局而言, 例如A类中有B,C类,在Java或者Python这样的语言中存储的实际是指针(类似)。保证了内存访问的局部性 ,这在现代处理器架构中是有性能优势的。缺点是复制对象的开销大大增加,故有移动语义这样的东西存在。
2.3.3 完美转发 std::forward
万能引用:
1 2 3 4 temmplate <typename T> void f (T&& arg) {}
如果是左值引用,则重载到&&,引用折叠,还是左值。如果是右值引用,重载到&&,引用折叠,是右值。这里的重载是为了检查右值
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 template <typename _Tp>constexpr _Tp&&forward(typename std ::remove_reference<_Tp>::type& __t ) noexcept { return static_cast <_Tp&&>(__t ); } template <typename _Tp>constexpr _Tp&&forward(typename std ::remove_reference<_Tp>::type&& __t ) noexcept { static_assert (!std ::is_lvalue_reference<_Tp>::value, "template argument" " substituting _Tp is an lvalue reference type" ); return static_cast <_Tp&&>(__t ); }
引用坍缩和完美转发
引用坍缩主要是由于模板的推导结果可能是引用。即,对于
1 template <typename T> foo(T&&);
如果传递左值,则T推导为左值引用,如果传递是右值,则T推导为类型本身。T&&保持值类别进转发,即做到了完美转发。
3. 智能指针 实现一个智能指针:
unique_ptr
需要考虑赋值运算符函数的问题
实现*, -> 等运算符函数即可
在C++11之前auto_ptr实现有问题
当一不小心传递给另外一个ptr,你就不再拥有这个对象了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 smart_ptr& operator =(smart_ptr rhs) { rhs.swap(*this ); return *this ; } void swap (smart_ptr& rhs) { using std ::swap; swap(m_ptr, rhs.m_ptr); }
shared_ptr实现(不考虑多线程)
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 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 class my_shared_ptr { public : template <typename U> friend class my_shared_ptr ; explicit my_shared_ptr (T* ptr = nullptr ) : m_ptr (ptr) { if (ptr) { m_shared_count = new shared_count(); } } my_shared_ptr(const my_shared_ptr& other) { m_ptr = other.m_ptr; if (m_ptr) { other.m_shared_count->add_count(); m_shared_count = other.m_shared_count; } } template <typename U> my_shared_ptr(const my_shared_ptr<U>& other) noexcept { m_ptr = other.m_ptr; if (m_ptr) { other.m_shared_count->add_count(); m_shared_count = other.m_shared_count; } } template <typename U> my_shared_ptr(const my_shared_ptr<U>& other, T* ptr) { m_ptr = ptr; if (m_ptr) { other.m_shared_count->add_count(); m_shared_count = other.m_shared_count; } } template <typename U> my_shared_ptr(my_shared_ptr<U>&& other) noexcept { m_ptr = other.m_ptr; if (m_ptr) { m_shared_count = other.m_shared_count; other.m_ptr = nullptr ; } } my_shared_ptr& operator =(my_shared_ptr rhs) noexcept { rhs.swap(*this ); return *this ; } ~my_shared_ptr() { if (m_ptr && !m_shared_count->reduce_count()) { delete m_ptr; delete m_shared_count; } } public : long use_count () const { if (m_ptr) { return m_shared_count->get_count(); } else { return 0 ; } } T* get () const noexcept { return m_ptr; } T& operator *() const noexcept { return *m_ptr; } T* operator ->() const noexcept { return m_ptr; } operator bool () const noexcept { return m_ptr; } private : void swap (my_shared_ptr& rhs) { using std ::swap; swap(m_ptr, rhs.m_ptr); swap(m_shared_count, rhs.m_shared_count); } private : T* m_ptr; shared_count* m_shared_count; }
4. STL Array 优点:
C 数组作为参数有退化行为,传递给另外一个函数后那个函数不再能获得 C 数组的长度和结束位置
C 数组没有良好的复制行为,无法作为键类型