【C++】13.list的模擬實現

首先,我們需要把鏈表管理起來,也就是把一個個節點管理起來,但是每個節點的信息我們也需要管理,例如節點的前驅指針和后驅指針,以及節點的值,所以我們這里先封裝兩個類來管理節點和鏈表。

namespace Ro
{template<class T>struct list_node{list_node(const T& val = T()):_data(val),prev(nullptr),next(nullptr){}T _data;list_node<T>* prev;list_node<T>* next;};template<class T>class list{typedef list_node<T> Node;public:list(){_head = new Node;_head->prev = _head;_head->next = _head;_size = 0;}private:Node* _head;size_t _size;};
};

先將list的模子給寫好,同時介紹以下幾點:

list_node:

在list中我們需要經常訪問list_node中的成員變量,所以需要將list_node中的成員變量公有,干脆使用結構體struct,因為struct默認公有。

注意成員變量的類型list_node<T>需要顯示實例化,不可以直接list_node,雖然之前在函數模板章節有講過模板也可以不顯式的寫,讓編譯器自動推導類型,但是那是函數模板可以讓編譯器推導,在這里是類模板,類模板是不能自動推導類型的,所以需要顯式實例化。

另外在list_node中我們不需要顯式定義拷貝構造函數和析構函數,只需要寫構造函數

原因如下:

1.?默認行為已足夠

  • 如果節點類只包含簡單的數據成員(如基本數據類型或指針),默認的拷貝構造函數和析構函數已經能夠正確工作。
  • 拷貝構造函數:默認拷貝構造函數會逐成員拷貝?datanext?和?prev?指針。對于指針成員,這只會復制指針的值(即地址),而不會復制指針指向的對象。
  • 析構函數:默認析構函數會自動銷毀?data(如果?T?是一個非指針類型且需要銷毀),但不會自動釋放?next?和?prev?指針指向的內存(因為它們只是指針,不負責管理內存)。

2.?避免不必要的復雜性

  • 顯式定義拷貝構造函數和析構函數可能會引入復雜性,尤其是當節點類包含動態分配的資源時。
  • 如果手動定義拷貝構造函數,需要確保深拷貝邏輯正確實現,否則可能導致懸空指針或雙重釋放等問題。
  • 如果手動定義析構函數,需要確保釋放所有動態分配的資源,否則可能導致內存泄漏。
  • 通過依賴默認的拷貝構造函數和析構函數,可以避免這些潛在問題。

3.?鏈表本身的內存管理

  • 在實現一個鏈表時,通常由鏈表類本身負責管理節點的內存分配和釋放,而不是由節點類負責。
  • 例如,鏈表的插入和刪除操作會負責創建和銷毀節點,而節點類只需要存儲數據和指針。
  • 因此,節點類不需要顯式定義析構函數來釋放內存。

4.?性能考慮

  • 顯式定義拷貝構造函數和析構函數可能會引入額外的開銷,尤其是在節點類被頻繁拷貝或銷毀的情況下。
  • 依賴默認的拷貝構造函數和析構函數可以避免這種開銷,尤其是在節點類只包含簡單數據成員的情況下。

5.?指針語義的合理性

  • 在鏈表節點中,指針成員(如?next?和?prev)通常只是指向其他節點的指針,而不是擁有這些節點的所有權。
  • 因此,默認的淺拷貝行為(即復制指針值)是合理的,因為鏈表類本身會管理節點的生命周期。

6.?C++ 的規則五(Rule of Five)

  • 根據 C++ 的規則五,如果一個類需要顯式定義拷貝構造函數、拷貝賦值運算符、移動構造函數、移動賦值運算符或析構函數中的任何一個,那么通常需要顯式定義所有這些函數。
  • 如果節點類不需要顯式定義這些函數中的任何一個(因為默認行為已經足夠),那么就沒有必要顯式定義它們。

7.?現代 C++ 的智能指針

  • 如果需要更復雜的內存管理,可以在鏈表實現中使用智能指針(如?std::unique_ptr?或?std::shared_ptr)來管理節點的生命周期。
  • 例如,可以使用?std::unique_ptr<list_node<T>>?來管理節點的內存,從而避免手動管理內存的復雜性。

list:

我們這里實現的雙向帶頭循環鏈表,所以我們需要一個指向哨兵位頭節點的指針,方便我們管理鏈表,另外為方便得到list的size(),省去遍歷一遍鏈表的繁瑣,我們定義兩個成員變量_head和_size。同時在構造函數中,我們初始化頭節點,并把list_node<T>typedef一下為Node。

接下來我們正式來模擬實現list

1. push_back()

還是老規矩,我們先來實現尾插,讓list能夠跑起來

void push_back(const T& data)
{Node* newnode = new Node(data);Node* ptail = _head->prev;ptail->next = newnode;newnode->next = _head;newnode->prev = ptail;_head->prev = newnode;_size++;
}

測試一下:

void test_list1()
{list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);
}


2.迭代器實現

在string和vector中,迭代器是原生指針typedef來的,但是在list中這樣是不行的,因為string和vector是連續的空間,++就能得到下一個數據的地址,對迭代器解引用就能得到數據,但是list空間是不連續的,直接++是不行的,直接解引也是不行的,因為此時指針指向的是一個結構體,對結構體解引用得到的是該結構體的左值引用,但是我們想做到解引用直接拿到結構體中存的數據。

那要怎么做呢?在之前我們有提過,迭代器是一個像原生指針一樣的東西,我們要讓迭代器能夠模擬指針一樣的操作,所以這里我們可以將迭代器封裝成一個類,通過operator++operator*等運算符重載來模擬指針的行為。

template<class T>
struct list_iterator
{typedef list_node<T> Node;typedef list_iterator<T> Self;Node* _node;list_iterator(Node* node):_node(node){ }};

我們需要獲取list_node中的值和前驅指針以及后驅指針,所以我們可以讓指向list_node的指針_node作為成員變量,同時迭代器++的返回值同樣是迭代器,所以我們也對迭代器類型也typedef一下


2.1 operator*()

T& operator*()
{return _node->_data;
}

直接返回節點的數據


2.2 operator++()

operator++分為前置++和后置++,在前面的章節中我們有講過,前置++和后置++,可以通過占位符來區分。

前置++:

Self& operator++()
{_node = _node->next;return *this;
}

后置++:

Self operator++(int)
{Self tmp(*this);//先儲存原來的值_node = _node->next;//再++return tmp;//返回原來的值
}

后置++需要返回++前的指針,所以我們先拷貝構造儲存一下++前的指針,再++,然后返回tmp


2.3 operator--()

有++就有--,和++一樣也分為前置--和后置--

前置--:

//前置--
Self& operator--()
{_node = _node->prev;return *this;
}

后置--:

//后置--
Self operator--(int)
{Self tmp(*this);_node = _node->prev;return tmp;
}

2.4 operator!=()和operator==()

迭代器是有比較的,例如我們在使用迭代器遍歷容器的時候經常會這樣用:it != lt.end()

所以還需要比較運算符

bool operator!=(const Self& s) const
{return _node != s._node;
}bool operator==(const Self& s) const
{return _node == s._node;
}

2.5 operator->()

我們知道對結構體的解引用還有箭頭運算符,如果我們list_node中的數據_data是自定義類型,例如是一個日期類Date的話,雖然我們可以 { (*it)._year,(*it)._month 和 (*it)._day }這樣的操作來訪問,但是對于 .? 這種對結構體成員訪問,使用?-> 訪問結構體成員還是更為普遍的

T* operator->()
{return &_node->_data;
}

直接取結構體的地址返回就行,因為結構體指針能夠使用->運算符訪問結構體成員


2.6 begin()和end()

typedef list_iterator<T> iterator;
iterator begin()
{//return iterator(_head->next);return _head->next;
}iterator end()
{//return iterator(_head);return _head;
}

這里我們可以返回一個iterator的臨時對象,不過這里也可以直接返回_head->next,讓它自己走隱式類型轉換,begin()指向第一個數據,end()指向最后一個數據的下一個位置,最后一個數據的下一個位置就是頭節點


2.7 測試

迭代器的基本功能已經實現的差不多了,接下來我們來測試一下

void test_list1()
{list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);list<int>::iterator it = lt.begin();while (it != lt.end()){cout << *it << ' ';it++;}cout << endl;//范圍forfor (auto e : lt){cout << e << ' ';}cout << endl;
}

再來測試一下重載的箭頭運算符

void test_list2()
{struct AA{int a1 = 1;int a2 = 2;};list<AA> lt;lt.push_back(AA());lt.push_back(AA());lt.push_back(AA());lt.push_back(AA());list<AA>::iterator it = lt.begin();while (it != lt.end()){//特殊處理,本來應該是兩個->,但是為了可讀性省略了一個->cout << it.operator->()->a1 << ':' << it.operator->()->a2 << endl;cout << it->a1 << ':' << it->a2 << endl;it++;}}

需要注意的是這里本來應該是兩個->才合理的,第一個operator->()返回的是AA*的指針,第二個->是訪問結構體AA的成員,但是為了可讀性,省略了一個->


2.8 const迭代器

上面實現的迭代器是普通迭代器可讀可寫,但是還有const迭代器,可讀不可寫。

那const迭代器要怎么實現呢?其實只需要復用一下迭代器的代碼,然后在修改一下細節就行

const迭代器:

template<class T>
struct const_list_iterator
{typedef list_node<T> Node;typedef const_list_iterator<T> Self;Node* _node;const_list_iterator(Node* node):_node(node){ }//前置++Self& operator++(){_node = _node->next;return *this;}//后置++Self operator++(int){Self tmp(*this);//先儲存原來的值_node = _node->next;//再++return tmp;//返回原來的值}//前置--Self& operator--(){_node = _node->prev;return *this;}//后置--Self operator--(int){Self tmp(*this);_node = _node->prev;return tmp;}bool operator!=(const Self& s) const{return _node != s._node;}bool operator==(const Self& s) const{return _node == s._node;}const T* operator->(){return &_node->_data;}const T& operator*(){return _node->_data;}
};

這里我們只需要將返回值修改,返回const迭代器,const T&和const T*。這樣就可以限制寫的功能

在list中,我們同樣typedef一下const迭代器

template<class T>
class list
{typedef list_node<T> Node;
public:typedef list_iterator<T> iterator;typedef const_list_iterator<T> const_iterator;list(){_head = new Node;_head->prev = _head;_head->next = _head;_size = 0;}iterator begin(){//return iterator(_head->next);return _head->next;}iterator end(){//return iterator(_head);return _head;}const_iterator begin() const{return _head->next;}const_iterator end() const{return _head;}void push_back(const T& data){Node* newnode = new Node(data);Node* ptail = _head->prev;ptail->next = newnode;newnode->next = _head;newnode->prev = ptail;_head->prev = newnode;_size++;}
private:Node* _head;size_t _size;
};

注意:const迭代器是const迭代器修飾的對象是const,可讀不可寫,不是迭代器不可寫,同樣const迭代器的begin()和end()需要用const修飾,與const迭代器兼顧,同時防止意外修改容器中的元素

I 測試一下

這里我們發現報錯了,為什么呢?

因為這里我們定義的lt并非const對象,所以 lt.begin() 和 lt.end() 調用的是普通迭代器,但是我們把普通迭代器賦值給const迭代器的對象 it ,造成權限的放大(it的權限放大為普通迭代器),在之前的章節中,我們有講過權限可以縮小,但是不能放大,所以這里會報錯,同時這里 const迭代器對象 it 和普通迭代器 lt.end() 是不能比較的,因為兩個不同類型的對象不能比較,所以這里會報錯沒有匹配的 != 運算符

那怎么解決呢?由于我們目前實現的list只是一些簡單的功能,像拷貝構造等還沒有實現,不然可以拷貝構造一個const對象?

像這樣的話就不會出問題,但是我們還沒實現拷貝構造,那我們干脆實現一個print的函數專門打印,后面測試的時候也可以直接調用

template<class Container>
void print(const Container& con)
{typename Container::const_iterator it = con.begin();while (it != con.end()){//*it += 10;cout << *it << ' ';it++;}cout << endl;//范圍forfor (auto e : con){cout << e << ' ';}cout << endl;
}

在形參處使用const,所以這里con是const對象,我們要使用const迭代器

這里我們自定義了一個Container容器類,這樣我們傳不同的容器都能打印,不止于list

注意這里需要用typename來聲明一下Container::const_iterator是一個類,不然編譯器會分不清這是類還是靜態成員變量

如果我們對const迭代器的對象修改(*it += 10),編譯器會報錯

只讀不寫的話const迭代器就不會報錯

II 巧用模板實現迭代器

雖然我們直接cv一份代碼修改一下細節就能實現const迭代器,但是這樣寫代碼太冗余了,而且普通迭代器和const迭代器只是在返回值類型上不同,其余都一樣,那有沒有什么更好的方法實現呢?我們來看看STL3.0中是怎么做的

我們可以看到在STL3.0中使用了3個類模板參數,這樣就可以讓編譯器給我們實例化出兩個不同的類,這樣寫確實很妙,那我們也這樣模擬豈不美哉

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++(int){Self tmp(*this);//先儲存原來的值_node = _node->next;//再++return tmp;//返回原來的值}//前置--Self& operator--(){_node = _node->prev;return *this;}//后置--Self operator--(int){Self tmp(*this);_node = _node->prev;return tmp;}bool operator!=(const Self& s){return _node != s._node;}bool operator==(const Self& s){return _node == s._node;}Ptr operator->(){return &_node->_data;}Ref operator*(){return _node->_data;}
};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;
}

這樣我們傳什么樣的類模板,就能實例化出不同的迭代器


3.list的增刪查改

3.1 insert()

iterator insert(iterator pos, const T& data)
{Node* newnode = new Node(data);Node* cur = pos._node;//pos位置的節點Node* pre = cur->prev;//在pos前插入,先找到pos前的節點//pre newnode curpre->next = newnode;newnode->next = cur;newnode->prev = pre;cur->prev = newnode;_size++;return newnode;
}

在pos位置前插入,我們要先找到pos位置和pos位置前的節點,然后再將新節點插入其中

測試一下:

void test_list4()
{list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);list<int>::iterator it = lt.begin();int k = 3;while (k--){it++;}lt.insert(it, 20);print(lt);
}

insert實現好了,我們可以把之前的push_back()直接復用insert()的代碼

push_back:

在頭節點前插入

void push_back(const T& data)
{/*Node* newnode = new Node(data);Node* ptail = _head->prev;ptail->next = newnode;newnode->next = _head;newnode->prev = ptail;_head->prev = newnode;_size++;*/insert(end(), data);
}

push_front:

在begin()前插入

void push_front(const T& data)
{insert(begin(), data);
}

3.2 erase()

刪除pos位置的數據

iterator erase(iterator pos)
{assert(pos != end());Node* nxt = pos._node->next;Node* pre = pos._node->prev;pre->next = nxt;nxt->prev = pre;delete pos._node;_size--;return nxt;
}

注意:不能把哨兵位頭節點刪除了,所以我們這里加一個斷言,如果刪除的是頭節點就斷言報錯

刪除pos位置的數據后,pos位置的迭代器就會失效,所以我們這里返回pos位置的下一個位置的迭代器,這里我們返回nxt節點讓它自己走隱式類型轉換

測試一下:

刪除所有偶數

void test_list5()
{list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(5);list<int>::iterator it = lt.begin();while (it != lt.end()){if (*it % 2 == 0) it = lt.erase(it);else it++;}print(lt);
}

pop_back:

直接復用erase(),刪除頭節點前的數據

void pop_back()
{erase(_head->prev);
}

pop_front:

刪除頭節點后的數據

void pop_front()
{erase(_head->next);
}

3.3 size()和empty()

size_t size() const
{return _size;
}bool empty() const
{return _size == 0;
}

3.4 front() 和 back()

T& front()
{return *begin();
}T& back()
{return *(--end());
}const T& front() const
{return *begin();
}const T& back() const
{return *(--end());
}

比較簡單就不多介紹了


3.5 clear()

void clear()
{auto it = begin();while (it != end()){it = erase(it);}
}

清除所有節點,但不能清除頭節點,所有我們可以直接復用erase


4.list的拷貝構造和賦值重載

4.1 析構函數

~list()
{clear();delete _head;_head = nullptr;
}

這里直接復用clear(),將所有節點清除,最后釋放頭節點。


4.2拷貝構造

和string,vector一樣,list也有涉及深淺拷貝的問題,如果不寫自己的深拷貝的話,走編譯器自己默認的淺拷貝,那么兩個對象指向的就是同一份鏈表,會導致析構兩次。

這里我們可以試一下淺拷貝:

void test_list7()
{list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(5);list<int> lt2(lt);print(lt);print(lt2);
}

直接運行崩潰了,所以還是要動手完成自己的深拷貝

但是拷貝構造也得要有自己的頭節點,所以我們要先空初始化,創建一個頭節點,那我們干脆直接將構造函數中的初始化頭節點封裝為一個空初始化的函數,在拷貝構造之前先調用空初始化創造自己的頭節點

void empty_init()
{_head = new Node;_head->prev = _head;_head->next = _head;_size = 0;
}

不過我們要將空初始化給私有,因為我們不希望外面可以調用這個接口

//構造函數
list()
{empty_init();
}//拷貝構造
list(const list<T>& l)
{empty_init();for (auto e : l){push_back(e);}
}

空初始化之后,在遍歷鏈表,尾插重新創建一個自己的鏈表。


4.3賦值重載

list<T>& operator=(list<T> tmp)
{swap(_head, tmp._head);return *this;
}

直接使用現代寫法


4.4其余構造函數

可以看到這里還有兩種構造函數,一個是構造n個值為value的鏈表,另一個是迭代器區間構造

我們都來實現一下

list(int n, const T& value = T())
{empty_init();for (int i = 0; i < n; i++) push_back(value);
}template<class InputIterator>
list(InputIterator first, InputIterator last)
{empty_init();while (first != last){push_back(*first);first++;}
}

迭代器區間構造在vector模擬實現時有講過,這里也不多介紹了

代碼如下

list.h:

#pragma once
#include <iostream>
#include <assert.h>using namespace std;namespace Ro
{template<class T>struct list_node{list_node(const T& val = T()):_data(val),prev(nullptr),next(nullptr){}T _data;list_node<T>* prev;list_node<T>* next;};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++(int){Self tmp(*this);//先儲存原來的值_node = _node->next;//再++return tmp;//返回原來的值}//前置--Self& operator--(){_node = _node->prev;return *this;}//后置--Self operator--(int){Self tmp(*this);_node = _node->prev;return tmp;}bool operator!=(const Self& s){return _node != s._node;}bool operator==(const Self& s){return _node == s._node;}Ptr operator->(){return &_node->_data;}Ref operator*(){return _node->_data;}};//template<class T>//struct const_list_iterator//{//	typedef list_node<T> Node;//	typedef const_list_iterator<T> Self;//	Node* _node;//	const_list_iterator(Node* node)//		:_node(node)//	{ }//	//前置++//	Self& operator++()//	{//		_node = _node->next;//		return *this;//	}//	//后置++//	Self operator++(int)//	{//		Self tmp(*this);//先儲存原來的值//		_node = _node->next;//再++//		return tmp;//返回原來的值//	}//	//前置--//	Self& operator--()//	{//		_node = _node->prev;//		return *this;//	}//	//后置--//	Self operator--(int)//	{//		Self tmp(*this);//		_node = _node->prev;//		return tmp;//	}//	bool operator!=(const Self& s) const//	{//		return _node != s._node;//	}//	bool operator==(const Self& s) const//	{//		return _node == s._node;//	}//	const T* operator->()//	{//		return &_node->_data;//	}//	const T& operator*()//	{//		return _node->_data;//	}//};template<class T>class list{typedef list_node<T> Node;void empty_init(){_head = new Node;_head->prev = _head;_head->next = _head;_size = 0;}public:typedef list_iterator<T, T&, T*> iterator;typedef list_iterator<T, const T&, const T*> const_iterator;//構造函數list(){empty_init();}list(int n, const T& value = T()){empty_init();for (int i = 0; i < n; i++) push_back(value);}//迭代器區間構造template<class InputIterator>list(InputIterator first, InputIterator last){empty_init();while (first != last){push_back(*first);first++;}}//拷貝構造list(const list<T>& l){empty_init();for (auto e : l){push_back(e);}}//析構~list(){clear();delete _head;_head = nullptr;}//賦值重載-現代寫法list<T>& operator=(list<T> tmp){swap(_head, tmp._head);return *this;}iterator begin(){//return iterator(_head->next);return _head->next;}iterator end(){//return iterator(_head);return _head;}const_iterator begin() const{return _head->next;}const_iterator end() const{return _head;}void clear(){auto it = begin();while (it != end()){it = erase(it);}}T& front(){return *begin();}T& back(){return *(--end());}const T& front() const{return *begin();}const T& back() const{return *(--end());}size_t size() const{return _size;}bool empty() const{return _size == 0;}void pop_back(){erase(_head->prev);}void pop_front(){erase(_head->next);}iterator erase(iterator pos){assert(pos != end());Node* nxt = pos._node->next;Node* pre = pos._node->prev;pre->next = nxt;nxt->prev = pre;delete pos._node;_size--;return nxt;}iterator insert(iterator pos, const T& data){Node* newnode = new Node(data);Node* cur = pos._node;//pos位置的節點Node* pre = cur->prev;//在pos前插入,先找到pos前的節點//pre newnode curpre->next = newnode;newnode->next = cur;newnode->prev = pre;cur->prev = newnode;_size++;return newnode;}void push_front(const T& data){insert(begin(), data);}void push_back(const T& data){/*Node* newnode = new Node(data);Node* ptail = _head->prev;ptail->next = newnode;newnode->next = _head;newnode->prev = ptail;_head->prev = newnode;_size++;*/insert(end(), data);}private:Node* _head;size_t _size;};template<class Container>void print(const Container& con){typename Container::const_iterator it = con.begin();while (it != con.end()){//*it += 10;cout << *it << ' ';it++;}cout << endl;//范圍forfor (auto e : con){cout << e << ' ';}cout << endl;}void test_list1(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);list<int>::iterator it = lt.begin();while (it != lt.end()){cout << *it << ' ';it++;}cout << endl;//范圍forfor (auto e : lt){cout << e << ' ';}cout << endl;}void test_list2(){struct AA{int a1 = 1;int a2 = 2;};list<AA> lt;lt.push_back(AA());lt.push_back(AA());lt.push_back(AA());lt.push_back(AA());list<AA>::iterator it = lt.begin();while (it != lt.end()){//特殊處理,本來應該是兩個->,但是為了可讀性省略了一個->cout << it.operator->()->a1 << ':' << it.operator->()->a2 << endl;cout << it->a1 << ':' << it->a2 << endl;it++;}}void test_list3(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);print(lt);}void test_list4(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);list<int>::iterator it = lt.begin();int k = 3;while (k--){it++;}lt.insert(it, 20);print(lt);}void test_list5(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(5);list<int>::iterator it = lt.begin();while (it != lt.end()){if (*it % 2 == 0) it = lt.erase(it);else it++;}print(lt);}void test_list6(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(5);cout << lt.front() << endl;cout << lt.back() << endl;//print(lt);}void test_list7(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(5);list<int> lt2(2);//lt2 = lt;print(lt);print(lt2);}
};

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

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

相關文章

TinyVue v3.22.0 正式發布:深色模式上線!集成 UnoCSS 圖標庫!TypeScript 類型支持全面升級!

我們非常高興地宣布&#xff0c;2025年4月7日&#xff0c;TinyVue發布了v3.22.0&#x1f389;。 本次 3.22.0 版本主要有以下重大變更&#xff1a; 支持深色模式增加基于 UnoCSS 的圖標庫更豐富的 TypeScript 類型聲明支持 XSS 配置 詳細的 Release Notes 請參考&#xff1a…

超級創新思路:基于CBAM-Transformer的強化學習時間序列預測模型(Python\matlab實現)

首先聲明,該模型為原創!原創!原創!且該思路還未有成果發表,感興趣的小伙伴可以借鑒!需要完整代碼可私信或評論! 本方案可用于醫療、金融、交通、零售、光伏功率預測、估計預測、天氣預測、流量預測、故障檢測等領域! 目錄 首先聲明,該模型為原創!原創!原創!且該思…

Apache Sqoop數據采集問題

Sqoop數據采集格式問題 一、Sqoop工作原理二、Sqoop命令格式三、Oracle數據采集格式問題四、Sqoop增量采集方案 Apache Sqoop是一款開源的工具&#xff0c;主要用于在Hadoop(Hive)與傳統的數據庫(mysql、postgresql…)間進行數據的傳遞&#xff0c;可以將一個關系型數據庫&…

Grok發布了Grok Studio 和 Workspaces兩個強大的功能。該如何使用?如何使用Grok3 API?

最近Grok又更新了幾個功能&#xff1a;Grok Studio 和 Workspaces。 其中 Grok Studio 主要功能包括&#xff1a; 代碼執行&#xff1a;在預覽標簽中運行 HTML 片段、Python、JavaScript 等。 Google Drive 集成&#xff1a;附加并處理 Docs、Sheets、Slides等文件。 協作工…

Vue選項式 API 與組合式 API

選項式 API 與組合式 API 選項式 API 選項式 API 是 Vue 2 中常用的開發方式&#xff0c;在 Vue 3 里依舊得到支持。它把組件邏輯劃分為不同的選項&#xff0c;像 data、methods、computed 等。 <template><div><p>Count: {{ count }}</p><button…

SiamMask中的分類分支、回歸分支與Mask分支,有何本質差異?

SiamMask中的分類分支、回歸分支與Mask分支&#xff0c;有何本質差異&#xff1f; 一、引言二、分支定位與任務目標三、網絡結構與感受野設計3.1 分類分支&#xff08;Classification Head&#xff09;3.2 回歸分支&#xff08;Regression Head&#xff09;3.3 Mask分支&#x…

threejs學習day02

場景、相機、渲染器 一、創建3D場景 // 引入threejs import * as THREE from three// 創建一個三維場景scene const scene new THREE.Scene();// 給三維場景添加物品 const geometry new THREE.BoxGeometry(100,100,100) // 形狀 const meterial new THREE.MeshBasicMat…

K8S Pod 常見數據存儲方案

假設有如下三個節點的 K8S 集群&#xff1a; k8s31master 是控制節點 k8s31node1、k8s31node2 是工作節點 容器運行時是 containerd 一、理論介紹 1.1、Volumes 卷 Kubernetes 的卷是 pod 的?個組成部分&#xff0c;因此像容器?樣在 pod 的規范&#xff08;pod.spec&#x…

【MySQL數據庫】函數操作

目錄 1&#xff0c;日期函數 2&#xff0c;字符串函數 3&#xff0c;數學函數 1&#xff0c;日期函數 樣例&#xff1a; 獲得年月日 select current_date(); 獲取時分秒 select current_time(); 獲得時間戳 select current_timestamp(); 在日期的基礎上加日期 在2025年4月27…

【每日隨筆】文化屬性 ① ( 天機 | 強勢文化與弱勢文化 | 文化屬性的形成與改變 | 強勢文化 具備的特點 )

文章目錄 一、文化屬性1、天機2、文化屬性的強勢文化與弱勢文化強勢文化弱勢文化 二、文化屬性的形成與改變1、文化屬性形成2、文化屬性改變3、文化知識的階層 三、強勢文化 具備的 特點 一、文化屬性 1、天機 如果想要 了解這個世界的 底層架構 , 就需要掌握 洞察事物本質 的能…

【Fifty Project - D18】

感覺自己就不是計劃星球人&#xff0c;雖然fifty project要求每天早上完成一天的計劃&#xff0c;但是對于一個p人腦子&#xff0c;強制自己按照計劃行事真的太難了。我也理解在早晨花費時間做好一天的計劃有很多好處&#xff0c;但是實際行動起來完成率極低。p人的世界里變動太…

Linux系統編程 day11 鎖 (兩天沒有更新了,中期完就休息了)

鎖的注意事項 1、盡量保證鎖的粒度&#xff0c;越小越好。(訪問共享數據前&#xff0c;加鎖&#xff0c;訪問結束后立即解鎖) 2、互斥鎖&#xff0c;本質是結構體&#xff0c;但是可以看成整數&#xff0c;初值為1。(pthread_mutex_init調用成功) 3、加鎖&#xff1a; --操作…

【Maven】特殊pom.xml配置文件 - BOM

文章目錄 特殊pom.xml配置文件 - BOM一、例子二、注意事項1.特殊的子pom.xml文件2.dependencyManagement 特殊pom.xml配置文件 - BOM 僅用于集中管理項目依賴版本 在 Maven 中&#xff0c;BOM 用于定義一個項目的依賴版本的集合&#xff0c;通常用于管理一組共享的依賴版本。這…

《代碼整潔之道》第5章 格式 - 筆記

你應該選擇一套管理代碼格式的簡單規則。如果是團隊&#xff0c;應該選擇一套團隊一致同意采用的簡單格式規則。 最重要的原則&#xff1a;一致性&#xff08;Consistency&#xff09;&#xff01; 沒有完美的格式規范&#xff0c;但有統一的規范。 整個團隊&#xff08;或者…

C++ 類與對象(中)—— 默認成員函數與運算符重載的深度解析:構造函數,析構函數,拷貝構造函數,賦值運算符重載,普通取地址重載,const取地址重載

在 C 中&#xff0c;類的默認成員函數是編譯器自動生成的重要機制&#xff0c;合理利用這些函數可以簡化代碼編寫&#xff0c;同時避免資源管理錯誤。本文將從構造函數、析構函數、拷貝構造函數、賦值運算符重載等核心內容展開&#xff0c;結合具體案例深入解析。 一、默認成員…

【KWDB創作者計劃】_企業級多模數據庫實戰:用KWDB實現時序+關系數據毫秒級融合(附代碼、性能優化與架構圖)

一、技術背景與行業痛點 1.1 多模數據融合挑戰 場景痛點&#xff1a; 工業物聯網設備每秒產生百萬級傳感器數據&#xff08;時序數據&#xff09;。需關聯設備檔案&#xff08;關系數據&#xff09;生成設備健康報告&#xff0c;傳統方案需多數據庫跳轉&#xff0c;延遲>5…

w~嵌入式C語言~合集4

我自己的原文哦~ https://blog.51cto.com/whaosoft/13870376 一、STM32怎么選型 什么是 STM32 STM32&#xff0c;從字面上來理解&#xff0c;ST是意法半導體&#xff0c;M是Microelectronics的縮寫&#xff0c;32表示32位&#xff0c;合起來理解&#xff0c;STM32就是指S…

Multisim使用教程詳盡版--(2025最新版)

一、Multisim14前言 1.1、主流電路仿真軟件 1. Multisim&#xff1a;NI開發的SPICE標準仿真工具&#xff0c;支持模擬/數字電路混合仿真&#xff0c;內置豐富的元件庫和虛擬儀器&#xff08;示波器、頻譜儀等&#xff09;&#xff0c;適合教學和競賽設計。官網&#xff1a;艾…

分布式理論和事務

微服務和分布式 微服務 是一種軟件架構風格&#xff0c;它將應用程序拆分成一系列小型、獨立的服務&#xff0c;每個服務專注于單一功能&#xff0c;彼此通過輕量級通信機制&#xff08;如 API&#xff09;進行交互。微服務通常是松耦合的&#xff0c;可以獨立開發、部署和擴展…

JAVA:紅黑樹應用的技術指南

&#x1f333; 1、簡述 紅黑樹是一種自平衡二叉查找樹&#xff08;Self-Balancing Binary Search Tree&#xff09;&#xff0c;被廣泛應用于操作系統調度、Java集合、數據庫索引等核心模塊中。本文將從 基本原理 入手&#xff0c;結合 實際應用場景與代碼實例&#xff0c;帶你…