1. 左值和右值
簡單的定義來說,能夠放在賦值等號左邊的就是左值,反之則是右值(所有運算式不是左值就是右值,左右值不存在交集)——但是這個解釋實在有點雞肋,下面對定義結合例子做些補充,
- 右值:在記憶體中不占有記憶體的運算式
- 左值:在記憶體中占有一定記憶體位置的運算式
1 int i; 2 i = 2; //合法 3 2 = i; //非法
例子合法性很好理解——可以在結合左右值的定義,
1 #include<iostream> 2 int main() 3 { 4 int t; 5 int* q = &(t + 1);//非法,t+1在記憶體中沒有位置 6 7 int arr[] = { 1,2,3 }; int r[] = { 1,2,3 }; 8 int* i = arr; //合法 9 std::cout << arr << std::endl; // 006FF808 10 arr = r; // 非法 11 *(arr) = 10;//合法 12 }
在這里,我們需要理解下第八行,arr 作為右值—— 注意 arr 是記憶體地址,是一個地址,不同于第一個例子中的 i ,在第九行可以看到列印的 arr 代表的地址內容,第十行也就必定是錯誤的(是右值就不可能作為左值),
而第11行,arr 貌似變成了左值——也就是左值和右值在一定程度上是可以轉換的,其中扮演關鍵角色的就是 * 解參考,既然是左值,按照我們前面說的,那應該在記憶體中存在一處分配的記憶體位置,*arr 解參考之后的確是存了 1 這個數,所以可以重新賦值10,
2. 右值參考
通過下面的例子來理解:
1 #include<iostream> 2 #include<string> 3 using namespace std; 4 struct A 5 { 6 // 建構式 7 explicit A(size_t _size = 0) :size(_size),ptr(new int[_size]()) { 8 cout << "construct..."<< endl; 9 } 10 11 // 拷貝賦值運算子 12 A& operator=(const A& tmp) { 13 if (&tmp != this) 14 { 15 this->ptr = new int[tmp.size]; 16 for (size_t i = 0; i < tmp.size; ++i) { 17 this->ptr[i] = tmp.ptr[i]; 18 } 19 } 20 cout << "copy assignment 等于..." << endl; 21 return *this; 22 } 23 24 // 拷貝建構式 25 A(const A& tmp) { 26 this->ptr = new int[tmp.size]; 27 for (size_t i = 0; i < tmp.size; ++i) { 28 this->ptr[i] = tmp.ptr[i]; 29 } 30 cout << "copy construct..." << endl; 31 } 32 33 // 解構式 34 ~A() { 35 if (ptr) 36 { 37 delete[] ptr; 38 size = 0; 39 cout << "destructor..." << endl; 40 } 41 } 42 43 private: 44 int* ptr; 45 size_t size; 46 }; 47 48 int main() 49 { 50 A a(10); 51 A b; 52 53 cout<< "=====start=====" <<endl; 54 b = a; 55 cout << "=====end=====" << endl << endl; 56 57 cout << "=====start2=====" << endl; 58 A c = a;//是拷貝構造,或者加上explicit改成A c(a) 可能更好理解 59 cout << "=====end2=====" << endl << endl; 60 61 A d; 62 cout << "=====start3=====" << endl; 63 d = A(5); 64 cout << "=====end3=====" << endl << endl; 65 66 return 0; 67 }

結果上看,應該都比較好理解,
其中第三處是要引出的重點,可以看到應該使用了 d = A(10),這條陳述句,A() 是一個臨時變數,在完成 construct -> copy assignment 之后就進行了 destructor,我們來討論下這一句賦值陳述句的內部程序,A(10) 呼叫建構式并申請了記憶體空間,然后去實作拷貝賦值運算子,臨時變數A(10)的內容【copy】給 d 變數,隨后 A(10) 被銷毀,而我們知道在 copy assignment (拷貝賦值運算子)方法中,我們又為 d 變數同樣申請了 大小為10的陣列空間,這就出現了一定的“低效處理”,不妨這樣想,如果我們將A(10)申請的空間,通過拷貝賦值運算子,直接將這個空間給變數d,這樣d就不用再去申請空間(反正A(10)的空間申請了,馬上就會釋放,相當于白白多申請和釋放了一次,不如直接將A(10)的空間轉移到d變數名下),
這就引入了右值參考,如下例子:
1 #include<iostream> 2 #include<string> 3 using namespace std; 4 struct A 5 { 6 // 建構式 7 explicit A(size_t _size = 0) :size(_size),ptr(new int[_size]()) { 8 cout << "construct..."<< endl; 9 } 10 11 // 拷貝賦值運算子 12 A& operator=(const A& tmp) { 13 if (&tmp != this) 14 { 15 this->ptr = new int[tmp.size]; 16 for (size_t i = 0; i < tmp.size; ++i) { 17 this->ptr[i] = tmp.ptr[i]; 18 } 19 } 20 cout << "copy assignment 等于..." << endl; 21 return *this; 22 } 23 24 // 使用右值參考的拷貝賦值運算子 25 A& operator=(A&& tmp) { 26 if (&tmp != this) 27 { 28 this->ptr = tmp.ptr; 29 this->size = tmp.size; 30 31 tmp.ptr = nullptr; 32 tmp.size = 0; 33 } 34 cout << "copy assignment 等于2..." << endl; 35 return *this; 36 } 37 38 // 拷貝建構式 39 A(const A& tmp) { 40 this->ptr = new int[tmp.size]; 41 for (size_t i = 0; i < tmp.size; ++i) { 42 this->ptr[i] = tmp.ptr[i]; 43 } 44 cout << "copy construct..." << endl; 45 } 46 47 // 解構式 48 ~A() { 49 if (ptr) 50 { 51 delete[] ptr; 52 size = 0; 53 cout << "destructor..." << endl; 54 } 55 } 56 57 private: 58 int* ptr; 59 size_t size; 60 }; 61 62 int main() 63 { 64 A a(10); 65 A b; 66 67 cout<< "=====start=====" <<endl; 68 b = a; 69 cout << "=====end=====" << endl << endl; 70 71 cout << "=====start2=====" << endl; 72 A c = a;//是拷貝構造,或者加上explicit改成A c(a) 可能更好理解 73 cout << "=====end2=====" << endl << endl; 74 75 A d; 76 cout << "=====start3=====" << endl; 77 d = A(10); 78 cout << "=====end3=====" << endl << endl; 79 80 return 0; 81 }

可以看到通過使用右值參考,執行一樣的 d = A(5)操作會呼叫右值參考的拷貝運算子方法,這樣寫就能達到如下效果:A(5)申請了臨時空間,但是這空間通過拷貝運算子方法(右值參考)直接被 d 變數接管,d 變數無需再去申請空間了,注意:第31,32行代碼不能不寫,經過28,29行之后,d 變數和 A(5) 臨時變數的指標都指向了同一塊記憶體,而之后 A(5) 將被銷毀,所以需要將 A(5) 的指標置為 nullptr,但這部分記憶體由 d 物件接管,注意:這里看上去好像少了臨時變數 A(5) destructor(沒有列印),其實物件 A(5) 也是執行了析構,析構時釋放這個類——也就是臨時A(5)這個類,但是因為我解構式有個if條件,所以只是看上去沒有列印罷了,反正依舊會執行析構,
3. std::move()
我們先看下面的例子:
1 #include<iostream> 2 using namespace std; 3 void f(int&& i) { 4 cout << "右值參考" << endl; 5 } 6 7 void f(int& i) { 8 cout << "左值參考" << endl; 9 } 10 11 int main() 12 { 13 f(4); // 右值參考 14 int i = 3; 15 f(i); // 左值參考 16 f(int());// 右值參考.int() 呼叫默認建構式,回傳 0 值 17 18 f(std::move(i)); // 右值參考 19 f(std::move(3)); // 左值參考 20 }
因為我們已經知道了如果分辨左值和右值,所以輸出應該都在意料之內,
但是請比較第15行 和 第18行,可以看到同一個變數 i 怎么還能輸出不同的結果?其中的關鍵就是 std::move(),請注意:std::move() 并不是如上文提到的記憶體接管,而是將 左值->變換成-> 右值,
3.1 實體一:移動建構式
1 #include<iostream> 2 using namespace std; 3 class Test 4 { 5 public: 6 Test() 7 { 8 cout << "constructor " << endl; 9 } 10 11 Test(const Test& t) 12 { 13 cout << "copy constructor " << endl; 14 } 15 16 Test& operator = (const Test& t) 17 { 18 cout << "copy = constructor" << endl; return *this; 19 } 20 21 Test(Test&& t) 22 { 23 cout << "move constructor" << endl; 24 } 25 26 Test& operator =(Test&& t) 27 { 28 29 cout << "move = constructor" << endl; return *this; 30 } 31 32 ~Test() 33 { 34 cout << "destructor " << endl; 35 36 } 37 }; 38 39 int main() 40 { 41 Test a; 42 cout << "start " << endl; 43 Test b = std::move(a); 44 cout << "end " << endl << endl; 45 46 cout << "start2 " << endl; 47 Test c = a; 48 cout << "end2 " << endl << endl; 49 return 0; 50 }

注意:如果沒有 21~24行的移動建構式,則結果如下,再構造 Test b 的時候會使用賦值建構式

3.2 實體二 移動賦值函式
改變main 函式中的兩句話:
1 #include<iostream> 2 using namespace std; 3 class Test 4 { 5 public: 6 Test() 7 { 8 cout << "constructor " << endl; 9 } 10 11 Test(const Test& t) 12 { 13 cout << "copy constructor " << endl; 14 } 15 16 Test& operator = (const Test& t) 17 { 18 cout << "copy = constructor" << endl; 19 return *this; 20 } 21 22 Test(Test&& t) 23 { 24 cout << "move constructor" << endl; 25 } 26 27 Test& operator =(Test&& t) 28 { 29 30 cout << "move = constructor" << endl; 31 return *this; 32 } 33 34 ~Test() 35 { 36 cout << "destructor " << endl; 37 38 } 39 }; 40 41 int main() 42 { 43 Test a; 44 Test b; 45 cout << "start " << endl; 46 b = std::move(a); 47 cout << "end " << endl << endl; 48 49 Test c; 50 cout << "start2 " << endl; 51 c = a; 52 cout << "end2 " << endl << endl; 53 return 0; 54 }

完結,撒花,*★,°*:.☆( ̄▽ ̄)/$:*.°★* ,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/102076.html
標籤:C++
