前言:
前面我們已經將C++的重點語法講的大差不差了,但是在C++11版本之后,又出來了很多新的語法,其中有一些作用還是非常大的,今天我們就先來學習其中一個很重要的點——右值引用以及它所擴展的移動定義
目錄
一、左值引用和右值引用
左值引用?
右值引用
二、左值引用與右值引用的比較
三、右值引用的使用
移動構造
移動賦值
四、總結
一、左值引用和右值引用
左值引用?
左值引用是最常見的引用類型,通常用于綁定到一個左值。左值是一個具有名稱的對象,可以取地址,通常出現在賦值操作符的左邊。(簡單的說,能取地址的就是左值)
語法:
類型 &引用名 = 左值;
示例:
int a = 10;
int &refA = a; // refA是一個左值引用,綁定到左值a
特點:
- 左值引用必須初始化,并且只能綁定到左值。
- 左值引用可以修改綁定的對象。
右值引用
右值引用是C++11引入的新特性,用于綁定到一個右值。右值是一個臨時對象,通常沒有名稱,不能取地址,通常出現在賦值操作符的右邊。(右值不能取地址,比如常量)
語法:
類型 &&引用名 = 右值;
示例:
int &&refB = 20; // refB是一個右值引用,綁定到右值20
特點:
- 右值引用必須初始化,并且只能綁定到右值。
- 右值引用主要用于實現移動語義和完美轉發。
有一個需要強調的是,常變量雖然也屬于常量,但是它可以取地址,所以它屬于左值
二、左值引用與右值引用的比較
左值引用:
1. 左值引用只能引用左值,不能引用右值。2. 但是const左值引用既可引用左值,也可引用右值
int main()
{// 左值引用只能引用左值,不能引用右值。int a = 10;int& ra1 = a; ? // ra為a的別名//int& ra2 = 10; ? // 編譯失敗,因為10是右值// const左值引用既可引用左值,也可引用右值。const int& ra3 = 10;const int& ra4 = a;return 0;
}
右值引用:
1. 右值引用只能右值,不能引用左值。2. 但是右值引用可以move以后的左值。
int main()
{// 右值引用只能右值,不能引用左值。int&& r1 = 10;// error C2440: “初始化”: 無法從“int”轉換為“int &&”// message : 無法將左值綁定到右值引用int a = 10;int&& r2 = a;// 右值引用可以引用move以后的左值int&& r3 = std::move(a);return 0;
}
三、右值引用的使用
在上面我們也已經講到了,左值引用及可以引用左值,又可以引用右值,那么C++11為什么還要設計右值引用呢?下面我們來看一下原因。
我們借助string類來講解
先來看一下下面所出現的所有代碼,可以先思考看看思考思考
namespace zda
{class string{public:typedef char* iterator;iterator begin(){return _str;}iterator end(){return _str + _size;}string(const char* str = ""):_size(strlen(str)), _capacity(_size){//cout << "string(char* str)" << endl;_str = new char[_capacity + 1];strcpy(_str, str);}// s1.swap(s2)void swap(string& s){::swap(_str, s._str);::swap(_size, s._size);::swap(_capacity, s._capacity);}// 拷貝構造string(const string& s):_str(nullptr){cout << "string(const string& s) -- 深拷貝" << endl;string tmp(s._str);swap(tmp);}// 賦值重載string& operator=(const string& s){cout << "string& operator=(string s) -- 深拷貝" << endl;string tmp(s);swap(tmp);return *this;}// 移動構造string(string&& s) //右值引用:_str(nullptr), _size(0), _capacity(0){cout << "string(string&& s) -- 移動語義" << endl;swap(s);}// 移動賦值string& operator=(string&& s) //右值引用{cout << "string& operator=(string&& s) -- 移動語義" << endl;swap(s);return *this;}~string(){delete[] _str;_str = nullptr;}char& operator[](size_t pos){assert(pos < _size);return _str[pos];}void reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}void push_back(char ch){if (_size >= _capacity){size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;reserve(newcapacity);}_str[_size] = ch;++_size;_str[_size] = '\0';}//string operator+=(char ch)string& operator+=(char ch){push_back(ch);return *this;}const char* c_str() const{return _str;}private:char* _str;size_t _size;size_t _capacity; // 不包含最后做標識的\0};string to_string(int value){bool flag = true;if (value < 0){flag = false;value = 0 - value;}string str;while (value > 0){int x = value % 10;value /= 10;str += ('0' + x);}if (flag == false){str += '-';}std::reverse(str.begin(), str.end());return str;}
}
左值引用使用場景:
void func1(bit::string s)
{}
void func2(const bit::string& s)
{}
int main()
{bit::string s1("hello world");// func1和func2的調用我們可以看到左值引用做參數減少了拷貝,提高效率的使用場景和價值func1(s1);func2(s1);// string operator+=(char ch) 傳值返回存在深拷貝// string& operator+=(char ch) 傳左值引用沒有拷貝提高了效率s1 += '!';return 0;
}
左值引用短板:
當函數返回對象為臨時變量的時候,左值引用就派不上用場了,就只能傳值返回,就需要拷貝至少一次(老一點的編譯器為兩次)

移動構造
// 移動構造
string(string&& s):_str(nullptr),_size(0),_capacity(0)
{cout << "string(string&& s) -- 移動語義" << endl;swap(s);
}
int main()
{zda::string ret2 = bit::to_string(-1234);return 0;
}
當返回值是右值時,因為移動構造并沒有開辟空間進行深拷貝,所以效率就會更高
需要注意的是,當拷貝構造和移動構造同時存在時,編譯器默認的也會調用移動構造,因為編譯器會默認調用效率更高的函數
移動賦值
// 移動賦值
string& operator=(string&& s)
{
cout << "string& operator=(string&& s) -- 移動語義" << endl;
swap(s);
return *this;
}
int main()
{zda::string ret1;ret1 = zda::to_string(1234);return 0;
}// 運行結果:
// string(string&& s) -- 移動語義
// string& operator=(string&& s) -- 移動語義
這里運行后發現,調用了一次移動構造和一次移動賦值,因為這里的ret1是一個已經存在的對象,用它來接受函數返回值的時候編譯器就無法再優化了,所以會在移動構造后創建一個臨時變量,且這個臨時變量會被編譯器識別為右值,從而調用移動賦值
四、總結
上面我們就簡單的先提了一下右值引用的應用:移動語義,下一篇我們再重點講解一下右值引用的另一個重點語法:完美揮發