zl程序教程

您现在的位置是:首页 >  其他

当前栏目

左值引用与右值引用

2023-04-18 14:55:21 时间

左值引用和右值引用的区别?右值引用的意义

  • 左值引用是对左值的引用,右值引用是对右值的引用

    • 左值右值的概念

      • 左值:可以在等号左边,能够取地址,并且具备名字的(左值可以放在右边,只要能够放在等号左边就是左值)(const左值引用能指引右值,局限是不能修改这个值)

        • int i = 0;//运行流程是i+1之后被赋值10
          ++i = 10; //表达式的结果是左值引用,指向 i 的内存地址
          
        • (i=9) = 100;//(i=9)可以做为做值,能够取到地址,且具备名字
          (i+=10) = 1000;//(i+=10)可以作为左值
          
        • 解引用(操作符是* 例如:*ptr。解引用是指将指针转换为实际值或对象的过程。)也可以作为左值

           int* ptr = new int(10);
           *ptr = 20; // 解引用作为左值
          
      • 右值只能在等号右边,不能够取地址,不具备名字的(关键是如何区分右值)(右值指的是不具备“可修改状态”的表达式)

        • 纯右值:字面值

          int getValue() {return 10;}
          int main() {int a = getValue();  // 右值赋值到左值
             			 return 0;}
          ---------------------------------------------------
          void myFunction(int&& value) {// do something}
          int main() {myFunction(getValue());  // 右值作为函数参数
              		return 0;}    
          //在上述代码中,myFunction()函数的参数类型为int&&,表示该参数为右值引用类型,可以接受值作为参数。
          
          
          • int i = 10,这个10是纯右值

          • 返回非引用类型的函数调用

          • 后置自增/自减

            • int x = 10;
              int y = x++; // x 先赋值给 y,再进行自增
              //这里的 x++ 表示先将 x 的值赋给 y,然后再将 x 自增,由于自增操作返回的是自增前的值,因此 x++ 是一个右值
              //举个反例子int y = ++x;
              //其中++x是一个左值引用    
              
        • 将亡值:c++11引入的一个与右值引用(移动语义:移动语义是指通过移动资源的所有权而不是复制资源,来提高代码性能的C++语言特性)相关的值类型

        • std::vector<int> createVector() {
          	std::vector<int> v = {1, 2, 3};
              return v;}
          int main() {
              std::vector<int> v2 = createVector(); // 这里会调用移动构造函数
              return 0;}
          //在createVector()函数中,我们创建了一个std::vector<int>类型的局部变量v,并在函数结束时将其返回。由于返回值是一个临时变量,这个临时变量在调用函数结束时就会被销毁。因此,在将其返回时,编译器会自动使用移动构造函数将其转换为一个将亡值,以便在函数调用结束时将其移动到调用方的变量v2中。
          
          • 用将亡值来触发移动构造注1/移动赋值构造并进行资源转移,之后将调用析构函数注2
      • 左值右值的区别

  • 左值引用

    • 避免对象的拷贝,可以直接使用内容
    • const左值引用能指引右值,局限是不能修改这个值
    • 声明出来的左值引用或者右值引用都是左值
  • 右值引用

    • 意义:
      • 实现移动语义
      • 实现完美转发
    • 通过std::move()实现指向左值

声明出来的左值引用或者右值引用都是左值,左值引用是对左值的引用,右值的引用是对右值的引用,两者都是引用,声明出来的变量都是左值,比如说int &a和int &&a中的a都是左值。const左值引用可以指向右值,const int &a就可以引用右值,但是不能修改这个值,因此需要右值引用解决这个问题右值引用也可以指向左值,可以通过move函数指向这个左值。


注1移动构造函数

​ 移动构造是一种特殊的构造函数,用于将一个对象的资源(比如内存)转移到另一个对象,同时避免不必要的内存拷贝,从而提高程序性能。通常情况下,移动构造函数会接受一个将亡值(即将被销毁的对象),并将其资源转移到一个新的对象中,然后将将亡值的指针置为null,避免其析构函数再次释放资源。

class MyString {
public:
    // 构造函数
    MyString(const char* str) {
        std::size_t size = std::strlen(str) + 1;
        data_ = new char[size];
        std::strcpy(data_, str);
    }

    // 移动构造函数
    MyString(MyString&& other) {
        data_ = other.data_;
        other.data_ = nullptr;
    }

    // 析构函数
    ~MyString() {
        delete[] data_;
    }

private:
    char* data_;
};

//在使用该类的过程中,可能会发生对象的拷贝,如下所示:
MyString s1("Hello");
MyString s2(s1); // 拷贝构造
//这时候,由于 s1 和 s2 都有自己的数据存储,因此需要进行数据的复制,这样就浪费了一部分时间和内存空间。而如果使用移动构造函数,则可以直接将 s1 的数据指针 data_ 赋给 s2,避免了数据的复制:
MyString s1("Hello");
MyString s2(std::move(s1)); // 移动构造
//在移动构造的过程中,由于 s1 的数据指针 data_ 已经被转移给了 s2,因此 s1 的析构函数就不再需要删除 data_ 了,避免了重复删除已经被释放的内存空间。

构造函数:

​ 构造函数是一种特殊的成员函数,用于创建和初始化对象。当定义一个对象时,构造函数会自动调用来初始化对象的数据成员。

class MyClass {
public:
    int x;
    // 构造函数
    MyClass(int val) {
        x = val;
        std::cout << "MyClass object is created with x = " << x << std::endl;
    }
};
int main() {
    MyClass obj(10); // 使用构造函数创建对象

    return 0;
}

//在上面的示例中,MyClass 类有一个带有一个整数参数的构造函数,用于初始化类中的数据成员 x。在 main 函数中,我们创建了一个名为 obj 的 MyClass 类型的对象,并将值 10 传递给构造函数。,这表明构造函数被成功调用,并使用传递的值 10 初始化了 x 数据成员。

构造函数的作用可以总结为以下几点:

  1. 初始化对象的成员变量,确保对象在创建后的初始状态是正确的。
  2. 分配内存或资源,并对其进行初始化,如动态分配内存、打开文件、连接网络等。
  3. 对象创建时执行一些必要的操作,如日志记录、统计等。
  4. 可以提供默认参数,方便用户使用。

注2

​ 析构函数的作用是在对象被销毁时自动执行清理操作,如释放对象所占用的内存等。当一个对象的生命周期结束时,C++编译器会自动调用其析构函数。

class MyClass {
public:
    MyClass() {
        std::cout << "MyClass constructor called" << std::endl;
    }

    ~MyClass() {
        std::cout << "MyClass destructor called" << std::endl;
    }
};

int main() {
    MyClass myObj;
    return 0;
}
//在上述代码中,我们定义了一个名为MyClass的类,其中包含了一个构造函数和一个析构函数。在main函数中,我们创建了一个MyClass对象myObj,并在程序结束时销毁它。由于MyClass包含了一个析构函数,因此在myObj被销毁时,其析构函数会自动被调用,输出一条提示信息。