当前位置: 首页 > article >正文

【C++第十六课 - C++11】列表初始化、右值引用、移动构造、移动赋值、lambda表达式

目录

  • 列表初始化
  • 声明
    • auto
    • decltype
    • typeid
    • nullptr
    • 范围for
    • 智能指针(后面讲)
    • STL的变化
      • 新容器
      • 新接口
  • 右值引用和移动语义
    • 左值引用
    • 右值引用
    • 注意
    • 移动构造
    • 移动赋值
    • 引用折叠/万能引用
    • 完美转发
  • stl变化
      • 移动构造函数
      • 移动赋值函数
      • 默认成员函数的强制生产
      • 禁止生产
      • 类变成最终类,不能被继承
      • 强制派生类的虚函数进行重写
      • 可变参数模板
  • lambda表达式
    • lambda表达式书写格式
      • 捕捉列表

列表初始化

这个很难评

列表是{}
一切皆可用列表初始化
int a(10)int b = int(1):这个是模板那边支持的,构造和拷贝构造

(1)在C++98中,标准允许使用花括号{}对数组或者结构体元素进行统一的列表初始值设定。
(2)C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自定义的类型,使用初始化列表时,可添加等号(=),也可不添加。

int main()
{
	//普通定义
	int a = 0;

	//C++11列表初始化
	int b = { 0 };
	int c{ 0 };
	int arry1[]{ 1, 2, 3, 4, 5, 6 };
	Date x{ 2021,4,9 };
	vector<int> v = { 1, 2, 5, 6, 8, 9 };
	//构造
	Date x(2001, 2, 9);

	//Date x = (1994, 6, 6); 这个不行这个如果可以的话是隐式类型转换,但对于类的隐式类型转换只允许单参数的
	return 0;
}

C++11的列表初始化最佳用法

	Date* darr1 = new Date[3]{ {2001, 3, 29}, { 2005, 5, 30}, {2024, 8, 8} };
	Date* darr2 = new Date[2]{ x, x2 };

下面用法的实现是在vector里面新增了一个构造函数,这个数组先存到常量区里面,initializer_list<value_type>里面存了指向这个数组的指针begin、end再进行拷贝

vector<int> v = { 1, 2, 5, 6, 8, 9 };

在这里插入图片描述
在这里插入图片描述
底层
map的initializer_list

为何pair<const string, string>可以用pair<const char*, char*>来初始化

声明

auto

decltype

推导类型,可以推导某个变量的类型,再用这个类型定义新的变量

取类型的时候会去掉顶层的const
typeid也是下面的规则
修饰本身是顶层const
修饰指向的内容是底层const

顶层const
在这里插入图片描述
底层const
在这里插入图片描述

decltype实际用的地方
在这里插入图片描述
如上图,如果有人写了上述数据,然后想要用vector存储funca的数据

	auto x = funca();
	vector<decltype(x)> v;

typeid

推导类型,打印类型,推导出来的类型是一个字符串,不能用它推导出来的类型再进行定义
在这里插入图片描述

nullptr

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

范围for

智能指针(后面讲)

STL的变化

新容器

在这里插入图片描述

增加不好的地方
forward_list:单链表,只支持++,插入只能在当前位置的后一个插入
array:静态数组,风险:栈溢出;优势:越界读写可以立即判断

新接口

1、cbegin、cend
2、initializer_list的构造(好用)
3、push_xxx/insert/emplace等增加右值引用插入版本意义重大、提高效率
在这里插入图片描述
4、容器增加移动构造和移动赋值,也可以减少拷贝,提高效率

右值引用和移动语义

左值和右值的区别:能否取地址

左值引用

左值:左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址+一般情况可以对它赋值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边左值可以取地址,一般情况下可以修改

一般能取地址就是左值的最主要特征
左值引用底层是指针,用指针存当前左值的地址
const左值引用可以给右值取别名 –

引用的意义:本质是减少拷贝
为什么有了左值引用?还需要右值引用

左值引用
1、解决传参拷贝的问题
2、解决部分返回对象拷贝问题,(除了函数作用域,返回对象还在,可以左值引用返回,减少拷贝)
没有解决的问题,返回对象是局部对象,出了函数作用域生命周期就到了,只能传值返回,就存在拷贝,如果有些对象消耗巨大。
(1)C++11出来之前,编译器已做了一部分优化
拷贝构造+拷贝构造 -> 拷贝构造
临时对象存在那?比较小存在寄存器里,比较大就存在两个栈帧之间的地方

右值引用

之前学的是左值引用:给左值取别名
右值引用:给右值取别名

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

右值引用底层是指针,把当前右值拷贝到栈上的一个临时空间,存储这个临时空间的地址

1、对于右值引用的划分
(1)纯右值:内置类型的右值,eg:10/a+b
(2)将亡值:自定义类型的右值,eg:匿名对象、传值返回函数/obj++/to_string(-123)

注意

左值引用不用给右值取别名,但const左值引用可以给右值取别名
在这里插入图片描述
右值引用不能给左值取别名,但是右值引用可以给move以后的左值取别名
在这里插入图片描述

左值引用解决的问题
(1)传参的拷贝全解决了
(2)传返回值的问题解决了一部分(局部对象即出了作用域销毁的对象,返回的拷贝问题,没有解决
右值引用解决的问题
(1) 解决局部对象返回的拷贝问题

右值引用都是将亡值,对其拷贝的时候直接移动拷贝(直接转移你的资源)

临时对象
比较小:寄存器
比较大:存在两个栈帧之间

移动构造

移动构造是右值引用的运用

思考
1、是不是所有的类都写移动构造
浅拷贝的类不需要移动构造,eg:日期类
深拷贝的类才需要移动构造

c++98对于函数的返回值,由两次拷贝构造 --> 优化成了一次拷贝构造(换行写就无法达成优化条件,变成了:一次拷贝构造+一次赋值)
c++11对于函数的返回值,由一次拷贝构造形成临时对象(右值)+对临时对象的移动构造 --> 优化成一次移动构造

在这里插入图片描述
左值是不敢动的
如果是右值将亡值(内置类型不存在转移资源),可以之间交换资源

在这里插入图片描述

右值引用的移动语义
合二为一,省去中间生产的临时对象
隐式的强行对move(str)识别成右值

问题:想要优化成一次移动构造,但是返回值是左值
(1)对返回值强行move,但是这样的话以前代码要都加一个move吗?这样是不好的,没有向前包容
编译器隐式的将左值的返回值move(可以这么理解,但底层不是这么做的)

对于传值返回有两种情况
1、写成一行
2、写成两行

右值引用的用处
1、传值返回
2、容器的插入

不要轻易对左值move,除非已经确定可以对左值进行move

int&& r = 10;
r++;

右值被右值引用以后,右值引用r的属性是左值

右值引用的本质:把右值拷到栈上的一段临时空间,右值引用是这段临时空间的地址,有可以修改的权限(对于常量会拷贝一份,对于其他还是一个指针指向不回去拷贝)
右值不可用被修改
为什么这么设计?
右值不能改变,那返回值那怎么转移其资源
右值被右值引用后,右值引用的属性是左值,可以被改变,这样资源才能被转移

移动赋值

移动将亡值资源,并且把不要的空间给将亡值,让将亡值帮忙释放

引用折叠/万能引用

有推演的过程
传入& && --> &
传入&& &&–> &&

只有函数模板的T才是推演出来的
对于类模板来说就不可以,类在实例化的时候T就确定了

template<typename T>
void PerfectForward(T&& t)
{}

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

完美转发

move是确定的把左值转成右值

forward:完美转发,保持属性。本身是左值继续保持左值属性;如果本身是右值,转成右值,相当于move一下2

#include<iostream>
#include<utility>

void Func(int& t)
{
    std::cout << "int& t 左值" << std::endl;
}
void Func(int&& t)
{
    std::cout << "int&& t 右值" << std::endl;
}
void Func(const int& t)
{
    std::cout << "const int& t const左值" << std::endl;
}
void Func(const int&& t)
{
    std::cout << "const int&& t const右值" << std::endl;
}

template<typename T>
void PerfectForward(T&& t)
{
    Func(t);
}
int main()
{
    int a = 10;
    PerfectForward(a);
    PerfectForward(1);
    const int b = 20;
    PerfectForward(b);
    PerfectForward(std::move(b));
    return 0;
}

在这里插入图片描述

右值经过右值引用属性就会变成左值,可以通过forward恢复

在这里插入图片描述

在这里插入图片描述

stl变化

c++11之后所有
的容器都增加了移动构造和移动赋值

移动构造函数

编译器默认生成:内置类型完成浅拷贝。自定义类型调用它的移动构造,若它没有实现移动构造就调用它的拷贝构造

移动构造实现条件
1、没有实现异构构造
2、且没有实现析构函数、拷贝函数、拷贝赋值重载中的任意一个

移动赋值函数

移动赋值和移动构造类似

默认成员函数的强制生产

default

禁止生产

例如有些类不期望被拷贝 ---- 单例模式

C++98:声明拷贝函数放到private
C++11:A(const A& aa) = delete
delete

类变成最终类,不能被继承

final

强制派生类的虚函数进行重写

override

可变参数模板

在这里插入图片描述

参数包是0-N个参数

实践中的使用
emplace_back:一次给多个参数

std::list<pair<std::string, std::string>> lt2;
pair<std::string, std::string>kv1("xxxxx", "yyyyy");
lt2.push_back(kv1);
lt2.emplace_back(kv1);
lt2.emplace_back("xxxx", "yyyy");

push_back:

类型是单个值,没啥区别

lambda表达式

底层是仿函数

仿函数 – 降序排序

#include <iostream>
#include <vector>
#include <algorithm>

class Good
{
public:
	Good(std::string name, double price, int number)
		:_name(name), _price(price), _number(number)
	{}
	~Good()
	{}
	std::string _name;
	double _price;
	int _number;
};
struct CompareGreater
{
	bool operator()(const Good& g1, const Good& g2)
	{
		return g1._price > g2._price;
	}
};

int main()
{
	std::vector<Good> v = {{"苹果", 2.5, 40}, {"香蕉", 9.6, 10}, {"猕猴桃", 7, 55} };

	sort(v.begin(), v.end(), CompareGreater());

	return 0;
}

lambda表达式书写格式

[capture-list] (parameters) mutable -> return-type {statement}
  1. lambda表达式各部分说明
  1. [capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。类似仿函数的成员变量
  2. (parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略
  3. mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)
  4. ->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回
    值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推
    导。返回值任何情况都可以省略,可以自动推导
  5. {statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获
    到的变量。

lambda是局部的匿名函数对象

auto add = [](int a, int b)->int {return a+b; };
cout << add(1,2) << endl;
auto add = [](int a, int b){return a+b; };
cout << add(1,2) << endl;
	std::vector<Good> v = {{"苹果", 2.5, 40}, {"香蕉", 9.6, 10}, {"猕猴桃", 7, 55} };

	sort(v.begin(), v.end(), [](const Good& g1, const Good& g2) {
		return g1._price > g2._price; 
	});

捕捉列表

int x = 5, y = 1;
auto swap2[x, y]()mutable{
	int tmp = x;
	cin >> x;
	y = tmp;
};
swap1();

注意

  1. lambda函数总是一个const函数,mutable可以取消其常量性,想要改变x和y的值必须添加mutable,而添加mutable必须写参数列表
  2. 上述x和y并没有改变,因为是拷贝的x和y,并没有对原来的x和y进行改变
int x = 5, y = 1;
auto swap2[&x, &y]{
	int tmp = x;
	cin >> x;
	y = tmp;
};
swap1();

上述&是传引用,不是取地址

传值捕捉当前域的所有对象

int x = 5, y = 1, m = 2, n = 6;
auto swap2[=]{
	return x+y*m-n;
};

传引用捕捉当前域的所有对象

int x = 5, y = 1, m = 2, n = 6;
auto swap2[&]{
	return x+y*m-n;
};

混合捕捉当前域的所有对象
m是传值捕捉,其他传引用

int x = 5, y = 1, m = 2, n = 6;
auto swap2[&, m]{
	return x+y*m-n;
};

两个lambda即使实现方法意义,但是类也不同


http://www.kler.cn/a/455072.html

相关文章:

  • CSES-1687 Company Queries I(倍增法)
  • 游戏开发-UE4高清虚幻引擎教程
  • springboot 工程使用proguard混淆
  • LeetCode 83 :删除排链表中的重复元素
  • Java中使用四叶天动态代理IP构建ip代理池,实现httpClient和Jsoup代理ip爬虫
  • ArrayList源码解析
  • 大模型笔记!以LLAMA为例,快速入门LLM的推理过程
  • Vue异步处理、异步请求
  • 无人零售 4G 工业无线路由器赋能自助贩卖机高效运营
  • 【基础】卒的遍历(DFS)
  • dockfile 配置 /etc/apt/source.list.d/debian.list 清华镜像
  • 记录一个制作Fortran的docker镜像
  • 【NODE】01-fs和path常用知识点
  • 【量化策略】波动指数-用Python检测范围和趋势市场
  • Django 管理命令中使用 `logging` 和 输出样式
  • openGauss与GaussDB系统架构对比
  • SpringBoot 依赖之Spring Web
  • 随机游走(Random Walk)
  • 「瑞仕云曜璟庭」多轨交通+成熟配套 杨浦滨江宜居之高地
  • 《第三期(先导课)》之《Python工程应用》
  • 京东零售数据可视化平台产品实践与思考
  • 突破传统,探索单页网站的强大潜力!
  • 论文DiffBP: generative diffusion of 3D molecules for target protein binding
  • [按键精灵IOS安卓版][脚本基础知识]按键post基本写法
  • 适配模式,桥接模式,组合模式,装饰模式和代理模式
  • 利用 deepin-IDE 的 AI 能力,我实现了文件加密扩展