网站首页 > 基础教程 正文
我们知道,编程语言中赋值表达式左边的值称为左值,右边的值称为右值。左值对应内存的一段命名空间,可以用取址运算符“&”取出内存地址,此时的内存单元有或显式或隐式使用的地址值和实际值。右值则是显式利用的是内存单元的实际值,其临时存在于内存或寄存器中。
使用“&&”运算符可以定义一个右值引用,将一个右值转变为一个左值,move()函数可以将一个左值转换为一个右值:
int &&i = 1; int b = 2; cout << i << endl; i = b; cout << i << endl; int &&rr = move(b); cout<<rr<<endl; //输出1 2 2
需要注意的是:
左值引用, 使用 T&, 只能绑定左值(其中T是一个具体类型);
右值引用, 使用 T&&, 只能绑定右值;
常量左值, 使用 const T&, 既可以绑定左值又可以绑定右值;
已命名的右值引用,编译器会认为是个左值;
回顾一下如何用c++实现一个字符串类MyString,MyString内部管理一个C语言的char *数组,这个时候一般都需要实现拷贝构造函数和拷贝赋值函数,因为默认的拷贝是浅拷贝,而指针这种资源不能共享,不然一个析构了,另一个也就完蛋了。
具体代码如下:
#include <iostream> #include <cstring> #include <vector> using namespace std; class MyString { public: static size_t Ctor; //统计调用拷贝构造函数的次数 static size_t CCtor; //统计调用拷贝构造函数的次数 public: // 构造函数 MyString(const char* cstr=0){ if (cstr) { m_data = new char[strlen(cstr)+1]; strcpy(m_data, cstr); } else { m_data = new char[1]; *m_data = '\0'; } Ctor++; } // 拷贝构造函数 MyString(const MyString& str) { CCtor ++; m_data = new char[ strlen(str.m_data) + 1 ]; strcpy(m_data, str.m_data); } // 拷贝赋值函数 =号重载 MyString& operator=(const MyString& str){ if (this == &str) // 避免自我赋值!! return *this; delete[] m_data; m_data = new char[ strlen(str.m_data) + 1 ]; strcpy(m_data, str.m_data); return *this; } ~MyString() { delete[] m_data; } char* get_c_str() const { return m_data; } private: char* m_data; }; size_t MyString::Ctor = 0; size_t MyString::CCtor = 0; int main() { vector<MyString> vecStr; vecStr.reserve(1000); //先分配好1000个空间,不这么做,调用的次数可能远大于1000 for(int i=0;i<1000;i++){ vecStr.push_back(MyString("hello")); } cout << MyString::Ctor << endl; cout<<MyString::CCtor << endl; } 输出: 1000 1000
执行了1000次的构造函数和拷贝构造函数,MyString("hello")构造出来的字符串只是临时对象,拷贝完就没什么用了,这就造成了没有意义的资源申请和释放操作,如果能够直接使用临时对象已经申请的资源,既能节省资源,又能节省资源申请和释放的时间。而C++11新增加的移动语义就能够做到这一点。
要实现移动语义就必须增加两个函数:移动构造函数和移动赋值构造函数。
#include <iostream> #include <cstring> #include <vector> using namespace std; class MyString { public: static size_t CCtor; //统计调用拷贝构造函数的次数 static size_t MCtor; //统计调用移动构造函数的次数 static size_t CAsgn; //统计调用拷贝赋值函数的次数 static size_t MAsgn; //统计调用移动赋值函数的次数 public: // 构造函数 MyString(const char* cstr=0){ if (cstr) { m_data = new char[strlen(cstr)+1]; strcpy(m_data, cstr); } else { m_data = new char[1]; *m_data = '\0'; } } // 拷贝构造函数 MyString(const MyString& str) { CCtor ++; m_data = new char[ strlen(str.m_data) + 1 ]; strcpy(m_data, str.m_data); } // 移动构造函数 MyString(MyString&& str) noexcept :m_data(str.m_data) { MCtor ++; str.m_data = nullptr; //不再指向之前的资源了 } // 拷贝赋值函数 =号重载 MyString& operator=(const MyString& str){ CAsgn ++; if (this == &str) // 避免自我赋值!! return *this; delete[] m_data; m_data = new char[ strlen(str.m_data) + 1 ]; strcpy(m_data, str.m_data); return *this; } // 移动赋值函数 =号重载 MyString& operator=(MyString&& str) noexcept{ MAsgn ++; if (this == &str) // 避免自我赋值!! return *this; delete[] m_data; m_data = str.m_data; str.m_data = nullptr; //不再指向之前的资源了 return *this; } ~MyString() { delete[] m_data; } char* get_c_str() const { return m_data; } private: char* m_data; }; size_t MyString::CCtor = 0; size_t MyString::MCtor = 0; size_t MyString::CAsgn = 0; size_t MyString::MAsgn = 0; int main() { vector<MyString> vecStr; vecStr.reserve(1000); //先分配好1000个空间 for(int i=0;i<1000;i++){ vecStr.push_back(MyString("hello")); } cout << "CCtor = " << MyString::CCtor << endl; cout << "MCtor = " << MyString::MCtor << endl; cout << "CAsgn = " << MyString::CAsgn << endl; cout << "MAsgn = " << MyString::MAsgn << endl; } /* 结果 CCtor = 0 MCtor = 1000 CAsgn = 0 MAsgn = 0 */
可以看到,移动构造函数与拷贝构造函数的区别是,拷贝构造的参数是const MyString& str,是常量左值引用,而移动构造的参数是MyString&& str,是右值引用,而MyString("hello")是个临时对象,是个右值,优先进入移动构造函数而不是拷贝构造函数。而移动构造函数与拷贝构造不同,它并不是重新分配一块新的空间,将要拷贝的对象复制过来,而是将临时对象变成了相对持久的内存对象,将自己的指针指向别人的资源,然后将别人的指针修改为nullptr,这一步很重要,如果不将别人的指针修改为空,那么临时对象析构的时候就会释放掉这个资源。
参考:https://www.jianshu.com/p/d19fc8447eaa
-End-
猜你喜欢
- 2024-10-12 全面剖析 C++ Boost 智能指针!| CSDN 博文精选
- 2024-10-12 C++设计模式——原型模式 设计模式之原型模式
- 2024-10-12 如何攻克 C++ 中复杂的类型转换? c++中四种类型转换的方式
- 2024-10-12 C++|由成员函数到运算符重载(类内、类外、友元方式重载)
- 2024-10-12 C++11新特性(49)- 用移动类对象代替拷贝类对象
- 2024-10-12 C++类的默认成员函数 c++类中定义的成员默认访问属性为( )
- 2024-10-12 C++的23种设计模式(上篇-创建型模式)
- 2024-10-12 C++构造函数和析构函数详解 c语言构造函数和析构函数
- 2024-10-12 c++——默认成员函数 c++成员变量默认值
- 2024-10-12 C++|类中实现操作符重载,用操作符代替成员函数名
- 最近发表
- 标签列表
-
- gitpush (61)
- pythonif (68)
- location.href (57)
- tail-f (57)
- pythonifelse (59)
- deletesql (62)
- c++模板 (62)
- css3动画 (57)
- c#event (59)
- linuxgzip (68)
- 字符串连接 (73)
- nginx配置文件详解 (61)
- html标签 (69)
- c++初始化列表 (64)
- exec命令 (59)
- canvasfilltext (58)
- mysqlinnodbmyisam区别 (63)
- arraylistadd (66)
- node教程 (59)
- console.table (62)
- c++time_t (58)
- phpcookie (58)
- mysqldatesub函数 (63)
- window10java环境变量设置 (66)
- c++虚函数和纯虚函数的区别 (66)