第十九講:C++11第一部分

目錄

1、C++11簡介

2、列表初始化

2.1、{}初始化

2.2、initializer_list

2.2.1、成員函數

2.2.2、應用

3、變量類型推導

3.1、auto

3.2、decltype

3.3、nullptr

4、范圍for

5、智能指針

6、STL的一些變化

7、右值引用和移動語義

7.1、右值引用

7.2、右值與左值引用對比

7.2.1、左值引用

7.2.2、右值引用

7.3、右值引用的使用場景

7.4、右值引用引用移動左值

7.5、萬能引用與完美轉發

7.5.1、萬能引用?

7.5.2、完美轉發


1、C++11簡介

在2003年C++標準委員會曾經提交了一份技術勘誤表,使得C++03這個名字已經取代了C++98稱為C++11之前的最新C++標準名稱。不過由于C++03主要是對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增加的語法特性非常篇幅非常多,我們這里沒辦法一一講解,所以主要講解實際中比較實用的語法。

注:1998年是C++標準委員會成立的第一年,本來計劃以后每5年視實際需要更新一次標準,C++國際標準委員會在研究C++03的下一個版本的時候,一開始計劃是2007年發布,所以最初這個標準叫 C++ 07。但是到06年的時候,官方覺得2007年肯定完不成C++07,而且官方覺得2008年可能也完不成。最后干脆叫C++ 0x。x的意思是不知道到底能在07還是08還是09年完成。結果2010年的時候也沒完成,最后在2011年終于完成了C++標準,所以最終定名為C++11。另外,C++也是從C++98開始成熟起來的。

2、列表初始化

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擴大了大括號的使用范圍,使其可用于所有的內置類型和用戶自定義的類型,使用初始化列表時,可添加等號(也就是賦值符號=),也可不添加。例如:

struct Point
{int _x;int _y;
};int main()
{int x1 = 1;int x2{ 2 };int x3 = { 3 };int array1[]{ 1, 2, 3, 4, 5 };int array2[5]{ 0 };Point p{ 1, 2 };// C++11中列表初始化也可以適用于new表達式中int* pa = new int[4]{ 0 };return 0;
}

創建對象時也可以使用列表初始化方式調用構造函數初始化,例如:

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;}private:int _year;int _month;int _day;
};int main()
{Date d1(2022, 1, 1); // 舊的使用方式// C++11支持的列表初始化,這里會調用構造函數初始化//注意:實際上這里的本質是類型轉換,構造+拷貝構造->直接構造Date d2{ 2022, 1, 2 };Date d3 = { 2022, 1, 3 };Date* p1 = new Date[3]{ d1, d2, d3 };Date* p2 = new Date[3]{ {2022,11,25},{2022,11,25},{2022,11,26} };return 0;
}

2.2、initializer_list

template<class T> class initializer_list;

initializer_list參考文檔

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

initializer_list是什么類型呢?例如:

int main()
{//il的類型是initializer_listauto il = { 10, 20, 30 };cout << typeid(il).name() << endl; //在typeindex庫中return 0;
}

運行結果為:

2.2.1、成員函數
initializer_list() noexcept; // 構造size_t size() const noexcept; // 返回元素個數const T* begin() const noexcept; // 迭代器
const T* end() const noexcept;

例如:

int main()
{initializer_list<int> lt = { 10, 20, 30 };initializer_list<int>::iterator it = lt.begin();while (it != lt.end()){cout << *it << ' ';++it;}cout << endl;for (auto e : lt){cout << e << ' ';}cout << endl;cout << lt.size() << endl;return 0;
}

如何理解上面的initializer_list呢?,實際上initializer_list可以理解成內部有兩個指針,假設叫start和finish,數據會以數組的形式放到常量區存著,start和finish分別指向這個數組的首和尾,如下圖所示:

2.2.2、應用

例如:

int main()
{vector<int> v = { 1, 2, 3, 4 }; list<int> lt = { 1, 2 };// 這里{"sort", "排序"}會先隱式類型轉換,然后再區間構造。map<string, string> dict = { {"sort", "排序"}, {"insert", "插入"} };// 使用大括號對容器賦值v = { 10, 20, 30 };return 0;
}

讓我們之前模擬實現的vector也支持{}初始化,例如:

vector(initializer_list<T> lt) // C++11新增內容
{reserve(lt.size());for (auto& e : lt){push_back(e);}
}

注意:{}初始化與initializer_list是不同的。

3、變量類型推導

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

3.1、auto

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

例如:

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;
}

3.2、decltype

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

例如:

// 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的類型是const int*cout << typeid(ret).name() << endl;cout << typeid(p).name() << endl; // 注意:這個name的作用是返回該類型的字符串,它返回的這個字符串是不能用來定義變量的。F(1, 'a');return 0;
}

3.3、nullptr

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

4、范圍for

范圍for的底層就是迭代器,這個我們在前面的內容中已經進行了講解,這里就不進行講解了。

5、智能指針

這個在后面單獨進行講解,在這里就不進行講解了,因為內容比較多。

6、STL的一些變化

6.1、新容器:新添加的容器有array、forward_list、unordered_map以及unordered_set等,但實際上最有用的是unordered_map 和 unordered_set。

6.2、新的構造(新的構造是指initializer_list作為參數的構造。)

6.3、移動構造和移動賦值(后面講)

6.4、右值引用插入(后面講)

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

7、右值引用和移動語義

7.1、右值引用

傳統的C++語法中就有引用的語法,而C++11中新增了的右值引用語法特性,所以從現在開始我們 之前學習的引用就叫做左值引用。無論左值引用還是右值引用,都是給對象取別名。

左值是一個表示數據的表達式,我們可以獲取它的地址,一般也可以對它賦值,左值可以出現賦值符號的左邊(也可以出現在右邊),右值不能出現在賦值符號左邊。定義時const修飾后的左值,不能給他賦值,但是可以取它的地址,也屬于左值。左值引用就是給左值取的引用,也就是給左值取別名。簡單來說只要可以取地址的,都是左值。

例如:

int main()
{// 以下的p、b、c、*p都是左值int* p = new int(0);int b = 1;const int c = 2;*p = 10;// 以下幾個是對上面左值的左值引用int*& rp = p;int& rb = b;const int& rc = c;int& rpp = *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); // 該函數在math.h文件中// 下面編譯會報錯:左操作數必須為左值//10 = 1;//x + y = 1;//fmin(x, y) = 1;return 0;
}

需要注意的是右值是不能取地址的,但是給右值取別名后,會導致右值被存儲到特定位置,且可 以取到該位置的地址,也就是說例如:不能取字面量10的地址,但是rr1引用后,可以對rr1取地 址,也可以修改rr1。如果不想rr1被修改,可以用const int&& rr1 去引用。例如:

int main()
{double x = 1.1, y = 2.2;int&& rr1 = 10;const double&& rr2 = x + y;rr1 = 20;//rr2 = 5.5;  // 報錯return 0;
}

7.2、右值與左值引用對比

7.2.1、左值引用

左值引用總結:

1、左值引用只能引用左值,不能引用右值。

2、但是const左值引用既可引用左值,也可引用右值。

例如:

int main()
{// 左值引用只能引用左值,不能引用右值。int a = 10;int& ra1 = a;//int& ra2 = 10;   // 編譯失敗,因為10是右值// const左值引用既可引用左值,也可引用右值。const int& ra3 = 10;const int& ra4 = a;return 0;
}
7.2.2、右值引用

右值引用總結:

1、右值引用只能引用右值,不能引用左值。

2、但是右值引用可以引用move以后的左值。(后面講)

例如:

int main()
{// 右值引用只能右值,不能引用左值int&& r1 = 10;int a = 10;//int&& r2 = a; // 報錯// 右值引用可以引用move以后左值int&& r3 = std::move(a);return 0;
}

7.3、右值引用的使用場景

前面我們可以看到左值引用既可以引用左值和又可以引用右值,那為什么C++11還要提出右值引 用呢?右值引用就是用來補左值引用的短板的,下面我們來看看左值引用的短板,以及右值引用是如何補齊這個短板的。

左值引用的使用場景:

1、引用傳參:可以提高效率。

2、引用返回:也可以提高效率(部分場景適用)

左值引用的短板:當函數返回對象是一個局部變量,出了函數作用域就不存在了,就不能使用左值引用返回, 只能傳值返回,傳值返回在有些情況下效率極低。

例如:下面的to_String函數就沒法使用左值引用返回

#include <iostream>
#include <cassert>
#include <string>
#include <list>
using namespace std;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);}void swap(String& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}// 拷貝構造String(const String& s){cout << "String(const String& s)-拷貝構造" << endl;_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}// 移動構造/*String(String&& s){cout << "String(String&& s)-移動構造" << endl;swap(s);}*/// 拷貝賦值String& operator=(const String& s){cout << "String& operator=(const String& s)-拷貝賦值" << endl;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(){cout << "~String()" << endl;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){push_back(ch);return *this;}const char* c_str() const{return _str;}private:char* _str = nullptr;size_t _size = 0;size_t _capacity = 0;};bit::String to_String(int value){bool flag = true;if (value < 0){flag = false;value = 0 - value;}bit::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;}
}int main()
{bit::String s1;s1 = bit::to_String(1234);return 0;
}

運行結果為:

從上面的結果中可以看到,傳值返回會導致一次拷貝構造和一次拷貝賦值。在這個過程中str拷貝一份給中間變量,然后str銷毀,然后中間變量再賦值給s1,最后中間變量再銷毀,在這個過程中存在極大的浪費。如下圖所示:

例如:對于下面的場景也是類似

int main()
{bit::String s1 = bit::to_String(1234);return 0;
}

運行結果為:

從上面的運行結果可以看出,傳值返回會導致兩次拷貝構造。在這個過程中str拷貝一份給中間變量,然后str銷毀,然后中間變量再拷貝給s1,最后中間變量再銷毀。也存在極大的浪費。

注:有些新一些的編譯器可能會有一些優化,比如上面的兩次拷貝構造就變成了一次拷貝構造。

右值引用和移動語義可以解決上述問題:移動構造和移動賦值本質是將右值的資源竊取過來,占為已有,那么就不用做深拷貝了。

例如:我們把上面代碼的移動構造和移動賦值的注釋去掉,有了移動構造和移動賦值以后,之前上面的兩個場景,就提高了效率,如下:

int main()
{bit::String s1;s1 = bit::to_String(1234);return 0;
}

運行結果為:

對于上面的程序,這時調用的是移動構造和移動賦值,不再調用拷貝構造和拷貝賦值了。從程序的運行結果我們可以看到,編譯器很聰明的在這里把 str 識別成了右值(實際上str是左值),調用了移動構造然后再把這個臨時對象(該臨時對象是右值)作為 to_String 函數的返回值賦值給ret,這里調用的是移動賦值。因為移動構造和移動賦值僅僅只是轉移資源。所以效率得到了很大的提升。

例如:對于下面的情形也是類似

int main()
{bit::String s1 = bit::to_String(1234);return 0;
}

運行結果為:

對于上面的程序,就不再調用拷貝構造了,而是調用了移動構造,移動構造中沒有新開空間,拷貝數據,所以效率提高了。

注:有些新一些的編譯器可能會有一些優化,比如上面的兩次移動構造就變成了一次移動構造。

注意:移動構造和移動賦值對于深拷貝的類是有意義的,因為可以提高效率;對于淺拷貝的類沒有多大的意義。

STL中的容器,在C++11以后都是增加了移動構造和移動賦值的。例如:以list為例

7.4、右值引用引用移動左值

有些場景下,需要用右值去引用左值實現移動語義。當需要用右值引用引用一個左值時,可以通過move 函數將左值轉化為右值。C++11中,move()函數的聲明位于utility文件中,該函數并不搬移任何東西,唯一的功能就是將一個左值強制轉化為右值引用,然后實現移動語義。例如:

int main()
{bit::String s1("hello world");// 這里s1是左值,調用的是拷貝構造bit::String s2(s1);// 這里我們把s1 move處理以后, 會被當成右值,調用移動構造// 但是這里要注意,一般是不要這樣用的,因為我們會發現s1的// 資源被轉移給了s3,s1被置空了。bit::String s3(std::move(s1));return 0;
}

注意:move(左值) 是不改變左值本身的,而是返回值是一個右值。也就是說move(左值) 是不改變左值的屬性的,只是返回值是一個右值。

例如:

int main()
{bit::String s1("hello world");std::move(s1);bit::String s3 = s1; // 調用的仍然是拷貝構造return 0;
}

STL中的容器,在C++11以后插入接口也增加了右值引用的版本。例如:以list為例

例如:

int main()
{list<bit::String> lt;bit::String s1("hello world");cout << "------" << endl;lt.push_back(s1); // 調用普通版本cout << "------" << endl;lt.push_back(bit::to_String(1234)); // 調用右值引用版本cout << "------" << endl;lt.push_back("111111"); // 調用右值引用版本,先構造一個臨時對象,然后再進行移動構造cout << "-------" << endl;return 0;
}

運行結果為:

例如:我們可以給我們模擬實現的list加上右值引用版本的插入接口

namespace bit
{template<class T>struct list_node{T _data;list_node<T>* _next;list_node<T>* _prev;list_node(const T& x = T()): _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;}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 const_iterator(_head->_next);}const_iterator end() const{return const_iterator(_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();}list(const list<T>& lt){empty_init();for (auto e : lt){push_back(e);}}void swap(list<T>& lt){std::swap(_head, lt._head);std::swap(_size, lt._size);}list<int>& operator=(list<int> lt){swap(lt);return *this;}~list(){clear();delete _head;_head = nullptr;}void clear(){iterator it = begin();while (it != end()){it = erase(it);}}void push_back(const T& x){insert(end(), x);}void push_back(T&& x)  //  右值引用版本{insert(end(), move(x));}void push_front(const T& x){insert(begin(), x);}void pop_front(){erase(begin());}void pop_back(){erase(--end());}iterator insert(iterator pos, const T& x){Node* cur = pos._node;Node* newnode = new Node(x);Node* prev = cur->_prev;prev->_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));Node* prev = cur->_prev;prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;++_size;return iterator(newnode);}iterator erase(iterator pos){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;};
}

同樣的程序,如下:使用我們模擬實現的接口

int main()                                                                                                                                   
{                                                                                                                                            bit::list<bit::String> lt;                                                                                                                  bit::String s1("hello world");                                                                                                              cout << "------------" << endl;                                                                                                             lt.push_back(s1);                                                                                                                           cout << "-------------" << endl;                                                                                                            lt.push_back(bit::to_String(1234));                                                                                                         cout << "------------" << endl;                                                                                                             lt.push_back("111111");                                                                                                                     cout << "------------" << endl;                                                                                                                      return 0;
}

運行結果為:

注意:右值被右值引用之后的別名的屬性是左值。右值不能修改,但是右值被右值引用之后的別名就可以被修改了。

7.5、萬能引用與完美轉發

7.5.1、萬能引用?

例如:

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);
}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;
}

運行結果為:

如果我們對 t 使用 move的話,例如:

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(move(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;
}

運行結果為:

如果我們希望能夠在傳遞過程中保持它的左值或者右值的屬性,就需要完美轉發。

注意:只有函數模板才可以萬能引用。

7.5.2、完美轉發

完美轉發在傳參的過程中保留對象原生類型屬性,使用forward(在utility文件中)實現,它通過模板參數 T 來判斷原始對象的類型,并根據 T 的屬性(左值引用或非左值引用),將參數 “轉發” 為對應的左值或右值。

例如:

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; }// std::forward<T>(t)在傳參的過程中保持了t的屬性。
template<typename T>
void PerfectForward(T&& t)
{Fun(std::forward<T>(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;
}

運行結果為:

對于上面的list的模擬實現,其實使用forward比使用move更好一些。例如:

namespace bit
{template<class T>struct list_node{T _data;list_node<T>* _next;list_node<T>* _prev;list_node(const T& x = T()): _data(x), _next(nullptr), _prev(nullptr){}list_node(T&& x)   // 右值引用: _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;}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 const_iterator(_head->_next);}const_iterator end() const{return const_iterator(_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();}list(const list<T>& lt){empty_init();for (auto e : lt){push_back(e);}}void swap(list<T>& lt){std::swap(_head, lt._head);std::swap(_size, lt._size);}list<int>& operator=(list<int> lt){swap(lt);return *this;}~list(){clear();delete _head;_head = nullptr;}void clear(){iterator it = begin();while (it != end()){it = erase(it);}}void push_back(const T& x){insert(end(), x);}void push_back(T&& x)  //  右值引用版本{insert(end(), forward<T>(x));}void push_front(const T& x){insert(begin(), x);}void pop_front(){erase(begin());}void pop_back(){erase(--end());}iterator insert(iterator pos, const T& x){Node* cur = pos._node;Node* newnode = new Node(x);Node* prev = cur->_prev;prev->_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(forward<T>(x));Node* prev = cur->_prev;prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;++_size;return iterator(newnode);}iterator erase(iterator pos){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;};
}

總結:右值引用和移動語義出來以后,對深拷貝的類影響比較大,對于淺拷貝的類意義不大,因為淺拷貝的類沒有資源可以轉移。

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

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

相關文章

書寫本體論視域下的文字學理論重構

在符號學與哲學的交叉領域&#xff0c;文字學&#xff08;Grammatologie&#xff09;作為一門顛覆性學科始終處于理論風暴的中心。自德里達1967年發表《論文字學》以來&#xff0c;傳統語言學中"語音中心主義"的霸權地位遭遇根本性動搖&#xff0c;文字不再被視為語言…

為什么要做架構設計?架構設計包含哪些內容?

大家好,我是IT孟德,You can call me Aman(阿瞞,阿彌陀佛的ē,Not阿門的ā),一個喜歡所有對象(熱愛技術)的男人。我正在創作架構專欄,秉承ITer開源精神分享給志同道合(愛江山愛技術更愛美人)的朋友。專欄更新不求速度但求質量(曹大詩人傳世作品必屬精品,請腦補一下《…

Vue2封裝Axios

一、介紹Axios 是一個基于 promise 的 HTTP 庫&#xff0c;簡單的講就是可以發送get、post等請求。二、安裝npm install axios --save二、axios不同請求方式axios(config)這是 Axios 的核心方法&#xff0c;用于發送自定義配置的 HTTP 請求。通過傳入一個包含請求配置的對象&am…

DataAnalytics之Tool:Metabase的簡介、安裝和使用方法、案例應用之詳細攻略

DataAnalytics之Tool&#xff1a;Metabase的簡介、安裝和使用方法、案例應用之詳細攻略 目錄 Metabase的簡介 1、特點 Metabase的安裝和使用方法 1、安裝 快速設置&#xff1a;開發環境 前端快速設置 后端快速設置 2、使用方法 Metabase的案例應用 Metabase的簡介 Met…

frp v0.64.0 更新:開源內網穿透工具,最簡潔教程

frp是一款跨平臺的內網穿透工具&#xff0c;支持 Windows、macOS 與 Linux&#xff0c;它需要你有一臺擁有固定公網 IP 的電腦&#xff0c;VPS 最好&#xff0c;然后就能愉快的進行內網穿透了。還支持 https&#xff0c;甚至可以用它進行小程序開發。Appinn v0.64.0 新增token…

【數據結構】B+ 樹——高度近似于菌絲網絡——詳細解說與其 C 代碼實現

文章目錄B 樹的定義B 樹組織數據的方法往 B 樹中插入鍵值對數據從 B 樹中刪除鍵值對把 B 樹看作是 “真菌網絡”——我理解并記憶 B 樹的方法B 樹的 C 代碼實現初始化節點、B 樹B 樹節點內的二分查找B 樹的數據插入操作B 樹的刪除數據操作范圍查詢與全局遍歷銷毀 B 樹測試代碼&…

01、數據結構與算法--順序表

正式進入數據結構的學習&#xff0c;先從預備知識學起&#xff0c;戒焦戒躁戒焦戒躁...一、泛型的引入1、為什么需要泛型&#xff1f;先來看一個題目&#xff1a;實現一個類&#xff0c;類中包含一個數組成員&#xff0c;使得數組中可以存放任何類型的數據&#xff0c;也可以根…

8.23打卡 DAY 50 預訓練模型+CBAM模塊

DAY 50: 預訓練模型與 CBAM 模塊的融合與微調 今天&#xff0c;我們將把之前學到的知識融會貫通&#xff0c;探討如何將 CBAM 這樣的注意力模塊應用到強大的預訓練模型&#xff08;如 ResNet&#xff09;中&#xff0c;并學習如何高效地對這些模型進行微調&#xff0c;以適應我…

北極圈邊緣生態研究:從數據采集到分析的全流程解析

原文鏈接&#xff1a;https://onlinelibrary.wiley.com/doi/10.1111/1744-7917.70142?afR北極圈邊緣生態研究&#xff1a;從數據采集到分析的全流程解析簡介本教程基于一項在俄羅斯摩爾曼斯克州基洛夫斯克市開展的長期生態學研究&#xff0c;系統講解如何對高緯度地區特定昆蟲…

Excel處理控件Aspose.Cells教程:使用Python將 Excel 轉換為 NumPy

使用 Python 處理 Excel 數據非常常見。這通常涉及將數據從 Excel 轉換為可高效操作的形式。將 Excel 數據轉換為可分析的格式可能非常棘手。在本篇教程中&#xff0c;您將學習借助強大Excel處理控件Aspose.Cells for Python&#xff0c;如何僅用幾行代碼將 Excel 轉換為 NumPy…

python 字典有序性的實現和OrderedDict

文章目錄 一、Python 3.7+ 字典有序性的驗證 二、如何在字典頭部插入鍵值對 方法 1:創建新字典(推薦) 方法 2:使用 `collections.OrderedDict`(適合頻繁頭部插入場景) 方法 3:轉換為列表操作(不推薦,效率低) 底層核心結構:雙數組哈希表 有序性的實現原理 與舊版本(…

JVM 調優全流程案例:從頻繁 Full GC 到百萬 QPS 的實戰蛻變

&#x1f525; JVM 調優全流程案例&#xff1a;從頻繁 Full GC 到百萬 QPS 的實戰蛻變 文章目錄&#x1f525; JVM 調優全流程案例&#xff1a;從頻繁 Full GC 到百萬 QPS 的實戰蛻變&#x1f9e9; 一、調優本質&#xff1a;性能瓶頸的破局之道&#x1f4a1; 為什么JVM調優如此…

基于TimeMixer現有腳本擴展的思路分析

文章目錄1. 加入數據集到data_loader.py和data_factory.py2. 參照exp_classification.py寫自定義分類任務腳本&#xff08;如exp_ADReSS.py&#xff09;3. 接一個MLP分類頭4. 嵌入指標計算、繪圖、保存訓練歷史的函數5. 開始訓練總結**一、可行性分析****二、具體實現步驟****1…

技術演進中的開發沉思-75 Linux系列:中斷和與windows中斷的區分

作為一名從 2000 年走過來的老程序員&#xff0c;看著 IT 技術從桌面開發迭代到微服務時代&#xff0c;始終覺得好技術就像老故事 —— 得有骨架&#xff08;知識點&#xff09;&#xff0c;更得有血肉&#xff08;場景與感悟&#xff09;。我想正是我的經歷也促成了我想寫這個…

【8位數取中間4位數】2022-10-23

緣由請輸入一個8位的十進制整數&#xff0c;編寫程序取出該整數的中間4位數&#xff0c;分別輸出取出的這4位數以及該4位數加上1024的得數。 輸入&#xff1a;一個整數。 輸出&#xff1a;兩個整數&#xff0c;用空格分隔-編程語言-CSDN問答 int n 0;std::cin >> n;std:…

mac電腦使用(windows轉Mac用戶)

首先&#xff0c;我們學習mac的鍵盤復制 command c 粘貼 command v 剪切 command xlinux命令行 退出中止 control c 退出后臺 control d中英文切換大小寫&#xff0c;按住左邊向上的箭頭 字母鼠標操作 滾輪&#xff1a;2個指頭一起按到觸摸板&#xff0c;上滑&#xff0c;…

項目中優惠券計算邏輯全解析(處理高并發)

其實這個部分的代碼已經完成一陣子了&#xff0c;但是想了一下決定還是整理一下這部分的代碼&#xff0c;因為最開始做的時候業務邏輯還是感覺挺有難度的整體流程概述優惠方案計算主要在DiscountServiceImpl類的findDiscountSolution方法中實現。整個計算過程可以分為以下五個步…

支持電腦課程、游戲、會議、網課、直播錄屏 多場景全能錄屏工具

白鯊錄屏大師&#xff1a;支持電腦課程、游戲、會議、網課、直播錄屏 多場景全能錄屏工具&#xff0c;輕松捕捉每一刻精彩 在數字化學習、娛樂與辦公場景中&#xff0c;高質量的錄屏需求日益增長。無論是課程內容的留存、游戲高光的記錄&#xff0c;還是會議要點的復盤、網課知…

LeetCode算法日記 - Day 20: 兩整數之和、只出現一次的數字II

目錄 1. 兩數之和 1.1 題目解析 1.2 解法 1.3 代碼實現 2. 只出現一次的數字II 2.1 題目解析 2.2 解法 2.3 代碼實現 1. 兩數之和 371. 兩整數之和 - 力扣&#xff08;LeetCode&#xff09; 給你兩個整數 a 和 b &#xff0c;不使用 運算符 和 - &#xff0c;計算并…

Spring AI 快速接入 DeepSeek 大模型

Spring AI 快速接入 DeepSeek 大模型 文章目錄Spring AI 快速接入 DeepSeek 大模型Spring AI 框架概述核心特性適用場景官網與資源AI 提供商與模型類型模型類型&#xff08;Model Type&#xff09;AI提供商&#xff08;Provider&#xff09;兩者的關系Spring AI 框架支持哪些 A…