C++11详解(一) -- 列表初始化,右值引用和移动语义

文章目录

  • 1.列表初始化
    • 1.1 C++98传统的{}
    • 1.2 C++11中的{}
    • 1.3 C++11中的std::initializer_list
  • 2.右值引用和移动语义
    • 2.1左值和右值
    • 2.2左值引用和右值引用
    • 2.3 引用延长生命周期
    • 2.4左值和右值的参数匹配问题
    • 2.5右值引用和移动语义的使用场景
      • 2.5.1左值引用主要使用场景
      • 2.5.2移动构造和移动赋值
      • 2.5.3右值引用和移动语义解决传值返回问题
        • 2.5.1右值对象构造,只有拷贝构造,没有移动构造的场景->拷贝构造
        • 2.5.2右值对象构造,有拷贝构造,也有移动构造的场景->移动构造
        • 2.5.3右值对象赋值,只有拷贝构造和拷贝赋值,没有移动构造和移动赋值的场景->拷贝构造和拷贝赋值
        • 2.5.4右值对象赋值,既有拷贝构造和拷贝赋值,也有移动构造和移动赋值的场景->移动构造和移动赋值
  • 3.面试经常问到的

1.列表初始化

1.1 C++98传统的{}

C++98的{}主要支持数组结构体的初始化

struct Hello
{int _a;int _b;
};int main()
{int a[] = { 1,2,3,4,5 };int b[5] = { 0 };Hello c = { 1,2 };return 0;
}

1.2 C++11中的{}

  1. C++11规定了一切对象都可以用{}初始化,{}初始化也叫列表初始化
  2. 内置类型可以用{}初始化
  3. C++98支持单参数的类型转换,也可以不用{}
Date d3 = { 2025 };// C++11
Date d4 = 2025;// C++98
string s = "11111";
// 单参数的支持隐式类型转换
// 不支持,只有{}初始化才能省略=
// Date d7 2025;vector<Date> v;
v.push_back(d5);// 有名对象
v.push_back(Date( 2025,1,2 ));// 匿名对象
v.push_back({ 2025,1,1 });// {}map<string, string> dict; 
dict.insert({ "string","字符串" });
class Date
{
public:Date(int year = 1, int month = 1, int day = 1):_year(year), _month(month), _day(day){cout << "Date(int year, int month, int day)" << endl;}Date(const Date& d):_year(d._year), _month(d._month), _day(d._day){cout << "Date(const Date& d)" << endl;}
private:int _year;int _month;int _day;
};int main()
{// 1. 内置类型int x1 = { 2 };// 2.自定义类型,构造,隐式类型转换,构造 + 拷贝构造// 优化为了直接构造Date d1 = { 2025,1,12 };// 直接调用构造Date d10(2025,1,1);// d2引用的是{2025,1,15}构造的临时对象const Date& d2 = {2025,1,15};// 可以省略掉=Hello p{ 1,2 };int a{ 2 };Date d5{ 2025,1,1 };const Date& d6{ 2025,1,2 };return 0;
}

1.3 C++11中的std::initializer_list

  1. ⼀个vector对象,我想⽤N个值去构造初始化,那么我们得实现很多个构造函数才能⽀持,因为参数的个数不同
    vector v1 ={1,2,3};
    vector v2 = {1,2,3,4,5};
  2. C++11库中提出了⼀个std::initializer_list的类底层是一个数组,将数据拷贝过来,std::initializer_list中有两个指针,分别指向数组的开始和结束
  3. std::initializer_list支持迭代器遍历
  4. 有了std::initializer_list就可以进行多个值的初始化,比如{x1,x2,x3…}
vector<int> v1 = {1,2,3,4};
vector<int> v2 = {10,20,30,1,2,3,4};
const vector<int>& v3 = {10,20,30,1,2,3,4};// {}初始化可以省略=
vector<int> v1{1,2,3,4};
vector<int> v2{10,20,30,1,2,3,4};
const vector<int>& v3{10,20,30,1,2,3,4};// initializer_list 构造
vector<int> v4({1,2,3,4,5,6});// a数组的指针和il1的两个指针都在栈上,数组也在栈上
// a数组和这两个指针的位置非常接近
initializer_list<int> il1 = {1,2,3};
int a[] = {1,2,3};
  1. map的initializer_list的初始化
    外层是initializer_list,内层是pair的键值对的初始化
// initializer_list + {}pair初始化的隐式类型转换
map<string, string> dict = { {"sort","kai"},{"string","men"} };

2.右值引用和移动语义

1. C++98中的引用是左值引用,C++11中有了右值引用

type & x 左值引用
type && y 右值引用

2.1左值和右值

1.左值是一个表示数据的表达式(如变量名解引用的指针),一般是有持久状态,存储在内存中,我们可以获取它的地址,左值可以出现赋值符号的左边,也可以出现在赋值符号右边。定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址
2.右值也是⼀个表示数据的表达式,要么是字面值常量、要么是表达式求值过程中创建的临时对象等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址
3.区分左值和右值的重要特点就是是否可以取地址

int main()
{// 1.左值 p,b,c,*p,s,s[0]// 变量int* p = new int(0);int b = 1;const int c = b;// 解引用*p = 10;// 有名对象string s("111111");// 函数的返回类型是引用s[0] = 'x';// 2.右值 10 x + y fmin(x,y) string("111")// 右值通常在寄存器中或者#define直接替换了// 所以不能够直接取地址int x = 0, y = 0;// 字面量常量// 10// 表达式的返回值x + y;// 函数的返回值是存在寄存器中的或是临时对象的拷贝fmin(x, y);// 匿名对象string("111");return 0;
}

2.2左值引用和右值引用

1. Type& r1 = x; Type&& rr1 = y;
第一个语句就是左值引用,左值引用就是给左值取别名;第二个就是右值引用,右值引用就是给右值取别名

左值引用取别名
int*& r1 = p;
int& r2 = b;
int& r6 = *p;
const int& r3 = c;
string& r4 = s;
char& r5 = s[0];右值引用取别名
int&& p1 = 10;
int&& p2 = x + y;
int&& p3 = fmin(x, y);
string&& p4 = string("111");

2. 左值引用不能直接引用右值,但是const左值引用可以引用右值,因为右值通常具有常性

对上面的解释
1.右值到左值要权限放大(但是权限不能放大)
2.权限可以平移// 左值引⽤不能直接引⽤右值,但是const左值引⽤可以引⽤右值
const int& rx1 = 10;
const double& rx2 = x + y;
const double& rx3 = fmin(x, y);
const string& rx4 = string("11111");

3.右值引用不能直接引用左值,但是右值引用可以引用move(左值)

// 右值引⽤不能直接引⽤左值,但是右值引⽤可以引⽤move(左值)
int&& rrx1 = move(b);
int*&& rrx2 = move(p);
int&& rrx3 = move(*p);
string&& rrx4 = move(s);
// move的底层其实是强制类型转换
// 把左值强转为右值(s有名对象为左值)
string&& rrx5 = (string&&)s;

move实际是一个函数模版,其实也涉及到了引用折叠,后面会细讲
在这里插入图片描述

// (左值)强转之后不会改变本身的属性
// 强转之后再使用左值,还是左值本身的属性
// 用的只是临时对象
// b、r1、rr1都是变量表达式,都是左值
cout << &b << endl;
cout << &r1 << endl;
cout << &rr1 << endl;int i = 1;
int* pi = (int*)i;
i还是int类型不会改变类型

4. 需要注意的是变量表达式都是左值属性,也就意味着一个右值被右值引用绑定后,右值引用变量变量表达式的属性是左值

左值引用的属性是左值
右值引用的属性也是左值
int&& rr1 = 10;
引用完rr1的属性变为左值
rr1的属性是左值,所以不能被右值引用绑定,除非move一下
int& rr6 = rr1;// 不报错
int&& rrx6 = rr1;// 报错
int&& rrx6 = move(rr1);

5. 在语法层面,左值引用和右值引用都是取别名,不开空间;在底层都是指针实现的

2.3 引用延长生命周期

1. 可以延长临时对象和匿名对象的生命周期,临时对象和匿名对象的生命周期只在当前的一行

std::string s1 = "Test";
const左值引用延长生命周期
const std::string& r1 = s1 + s1;
右值引用延长生命周期
std::string&& r2 = s1 + s1;
延长到r1和r2使用完

2.4左值和右值的参数匹配问题

1.C++98中,我们实现⼀个const左值引用作为参数的函数,那么实参传递左值和右值都可以匹配。

template<class T>
void func(const T& x)
{}
传左值,权限缩小,左值传左值
传右值,权限平移,右值传const左值

2. C++11以后,分别重载左值引用、const左值引用、右值引用作为形参的f函数,那么实参是左值会
匹配f(左值引用),实参是const左值会匹配f(const 左值引用),实参是右值会匹配f(右值引用),也就是编译器会调用最匹配的类型,如果没有右值引用,会调用const左值引用

void f(int& x)
{std::cout << "左值引用重载 f(" << x << ")\n";
}void f(const int& x)
{std::cout << "到 const 的左值引用重载 f(" << x << ")\n";
}void f(int&& x)
{std::cout << "右值引用重载 f(" << x << ")\n";
}int main()
{int i = 1;const int ci = 2;f(i); // 调用 f(int&)f(ci); // 调用 f(const int&)f(3); // 调用 f(int&&),如果没有 f(int&&) 重载则会调用 f(const int&)f(std::move(i)); // 调用 f(int&&)return 0;
}

3. 右值引用变量在用于表达式时属性是左值

    右值引用本身的属性是左值int&& x = 1;f(x);调用f(int& x)f(std::move(x));调用f(int&& x)

2.5右值引用和移动语义的使用场景

2.5.1左值引用主要使用场景

1.左值引用的主要场景是在函数中,左值引用传参左值引用传返回值减少拷贝,同时左值引用传参在函数中修改形参可以改变实参,传引用返回可以修改返回对象
2.左值引用已经解决大多数场景的拷贝效率问题,但是有些场景不能使用传左值引用返回,如addStrings和generate函数里面返回的是局部对象,C++98中的解决方案只能是被迫使用输出型参数解决(传值返回)。
3.那么C++11以后这里可以使用右值引用做返回值解决吗?显然是不可能的,因为这里的本质是返回对象是一个局部对象,函数结束这个对象就析构销毁了,右值引用返回也无法概念对象已经析构销毁的事实。

class Solution 
{
public:// 传值返回需要拷⻉string addStrings(string num1, string num2) {string str;int end1 = num1.size() - 1, end2 = num2.size() - 1;// 进位int next = 0;while (end1 >= 0 || end2 >= 0){int val1 = end1 >= 0 ? num1[end1--] - '0' : 0;int val2 = end2 >= 0 ? num2[end2--] - '0' : 0;int ret = val1 + val2 + next;next = ret / 10;ret = ret % 10;str += ('0' + ret);}if (next == 1)str += '1';reverse(str.begin(), str.end());return str;}
};class Solution 
{
public:
// 这⾥的传值返回拷⻉代价就太⼤了
vector<vector<int>> generate(int numRows) 
{vector<vector<int>> vv(numRows);for(int i = 0; i < numRows; ++i){vv[i].resize(i+1, 1);}for(int i = 2; i < numRows; ++i){for(int j = 1; j < i; ++j){vv[i][j] = vv[i-1][j] + vv[i-1][j-1];}}return vv;
}
};

2.5.2移动构造和移动赋值

右值引用如何解决返回的对象是局部变量的问题?
1. 移动构造函数是一种构造函数,类似拷贝构造,要求第一个参数必须是类类型的引用,第一个参数必须是右值引用,拷贝构造是左值引用,如果有其他参数,必须有缺省值
2. 移动赋值是一个赋值运算符的重载,他跟拷贝赋值构成函数重载,类似拷贝赋值函数,移动赋值函数要求第一个参数是该类类型的引用,但是不同的是要求这个参数是右值引用
3. 对于像string/vector这样的深拷贝的类或者包含深拷贝的成员变量的类,移动构造和移动赋值才有意义,因为移动构造和移动赋值的第一个参数都是右值引用的类型,它的本质是要“窃取”引用的右值对象的资源,而不是像拷贝构造和拷贝赋值那样去拷贝资源,从提高效率

void swap(string& ss)
{::swap(_str, ss._str);::swap(_size, ss._size);::swap(_capacity, ss._capacity);
}// 移动构造
string(string&& s)
{// 右值引用本身具有左值的属性// 因为要和this交换,左值可以修改值,右值不可以修改值cout << "string(string&& s) -> 移动构造" << endl;swap(s);// 交换需要的资源,转移掠夺你的资源
}

在这里插入图片描述

int main()
{string s1("111");string s2 = s1;string s3 = string("222");// s1本来的地址是xxxa510,现在s5的地址是a510// 说明移动构造掠夺了s1的资源给了s5string s5 = move(s1);return 0;
}

在这里插入图片描述

2.5.3右值引用和移动语义解决传值返回问题

2.5.1右值对象构造,只有拷贝构造,没有移动构造的场景->拷贝构造

1. vs2019debug下,左边为不优化的场景,传值返回会发生拷贝构造,会产生临时对象,临时对象再拷贝构造ret,右边的场景是直接优化为一次拷贝构造
2.vs2019release和vs2022下,会直接将str对象的构造,str拷贝构造临时对象,临时对象拷贝构造ret对象,合三为一,变为直接构造。本质就是str对象本质是ret对象的引用,底层是指针,str对象和ret对象的地址是一样的,所以就没有拷贝,没有构造,只有引用了
3. Linux下也是和2022一样的,编译时用 g++ test.cpp -fno-elide-constructors 的方式关闭构造优化,就是两次拷贝构造

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

在这里插入图片描述

2.5.2右值对象构造,有拷贝构造,也有移动构造的场景->移动构造

1. 传值返回会被编译器识别为右值,图2展示了vs2019 debug环境下编译器对拷贝的优化,左边为不优化的情况下,两次移动构造,右边为编译器优化的场景下连续步骤中的拷贝合二为一变为一次移动构造
2. 需要注意的是在vs2019的release和vs2022的debug和release,会直接将str对象的构造,str移动构造临时对象,临时对象移动构造ret对象,合三为一,变为直接构造。

在这里插入图片描述

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

2.5.3右值对象赋值,只有拷贝构造和拷贝赋值,没有移动构造和移动赋值的场景->拷贝构造和拷贝赋值

1. 左图不优化,一次拷贝构造,一次拷贝赋值,右图优化为直接拷贝赋值,str是临时对象的别名,直接用临时对象拷贝赋值ret

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

2.5.4右值对象赋值,既有拷贝构造和拷贝赋值,也有移动构造和移动赋值的场景->移动构造和移动赋值

1. 需要注意的是在vs2019的release和vs2022的debug和release,下面代码会进一步优化,直接构造要返回的临时对象,str本质是临时对象的引用,底层角度用指针实现。运行结果的角度,我们可以看到str的析构是在赋值以后,说明str就是临时对象的别名。(临时对象的生命周期只存在于当前行,赋值完之后就会销毁)
2. 如果是传值返回并且是右值对象,会移动你的资源,不用拷贝了

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

3.面试经常问到的

1. 左值引用和右值引用的最终目的是减少拷贝提高效率
2.左值引用还可以修改参数和返回值,比如输出型参数(在函数体内修改参数可以影响实参)和operator

在这里插入图片描述
3. 左值引用的不足:
部分函数返回场景,只能传值返回,不能左值引用返回,比如当前函数的局部对象,出了当前函数的作用域,生命周期就到了,就销毁了,不能左值引用返回,只能传值返回
解决方案1,2,3:
1.不用返回值,用输出型参数解决问题,不足:牺牲了可读性
2.编译器的优化
3.右值引用和移动语义

更早的编译器只能使用这种输出型参数,避免大量拷贝数据

class Solution 
{
public:void generate(int numRows, vector<vector<int>>& vv) {vector<vector<int>> vv(numRows);for (int i = 0; i < numRows; ++i){vv[i].resize(i + 1, 1);}for (int i = 2; i < numRows; ++i){for (int j = 1; j < i; ++j){vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];}}}
};int main()
{// vector<vector<int>> ret = Solution().generate(100);vector<vector<int>> ret;Solution().generate(100, ret);// 输出型参数可以解决这个问题,传引用过去,就不需要拷贝了return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/894646.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

手写MVVM框架-构建虚拟dom树

MVVM的核心之一就是虚拟dom树&#xff0c;我们这一章节就先构建一个虚拟dom树 首先我们需要创建一个VNode的类 // 当前类的位置是src/vnode/index.js export default class VNode{constructor(tag, // 标签名称&#xff08;英文大写&#xff09;ele, // 对应真实节点children,…

【大数据技术】教程03:本机PyCharm远程连接虚拟机Python

本机PyCharm远程连接虚拟机Python 注意:本文需要使用PyCharm专业版。 pycharm-professional-2024.1.4VMware Workstation Pro 16CentOS-Stream-10-latest-x86_64-dvd1.iso写在前面 本文主要介绍如何使用本地PyCharm远程连接虚拟机,运行Python脚本,提高编程效率。 注意: …

pytorch实现门控循环单元 (GRU)

人工智能例子汇总&#xff1a;AI常见的算法和例子-CSDN博客 特性GRULSTM计算效率更快&#xff0c;参数更少相对较慢&#xff0c;参数更多结构复杂度只有两个门&#xff08;更新门和重置门&#xff09;三个门&#xff08;输入门、遗忘门、输出门&#xff09;处理长时依赖一般适…

PAT甲级1032、sharing

题目 To store English words, one method is to use linked lists and store a word letter by letter. To save some space, we may let the words share the same sublist if they share the same suffix. For example, loading and being are stored as showed in Figure …

最小生成树kruskal算法

文章目录 kruskal算法的思想模板 kruskal算法的思想 模板 #include <bits/stdc.h> #define lowbit(x) ((x)&(-x)) #define int long long #define endl \n #define PII pair<int,int> #define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0); using na…

为何在Kubernetes容器中以root身份运行存在风险?

作者&#xff1a;马辛瓦西奥内克&#xff08;Marcin Wasiucionek&#xff09; 引言 在Kubernetes安全领域&#xff0c;一个常见的建议是让容器以非root用户身份运行。但是&#xff0c;在容器中以root身份运行&#xff0c;实际会带来哪些安全隐患呢&#xff1f;在Docker镜像和…

ConcurrentHashMap线程安全:分段锁 到 synchronized + CAS

专栏系列文章地址&#xff1a;https://blog.csdn.net/qq_26437925/article/details/145290162 本文目标&#xff1a; 理解ConcurrentHashMap为什么线程安全&#xff1b;ConcurrentHashMap的具体细节还需要进一步研究 目录 ConcurrentHashMap介绍JDK7的分段锁实现JDK8的synchr…

[ESP32:Vscode+PlatformIO]新建工程 常用配置与设置

2025-1-29 一、新建工程 选择一个要创建工程文件夹的地方&#xff0c;在空白处鼠标右键选择通过Code打开 打开Vscode&#xff0c;点击platformIO图标&#xff0c;选择PIO Home下的open&#xff0c;最后点击new project 按照下图进行设置 第一个是工程文件夹的名称 第二个是…

述评:如果抗拒特朗普的“普征关税”

题 记 美国总统特朗普宣布对美国三大贸易夥伴——中国、墨西哥和加拿大&#xff0c;分别征收10%、25%的关税。 他威胁说&#xff0c;如果这三个国家不解决他对非法移民和毒品走私的担忧&#xff0c;他就要征收进口税。 去年&#xff0c;中国、墨西哥和加拿大这三个国家&#…

九. Redis 持久化-AOF(详细讲解说明,一个配置一个说明分析,步步讲解到位 2)

九. Redis 持久化-AOF(详细讲解说明&#xff0c;一个配置一个说明分析&#xff0c;步步讲解到位 2) 文章目录 九. Redis 持久化-AOF(详细讲解说明&#xff0c;一个配置一个说明分析&#xff0c;步步讲解到位 2)1. Redis 持久化 AOF 概述2. AOF 持久化流程3. AOF 的配置4. AOF 启…

基于Springboot框架的学术期刊遴选服务-项目演示

项目介绍 本课程演示的是一款 基于Javaweb的水果超市管理系统&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的 Java 学习者。 1.包含&#xff1a;项目源码、项目文档、数据库脚本、软件工具等所有资料 2.带你从零开始部署运行本套系统 3.该项目附…

新版231普通阿里滑块 自动化和逆向实现 分析

声明: 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 逆向过程 补环境逆向 部分补环境 …

java-(Oracle)-Oracle,plsqldev,Sql语法,Oracle函数

卸载好注册表,然后安装11g 每次在执行orderby的时候相当于是做了全排序,思考全排序的效率 会比较耗费系统的资源,因此选择在业务不太繁忙的时候进行 --给表添加注释 comment on table emp is 雇员表 --给列添加注释; comment on column emp.empno is 雇员工号;select empno,en…

泰山派Linux环境下自动烧录脚本(EMMC 2+16G)

脚本名字&#xff1a; download.sh 输入./download -h获取帮助信息 &#xff0c;其中各个IMG/TXT烧录的地址和路径都在前几行修改即可 #!/bin/bash# # DownLoad.sh 多镜像烧录脚本 # 版本&#xff1a;1.1 # 作者&#xff1a;zhangqi # 功能&#xff1a;通过参数选择烧录指定镜…

正大杯攻略|分层抽样+不等概率三阶段抽样

首先&#xff0c;先进行分层抽样&#xff0c;确定主城区和郊区的比例 然后对主城区分别进行不等概率三阶段抽样 第一阶段&#xff0c;使用PPS抽样&#xff0c;确定行政区&#xff08;根据分层抽样比例合理确定主城区和郊区行政区数量&#xff09; 第二阶段&#xff0c;使用分…

开源智慧园区管理系统对比其他十种管理软件的优势与应用前景分析

内容概要 在当今数字化快速发展的时代&#xff0c;园区管理软件的选择显得尤为重要。而开源智慧园区管理系统凭借其独特的优势&#xff0c;逐渐成为用户的新宠。与传统管理软件相比&#xff0c;它不仅灵活性高&#xff0c;而且具有更强的可定制性&#xff0c;让各类园区&#…

计算机网络 应用层 笔记1(C/S模型,P2P模型,FTP协议)

应用层概述&#xff1a; 功能&#xff1a; 常见协议 应用层与其他层的关系 网络应用模型 C/S模型&#xff1a; 优点 缺点 P2P模型&#xff1a; 优点 缺点 DNS系统&#xff1a; 基本功能 系统架构 域名空间&#xff1a; DNS 服务器 根服务器&#xff1a; 顶级域…

人类心智逆向工程:AGI的认知科学基础

文章目录 引言:为何需要逆向工程人类心智?一、逆向工程的定义与目标1.1 什么是逆向工程?1.2 AGI逆向工程的核心目标二、认知科学的四大支柱与AGI2.1 神经科学:大脑的硬件解剖2.2 心理学:心智的行为建模2.3 语言学:符号与意义的桥梁2.4 哲学:意识与自我模型的争议三、逆向…

游戏引擎学习第86天

仓库: https://gitee.com/mrxiao_com/2d_game_2 回顾 继续之前的工作。 昨天已经让地形系统基本运行起来&#xff0c;但目前仍然需要进一步完善&#xff0c;使其能够生成更多的地块。目前的情况是&#xff0c;仅仅有一个地块位于中心区域&#xff0c;而真正需要的是让地块覆盖…

Python在线编辑器

from flask import Flask, render_template, request, jsonify import sys from io import StringIO import contextlib import subprocess import importlib import threading import time import ast import reapp Flask(__name__)RESTRICTED_PACKAGES {tkinter: 抱歉&…