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

(基本常识)左值引用、右值引用、万能引用、移动语义和完美转发——原理和代码示例

作者:求一个demo

版权声明:著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处

内容通俗易懂,没有废话,文章最后是面试常问内容(建议通过标题目录学习)

废话不多说,我们直接开始------>>>>>>

一、左值

        1、含义:左值是指出现在赋值表达式左侧的表达式,它有持久的状态(名字、存储空间等)。

        2、特点:

        ①有明确的存储空间(地址)。

        ②可以通过取地址符(&)获取其地址。

        ③生命周期超过当前表达式(例如类中定义一个变量,运行完这个定义的变量后,后面仍然可以使用这个左值变量)。

        ④左值一般是:函数名、变量名、++i、--i、*p等。

        3、示例:

int a = 10;  // a是左值
int* p = &a; // 可以获取a的地址

二、右值

        1、含义:右值是指出现在赋值表达式右侧的表达式,通常是指临时对象或字面量。

        2、特点:

        ①没有持久的状态(通常是临时对象)。

        ②不能获取其地址。

        ③通常是匿名的临时对象或字面量。

        ④生命周期仅限于当前表达式。

        3、示例:

int b = 20;     // 20是右值
int c = a + b;  // (a + b)的结果是右值

三、左值引用

        1、含义:使用&声明的引用,只能绑定到左值。左值引用可以指向左值,但是不能指向右值,但是前面加const可以指向右值(因为const左值引用延长了右值的生命周期,使得右值在引用的生命周期内保持有效)。

        2、特点:

        ①语法:Type&。

        ②只能绑定到左值。

        ③延长被引用对象的生命周期(只要引用存在,对象就不会被销毁)。

        ④常用于函数参数,避免拷贝。

        3、示例:

int x = 10;
int& ref = x;  // 正确,x是左值
// int& ref = 10; // 错误,不能绑定到右值

四、右值引用

        1、含义:使用&&声明的引用,只能绑定到右值(C++11引入)。std::move可以将左值转化为右值引用(move作用是转移对象的所有权),从而允许对左值进行类似右值的操作

        2、特点:

        ①语法:Type&&。

        ②只能绑定到右值(临时对象)。

        ③允许修改右值。

        ④主要用于移动语义和完美转发。

        ⑤可以延长临时对象的生命周期。

        3、示例:

int&& rref = 10;          // 正确,10是右值
// int&& rref = x;        // 错误,不能绑定到左值x
std::string&& s = getStr(); // getStr()返回的临时对象

五、优缺点和对比(左值引用和右值引用)

        1、左值引用优缺点:

        (1)优点:

                ①避免对象拷贝,提高效率。

                ②允许修改原对象(左值引用作为函数参数且不是const引用时,可以在函数内部修改引用绑定的对象)。

void increment(int& num) {
    num++;
}

                ③语法简单直观。

        (2)缺点:                

                ①不能绑定到临时对象/右值(左值引用只能绑定有持久内存地址和名称的对象,即左值。而右值是临时创建的,没有持久内存地址)。

                ②有时会导致不必要的对象保留(左值引用延长对象生命周期,当函数返回一个局部对象的引用时,如果该引用被外部使用<返回对象的引用作为函数参数传入、返回静态局部对象的引用等>,导致局部对象生命周期延长,可能引发内存泄漏或未定义)。

int& getLocalReference() {
    int num = 10;
    return num; // 错误,返回局部对象的引用,会导致未定义行为
}
int main() {
    // int& ref = getLocalReference(); 
    return 0;
}

        2、右值引用优缺点:

        (1)优点:

                ①支持移动语义,避免深拷贝(移动语义允许将一个资源从一个对象快速转移到另一个对象,而不是进行深拷贝。右值引用是实现移动语义的关键,通过移动构造函数和移动赋值运算符,可以在对象所有权转移时避免不必要的复制)。

class MyVector {
private:
    int* data;
    size_t size;
public:
    // 构造函数
    MyVector(size_t s) : size(s) {
        data = new int[size];
        for (size_t i = 0; i < size; ++i) {
            data[i] = i;
        }
    }
    // 移动构造函数
    MyVector(MyVector&& other) noexcept : data(other.data), size(other.size) {
        other.data = nullptr;
        other.size = 0;
    }
    // 析构函数
    ~MyVector() {
        delete[] data;
    }
};
int main() {
    MyVector v1(1000000);
    MyVector v2(std::move(v1)); // 使用移动构造函数,避免深拷贝
    return 0;
}

                ②可以绑定并修改临时对象,并且可以在绑定后对临时对象进行修改(例如在函数返回临时对象时,可以通过右值引用接收并处理)。

int&& getRValue() {
    return std::move(10);
}
int main() {
    int&& rref = getRValue();
    rref = 20;
    std::cout << rref << std::endl; // 输出 20
    return 0;
}

                ③右值引用结合forward函数可以实现完美转发(即在函数模板中能将参数以原有的值类别<左值或右值>传递给其他函数)。

void printValue(int& value) {
    std::cout << "左值: " << value << std::endl;
}
void printValue(int&& value) {
    std::cout << "右值: " << value << std::endl;
}
template<typename T>
void forwardValue(T&& value) {
    printValue(std::forward<T>(value));
}
int main() {
    int x = 10;
    forwardValue(x); // 传递左值
    forwardValue(20); // 传递右值
    return 0;
}

                ④提供性能,特别是处理大型对象时(使用右值引用和移动语义可以避免深拷贝,直接转移资源的所有权,提高性能)。

        3、左值引用和右值引用的对比

        ①绑定对象上:左值引用只能绑定左值;右值引用只能绑定右值。

        ②语法上:左值引用是单个&;右值引用是&&。

        ③主要用途上:左值引用是避免拷贝;右值引用是移动语义,资源转移。

        ④生命周期影响上:左值引用是延长被引用的对象;右值引用是延长临时对象。

        (2)缺点:                

                ①语法较复杂(C++11新特性)。

                ②不当使用可能导致对象状态意外改变(右值引用会对临时对象进行修改或者资源转移,如果移动构造或移动运算符没正确处理对象,可能导致对象在移动后处于无效状态,从而导致未定义等)。

六、万能引用

        1、含义:使用T&&的形参,既能绑定右值,也能绑定左值(C++11新特性)。

        2、特点:

        ①既可以绑定右值,也可以绑定左值。

        ②依赖类型推导(模板或auto),如果不是类型推导的时候使用,那么T&&表示右值引用。

        ③配合forward实现完美转发。

        3、示例:

template<typename T>
void foo(T&& param);  // 万能引用

auto&& var = x;       // 万能引用

七、移动语义

        1、含义:转移资源所有权而非拷贝,提高性能。

        2、关键操作:

        (1)移动构造函数:T(T&& other)。

        (2)移动赋值运算符:T& operator=(T&& other)。

        3、优点:

        ①避免不必要的深拷贝(移动语义允许将一个资源从一个对象快速转移到另一个对象,而不是进行深拷贝。右值引用是实现移动语义的关键,通过移动构造函数和移动赋值运算符,可以在对象所有权转移时避免不必要的复制)。

        ②提升大型对象处理效率。

        ③使不可拷贝的对象可以转移。

        4、示例:

class Buffer {
    char* data;
public:
    Buffer(Buffer&& other) : data(other.data) {
        other.data = nullptr;  // 转移所有权
    }
};

八、完美转发

        1、保持参数原始值类别(左值或右值)转发到其他函数。

        2、实现方法:

        ①结合万能引用和forward。

        ②保持参数的const属性和值类别。

        3、示例:

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

九、校招面试常问内容

1、什么是左值右值?如何区分?

        (1)左值和右值:左值有持久的状态,可以取地址,通常出现在赋值左侧;右值一般是临时对象或字面量,没有持久的状态,不能取地址,只能出现在赋值右侧。

        (2)如何区分?

                ①能否用&取地址:左值可以,右值不可以。

                ②生命周期:左值超出当前表达式,右值通常仅限于当前表达式。

                ③能够放在赋值左侧:左值可以,右值不可以。

2、左值引用和右值引用的区别?

        ①绑定对象上:左值引用只能绑定左值;右值引用只能绑定右值。

        ②语法上:左值引用是单个&;右值引用是&&。

        ③主要用途上:左值引用是避免拷贝;右值引用是移动语义,资源转移。

        ④生命周期影响上:左值引用是延长被引用的对象;右值引用是延长临时对象。

3、什么是万能引用?它和右值引用有什么不同?

        (1)万能引用:万能引用是形式上类似右值引用(T&&),是类型推导中的特殊引用,可以绑定到左值或右值。

        (2)区别:

                ①万能引用只出现在模板参数或auto&&推导中。

                ②万能引用可以绑定任意值(在类型推导中),右值引用只能绑定右值。

                ③万能引用主要用于完美转发(万能引用可以接受左值或右值),右值引用主要用于移动语义(资源高效转移,而不是深拷贝,适用于处理大型对象)。

4、移动语义解决了什么问题?如何实现?

        (1)解决的问题:

                ①避免不必要的深拷贝,提高性能(通过移动构造函数和移动赋值运算符,可以在对象所有权转移时避免不必要的复制)。

                ②使不可拷贝的对象可以转移资源所有权。

                ③优化临时对象处理

        (2)如何实现:

                ①移动构造函数:T(T&& other)。

                ②移动赋值运算符:T& operator=(T&& other)。

                ③使用std::move将左值转为右值引用。

5、std::move和std::forward的区别?

        ①作用上:move是无条件转为右值引用;forward是有条件(根据模板参数)保持值类别。

        ②使用场景上:move主要用在移动语义;forward主要用在完美转发。

        ③参数类型上:move任意类型;forward必须配合万能引用。

        ④返回值上:move是T&&;forward是T&&。

        ⑤实质:move是static_cast<T&&>;forward是根据T决定转换方式。

6、完美转发是如何实现的?为什么要用forward?

        (1)如何实现?

                ①结合万能引用接收任意值类别的参数。

                ②使用forward根据模板参数类型决定转发为左值还是右值。

                ③保持参数的原始值类别和const属性。

        (2)为什么用forward?

                ①避免转发时丢失值类别信息。

                ②保证右值参数能正确触发移动语义。

                ③实现精准的参数传递。

 

最后,如有不足和错误的地方,期待私信指正!

 

相关文章:

  • Linux学习:进程通信(管道)
  • HarmonyOS:GridObjectSortComponent(两个Grid之间网格元素交换)
  • 微软下一个大更新:Windows 11 25H2或已在路上!
  • CSS(八)
  • Linux笔记---动静态库(使用篇)
  • 全书测试:《C++性能优化指南》
  • 如何在 Postman 中配置并发送 JSON 格式的 POST 请求?
  • ‌国产芯片解析:龙迅HDMI发射机系列产品详解
  • 【C++】内存模型分析
  • Cherry Studio开源程序 是一个支持多个LLM提供商的桌面客户端。支持 deepseek-r1,可在 Windows、Mac 和 Linux 上使用
  • 数据库基础知识点(系列六)
  • 遍历整个列表
  • 天梯赛测试题2(L1答案及其解析)
  • .netCore的winform程序如何调用webapi
  • 软考笔记——软件工程基础知识
  • 未来技术的发展趋势与影响分析
  • dji飞行控制
  • AOA(到达角度)与TOA(到达时间)两个技术的混合定位,MATLAB例程,自适应基站数量,三维空间下的运动轨迹,滤波使用UKF(无迹卡尔曼滤波)
  • 7.5 窗体事件
  • [学成在线]07-视频转码
  • 知名中医讲师邵学军逝世,终年51岁
  • 莫高义在第四届中国新闻发言人论坛开幕式上的致辞
  • 病愈出院、跳大神消灾也办酒,新华每日电讯:农村滥办酒席何时休
  • 上海:到2027年,实现近海航线及重点海域5G网络高质量覆盖
  • 临港新片区将新设5亿元启航基金:专门投向在临港发展的种子期、初创型企业
  • “大型翻车现场”科技满满,黄骅打造现代化港口和沿海新城典范