C++進階—C++11

第一章:C++11簡介

在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則帶來了數量可觀的變化,其中包含了約140個新特性,以及對C++03標準中約600個缺陷的修正,這使得C++11更像是從C++98/03中孕育出的一種新語言。相比較而言,C++11能更好地用于系統開發和庫開發、語法更加泛華和簡單化、更加穩定和安全,不僅功能更強大,而且能提升程序員的開發效率,公司實際項目開發中也用得比較多,所以我們要作為一個重點去學習。C++11增加的語法特性非常篇幅非常多,所以本節主要講解實際中比較實用的語法。

https://en.cppreference.com/w/cpp/11

第二章:統一的列表初始化

2.1 {}初始化

在C++98中,標準允許使用花括號{}對數組或者結構體元素進行統一的列表初始值設定。比如:
struct Point {int _x;int _y;
};int main() {int array1[] = { 1, 2, 3, 4, 5 };int array2[5] = { 0 };Point p = { 1, 2 };return 0;
}

C++11擴大了用大括號括起的列表(初始化列表)的使用范圍,使其可用于所有的內置類型和用戶自定義的類型,使用初始化列表時,可添加等號(=),也可不添加

class Date {
public:Date(int year, int month, int day):_year(year), _month(month), _day(day) {cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};int main() {int i = 0;int j = { 0 };int k{ 0 };int array1[]{ 1, 2, 3, 4, 5 };int array2[5]{ 0 };Point p{ 1, 2 };//創建對象時也可以使用列表初始化方式調用構造函數初始化Date d1(2025, 07, 24); // old style// C++11支持的列表初始化,這里會調用構造函數初始化//本質是類型轉換,用{}里的內容構造一個臨時對象,再用臨時對象去拷貝構造//編譯器優化為直接構造(構造+拷貝構造->優化直接構造)Date d2 = { 2025, 07, 25 };Date d3{ 2025, 07, 26 };//Date& d4 = { 2025, 07, 27 };//不可以引用,因為臨時對象具有常性const Date& d4 = { 2025, 07, 27 };//const修飾才能引用常量Date* p1 = new Date[3]{ d1,d2,d3 };Date* p1 = new Date[3]{ {2025, 07, 24},{2025, 07, 25},{2025, 07, 26} };return 0;
}

2.2 std::initializer_list

std::initializer_list的介紹文檔:http://www.cplusplus.com/reference/initializer_list/initializer_list/

std::initializer_list是什么類型:
//std::initializer_list 是 C++11 引入的一個輕量級模板類,用于支持列表初始化(uniform initialization),
//即使用 { } 初始化對象。它使得初始化語法更加統一,并允許在構造函數或函數中接收任意長度的初始化列表。int main() {// the type of il is an initializer_list auto il = { 10, 20, 30 };cout << typeid(il).name() << endl;//class std::initializer_list<int>return 0;
}

std::initializer_list使用場景:
std::initializer_list一般是作為構造函數的參數,C++11對STL中的不少容器就增加std::initializer_list作為參數的構造函數,這樣初始化容器對象就更方便了。也可以作為operator=的參數,這樣就可以用大括號賦值。

int main() {//Date屬于多參數構造的類型轉換,構造+拷貝構造->優化直接構造//需要和對應構造函數的參數個數匹配Date d1 = { 2025, 07, 24 };//這兩種方式的初始化與上面Date初始化并不一樣//它們{}中的參數個數可以是任意個數//這里{}中的參數是initializer_listvector<int> v1 = { 1,2,3,4 };vector<int> v2 = { 1,2,3,4,5,6 };list<int> lt = { 10,20,30 };//使用initializer_list賦值v1 = { 10,20,30,40, };initializer_list<int> il2 = { 10,20,30,40 };initializer_list<int>::iterator it2 = il2.begin();while (it2 != il2.end()) {cout << *it2 << " ";++it2;}cout << endl;for (auto e : il2)cout << e << " ";cout << endl;//vector支持initializer_list初始化的實現//vector(const initializer_list<T>&lt) {//	reserve(lt.size());//	for (auto& e : lt) push_back(e);//}return 0;
}

第三章:聲明

c++11提供了多種簡化聲明的方式,尤其是在使用模板時。

3.1 auto

在C++98中auto是一個存儲類型的說明符,表明變量是局部自動存儲類型,但是局部域中定義局部的變量默認就是自動存儲類型,所以auto就沒什么價值了。C++11中廢棄auto原來的用法,將其用于實現自動類型推斷。這樣要求必須進行顯示初始化,讓編譯器將定義對象的類型設置為初始化值的類型。

3.2 decltype

關鍵字decltype將變量的類型聲明為表達式指定的類型。

int main() {int i = 1;double d = 2.2;//把類型以字符串形式獲取到cout << typeid(i).name() << endl;cout << typeid(d).name() << endl;//typeid(i).name() j;//這是字符串,不能定義類型auto ret = i * d;//vector<auto> v;//錯誤,auto無法獲取類型//用decltype獲取ret的類型去實例化vector//decltype可以推導對象的類型。該類型可以用作模板實參,或定義對象vector<decltype(ret)> v;v.push_back(1);v.push_back(1.1);for (auto e : v)cout << e << " ";cout << endl;return 0;
}

3.3 nullptr

由于C++中NULL被定義成字面量0,這樣就可能回帶來一些問題,因為0既能指針常量,又能表示整形常量。所以出于清晰和安全的角度考慮,C++11中新增了nullptr,用于表示空指針。

第四章:范圍for循環

這個在前面的章節中已經進行了非常詳細的講解。

第五章:智能指針

這個將在智能指針中進行詳細的講解。

第六章:STL中一些變化

<array>、<forward_list>、<unordered_map>、<unordered_set>是C++11中的一些幾個新容器,但是實際最有用的是unordered_map和unordered_set。這兩個前面已經進行了非常詳細的講解,其他的了解即可。

容器中的一些新方法

比如提供了cbegin和cend方法返回const迭代器等等,但是實際意義不大,因為begin和end也是可以返回const迭代器的,這些都是屬于錦上添花的操作。

實際上C++11更新后,容器中增加的新方法最后用的插入接口函數的右值引用版本:

但是這些接口到底意義在哪?網上都說他們能提高效率,他們是如何提高效率的?

請看下面的右值引用和移動語義章節的講解。另外emplace還涉及模板的可變參數,也需要再繼續深入學習后面章節的知識。

第七章:右值引用和移動語義

7.1 左值引用和右值引用

什么是左值?什么是左值引用?

左值是一個表示數據的表達式(如變量名或解引用的指針),我們可以獲取它的地址+可以對它賦值,左值可以出現賦值符號的左邊,右值不能出現在賦值符號左邊。定義時const修飾符后的左值,不能給他賦值,但是可以取它的地址。左值引用就是給左值的引用,給左值取別名。

int main() {// 以下的p、b、c、*p都是左值int* p = new int(0);int b = 1;const int c = 2;// 以下幾個是對上面左值的左值引用int*& rp = p;int& rb = b;const int& rc = c;int& pvalue = *p;return 0;
}

什么是右值?什么是右值引用?

右值也是一個表示數據的表達式,如:字面常量、表達式返回值,函數返回值(這個不能是左值引用返回)等等,右值可以出現在賦值符號的右邊,但是不能出現出現在賦值符號的左邊,右值不能取地址。右值引用就是對右值的引用,給右值取別名。

int main() {double x = 1.1, y = 2.2;// 以下幾個都是常見的右值10;x + y;fmin(x, y);// 以下幾個都是對右值的右值引用int&& rr1 = 10;double&& rr2 = x + y;double&& rr3 = fmin(x, y);// 這里編譯會報錯:error C2106: “=”: 左操作數必須為左值//10 = 1;//x + y = 1;//fmin(x, y) = 1;return 0;
}

需要注意的是右值是不能取地址的,但是給右值取別名后,會導致右值被存儲到特定位置,且可以取到該位置的地址,也就是說例如:不能取字面量10的地址,但是rr1引用后,可以對rr1取地址,也可以修改rr1。如果不想rr1被修改,可以用const int&& rr1 去引用,是不是感覺很神奇,這個了解一下實際中右值引用的使用場景并不在于此,這個特性也不重要。

7.2 左值引用與右值引用比較

int main() {//左值引用能否給右值取別名//不能,但const左值引用可以const int& r1 = 10;int i = 0;int j = i;const int& r2 = i + j;//右值引用給右值取別名int&& rr1 = 10;int&& rr2 = i + j;//右值引用能否給左值取別名//不能,但右值引用可以給move(左值)取別名int&& rr3 = move(i);return 0;
}

左值是可以取地址的值(非const可以修改,const不可以修改)。
左值引用可以給左移取別名。
左值引用不能給右值取別名,但const左值引用可以給右值取別名。

右值是不能取地址的值。
右值引用可以給右值取別名。
右值引用不能給左值取別名,但可以給move(左值)取別名。

7.3 右值引用使用場景和意義

左值引用的使用場景:
做參數和做返回值都可以提高效率。

左值引用的短板:
但是當函數返回對象是一個局部變量,出了函數作用域就不存在了,就不能使用左值引用返回,只能傳值返回。例如:bit::string to_string(int value)函數中可以看到,這里只能使用傳值返回,傳值返回會導致至少1次拷貝構造(如果是一些舊一點的編譯器可能是兩次拷貝構造)。

右值引用和移動語義解決上述問題:
在bit::string中增加移動構造,移動構造本質是將參數右值的資源竊取過來,占位已有,那么就不用做深拷貝了,所以它叫做移動構造,就是竊取別人的資源來構造自己。

namespace bit {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) { //const左值引用既可以引用左值,也可以引用右值cout << "string(const string& s) -- 拷貝構造 深拷貝" << endl;string tmp(s._str);swap(tmp);}//C++的右值分2類://1.純右值:內置類型(純粹的字面值、臨時對象或表達式計算結果,沒有持久的內存地址(不能取地址))//2.將亡值:自定義類型(即將被移動(資源被轉移)的對象,通常是“生命周期即將結束”的值。)//         有內存地址(可以取地址),但允許被“掏空”(資源被移動)。//移動構造string(string&& s) {//to_string 中的 ret 是即將銷毀的局部變量//編譯器將其視為右值(即使它是左值名稱,但處于"將亡值"場景)//構造臨時對象:需要用一個右值(ret)構造臨時對象//移動構造函數內部:新對象(臨時對象)剛創建,它的成員是默認值。//參數 s 是 ret 的別名(引用),持有 ret 的真實數據//調用全局的 swap 函數交換每個成員//函數結束后:ret(即移動后的 s)被銷毀//臨時對象現在持有原 ret 的資源("1234"),沒有發生深拷貝cout << "string(string&& s) -- 移動構造" << endl;swap(s);//this:指向正在構造的臨時對象(內存由編譯器分配)//臨時對象不新開空間}// 賦值重載string& operator=(const string& s) {cout << "string& operator=(string s) -- 賦值重載 深拷貝" << endl;//string tmp(s);//swap(tmp);if (this != &s) {char* tmp = new char[s._capacity + 1];strcpy(tmp, s._str);delete[] _str;_str = tmp;_size = s._size;_capacity = s._capacity;}return *this;}string& operator=(string&& s) {//ret 的資源("1234")已通過 swap 轉移給臨時對象。//參數 s:就是剛才那個臨時對象(右值引用綁定)。 // this:指向 main 中的str。str和s交換資源。//臨時對象s帶著str的資源被銷毀,并釋放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 = nullptr;size_t _size = 0;size_t _capacity = 0; // 不包含最后做標識的\0};bit::string to_string(int x) {bit::string ret;while (x) {int val = x % 10;x /= 10;ret += ('0' + val);}reverse(ret.begin(), ret.end());return ret;//該場景不能使用左值引用返回,ret出了作用域要銷毀//也不能使用右值引用返回,右值引用不能引用左值//ret拷貝構造臨時對象,在賦值給外面的str,發生兩次深拷貝//ret開辟空間存1234,然后拷貝構造給臨時對象,臨時對象也開辟空間存1234,//出了函數作用域ret銷毀并釋放空間,當ret賦值給外面的str并走到下一行,s銷毀并釋放空間//ret 是局部變量(左值),但 return 時會隱式轉換為將亡值(xvalue)。//調用string(string && s),s 就是 ret 轉換成的將亡值。//移動構造后,ret 的資源被轉移,外部得到新對象。}
}int main() {//無移動構造和移動賦值://這里有深拷貝發生,to_string函數中要拷貝構造臨時對象//再用臨時對象賦值給s//有移動構造和移動賦值//整個移動構造和移動復制的過程中只有ret開辟了空間存儲資源,臨時對象把ret的換走了,//然后main函數中的str再把臨時對象的換走了,也就相當于間接換走了ret的資源。bit::string str;	str = bit::to_string(1234);//------------------------------------------------------------------//如果沒有移動構造,下面這種寫法還是會發生一次深拷貝,相當于ret拷貝構造str。//有了移動構造就是ret和str交換資源bit::string str = bit::to_string(1234);//-------------------------------------------------------------------//下面這里將s1轉為右值,雖然不調用拷貝構造(調用移動構造)//但資源交換后,s1變為空了bit::string s1("hello world");bit::string s2 = s1;//這里調用拷貝構造bit::string s3 = move(s1);//-------------------------------------------------------------------//move不會改變s1的屬性,只是move的返回值是右值move(s1);bit::string s3 = s1;//調用的是拷貝構造return 0;//右值引用的移動語義主要針對深拷貝,淺拷貝意義不大//左值引用(T&)只能綁定到有名字的、持久化的對象(左值),但無法綁定到://臨時對象(右值):如函數返回值、表達式結果等。//需要被“移動”而非拷貝的資源:比如動態分配的數組、文件句柄等。//右值引用的核心作用//右值引用(T&& )直接綁定到臨時對象(右值),并允許“竊取”其資源,避免深拷貝。典型場景包括://函數返回臨時對象:如std::vector<int> createVector()。//臨時對象賦值或傳參:如a = createVector()。//標準庫容器的移動語義:如std::vector的移動構造函數。
}

STL容器插入接口函數也增加了右值引用版本

int main() {list<bit::string> lt;bit::string s1("hello world");lt.push_back(s1);//左值引用插入,拷貝構造 深拷貝lt.push_back(bit::to_string(1234));//調用2次移動構造。//當bit::to_string(1234)執行完畢后://它返回一個臨時對象(右值)ret。//由于 ret 是一個局部變量,在函數返回時,它會被視為即將銷毀的右值。//此時,編譯器會優先調用 移動構造函數(string(string && s))來構造返回值,而不是拷貝構造。//第二次移動構造:push_back 接收臨時對象//push_back 接收到 bit::to_string(1234) 返回的臨時對象(右值)。//STL 的 list 在插入元素時,會構造一個新的節點來存儲該字符串。//由于傳入的是 右值,list 內部會調用 string 的 移動構造函數 來構造新節點中的string對象。lt.push_back("11111");//只調用一次移動構造//lt.push_back("11111"); 直接傳入 const char*,//只需一次隱式構造 + 一次移動構造,沒有額外的返回值傳遞過程。//右值引用(&& )不僅優化了函數返回值(如 return 臨時對象),//還優化了參數傳遞(如函數傳參或容器插入操作),//特別是對于需要深拷貝的類(如 string、vector 等),//它可以避免不必要的深拷貝,直接“竊取”資源,提高性能。return 0;
}

7.4 右值引用引用左值及其一些更深入的使用場景分析

按照語法,右值引用只能引用右值,但右值引用一定不能引用左值嗎?因為:有些場景下,可能真的需要用右值去引用左值實現移動語義。當需要用右值引用引用一個左值時,可以通過move函數將左值轉化為右值。C++11中,std::move()函數位于 頭文件中,該函數名字具有迷惑性,它并不搬移任何東西,唯一的功能就是將一個左值強制轉化為右值引用,然后實現移動語義

驗證自己實現的list

List Class.h

#pragma oncenamespace bit {template<class T>struct list_node { //節點結構體。T _data;list_node<T>* _next;list_node<T>* _prev;list_node(const T& x = T())//如果T是自定義類型,則x會是該類型的默認構造對象,取決于該類型的默認構造函數如何定義。:_data(x), _next(nullptr), _prev(nullptr) {}//為右值引用提供一個構造函數,不能帶缺省參數,否則調用歧義list_node(T&& x):_data(move(x)), _next(nullptr), _prev(nullptr) {}};template<class T, class Ref, class Ptr>//迭代器是模擬指針的行為struct __list_iterator { //迭代器typedef list_node<T> Node;typedef __list_iterator<T, Ref, Ptr> self;Node* _node;//迭代器可以理解為節點指針__list_iterator(Node* node)//單參數構造函數支持隱式類型轉換,也就是說節點指針也可以隱式類型轉換為迭代器:_node(node) {}self& operator++() { //前置++_node = _node->_next;return *this;}self& operator--() { //前置--_node = _node->_prev;return *this;}self operator++(int) { //后置++self tmp(*this);_node = _node->_next;return tmp;}self operator--(int) { //后置--self tmp(*this);_node = _node->_prev;return tmp;}Ref operator*() { return _node->_data; }Ptr operator->() { return &_node->_data; }//提供給自定義類型解引用。自定義類型解引用是 指針->形式,所以返回指針//非const和const迭代器中只有上面這兩個運算符重載的返回值不同bool operator!= (const self& s) { return _node != s._node; }bool operator== (const self& s) { return _node == s._node; }};template<class T>class list {typedef list_node<T> Node;public:typedef __list_iterator<T, T&, T*> iterator;typedef __list_iterator<T, const T&, const T*> const_iterator;const_iterator begin() const { return _head->_next; }//支持隱式類型轉換const_iterator end() const { return _head; }//支持隱式類型轉換iterator begin() { return _head->_next; }iterator end() { return _head; }void empty_init() {_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;}list() { empty_init(); } //構造函數本質是創建一個哨兵位節點//lt2(lt1)list(const list<T>& lt) { //lt是const對象,需要const迭代器empty_init();//創建(lt2的)哨兵位for (auto e : lt)//遍歷lt1每個節點賦值給epush_back(e);//lt2尾插每個e節點}//現代寫法void swap(list<T>& lt) {std::swap(_head, lt._head);std::swap(_size, lt._size);}list<T>& operator=(list<T> lt) {swap(lt);return *this;}~list() {clear();delete _head;//清除哨兵位_head = nullptr;}void clear() {iterator it = begin();//begin指向哨兵位的下一節點,即有效數據的頭節點while (it != end())it = erase(it);}void push_back(const T& x) { insert(end(), x); }//end指向哨兵位,在哨兵位之前插入//void push_back(T&& x) { insert(end(), x); }//右值引用版本void push_back(T&& x) { insert(end(), move(x)); }//右值被右值引用 引用后的屬性是左值,這樣insert時走的就是左值引用版本//所以move改變x的屬性為右值void push_front(const T& x) { insert(begin(), x); }void pop_back() { erase(--end()); }//哨兵位前一節點就是尾結點void pop_front() { erase(begin()); }iterator insert(iterator pos, const T& x) {Node* cur = pos._node;Node* newnode = new Node(x);Node* prev = cur->_prev;//prev newnode curprev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;++_size;return iterator(newnode);}iterator insert(iterator pos, T&& x) {Node* cur = pos._node;Node* newnode = new Node(move(x));//右值引用版本//這里和push_back同理,右值被右值引用 引用后的屬性是左值//這樣走的就是左值引用版本的構造函數,所以move改變x的屬性為右值Node* prev = cur->_prev;//prev newnode curprev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;++_size;return iterator(newnode);}iterator erase(iterator pos) { //erase后迭代器失效,因為迭代器指向的節點被釋放了,所以返回下一個節點的位置Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;delete cur;prev->_next = next;next->_prev = prev;--_size;return iterator(next);}size_t size() { return _size; }private:Node* _head;size_t _size;//增加該成員避免每次獲取size都要遍歷一遍鏈表};
}

Test.cpp

int main() {bit::list<bit::string> lt;bit::string s1("hello world");//拷貝構造 深拷貝lt.push_back(s1);//拷貝構造 深拷貝lt.push_back(bit::to_string(1234));//具體發生過程:拷貝構造 深拷貝 -> 移動構造 ->拷貝構造 深拷貝//bit::to_string(1234)返回值是右值,所以傳給了push_back的右值引用版本,//但是右值被右值引用 引用后的屬性是左值。//因為如果不這樣,移動構造和移動賦值里的臨時對象就無法修改,也就無法實現交換。//即右值不能被直接修改,但右值被右值引用后需要被修改。lt.push_back("11111");return 0;
}

7.5 完美轉發

模板中的&& 萬能引用
std::forward 完美轉發在傳參的過程中保留對象原生類型屬性
?模板中的&&不代表右值引用,而是萬能引用,其既能接收左值又能接收右值。 模板的萬能引用只是提供了能夠同時接收左值引用和右值引用的能力, 但是引用類型的唯一作用就是限制了接收的類型,后續使用中都退化成了左值。
“引用類型的唯一作用就是限制了接收的類型”的意思是:在函數模板參數中使用的引用(無論是左值引用&還是右值引用&&),其主要作用就是限制函數可以接受的參數類型,而不會改變參數在函數內部使用時的值類別。
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; }
template<typename T>
void PerfectForward(T&& t) {//Fun(t);//都退化為左值//Fun(move(t));//這種方式會導致全部變成右值引用或const 右值引用//如果期望實參的屬性與形參一致,使用完美轉發Fun(forward<T>(t));
}//完美轉發不能再非模板使用,因為只有在模板中&&才代表萬能引用,
//這里是右值引用,不能傳左值
void PerfectForward(int&& t) {Fun(forward<int>(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 右值//雖然上面傳的值有左值和右值,但調用后全部變成左值引用或const 左值引用//無論參數是以什么引用形式傳入的(左值引用或右值引用),在函數內部使用時,這個參數名本身都是一個左值//也就是說,即使你傳入的是一個右值引用,在函數內部使用這個參數名時,它仍然是一個左值//原因:C++不自動將具名變量(任何有名字的變量或參數)當作右值的核心原因:避免意外的資源竊取導致后續代碼出錯。return 0;
}


使用完美轉發修改之前的list

List Class.h
?
#pragma oncenamespace bit {template<class T>struct list_node { //節點結構體。T _data;list_node<T>* _next;list_node<T>* _prev;list_node(const T& x = T())//如果T是自定義類型,則x會是該類型的默認構造對象,取決于該類型的默認構造函數如何定義。:_data(x), _next(nullptr), _prev(nullptr) {}//為右值引用提供一個構造函數,不能帶缺省參數,否則調用歧義list_node(T&& x)//:_data(move(x)), _next(nullptr), _prev(nullptr) {:_data(forward<T>(x)), _next(nullptr), _prev(nullptr) {}};template<class T, class Ref, class Ptr>//迭代器是模擬指針的行為struct __list_iterator { //迭代器typedef list_node<T> Node;typedef __list_iterator<T, Ref, Ptr> self;Node* _node;//迭代器可以理解為節點指針__list_iterator(Node* node)//單參數構造函數支持隱式類型轉換,也就是說節點指針也可以隱式類型轉換為迭代器:_node(node) {}self& operator++() { //前置++_node = _node->_next;return *this;}self& operator--() { //前置--_node = _node->_prev;return *this;}self operator++(int) { //后置++self tmp(*this);_node = _node->_next;return tmp;}self operator--(int) { //后置--self tmp(*this);_node = _node->_prev;return tmp;}Ref operator*() { return _node->_data; }Ptr operator->() { return &_node->_data; }//提供給自定義類型解引用。自定義類型解引用是 指針->形式,所以返回指針//非const和const迭代器中只有上面這兩個運算符重載的返回值不同bool operator!= (const self& s) { return _node != s._node; }bool operator== (const self& s) { return _node == s._node; }};template<class T>class list {typedef list_node<T> Node;public:typedef __list_iterator<T, T&, T*> iterator;typedef __list_iterator<T, const T&, const T*> const_iterator;const_iterator begin() const { return _head->_next; }//支持隱式類型轉換const_iterator end() const { return _head; }//支持隱式類型轉換iterator begin() { return _head->_next; }iterator end() { return _head; }void empty_init() {_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;}list() { empty_init(); } //構造函數本質是創建一個哨兵位節點//lt2(lt1)list(const list<T>& lt) { //lt是const對象,需要const迭代器empty_init();//創建(lt2的)哨兵位for (auto e : lt)//遍歷lt1每個節點賦值給epush_back(e);//lt2尾插每個e節點}//現代寫法void swap(list<T>& lt) {std::swap(_head, lt._head);std::swap(_size, lt._size);}list<T>& operator=(list<T> lt) {swap(lt);return *this;}~list() {clear();delete _head;//清除哨兵位_head = nullptr;}void clear() {iterator it = begin();//begin指向哨兵位的下一節點,即有效數據的頭節點while (it != end())it = erase(it);}void push_back(const T& x) { insert(end(), x); }//end指向哨兵位,在哨兵位之前插入//void push_back(T&& x) { insert(end(), x); }//右值引用版本//void push_back(T&& x) { insert(end(), move(x)); }////右值被右值引用 引用后的屬性是左值,這樣insert時走的就是左值引用版本////所以move改變x的屬性為右值void push_back(T&& x) { insert(end(), forward<T>(x)); }void push_front(const T& x) { insert(begin(), x); }void pop_back() { erase(--end()); }//哨兵位前一節點就是尾結點void pop_front() { erase(begin()); }iterator insert(iterator pos, const T& x) {Node* cur = pos._node;Node* newnode = new Node(x);Node* prev = cur->_prev;//prev newnode curprev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;++_size;return iterator(newnode);}iterator insert(iterator pos, T&& x) {Node* cur = pos._node;//Node* newnode = new Node(move(x));//右值引用版本//這里和push_back同理,右值被右值引用 引用后的屬性是左值//這樣走的就是左值引用版本的構造函數,所以move改變x的屬性為右值Node* newnode = new Node(forward<T>(x));Node* prev = cur->_prev;//prev newnode curprev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;++_size;return iterator(newnode);}iterator erase(iterator pos) { //erase后迭代器失效,因為迭代器指向的節點被釋放了,所以返回下一個節點的位置Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;delete cur;prev->_next = next;next->_prev = prev;--_size;return iterator(next);}size_t size() { return _size; }private:Node* _head;size_t _size;//增加該成員避免每次獲取size都要遍歷一遍鏈表};
}?

第八章:新的類功能

默認成員函數

原來C++類中,有6個默認成員函數:

  1. ?構造函數
  2. 析構函數
  3. 拷貝構造函數
  4. 拷貝賦值重載
  5. 取地址重載
  6. const 取地址重載

最后重要的是前4個,后兩個用處不大。默認成員函數就是我們不寫編譯器會生成一個默認的。

C++11 新增了兩個:移動構造函數和移動賦值運算符重載。

針對移動構造函數和移動賦值運算符重載有一些需要注意的點如下:

  • 如果你沒有自己實現移動構造函數,且沒有實現析構函數 、拷貝構造、拷貝賦值重載中的任意一個。那么編譯器會自動生成一個默認移動構造。默認生成的移動構造函數,對于內置類型成員會執行逐成員按字節拷貝,自定義類型成員,則需要看這個成員是否實現移動構造,如果實現了就調用移動構造,沒有實現就調用拷貝構造。
  • 如果你沒有自己實現移動賦值重載函數,且沒有實現析構函數 、拷貝構造、拷貝賦值重載中的任意一個,那么編譯器會自動生成一個默認移動賦值。默認生成的移動構造函數,對于內置類型成員會執行逐成員按字節拷貝,自定義類型成員,則需要看這個成員是否實現移動賦值,如果實現了就調用移動賦值,沒有實現就調用拷貝賦值。(默認移動賦值跟上面移動構造完全類似)
  • 如果你提供了移動構造或者移動賦值,編譯器不會自動提供拷貝構造和拷貝賦值。
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() {}
private:bit::string _name;int _age;
};
int main() {//屏蔽Person(const Person& p)、Person& operator=(const Person& p)、~Person()Person s1;Person s2 = s1;//調用默認的拷貝構造 string(const string& s)Person s3 = std::move(s1);//調用默認的移動構造 string(string&& s)return 0;
}

強制生成默認函數的關鍵字default:
C++11可以讓你更好的控制要使用的默認函數。假設你要使用某個默認的函數,但是因為一些原因這個函數沒有默認生成。比如:我們提供了拷貝構造,就不會生成移動構造了,那么我們可以使用default關鍵字顯示指定移動構造生成。

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(Person&& p) = default;
private:bit::string _name;int _age;
};

禁止生成默認函數的關鍵字delete:
如果能想要限制某些默認函數的生成,在C++98中,是該函數設置成private,并且只聲明補丁已,這樣只要其他人想要調用就會報錯。在C++11中更簡單,只需在該函數聲明加上=delete即可,該語法指示編譯器不生成對應函數的默認版本,稱=delete修飾的函數為刪除函數。

class Person {
public:Person(const char* name = "", int age = 0):_name(name), _age(age) {}Person(const Person& p) = delete;
private:bit::string _name;int _age;
};

第九章:可變參數模板

C++11的新特性可變參數模板能夠創建可以接受可變參數的函數模板和類模板,相比C++98/03,類模版和函數模版中只能含固定數量的模版參數,可變模版參數無疑是一個巨大的改進。然而由于可變模版參數比較抽象,使用起來需要一定的技巧,所以現階段掌握一些基礎的可變參數模板特性就夠了。

下面就是一個基本可變參數的函數模板

// Args是一個模板參數包,args是一個函數形參參數包
// 聲明一個參數包Args...args,這個參數包中可以包含0到任意個模板參數。
template <class ...Args>
void ShowList(Args... args)
{}

上面的參數args前面有省略號,所以它就是一個可變模版參數,我們把帶省略號的參數稱為“參數包”,它里面包含了0到N(N>=0)個模版參數。我們無法直接獲取參數包args中的每個參數的,只能通過展開參數包的方式來獲取參數包中的每個參數,這是使用可變模版參數的一個主要特點,也是最大的難點,即如何展開可變模版參數。由于語法不支持使用args[i]這樣方式獲取可變參數,所以我們的用一些奇招來一一獲取參數包的值。

遞歸函數方式展開參數包
void _ShowList() { cout << endl; }template <class T, class ...Args>
void _ShowList(const T& val, Args... args) {cout << val << " ";_ShowList(args...);
}template <class ...Args>
void ShowList(Args... args) {_ShowList(args...);
}
//ShowList把參數包args傳給了_ShowList,然后_ShowList解析參數包第一個參數,并打印1。
//然后遞歸調用解析參數包第二個參數,以此類推,打印完123后,繼續解析,發現里面是0個參數,
//就不匹配void _ShowList(const T& val, Args... args)  而是更匹配void _ShowList(),最后打印一個換行結束。
int main() {ShowList(1);ShowList(1, 2);ShowList(1, 2, 3);ShowList(1, 2.2, 'x', 3.3);return 0;
}

逗號表達式展開參數包

這種展開參數包的方式,不需要通過遞歸終止函數,是直接在expand函數體中展開的, printarg不是一個遞歸終止函數,只是一個處理參數包中每一個參數的函數。這種就地展開參數包的方式實現的關鍵是逗號表達式。我們知道逗號表達式會按順序執行逗號前面的表達式。

expand函數中的逗號表達式:(printarg(args), 0),也是按照這個執行順序,先執行printarg(args),再得到逗號表達式的結果0。同時還用到了C++11的另外一個特性——初始化列表,通過初始化列表來初始化一個變長數組, {(printarg(args), 0)...}將會展開成((printarg(arg1),0), (printarg(arg2),0), (printarg(arg3),0), etc... ),最終會創建一個元素值都為0的數組int arr[sizeof...(Args)]。由于是逗號表達式,在創建數組的過程中會先執行逗號表達式前面的部分printarg(args)打印出參數,也就是說在構造int數組的過程中就將參數包展開了,這個數組的目的純粹是為了在數組構造的過程展開參數包

//方式一
template <class T>
void PrintArg(T t) {cout << t << " ";
}
//展開函數
template <class ...Args>
void ShowList(Args... args) {//PrintArg(args)是展開參數包的,逗號表達式是為了順序執行且返回最后一個值,//就是展開參數包后再返回0。因為0才能去初始化數組,參數包的返回值(void)不能初始化數組。int arr[] = { (PrintArg(args), 0)... };cout << endl;
}//方式二
template <class T>
int PrintArg(T t) {cout << t << " ";return 0;
}
//展開函數
template <class ...Args>
void ShowList(Args... args) {//數組開多大空間取決于參數包有幾個參數//有幾個參數PrintArg就會調用幾次int arr[] = { PrintArg(args) };cout << endl;
}int main() {ShowList(1);ShowList(1, 'A');ShowList(1, 'A', std::string("sort"));return 0;
}

演示可變參數包使用場景

namespace bit {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) { //const左值引用既可以引用左值,也可以引用右值cout << "string(const string& s) -- 拷貝構造 深拷貝" << endl;string tmp(s._str);swap(tmp);}//移動構造string(string&& s) {cout << "string(string&& s) -- 移動構造" << endl;swap(s);//this:指向正在構造的臨時對象(內存由編譯器分配)//臨時對象不新開空間}// 賦值重載string& operator=(const string& s) {cout << "string& operator=(string s) -- 賦值重載 深拷貝" << endl;//string tmp(s);//swap(tmp);if (this != &s) {char* tmp = new char[s._capacity + 1];strcpy(tmp, s._str);delete[] _str;_str = tmp;_size = s._size;_capacity = s._capacity;}return *this;}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 = nullptr;size_t _size = 0;size_t _capacity = 0; // 不包含最后做標識的\0};
}int main() {std::list<bit::string> lt;//該場景下,push_back 和 emplace_back沒區別bit::string s1("111");lt.push_back(s1);//拷貝構造 深拷貝lt.push_back(move(s1));//移動構造//void emplace_back (Args&&... args);bit::string s2("111");lt.emplace_back(s2);//拷貝構造 深拷貝lt.emplace_back(move(s2));//移動構造//插入匿名對象lt.push_back("xxx");//調用構造 和 移動構造lt.emplace_back("xxx");//調用構造//push_back 的聲明是 void push_back(const T& val) 或 void push_back(T&& val)。//"xxx" 是 const char* 類型,需要先構造一個臨時 bit::string 對象。//然后調用 push_back(T&& val)(臨時對象是右值,觸發移動構造)。//emplace_back 的聲明是 template <class... Args> void emplace_back(Args&&... args)。//它直接將參數包 Args... 轉發到 bit::string 的構造函數://對于 emplace_back("xxx"),Args 推導為 const char* 。//直接在 list 的節點內存中調用 bit::string("xxx"),省去臨時對象和移動操作。return 0;
}

多參數場景

int main() {std::list<pair<bit::string, int>> lt;lt.push_back(make_pair("1111", 1));//構造 + 移動構造lt.emplace_back("2222", 2);//構造//行為	    push_back	                emplace_back//構造時機	先構造臨時對象,再放入容器	直接在容器內構造//參數處理	必須構造完整對象傳入	        接受參數包,延遲構造//效率	    可能有額外拷貝 / 移動	        更高效,避免臨時對象return 0;
}

第十章:lambda表達式

10.1 C++98中的一個例子

在C++98中,如果想要對一個數據集合中的元素進行排序,可以使用std::sort方法。

如果待排序元素為自定義類型,需要用戶定義排序時的比較規則:

struct Goods {string _name; //名字double _price; //價格int _evaluate; //評價Goods(const char* str, double price, int evaluate):_name(str), _price(price), _evaluate(evaluate) {}
};struct ComparePriceLess {bool operator()(const Goods& gl, const Goods& gr) { return gl._price < gr._price; }
};
struct ComparePriceGreater {bool operator()(const Goods& gl, const Goods& gr) { return gl._price > gr._price; }
};int main() {vector<Goods> v = { { "蘋果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2, 3 }, { "菠蘿", 1.5, 4 } };//sort無法直接排序,需要自定義仿函數(即比較規則)sort(v.begin(), v.end(), ComparePriceLess());sort(v.begin(), v.end(), ComparePriceGreater());return 0;
}

隨著C++語法的發展,人們開始覺得上面的寫法太復雜了,每次為了實現一個algorithm算法,都要重新去寫一個類,如果每次比較的邏輯不一樣,還要去實現多個類,特別是相同類的命名,這些都給編程者帶來了極大的不便。因此,在C++11語法中出現了Lambda表達式。

10.2 lambda表達式

struct Goods {string _name; //名字double _price; //價格int _evaluate; //評價Goods(const char* str, double price, int evaluate):_name(str), _price(price), _evaluate(evaluate) {}
};struct ComparePriceLess {bool operator()(const Goods& gl, const Goods& gr) { return gl._price < gr._price; }
};
struct ComparePriceGreater {bool operator()(const Goods& gl, const Goods& gr) { return gl._price > gr._price; }
};int main() {vector<Goods> v = { { "蘋果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2, 3 }, { "菠蘿", 1.5, 4 } };sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2)->bool {return g1._price < g2._price; });sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {return g1._evaluate > g2._evaluate; });return 0;
}

10.3 lambda表達式語法

lambda表達式書寫格式:[capture-list] (parameters) mutable -> return-type { statement }

1. lambda表達式各部分說明
  • [capture-list] : 捕捉列表,該列表總是出現在lambda函數的開始位置,編譯器根據[]來判斷接下來的代碼是否為lambda函數,捕捉列表能夠捕捉上下文中的變量供lambda函數使用。
  • (parameters):參數列表。與普通函數的參數列表一致,如果不需要參數傳遞,則可以連同()一起省略
  • mutable:默認情況下,lambda函數總是一個const函數,mutable可以取消其常量性。使用該修飾符時,參數列表不可省略(即使參數為空)。
  • ->returntype:返回值類型。用追蹤返回類型形式聲明函數的返回值類型,沒有返回值時此部分可省略。返回值類型明確情況下,也可省略,由編譯器對返回類型進行推導。
  • {statement}:函數體。在該函數體內,除了可以使用其參數外,還可以使用所有捕獲到的變量。

注意:
在lambda函數定義中,參數列表和返回值類型都是可選部分,而捕捉列表和函數體可以為空。因此C++11中最簡單的lambda函數為:[]{}; 該lambda函數不能做任何事情。

2. 捕獲列表說明

捕捉列表描述了上下文中那些數據可以被lambda使用,以及使用的方式傳值還是傳引用
  • [var]:表示值傳遞方式捕捉變量var
  • [=]:表示值傳遞方式捕獲所有父作用域中的變量(包括this)
  • [&var]:表示引用傳遞捕捉變量var
  • [&]:表示引用傳遞捕捉所有父作用域中的變量(包括this)
  • [this]:表示值傳遞方式捕捉當前的this指針

注意:
?a. 父作用域指包含lambda函數的語句塊
?b. 語法上捕捉列表可由多個捕捉項組成,并以逗號分割。
? ? ?比如:[=, &a, &b]:以引用傳遞的方式捕捉變量a和b,值傳遞方式捕捉其他所有變量
? ? ?[&,a, this]:值傳遞方式捕捉變量a和this,引用方式捕捉其他變量
?c. 捕捉列表不允許變量重復傳遞,否則就會導致編譯錯誤。
? ? ?比如:[=, a]:=已經以值傳遞方式捕捉了所有變量,捕捉a重復
?d. 在塊作用域以外的lambda函數捕捉列表必須為空。
?e. 在塊作用域中的lambda函數僅能捕捉父作用域中局部變量,捕捉任何非此作用域或者非局部變量都會導致編譯報錯。
?f. lambda表達式之間不能相互賦值,即使看起來類型相同

lambda表達式 基本使用

int main() {auto f1 = [](int x)->int {cout << x << endl; return 0; };f1(1);cout << typeid(f1).name() << endl;//class <lambda_620c015845ff2142109bf3f80a50cb0a>//每個lambda都是一個類,f1就是一個類對象//f1對象可以調用operator(),所以lambda本質是仿函數//省略返回類型,編譯器自動推導auto f2 = [](int x) {cout << x << endl; return 0; };f2(2);cout << typeid(f2).name() << endl;//class <lambda_5d21ffa5b2bbbd0b5a72ac1266224191>ComparePriceGreater f3;cout << typeid(f3).name() << endl;//struct ComparePriceGreaterreturn 0;
}

lambda的其他用法

int main() {//使用lambda交換兩個元素int x = 0, y = 1;cout << x << " " << y << endl;auto f1 = [](int& r1, int& r2) {int tmp = r1;r1 = r2;r2 = tmp;};f1(x, y);cout << x << " " << y << endl;cout << x << " " << y << endl;//[]可以捕獲參數作為該類的成員變量,也就是仿函數的參數// ()中的參數默認是const修飾//所以加mutable可以取消其常量性//不需要參數傳遞()可以省略,但使用mutable參數列表不可省略//auto f2 = [x, y] () mutable { //交換失敗,因為這是傳值捕獲//auto f2 = [&x, &y] () mutable { //修改為傳引用捕獲//mutable 的作用是允許 lambda 表達式修改 按值捕獲的變量。//mutable 不影響引用捕獲,因為 mutable 控制的是 lambda 對象本身的狀態,//而引用捕獲只是別名,修改的是外部變量。auto f2 = [&x, &y] { //可以省略() mutableint tmp = x;x = y;y = tmp;};f2();cout << x << " " << y << endl;return 0;
}

[]捕獲用法

class AA {
public:void func() {//捕獲列表 [] 只能捕獲 當前作用域內的局部變量 或 this 指針(對于成員函數),//而不能直接捕獲 類的成員變量(如 _a1 和 _a2)。//在 AA::func() 內部,_a1 和 _a2 是 類的成員變量,而不是 局部變量。//C++ 的 lambda 捕獲機制 只能捕獲當前作用域內可見的變量(如局部變量、函數參數、全局變量等),//而成員變量必須通過 this 指針訪問。//auto f1 = [_a1, _a2] {//不可行//auto f1 = [this] {auto f1 = [=] {cout << _a1 << endl;cout << _a2 << endl;};f1();}
private:int _a1;int _a2;
};int main() {int x = 0, y = 1, z = 2;auto f1 = [=, &z] {z++;cout << x << endl;cout << y << endl;cout << z << endl;};f1();//捕獲列表[]:用于 讓 lambda 訪問外部變量(相當于給 lambda 類添加了成員變量)。//參數列表():用于 在調用 lambda 時傳入臨時參數(相當于函數參數)。//它們互相獨立,捕獲變量后仍然可以(或需要)參數列表,取決于你的需求。return 0;
}

10.4 函數對象與lambda表達式

函數對象,又稱為仿函數,即可以想函數一樣使用的對象,就是在類中重載了operator()運算符的類對象。

從使用方式上來看,函數對象與lambda表達式完全一樣。

函數對象將rate作為其成員變量,在定義對象時給出初始值即可,lambda表達式通過捕獲列表可以直接將該變量捕獲到。

實際在底層編譯器對于lambda表達式的處理方式,完全就是按照函數對象的方式處理的,即:如果定義了一個lambda表達式,編譯器會自動生成一個類,在該類中重載了operator()。

第十一章:包裝器

function包裝器 也叫作適配器。C++中的function本質是一個類模板,也是一個包裝器。
template<class F, class T>
T useF(F f, T x) {static int count = 0;cout << "count:" << ++count << endl;cout << "count:" << &count << endl;return f(x);
}
double f(double i) { return i / 2; }
struct Functor {double operator()(double d) { return d / 3; }
};int main() {// 函數名cout << useF(f, 11.11) << endl;// 函數對象cout << useF(Functor(), 11.11) << endl;// lamber表達式cout << useF([](double d)->double { return d / 4; }, 11.11) << endl;return 0;
}

通過上面的程序驗證,我們會發現useF函數模板實例化了三份。

包裝器可以很好的解決上面的問題

std::function在頭文件<functional>// 類模板原型如下
template <class T> function; ? ? // undefinedtemplate <class Ret, class... Args>
class function<Ret(Args...)>;模板參數說明:
Ret: 被調用函數的返回類型
Args…:被調用函數的形參

使用方法
void swap_func(int& r1, int& r2) {int tmp = r1;r1 = r2;r2 = tmp;
}struct Swap {void operator()(int& r1, int& r2) {int tmp = r1;r1 = r2;r2 = tmp;}
};// 包裝函數名(函數指針)\函數、函數對象、lamber表達式
int main() {int x = 0, y = 1;cout << x << " " << y << endl;auto swaplambda = [](int& r1, int& r2) {int tmp = r1;r1 = r2;r2 = tmp;};//function<void(int&, int&)> f1(swap_func);function<void(int&, int&)> f1 = swap_func;f1(x, y);cout << x << " " << y << endl;function<void(int&, int&)> f2 = Swap();f2(x, y);cout << x << " " << y << endl;function<void(int&, int&)> f3 = swaplambda;f3(x, y);cout << x << " " << y << endl;map<string, function<void(int&, int&)>> cmdOP = { {"函數指針",swap_func},{"仿函數",Swap()}, {"lambda",swaplambda} };cmdOP["函數指針"](x, y);cout << x << " " << y << endl;cmdOP["仿函數"](x, y);cout << x << " " << y << endl;cmdOP["lambda"](x, y);cout << x << " " << y << endl;return 0;
}

bind

std::bind函數定義在頭文件中,是一個函數模板,它就像一個函數包裝器(適配器),接受一個可調用對象(callable object),生成一個新的可調用對象來“適應”原對象的參數列表。一般而言,我們用它可以把一個原本接收N個參數的函數fn,通過綁定一些參數,返回一個接收M個(M可以大于N,但這么做沒什么意義)參數的新函數。同時,使用std::bind函數還可以實現參數順序調整等操作。

// 原型如下:
template <class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);
// with return type (2) 
template <class Ret, class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);

可以將bind函數看作是一個通用的函數適配器,它接受一個可調用對象,生成一個新的可調用對象來“適應”原對象的參數列表。
調用bind的一般形式:auto newCallable = bind(callable,arg_list);

其中,newCallable本身是一個可調用對象,arg_list是一個逗號分隔的參數列表,對應給定的callable的參數。當我們調用newCallable時,newCallable會調用callable,并傳給它arg_list中的參數。

arg_list中的參數可能包含形如_n的名字,其中n是一個整數,這些參數是“占位符”,表示newCallable的參數,它們占據了傳遞給newCallable的參數的“位置”。數值n表示生成的可調用對象中參數的位置:_1為newCallable的第一個參數,_2為第二個參數,以此類推。

演示bind用法

int Sub(int a, int b) { return a - b; }int main() {function<int(int, int)> f1 = Sub;cout << f1(10, 5) << endl;//5//調整參數的順序function<int(int, int)> f2 = bind(Sub, placeholders::_2, placeholders::_1);cout << f2(10, 5) << endl;//-5//調整參數的個數,有些參數可以bind時寫死。20固定是第一個function<int(int)> f3 = bind(Sub, 20, placeholders::_1);cout << f3(5) << endl;//15return 0;
}

包裝類的成員函數

int f(int a, int b) { return a + b; }struct Functor {
public:int operator() (int a, int b) { return a + b; }
};class Plus {
public:static int plusi(int a, int b) { return a + b; }double plusd(double a, double b) { return a + b; }
};int main() {//成員函數取地址要加&和類域,雖然靜態成員函數不用&,但建議加上function<int(int, int)> f1 = &Plus::plusi;cout << f1(1, 2) << endl;////成員函數參數中還有this指針,所以不匹配//function<double(double, double)> f2 = &Plus::plusd;function<double(Plus*, double, double)> f2 = &Plus::plusd;Plus ps;cout << f2(&ps, 1.1, 2.2) << endl;//cout << f2(&Plus(), 1.1, 2.2) << endl;//不可以傳匿名對象(是右值),左值才能取地址//特殊處理,相當于用對象調函數function<double(Plus, double, double)> f3 = &Plus::plusd;cout << f3(Plus(), 1.1, 2.2) << endl;//使用bind綁定第一個參數function<double(double, double)> f4 = bind(&Plus::plusd, Plus(), placeholders::_1, placeholders::_2);cout << f4(1.1, 2.2) << endl;return 0;
}

作業

1. 下面關于右值引用作用說法錯誤的是()

A.右值引用于引用一樣,只是一個別名,別無它用
B.通過右值引用可以實現完美轉發
C.通過右值引用可以將臨時對象中的資源轉移出去
D.通過右值引用可以實現移動構造函數,提高程序運行效率

答案:A
A:錯誤,右值引用是C++11中的一個重點,可以實現移動語義、完美轉發
B:正確,實現完美轉發時,需要用到forward函數
C:正確,右值引用的一個主要作用就是實現移動語義,以提高程序的效率
D:正確

2. 關于引用和右值引用的區別,說法正確的是()

A.引用只能引用左值,不能引用右值
B.右值引用只能引用右值,不能引用左值
C.引用和右值引用都可以引用左值和右值
D.以上說法都不對

答案:C
A:錯誤,T& 只能引用左值,const T& 是萬能引用,左值和右值都可以引用
B:錯誤,一般右值引用只能引用右值,如果需要引用左值時,可以通過move函數轉
C:正確,參考A和B的解析
D:錯誤

3. 關于右值引用說法正確的是()

A.引用是別名,使用比指針更方便安全,右值引用有點雞肋
B.右值引用不能引用左值
C.右值引用只能引用右值
D.右值引用于引用一樣,都是別名

答案:D
A:錯誤,右值引用是C++11中的一個重點,可以實現移動語義、完美轉發
B:錯誤,一般右值引用只能引用右值,如果需要引用左值時,可以通過move函數轉
C:錯誤,同上
D:正確,右值引用也是引用,是別名

4. 下面關于lambda表達式說法錯誤的是()

A.lambda表達式的捕獲列表不能省略,但可以使空
B.lambda表達式就是定義了一個函數
C.lambda表達式底層編譯器是將其轉化為仿函數進行處理的
D.[]{}是一個lambda表達式

答案:B
A:正確,捕獲列表是編譯器判斷表達式是否為lambda表達式的依據,即使為空,也不能省略
B:錯誤,lambda表達式不是一個函數,在底層編譯器將其轉化為仿函數
C:正確
D:正確,lambda表達式原型:[捕獲列表](參數列表)mutable->返回值類型 {}
如果不需要捕獲父作用域中內容時,可以為空,但是[]不能省略,如果沒有參數,參數列表可以省略
如果不需要改變捕獲到父作用域中內容時,mutable可以省略,返回值類型也可以省略,讓編譯器根據返回的結果進行推演,但是{}不能省略,因此[]{}是最簡單的lambda表達式,但是該lambda表達式沒有任何意義

5. 下面關于默認的構造函數說法正確的是()

A.C++98中用戶可以選擇讓編譯器生成或者不生成構造函數
B.在C++11中,即使用戶定義了帶參的構造函數,也可以通過default讓編譯器生成默認的構造函數
C.delete的作用是刪除new的資源,別無它用
D.以下代碼會編譯通過: class A { public: A(){} A(int)=delete; }

答案:B
A:錯誤,C++98中用戶不能選擇,用戶如果沒有定義構造函數,編譯器會生成默認的,如果顯式定義,編譯器將不再生成
B:正確,C++11擴展了delete和default的用法,可以用來控制默認成員函數的生成與不生成
C:錯誤,C++11擴展了delete的用法,可以讓用戶控制讓編譯器不生成默認的成員函數
D:錯誤,類定義結束后沒有分號

6. 下面關于override說法正確的是()

A.override的作用發生在運行時期
B.override修飾子類成員函數時,編譯時編譯器會自動檢測是否對基類中那個成員函數進行重寫
C.override可以修飾基類的虛函數
D.override只能修飾子類的虛函數

答案:D
A:override的作用時讓編譯器幫助用戶檢測是否派生類是否對基類總的某個虛函數進行重寫,如果重寫成功,編譯通過,否則,編譯失敗,因此其作用發生在編譯時。
B:錯誤,修飾子類虛函數時,編譯時編譯器會自動檢測是否對基類中那個成員函數進行重寫
C:錯誤,不能,因為override主要是檢測是否重寫成功的,而基類的虛函數不可能再去重寫那個類的虛函數
D:正確

7. 下列關于final說法正確的是()

A.final只能修飾類,表示該類不能被繼承
B.final可以修飾任意成員函數
C.final修飾成員函數時,表示該函數不能被子類繼承
D.final修飾派生類虛函數時,表示該虛函數再不能被其子類重寫

答案:D
A:錯誤,final修飾類時,表示該類不能被繼承,修飾派生類的虛函數時,表示該虛函數不能被子類繼承
B:錯誤,只能修飾派生類的虛函數
C:錯誤,修飾派生類虛函數時
D:正確

8. 下面關于列表初始化說法正確的是()

A.C++語言從C++98開始,就支持列表方式的初始化
B.列表初始化沒有什么實際作用,直接調用對應的構造函數就可以了
C.自定義類型可以支持多個對象初始化,只需要增加initializer_list類型的構造函數即可
D.以下關于c和d的初始化,結果完全相同 // short c = 65535; short d { 65535 };

答案:C
A:錯誤,列表初始化是從C++11才開始支持的
B:錯誤,列表初始化可以在定義變量時就直接給出初始化,非常方便
C:正確
D:錯誤,不同,列表初始化在初始化時,如果出現類型截斷,是會報警告或者錯誤的

9. 下面關于列表初始化說法錯誤的是()

A.在C++98中,{}只能用來初始化數組
B.在C++98中,new單個int類型空間可以直接初始化,new一段連續int類型空間不能直接初始化
C.在C++11中,{}的初始化范圍增大了,任意類型都可以初始化
D.使用{}初始化時,必須要加=號

答案:D
A:正確,C++98中只能初始化數組,C++11中支持列表初始化,才可以初始化容器
B:正確,C++11對于new[]申請的空間,可以直接初始化
C:正確,對于自定義類型,需要提供initializer_list<T>類型的構造函數
D:錯誤,加 = 和不加 = 沒有區別

10. 下面關于范圍for說法錯誤的是()

A.范圍for可以直接應用在數組上
B.對于STL提供的所有容器,均可以使用for依次訪問器元素
C.使用范圍for操作stack,可以簡化代碼
D.對于自定義類型,想要支持范圍for,必須提供begin和end迭代器
E.范圍for編譯器最終是將其轉化為迭代器來進行處理的

答案:C
A:正確,只要是范圍確定的,都可以直接使用范圍for
B:正確,對于STL提供的容器,采用范圍for來進行遍歷時,編譯器最后還是將范圍for轉化為迭代器來訪問元素的
C:錯誤,stack不需要遍歷,也沒有提供迭代器,因此不能使用范圍for遍歷
D:正確,因為范圍for最終被編譯器轉化為迭代器來遍歷的
E:正確

11. 下面關于auto的說法錯誤的是:()

A.auto主要用于類型推到,可以讓代碼的書寫更簡潔
B.在C++11中,auto即可以用來聲明自動類型變量,也可以用來進行變量類型推導
C.auto不能推導函數參數的類型
D.auto是占位符,在編譯階段,推演出初始化表達式的實際類型來替換auto位置

答案:B
A:正確
B:錯誤,C++11中已經去除了auto聲明自動類型變量的功能,只可以用來進行變量類型推到
C:正確,因為函數在編譯時,還沒有傳遞參數,因此在編譯時無法推演出形參的實際類型
D: 正確,auto僅僅只是占位符,編譯階段編譯器根據初始化表達式推演出實際類型之后會替換auto
?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/93219.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/93219.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/93219.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

把振動數據轉成音頻并播放

把振動數據轉聲音并播放 1、實現流程 安裝第三方庫: pip install numpy==1.23.5 pip install scipy==1.10.1 pip install sounddevice==0.4.6流程: 1、導入振動數據 2、數據歸一化到[-1, 1]范圍 3、重采樣到44.1kHz 4、播放音頻 5、保存音頻為WAV文件(可選)2、代碼示例 …

ServBay 1.15.0 更新,擁抱 Bun Deno 新生態

歷時一個月&#xff0c;ServBay迎來了1.15.0的更新。我們始終堅信&#xff0c;一個優秀的本地開發環境&#xff0c;不僅要穩定、高效&#xff0c;更要緊跟技術的演進脈搏。ServBay 的使命是為開發者掃清開發環境配置的障礙&#xff0c;讓您能聚焦于創造本身。 本次ServBay 1.1…

Java設計模式-通俗舉例

設計模式就像做菜的食譜&#xff0c;告訴我們遇到常見問題時該用什么"烹飪方法"。今天我就用最生活化的例子&#xff0c;帶大家輕松掌握23種設計模式的精髓。一、創建型模式&#xff08;5種&#xff09;&#xff1a;怎么"造東西"1. 單例模式&#xff1a;公…

【跟我學YOLO】YOLO12(3)訓練自己的數據集

歡迎關注『跟我學 YOLO』系列 【跟我學YOLO】&#xff08;1&#xff09;YOLO12&#xff1a;以注意力為中心的物體檢測 【跟我學YOLO】&#xff08;2&#xff09;YOLO12 環境配置與基本應用 【跟我學YOLO】&#xff08;3&#xff09;YOLO12 訓練自己的數據集 【跟我學YOLO】&…

【NLP輿情分析】基于python微博輿情分析可視化系統(flask+pandas+echarts) 視頻教程 - 微博輿情分析實現

大家好&#xff0c;我是java1234_小鋒老師&#xff0c;最近寫了一套【NLP輿情分析】基于python微博輿情分析可視化系統(flaskpandasecharts)視頻教程&#xff0c;持續更新中&#xff0c;計劃月底更新完&#xff0c;感謝支持。今天講解微博輿情分析實現 視頻在線地址&#xff1…

【C++】手搓一個STL風格的vector容器

TOC(手搓一個STL風格的vector容器) 手搓一個STL風格的vector容器 github地址 有夢想的電信狗 0. 前言&#xff1a;動態數組的工程實踐 ? 在C標準庫中&#xff0c;vector容器作為最核心的序列式容器&#xff0c;其設計融合了動態數組的高效性與安全性。本文將通過完整實現…

24. 了解過 webp 嗎

總結 一種圖片格式 一、什么是 WebP&#xff1f; WebP&#xff08;發音為 “weppy”&#xff09;是由 Google 推出的一種現代圖片格式&#xff0c;支持有損壓縮和無損壓縮&#xff0c;旨在提供更小的文件體積和更高質量的圖像顯示。 它兼容常見的圖片功能&#xff0c;如&#…

【Unity筆記】Unity Camera.cullingMask 使用指南:Layer 精準控制、XR 多視圖與性能提升

Unity Camera.cullingMask 使用指南&#xff1a;Layer 精準控制、XR 多視圖與性能提升 關鍵詞&#xff1a;Unity、Camera、Culling Mask、Layer 控制、XR 渲染分離、UI 顯隱、性能優化 特別說明&#xff1a; 本文為近期項目所遇問題的總結&#xff0c;僅純文字記錄&#xff0c;…

攜帶參數的表單文件上傳 axios, SpringBoot

頁面上的表單如上圖, 點擊確定按鈕需要把參數統一傳給后端.前端代碼:表單的提交方法const submit async () > {const formData new FormData();formData.append("bookName", bookForm.value.bookName);formData.append("author", bookForm.value.auth…

黑馬JavaWeb【復習到哪更新到哪】

登錄認證&#xff08;復習Javaweb的登錄校驗&#xff09; 登錄功能 思路就是loginController->service層->mapper層&#xff0c;從數據庫中查找username和password是否和前端用戶提交的表單內容一致&#xff0c;一致就登錄成功&#xff0c;否則就返回登錄失敗的信息。 登…

NVMe高速傳輸之擺脫XDMA設計21:PCIe請求模塊設計(下)

在接收到請求總線接口的請求事務后&#xff0c;當請求類型的值為0時&#xff0c;表示通過PCIE硬核的配置管理接口發送請求&#xff0c;由于請求接口的接口和時序與配置管理接口基本一致&#xff0c;因此此時直接將請求接口信號驅動到配置管理接口完成請求的發送&#xff0c;請求…

機器學習sklearn:不純度與決策樹構建

不純度與決策樹構建不純度概念&#xff1a;決策樹通過不純度指標來選擇最佳分割節點和分枝方式不純度衡量節點中樣本類別的混雜程度不純度越低&#xff0c;節點中樣本類別越純凈&#xff0c;擬合效果越好常用不純度指標&#xff1a;信息熵(Entropy)&#xff1a;基于信息論的概念…

rk356x IR紅外發射與接收之NEC協議

紅外接收紅外接收頭解碼器&#xff08;紅外信號解碼&#xff0c;主要是NEC解碼&#xff09;紅外發射器紅外發光二極管晶振NEC編碼組成共32位&#xff08;4bit&#xff09;&#xff1a;由8位用戶碼1 8位用戶碼2 8位命令碼 8位命令碼反碼有時會存在按鍵一直按下的一幀信息&…

C++算法之單調棧

C算法中的單調棧&#xff1a;從入門到實戰指南 大家好&#xff01;今天我們來聊聊C算法中一個超級實用的工具——單調棧。別被名字嚇到&#xff0c;它其實很簡單&#xff0c;就像排隊買奶茶一樣&#xff1a;隊伍總是從矮到高&#xff08;或從高到矮&#xff09;排得整整齊齊&a…

React入門指南——指北指南(第二節)

React 實踐:創建你的第一個待辦事項列表 在前面的章節中,我們學習了 React 的核心概念(組件、Props、State 等)。本節將通過一個實際案例——創建待辦事項列表(Todo List),幫助你鞏固這些概念,并掌握 React 中處理用戶交互、動態數據的基本方法。 案例目標 我們將構…

WAIC看點:可交付AI登場,場景智能、專屬知識將兌現下一代AI價值

7月28日&#xff0c;為期三天的2025世界人工智能大會&#xff08;WAIC 2025&#xff09;在上海落下帷幕。作為全球 AI 領域最受關注的盛會之一&#xff0c;今年 WAIC 聚焦 AI 關鍵命題&#xff0c;圍繞大模型與智能體應用、算力新基建及大數據、智能終端與具身智能、AI金融、AI…

設計模式(十一)結構型:外觀模式詳解

設計模式&#xff08;十一&#xff09;結構型&#xff1a;外觀模式詳解外觀模式&#xff08;Facade Pattern&#xff09;是 GoF 23 種設計模式中的結構型模式之一&#xff0c;其核心價值在于為一個復雜的子系統提供一個統一、簡化的高層接口&#xff0c;從而降低客戶端與子系統…

接口測試核心概念與實踐指南

核心概念什么是接口&#xff1f;軟件不同部分之間進行通信和數據交換的約定或契約。定義了&#xff1a;請求方 (Client/Consumer) 如何調用&#xff08;方法、URL、參數&#xff09;。提供方 (Server/Provider) 如何響應&#xff08;數據結構、狀態碼&#xff09;。雙方需要遵循…

【NLP輿情分析】基于python微博輿情分析可視化系統(flask+pandas+echarts) 視頻教程 - 熱詞數量分析日期統計功能實現

大家好&#xff0c;我是java1234_小鋒老師&#xff0c;最近寫了一套【NLP輿情分析】基于python微博輿情分析可視化系統(flaskpandasecharts)視頻教程&#xff0c;持續更新中&#xff0c;計劃月底更新完&#xff0c;感謝支持。今天講解熱詞數量分析日期統計功能實現 視頻在線地…

ICPC 2024 網絡賽(I)

M. Find the Easiest Problem 題目大意 給定所有的提交記錄&#xff0c;找到通過隊伍最多且字典序最小的題目。 解題思路 按題意模擬即可 代碼實現 #include <bits/stdc.h>using i64 long long;int main() {std::ios::sync_with_stdio(false);std::cin.tie(0);std…