[STL] リストとシミュレーション実装の共通使用法 (完全なソースコードを添付)

序文

今回は引き続きSTLにおけるコンテナについて学び、リストについて解説していきます。

1. リストの導入と利用

1.1 リストの紹介

文書のリスト
ここに画像の説明を挿入します
list の基礎となる実装は、データ構造から学習した先頭の双方向循環リンク リストです。
ここに画像の説明を挿入します

1.2 リストの使用法

一般的に使用されるインターフェイスをいくつか見てみましょう。

まず、コンストラクターを見てみましょう。
ここに画像の説明を挿入します
ここには、デフォルト コンストラクター、n val コンストラクター、反復子間隔コンストラクター、コピー コンストラクターなど、私たちがよく知っているコンストラクターがあります。
もう一度イテレータを見てみましょう。
ここに画像の説明を挿入します
イテレータについては前の記事で詳しく紹介していると思うので、ここでは詳しく説明しません。

変更操作を見てみましょう。
ここに画像の説明を挿入します

ここでのリスト、文字列、ベクトルの違いは、
リストにはオーバーロード [] がないことです。つまり、リストを走査してアクセスしたい場合はイテレータのみを使用できます。イテレータは普遍的なメソッドであり、すべてのコンテナでイテレータを使用できますが、イテレータは[]特定のコンテナにとって特別なメソッドにすぎません。

トラバース

int main()
{
    
    
    list<int> l;
    l.push_back(1);
    l.push_back(2);
    l.push_back(3);
    l.push_back(3);
    l.push_back(5);
    for (auto it = l.begin(); it != l.end(); ++it)
        cout << *it << " ";
    cout << endl;

    for (auto e : l)
        cout << e << " ";
    cout << endl;

    for (auto rit = l.rbegin(); rit != l.rend(); ++rit)
        cout << *rit << " ";
    cout << endl;
    return 0;
}

ここに画像の説明を挿入します

これまであまり使用したことのないインターフェイスをいくつか見てみましょう。
ここに画像の説明を挿入します
ここではsplice、リンク リストの一部を別のリンク リストに転送できます。
ここに画像の説明を挿入します
removeつまり、指定された要素を削除できます。2
ここに画像の説明を挿入します
mergeつの順序付けされたリンク リストをマージできます。
ここに画像の説明を挿入します

2. リストシミュレーションの実装

2.1 イテレータ関数の分類

1. 一方向反復子: のみ可能++、不可能- -例: 単一リンク リスト、ハッシュ テーブル;
2. 双方向反復子:++両方を使用できます--たとえば、二重リンク リスト;
3. ランダム アクセス反復子: はい++ - -+合計することもできます-たとえば、ベクトルと文字列です。
イテレータは組み込み型です (内部クラスまたはクラス内で定義されています)。

2.2 リスト反復子のシミュレーション実装
2.2.1 通常の反復子

文字列とベクトルの実装をシミュレートする場合、ネイティブ ポインターが使用され、反復子はクラスでカプセル化されませんが、STL 標準ライブラリでは反復子もクラスでカプセル化されます。リスト反復子の実装をシミュレートする場合、リストのノード アドレスが不連続であるため、ネイティブ ポインターは使用できません。

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

		Ref operator*()
		{
    
    
			return _node->_val;
		}

		Ptr operator->()
		{
    
    
			return &_node->_val;
		}

		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& it) const
		{
    
    
			return _node != it._node;
		}

		bool operator==(const self& it) const
		{
    
    
			return _node == it._node;
		}
	};

2.2.2 定数反復子

まずは間違った書き方を見てみましょう。
在这里插入代码片typedef __list_iterator<T> iterator; const list<T>::iterator it=lt.begin();

STL ライブラリでは、クラス テンプレートにパラメータを 1 つ追加することでこれを実現し、同じクラス テンプレートで 2 つの異なるタイプのイテレータを生成できます。

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;


		iterator begin()
		{
    
    
			//return _head->_next;
			return iterator(_head->_next);
		}

		iterator end()
		{
    
    
			return _head;
			//return iterator(_head);
		}

		const_iterator begin() const
		{
    
    
			//return _head->_next;
			return const_iterator(_head->_next);
		}

		const_iterator end() const
		{
    
    
			return _head;
			//return const_iterator(_head);
		}

3. リストとベクトルの違い

ベクター リスト
基礎構造 動的シーケンスリスト、連続スペース ヘッドノードを備えた双方向循環リンクリスト
ランダムアクセス ランダムアクセスをサポートしており、特定の要素へのアクセス効率はO(1)です。 ランダム アクセスはサポートされておらず、要素へのアクセス効率は O(N) です。
挿入と削除 任意の位置での挿入と削除は非効率であり、要素の移動が必要です。時間計算量は O(N) です。挿入中に容量の拡張が必要になる場合があります。容量の拡張: 新しい領域を開き、要素をコピーし、古い領域を解放するため、効率が低下します。 。 任意の位置での挿入と削除は非常に効率的で、要素を移動する必要がなく、時間計算量は O(1) です。
スペース利用 最下層は連続空間であり、メモリの断片化を引き起こしにくく、空間使用率とキャッシュ使用率が高くなります。 基盤となるノードは動的に開かれますが、小さいノードではメモリの断片化、スペース使用率、キャッシュ使用率の低下が起こりやすくなります。
イテレータ オリジナルのエコロジー指針 オリジナルのエコロジーポインタをカプセル化する
イテレータが無効です 要素を挿入するときは、すべての反復子を再割り当てする必要があります。要素を挿入すると再拡張が発生し、元の反復子が無効になる可能性があるためです。削除するときは、現在の反復子を再割り当てする必要があります。そうしないと無効になります。 ヘッドノードを備えた双方向循環リンクリスト
使用するシーン 効率的なストレージが必要、ランダム アクセスをサポート、挿入と削除の効率は気にしない 大量の挿入および削除操作、ランダム アクセスは気にしない

4. ソースコード

list.h

#include<iostream>
using namespace std;
namespace w
{
    
    
	template<class T>
	struct list_node
	{
    
    
		list_node<T>* _next;
		list_node<T>* _prev;
		T _val;

		list_node(const T& val = T())
			:_next(nullptr)
			, _prev(nullptr)
			, _val(val)
		{
    
    }
	};

	// typedef __list_iterator<T, T&, T*> iterator;
	// typedef __list_iterator<T, const T&, const T*> const_iterator;
	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)
		{
    
    }

		Ref operator*()
		{
    
    
			return _node->_val;
		}

		Ptr operator->()
		{
    
    
			return &_node->_val;
		}

		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& it) const
		{
    
    
			return _node != it._node;
		}

		bool operator==(const self& it) const
		{
    
    
			return _node == it._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;


		iterator begin()
		{
    
    
			//return _head->_next;
			return iterator(_head->_next);
		}

		iterator end()
		{
    
    
			return _head;
			//return iterator(_head);
		}

		const_iterator begin() const
		{
    
    
			//return _head->_next;
			return const_iterator(_head->_next);
		}

		const_iterator end() const
		{
    
    
			return _head;
			//return const_iterator(_head);
		}

		void empty_init()
		{
    
    
			_head = new Node;
			_head->_prev = _head;
			_head->_next = _head;

			_size = 0;
		}

		list()
		{
    
    
			empty_init();
		}

		// lt2(lt1)
		list(const list<T>& lt)
		//list(const list& 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<T>& operator=(list<T> lt)
		//list& operator=(list lt)
		{
    
    
			swap(lt);

			return *this;
		}

		~list()
		{
    
    
			clear();

			delete _head;
			_head = nullptr;
		}

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

			_size = 0;
		}

		void push_back(const T& x)
		{
    
    
			insert(end(), x);
		}

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

		void pop_back()
		{
    
    
			erase(--end());
		}

		void pop_front()
		{
    
    
			erase(begin());
		}

		// pos位置之前插入
		iterator insert(iterator pos, const T& x)
		{
    
    
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* newnode = new Node(x);

			prev->_next = newnode;
			newnode->_next = cur;

			cur->_prev = newnode;
			newnode->_prev = prev;

			++_size;

			return newnode;
		}

		iterator erase(iterator pos)
		{
    
    
			assert(pos != end());

			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* next = cur->_next;

			prev->_next = next;
			next->_prev = prev;

			delete cur;

			--_size;

			return next;
		}

		size_t size()
		{
    
    
			return _size;
		}

	private:
		Node* _head;
		size_t _size;
	};

	void Print(const list<int>& lt)
	{
    
    
		list<int>::const_iterator it = lt.begin();
		while (it != lt.end())
		{
    
    
		
			cout << *it << " ";
			++it;
		}
		cout << endl;
	}

	
}

おすすめ

転載: blog.csdn.net/weixin_69423932/article/details/133484490