首页 > 编程语言 >c++之左值引用 右值引用 万能引用

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

时间:2025-01-03 14:58:06浏览次数:3  
标签:右值 int 万能 左值 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. 正是因为有这些规则,万能引用才能正确处理各种值类别

标签:右值,int,万能,左值,c++,引用,&&
From: https://blog.csdn.net/iqanchao/article/details/144878374

相关文章

  • C++之模板进阶
    文章目录1.引言2.非类型模板参数2.1概念及使用2.2与#define定义宏的对比3.模板的特化3.1概念3.2函数模板特化3.3类模板特化3.3.1全特化3.3.2偏特化(也称半特化)3.3.3类模板特化应用示例4.模板的分离编译(了解)4.1什么是分离编译4.2模板的分离编译5.模......
  • Effective C++读书笔记——item2(const,enum,inlines取代#define)
    关于用常量取代#define的总体原则在编程中,应尽量减少预处理器(特别是#define)的使用,可通过合适的替代方式来避免#define带来的诸多问题,虽然不能完全消除预处理器相关指令(如#include、#ifdef/#ifndef仍有重要作用),但要让其使用频率降低。简单常量方面问题阐述:使用#defi......
  • leetcode热题100(84. 柱状图中最大的矩形)c++
    链接:84.柱状图中最大的矩形-力扣(LeetCode)给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为1。求在该柱状图中,能够勾勒出来的矩形的最大面积。示例1:输入:heights=[2,1,5,6,2,3]输出:10解释:最大的矩形为图中红色区域,面积为10示......
  • C++基础之移动语义(Move Semantic)
    文章目录引言Move语义的定义与优点示例:拷贝和移动操作应用场景Move后的变量自定义类中如何支持MoveMove语义的误区`std::move`并不移动数据移动后继续使用对象对整型或者布尔类型无效总结参考链接引言在现代C++中,Move语义通过优化资源管理和减少内存......
  • C++程序设计谭浩强第四版-第十一章
    第十一章类的继承(面向对象的程序设计)面向对象程序的4大特点抽象封装继承多态派生类(子类)是基类(父类)的具体化,基类是派生类的抽象类的继承:一个新类从已有的类那里获得其已有特性派生:从已有的类(父类)产生一个新的子类class派生类名:[继承方式]基类名{......
  • C++11 thread线程的使用
    C++11thread线程的使用文章目录C++11thread线程的使用构造函数1.`thread()noexcept=default;`2.`thread(thread&)=delete;`3.`thread(constthread&&)=delete;`4.`thread(thread&&__t)noexcept`5.`template<typename_Callable,typename..._Args&g......
  • leetcode热题100(394. 字符串解码)c++
    链接:394.字符串解码给定一个经过编码的字符串,返回它解码后的字符串。编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格......
  • <<零基础学C++,类和对象(上)--类的定义,访问限定符,类域,实例化>>
    目录类的定义访问限定符 类域实例化实例化的概念 类的定义class为定义类的关键字,Date为类的名字(类名就相当于类型),{}中为类的主体,注意类定义结束时后⾯分号不能省略。类体中内容称为类的成员:类中的变量称为类的属性或成员变量;类中的函数称为类的⽅法或者成员......
  • 【算法一周目】位间流转,数字律动——洞察 C++ 位运算中的精妙与哲思
    文章目录常见位运算1.位1的个数2.比特位计数3.汉明距离4.只出现一次的数字5.只出现一次的数字III6.只出现一次的数字II7.判定字符是否唯一8.丢失的数字9.两整数之和10.只出现一次的数字II常见位运算判断一个数的二进制表示的第x位是0还是1(n>>x)&1......
  • DirectX 修复工具 V4.3 绿色增强版:完美解决 DirectX 和 C++ 问题(修复 0xc000007b 错误
    DirectX修复工具V4.3绿色增强版:完美解决DirectX和C++问题简介DirectX修复工具是一款专为检测和修复DirectX问题而设计的实用工具。它能够精准定位问题并进行高效修复,特别是针对0xc000007b错误,拥有极高的修复成功率。本工具支持所有版本DirectX修复,同时增强版还额......