C++ container - simulation implementation of list

Table of contents

1. Basic structure of list

2. Next is the design of the list class constructor:

3. Addition of linked list data:

4. The next step is to create the iterator:

4. Implementation of simple functions:

5. Construction and destruction 

6. Copy construction and assignment overloading

Traditional writing:

Modern writing:

7. Iterator template type


     1. Basic structure of list

         If you want to simulate and implement the list class, you need to first understand its underlying architecture. As mentioned in the previous blog: the bottom layer of the list container is a doubly linked list, so you need to customize a node class. Through the node class, you can create nodes and set the before and after of the nodes. pointers and data values. Then you can create member variables of the list class through this class type.

template<class T>
struct list_node {	//该类为内部类,是list的内部类

	list_node(const T& val)
		:_next(nullptr)
		, _prev(nullptr)
		, _data(val) {
	}
	//成员变量
	list_node* _next;    //后指针
	list_node* _prev;    //前指针
	T _data;            //值
};

template<class T>
class list {
    public:
	    typedef list_node<T>  node;    //将节点类作为类类型

    private:
	    node* _head;	//指向堆区空间链表的指针
	    size_t _size;    //计数
};

The node* type is like an encapsulation of the node class. 

2. Next is the design of the list class constructor:

template<class T>
class list {
    public:
	    typedef list_node<T>  node;    //将节点类作为类类型


    //初始化操作
    void empty_Init() {
		_head = new node(T());
		_head->_next = _head;
		_head->_prev = _head;
		_size = 0;
	}

	list()    //构造函数
	:_head(nullptr)
    ,_size(0){
		empty_Init();
	}

  private:
	    node* _head;	//指向堆区空间链表的指针
	    size_t _size;    //计数
};

        The initialization design of the constructor is to create a sentinel head node, let the linked list pointer point to the sentinel head node, and let the sentinel head node control the addition, deletion, checking and modification of nodes, avoiding the need for control by the linked list pointer, both in operation and form. A lot more convenient.

         Note: The sentinel header node is created in the empty_Init() function!

3. Addition of linked list data:

template<class T>
class list{

public:
    typedef Node<T> node;
     //尾插  
     void push_back(const T& val) {
		 node* newnode = new node(val);
		 node* tail = _head->_prev;
		 tail->_next = newnode;
		 newnode->_prev = tail;
		 newnode->_next = _head;
		 _head->_prev = newnode;
		 ++_size;
	 }
    //尾删
	 void pop_back() {
		 assert(!empty());
		 node* tail = _head->_prev;
		 node* last = tail->_prev;
		 last->_next = _head;
		 _head->_prev = last;
		 delete tail;
		 --_size;
	 }
    //头插
	 void push_front(const T& val) {
		 node* newnode = new node(val);
		 node* first = _head->_next;
		 _head->_next = newnode;
		 newnode->_prev = _head->_next;
		 newnode->_next = first;
		 first->_prev = newnode;
		 ++_size;
	 }
    //头删
	 void pop_front() {
		 assert(!empty());
		 node* first = _head->_next;
		 node* second = first->_next;
		 _head->_next = second;
		 second->_prev = _head->_next;
		 delete first;
		 --_size;
	 }

    //任意位置的插入
    iterator insert(iterator pos, const T& val=T()) {
		 if (pos == this->begin()) {
			 push_front(val);    //复用代码
		 }

		 else if (pos == this->end()) {
			 push_back(val);    //复用代码
		 }

		 else {
			 node* newnode = new node(val);
			 node* cur = pos.phead;
			 node* last = cur->_prev;
			 last->_next = newnode;
			 newnode->_prev = last;
			 newnode->_next = cur;
			 cur->_prev = newnode;
			 ++_size;
		 }
		 return pos;
	 }

    //任意位置的删除
     iterator erase(iterator pos) {
		 assert(!empty());
		 if (pos == this->begin()) {
			 pop_front();
		 }
		 else if (pos == this->end()) {
			 pop_back();
		 }
		 else {
			 node* cur = pos.phead;
			 node* tail = cur->_next;
			 node* last = cur->_prev;
			 last->_next = tail;
			 tail->_prev = last;
			 delete cur;
			 --_size;
		 }
		 return pos;
	 }

  private:
	    node* _head;	//指向堆区空间链表的指针
	    size_t _size;    //计数

};

        For the addition and deletion of data, head plug deletion and tail plug deletion are simple. The focus is on the implementation of the insert and erase functions. As shown in the above code, in insert and erase, there are three situations each, among which the head and tail operations are direct Just reuse the function. For insertion and deletion in the middle position, what I want to say is that the specified pos parameter is of iterator type - a custom iterator class, which is a pointer! ! ! It just points to the position of the node element, so if you want to get the node at that position, you need pos.phead to get the node. Only when you get the node can you use the before and after pointers near the node! 

 

4. The next step is to create the iterator:

        In the simulation implementation of vector and String containers, I did not create separate iterators. This is because the bottom layers of these two containers are arrays, which are a continuous address space. The members begin and end in the iterator are You can directly let the pointer type byte ++/--. It is very convenient to use the native pointer to determine the position.

        For the list container, its bottom layer is a linked list, and the positions of each node are discontinuous and random. Using native pointers cannot traverse every element of an object! Therefore, for the list container, you need to create a custom type iterator to traverse the linked list. 

template<class T>
	struct list_iterator
	{
		typedef list_node<T> node;

		node* _pnode;            //成员变量

		list_iterator(node* p)    //构造函数
			:_pnode(p){}

		T& operator*(){                    //指针解引用
			return _pnode->_data;
		}
                
		list_iterator<T>& operator++(){    //指针++
			_pnode = _pnode->_next;
			return *this;
		}

		bool operator!=(const list_iterator<T>& it){   //!=运算符重载
			return _pnode != it._pnode;
		}
	};

template<class T>
	class list{
	public:
        typedef list_node<T> node;
		typedef list_iterator<T> iterator;

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

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

        In the custom iterator class, I wrote several operator overloaded functions that will definitely be used based on the iterator code of vector and String that I usually practice: dereference, pointer++, != and other functions used in traversal .

        After writing the custom iterator class, you need to rename the class in the list class.

        After writing the iterator, we can test it:

 

 

 Note: The above iterator is only the implementation of ordinary iterator. There will also be const iterator and reverse iterator that need to be implemented, which means that two more iterator classes have to be written.

4. Implementation of simple functions:

template<class T>
class list{
  public:
     size_t size()const {
		 return _size;
		 //方法2:利用指针遍历,每遍历一次记一次数
	 }

	 bool empty() const {
		 //方法1:
		 return _size == 0;
        //方法2:return _head->next==_head;
	 }

    void clear() {
		node* cur = _head->_next;
		 while (cur != _head) {
			 node* del = cur;
			 cur = cur->_next;
			 delete del;
		 }
		 cur->_next = _head;
		 cur->_prev = _head;
		 _size = 0;
	 }

	 T& front() {
		 return  this->begin().phead->_data;
	 }

	 T& back() {
		 return  this->end().phead->_prev->_data;
	 }

  private:
    node* _head;
    size_t _size;

    };

5. Construction and destruction 

        With the iterator, we can implement the iterator interval construction of the list constructor:

template<class T>
class list{
public:
    //迭代器区间构造函数
    template<class Inputiterator>
	list(Inputiterator first, Inputiterator last) {		
		empty_Init();
		while (first != last) {
			push_back(*first);
			++first;
		}
	}

    void empty_Init() {
		_head = new node(T());
		_head->_next = _head;
		_head->_prev = _head;
		_size = 0;
	}

	list()    //无参构造
	{
		empty_Init();
	}

	//析构函数
	~list() {
		this->clear();
		delete _head;
		_head = nullptr;
		_size = 0;
	}
private:
    node* _head;
    size_t _size;
};

The destructor is to traverse each node in the linked list and release it, setting the pointer to null and setting the variable to zero.

6. Copy construction and assignment overloading

 

Traditional writing:

//拷贝构造——传统写法
	list(const list<T>& lt) {
		empty_Init();
		for (auto& e : lt) {
				this->push_back(e);
			}
		}

//赋值重载函数——传统写法
    list<T>& operator=(const list<T>& lt) {
		if (this != &lt) {
			this->clear();
		}
		for (const auto& e : lt) {
			this->push_back(e);
		}
		return *this;
	}

        Copy construction and assignment overloading are essentially the same. They copy the existing list object and then deep copy the data to itself. Deep copy is to create a head node of its own, and the remaining data is a shallow copy (brainlessly traversing the data to let its own head pointer perform pointer linking). 

Modern writing:

    //调用std库中swap函数进行成员交换
    void Swap(list<T>& lt) {
		std::swap(this->_head, lt._head);
		std::swap(this->_size, lt._size);
	}	

    //拷贝构造——现代写法
	list(const list<T>& lt) {
		empty_Init();
		list<T> tmp(lt.begin(), lt.end());		//调用迭代器区间构造函数
		this->Swap(tmp);
	}

    //赋值重载——现代写法
	list<T>& operator=(list<T> lt) {
		this->Swap(lt);    //值传递,形参的改变不影响实参
		return *this;
	}

 

7. Iterator template type

        As mentioned at the end of the above discussion of iterators, iterators have a normal version, a const version, a reverse version, and a reverse const version, which means that we need to create four iterator types, but the operator overloaded functions that can be used by iterators are the same. , are all dereference, pointer++, != operators.

//自定义普通迭代器类
template<class T>
	struct list_iterator{
		typedef list_node<T> node;
		node* _pnode;

		list_iterator(node* p)
			:_pnode(p){}

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

		list_iterator<T>& operator++(){
			_pnode = _pnode->_next;
			return *this;
		}

		list_iterator<T>& operator--(){
			_pnode = _pnode->_prev;
			return *this;
		}

		bool operator!=(const list_iterator<T>& it){
			return _pnode != it._pnode;
		}
	};

    //const迭代器类
	template<class T>
	struct list_const_iterator{
		typedef list_node<T> node;
		node* _pnode;

		list_const_iterator(node* p)
			:_pnode(p){}

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

		list_const_iterator<T>& operator++(){
			_pnode = _pnode->_next;
			return *this;
		}

		list_const_iterator<T>& operator--(){
			_pnode = _pnode->_prev;
			return *this;
		}

		bool operator!=(const list_const_iterator<T>& it){
			return _pnode != it._pnode;
		}
	};

	template<class T>
	class list{
		typedef list_node<T> node;
	public:
		typedef list_iterator<T> iterator;
		typedef list_const_iterator<T> const_iterator;

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

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

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

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

        As above, the only difference between ordinary iterator classes and const iterator classes is that in traversal, *it cannot be used to modify data in the dereference operator overloaded function of the const iterator class, so the implementation of other functions in these two iterator classes is completely Likewise, this greatly causes code redundancy and reduces readability! ! !

        So in order to embody different types of iterators in one iterator class, you can do this:

template<class T, class Ref, class Ptr>	
struct _list_iterator {
	typedef list_node<T> node;
	typedef _list_iterator<T, Ref,Ptr> Self;	//Self是T与ref,ptr 模板类型的另一种别称

	//迭代器构造函数
	_list_iterator(node* p)
		:_pnode(p) {}

	//解引用
	Ref operator*() {
		return _pnode->_data;
	}

    //箭头只有是结构体才可以用
	Ptr operator->() {
		return &_pnode->_data;		//返回的是该结点的地址
	}

	Self& operator++() {
		_pnode = _pnode->_next;
		return *this;
	}

	//后置++,使用占位符int,与前置++以示区分
	Self operator++(int) {
		Self tmp(*this);
		_pnode = _pnode->_next;
		return tmp;
	}
    //前置--
	Self& operator--() {
		_pnode = _pnode->_prev;
		return *this;
	}

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

	bool operator!=(const Self& lt) const {
		return _pnode != lt._pnode;
	}

	bool operator==(const Self& lt)  const{
		return _pnode == lt._pnode;
	}

	node* _pnode;
};

        In the iterator class, three template parameters are taken. These three template parameters: T represents a generic value, Ref represents a generic reference, and Ptr represents a generic pointer. These three parameters are mainly used for function return values ​​and function parameters of operator overloaded functions. They are quite convenient and serve multiple purposes. Different types of functions can be called by passing different actual parameters.

 

template<class T>
class list {
public:
    typedef list_node<T>  node;
    typedef _list_iterator<T, T&, T*> iterator;
	//typedef list_const_iterator<T> const_iterator;
	typedef _list_iterator<T, const T&, const T*> const_iterator;

Complete simulation implementation code .h file:

#include<iostream>
#include<assert.h>

using std::cout;
using std::endl;

template<class T>
struct list_node {	//该类为内部类,是list的内部类

	list_node(const T& val)
		:_next(nullptr)
		, _prev(nullptr)
		, _data(val) {
	}

	//成员变量
	list_node* _next;
	list_node* _prev;
	T _data;
};

//typedef list_iterator<T, T&> iterator;
//typedef list_iterator<T, const T&> const_iterator;

//这种写法来源:vector<int>,vector<string>,vector<vector<int>> 
template<class T, class Ref, class Ptr>	//新增一个模板参数	,T是一种类型,ref是一种类型
struct list_iterator {
	typedef list_node<T> node;
	typedef list_iterator<T, Ref, Ptr> Self;	//Self是T与ref,ptr 模板类型的另一种别称

	//迭代器构造函数
	list_iterator(node* p)
		:_pnode(p) {}

	//在下面的运算符重载中,const版与非const版只有解引用运算符重载函数的类型不同,其他运算符重载都一样
	//所以operator* 的类型需要使用ref,ref可以理解为constT&, 而非const对象也可以调用const函数,权限够
	//const对象不可调用非const函数,权限不够,所以使用ref

	//解引用
	Ref operator*() {
		return _pnode->_data;
	}

	//箭头只有是结构体才可以用
	Ptr operator->() {
		return &_pnode->_data;		//返回的是该结点的地址
	}


	//前置++,
	//为啥要用引用? 原因:return *this ,this(迭代器对象)出了该函数体,还存在(this的生命周期在该类中是全局的)
	Self& operator++() {
		_pnode = _pnode->_next;
		return *this;
		//既然this还在,那么直接用引用返回,栈帧中不开临时空间,减少拷贝次数,提高效率
		//记住:使用引用返回的前提是,要返回的值出了函数体仍在才可以使用,否则会报错
	}

	//后置++,使用占位符int,与前置++以示区分
	Self operator++(int) {
		Self tmp(*this);
		_pnode = _pnode->_next;
		return tmp;
		//返回tmp后,tmp为临时对象,出了函数就消失了,tmp对象不在,需要拷贝,那就得用传值返回,在栈帧中
		//创建一个临时空间去接收返回的tmp对象数据。设置一个默认参数和前置++做区分,构成函数重载。
		//若使用引用返回,那么该函数结束后,返回的tmp已经不存在了,引用返回返回野指针(随机值)就会报错!!!
	}

	Self& operator--() {
		_pnode = _pnode->_prev;
		return *this;
	}

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

	bool operator!=(const Self& lt) const {
		return _pnode != lt._pnode;
	}

	bool operator==(const Self& lt)  const{
		return _pnode == lt._pnode;
	}

	node* _pnode;
};

//--------------------------------------------------------------------------------

template<class T>
class list {
public:
	typedef list_node<T>  node;
	typedef list_iterator<T, T&, T*> iterator;
	typedef list_iterator<T, const T&, const T*> const_iterator;

	void empty_Init() {
		_head = new node(T());
		_head->_next = _head;
		_head->_prev = _head;
		_size = 0;
	}

	list(){
		empty_Init();
	}

	//析构
	~list() {
		this->clear();
		delete _head;
		_head = nullptr;
		_size = 0;
	}

	template<class Inputiterator>
	list(Inputiterator first, Inputiterator last) {		//拷贝构造的天选打工人
		//先初始化,给头节点,否则没法继续
		empty_Init();
		while (first != last) {
			push_back(*first);
			++first;
		}
	}

	void swap(list<T>& lt) {
		std::swap(this->_head, lt._head);
		std::swap(this->_size, lt._size);
	}

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

	//拷贝构造——传统写法
	/*list(const list<T>& lt) {
		empty_Init();
		for (auto& e : lt) {
				this->push_back(e);
			}
		}*/

		//拷贝构造——现代写法
	list(const list<T>& lt) {
		empty_Init();
		list<T> tmp(lt.begin(), lt.end());		//调用迭代器区间构造函数
		this->swap(tmp);
	}

	//赋值重载——传统写法
	/*list<T>& operator=(const list<T>& lt) {
		if (this != &lt) {
			this->clear();
		}
		for (const auto& e : lt) {
			this->push_back(e);
		}
		return *this;
	}*/

	//赋值重载——现代写法
	list<T>& operator=(list<T> lt) {
		this->swap(lt);
		return *this;
	}

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

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

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

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

	//尾插
	void push_back(const T& val) {
		node* newnode = new node(T(val));
		node* tail = _head->_prev;
		tail->_next = newnode;
		newnode->_prev = tail;
		newnode->_next = _head;
		_head->_prev = newnode;
	}

	//insert
	iterator insert(iterator pos, const T& val) {
		node* newnode = new node(T(val));
		node* cur = pos._pnode;
		node* first = cur->_prev;
		first->_next = newnode;
		newnode->_prev = first;
		newnode->_next = cur;
		cur->_prev = newnode;
		_size++;
		//insert后返回新节点的位置,那么下一次pos就会指向最近一次创建新节点的位置了
		return iterator(newnode);
	}

	iterator erase(iterator pos) {
		//pos不能指向哨兵位头节点,因为pos一旦指向哨兵位头,那么该链表一定为空,空链表是不能再删数据的
		assert(pos != end());
		node* first = pos._pnode->_prev;
		node* last = pos._pnode->_next;
		first->_next = last;
		last->_prev = first;
		delete pos._pnode;
		pos._pnode = nullptr;
		--_size;
		return iterator(last);
	}

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

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

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

	size_t size() const{
		/*size_t s = 0;
		iterator it = this->begin();
		while (it != this->end()) {
			++it;
			++s;
		}
		return s;*/

		//复用insert和erase
		//因为在链表中,一切的新增和减少都是复用的insert和erase,所以在insert和erase中size++,size--即可
		return _size;
	}

	bool empty() const {
		//return _head->_next == _head;

		//也可以复用size()
		return _size == 0;
	}

private:
	node* _head;	//头节点
	size_t _size;
};

 

Guess you like

Origin blog.csdn.net/weixin_69283129/article/details/131966073