【高级程序设计语言C++】右值引用

news2024/9/21 2:34:21

  • 1. 左值引用和右值引用
  • 2. 修改的右值
  • 3. 左值引用和右值引用的比较
    • 3.1. 左值引用总结
    • 3.2. 右值引用总结
  • 4. 右值引用使用场景和意义
  • 5. 完美转发

1. 左值引用和右值引用

  1. 什么是左值?什么是左值引用?

左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址+可以对它赋

,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。定义时const修饰符后的左

值,不能给他赋值,但是可以取它的地址。左值引用就是给左值的引用,给左值取别名

  1. 什么是右值?什么是右值引用?

右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引

用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能

取地址。右值引用就是对右值的引用,给右值取别名。

  1. 当我们讨论C++中的左值和右值时,可以将其类比为“物品”。

左值可以理解为“可修改的物品”。它是具有持久性和身份的物品,我们可以对其进行赋值、取地址和修改操作。就像我们拿着一个实际的物品,可以一直使用它,甚至可以改变它的状态

右值可以理解为“临时的物品”。它是没有持久性和身份的物品,通常是临时产生的中间结果。我们不能对其进行赋值、取地址或修改操作,因为它们即将被丢弃。就像我们拿着一个临时使用的物品,用完之后就会丢弃掉。

举个例子来说明:

假设我们有一个盒子,里面装着一些物品。左值就像是我们拿起一个物品,可以一直使用它,甚至可以改变它的状态。右值就像是我们拿起一个临时使用的物品,用完之后就会丢弃掉。

在C++中,我们可以将一个左值赋值给左值引用,而右值则可以赋值给右值引用。这是因为左值引用期望一个持久的物品,而右值引用期望一个临时的物品。

总结起来,左值是可修改的物品,右值是临时的物品。这是一个简化的描述,但希望能帮助你理解左值和右值的基本概念。

void modifyValue(int& value) 
{
    value = 10; // 修改左值的值
}

int main() 
{
    int x = 5; // 左值 x

    modifyValue(x); // 传递左值给函数进行修改
    std::cout << "Modified value: " << x << std::endl;

    int&& y = 20; // 右值 y
    std::cout << "Original value: " << y << std::endl;

    return 0;
}

在上面的代码中,我们定义了一个函数modifyValue,它接受一个左值引用作为参数,并修改传入的左值的值。在main函数中,我们声明了一个左值x,并将其传递给modifyValue函数进行修改。最后,我们输出修改后的值。另外,在main函数中,我们还声明了一个右值引用y,并将一个临时的右值20赋值给它。然后,我们输出原始的右 值。

通过这段代码,我们可以看到左值可以被修改,而右值通常是临时的、不可修改的。这展示了左值和右值的基本特性。

2. 修改的右值

在上面的描述中,我们知道右值通常是临时的、不可修改的。注意说的是通常!

来看这样一段代码:

int main() 
{
    int&& rr1 = 10;
    int* address = &rr1;
    *address = 20;
    cout << rr1 << endl;
    return 0;
}

上面的代码会输出什么呢?

输出结果:

img

那为什么会这样子呢?

这是因为在将字面量10绑定到右值引用rr1时,编译器会创建一个临时的整数对象,并将其值设置为10。这个临时的整数对象具有与rr1相同的生命周期,因此我们可以通过指针修改它的值。然而,需要注意的是,这种修改右值的方式并不是右值引用的主要用途。右值引用主要用于实现移动语义和完美转发等特性,以提高性能和灵活性。在实际中,我们很少直接修改右值,而是将其用于特定的语义操作。如果不想rr1被修改,可以用const int&& rr1 去引用。

3. 左值引用和右值引用的比较

3.1. 左值引用总结

  1. 左值引用只能引用左值,不能引用右值。
void modifyValue(int& value) 
{
    // 在函数内部修改value的值
    value = 10;
}

int main() 
{
    int x = 5;

    modifyValue(x); // 传递左值
    modifyValue(20); // 错误,无法传递右值

    return 0;
}

在这个示例中,我们定义了一个函数modifyValue,它接受一个左值引用参数value。在函数内部,我们将value的值修改为10。

在main函数中,我们分别调用modifyValue函数,并传递一个左值x和一个右值20作为参数。对于左值x,可以被左值引用接受,并在函数内部被修改。但是对于右值20,无法被左值引用接受,因为左值引用只能引用左值

所以,当我们尝试传递右值给左值引用时,编译器会报错

运行结果:

img

  1. 但是const左值引用既可引用左值,也可引用右值。
void modifyValue(const int& value) 
{
    // value是const左值引用,可以引用左值和右值
    // 但是在函数内部无法修改value的值,因为它是const的
    cout << "Value: " << value << endl;
}

int main() 
{
    int x = 5;
    const int y = 10;

    modifyValue(x); // 传递左值
    modifyValue(20); // 传递右值

    return 0;
}

在这个示例中,我们定义了一个函数modifyValue,它接受一个const左值引用参数value。在函数内部,我们将value的值输出到控制台上。

在main函数中,我们分别调用modifyValue函数,并传递一个左值x和一个右值20作为参数。无论是左值还是右值,都可以被const左值引用接受。在函数内部,我们无法修改value的值,因为它是const的。

这个示例展示了const左值引用可以引用左值和右值的特性。这在某些情况下是很有用的,例如当我们希望以只读方式访问参数时。

运行结果:

img

3.2. 右值引用总结

  1. 右值引用只能右值,不能引用左值。
void modifyValue(int&& value) 
{
    // 在函数内部修改value的值
    value = 10;
}

int main() 
{
    int x = 5;

    modifyValue(x); // 错误,无法传递左值
    modifyValue(20); // 传递右值

    return 0;
}

在这个示例中,我们定义了一个函数modifyValue,它接受一个右值引用参数value。在函数内部,我们将value的值修改为10。

在main函数中,我们尝试分别调用modifyValue函数,并传递一个左值x和一个右值20作为参数。对于左值x,无法被右值引用接受,因为右值引用只能引用右值。但是对于右值20,可以被右值引用接受,并在函数内部被修改。

所以,当我们尝试传递左值给右值引用时,编译器会报错

  1. 但是右值引用可以move以后的左值
void modifyVector(vector<int>&& vec)
{
    // 在函数内部修改vec的值
    vec.push_back(10);
}

int main() 
{
    vector<int> v = { 1, 2, 3 };

    modifyVector(move(v)); // 传递move后的左值

    for (auto e : v)
    {
        cout << e << " ";
    }
    return 0;
}

在这个示例中,我们定义了一个函数modifyVector,它接受一个右值引用参数vec。在函数内部,我们使用push_back函数向vec中添加一个元素。

在main函数中,我们创建了一个名为v的vector对象,并初始化为{1, 2, 3}。然后,我们将v传递给modifyVector函数,通过调用move函数将v转换为右值。这样,我们可以将move后的左值传递给右值引用参数,并在函数内部修改它

这个示例展示了右值引用可以引用move后的左值的特性。通过使用move函数,我们可以将左值转换为右值,并在函数内部修改它,而无需进行深拷贝操作。

4. 右值引用使用场景和意义

下面我们来看看左值引用的短板,右值引用是如何补齐这个短板的!

int& getRef() 
{
    int x = 5;
    return x; // 返回局部变量的引用,悬空引用
}

int main() 
{
    int& ref = getRef(); // 悬空引用
    // ...
}

在这个例子中,getRef函数返回了一个局部变量x的引用,但当getRef函数结束时,x的生命周期结束,引用ref成为了悬空引用,访问它将导致未定义行为。**但是当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回,只能传值返回。**使用传值返回,会产生拷贝的问题,影响效率。

img

所以此时右值引用就有了它的价值

int&& getRef() 
{
    int x = 5;
    return move(x); // 返回右值引用,延长生命周期
}

int main() 
{
    int&& ref = getRef(); // 正确,延长生命周期
    // ...
}

在这个例子中,getRef函数返回了一个右值引用,通过使用move函数将局部变量x转换为右值引用,并延长了其生命周期。因此,在main函数中,我们可以安全地使用右值引用ref。

img

下面来看一段代码,看看右值引用在拷贝方面的好处。

class MyString 
{
public:
    MyString(const std::string& str) : m_data(str) 
    {
        std::cout << "拷贝构造函数" << std::endl;
    }

    MyString(std::string&& str) : m_data(move(str)) 
    {
        std::cout << "移动构造函数" << std::endl;
    }

    // 拷贝赋值运算符
    MyString& operator=(const MyString& other) 
    {
        std::cout << "拷贝赋值运算符" << std::endl;
        if (this != &other) 
        {
            m_data = other.m_data;
        }
        return *this;
    }

    // 移动赋值运算符
    MyString& operator=(MyString&& other) 
    {
        std::cout << "移动赋值运算符" << std::endl;
        if (this != &other) 
        {
            m_data = std::move(other.m_data);
        }
        return *this;
    }

    const std::string& getData() const 
    {
        return m_data;
    }

private:
    std::string m_data;
};

int main() 
{
    std::string str = "Hello, world!";
    MyString myStr1(str); // 使用拷贝构造函数,将str拷贝到myStr1中
    MyString myStr2(std::move(str)); // 使用移动构造函数,将str的内容移动到myStr2中

    std::cout << "myStr1: " << myStr1.getData() << std::endl; // 输出: Hello, world!
    std::cout << "myStr2: " << myStr2.getData() << std::endl; // 输出: Hello, world!

    return 0;
}

在这个例子中,我们定义了一个MyString类,它包含一个std::string成员变量来管理字符串数据。MyString类提供了拷贝构造函数、移动构造函数、拷贝赋值运算符和移动赋值运算符。

在main函数中,我们首先创建了一个std::string对象str,然后使用拷贝构造函数将其拷贝到myStr1对象中。这里的拷贝构造函数会创建一个新的std::string对象,并将str的内容拷贝到新对象中。

接下来,我们使用移动构造函数将str的内容移动到myStr2对象中。这里的移动构造函数使用了右值引用,并通过std::move将str转换为右值引用。这样做的好处是,移动构造函数不需要创建新的对象,而是直接将str的内容移动到myStr2的成员变量中。这样可以避免不必要的拷贝操作,提高性能和效率。

最后,我们输出myStr1和myStr2对象中的字符串内容,可以看到它们都是Hello, world!。这证明了在拷贝时使用右值引用可以避免不必要的拷贝操作,并且不会影响最终结果。

运行结果:

img

  1. 48行的代码,传的是左值,调用的是拷贝构造函数

img

  1. 49行的代码,传的是右值,调用的是移动构造函数

img

从调试的角度看一看str的变化。

img

img

然后str从左值变为右值,传给myStr2的移动构造函数,造成了str的消亡。

img

此时没有产生拷贝,而是将str的内容直接移动到了myStr2的成员中,相当于减少了拷贝,提高了效率。要知道C++的类型是很多的,如果是map或者是unordered_map的拷贝,将会影响效率。

注意:此时将str移动到myStr2的成员中去,str自己本身是会嘎了的。你就把str当成一个武林高手,但是时日无多了,刚好遇到了一个小伙myStr2,str临死前把一身武功都传给了myStr2,然后str就嘎了。

5. 完美转发

有时候,我们有一个函数,它接收一个参数,并且我们希望将这个参数传递给另一个函数,但我们不知道这个参数的具体类型。我们希望能够以一种通用的方式将参数转发给另一个函数,而不需要为每种可能的类型编写不同的转发函数。

这就是完美转发的概念。完美转发允许我们将参数以原样转发给另一个函数,无论参数是左值还是右值。它可以保留参数的值类别(左值或右值),并将其传递给适当的函数。

在C++中,我们可以使用模板和引用折叠来实现完美转发。引用折叠是一种特殊的规则,它允许我们在模板函数中保留参数的值类别。

template <typename T>
void forwardFunction(T&& arg) 
{
    anotherFunction(std::forward<T>(arg));
}

在这个例子中,我们定义了一个模板函数forwardFunction,它接收一个参数arg。这里的T&&是一个右值引用折叠的语法,它可以接收任意类型的参数,不管是左值还是右值,并保留参数的值类别

在forwardFunction中,我们使用std::forward来实现完美转发。std::forward是一个模板函数,它接收一个参数,并将其转发为对应的左值引用或右值引用。这里的T是模板类型参数,它会根据参数的值类别来确定是转发为左值引用还是右值引用。

通过使用std::forward,我们可以保留参数的值类别,并将其传递给anotherFunction,实现了完美转发。

总之,完美转发允许我们以一种通用的方式将参数转发给其他函数,无论参数是左值还是右值。这可以提高代码的重用性和灵活性,避免了为每种可能的类型编写不同的转发函数。

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

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

相关文章

计算机网络 概述部分

目录 计算机网络在信息时代的作用 计算机网络的重要特征 网络&#xff0c;internet,Internet的区别 局域网 广域网的区别 网络协议的分层 计算机网络在信息时代的作用 计算机网络的重要特征 连通性&#xff1a;彼此联通&#xff0c;交换信息 共享性&#xff1a;信息共享…

精准运营,智能决策!解锁天翼物联水利水务感知云

面向智慧水利/水务数字化转型需求&#xff0c;天翼物联基于感知云平台创新能力&#xff0c;提供涵盖水利水务泛协议接入、感知云水利/水务平台、水利/水务感知数据治理、数据看板在内的水利水务感知云服务&#xff0c;构建水利水务感知神经系统新型数字化底座&#xff0c;实现智…

WebGpu VS WebGL

推荐&#xff1a;使用 NSDT场景编辑器 助你快速搭建3D应用场景 WEBGPU VS. WEBGL 粗略地概述一下WebGPU与WebGL的不同之处是很有用的。在不涉及太多复杂的技术细节的情况下&#xff0c;两者的整体设计大致如下&#xff1a; WebGL和OpenGL一样&#xff0c;涉及许多单独的函数调…

Docker基础入门:容器数据卷与Dockerfile构建镜像(发布)

Docker基础入门&#xff1a;容器数据卷与Dockerfile构建镜像&#xff08;发布&#xff09; 一、docker容器数据卷1.1、使用docker容器数据卷1.2、具名挂载、匿名挂载1.3、如何确定是具名挂载还是匿名挂载 二、使用dockerfile2.1 初识Dockerfile2.2 Dockerfile构建过程2.3 Docke…

Nacos 未授权访问(CVE-2021-29441)

Nacos 未授权访问(CVE-2021-29441) Nacos是阿里巴巴推出的一个新的开源项目。它是一个动态的服务发现、配置管理和服务管理平台&#xff0c;可以更轻松地构建云原生应用程序。致力于帮助发现、配置和管理微服务。Nacos 提供了一套简单易用的功能集&#xff0c;可以快速实现动态…

火山引擎 DataLeap 助你拥有 Notebook 交互式的开发体验

更多技术交流、求职机会&#xff0c;欢迎关注字节跳动数据平台微信公众号&#xff0c;回复【1】进入官方交流群 Notebook 是一种支持 REPL 模式的开发环境。所谓「REPL」&#xff0c;即「读取-求值-输出」循环&#xff1a;输入一段代码&#xff0c;立刻得到相应的结果&#xff…

SAP_ABAP_接口技术_PI实践总结

SAP ABAP顾问能力模型梳理_企业数字化建设者的博客-CSDN博客SAP Abap顾问能力模型https://blog.csdn.net/java_zhong1990/article/details/132469977一、背景说明 1.1 案例介绍 1.1.1 实现OA系统 --> PI系统 --> SAP系统的过程 二、实现步骤 1.2 PI中间件的实现过程 …

LabVIEW | 串口基础【自学】

转载 B站   up&#xff1a;不烧板子 地址&#xff1a;https://www.bilibili.com/read/cv9435378 原博图片不清楚&#xff0c;自己重新跟学截图自留&#xff0c;侵删 文章目录 一、串口基础1.串口发送&#xff08;1&#xff09;简单发送&#xff08;2&#xff09;循环发送&…

第三方ipad电容笔哪个牌子好用?开学好用电容笔推荐

现在&#xff0c;市面上有很多种类型的电容笔&#xff0c;在选择的时候&#xff0c;我们很容易踩雷&#xff0c;比如&#xff0c;我们购买的一些产品在书写过程中&#xff0c;往往会出现断触&#xff0c;或者是防误触功能不起作用。所以我们买东西的时候必须要注意产品配置。对…

87. 扰乱字符串

题目链接&#xff1a;力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 解题思路&#xff1a; 对于给定的两个字符串S和T。 如果S和T的长度不相等&#xff0c;T肯定不是S的扰乱字符串。 如果S和T的长度相等&#xff0c;则可以在某一个随机下标处进行…

空时自适应处理用于机载雷达——波束空间空时自适应处理(Matla代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

[CFI-CTF 2018]powerPacked 题解

脱掉upx壳 逻辑很简单 str"EHK}kanqxgarqygtre" flag"" for i in str:flagchr(ord(i)-2) print(flag) CFI{i_love_powerpc}

ThreadLocal概述

一、概述 ThreadLocal被称为线程局部变量&#xff0c;用于在线程中保存数据。由于在ThreadLocal中保存的数据仅属于当前线程&#xff0c;所以该变量对其他线程而言是隔离的&#xff0c;也就是说该变量是当前线程独有的变量。 ThreadLocal用于在同一个线程间&#xff0c;在不同的…

Prompt GPT推荐社区

大家好&#xff0c;我是荷逸&#xff0c;这次给大家带来的是我日常学习Prompt社区推荐 Snack Prompt 访问地址&#xff1a;http://snackprompt.com Snack Prompt是一个采用的Prompts诱导填空式的社区&#xff0c;它提供了一种简单的prompt修改方式&#xff0c;你只需要输入关…

​Spring Cloud Alibaba与Nacos版本对应关系​

下面是Spring Cloud Alibaba与Nacos版本对应关系 Spring Cloud Alibaba VersionNacos Version2021.0.1.0*1.4.22.2.7.RELEASE2.0.32.2.6.RELEASE1.4.22021.1 or 2.2.5.RELEASE or 2.1.4.RELEASE or 2.0.4.RELEASE1.4.12.2.3.RELEASE or 2.1.3.RELEASE or 2.0.3.RELEASE1.3.32.…

curl请求https|http网站时出现Binary output can mess up your terminal

请求网站时出现​ 那么这里有几种情况 文件本身为二进制文件内容压缩 如果是第一种情况&#xff0c;那么直接保存你要下载的二进制文件&#xff0c;使用 curl https://a.com -o 文件名保存在一个文件中 或者使用 -o -直接输出在终端 curl https://a.com -o -如果你本来访问…

Nacos源码构建报错程序包不存在com.alibaba.nacos.consistency.entity

直接启动 Nacos.java 报错。 Nacos源码构建报错程序包不存在com.alibaba.nacos.consistency.entity 解决方案 执行 mvn compile。 原因 com.alibaba.nacos.consistency.entity包目录是由protobuf在编译时自动生成。可通过mvn compile来自动生成他们。如果使用的是IDEA&…

问道管理:A股连续两日放量上扬,分析称筹码充分交换后或趋势性上涨

利好加持下前一买卖日高开低走后&#xff0c;A股迎来全线暴升&#xff01; 8月29日&#xff0c;三大指数均涨超1%&#xff0c;其间深证成指和创业板指更是涨逾2%&#xff0c;分别上行2.17%、2.82%。值得一提的是&#xff0c;商场上行中科创板股票全线迸发&#xff0c;科创50指…

供水管网安全运行监测,持续保障市民用水安全需求

供水管网是城市供水系统的核心组成部分&#xff0c;安全运行对人民的生活和社会发展至关重要。要持续不断地向城市供应数量充足、质量合格的水&#xff0c;应解决管道爆管问题、管网漏损导致严重的资源浪费等&#xff0c;及时发现管网故障&#xff0c;提高维护效率、降低损失&a…

【数据结构】初识树

目录 一&#xff0c;树的基本概念 1.1树的相关概念 1.2树的表示 二&#xff0c;二叉树的基本概念 2.1特殊的二叉树&#xff1a; 2.2二叉树的性质 2.3二叉树的存储结构 1. 顺序存储 2.链式存储 一&#xff0c;树的基本概念 树是一类重要的非线性数据结…