C++:左值(引用)右值(引用)

2024-05-11 00:44
文章标签 c++ 引用 右值 左值

本文主要是介绍C++:左值(引用)右值(引用),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

〇、前言

本文会讨论C++中的左值,右值,左值引用,右值引用,以及会理清它们之间的关系。

一、左值与右值

(一)概述

1. 左值是一般指表达式结束后依然存在的持久化对象。右值指表达式结束时就不再存在的临时对象。便捷的判断方法:能对表达式取地址、有名字的对象为左值。反之,不能取地址、匿名的对象为右值。

2. C++ 表达式(运算符带上其操作数、字面量、变量名等)有两种独立的属性:类型值类别 。类型指变量声明时的类型,而值类别是表达式结果的类型,它必属于左值、纯右值或将亡值三者之一。如int&& x;其中 x 的类型为右值引用,但作为表达式使用时其值类别为左值(因为有名字,可以取址)。比如:

#include <iostream>
void func(int &&b) {std::cout << "b: " << b << " addr: " << &b << std::endl;b++;std::cout << b << std::endl;
}
int main() {int c = 10;func(std::move(c));std::cout << "c: " << c << " addr: " << &c << std::endl;return 0;
}

上例中的 b 就是一个右值引用,它接受了一个右值 c。在 main() 中,c 被转化为右值引用传入 func(),因此,func() 中的 b 其实就是 main() 中的 c 别名,它们指向的是同一块内存区域。需要注意的是,func() 中的 b 其实是一个左值,换句话说,右值引用如果绑定了一个右值,它会延长这个右值的生命周期。 这种生命周期的延长意味着,尽管原始表达式产生的值是一个右值,一旦它被一个右值引用所绑定,它就不再是一个"即将销毁的临时值",而更像是一个普通的变量。 这允许开发者在保证效率的同时,也能够更灵活地控制这些值。

运行结果:

g++ 1.cxx -o main -std=c++14
./main
b: 10 addr: 0x16ba16e28
11
c: 11 addr: 0x16ba16e28

我们需要注意的是,main() 中的 c 被转换成了右值引用,但是它的状态没有发生任何改变。它在 func 中被修改,并在 func 返回后仍然保持有效。std::move 只是告诉编译器一个对象可以被“安全地”当作右值来使用,这样就允许在移动语义上下文中使用该对象。

(二)右值的分类

1. 纯右值(prvalue):用于识别临时变量和一些不与对象关联的值。函数返回值为非引用类型、表达式临时值(如1+3)、lambda表达式等。

2. 将亡值(xvalue):是与右值引用相关的表达式,通常指将要被移动的对象。如,函数返回类型为T&&std::move的返回值、转换为T&&的类型转换函数的返回值(注意,这些都是与右值引用相关的表达式)或临时对象。

表格形式:

类型分类特征绑定规则和类型安全转换实例和特殊情况
左值 (lvalue)存在变量、函数调用产生的对象,有固定地址。可绑定到左值引用,例如 int& x = a;变量 int a = 10; 函数调用(返回引用)例如 int& foo();
纯右值 (prvalue)不适宜被移动,不具有可识别的地址。可绑定到右值引用或 const 左值引用,例如 int&& x = 5;字面量例如 42,表达式例如 a + b,函数调用(返回非引用)例如 int foo();
将亡值 (xvalue)适宜被移动,具有地址但对象即将被销毁。可绑定到右值引用,例如使用 std::move 例如 int&& x = std::move(a);通过 std::move 生成的将亡值,例如 std::move(a);使用移动构造函数或移动赋值操作时,传递的实际参数。
泛左值 (glvalue)概括左值和将亡值,即拥有特定身份(地址)且可能即将被销毁。可以绑定到左值引用或右值引用,取决于上下文。例如使用 decltype 捕获引用时,decltype((a)) x = a; 捕获的是 a 的引用。

二、左值引用与右值引用

(一)概述

1. 左值引用和右值引用都属于引用类型。无论是声明一个左值引用还是右值引用都必须立即进行初始化(今天考试刚考到)。

2. 左值引用都是左值。但具名的右值引用是左值,而匿名的右值引用是右值(比如上面的 func 中的 b,虽然它是右值引用,但是它是一个左值)。

(二)可绑定的值类型(设T是个具体类型)

1. 左值引用(T&):只能绑定到左值(非常量左值)

2. 右值引用(T&&):只能绑定到右值(非常量右值)

3. 常量左值引用(const T&):常量左值引用是个“万能”的引用类型:它既可以绑定到左值也可以绑定到右值,它像右值引用一样可以延长右值的生命期。不过相比于右值引用所引用的右值,常量左值引用的右值在它的“余生”中只能是只读的。对于这点,可以参考这个例子:

#include <iostream>
void func(const int &b) {std::cout << "b: " << b << " addr: " << &b << std::endl;b++; // 会报错std::cout << b << std::endl;
}
int main() {int c = 10;func(std::move(c));std::cout << "c: " << c << " addr: " << &c << std::endl;return 0;
}

编译出错:

g++ 2.cxx -o main -std=c++14
2.cxx:4:6: error: cannot assign to variable 'b' with const-qualified type 'const int &'b++;~^
2.cxx:2:22: note: variable 'b' declared const here
void func(const int &b) {~~~~~~~~~~~^
1 error generated.

4. 常量右值引用(const T&&):可绑定到右值或常量右值。由于移动语义需要右值可以被修改,因此常量右值引用没有实际用处。如果需要引用右值且让其不可更改,则常量左值引用就足够了。

三、万能引用(universal reference)

(一)T&&的含义

1. 当T是一个具体的类型时,T&& 表示右值引用,只能绑定到右值。

2. 当涉及T类型推导时,T&& 为万能引用。若用右值初始化万能引用,则T&&为右值引用。若用左值初始化万能引用,则T&&为左值引用。但不管哪种情况,T&&都是一种引用类型

(二)万能引用

1. T&&是万能引用的两个条件:

(1)必须涉及类型推导

(2)声明的形式也必须正好形如 T&&并且该形式被限定死了,任何对其修饰都将剥夺T&&成为万能引用的资格

2. 万能引用使用的场景

(1)函数模板形参
    
(2)auto&&

一个例子:

#include <iostream>
#include <vector>using namespace std;class Widget {};void func1(Widget &&param){}; // param为右值引用类型(不涉及类型推导)template <typename T>
void func2(T &&param) {} // param为万能引用(涉及类型推导)template <typename T>
void func3(std::vector<T> &&param) {
}template <typename T>
void func4(const T &&param) {}
template <class T> class MyVector {public:void push_back(T &&x) {} // x为右值引用。因为当定义一个MyVector对象后,T己确定。当调用该函数时T的类型不用再推导!template <class... Args>void emplace_back(Args&&...args){}; // args为万能引用,因为Args独立于T的类型,当调用该函数时,需推导Args的类型。
};int main() {Widget w;func2(w);            // 万能引用, func2(T&& param),param为Widget&(左值引用)func2(std::move(w)); // 万能引用, param为Widget&&,是个右值引用。int x = 0;Widget &&var1 = Widget(); // var1为右值引用(不涉及类型推导)auto &&var2 = var1; //万能引用,auto&&被推导为Widget&(左值引用)auto &&var3 = x; //万能引用,被推导为int&;(左值引用)// 3. 计算任意函数的执行时间:auto&&用于lambda表达式形参(C++14)auto timefunc = [](auto &&func, auto &&...params) {//计时器启动//调用func(param...)函数std::forward<decltype(func)>(func)( //根据func的左右值特性来调用相应的重载&或&&版本的成员函数std::forward<decltype(params)>(params)... //保持参数的左/右值特性);//计时器停止并记录流逝的时间};timefunc(func1, std::move(w)); //计算func1函数的执行时间return 0;
}

在你提供的代码中,main 函数涉及到万能引用的语句如下:

  1. func2(w);func2(std::move(w)); — 这里的 func2 函数模板参数 T&& 是一个万能引用。它可以绑定到左值和右值。在这两个调用中,第一次调用时 T 被推导为 Widget&(因为 w 是一个左值),而第二次调用时 T 被推导为 Widget(因为 std::move(w) 产生一个右值)。

  2. auto &&var2 = var1; — 这里使用了 auto&&,它也是一个万能引用。var1 是一个左值(尽管它本身是一个绑定到临时对象的右值引用),所以 var2 被推导为 Widget&

  3. auto &&var3 = x; — 这同样使用了 auto&&,这是一个万能引用。由于 x 是一个左值,var3 被推导为 int&

  4. timefunc lambda 表达式的定义中,参数列表 (auto &&func, auto &&...params) 使用了万能引用。这里 auto&& 用于单个参数和参数包,允许这个 lambda 接受任意数量的任意类型的参数,并保持他们的值类别(左值或右值)。

这些例子展示了万能引用在模板类型推导中的强大功能,尤其是在泛型编程和函数重载解析中的应用。通过万能引用,可以写出更灵活的函数和模板,使得它们能够同时接受左值和右值,而无需重载函数。

四、参考

这里。

这篇关于C++:左值(引用)右值(引用)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/978030

相关文章

C++ 中的 if-constexpr语法和作用

《C++中的if-constexpr语法和作用》if-constexpr语法是C++17引入的新语法特性,也被称为常量if表达式或静态if(staticif),:本文主要介绍C++中的if-c... 目录1 if-constexpr 语法1.1 基本语法1.2 扩展说明1.2.1 条件表达式1.2.2 fa

C++中::SHCreateDirectoryEx函数使用方法

《C++中::SHCreateDirectoryEx函数使用方法》::SHCreateDirectoryEx用于创建多级目录,类似于mkdir-p命令,本文主要介绍了C++中::SHCreateDir... 目录1. 函数原型与依赖项2. 基本使用示例示例 1:创建单层目录示例 2:创建多级目录3. 关键注

C++从序列容器中删除元素的四种方法

《C++从序列容器中删除元素的四种方法》删除元素的方法在序列容器和关联容器之间是非常不同的,在序列容器中,vector和string是最常用的,但这里也会介绍deque和list以供全面了解,尽管在一... 目录一、简介二、移除给定位置的元素三、移除与某个值相等的元素3.1、序列容器vector、deque

C++常见容器获取头元素的方法大全

《C++常见容器获取头元素的方法大全》在C++编程中,容器是存储和管理数据集合的重要工具,不同的容器提供了不同的接口来访问和操作其中的元素,获取容器的头元素(即第一个元素)是常见的操作之一,本文将详细... 目录一、std::vector二、std::list三、std::deque四、std::forwa

C++字符串提取和分割的多种方法

《C++字符串提取和分割的多种方法》在C++编程中,字符串处理是一个常见的任务,尤其是在需要从字符串中提取特定数据时,本文将详细探讨如何使用C++标准库中的工具来提取和分割字符串,并分析不同方法的适用... 目录1. 字符串提取的基本方法1.1 使用 std::istringstream 和 >> 操作符示

C++原地删除有序数组重复项的N种方法

《C++原地删除有序数组重复项的N种方法》给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度,不要使用额外的数组空间,你必须在原地修改输入数组并在使用O(... 目录一、问题二、问题分析三、算法实现四、问题变体:最多保留两次五、分析和代码实现5.1、问题分析5.

C++ 各种map特点对比分析

《C++各种map特点对比分析》文章比较了C++中不同类型的map(如std::map,std::unordered_map,std::multimap,std::unordered_multima... 目录特点比较C++ 示例代码 ​​​​​​代码解释特点比较1. std::map底层实现:基于红黑

C++中函数模板与类模板的简单使用及区别介绍

《C++中函数模板与类模板的简单使用及区别介绍》这篇文章介绍了C++中的模板机制,包括函数模板和类模板的概念、语法和实际应用,函数模板通过类型参数实现泛型操作,而类模板允许创建可处理多种数据类型的类,... 目录一、函数模板定义语法真实示例二、类模板三、关键区别四、注意事项 ‌在C++中,模板是实现泛型编程

利用Python和C++解析gltf文件的示例详解

《利用Python和C++解析gltf文件的示例详解》gltf,全称是GLTransmissionFormat,是一种开放的3D文件格式,Python和C++是两个非常强大的工具,下面我们就来看看如何... 目录什么是gltf文件选择语言的原因安装必要的库解析gltf文件的步骤1. 读取gltf文件2. 提

C++快速排序超详细讲解

《C++快速排序超详细讲解》快速排序是一种高效的排序算法,通过分治法将数组划分为两部分,递归排序,直到整个数组有序,通过代码解析和示例,详细解释了快速排序的工作原理和实现过程,需要的朋友可以参考下... 目录一、快速排序原理二、快速排序标准代码三、代码解析四、使用while循环的快速排序1.代码代码1.由快