当前位置:   article > 正文

C++11新特性,应用

C++11新特性,应用

23. C++11 新特性

23.1. nullptr替代NULL

  • nullptr是空指针常量,可以隐式转换成任意指针类型,但不会自动转换为整数类型;而NULL是宏,整数类型,【注意】 存在类型安全隐患。
  • 应用:将指针置空时。

23.2. 引入了auto和decltype这两个关键字实现类型推导

  • auto让编译器通过初始值进行类型推导,从而获得定义变量的类型。
  • 应用:在不知道类型怎么给,或者类型写起来复杂时。
  • 【C++14】 decltype(auto),它可以根据表达式的运算结果自动推导变量的类型,示例如下。
template<typename T, typename U>
auto add(T x, U y) -> decltype(auto) //C++11时,-> decltype(x+y)
{
    return x + y;
}

int main() {
    cout << typeid(add(10, 40.44)).name() << endl; //输出double
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

23.3. 基于范围的for循环,列表初始化 {}

  • 应用:遍历容器中的元素更加简单和直观,示例如下。
int main() {
    vector<int> v = { 1, 2, 3, 4, 5 };

    for (auto&& u : v)
        cout << u << " ";

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

23.4. STL新增容器,对已有容器新增接口

  • 新增容器
    • array(固定大小的数组),forward_list(单链表),unordered_map,unordered_set。
  • 已有容器新增接口
    • 列表初始化。
    • cbegin() 返回第一个元素的常量迭代器,即不能修改指向的内容。cend() 返回超过末尾的常量迭代器。
    • 移动构造函数,移动赋值运算符。
    • 右值引用版本的插入接口。

23.6. 右值引用和move语义

  • 左值:可以取地址的对象。
  • 左值引用:对左值的引用,即给左值取别名。
  • 右值:不能取地址的对象,如10,x+y。
  • 右值引用:对右值的引用,即给右值取别名。
  • move语义:它允许将资源从一个对象移动到另一个对象,而不是通过复制。即通过避免不必要的资源分配和复制操作来优化性能。
  • 【注意】 参数进入函数后被当成左值处理,可使用static_cast<T&&>或move()转换成右值。

23.7. 完美转发

  • 应用:使用完美转发创建通用的函数模板,它可以保持参数的属性(左值和右值),示例如下。
void processValue(int& lvalue) {
    cout << "Processing lvalue: " << lvalue << endl;
}

void processValue(int&& rvalue) {
    cout << "Processing rvalue: " << rvalue << endl;
}

//使用完美转发的函数模板 
//模板中&&不代表右值引用,而是万能引用,既能接收左值也能接收右值。
template<typename T>
void forwarder(T&& arg) { 
    processValue(forward<T>(arg));
}

int main() {
    int x = 5;
    forwarder(x); // 输出:Processing lvalue: 5  
    forwarder(10); // 输出:Processing rvalue: 10  
    forwarder(move(x)); // 输出:Processing rvalue: 5 
    forwarder(static_cast<int&&>(x)); // 输出:Processing rvalue: 5 

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 另外,emplace_back()的参数使用forward<>完美转发给构造函数,然后就地构造。而push_back()的参数使用move()转换成右值再传入,先构造再移动到容器末尾,即先调用构造函数,再调用移动构造函数。

23.8. 变参模板

  • 应用:将函数的参数包传递给另一个函数调用,示例如下。
#include <iostream>
#include <type_traits> //is_lvalue_reference_v  
#include <utility> //forward  
using namespace std;

template<typename U,typename... T>
void foo(U&& arg,T&&... args) {  

    // 检查参数属性  
    if constexpr (is_lvalue_reference_v<U>)
        cout << "lvalue: " << arg << endl;
    else
        cout << "rvalue: " << arg << endl;

    // 如果还有参数,递归调用foo  
    if constexpr (sizeof... (args) > 0)
        foo(forward<T>(args)...);
}

 
template<typename... T>
void wrapper(T&&... arg) {
    foo(forward<T>(arg)...); //使用完美转发将参数包扔过去
}

int main() {
    int x = 5;
    wrapper(x,move(x),3.4); // 输出:lvalue: 5, rvalue: 5,rvalue: 3.4  
    return 0;
}
  • 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

23.9. Lambda 表达式

  • 底层实现:当定义一个lambda表达式后,编译器会自动生成一个匿名类,它重载了()运算符,称为闭包类型。那么在运行时,这个lambda表达式会返回一个匿名的闭包实例。闭包的一个强大之处是其可以通过传值或者引用的方式捕捉其封装作用域内(定义闭包所处的函数作用域)的变量。
  • lambda表达式的语法定义如下。
[capture] (parameters) mutable ->return-type {statement}; 
  • 1
  • 捕捉列表说明如下。
    • [ = ] 按复制方式(值传递方式)捕获全部本地变量。
    • [ & ] 按引用方式捕获全部本地变量。【注意】 当lambda函数引用的变量超出了其定义时的作用域,那么调用lambda函数就会导致未定义行为 (悬垂引用)
    • [ = , &j , &k ] 按复制方式捕获全部本地变量,针对其中一两个采用引用方式捕获。
    • [ & , j , k ] 按引用方式捕获全部本地变量,针对其中一两个采用复制方式捕获。
    • [ &i , j, &k ] 仅要捕获几个变量。
    • [ this ] 类的数据成员无法直接捕获,若想从lambda函数内部访问类的数据成员,则需在捕获列表中加上this指针。
    • 【C++14】 广义捕获,它能够捕获表达式的运算结果,而不限于直接复制或引用本地变量。该特性最常用于以移动方式捕获,从而避免以引用方式捕获,可能造成的悬垂引用问题,示例如下。
int main() {

    vector<int> nums = { 1, 2, 3, 4, 5 };

    // 使用广义捕获和移动语义来捕获nums
    auto printFunc = [vec = move(nums)]() {
        for (auto num : vec) {
            cout << num << " ";
        }
        cout << std::endl;
    };

    printFunc(); // 调用lambda函数

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 应用:利用lambda表达式可以编写内嵌的匿名函数,以替换独立函数或函数对象,示例如下。
struct Adder {
    int operator()(int a, int b) const {
        return a + b;
    }
};


int main() {

    Adder obj;
    cout << obj(2, 3) << endl; // 调用函数对象

    auto add = [](int a,int b) {return a + b; };
    cout << add(2, 3) << endl; //调用匿名函数对象

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

分析函数对象(仿函数)是指可以像函数一样被调用的对象,它是重载()运算符的类或结构体,如这里的Adder。而lambda表达式可以定义匿名函数对象,如这里的add,同时也是闭包实例。

23.10. 默认成员函数控制

  • =default 显式缺省函数
    • 作用:让编译器提供默认成员函数。
    • 应用:如我们定义了构造函数时,还想要编译器提供默认构造函数时,可以加上默认构造函数=default。
  • =delete 删除函数
    • 作用:让编译器不生成默认成员函数。
    • 应用 :
      • 可以删除拷贝构造函数、赋值运算符、移动构造函数、移动赋值运算符,来禁止复制,如单例类。
      • 移除特定的重载版本,如防止int强制向下转换成short,示例如下。
class Converter {
private:
    short value;
public:
   	Converter(short value) : value(value) {}
    Converter(int) = delete; 
};

int main() {
    int intValue = 10000;
    Converter converter(intValue); //报错,防止了int强制向下转换成short

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/587666
推荐阅读
相关标签
  

闽ICP备14008679号