C++: list addition, deletion, checking and modification simulation implementation

Preface

This blog uses the SGI version. Considering that most of the blogs are beginners, the blogger only gives useful snippets.C++: The simulation implementation of list addition, deletion, check and modification is to use C++ to copy the double linked list, which is very simple. The difficulty lies mainly in the implementation of iterators

1. List underlying double linked list verification and node construction

1.1 list underlying data structure

What data structure is used at the bottom of list to implement it? Let's first take a look at the member functions and initialization of list in the SGI library.
Insert image description here

We found that in the list implementation, there is only one member variable node. The constructor constructs a sentinel bit connected end to end. At the same time, it is verified that the bottom layer of the list is a double linked list with sentinel bits.

1. 2 Node structure

Nodes have the same three members as doubly linked lists: node data, pointing to the previous node (prev), and pointing to the next node (next).

//节点
template<class T>
struct List_node
{
    
    
	T _data;
	List_node<T>* _prev;
	List_node<T>* _next;

	List_node(const T& x = T())
		:_data(x)
		,_prev(nullptr)
		,_next(nullptr)
	{
    
    }
};

Small tips:

  1. Our class name here is the same as in the library (List_node), and it will be typedefed when used in other classes later.
  2. The reason why the class name here is struct instead of class is that the default access permission of struct is public, and subsequent other classes will only need to use it extensively. If you use class, you need to use a large number of friend classes, which is too cumbersome.

2. Implementation of iterator encapsulation (key points and difficulties)

2.1 Preliminary instructions

  1. We know that the biggest use of iterators is to traverse data. But when it stops, what happens when the iterator points to the space to store data...leading us to need to use !=, ++, * and other operations. However, custom types cannot support the use of these operators. The solution given for this is:Encapsulating plus operator overloading
  2. Iterators are divided into ordinary iterators and const iterators (also divided into one-way iterators, two-way iterators and random access iterators). One of the simplest implementations is to implement two classes. but. . . We know that the difference between the two iterators is that the const iterator cannot modify the data. It is just that the j-related excuses (here are *, ->) are different. It would be too "big fuss" to implement two classes.
    So let’s take a look at how the library cleverly solves this problem!
    Insert image description here

2.2 Iterator implementation

The pre-explanation also explains how to implement a class to achieve the purpose. The following implementation is too simple and will not be explained separately.

//迭代器封装
template<class T, class Ref, class Ptr>
struct __list_iterator
{
    
    
	typedef List_node<T> Node;//节点类名重命名
	//由于迭代器实现中,如++、--等重载函数返回值类型为__list_iterator,名字过长,这里我们重命名self意味自身
	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;
	}
};

3. List implementation

3.1 Basic framework

In the list simulation, we give a head node _head and _size two member variables just like in the library. At the same time, we rename the nodes and iterators. Iterator renaming is not just for convenience, but more importantly, it provides a unified interface (for example, sting, vector, set, etc. interfaces are all the same), shields the underlying variables and member function attributes, and the implementation process and differences.

//list模拟实现
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;
private:
	Node* _head;//头节点
	int _size;
};

3.2 Iterators and const iterators

Below is where begin() and end() point to a valid two-line table.
Insert image description here

accomplish:

const_iterator begin()const
{
    
    
	//return const_iterator(_head->_next);或
	return _head->_next;//单参数类型支持隐式类型转换
}

const_iterator end()const
{
    
    
	return _head;
}

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

iterator end()
{
    
    
	return _head;
}

3.2 Constructor, destructor, copy construction, assignment overloading

3.2.1 Constructor

The implementation of the constructor is the same as what we saw at the beginning: a function (empty_Init) is called in the constructor to implement it. empty_Init() is used to complete initialization. (The empty_Init() function will also be called later by other interfaces)

//初始化
void empty_Init()
{
    
    
	_head = new Node;
	_head->_next = _head;
	_head->_prev = _head;

	_size = 0;
}

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

3.2.2 Destructor

In the destructor, we call a clear function to clear all the data, and then release the _head variable.

//析构函数
~List()
{
    
    
	clear();
	delete _head;
	_head = nullptr;
}

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

3.2.3 Copy construction

When copying and constructing, you must first initialize a node, and then tail-insert the data that needs to be copied (the implementation of the tail-insert interface will be given later)

//拷贝构造
List(const List<T>& It)
{
    
    
	empty_Init();
	for (auto e : It)
	{
    
    
		push_back(e);
	}
}

3.2.4 Assignment overloading

There are many ways to overload assignment. For relatively simple parameters, we directly pass the value, and then exchange the data of the variable to be assigned with the temporary variable generated by passing the parameter by value.

void swap(const List<T>& It)
{
    
    
	std::swap(_head, It._head);
}

//赋值重载
List<T>& operator=(const List<T> It)
{
    
    
	swap(It);
	return *this;
}

3.3 Insert at any position, delete at any position, end insertion, end deletion, head insertion, head deletion

3.3.1 Insert at any position

First, new takes out the node newnode to be inserted, and then connects prev, newnode, and pos, the node before pos. Finally ++_size is enough.
Insert image description here

iterator insert(iterator pos, const T& x)
{
    
    
	Node* cur = pos._node;
	Node* prev = cur->_prev;
	Node* newnode = new Node(x);

	prev->_next = newnode;
	newnode->_prev = prev;

	newnode->_next = cur;
	cur->_prev = newnode;
	_size++;
	return newnode;
}

3.3.2 Insertion and deletion at any position

First save the front and back nodes of the data to be deleted, then delete the node at pos, and then connect the front and back nodes. Finally –_size is enough.

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

3.3.3 Tail insertion, tail deletion, head insertion, head deletion

Tail insertion, tail deletion, head insertion, and head deletion all reuse the insertion and deletion interfaces at any position.

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

4. The list function is perfect

4.1 Iterator operator->()

Let’s take a look at the following code first, right?

struct AA
{
    
    
	AA(int a1 = 0, int a2 = 0)
		:_a1(a1)
		,_a2(a2)
	{
    
    }

	int _a1;
	int _a2;
};

void test_list3()
{
    
    
	list<AA> lt;
	lt.push_back(AA(1, 1));
	lt.push_back(AA(2, 2));
	lt.push_back(AA(3, 3));

	list<AA>::iterator it = lt.begin();
	while (it != lt.end())
	{
    
    
		cout<<*it<<" ";
		++it;
	}
	cout << endl;
}

Since list does not overload <<, a compilation error will be reported when accessing stored custom types.
Then can’t we just overload the << operator? Unfortunately, the C++ library does not support << in the list. The main reason is that the compiler does not know how to get the data.

So for custom types, we can dereference first and then access the members, or we can overload the operator->() function in the iterator. But it should be noted that the compiler hides a ->. The specific native behavior is as follows:

struct AA
{
    
    
	AA(int a1 = 0, int a2 = 0)
		:_a1(a1)
		,_a2(a2)
	{
    
    }

	int _a1;
	int _a2;
};

void test_list3()
{
    
    
	list<AA> lt;
	lt.push_back(AA(1, 1));
	lt.push_back(AA(2, 2));
	lt.push_back(AA(3, 3));

	list<AA>::iterator it = lt.begin();
	while (it != lt.end())
	{
    
    
		//cout << (*it)._a1<<" "<<(*it)._a2 << endl;
		cout << it->_a1 << " " << it->_a2 << endl;
		//上面编译器访问成员变量的真正行为如下:
		//cout << it.operator->()->_a1 << " " << it.operator->()->_a1 << endl;
		++it;
	}
	cout << endl;
}

4.2 Print data

//大多数情况模板是class还是typename是一样的,但当有未实例化模板时,必须使用typename
//template<typename T>
void print_list(const list<T>& lt)
{
    
    
	// list<T>未实例化的类模板,编译器不能去他里面去找
	// 编译器就无法list<T>::const_iterator是内嵌类型,还是静态成员变量
	// 前面加一个typename就是告诉编译器,这里是一个类型,等list<T>实例化
	// 再去类里面去取
	typename list<T>::const_iterator it = lt.begin();
	while (it != lt.end())
	{
    
    
		cout << *it << " ";
		++it;
	}
	cout << endl;  
}

Optimization: The above printed data is for the list, and the following is for the container.

// 模板(泛型编程)本质,本来应该由我们干的活交给编译器
template<typename Container>
void print_container(const Container& con)
{
    
    
	typename Container::const_iterator it = con.begin();
	while (it != con.end())
	{
    
    
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

5. All codes and test cases

giteeC++: list addition, deletion, checking and modification simulation implementation code and test case
recommendation: giteeC++: list addition, deletion, checking and modification simulation implementation code (final version, feeling version!!!)

Guess you like

Origin blog.csdn.net/Zhenyu_Coder/article/details/135185031