左值引用与右值引用
左值引用和右值引用的区别?右值引用的意义
-
左值引用是对左值的引用,右值引用是对右值的引用
-
左值右值的概念
-
左值:可以在等号左边,能够取地址,并且具备名字的(左值可以放在右边,只要能够放在等号左边就是左值)(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 数据成员。
构造函数的作用可以总结为以下几点:
- 初始化对象的成员变量,确保对象在创建后的初始状态是正确的。
- 分配内存或资源,并对其进行初始化,如动态分配内存、打开文件、连接网络等。
- 对象创建时执行一些必要的操作,如日志记录、统计等。
- 可以提供默认参数,方便用户使用。
注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被销毁时,其析构函数会自动被调用,输出一条提示信息。