😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀
🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C++、数据结构、音视频🍭
⏰发布时间⏰:2024-06-12 16:35:55
本文未经允许,不得转发!!!
目录
- 🎄一、概述
- 🎄二、左值(lvalue)
- 🎄三、右值(rvalue)
- 🎄四、左值引用
- 🎄五、右值引用(rvalue reference)
- ✨5.1 右值引用的理解
- ✨5.2 std::move
- 🎄六、总结
🎄一、概述
左值、右值、左值引用、右值引用,这几个概念常常在C++编程中出现,如果没弄清楚这几个概念的意思,有时会在看一些C++书籍时会感到特别困惑。
左值和右值是表达式的属性。一些表达式生成或要求左值,而另外一些则生成或要求右值。一般而言,一个左值表达式表示的是一个对象的身份,而一个右值表达式表示的是对象的值。
左值、右值最初是出现在C语言中的,左值最初指的是可出现在赋值语句左边的实体,引入了const关键字之后,常规变量和 const 变量都可视为左值, 因为可通过地址访问它们,但常规变量属于可修改的左值, 而 const 变量属于不可修改的左值。右值一般是没有明确的内存位置,无法使用&获取地址,值不可被修改。例如:立即数、常量、字面值、&(变量)。
在C++中,增加了引用的概念,所以会出现左值引用、右值引用,下文将举例说明这几个概念。
🎄二、左值(lvalue)
左值:英文简写为“lvalue”,是“locator value”的缩写,可意为存储在内存中、有明确存储地址(可寻址)的数据。
左值:本质上是可以操作的一块内存区域,一般可以通过&取到该内存起始地址,这块内存的值可以被修改(const对象除外)。除了const对象的其他左值都可以出现在赋值语句左边。
常见的左值有:
1、变量(对象);
2、const 变量(对象);
3、对指针解引用,*(指针)
;
4、数组元素,a[1]
;
5、结构体成员、类成员,s.m_a
、ps->ma
;
// g++ 13_lvalue.cpp
int main()
{
int i = 0;
i = 1; // i是变量(对象),左值
const int ci = 5;
//ci = 6; // ci是const变量(对象),是左值但不能出现在赋值号左边
int *pi = &i;
*pi = 1; // *pi 对指针解引用,左值
int arr[5] = {0,};
arr[0] = 1; // arr[0] 是数组元素,左值
struct {
int m_a;
int m_b;
} st, *pst=&st;
st.m_a = 1; // 结构体成员,左值
pst->m_b = 2;
return 0;
}
🎄三、右值(rvalue)
右值:英文简写为“rvalue”,是"read value"的缩写,指的是那些可以提供数据值的数据(不一定可以寻址,例如存储于寄存器中的数据)。
右值:指一种表达式,其结果是值而非值所在的位置。一般是没有明确的内存位置,无法使用&获取地址,值不可被修改的。例如:立即数、常量、字面值、&(变量)。另外,很多时候左值可以当右值使用。
关于右值的,有一个重要的原则,是在需要右值的地方 (除了移动构造函数) 可以用左值来代替,但是不能把右值当成左值(也就是位置)使用。当一个左值被当成右值使用时,实际使用的是它的内容(值)。
常见的右值有:
1、字面常量(立即数),引号括起的字符串除外(它们由其地址表示),42
、'a'
;
2、算术运算符(+、-、*、/、%、正号、负号
)的求值结果,1+2
;
3、逻辑运算符(&&、||、!
)的求值结果,a!=10
;
4、关系运算符(>、>=、<、<=、==、!=
)的求值结果,a>10 && a<100
;
5、函数的非引用返回值。这种返回值位于临时内存单元中,运行到下一条语句时,它们可能不再存在;
6、当条件运算符的两个表达式都是左值或者能转换成同一种左值类型时,运算的结果是左值;否则运算的结果是右值。
// g++ 13_rvalue.cpp
int get100()
{
return 100;
}
int main()
{
int i = 0, c=0;
i = 42; // 字面值42,右值
c = 'a'; // 字面值'a',右值
i = 1+2; // 算术运算符的求值结果,右值
i = (c!='a'); // 逻辑运算符的求值结果,右值
i = (c >= 'a'); // 关系运算符的求值结果,右值
i = get100(); // 函数的非引用返回值,右值
i>0?i:c = i>0?1:0; // 条件运算符的两个表达式都是左值,则可以做左值,本例的 i>0?i:c
// 条件运算符的两个表达式都是右值,则为右值,本例的 i>0?1:0;
i = c; // c是左值,左值的值可以当右值使用
return 0;
}
🎄四、左值引用
左值引用:左值引用这个词是在C++11增加了右值引用之后才出现,为了与右值引用区分开,把C++11之前出现的引用成为左值引用。所以,左值引用就是C++中的引用类型,是己定义的变量的别名,主要用作函数形参或返回值。
传统的 C++引用(现在称为左值引用) 使得标识符关联到左值,左值引用不能绑定到要求转换的表达式、字面常量或是返回右值的表达式。
const左值引用既可以绑定到左值,也可以绑定到右值。但不能修改const左值引用绑定的内容。
怎样声明、定义左值引用:左值引用的定义需要使用&
,格式一般是类型 &变量名 = 变量
。左值引用定义必须初始化,且初始化后无法改变该引用关联的对象。下面例子介绍了左值引用的定义、const左值引用初始化为左值、const左值引用初始化为右值,
// g++ 13_lvalue_reference.cpp
int get100()
{
return 100;
}
int main()
{
int i = 0;
int &ri = i; // 左值引用
// int &*pri = &ri; // 不能定义引用的指针。报错:cannot declare pointer to ‘int&’
// int &&rri = ri; // 不能定义引用的引用。在C++11标准里,这是一个右值引用
int *pi = &i;
int *&rpi = pi; // pi是左值,rpi是指针的引用
int &r_pi = *pi; // *pi是左值,可以初始化左值引用
int arr[5] = {0};
int &rarr = arr[0]; // arr[0]是左值,可以初始化左值引用
// const 左值引用可以被初始化为一些左值
const int &cri = i; // const 左值引用
// const int *&crpi = pi; // 报错:用 ‘int*’ 初始化 ‘const int*&’ 无效
const int &cr_pi = *pi; // const 左值引用
const int &crarr= arr[0];
// const 左值引用可以被初始化为右值
const int &ri0 = 42;
const int &ri1 = 'a';
const int &ri2 = 1+2;
const int &ri3 = (ri1 != 'a');
const int &ri4 = (ri1 >= 'a');
const int &ri5 = get100(); // 函数的非引用返回值,右值
const int &ri6 = i>0?1:0;
// ri0 = 1; // 报错,const引用是只读的,其值不能修改
return 0;
}
左值引用常用作函数的形参、返回值。返回引用的函数是左值的,意味着这些函数返回的是对象本身而非对象的副本。返回左值引用的函数, 连同赋值、下标、解引用和前置递增/递减运算符, 都是返回左值的表达式的例子。
另外,应尽可能将引用形参声明为 const ,有以下三个理由:
• 使用 const 可以避免无意中修改数据的编程错误;
• 使用 const 使函数能够处理 const 和非 const 实参, 否则将只能接受非 const 数据;
• 使用 const 引用使函数能够正确生成并使用临时变量。
如果实参为右值, const 引用形参将指向一个临时变量。
🎄五、右值引用(rvalue reference)
右值引用:这是为了支持移动操作,C++11标准引入的一种新的引用类型。所谓右值引用就是必须绑定到右值的引用。我们通过 &&
而不是 &
来获得右值引用。编译时,需要加 -std=c++11 来支持C++11标准。
类似任何引用,一个右值引用也不过是某个对象的另一个名字而已。
✨5.1 右值引用的理解
怎样定义右值引用?右值引用使用
&&
来定义,也是必须初始化,初始化后无法改变其绑定的对象。定义右值引用格式:类型 &&引用名称 = 右值;
。定义一个右值引用可以参考下面代码:int &&rl = 13; int *pi = &rl;
将右值关联到右值引用导致该右值被存储到特定的位置,且可以获取该位置的地址。也就是说,虽然不能将运算符&用于 13,但可将其用于 rl。通过将数据与特定的地址关联,使得可以通过右值引用来访问该数据。
右值引用只能绑定到一个右值,右值要么是字面常量,要么是在表达式求值过程中创建的临时对象;
而临时对象有两个特点:
1、该对象将要被销毁;
2、该对象没有其他用户再使用它。
这就意味着,右值引用的代码是最后使用这个对象的了,可以自由地接管所引用的对象的资源。
如上图,变量a、b相加之后会产生一个值,这个值就是临时量,但它在内存中肯定是存在某个地址的,没有右值引用之前,这个值使用完就会被销毁,我们也不会知道它的内存地址。现在,这块内存可以被右值引用关联,关联后,右值引用甚至可以改变内存的内容,等右值引用使用完再销毁。
看看下面关于右值引用的例子,加深理解:
// g++ 13_rvalue_reference.cpp -std=c++11
#include <iostream>
using namespace std;
int get100()
{
return 100;
}
void fun(int &&rri)// 右值引用作为函数形参
{
rri = 0;
}
int main()
{
// 1、右值引用必须被初始化为右值
int &&ri0 = 42; // 将 42 存到一个临时量,然后引用这个临时量
int &&ri1 = 'a'; // 将 'a' 存到一个临时量,然后引用这个临时量
int &&ri2 = 1+2; // 将 1+2 存到一个临时量,然后引用这个临时量
int &&ri3 = (ri1 != 'a');
int &&ri4 = (ri1 >= 'a');
int &&ri5 = get100(); // 函数的非引用返回值,右值
int &&ri6 = ri0>0?1:0;
// 2、右值引用的内容可以被修改
ri0 = 1;
cout << "ri0=" << ri0 << endl;
// 3、虽然没办法获取右值的地址,但可以获取右值引用的地址,并改变该地址的值
int *pi = &ri0;
*pi = 2;
cout << "pi=" << pi << ", *pi=" << *pi << ", ri0=" << ri0 << endl;
// 4、传入右值
fun(1+2);
return 0;
}
✨5.2 std::move
虽然不能将一个右值引用直接绑定到一个左值上, 但我们可以显式地将一个左值转换为对应的右值引用类型。通过调用 std::move
的标准库函数可以获取绑定到左值上的右值引用,此函数定义在 utility 头文件中。
int i = 0;
int &&rri = std::move(i);
std::move 告诉编译器,我们有一个左值,但希望像右值一样处理它。
注意,使用 std::move 函数处理的对象,被成为移后源对象,我们可以修改该对象的值,但不可以再使用这个对象的值了,虽然语法上允许,但可能产生一些不可知的结果。因为调用了std::move 函数处理一个对象,也应被视为即将销毁的对象,如果这个对象还需要在后面的代码使用,则不建议对它调用 std::move 函数处理。
🎄六、总结
👉本文介绍了C++的左值、右值、左值引用、右值引用。左值一般表示某一块内存,可以获取其地址;右值则一般只表示数据,不能被获取地址,很多情况,左值的值可以是右值;左值引用是某个左值的别名;右值引用是某个右值的别名。
如果文章有帮助的话,点赞👍、收藏⭐,支持一波,谢谢 😁😁😁