当前位置: 首页 > article >正文

C++ 左值右值引用梳理(一)

C++ 左值右值引用梳理(一)

左值与右值的区别

在参考资料上看到这样一句话
https://www.internalpointers.com/post/understanding-meaning-lvalues-and-rvalues-c

In C++ an lvalue is something that points to a specific memory location. On the other hand, a rvalue is something that doesn’t point anywhere. In general, rvalues are temporary and short lived, while lvalues live a longer life since they exist as variables. It’s also fun to think of lvalues as containers and rvalues as things contained in the containers. Without a container, they would expire.

总的来说,左值可以获取地址,而右值不能。lvalue指的是可以放在赋值表达式左边的事物——在栈上或堆上分配的命名对象,或者其他对象成员——有明确的内存地址。rvalue指的是可以出现在赋值表达式右侧的对象——例如,文字常量和临时变量。

同样的,在《现代C++语言核心特性解析》中有这句话:

在C++中所谓的左值一般是指一个指向特定内存的具有名称的值(具名对象),它有一个相对稳定的内存地址,并且有一段较长的生命周期。而右值则是不指向稳定内存地址的匿名值(不具名对象),它的生命周期很短,通常是暂时性的。基于这一特征,我们可以用取地址符&来判断左值和右值,能取到内存地址的值为左值,否则为右值。

右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址。左值可以在左边也可以在右边,右值只能在右边,举例说明

int x = 666;   // ok
int* y = &x;   // ok
666 = y; // error!

这里666是一个右值;一个字面常量,没有特定的内存地址。这个数字被分配给x,这是一个变量。一个变量有一个特定的内存位置,所以它是一个左值。C++声明赋值需要左值作为其左操作数。

第二句话,通过取地址运算符&将x的地址取出,&x是一个临时变量,是一个右值。
第三句话,666是一个右值,不能放在左边。

返回左值和右值的函数

对比这两个例子

int setValue()
{
    return 6;
}

// ... somewhere in main() ...

setValue() = 3; // error!

第一个例子,setValue()返回一个右值(临时数字6),它不能是赋值的左操作数。

int global = 100;

int& setGlobal()
{
    return global;    
}

// ... somewhere in main() ...

setGlobal() = 400; // OK

第二个例子,setGlobal返回一个引用,不像上面的setValue()。
参考链接:
【C 语言】变量本质 ( 变量概念 | 变量本质 - 内存空间别名 | 变量存储位置 - 代码区 | 变量三要素 )
关于引用为什么可以做左值,可以参考:,其实你看底层,就是指针的简化,理解后就可以明白为什么可以做左值。
从外到内理解c++引用,一文清晰解读。
C++引用在本质上是什么,它和指针到底有什么区别?

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

左值引用

顾名思义,左值引用就是给左值的引用,给左值取别名。右值引用就是对右值的引用,给右值取别名。而C++11中新增了的右值引用语法特性。
在赋值运算符左侧是左值引用符,右侧必须是左值;在赋值运算符左侧是右值引用符,右侧必须是右值。
看下面的例子:

int y = 10;
int& yref = y;
yref++;        // y is now 11
int& yref = 10;  // wrong
void fnc(int& x)
{
}

int main()
{
    fnc(10);  // Nope!
    // This works instead:
    // int x = 10;
    // fnc(x);
}

但是下面的例子成功:

void fnc(const int& x)
{
}

int main()
{
    fnc(10);  // OK!
}

常量引用(const T&)可以绑定到右值(right-value),这是C++11引入的新特性之一。下面举例:

const int &r = 10;

实际上,编译器会创建一个临时对象,并让常量引用 r 绑定到这个临时对象上。

const int _temp = 10;

const int &r = _temp;

这个临时对象 _temp 是由编译器隐式创建的,并且它的生命周期至少持续到引用 r 的生命周期结束。为什么这个临时对象是const类型。回答是这样的:
1.字面量值:字面量值 10 本身就是一个不可修改的值。
2.常量引用:常量引用 const int &r 确保了引用的不可修改性,即不能通过引用 r 来修改所引用的对象。

临时对象具有常性,因此也需要常量引用来绑定。

右值引用

右值引用有什么优点呢:
1.绑定临时对象,正如上面提到
2.移动语义,当一个类有移动构造和复制构造时,会优先选择移动构造:
下面的链接很好的说明了这一点:

https://stackoverflow.com/questions/68184575/why-does-c-give-preference-to-rvalue-reference-over-const-reference-while-func

const T&& 有什么用呢,为什么还需要const的右值引用呢?是在你没有const T& 的时候
下面的例子回答了这一点:
https://www.sandordargo.com/blog/2021/08/18/const-rvalue-references

#include <iostream>

struct T {};

void f(T&) { std::cout << "lvalue ref\n"; }  // #1
void f(const T&) { std::cout << "const lvalue ref\n"; }  // #2
void f(T&&) { std::cout << "rvalue ref\n"; } // #3
void f(const T&&) { std::cout << "const rvalue ref\n"; } // #4

const T g() { return T{}; }

int main() {
    f(g()); // #4, #2
}

可以看到2和4是一样的效果。
在文章中出现cv的字眼,意思是“const”和“volatile”限定符,它们可以用来修饰类型,以表明该类型的某些特性。
注意:如果返回const value,意味着你不能再使用移动语义。

#include <iostream>

class MyString {
public:
    MyString(const char* str) {
        m_length = strlen(str);
        m_data = new char[m_length + 1];
        strcpy(m_data, str);
    }

    // 拷贝构造函数
    MyString(const MyString& other) {
        m_length = other.m_length;
        m_data = new char[m_length + 1];
        strcpy(m_data, other.m_data);
        std::cout << "Copy constructor called." << std::endl;
    }

    // 移动构造函数
    MyString(MyString&& other) noexcept {
        m_length = other.m_length;
        m_data = other.m_data;
        other.m_length = 0;
        other.m_data = nullptr;
        std::cout << "Move constructor called." << std::endl;
    }

    // 拷贝赋值运算符
    MyString& operator=(const MyString& other) {
        if (this != &other) {
            delete[] m_data;
            m_length = other.m_length;
            m_data = new char[m_length + 1];
            strcpy(m_data, other.m_data);
        }
        return *this;
    }

    // 移动赋值运算符
    MyString& operator=(MyString&& other) noexcept {
        if (this != &other) {
            delete[] m_data;
            m_length = other.m_length;
            m_data = other.m_data;
            other.m_length = 0;
            other.m_data = nullptr;
        }
        return *this;
    }

    ~MyString() {
        delete[] m_data;
        std::cout << "Destructor called." << std::endl;
    }

    // 其他成员函数...
private:
    int m_length;
    char* m_data;
};

void print(MyString s) {
    std::cout << "String: " << s.m_data << std::endl;
}

int main() {
    MyString s1("Hello");

    // 使用临时对象调用移动构造函数
    MyString s2 = MyString("World");
    print(s2);

    // 使用右值引用调用移动构造函数
    MyString s3 = std::move(s1);
    print(s3);

    return 0;
}

3.完美转发,在后面会提到。
4.避免不必要的拷贝

完美转发


http://www.kler.cn/a/326666.html

相关文章:

  • 【Java语言】继承和多态(一)
  • 为啥学习数据结构和算法
  • 论文阅读-用于点云分析的自组织网络
  • (一)<江科大STM32>——软件环境搭建+新建工程步骤
  • 408——计算机网络(持续更新)
  • git 与当前代码的修改进行重新合并
  • 蓝桥杯—STM32G431RBT6(RTC时钟获取时间和日期)
  • python 如何引用变量
  • LeetCode 每日一题 最佳观光组合
  • 水波荡漾效果+渲染顺序+简单UI绘制
  • Chromium 屏蔽“缺少 Google API 密钥,因此 Chromium 的部分功能将无法使用。”提示 c++
  • Conda 虚拟环境使用指南,python,anaconda,miniconda
  • MySQL InnoDB 事务commit逻辑分析
  • C++的new关键字
  • 如何在Android上运行Llama 3.2
  • 关于TrustedInstaller权限
  • c++-类和对象-设计立方体类
  • 每天学习一个技术栈 ——【Django Channels】篇(2)
  • ansible实现远程创建用户
  • [BUUCTF从零单排] Web方向 03.Web入门篇之sql注入-1(手工注入详解)
  • Java 编码系列:注解处理器详解与面试题解析
  • Uptime Kuma运维监控服务本地部署结合内网穿透实现远程在线监控
  • PostgreSQL的扩展Citus介绍
  • 非常全面的中考总复习资料-快速提升中考成绩!
  • 总结C/C++中内存区域划分
  • 点餐小程序实战教程14点餐功能