【C++进阶】:C++11新增特性

一.统一列表的初始化

1.{}初始化

在C++98中,标准允许使用花括号{}对数组或者结构体元素进行统一的列表初始值设定。例如:

在这里插入图片描述

C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自定义的类型,使用初始化列表时,可添加等号(=),也可不添加

在这里插入图片描述

这里对自定义类型本质是进行了多参数的隐式类型转化。原来的c++只支持单参数的隐式类型转化,例如常量字符串转化成string。

在这里插入图片描述
在这里插入图片描述

2.initializer_list

看一个例子:

在这里插入图片描述

答案是不同的。a2使用的是多参数隐式类型转化,它只能写两个参数。而a1,可以写多个参数,例如可以写成vector< int >a1={1,2,3,4,5,6}。能这样写是因为c++11有一个initializer_list。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

那为什么vector为什么能用initializer_list进行构造呢?当然是因为vector有对应的构造函数了。

在这里插入图片描述

注意:

在C++中,使用初始化列表或使用成员初始化列表时,构造函数初始化的顺序是由成员变量在类中声明的顺序决定的,而不是由初始化列表中的顺序决定的。使用{}进行构造函数初始化时,也遵循相同的规则。

让我们通过一个简单的示例来说明这一点:

#include <iostream>

class MyClass {
public:
    MyClass(int a, int b) : b(b), a(a) {
        std::cout << "a: " << a << ", b: " << b << std::endl;
    }

private:
    int a;
    int b;
};

int main() {
    MyClass obj{10, 20};
    return 0;
}

在这个示例中,MyClass类有两个整型成员变量ab,在构造函数中使用{}进行初始化。尽管在构造函数的初始化列表中,ba之前,但是由于成员变量在类中声明的顺序是ab之前,因此实际上会先初始化a,再初始化b

因此,在这个示例中,输出将会是:

a: 10, b: 20

这就是C++中使用{}进行构造函数初始化时成员变量初始化顺序的规则。

二.声明

1.decltype

关键字decltype将变量的类型声明为表达式指定的类型。

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

2.nullptr

由于C++中NULL被定义成字面量0,这样就可能回带来一些问题,因为0既能指针常量,又能表示整形常量。所以出于清晰和安全的角度考虑,C++11中新增了nullptr,用于表示空指针。

在这里插入图片描述

三.右值引用和移动语义

1.转义语句

传统的C++语法中就有引用的语法,而C++11中新增了的右值引用语法特性,之前的引用就叫做左值引用。无论左值引用还是右值引用,都是给对象取别名。

左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址,左值可以出现赋值符号的左边。定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址。左值引用就是给左值的引用,给左值取别名。

右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址。右值引用就是对右值的引用,给右值取别名。

在这里插入图片描述

在这里插入图片描述
在以上情况里不能左值引用,因为函数结束后空间会被销毁。这里只能先将返回的s拷贝一份,再对main里s进行拷贝构造。接下来来一个补充知识。

在这里插入图片描述
在这里插入图片描述

为了避免上述多次拷贝造成的浪费,C++11对string进行了修改,多加了一个赋值重载(自定义的右值都是将亡值)。

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

2.完美转发

万能引用

在这里插入图片描述

在这里插入图片描述

为什么这里打印出来全是左值呢?为什么不是传右值就接收右值呢?

这是因为虽然我们传的是右值,但接收的t的属性实际上是左值。右值本身不可修改,但右值引用的变量会被编译器识别成左值,否则在移动构造的情况下就无法完成资源的转移。

如果我们想要t保持原有属性呢?

在这里插入图片描述

在这里插入图片描述

上文说到将右值变量强制识别成左值就是为了资源转移,那么这里保持原有属性又是为什么呢?看下面场景。

在这里插入图片描述

3.右值引用例子—vector

在这里插入图片描述

在这里插入图片描述

这里的b就是将亡值,vector的swap操作里封装了std::swap,而std::swap就是使用了move;a与b进行了交换后,ab的地址互换了。

std::swap
在这里插入图片描述

vector::swap

这里的data就是vector底层封装的数组。

在这里插入图片描述

四.可变参数模板

1.基本概念

C++11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板,相比C++98/03,类模版和函数模版中只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改
进。然而由于可变模版参数比较抽象,使用起来需要一定的技巧,所以这块还是比较晦涩的。

在这里插入图片描述

在这里插入图片描述

递归式展开参数包

上面的参数args前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为“参数 包“,它里面包含了0到N(N>=0)个模版参数。我们无法直接获取参数包args中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特 点,也是最大的难点,即如何展开可变模版参数。由于语法不支持使用args[i]这样方式获取可变 参数,所以我们的用一些奇招来一一获取参数包的值。

在这里插入图片描述

在这里插入图片描述

逗号式展开

这种展开参数包的方式,不需要通过递归终止函数,是直接在expand函数体中展开的, printarg
不是一个递归终止函数,只是一个处理参数包中每一个参数的函数。这种就地展开参数包的方式
实现的关键是逗号表达式。我们知道逗号表达式会按顺序执行逗号前面的表达式。

expand函数中的逗号表达式:(printarg(args), 0),也是按照这个执行顺序,先执printarg(args),再得到逗号表达式的结果0。同时还用到了C++11的另外一个特性——初始化列表,通过初始化列表来初始化一个变长数组, {(printarg(args), 0)…}将会展开成((printarg(arg1),0),(printarg(arg2),0), (printarg(arg3),0), etc… ),最终会创建一个元素值都为0的数组int arr[sizeof…(Args)]。由于是逗号表达式,在创建数组的过程中会先执行逗号表达式前面的部分printarg(args)打印出参数,也就是说在构造int数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在数组构造的过程展开参数包。

在这里插入图片描述

在这里插入图片描述

2.STL里emplace类接口

在这里插入图片描述

在这里插入图片描述

例子

在这里插入图片描述

在这里插入图片描述

首先我们看到的emplace系列的接口,支持模板的可变参数,并且万能引用。那么相对insert和
emplace系列接口的优势到底在哪里呢?

在这里插入图片描述

五.lambda表达式

语法形式

lambda表达式书写格式:[capture-list] (parameters) mutable -> return-type { statement }


例子

在这里插入图片描述

lambda里可不可以再调函数呢?

可以调用全局函数,不能调用局部函数。

在这里插入图片描述

但如果我们需要调用局部数据,可以使用捕捉列表。

捕捉列表

例一:

在这里插入图片描述

例二:

在这里插入图片描述

例三:

在这里插入图片描述

在这里插入图片描述

六.包装器

1.简单使用

function包装器

function包装器 也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。
那么我们来看看,我们为什么需要function呢?

在这里插入图片描述

在这里插入图片描述

通过上面的程序验证,我们会发现useF函数模板实例化了三份。包装器可以很好的解决上面的问题。

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

此时编译器就将三种不同类型识别为统一类型,不仅解决了重复问题而且可以将不同类型放入同一容器内。

2.适配器

bind
std::bind函数定义在头文件中,是一个函数模板,它就像一个函数包装器(适配器),接受一个可
调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。一般而
言,我们用它可以把一个原本接收N个参数的函数fn,通过绑定一些参数,返回一个接收M个(M
可以大于N,但这么做没什么意义)参数的新函数。同时,使用std::bind函数还可以实现参数顺
序调整等操作。

在这里插入图片描述

可以将bind函数看作是一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对
象来“适应”原对象的参数列表。

调用bind的一般形式:auto newCallable = bind(callable,arg_list);
其中,newCallable本身是一个可调用对象,arg_list是一个逗号分隔的参数列表,对应给定的
callable的参数。当我们调用newCallable时,newCallable会调用callable,并传给它arg_list中
的参数。

arg_list中的参数可能包含形如_n的名字,其中n是一个整数,这些参数是“占位符”,表示newCallable的参数,它们占据了传递给newCallable的参数的“位置”。数值n表示生成的可调用对
象中参数的位置:_1为newCallable的第一个参数,_2为第二个参数,以此类推。

在这里插入图片描述在这里插入图片描述

在这里插入图片描述

七.新的类功能

原来C++类中,有6个默认成员函数:

  1. 构造函数
  2. 析构函数
  3. 拷贝构造函数
  4. 拷贝赋值重载
  5. 取地址重载
  6. const 取地址重载

最后重要的是前4个,后两个用处不大。默认成员函数就是我们不写编译器会生成一个默认的。
C++11 新增了两个:移动构造函数和移动赋值运算符重载

针对移动构造函数和移动赋值运算符重载有一些需要注意的点如下:
如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任
意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类
型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,
如果实现了就调用移动构造,没有实现就调用拷贝构造。

如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中
的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内
置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋
值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造
完全类似)如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

咸蛋挞

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值