新C++(14):移动语义与右值引用

news2024/11/26 15:48:24

当你在学习语言的时候,是否经常听到过一种说法,"="左边的叫做左值,"="右边的叫做右值。这句话对吗?从某种意义上来说,这句话只是说对了一部分。

---前言

一、什么是左右值?

通常认为:
左值是一个表示数据的表达式(如变量名或解引用的指针), 我们可以 获取它的地址 + 可以对它赋
值(使用空间) ,左值可以出现赋值符号的左边,也可以出现在等号右边。

右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值等等。右值 可以出现在赋值符号的右边 ,但是 不能出现在赋值符号的左边 右值不能取地址
    //x \ y 都是左值 都可以取地址
    double x = 1.1, y = 2.2;
    int a = 10,b = 20;

    //以下都是右值 都不用取地址
    10;
    x + y;
    func();


二、左右值引用

(1)左值引用

type& x;

在我们学习引用的时候,一定会和C语言的指针联系到一起。我们来看看下面的swap代码吧。

void SwapByPtr(int* a,int* b)
{
    int tmp = *a;
    *a = *b;
    *b = tmp;
}

void SwapByVal(int a, int b)
{
    int tmp = a;
    a = b;
    b = a;
}

结果我想你一定知道的!函数传值与函数传地址是不同的!一个是一份拷贝,一个是记录的地址,可以访问原变量。

但是我们知道,指针是有它的缺陷,如果不是一位资深程序员,甚至你是,也得对指针的使用报以"敬畏之心"。因此,在C++中引入了新的语法,"引用"。虽然它底层仍然是用指针实现的,但是却比指针用起来更加方便。

void SwapByRef(int& a,int& b)
{
    int tmp = a;
    a = b;
    b = a;
}

(2)右值引用

我们时常说"引用","引用",其实都是"左值引用"。为了区别左值引用呢,右值引用的语法格式上是这样的。

type &&;
    int a = 10;
    int& ra = a;         //左值引用

    int&& rra = 10;      //右值引用

(3)左右值引用的特性

左值引用:
①只能引用左值,不能引用右值
②但是const左值引用 可以引用右值也可以引用左值
    int a = 10;
    int& ra = a;          //只能引用左值

    int& rb1 = 10;        //不能引用右值 ×

    //既可以引用左值、也可以引用右值
    const int& rb2 = a;
    const int& rb2 = 10; 
右值引用:
①右值引用只能引用右值,不能引用左值
②标准库中提供move()函数,可以将一个左值变为右值
    int a = 10;
    int&& rra1 = 10;    //只能引用右值 
    int&& rra2 = a;     //不能引用左值 ×
    //move后可以 a变成了右值
    int&& rra3 = std::move(a);

右值不能取地址,但是右值引用能够取地址!!

右值当然没有地址,但是我们给右值取引用时,那么这个右值引用就该有它的地址,并且可以对它引用的对象进行修改。如果你不想允许让对右值引用的值发生改变,请给它+"const"吧。
为什么这么设计呢?这和右值引用的场景有关,也就是我们之后要细讲的。

当然,这很符合我们的预期。


三、左右值引用的应用场景

也许你会疑问,已经有了左值引用,为什么还需要右值引用呢?右值引用一定有它存在必要的场景。在此之前,我们就先来列举列举左值引用的使用场景吧。

左值引用场景:
①函数传参防拷贝。
②函数返回值 引用返回。
//函数传参防拷贝
vector<int>& Func(vector<int>& ret)
{
    ret.push_back(1);
    //...
    //函数引用返回值
    return ret;
}

当要进行左值引用返回时,唯一一个条件时,该对象出了作用域仍然存在!那如果该对象就是在函数体内创建的,出了作用域它就会销毁,但其拷贝的代价又很大。遇到这样的情况,我们应该怎么处理呢?

(1)移动赋值与移动构造

我们首先实现一个to_string的函数,用来将一个数字,转换为自定义字符串。

//to_string函数
    string to_string(int value)
    {
        bool flag = true;
        if (value < 0)
        {
            flag = false;
            value = 0 - value;
        }

        dy::string str;
        while (value > 0)
        {
            int x = value % 10;
            value /= 10;

            str += ('0' + x);
        }
        if (flag == false)
        {
            str += '-';
        }

        std::reverse(str.begin(), str.end());
        return str;
    }
//自定义string 类
    class string
    {
    public:
        typedef char* iterator;
        iterator begin()
        {
            return _str;
        }

        iterator end()
        {
            return _str + _size;
        }

        string(const char* str = "")
            :_size(strlen(str))
            , _capacity(_size)
        {
            _str = new char[_capacity + 1];
            strcpy(_str, str);
        }

        // s1.swap(s2)
        void swap(string& s)
        {
            ::swap(_str, s._str);
            ::swap(_size, s._size);
            ::swap(_capacity, s._capacity);
        }

        // 拷贝构造
        string(const string& s)
        {
            cout << "string(const string& s) -- 深拷贝" << endl;

            string tmp(s._str);
            swap(tmp);
        }

        // 赋值重载
        string& operator=(const string& s)
        {
            cout << "string& operator=(string s) -- 深拷贝" << endl;
            string tmp(s);
            swap(tmp);

            return *this;
        }

        ~string()
        {
            delete[] _str;
            _str = nullptr;
        }

        char& operator[](size_t pos)
        {
            assert(pos < _size);
            return _str[pos];
        }

        void reserve(size_t n)
        {
            if (n > _capacity)
            {
                char* tmp = new char[n + 1];
                strcpy(tmp, _str);
                delete[] _str;
                _str = tmp;

                _capacity = n;
            }
        }

        void push_back(char ch)
        {
            if (_size >= _capacity)
            {
                size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
                reserve(newcapacity);
            }

            _str[_size] = ch;
            ++_size;
            _str[_size] = '\0';
        }

        //string operator+=(char ch)
        string& operator+=(char ch)
        {
            push_back(ch);
            return *this;
        }

        const char* c_str() const
        {
            return _str;
        }
    private:
        char* _str = nullptr;
        size_t _size = 0;
        size_t _capacity = 0; // 不包含最后做标识的\0
    };

我们此时用一个整数,使用to_string函数,得到一个自定义string类型。

int main()
{
    int x = 1234;
    dy::string ret = dy::to_string(x);

    return 0;
}

但是,我们为了一个在to_string函数类,一个出作用域就会销毁的对象,为了得到它其中的资源,就得付出"深拷贝"一份的代价,未免有些太大。

如果仅仅是拷贝内置类型来说,那么微乎其微,但如果深拷贝对象是map、set呢?也许你仅仅只需要得到这个即将销毁对象的"根节点"即可,而非是在return返回时,让该对象拷贝构造临时对象而付出巨大代价。

秉持这样的想法,我们为该自定义类设计一个新的拷贝构造函数。

        //移动赋值与移动构造
        string(string&& s)
        {
            cout << "string(string&& s): 移动构造" << endl;
            swap(s);
        }

        string& operator=(string&& s)
        {
            cout << "string& operator=(string s) 移动赋值" << endl;
            swap(s);
            return *this;
        }

我们为该类增加这两个函数,并再次运行相同的代码。

这是为什么??该对象的"拷贝"没有选择去调用"深拷贝"?那么,我们不得不搞懂以下的三个问题!

能够搞懂上述的问题,我们也就能够预知编译器会选择怎样做。

那如果是以下这样的调用,会打印出什么呢?

int main()
{    
    dy::string ret2;
    ret2 = dy::to_string(123);
    return 0;
}

小结:

左值引用与右值引用减少拷贝的方式是不一样的:

左值引用是直接起作用的,就是给一个变量取别名。

右值引用是间接起作用的,利用移动构造、移动赋值 实现的是一种资源的转移。而被转移的资源也叫做 "将亡值"。也就是出了这个作用域,就会销毁的对象。


四、左右值引用的其他应用

(1)完美转发

在前文已经提到过,一旦给右值取别名时,那么该右值引用名义上虽然是右值的别名,但本质是一个可以取地址、甚至可以改变的左值。我们来看看如下的代码。

void Func(int& x)
{
    cout << "左值引用" << endl;
}

void Func(int&& x)
{
    cout << "右值引用" << endl;
}

void GetFunc(int&& x)
{
    Func(x);
}

int main()
{
    int a = 10;
    GetFunc(10);
    return 0;
}

唔,我们分别重载了两个函数Func,一个是用来接收左值引用的、一个是来接收右值引用的,我们传进来的是一个右值10,那么很显然调用后打印的是 "右值引用"。

当右值引用作为参数时,虽然名义上接收的是右值,但是向下传递时,已经改变为了左值。但是我们就想让它保持原有的属性。

C++库中给提供了一个函数转发
std::forward<type>();

我们也就可以看到如我们的预期结果。

(2)万能引用

函数参数有左值引用、也有右值引用,C++中也有模板,那是否模板也有模板左值引用与模板右值引用呢? 是的!

template<class T>
void PerfectFunc(T& x)
{
    Func(x);
}
template<class T>
void PerfectFunc(const T& x)
{
    Func(x);
}

但其实这都用得不多。因为接下来的操作可能会惊掉你的下把。

template<class T>
void PerfectFunc(T&& x)
{
    Func(x);
}

这什么鬼???

在有模板的情况下;
template<class T>
void Func(T&& ..);
就叫做 "万能引用!"

当然,如果你没好好阅读上文,你可能还会惊奇,为什么只会调用左值引用与const左值引用。我们只需要让向下传入的值保持原属性即可。

由此可见,我们能万能引用的情况下,肯定不会去选择"T&"这单调的左值引用参数。


总结:

①左右值区分的最根本方法是,能否取地址,能否使用它的空间。

②左值引用只能引用左值,右值引用只能引用右值。但是const 左值引用可以引用左值 也可以引用右值。

③右值一定没有地址并且不能修改,但是右值引用有它自己的地址,非const可以进行修改。

④左值引用的防拷贝方式更加直接显著。右值引用防拷贝的方式是间接的,也叫"资源转移"。

⑤std::move()可以将一个左值变为右值。std::forward<T>()能保持参数的原属性。

本篇到此结束,感谢你的阅读。

祝你好运,向阳而生~

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

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

相关文章

Git版本控制管理

Git日志记录查看日志获取执行过的命令查看每一次提交记录比较文件差异还原文件git远程仓库克隆远程仓库移除无效的远程仓库Git远程仓库推送、抓送&#xff0c;和拉取Git远程仓库多人协作冲突问题Git远程仓库SSH协议推送Git分支查看分支创建分支修改分支切换分支推送至远程仓库分…

Python3实现“美颜”功能

导语利用Python实现美颜。。。这是之前在GitHub上下载的一个项目。。。似乎有些日子了。。。所以暂时找不到原项目的链接了。。。今天抽空看了下它源代码的主要思想&#xff0c;似乎挺简单的。。。于是决定用Python3自己复现一下。。。T_T感觉还是挺有趣的。。。Just have a tr…

源码分析Spring @Configuration注解如何巧夺天空,偷梁换柱。

前言 回想起五年前的一次面试&#xff0c;面试官问Configuration注解和Component注解有什么区别&#xff1f;记得当时的回答是&#xff1a; 相同点&#xff1a;Configuration注解继承于Component注解&#xff0c;都可以用来通过ClassPathBeanDefinitionScanner装载Spring bean…

IT服务发布管理过程文件

目的 规范发布管理的提交、审批、沟通、测试、回滚、实施等活动。 范围 适用于我公司的IT服务管理重大变更的内、外部发布。 术语定义 发布&#xff1a;将一个或多个变更交付、分发到实际运行环境中并可对其进行追溯。非项目的IT服务重大变更需要遵循本过程进行发布。发布主要包…

论文投稿指南——中文核心期刊推荐(工业经济)

【前言】 &#x1f680; 想发论文怎么办&#xff1f;手把手教你论文如何投稿&#xff01;那么&#xff0c;首先要搞懂投稿目标——论文期刊 &#x1f384; 在期刊论文的分布中&#xff0c;存在一种普遍现象&#xff1a;即对于某一特定的学科或专业来说&#xff0c;少数期刊所含…

使用TorchGeo进行地理空间深度学习

前言TorchGeo是一个PyTorch域库&#xff0c;提供特定于地理空间数据的数据集、采样器、转换和预训练模型。https://github.com/microsoft/torchgeo几十年来&#xff0c;地球观测卫星、飞机和最近的无人机平台一直在收集越来越多的地球表面图像。有了关于季节和长期趋势的信息&a…

【算法】算法基础入门详解:轻松理解和运用基础算法

&#x1f600;大家好&#xff0c;我是白晨&#xff0c;一个不是很能熬夜&#x1f62b;&#xff0c;但是也想日更的人✈。如果喜欢这篇文章&#xff0c;点个赞&#x1f44d;&#xff0c;关注一下&#x1f440;白晨吧&#xff01;你的支持就是我最大的动力&#xff01;&#x1f4…

查找Pycharm跑代码下载模型存放位置以及有关模型下载小技巧(model_name_or_path参数)

目录一、前言二、发现问题三、删除这些模型方法一&#xff1a;直接删除注意方法二&#xff1a;代码删除一、前言 当服务器连不上&#xff0c;只能在本地跑代码时需要使用***预训练语言模型进行处理 免不了需要把模型下载到本地 时间一长就会发现C盘容量不够 二、发现问题 正…

c++11 标准模板(STL)(std::unordered_map)(九)

定义于头文件 <unordered_map> template< class Key, class T, class Hash std::hash<Key>, class KeyEqual std::equal_to<Key>, class Allocator std::allocator< std::pair<const Key, T> > > class unordered…

Python进阶-----高阶函数zip() 函数

目录 前言&#xff1a; zip() 函数简介 运作过程&#xff1a; 应用实例 1.有序序列结合 2.无序序列结合 3.长度不统一的情况 前言&#xff1a; 家人们&#xff0c;看到标题应该都不陌生了吧&#xff0c;我们都知道压缩包文件的后缀就是zip的&#xff0c;当然还有r…

Mybatis源码分析系列之第四篇:Mybatis中代理设计模型源码详解

一&#xff1a; 前言 我们尝试在前几篇文章的内容中串联起来&#xff0c;防止各位不知所云。 1&#xff1a;背景 我们基于Mybatis作为后台Orm框架进行编码的时候&#xff0c;有两种方式。 //编码方式1 UserDao userDao sqlSession.getMapper(UserDao.class); userDao.quer…

[入门必看]数据结构1.1:数据结构的基本概念

[入门必看]数据结构1.1&#xff1a;数据结构的基本概念第一章 绪论1.1 数据结构的基本概念知识总览1.1.1 基本概念和术语数据类型、抽象数据类型&#xff1a;1.1.2 数据结构的三要素数据的逻辑结构数据的物理结构&#xff08;存储结构&#xff09;数据的运算知识回顾与重要考点…

【数据库概论】第十一章 数据库并发控制

第十一章 并发控制 在多处理机系统中&#xff0c;每个处理机可以运行一个事务&#xff0c;多个处理机可以同时运行多个事务&#xff0c;实现多个事务并行运行&#xff0c;这就是同时并发方式。当多个用户并发存取数据库时会产生多个事务同时存取同一事务的情况&#xff0c;如果…

ESP32设备驱动-红外寻迹传感器驱动

红外寻迹传感器驱动 1、红外寻迹传感器介绍 红外寻迹传感器具有一对红外线发射管与接收管,发射管发射出一定频率的红外线,当检测方向遇到障碍物(反射面)时,红外线反射回来被接收管接收,经过比较器电路处理之后,输出接口会输出一个数字信号(低电平或高电平,取决于电路…

Nginx配置实例-反向代理案例二

实现效果&#xff1a;使用nginx反向代理&#xff0c;根据访问的路径跳转到不同端口服务 nginx监听端口为9000&#xff0c; 访问 http://127.0.0.1:9000/edu/ 直接跳转到127.0.0.1:8080 访问 http://127.0.0.1:9000/vod/ 直接跳转到127.0.0.1:8081 一、准备工作 1. 准备两个tom…

TCP相关概念

目录 一.滑动窗口 1.1概念 1.2滑动窗口存在的意义 1.3 滑动窗口的大小变化 1.4丢包问题 二.拥塞控制 三.延迟应答 四.捎带应答 五.面向字节流 六.粘包问题 七.TIME_WAIT状态 八.listen第2个参数 九.TCP总结 一.滑动窗口 1.1概念 概念&#xff1a;双方在进行通信时&a…

2023年软考高级-系统分析师考试学习指导计划!

2023年软考高级-系统分析师考试学习指导计划&#xff01; 一、导学阶段 第一天 考试情况及备考攻略&#xff1a;https://www.educity.cn/xuanke/rk/rjsp/?sywzggw 考试介绍&#xff1a;https://www.educity.cn/wangxiao2/c410653/sp110450.html 考试经验分享&#xff1a;h…

如何在Linux中优雅的使用 head 命令,用来看日志简直溜的不行

当您在 Linux 的命令行上工作时&#xff0c;有时希望快速查看文件的第一行&#xff0c;例如&#xff0c;有个日志文件不断更新&#xff0c;希望每次都查看日志文件的前 10 行。很多朋友使用文本编辑的命令是vim&#xff0c;但还有个命令head也可以让轻松查看文件的第一行。 在…

记录使用chatgpt的复杂经历

背景 由于最近要写论文&#xff0c;c站的gpt也变样了&#xff0c;无奈之下和同学借了一个gpt账号&#xff0c;才想起没有npv&#xff0c;不好意思去要&#xff0c;也不想买&#xff0c;于是我找了很多临时免费的直到我看到有一家&#xff0c;邀请10人即可&#xff0c;而且只用…

江苏专转本 专科生的最好出入

专转本 专科生的最好出入(1) 减少每天刷某音&#xff0c;刷朋友圈的时间无所事事、捧着手机刷的不是某音就是朋友圈&#xff0c;三年下来没去成一次图书馆。碎片化信息很大程度只是在浪费时间&#xff0c;不如每天比同龄人至少多出1小时时间&#xff0c;用来看书、护肤、健身。…