当前位置: 首页 > 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/news/326666.html

相关文章:

  • 蓝桥杯—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点餐功能
  • 心理咨询行业为何要有自己的知识付费小程序平台 心理咨询小程序搭建 集师saas知识付费小程序平台搭建
  • 遇到 Docker 镜像拉取失败的问题时该如何解决
  • 六、设计模式-6.3、责任链模式
  • WebAssembly 为什么能提升性能,怎么使用它 ?
  • 晶圆厂如何突破多网隔离实现安全稳定又快速的跨网域文件传输?
  • 执行力怎么培养?
  • 建投数据自主研发相关系统获得欧拉操作系统及华为鲲鹏技术认证书
  • ArduSub程序学习(11)--EKF实现逻辑③
  • 宠物医院微信小程序源码
  • 二叉树深搜专题篇