通俗来讲,左值就是有名字有内存的量;右值就是没有名字的,比如函数返回的时候,有可能会在调用函数栈帧上构造的临时量,或者是没内存的,比如1、"hello"等
int a = 1;
int&& b = a; // 无法从int转换为int &&
int& b = 1; // 无法从int转换为int &
/*
int tmp = 1;
const int& a = tmp;
*/
const int& a = 1;
/*
int tmp = 1;
const int& a = tmp;
*/
int&& a = 1;
const左值引用和右值引用的汇编指令如下:
不管const左值引用,还是右值引用,都是先开辟一块空间,把值拷贝到该空间,然后引用会指向这个地址
const左值引用是不能修改值的,而右值引用可以修改值,如下:
// String是一个自定义类,见博客下方代码
String& str1 = String("hello"); // 无法从String转换为String &,String("hello")是临时量,属于右值
const String& str2 = String("hello"); // const左值引用可以引用右值
String&& str3 = String("hello"); // 右值引用可以引用右值
一个右值引用变量,本身是一个左值,有内存有名字
int&& a = 1; // a是左值,名字是a
int&& b = a; // 无法从int转换为int &&
一个左值引用变量,还是一个左值
int a = 1;
int& b = a;
int& c = b; // 编译通过
class String{
public:
String(const char* str = nullptr){
cout << "String(const char*)" << endl;
if (str != nullptr){
m_data = new char[strlen(str) + 1];
strcpy(m_data, str);
}
else{
m_data = new char[1];
*m_data = '\0';
}
}
String(const String& src){
cout << "String(const String& src)" << endl;
m_data = new char[strlen(src.m_data) + 1];
strcpy(m_data, src.m_data);
}
~String(){
cout << "~String()" << endl;
delete[]m_data;
m_data = nullptr;
}
//调用String&是为了支持连续的operator=赋值操作
String& operator=(const String& src){
cout << "operator=(const String& src)" << endl;
if (&src == this){
return *this;
}
delete[]m_data;
m_data = new char[strlen(src.m_data) + 1];
strcpy(m_data, src.m_data);
return *this;
}
const char* c_str() const {
return m_data;
}
private:
char* m_data;//用于保存字符串
};
String get_string(String& str) {
const char* pstr = str.c_str();
String tmp(pstr);
return tmp;
}
int main() {
String str1("11111111111111111");
String str2;
str2 = get_string(str1);
cout << str2.c_str() << endl;
return 0;
}
调用构造、析构函数次数太多,返回的tmp还会构造一个临时对象,用于给str2赋值,赋值完成后这个临时对象会析构,有很多的空间开辟释放以及数据拷贝的操作
解决方法一
返回的时候,这个临时对象不需要构造,直接返回char*即可自动调用构造函数。优化后的写法如下:
String get_string(String& str) {
return str.c_str();
}
int main() {
String str1("11111111111111111");
String str2 = get_string(str1); // 直接使用返回的char*构造str2
cout << str2.c_str() << endl;
return 0;
}
解决方法二
我们说,返回的tmp还会构造一个临时对象,用于给str2赋值,赋值完成后这个临时对象会析构,有很多的空间开辟释放以及数据拷贝的操作
我们这样优化:用临时对象给别的对象赋值时,不进行空间的开辟和数据拷贝,以及临时对象资源的释放,而是让被赋值对象直接拥有临时对象的资源,即让str2直接指向tmp的资源,然后把tmp置空,tmp出作用域析构时什么也不做
给String类添加带右值引用的拷贝构造函数和赋值运算符重载函数
// 临时对象(右值)会匹配到右值引用为参数的函数,这里的src是临时对象(右值)
String(String&& src) {
cout << "String(String&& src)" << endl;
m_data = src.m_data;
src.m_data = nullptr;
}
String& operator=(String&& src) {
cout << "operator=(String&& src)" << endl;
if (&src == this){
return *this;
}
delete[]m_data;
m_data = src.m_data; // 改变堆区资源指向
src.m_data = nullptr; // 临时对象的指针置空,防止析构的时候释放资源
return *this;
}
String get_string(String& str) {
const char* pstr = str.c_str();
String tmp(pstr);
return tmp;
}
int main() {
String str1("11111111111111111");
String str2;
str2 = get_string(str1);
cout << str2.c_str() << endl;
return 0;
}
调用普通构造函数时,是需要开辟空间的,而调用带右值引用的拷贝构造函数和赋值运算符重载函数是没有空间开辟、释放以及资源拷贝等操作的
给String类添加operator+重载运算符函数以及输出运算符重载函数
String operator+(const String& str1, const String& str2) {
char* str = new char[strlen(str1.m_data) + strlen(str2.m_data) + 1];
strcpy(str, str1.m_data);
strcat(str, str2.m_data);
return String(str);
}
ostream& operator<<(ostream& out, const String& str) {
out << str.m_data;
return out;
}
int main() {
String str1("hello ");
String str2("world");
String str = str1 + str2;
cout << str << endl;
return 0;
}
以上代码是可以成功执行的,但是有一个问题,就是new出来的空间str,并没有delete。我们做如下修改:
String operator+(const String& str1, const String& str2) {
char* str = new char[strlen(str1.m_data) + strlen(str2.m_data) + 1];
strcpy(str, str1.m_data);
strcat(str, str2.m_data);
String tmp(str); // 普通构造,需要开辟空间
delete[] str;
return tmp;
}
虽然执行成功,而且成功释放了堆内存,缺点是new了一块内存,然后给tmp对象做构造,此时需要再new一块空间,然后str马上就被delete,这样效率很低
我们做如下的修改:
String operator+(const String& str1, const String& str2) {
String tmp;
tmp.m_data = new char[strlen(str1.m_data) + strlen(str2.m_data) + 1];
strcpy(tmp.m_data, str1.m_data);
strcat(tmp.m_data, str2.m_data);
return tmp; // 右值引用拷贝构造
}
这样就没有多余的new操作了
String str1("hello ");
vector<String> vec;
vec.reserve(10);
cout << "=========================" << endl;
vec.push_back(str1); // 用str1在vec数组拷贝构造一个对象
/*
先用"123"调用普通构造函数,构造出一个临时对象,然后用临时对象拷贝构造vec数组内的元素(这里匹配右值拷贝构造)
这条语句执行完,会析构"123"构造的临时对象,此时什么也没做,因为该临时对象的m_data已经为nullptr了
*/
vec.push_back("123");
cout << "=========================" << endl;