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

c++之左值引用 右值引用 万能引用

详细大家无论是在阅读代码,还是阅读文档的时候经常看到这几个词或者对应的符号,但是可能也不是很清楚他们到底有哪些区别,本文将对这几个概念详细深入的介绍。

左值引用

左值引用的表现形式,如下所示:

int x = 2;

int& a = x;  //正确,这里a就是一个左值引用

int& a = 2; //错误,临时变量2是一个右值

std::string& str = "123";   //错误,"123"是一个常量

 从上面可以看到左值引用,一定是用&修饰的变量,且对左值引用只能使用左值来进行初始化。

这里提到了两个概念左值和右值。左值和右值相对于赋值运算符(=)而言的,=左边的就是左值;=右边以及常量等都是右值

左值引用很常见,也很容易理解。它经常作为函数参数使用,以减少数据值拷贝的开销。

需要注意的是const修饰的左值引用是可以使用右值进行初始化,如下所示:

const int& a = 2;  //正确

特点:

  1. 只能绑定到左值(可以取地址的、有名字的对象)
  2. 不能绑定到右值(临时对象)
  3. const 左值引用可以绑定到右值(这是一个重要的例外)
  4. 必须在声明时初始化右值引用

右值引用

右值引用的表现形式,如下所示:

int x = 2;

int&& a = x;  //错误,a作为一个右值引用,只能使用右值进行初始化,不能绑定左值

int&& a = 2; //正确,常量是一个临时变量

右值引用使用&&来表示,一个变量使用&&来进行修饰,这就表明该变量是一个右值引用。

右值引用经常会使用在类定义中,如移动构造函数,见下面例子,移动构造函数的参数就是右值引用。那么如何将一个左值转换为一个右值引用呢?答案是可以通过std::move来实现。

//该构造函数为移动构造函数

class MyString { public: MyString(MyString&& other) noexcept {

// 右值引用参数

// 移动资源

data_ = other.data_;

other.data_ = nullptr; }

private: char* data_;

};

右值引用的特点:

  1. 只能绑定到右值
  2. 主要用于实现移动语义
  3. 可以通过 std::move 将左值转换为右值引用
  4. 命名的右值引用本身是左值(这是个重要概念)

 万能引用

万能引用是c++11引入的概念,只有具备如下两个条件才能称作万能引用:

  1. 形式为T&&
  2. T需要进行类型推导

万能引用可以通俗的理解:万能引用既可以解释为左值引用,也可以解释为右值引用。但是不能同时被解释为左值引用和右值引用

具体例子参见如下所示:

template<typename T>
void foo(T&& param) {  // 这是万能引用
    // 使用 std::forward 来保持值类别
    bar(std::forward<T>(param));
}

// auto&& 也是万能引用
auto&& var = someValue;

// 这些不是万能引用,而是右值引用
void bar(Widget&& param);          // 具体类型的右值引用,因为Widget不需要进行类型推导
template<typename T>
void baz(const T&& param);        // const 限定符使其变成右值引用
template<typename T>
void qux(std::vector<T>&& param); // vector<T>&& 是右值引用,必须是严格的T&&形式

// 不是万能引用的情况
template<typename T>
class Container {
    // 构造函数中的 T&& 不是万能引用
    // 因为 T 在类模板实例化时就已确定

    Container(T&& value);  // 这是右值引用
};

// 是万能引用的情况
template<typename T>
class Container {
    // 这是万能引用,因为 U 需要推导
    template<typename U>
    void add(U&& value);
};

万能引用的折叠规则

首先看一个例子,如下所示:

template<typename T>
void foo(T&& param) {
    // 当传入左值时:
    // T 被推导为 int&
    // T&& 变成 int& && -> 折叠为 int&
    
    // 当传入右值时:
    // T 被推导为 int
    // T&& 保持为 int&&
}

int main() {
    int x = 42;
    foo(x);           // 传入左值,param类型为int&
    foo(42);          // 传入右值,param类型为int&&
}

通过上面的例子可以看到,当传入左值时,万能引用被推导为int&,结合T&&形式变成了int& &&,这个类型折叠为int&,是一个左值引用;当传入一个右值时,万能引用被推导为int&& &&,经过折叠后变成了int &&。那么折叠的规则是什么呢?答案如下所示:

& & → &      // 左值引用的左值引用 = 左值引用
& && → &     // 左值引用的右值引用 = 左值引用---这个不会在万能引用中使用到
&& & → &     // 右值引用的左值引用 = 左值引用
&& && → &&   // 右值引用的右值引用 = 右值引用-----这个不会在万能引用中使用到

关键记忆点

  1. 当类型中包含左值引用(&)时,折叠的结果总是左值引用(&)
  2. 只有当两个都是右值引用(&&)时,结果才是右值引用(&&)
  3. 这些规则是编译器自动应用的,我们不需要手动处理
  4. 正是因为有这些规则,万能引用才能正确处理各种值类别

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

相关文章:

  • PyTorch 自动混合精度AMP Grad Scaler 源码解析:_unscale_grads_ 与 unscale_ 函数
  • ChatGPT 主流模型GPT-4/GPT-4o mini的参数规模是多大?
  • 强化学习常用库的版本对应关系
  • 最新版Chrome浏览器加载ActiveX控件之CFCA安全输入控件
  • leetcode 面试经典 150 题:轮转数组
  • 【C++】B2092 开关灯
  • AI在电子制造中的应用:预测质量控制
  • 深入了解 Python 的 venv 虚拟环境
  • 01、Docker学习,第一天:简单入门与安装
  • 鸿蒙Flutter实战:15-Flutter引擎Impeller鸿蒙化、性能优化与未来
  • 数据可视化分析详解
  • leetcode hot100 乘积最大子数组
  • Cursor连接腾讯云Cloud Studio开发环境
  • 如何在 Ubuntu 22.04 上配置 Logrotate 高级教程
  • Go语言的 的数据封装(Data Encapsulation)基础知识
  • 关于Hugging Face模型国内下载
  • Kotlin 数据类与密封类
  • Linux下学【MySQL】表中修改和删除的进阶操作(配实操图和SQL语句通俗易懂)
  • 开源模型应用落地-Qwen2.5-7B-Instruct与vllm实现推理加速的正确姿势-利用KOR框架实现结构化输出(七)
  • 【汇编语言】外中断(二)—— 键盘的奥秘:编写自己的 int 9 中断例程
  • 【软考网工笔记】操作系统管理与配置——Windows
  • Python中使用PostgreSQL和Apache AGE扩展来绘制和显示图表
  • Go语言的 的变量声明(Variable Declaration)基础知识
  • MySQL叶子节点为啥使用双向链表?不使用单向呢?
  • centos7yum安装mysql5.7
  • 【机器学习:三、常见的代价函数】