C++11的新特性

    科技2024-03-24  82

    C++11 是第二个真正意义上的 C++ 标准,其中增加了很多现代编程语言的特性~

    1、新类型

    C++11新增了类型long long和unsigned long long,以支持64位的整型;新增了char16_t和char32_t,以支持16位和32位的字符表示。

    2、统一的初始化

    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 }; }

    3、声明

     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、右值引用(&&)

     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(第六版)

    Processed: 0.020, SQL: 8