欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 健康 > 养生 > c++之左值引用 右值引用 万能引用

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

2025/2/23 14:24:00 来源:https://blog.csdn.net/iqanchao/article/details/144878374  浏览:    关键词: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. 正是因为有这些规则,万能引用才能正确处理各种值类别

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词