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. 正是因为有这些规则,万能引用才能正确处理各种值类别
相关推荐
Java 第一深情1 分钟前
面试题解,Java中的“对象”剖析
java·jvm
兔爷眼红了4 分钟前
前端开发语言涉及到 的注解(Annotations)
开发语言·后端·golang
꧁坚持很酷꧂17 分钟前
Qt天气预报系统设计界面布局第四部分右边
开发语言·qt
花仙子16619 分钟前
C#运动控制系统:雷赛控制卡实用完整例子 C#雷赛开发快速入门 C#雷赛运动控制系统实战例子 C#快速开发雷赛控制卡
开发语言·算法·c#
AmosCloud201329 分钟前
3.5 字典树(Trie)与后缀树
开发语言·数据结构·链表·c#
计算机徐师兄30 分钟前
Java基于SSM框架的影院选座系统小程序【附源码、文档】
java·微信小程序·影院选座系统·影院选座系统小程序·影院选座微信小程序·影院选座
爬山算法38 分钟前
Tomcat(116) 如何在Tomcat中解决缓存问题?
java·缓存·tomcat
像素之间43 分钟前
maven的中国镜像有哪些
java·maven
帅气的人1231 小时前
proto maven 编译 组件
java·maven
Tiger Z1 小时前
R 语言科研绘图第 13 期 --- 柱状图-堆叠
开发语言·程序人生·r语言·贴图