C++11 是第二个真正意义上的 C++ 标准,其中增加了很多现代编程语言的特性~
C++11新增了类型long long和unsigned long long,以支持64位的整型;新增了char16_t和char32_t,以支持16位和32位的字符表示。
C++11扩大了用大括号括起列表的适用范围
int x = {5}; double y {2.75};通过初始化列表初始化对象。
class Foo { public: Foo(int) {} private: Foo(const Foo &); }; int main(void) { Foo a1(123); Foo a2 = 123; //error: 'Foo::Foo(const Foo &)' is private Foo a3 = { 123 }; Foo a4 { 123 }; int a5 = { 3 }; int a6 { 3 }; return 0; }除了上面所述的内容之外,列表初始化还可以直接使用在函数的返回值上:
struct Foo { Foo(int, double) {} }; Foo func(void) { return { 123, 321.0 }; }C++11提供了多种简易声明的功能,尤其是在使用模板时。
3.1.1 auto的简易用法
C++11中将auto用于实现自动类型推导,也就是说,使用了 auto 关键字以后,编译器会在编译期间自动推导出变量的类型,这样我们就不用手动指明变量的数据类型了。
注意:auto 仅仅是一个占位符,在编译器期间它会被真正的类型所替代。或者说,C++ 中的变量必须是有明确类型的,只是这个类型是由编译器自己推导出来的。
例:
auto i = 10; auto f = 12.8; auto pt = &n; auto url = "https://www.csdn.net/"; 第 1 行中,10 是一个整数,默认是 int 类型,所以推导出变量 n 的类型是 int。第 2 行中,12.8 是一个小数,默认是 double 类型,所以推导出变量 f 的类型是 double。第 3 行中,&n 的结果是一个 int* 类型的指针,所以推导出变量 f 的类型是 int*。第 4 行中,由双引号""包围起来的字符串是 const char* 类型,所以推导出变量 url 的类型是 const char*,也即一个常量指针。3.1.2 auto的高级用法
auto 除了可以独立使用,还可以和某些具体类型混合使用,这样 auto 表示的就是“半个”类型,而不是完整的类型。
int x = 0; auto *p1 = &x; //p1 为 int *,auto 推导为 int auto p2 = &x; //p2 为 int*,auto 推导为 int* auto &r1 = x; //r1 为 int&,auto 推导为 int auto r2 = r1; //r2 为 int,auto 推导为 int 第 2 行代码中,p1 为 int* 类型,也即 auto * 为 int *,所以 auto 被推导成了 int 类型。第 3 行代码中,auto 被推导为 int* 类型。第 4 行代码中,r1 为 int & 类型,auto 被推导为 int 类型。第 5 行代码是需要重点说明的,r1 本来是 int& 类型,但是 auto 却被推导为 int 类型,这表明当=右边的表达式是一个引用类型时,auto 会把引用抛弃,直接推导出它的原始类型。3.1.3 auto的限制
(1)auto 不能在函数的参数中使用。
(2)auto 不能作用于类的非静态成员变量(也就是没有 static 关键字修饰的成员变量)中。
(3)auto 关键字不能定义数组,下面的例子就是错误的:
char url[] = "https://www.csdn.net/"; auto str[] = url; //arr 为数组,所以不能使用 auto(4)auto 不能作用于模板参数
3.2 decltype
decltype是C++11新增的关键字,它和 auto 的功能一样,都用来在编译时期进行自动类型推导。
下面的语义的含义是,让y的类型与x相同,其中x为表达式
decltype(x) y;我们知道,auto 只能用于类的静态成员,不能用于类的非静态成员(普通成员),如果我们想推导非静态成员的类型,这个时候就必须使用 decltype 了。
#include <vector> using namespace std; template <typename T> class Base { public: void func(T& container) { m_it = container.begin(); } private: typename T::iterator m_it; //注意这里 }; int main() { const vector<int> v; Base<const vector<int>> obj; obj.func(v); return 0; }单独看 Base 类中 m_it 成员的定义,很难看出会有什么错误,但在使用 Base 类的时候,如果传入一个 const 类型的容器,编译器马上就会弹出一大堆错误信息。原因就在于,T::iterator并不能包括所有的迭代器类型,当 T 是一个 const 容器时,应当使用 const_iterator。
要想解决这个问题,在之前的 C++98/03 版本下只能想办法把 const 类型的容器用模板特化单独处理,增加了不少工作量,看起来也非常晦涩。但是有了 C++11 的 decltype 关键字,就可以直接这样写:
template <typename T> class Base { public: void func(T& container) { m_it = container.begin(); } private: decltype(T().begin()) m_it; //注意这里 };3.3 返回值后置
C++11 新增了一种函数声明语法:在函数名和参数列表后面指定返回类型
double f1(double, int); auto f2(double, int) -> double;这种写法一般用于泛型编程。考虑如下场景
template <typename R, typename T, typename U> R add(T t, U u) { return t+u; } int a = 1; float b = 2.0; auto c = add<decltype(a + b)>(a, b);我们并不关心 a+b 的类型是什么,因此,只需要通过 decltype(a+b) 直接得到返回值类型即可。但是像上面这样使用十分不方便,因为外部其实并不知道参数之间应该如何运算,只有 add 函数才知道返回值应当如何推导。
那么,在 add 函数的定义上能不能直接通过 decltype 拿到返回值呢?
返回类型后置语法是通过 auto 和 decltype 结合起来使用的。上面的 add 函数,使用新的语法可以写成:
template <typename T, typename U> auto add(T t, U u) -> decltype(t + u) { return t + u; }为了进一步说明这个语法,再看另一个例子:
int& foo(int& i); float foo(float& f); template <typename T> auto func(T& val) -> decltype(foo(val)) { return foo(val); }如果说前一个例子中的 add 使用 C++98/03 的返回值写法还勉强可以完成,那么这个例子对于 C++ 而言就是不可能完成的任务了。
3.4 using
使用 typedef 重定义类型是很方便的,但它也有一些限制,比如,无法重定义一个模板。using 则可以
template <typename Val> using str_map_t = std::map<std::string, Val>; // ... str_map_t<int> map1;实际上,using 的别名语法覆盖了 typedef 的全部功能。
4.1 C++的左值和右值
通常情况下,判断某个表达式是左值还是右值,最常用的有以下 2 种方法。
(1)可位于赋值号(=)左侧的表达式就是左值;反之,只能位于赋值号右侧的表达式就是右值。举个例子:
int a = 5; 5 = a; //错误,5 不能为左值其中,变量 a 就是一个左值,而字面量 5 就是一个右值。值得一提的是,C++ 中的左值也可以当做右值使用,例如:
int b = 10; // b 是一个左值 a = b; // a、b 都是左值,只不过将 b 可以当做右值使用(2)有名称的、可以获取到存储地址的表达式即为左值;反之则是右值。
4.2 C++右值引用
C++98/03 标准中就有引用,使用 "&" 表示。但此种引用方式有一个缺陷,即正常情况下只能操作 C++ 中的左值,无法对右值添加引用。
int num = 10; int &b = num; //正确 int &c = 10; //错误为此,C++11 标准新引入了另一种引用方式,称为右值引用,用 "&&" 表示。
需要注意的,和声明左值引用一样,右值引用也必须立即进行初始化操作,且只能使用右值进行初始化,比如:
int num = 10; //int && a = num; //右值引用不能初始化为左值 int && a = 10;4.3 C++11移动语义
所谓移动语义,指的就是以移动而非深拷贝的方式初始化含有指针成员的类对象。简单的理解,移动语义指的就是将其他对象(通常是临时对象)拥有的内存资源“移为已用”。
template <typename T> class DynamicArray { public: // ...其它省略 // 移动构造函数 DynamicArray(DynamicArray&& rhs) : m_size{ rhs.m_size }, m_array{rhs.m_array} { rhs.m_size = 0; rhs.m_array = nullptr; cout << "Move constructor: dynamic array is moved!\n"; } // 移动赋值操作符 DynamicArray& operator=(DynamicArray&& rhs) { cout << "Move assignment operator is called\n"; if (this == &rhs) return *this; delete[] m_array; m_size = rhs.m_size; m_array = rhs.m_array; rhs.m_size = 0; rhs.m_array = nullptr; return *this; } };如果使用左值初始化同类对象,但也想调用移动构造函数完成,有没有办法可以实现呢?
4.4 std::move
vector<int> v1{1, 2, 3, 4}; vector<int> v2 = v1; // 此时调用复制构造函数,v2是v1的副本 vector<int> v3 = std::move(v1); // 此时调用移动构造函数,v3与v1交换:v1为空,v3为{1, 2, 3, 4}你可能会想,std::move函数内部到底是怎么实现的。其实std::move函数并不“移动”,它仅仅进行了类型转换。下面给出一个简化版本的std::move:
template <typename T> typename remove_reference<T>::type&& move(T&& param) { using ReturnType = typename remove_reference<T>::type&&; return static_cast<ReturnType>(param); }4.5 std::forward与完美转发
首先解释下什么是完美转发,它指的是函数模板可以将自己的参数“完美”地转发给内部调用的其它函数。所谓完美,即不仅能准确地转发参数的值,还能保证被转发参数的左、右值属性不变。例:
template<typename T> void function(T t) { otherdef(t); }如上所示,function() 函数模板中调用了 otherdef() 函数。在此基础上,完美转发指的是:如果 function() 函数接收到的参数 t 为左值,那么该函数传递给 otherdef() 的参数 t 也是左值;反之如果 function() 函数接收到的参数 t 为右值,那么传递给 otherdef() 函数的参数 t 也必须为右值。
总的来说,在定义模板函数时,我们采用右值引用的语法格式定义参数类型,由此该函数既可以接收外界传入的左值,也可以接收右值;其次,还需要使用 C++11 标准库提供的 forword() 模板函数修饰被调用函数中需要维持左、右值属性的参数。由此即可轻松实现函数模板中参数的完美转发。
#include <iostream> using namespace std; //重载被调用函数,查看完美转发的效果 void otherdef(int & t) { cout << "lvalue\n"; } void otherdef(const int & t) { cout << "rvalue\n"; } //实现完美转发的函数模板 template <typename T> void function(T&& t) { otherdef(forward<T>(t)); } int main() { function(5); int x = 1; function(x); return 0; }文章参考:http://c.biancheng.net/view/7863.html
https://zhuanlan.zhihu.com/p/54050093
C++ Primer Plus(第六版)