深圳政府在線網站石家莊網絡seo推廣
目錄
1.?列表初始化initializer_list
2. 前面提到的一些知識點
2.1 小語法
2.2 STL中的一些變化
3. 右值和右值引用
3.1?右值和右值引用概念
3.2?右值引用類型的左值屬性
3.3?左值引用與右值引用比較
3.4?右值引用的使用場景
3.4.1 左值引用的功能和短板
3.4.2?移動構造
3.4.3?移動賦值
3.4.4?插入右值時減少深拷貝
4. 完美轉發(fā)
4.1?萬能引用(引用折疊)
4.2 完美轉發(fā)
5. 新的類功能
5.1 默認生成的移動構造/賦值
5.2 類里新的關鍵字
本篇完。
在2003年C++標準委員會曾經提交了一份技術勘誤表(簡稱TC1),使得C++03這個名字已經取代了C++98稱為C++11之前的最新C++標準名稱。不過由于C++03(TC1)主要是對C++98標準中的漏洞進行修復,語言的核心部分則沒有改動,因此人們習慣性的把兩個標準合并稱為C++98 / 03標準。從C++0x到C++11,C++標準10年磨一劍,第二個真正意義上的標準珊珊來遲。相比于C++98 / 03,C++11則帶來了數(shù)量可觀的變化,其中包含了約140個新特性,以及對C++03標準中約600個缺陷的修正,這使得C++11更像是從C++98 / 03中孕育出的一種新語言。相比較而言,C++11能更好地用于系統(tǒng)開發(fā)和庫開發(fā)、語法更加泛華和簡單化、更加穩(wěn)定和安全,不僅功能更強大,而且能提升程序員的開發(fā)效率,公司實際項目開發(fā)中也用得比較多,所以我們要作為一個重點去學習。C++11增加的語法特性非常篇幅非常多,我們這里沒辦法一 一講解,所以本主要講解實際中比較實用的語法。
1.?列表初始化initializer_list
- 列表:花括號:{ }就被叫做列表。
我們之前可以使用列表來初始化數(shù)組,初始化結構體變量,初始化元素類型為結構體變量的數(shù)組等等。
- C++11擴大了用大括號括起的列表(初始化列表)的使用范圍,使其可用于所有的內置類型和用戶自定義的類型,使用初始化列表時,可添加等號(=),也可不添加。
#include <iostream>
using namespace std;class Date
{
public:Date(int year, int month, int day):_year(year), _month(month), _day(day){cout << "Date(int year, int month, int day)" << endl;}protected:int _year = 1;int _month = 1;int _day = 1;
};int main()
{int x1 = 1;int x2 = { 2 }; // 要能看懂,但是不建議使用int x3{ 2 };Date d1(2023, 1, 1); // 都是在調用構造函數(shù)Date d2 = { 2023, 2, 2 }; // 要能看懂,但是不建議使用Date d3{ 2023, 3, 3 };return 0;
}
可以不加等號進行初始化,如上圖代碼所示,但是強烈不建議使用。
這其實很雞肋,沒有什么價值,繼續(xù)使用C++98中的方式就挺好的,而且容易理解,C++11中的方式反而不太好理解了。C++中這種雞肋的語法被很多人吐槽,理性看待。
列表初始化真正有意義的地方是用于初始化STL中的容器:
之前提到:vector和list以及map等STL中的容器也可以像普通數(shù)組一樣使用初始化列表來初始化了。這是因為列表初始化本身就是一個類模板:
如上圖所示,這是C++11才有的一個類型,該類型叫做列表初始化,而且還有自己的成員函數(shù),包括構造函數(shù),計算列表大小的接口,獲取列表迭代器位置。(但幾乎都不用)
C++11為這些容器提供了新的構造函數(shù),該構造函數(shù)是使用列表來初始化對象的,它的形參就是initializer_list,所以列表初始化才可以初始化STL中的容器。
賦值運算符重載函數(shù)也有一個列表的重載版本:
#include <iostream>
#include <vector>
#include <list>
#include <map>
using namespace std;class Date
{
public:Date(int year, int month, int day):_year(year), _month(month), _day(day){cout << "Date(int year, int month, int day)" << endl;}protected:int _year = 1;int _month = 1;int _day = 1;
};int main()
{int x1 = 1;int x2 = { 2 }; // 要能看懂,但是不建議使用int x3{ 2 };Date d1(2023, 1, 1); // 都是在調用構造函數(shù)Date d2 = { 2023, 2, 2 }; // 要能看懂,但是不建議使用Date d3{ 2023, 3, 3 };// 調用支持list (initializer_list<value_type> il)類似這樣的構造函數(shù)vector<int> v1 = { 1, 2, 3, 4, 5, 6 };vector<int> v2 { 1, 2, 3, 4, 5, 6 };list<int> lt1 = { 1, 2, 3, 4, 5, 6 };list<int> lt2{ 1, 2, 3, 4, 5, 6 };auto x = { 1, 2, 3, 4, 5, 6 };cout << typeid(x).name() << endl; // 打印初始化列表的類型vector<Date> v3 = {d1, d2, d3};vector<Date> v4 = { { 2022, 1, 1 }, {2022, 11, 11} };string s1 = "11111";map<string, string> dict = { { "sort", "排序" }, { "insert", "插入" } }; // 構造initializer_list<pair<const string, string>> kvil = { { "left", "左邊" }, { "right", "右邊" } }; // 賦值重載dict = kvil; // 上面的類型就不能用auto推導,編譯器不知道那里是一個pairreturn 0;
}
2. 前面提到的一些知識點
2.1 小語法
C++11提供了一些新的小語法,很多我們都接觸過甚至是使用過。
c++11提供了多種簡化聲明的方式,尤其是在使用模板時。這里講auto和decltype
auto:這個關鍵字我們已經使用過很多了
在C++98中auto是一個存儲類型的說明符,表明變量是局部自動存儲類型,但是局部域中定義局部的變量默認就是自動存儲類型,所以auto就沒什么價值了。C++11中廢棄auto原來的用法,將其用于實現(xiàn)自動類型推斷。這樣要求必須進行顯示初始化,讓編譯器將定義對象的類型設置為初始化值的類型。
#define _CRT_SECURE_NO_WARNINGS 1#include <iostream>
#include <map>
using namespace std;int main()
{int i = 10;auto p = &i;auto pf = strcpy;cout << typeid(p).name() << endl;cout << typeid(pf).name() << endl;map<string, string> dict = { {"sort", "排序"}, {"insert", "插入"} };//map<string, string>::iterator it = dict.begin();auto it = dict.begin();return 0;
}
decltype:
關鍵字decltype將變量的類型聲明為表達式指定的類型。
使用typeid().name()只能打印出類型的名稱,并不能用這個名稱繼續(xù)創(chuàng)建變量,而decltype可以:
template<class T1, class T2>
void F(T1 t1, T2 t2)
{decltype(t1 * t2) ret;cout << typeid(ret).name() << endl;
}int main()
{const int x = 1;double y = 2.2;decltype(x * y) ret; // ret的類型是doubledecltype(&x) p; // p的類型是int*cout << typeid(ret).name() << endl;cout << typeid(p).name() << endl;F(1, 'a');return 0;
}
使用decltype可以自動推演類型,并且可以用推演出的結果繼續(xù)創(chuàng)建變量,如上圖所示,對于一些不同類型直接的運算結果,decltype有奇效。
nullptr:
由于C++中NULL被定義成字面量0,這樣就可能回帶來一些問題,因為0既能指針常量,又能表示整形常量。所以出于清晰和安全的角度考慮,C++11中新增了nullptr,用于表示空指針。
在C++中存在條件編譯:(以后用nullptr就行了)這算是修復了一個bug
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void*)0)
#endif
#endif
范圍for循環(huán)
范圍for我們也一直都在使用,這是C++11提供的語法糖,使用起來非常方便,它的底層就是迭代器,只是編譯器給自動替換了,這里就不再講解了。
2.2 STL中的一些變化
新容器:
紅色框中的是C++11增加的新容器,基本只有unordered_map和unordered_set有用,其他很雞肋。容器array對標的是靜態(tài)數(shù)組,array也是一個靜態(tài)的,也就是在棧區(qū)上的,大小是通過一個非類型模板參數(shù)確定的。容器forward_list是一個單鏈表,也很雞肋,因為絕大部分場景雙鏈表都可以滿足要求,而且更加方便,唯一使用到單鏈表的地方就是哈希桶中。前面都提到過。
至于unordered_map和unordered_set,這兩個容器的底層是哈希桶,雖然不能實現(xiàn)排序,但是可以降重。而且在查找時具有其他容器無法比擬的效率。這兩個容器是非常實用的,而且也是我們經常使用的。
容器中的使用新方法:
1. 使用列表構造
在前面就講解過了,幾乎每個容器都增加了新的接口,使用std::initializer_list類型來構造。2. 移動構造和移動賦值
在下面講解了右值引用就可以明白了。3. emplace_xxx插入接口或者右值引用版本的插入接口。
同樣在后面才能學習到。
3. 右值和右值引用
3.1?右值和右值引用概念
什么是左值?什么是右值?
- 左值:一個表示數(shù)據(jù)的表達式,如變量名或者指針解引用。
- 特點:可以對左值取地址 + 可以對左值賦值。
?上圖代碼中所示的變量都屬于左值,要牢記左值可以取地址這一個特性。
- ?定義時const修飾符后的左值,不能給他賦值,但是可以取它的地址。
- ?左值可以出現(xiàn)在賦值符號的左邊,也可以出現(xiàn)在賦值符號的右邊。
- 右值:也是一個表示數(shù)據(jù)的表達式。如:字面常量,表達式返回值,函數(shù)返回值,類型轉換時的臨時變量等等。
- 特點:右值不可以取地址,不可以賦值。
要牢記右值特性:不能取地址不能賦值。
- ?右值可以出現(xiàn)在賦值符號的右邊,但是不能出現(xiàn)出現(xiàn)在賦值符號的左邊。
什么是右值引用?
左值引用是給左值取別名,右值引用顯而易見就是給右值取別名。
- 右值引用使用兩個&符號。
?上圖代碼中的rr1,rr2,rr3就是三個右值的別名,也就是右值引用。
3.2?右值引用類型的左值屬性
- 右值是不能取地址的,但是給右值取別名后,會導致右值被存儲到特定位置,且可以取到該位置的地址。
對于內置類型的右值,如字面常量,一旦右值引用以后,就會被存儲到特定的位置,
并且可以取到該地址,而且還可以修改。
int main()
{int&& rr1 = 10;cout << rr1 << endl;rr1 = 5;cout << rr1 << endl;const double&& rr2 = (1.1 + 2.2);//rr2 = 5.5; // 不能修改return 0;
}
字面常量10原本是不可以被修改的,但是右值引用以后,在特定的位置開辟了變量來存放10,所以就可以被修改了。
表達式或者函數(shù)的返回值,會有一個臨時變量來存放返回值,我們知道這樣的臨時變量具有常性,也是右值。對于這種右值引用,編譯器會修改它的屬性,將常性修改,并且存儲在特定位置。
注意const類型的右值,即便開辟了變量存放該右值也是不可以被修改的,因為被const修飾了。
內置類型的右值被稱為純右值。
自定義類型的右值被稱為將亡值。
對于自定義類型的右值,如容器的臨時變量,它確確實實會被銷毀,而不會被存放。
自定義類型的右值才能體現(xiàn)出右值存在的意義,后面會詳細講解。
- 右值引用是右值的別名,它所指向的右值是不可以被修改的。
- 但是右值引用本身也是一種類型,并且它的屬性是左值,可以取地址,可以賦值。
3.3?左值引用與右值引用比較
思考:左值引用可以引用右值嗎?
我們要知道,右值引用是C++11才出來的,右值傳參給函數(shù)還是右值,那我們以前寫的函數(shù)都用不了右值傳參了?
template<class T>
void Func(const T& x)
{}
這里去掉const肯定是不能傳參的,為了給右值傳參(當然還有其它原因),所以const的左值引用可以引用右值。總結:普通的左值引用不可以引用右值,const的左值引用可以引用右值:
思考:右值引用可以引用左值嗎?
右值引用不可以引用普通的左值,可以引用move以后的左值:(move這個語法先記住)
左值經過move以后就變成了右值,如:
int main()
{// 左值引用可以引用右值嗎? const的左值引用可以double x = 1.1, y = 2.2;//double& r1 = x + y;const double& r1 = x + y;// 右值引用可以引用左值嗎?可以引用move以后的左值int b = 7;//int&& rr5 = b;int&& rr5 = move(b);return 0;
}
成功編譯:
3.4?右值引用的使用場景
namespace rtx
{class string{public:string(const char* str = ""):_size(strlen(str)), _capacity(_size){_str = new char[_capacity + 1];strcpy(_str, str);}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(s);_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}string& operator=(const string& s) // 拷貝賦值{cout << "string& operator=(string s) -- 拷貝賦值(深拷貝)" << endl;string tmp(s);swap(tmp);return *this;}protected:char* _str;size_t _size;size_t _capacity;};
}
先自己實現(xiàn)一個string,只有拷貝構造函數(shù),賦值運算符重載函數(shù),析構函數(shù),以及一個普通的構造函數(shù)。無論是拷貝構造還是賦值運算符重載,都會進行深拷貝,采用現(xiàn)代寫法來實現(xiàn):
namespace rtx
{class string{public:string(const char* str = ""):_size(strlen(str)), _capacity(_size){_str = new char[_capacity + 1];strcpy(_str, str);}const char* c_str() const{return _str;}void swap(string& s){::swap(_str, s._str);::swap(_size, s._size);::swap(_capacity, s._capacity);}string(const string& s) // 拷貝構造:_str(nullptr), _size(0), _capacity(0){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(){delete[] _str;_str = nullptr;}protected:char* _str;size_t _size;size_t _capacity;};
}
左值引用的場景:
使用普通傳值調用,存在一次深拷貝:
void Func(rtx::string s)
{}int main()
{rtx::string s("hello world");Func(s);return 0;
}
使用傳拷貝引用時,不存在深拷貝,Func函數(shù)直接使用main函數(shù)中的s1對象:
void Func(rtx::string& s)
{}int main()
{rtx::string s("hello world");Func(s);return 0;
}
函數(shù)返回參數(shù)和上面一樣,傳引用返回有時確實能提高效率,
3.4.1 左值引用的功能和短板
左值引用的功能:
1、做參數(shù)。a、減少拷貝,提高效率。b、做輸出型參數(shù)。
2、做返回值。a、減少拷貝,提高效率。b、引用返回,可以修改返回對象(比如: operator[ ])。
但是左值引用做返回值只解決了70%的問題,在類似 to_string 函數(shù)中:
- 傳值返回時,存在一次深拷貝。
- rtx::string to_string(int value)
要知道深拷貝的代價是比較大的,深拷貝次數(shù)減少可以很大程度上提高代碼的效率。
- 傳左值引用返回時,不存在深拷貝。(可以嗎?)
- rtx::string& to_string(int value)
但是你敢傳引用返回嗎?我們把int value 轉換成string,此時的 string 是一個形參。出了函數(shù)就銷毀了。外面拿到的就是被銷毀了的棧幀。
所以左值引用存在的短板:
前面我們在調用 to_string 函數(shù)的時候,我們把int value 轉換成string,此時的 string 是一個形參。
所以只能傳值返回,此時mian函數(shù)中拿到 to_string 中的 string 對象要進行兩次深拷貝。
?第一次深拷貝,to_string函數(shù)返回時,會將string對象放在一個臨時變量中,此時發(fā)生的深拷貝。函數(shù)返回時,如果是內置類型等幾個字節(jié)的變量,會將函數(shù)中的臨時變量放在寄存器中返回,如果是自定義類型所占空間比較大,就會放在臨時變量中壓棧到上一級棧幀中。
?第二次深拷貝,main函數(shù)中,ret接收函數(shù)返回了的string對象時會再發(fā)生一次深拷貝。
但是編譯器會進行優(yōu)化,將兩次深拷貝優(yōu)化成一次。雖然只有一次,但有些情況代價還是很大的。
C++98是如何解決上面的問題?
那就是輸出型參數(shù):rtx::string to_string(int value)變成rtx::void to_string(int value,string& s)
但是這樣不太符合使用習慣。
- 有沒有辦法讓它符合使用習慣,并且一次深拷貝都沒有?那就要用到下面的C++11新增的移動構造和移動賦值了
3.4.2?移動構造
此時用右值引用就可以解決這個問題。
右值引用的價值之一:補齊臨時對象不能傳引用返回這個短板。?
前面的深拷貝是拷貝構造產生的:string(const string& s) // 拷貝構造(形參是左值引用)
演示在string類中增加一個移動構造函數(shù):
前面提到過:內置類型的右值被稱為純右值。
自定義類型的右值被稱為將亡值。(這里的傳右值就是將亡值)
基于拷貝構造:無論是左值還是右值都老老實實地開空間:
string(const string& s) // 拷貝構造:_str(nullptr), _size(0), _capacity(0){cout << "string(const string& s) -- 拷貝構造(深拷貝)" << endl;string tmp(s._str);swap(tmp);}
左值因為還要使用,肯定要開空間的,這里的右值是將亡值,沒用了,所以也不用開空間了,
因為不用開空間了,所以深拷貝也沒了,而是資源轉移(直接swap):
string(string&& s) // 移動構造:_str(nullptr), _size(0), _capacity(0){cout << "string(string&& s) -- 移動構造(資源轉移)" << endl;swap(s);}
- 移動構造的形參是右值引用。
從to_string中返回的string對象是一個臨時變量,具有常性,也就是我們所說的右值。
- 用右值來構造string對象時,會自定匹配移動構造函數(shù)。(以前沒有移動構造時,右值傳參會走拷貝構造,因為const 的左值引用可以接收右值,但是這不是最優(yōu)方案,現(xiàn)在寫了移動構造,右值傳參就會走移動構造)
3.4.3?移動賦值
拷貝賦值移動賦值和拷貝構造移動構造類似:
string& operator=(const string& s) // 拷貝賦值{cout << "string& operator=(string s) -- 拷貝賦值(深拷貝)" << endl;string tmp(s);swap(tmp);return *this;}string& operator=(string&& s) // 移動賦值{cout << "string& operator=(string s) -- 移動賦值(資源移動)" << endl;swap(s);return *this;}
總結:右值引用和左值引用減少拷貝的原理不太一樣。
- 左值引用是別名,直接在原本的對象上起作用。
- 右值引用是間接起作用,通過右值引用識別到右值,然后在移動構造和移動賦值中進行資源轉移。
使用移動構造和移動賦值時,被轉移資源的對象必須是個將亡值(像to_string的使用一樣),因為會被銷毀。
C++11的STL標準庫中也提供了移動構造和移動賦值函數(shù)。
3.4.4?插入右值時減少深拷貝
C++11在STL庫容器中的所有插入接口都提供了右值版本,push_back,insert等。
在我們寫的string恢復這兩個接口:
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';}
然后分別像庫里的 list 插入左值和右值
int main()
{list<rtx::string> lt;rtx::string s1("hello"); // 左值lt.push_back(s1); // 插入左值cout << "----------------------------------" << endl;lt.push_back(rtx::string("world")); // 插入右值//lt.push_back("world");return 0;
}
如果沒有移動構造那么下面的也是深拷貝了。
4. 完美轉發(fā)
4.1?萬能引用(引用折疊)
寫多個重載函數(shù),根據(jù)實參類型調用不同函數(shù)。
- ?形參類型分別是左值引用,const左值引用,右值引用,const右值引用:
void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }// 萬能引用(引用折疊):t既能引用左值,也能引用右值
template<typename T>
void PerfectForward(T&& t)
{Fun(t); // 此時t變成了左值/const左值
}int main()
{PerfectForward(10); // 右值int a;PerfectForward(a); // 左值PerfectForward(std::move(a)); // 右值const int b = 8;PerfectForward(b); // const 左值PerfectForward(std::move(b)); // const 右值return 0;
}
代碼中的perfectForward函數(shù)模板被叫做萬能引用模板,
無論調用該函數(shù)時傳的是什么類型,它都能推演出來:
在函數(shù)模板推演的過程中會發(fā)生引用折疊:模板參數(shù)T&&中的兩個&符號折疊成一個。
當傳入的實參是左值時,就會發(fā)生引用折疊,是右值時就不會發(fā)生引用折疊。
- 無論傳的實參是什么,都不用改變模板參數(shù)T&&,編譯器都能夠自己推演。
- 這就是萬能引用,只需要一個模板就可以搞定,不需要分類去寫。
上面萬能模板中,雖然推演出來了各自實參類型,但是由于右值引用本身是左值屬性,所以需要使用move改變屬性后才能調用對應的重載函數(shù)。
有沒有辦法不用move改變左值屬性,讓模板函數(shù)中的t保持它推演出來的類型。答案是有的,完美轉發(fā)就能夠保持形參的屬性不變。
4.2 完美轉發(fā)
完美轉發(fā)同樣是C++11提供的,它也是一個模板:
完美轉發(fā):完美轉發(fā)在傳參的過程中保留對象原生類型屬性。
實參傳遞過來后,推演出的形參是什么類型就保持什么類型繼續(xù)使用。
這里會語法就行:
void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }// 萬能引用(引用折疊):t既能引用左值,也能引用右值
template<typename T>
void PerfectForward(T&& t)
{Fun(std::forward<T>(t)); // 完美轉發(fā):保持t引用對象屬性
}int main()
{PerfectForward(10); // 右值int a;PerfectForward(a); // 左值PerfectForward(std::move(a)); // 右值const int b = 8;PerfectForward(b); // const 左值PerfectForward(std::move(b)); // const 右值return 0;
}
此時再使用萬能引用的時候,在函數(shù)模板中調用重載函數(shù)時只需要使用完美轉發(fā)就可以保持推演出來的屬性不變,右值引用仍然是右值,const右值引用也仍然是右值。
需要注意的是:
雖然右值不可以被修改,但是右值引用以后具有了左值屬性,才能被轉移,一旦被const修飾以后就無法轉移了。所以我們在使用右值引用的時候,不要使用const來修飾。
5. 新的類功能
在原來的C++類中,有6大默認成員函數(shù):
1. 構造函數(shù) 2. 析構函數(shù) 3. 拷貝構造函數(shù) 4. 拷貝賦值重載 5. 取地址重載 6. const 取地址重載
最后重要的是前4個,后兩個用處不大。默認成員函數(shù)就是我們不寫編譯器會生成一個默認的,而且完全符號我們使用的需求。
5.1 默認生成的移動構造/賦值
C++11中新增了兩個:移動構造和移動賦值運算符重載,此時C++11一共有8個默認成員函數(shù)了。
這兩個成員函數(shù)在前面已經介紹過了,這里站在默認成員函數(shù)的角度繼續(xù)談談。
滿足下列條件,編譯器會自定生成移動構造函數(shù):
- 沒有自己顯示定義移動構造函數(shù)
- 且沒有實現(xiàn)析構函數(shù),拷貝構造函數(shù),拷貝賦值重載中的任何一個。
此時編譯器會自定生成一個默認的移動構造函數(shù)。
- ?默認生成的移動構造函數(shù),對于內置類型會逐字節(jié)進行拷貝。
- ?對于自定義類型,如果實現(xiàn)了移動構造就調用移動構造,沒有實現(xiàn)就調用拷貝構造。
滿足下列條件,編譯器會自動生成移動賦值重載函數(shù)
- 自己沒有顯示定義移動賦值重載函數(shù)。
- 且且沒有實現(xiàn)析構函數(shù),拷貝構造函數(shù),拷貝賦值重載中的任何一個。
此時編譯器會自動生成一個默認移動賦值函數(shù)。
- ?對于內置類型會按字節(jié)拷貝。
- ?對于自定義類型,如果實現(xiàn)了移動賦值就調用移動賦值,如果沒有實現(xiàn)就調用拷貝賦值。
創(chuàng)建一個類,屏蔽掉拷貝構造,拷貝賦值,以及析構函數(shù),成員變量有一個是我們自己實現(xiàn)的string,里面有移動構造和移動賦值。
namespace rtx
{class string{public:string(const char* str = ""):_size(strlen(str)), _capacity(_size){_str = new char[_capacity + 1];strcpy(_str, str);}void swap(string& s){::swap(_str, s._str);::swap(_size, s._size);::swap(_capacity, s._capacity);}string(const string& s) // 拷貝構造:_str(nullptr), _size(0), _capacity(0){cout << "string(const string& s) -- 拷貝構造(深拷貝)" << endl;string tmp(s._str);swap(tmp);}string(string&& s) // 移動構造:_str(nullptr), _size(0), _capacity(0){cout << "string(string&& s) -- 移動構造(資源轉移)" << endl;swap(s);}string& operator=(const string& s) // 拷貝賦值{cout << "string& operator=(string s) -- 拷貝賦值(深拷貝)" << endl;string tmp(s);swap(tmp);return *this;}string& operator=(string&& s) // 移動賦值{cout << "string& operator=(string s) -- 移動賦值(資源移動)" << endl;swap(s);return *this;}~string(){delete[] _str;_str = nullptr;}protected:char* _str;size_t _size;size_t _capacity;};
}class Person
{
public://Person(const char* name = "", int age = 0)// :_name(name)// , _age(age)//{}//Person(const Person& p) // 拷貝構造// :_name(p._name)// , _age(p._age)//{}//Person& operator=(const Person& p) // 拷貝賦值//{// if (this != &p)// {// _name = p._name;// _age = p._age;// }// return *this;//}//~Person()//{}protected:rtx::string _name;int _age;
};int main()
{Person s1;Person s2 = s1;Person s3 = std::move(s1);Person s4;s4 = std::move(s2);return 0;
}
此時Person就自動生成了移動構造函數(shù),并且調用了string中的移動構造和移動賦值函數(shù)來構造string對象。
將Person中的拷貝構造,拷貝賦值,析構函數(shù)任意放出一個來。(這里只放出了析構)
使用右值構建string對象時,都會調用string的拷貝構造和拷貝賦值函數(shù)。
- 編譯器默認生成的移動賦值和移動構造非常類型。
- 如果符合條件就生成,內置內心按字節(jié)處理,自定義類型調用自定義類型的移動賦值或者移動構造,如果沒有的化就調用它們的拷貝賦值或者拷貝構造。
- 如果不符合條件,就直接調用自定義類型的拷貝復制或者拷貝構造。
5.2 類里新的關鍵字
強制生成默認函數(shù)的關鍵字default:
這個default并不是switch中的default,而是C++11的新用法。
- 假設類中的某個默認成員函數(shù)沒有自動生成,但是我們需要它,就可以用default,強制讓編譯器自動生成默認函數(shù)。
5.1里的代碼:將Person中的拷貝構造,拷貝復制,析構函數(shù)都顯示定義,此時就破壞了自動生成移動構造的條件。把Person里的注釋放開,使用default強制生成默認的移動構造函數(shù)
?從結果中可以看到,仍然調用了string中的移動構造函數(shù),而不是調用的拷貝構造(深拷貝)。
- ?說明Person中仍然生成了默認的移動構造函數(shù)。
禁止生成默認成員函數(shù)的關鍵字delete:
如果能想要限制某些默認函數(shù)的生成,在C++98中,是該函數(shù)設置成private,并且只聲明補丁 已,這樣只要其他人想要調用就會報錯。在C++11中更簡單,只需在該函數(shù)聲明加上=delete即 可,該語法指示編譯器不生成對應函數(shù)的默認版本,稱=delete修飾的函數(shù)為刪除函數(shù)。
C++98不生成默認成員函數(shù)的方法:直接一個分號(要放到保護或者私有里,這里就不放了)
在Person類中不顯示定義拷貝構造函數(shù),拷貝復制函數(shù),析構函數(shù),此時符合自動生成默認移動構造的條件。?聲明移動構造函數(shù),但是沒有定義(要放到保護或者私有里,防止類外實現(xiàn),這里就不放了)。此時在編譯的時候就會報錯,這是C++98中的方式,利用鏈接時找不到函數(shù)的定義報錯。C++11就新增delete關鍵字使其在編譯階段就報錯:
- C++11中,使用delete同樣可以實現(xiàn)不讓自動生成默認成員函數(shù)。
同樣在編譯時報錯了。編譯器會自動生成移動構造函數(shù),但是此時使用了delete,編譯器就會報錯,告訴我們這里生成了移動構造。
這是為了在編譯階段就報錯,而不是運行時再報錯。
以前提到的一道題:
// 要求delete關鍵字實現(xiàn),一個類,只能在堆上創(chuàng)建對象
class HeapOnly
{
public:HeapOnly(){_str = new char[10];}~HeapOnly() = delete;//void Destroy() // 如果要銷毀只能這樣//{//?? ?delete[] _str;//?? ?operator delete(this);//}private:char* _str;//...
};
繼承和多態(tài)中的final與override關鍵字
這兩個關鍵字在繼承和多態(tài)部分詳細講解過,這里不再詳細講解。
final
- 在繼承中,被final修飾的類叫做最終類,是無法繼承的。
- 在多態(tài)中,被final修飾的虛函數(shù)是無法進行重寫的。
override
- 在多態(tài)中,用來檢查虛函數(shù)是否完成了重寫。
本篇完。
C++11中的很多東西雖然讓C++越來越不像C++,比如列表初始化等內容,但是還是有一些非常有用的東西的:比如今天講到的右值引用,和下一篇學的lambda表達式。
下一篇:從C語言到C++_34(C++11_下)可變參數(shù)+ lambda+function+bind+筆試題。