34.左值引用和右值引用

34.引用-左值引用和右值引用


34.1 知识点

左值和右值的含义

左值和右值是表达式结果的分类,主要用于区分表达式的特性,如持久性和是否具有存储位置。它们帮助我们理解 C++ 如何管理对象的生命周期和内存使用。

左值

左值是在表达式结束后仍然存在的对象,具有持久性,通常可以被赋值。

特点如下:

  • 有明确的存储位置,可以通过取地址运算符 & 获取地址。
  • 可以出现在赋值运算符 = 的左边,因为它代表的是可修改的对象。

示例:

int x = 5;  // x 是左值,因为它有存储位置,可以被修改
// 输出左值 x 的地址,证明它有存储位置
std::cout << "x 的地址: " << &x << std::endl;
x = 10;    // 可以给左值 x 赋值,因为它是持久存在的
int* p = &x; // 可以获取左值 x 的地址并存储在指针 p 中

右值

右值是临时对象或不具持久性的值,一般在表达式计算完成后即被释放。

特点如下:

  • 通常没有明确的存储位置,不能直接使用取地址运算符 & 获取地址。
  • 右值表达式一般只能出现在赋值运算符 = 的右边,因为它是临时的。
  • 右值通常是临时计算的结果,或者没有名称的值,比如字面值、临时对象等。

示例:

int y = 5 + 10;  // 这里 5 + 10 的结果是一个右值,它是临时的,计算完成后就不再保留
// 以下代码会导致编译错误,因为无法获取右值的地址
// int* p2 = &(5 + 10);

简单记忆左右值的区别

  • 左值:有名称、持久存储在内存中的对象,可以被持续引用和修改。
  • 右值:在计算过程中产生的临时值,用完即弃,不具有持久性。

左值引用

左值引用的定义

我们之前学习的引用(使用 & 符号声明)是左值引用。左值引用只能绑定到左值,即绑定到持久存储在内存中的变量或对象。

示例:

int a = 10;
int& refA = a;  // 这是一个左值引用,refA 是 a 的别名

左值引用的限制

  • 左值引用不能绑定到右值。

以下代码会导致编译错误:

// int& refB = 15;  // 15 是右值,不能被左值引用绑定
// int& refC = getInt();  // getInt() 的返回值是右值,不能被左值引用绑定

原因:左值引用需要一个持久的存储位置,而右值是临时的。

右值引用

右值引用的定义

右值引用使用 && 符号声明,右值引用只能绑定到右值,如函数返回值、字面量等临时对象。

右值引用的特点

  • 可以绑定到其他右值。
  • 延长了右值的生命周期,使其在引用的作用域内持续存在。

示例

int&& refB = 15;  // 这是一个右值引用,refB 绑定到右值 15
std::cout << "refB 绑定到 15 的值: " << refB << std::endl;  // 输出右值引用 refB 的值
refB = getInt();  // 可以将右值引用绑定到另一个右值(getInt() 的返回值)
std::cout << "refB 绑定到 getInt() 的值: " << refB << std::endl;  // 输出更新后的 refB 的值

注意事项

右值引用的意义在当前阶段可能不太明显,但在后续学习中会变得重要,例如在移动语义和完美转发中。


34.2 知识点代码

Lesson34_引用_左值引用和右值引用.cpp

#include <iostream>
using namespace std;

// 函数声明,用于返回一个整数
int getInt();

int main()
{
    std::cout << "左值引用和右值引用\n";

    #pragma region 知识点一 左值和右值的含义

    // 左值和右值是表达式结果的分类,主要用于区分表达式的特性,如持久性和是否具有存储位置
    // 它们帮助我们理解 C++ 如何管理对象的生命周期和内存使用

    // 左值:
    // 左值是在表达式结束后仍然存在的对象,具有持久性,通常可以被赋值
    // 其特点如下:
    // 1. 有明确的存储位置,可以通过取地址运算符 & 获取地址
    // 2. 可以出现在赋值运算符 = 的左边,因为它代表的是可修改的对象
    // 示例:
    int x = 5;  // x 是左值,因为它有存储位置,可以被修改
    // 输出左值 x 的地址,证明它有存储位置
    std::cout << "x 的地址: " << &x << std::endl;
    x = 10;    // 可以给左值 x 赋值,因为它是持久存在的
    int* p = &x; // 可以获取左值 x 的地址并存储在指针 p 中

    // 右值:
    // 右值是临时对象或不具持久性的值,一般在表达式计算完成后即被释放
    // 其特点如下:
    // 1. 通常没有明确的存储位置,不能直接使用取地址运算符 & 获取地址
    // 2. 右值表达式一般只能出现在赋值运算符 = 的右边,因为它是临时的
    // 3. 右值通常是临时计算的结果,或者没有名称的值,比如字面值、临时对象等
    // 示例:
    int y = 5 + 10;  // 这里 5 + 10 的结果是一个右值,它是临时的,计算完成后就不再保留
    // 以下代码会导致编译错误,因为无法获取右值的地址
    // int* p2 = &(5 + 10); 

    // 简单记忆左右值的区别:
    // 左值就是有名称的、持久存储在内存中的对象,可以被持续引用和修改
    // 右值就是在计算过程中产生的临时值,用完即弃,不具有持久性

    #pragma endregion

    #pragma region 知识点二 左值引用

    // 我们之前学习的引用(使用 & 符号声明)是左值引用
    // 左值引用只能绑定到左值,即绑定到持久存储在内存中的变量或对象
    int a = 10;
    int& refA = a;  // 这是一个左值引用,refA 是 a 的别名
    // 左值引用不能绑定到右值,以下代码会导致编译错误:
    // int& refB = 15;  // 15 是右值,不能被左值引用绑定
    // int& refC = getInt();  // getInt() 的返回值是右值,不能被左值引用绑定
    // 因为左值引用需要一个持久的存储位置,而右值是临时的

    #pragma endregion

    #pragma region 知识点三 右值引用

    // 右值引用使用 && 符号声明
    // 右值引用只能绑定到右值,如函数返回值、字面量等临时对象
    // 特点:
    // 1. 可以绑定到其他右值
    // 2. 右值引用延长了右值的生命周期,使其在引用的作用域内持续存在
    // 注意:
    // 右值引用的意义在当前阶段可能不太明显,但在后续学习中会变得重要,例如在移动语义和完美转发中
    int&& refB = 15;  // 这是一个右值引用,refB 绑定到右值 15
    std::cout << "refB 绑定到 15 的值: " << refB << std::endl;  // 输出右值引用 refB 的值
    refB = getInt();  // 可以将右值引用绑定到另一个右值(getInt() 的返回值)
    std::cout << "refB 绑定到 getInt() 的值: " << refB << std::endl;  // 输出更新后的 refB 的值

    #pragma endregion
}

// 函数定义,返回一个整数
int getInt()
{
    int i = 666;
    return i;
}

34.3 练习题

左值和右值的区别是什么?

左值:指一个右持久内存地址的对象,可以出现在赋值语句的左边

右值:指一个没有持久内存地址的临时值,通常是字面量、临时对象或表达式的结果

区别是:

  • 左值有持久地址,可以取得地址;右值通常没有持久地址,不能取地址
  • 左值可以出现在赋值符号的左右两边;右值只能出现在右边(大部分情况下)
  • 左值是持久的;右值往往是临时的

左值引用和右值引用的区别

  • 左值引用 和 右值引用 是两种不同类型的引用,它们分别用于绑定左值和右值。

  • 左值引用声明方式为:变量类型& 左值引用名 = 左值变量;

    右值引用声明方式为:变量类型&& 右值引用名 = 右值;



转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏