【C++STL】list的使用及其模拟实现

一、list的介绍

list和sting、vector一样,我们可以使用cplusplus文档进行查询:list的文档介绍

【总结】

1.list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代

2.list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素

3.list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高效

4.与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率更好

5.与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这可能是一个重要的因素)

二、list的使用

1.构造函数

在这里插入图片描述

构造函数 接口说明
list (size_type n, const value_type& val = value_type()) 构造的list中包含n个值为val的元素
list() 构造空的list
list (const list& x) 拷贝构造函数
list (InputIterator fifirst, InputIterator last) 用[fifirst, last)区间中的元素构造list

在这里插入图片描述

2.容量操作

函数声明 接口说明
empty 检测list是否为空,是返回true,否则返回false
size 返回list中有效节点的个数

3.元素访问

函数声明 接口说明
front 返回list的第一个节点中值的引用
back 返回list的最后一个节点中值的引用

在这里插入图片描述

4.修改操作

在这里插入图片描述

函数声明 接口说明
push_front 在list首元素前插入值为val的元素
pop_front 删除list中第一个元素
push_back 在list尾部插入值为val的元素
pop_back 删除list中最后一个元素
insert 在list position位置中插入值为val的元
erase 删除list position位置的元素
swap 交换两个list中的元素
clear 清空list中的有效元素
void list_test3()
{
    
    
	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;
	// 头插
	lt.push_front(10);
	lt.push_front(20);
	lt.push_front(30);
	lt.push_front(40);

	for (auto e : lt)
	{
    
    
		cout << e << " ";
	}
	cout << endl;

	// 在pos之前插入数据
	auto pos = find(lt.begin(), lt.end(), 3);
	if (pos != lt.end())
	{
    
    
		lt.insert(pos, 30);
	}

	cout << *pos << endl;
	(*pos)++;

	for (auto e : lt)
	{
    
    
		cout << e << " ";
	}
	cout << endl;

	// 删除pos位置数据
	lt.erase(pos);
	for (auto e : lt)
	{
    
    
		cout << e << " ";
	}
	cout << endl;
}

在这里插入图片描述

5.其他接口

在这里插入图片描述

函数声明 接口说明
splice 将list1的元素转移到list2中
remove 移除list中指定元素
remove_if 有条件的移除元素
unique 去重
merge 合并两个链表
sort 链表排序
reverse 链表逆置
void list_test4()
{
    
    
	list<int> lt;
	lt.push_back(10);
	lt.push_back(2);
	lt.push_back(5);
	lt.push_back(3);
	lt.push_back(4);
	lt.push_back(4);
	lt.push_back(6);
	lt.push_back(4);
	lt.push_back(0);

	for (auto e : lt)
	{
    
    
		cout << e << " ";
	}
	cout << endl;

	lt.remove(3);

	for (auto e : lt)
	{
    
    
		cout << e << " ";
	}
	cout << endl;

	lt.remove(30);
	for (auto e : lt)
	{
    
    
		cout << e << " ";
	}
	cout << endl;

	lt.sort();
	for (auto e : lt)
	{
    
    
		cout << e << " ";
	}
	cout << endl;

	// 先排序,才能实现去重
	lt.unique();
	for (auto e : lt)
	{
    
    
		cout << e << " ";
	}
	cout << endl;
}

在这里插入图片描述

【注意】

1.由于list的物理结构是非连续的–前一个节点和后一个节点的地址大小是不确定的,所以list不支持随机访问,也没有了重载[]操作,因为重载的话效率很低O(N)

2.list不支持reserve操作,因为list的节点使用时开辟,使用完销毁,不能预留空间

3.链表排序只能使用list提供的sort接口,而不能使用algorithm提供的sort接口,因为链表物理地址不连续,迭代器为双向迭代器,不支持±操作,而算法库中的sort函数需要支持±的随机迭代器

4.链表去重之前必须保证链表有序,否则去重不完全

5.两个有序链表合并之后仍然有序

尽管list提供了这些额外的函数接口,他们有一定的作用,但是实际上这些特殊接口使用的频率很低,保存sort接口(链表排序的效率很低)

6.排序的性能分析

链表排序只能使用list提供的sort接口,而不能使用algorithm提供的sort接口,但是list提供的sort函数排序的效率非常低,我们通过下面的两组数据测试来直观的感受

一、vector排序与list排序对比

// N个数据需要排序,vector+ 算法sort  list+ sort
void test_op()
{
    
    
	srand(time(0));
	const int N = 100000;
	vector<int> v;
	v.reserve(N);

	list<int> lt;
	for (int i = 0; i < N; ++i)
	{
    
    
		auto e = rand();
		v.push_back(e);
		lt.push_back(e);
	}

	int begin1 = clock();
	sort(v.begin(), v.end());
	int end1 = clock();

	int begin2 = clock();
	lt.sort();
	int end2 = clock();

	printf("vector sort:%d\n", end1 - begin1);
	printf("list sort:%d\n", end2 - begin2);
}

在这里插入图片描述

二、list将数据拷贝到vector排序,排完以后再拷贝回来和list排序对比

// N个数据需要排序,vector+ 算法sort  list+ sort
void test_op()
{
    
    
	srand(time(0));
	const int N = 100000;
	vector<int> v;
	v.reserve(N);

	list<int> lt1;
	list<int> lt2;
	for (int i = 0; i < N; ++i)
	{
    
    
		auto e = rand();
		lt1.push_back(e);
		lt2.push_back(e);
	}

	// 拷贝到vector排序,排完以后再拷贝回来
	int begin1 = clock();
	for (auto e : lt1)
	{
    
    
		v.push_back(e);
	}
	sort(v.begin(), v.end());
	size_t i = 0;
	for (auto& e : lt1)
	{
    
    
		e = v[i++];
	}
	int end1 = clock();

	int begin2 = clock();
	lt2.sort();
	int end2 = clock();

	printf("copy vector sort:%d\n", end1 - begin1);
	printf("list sort:%d\n", end2 - begin2);
}

在这里插入图片描述

我们可以看到,list sort的效率远低于vector sort ,甚至我们可以说,我们先把数据从list拷贝到vector中,在vector中进行排序完毕之后再拷贝到list的效率都比直接在list进行排序的效率高,此外,我们在测试性能的时候,我们在release版本下比在debug版本下得到的结果更具有参考价值。

三、list迭代器的实现

1.迭代器的分类

根据迭代器的功能,我们可以将迭代器分为三类:

1.单向迭代器:迭代器仅仅支持++和解引用操作,单链表的迭代器就是单向迭代器

2.双向迭代器:迭代器支持++ 、–和解引用操作,但不支持+ 、-操作,list双向带头循环链表就是双向迭代器

3.随机迭代器:迭代器支持 ++ – + -以及解引用操作,即迭代器能够随机访问,string 和vector 就是随机迭代器

函数声明 接口说明
begin + end 返回第一个元素的迭代器+返回最后一个元素下一个位置的迭代器
rbegin + rend 返回第一个元素的reverse_iterator,即end位置,返回最后一个元素下一个位置的

【注意】

  1. begin与end为正向迭代器,对迭代器执行++操作,迭代器向后移动
  2. rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动

2.list迭代器的失效问题

迭代器失效即迭代器所指向的节点的无效,即该节点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入时是不会导致list的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响

list 和vector不同,list进行insert操作之后不会出现迭代器失效的问题,因为list插入的节点是动态开辟的,此外,list每个节点的物理地址是不相关的,所以插入的新节点并不会影响原来其他节点的地址

但是,list erase之后就会出现迭代器失效的问题,因为list删除节点时候直接将该节点释放了,此时我们再次访问该节点就会出现野指针的访问,从而造成越界访问

3.list迭代器的模拟实现

我们知道,迭代器是一个类似于一个指针一样的东西,能够像指针一样进行操作,比如++ – * + - 等等,对于string和vector的迭代器来说,我们可以使用原生指针,因为他们的结构是一个数组,指针解引用就是那个位置的数据,++就会移动数据类型的大小,地址是连续的,所以天然的支持以上的操作

对于list来说,list的节点是一个结构体,此外,list的每一个节点的物理地址是不连续的,如果我们还像string 、vector一样使用原生指针,将节点的指针typedef为迭代器的话,那么此时它并不能实现解引用、++等操作,所以我们需要使用一个类来对迭代器进行封装,再使用运算符重载使得迭代器能够实现我们期望的解引用、++、–等操作

SGI版本C++源码中对list迭代器实现的框架如下:

// 定义节点
template <class T>
struct __list_node {
    
    
  typedef void* void_pointer;
  void_pointer next;
  void_pointer prev;
  T data;
};

// 定义迭代器
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_iterator<T, T&, T*>             iterator;
  typedef __list_iterator<T, const T&, const T*> const_iterator;
  typedef __list_iterator<T, Ref, Ptr>           self;
  link_type node;

  __list_iterator(link_type x) : node(x) {
    
    }
  __list_iterator() {
    
    }
  __list_iterator(const iterator& x) : node(x.node) {
    
    }

  bool operator==(const self& x) const {
    
     return node == x.node; }
  bool operator!=(const self& x) const {
    
     return node != x.node; }
  reference operator*() const {
    
     return (*node).data; }
 }

从上面我们可以看出,我们为迭代器专门实现了一个__list_iterator类,然后在类中对解引用,++ != ==等等进行重载,使得该迭代器能够像指针一样使用

3.1普通迭代器

list的普通迭代器的实现非常简单,我们只需要一个模板参数T来代表数据类型,然后通过运算符重载来实现迭代器的各种操作:

template<class T>
// 定义节点结构
struct list_node
{
    
    
	list_node<T> _prev;
	list_node<T> _next;
	T _data;

	// 构造
	list_node(const T& x)
		:_prev(nullptr)
		,_next(nullptr)
		,_data(x)
	{
    
    }
};
// typedef __list_iterator<T> iterator;
template<class T>
struct __list_iterator
{
    
    
	typedef list_node<T> node; // 将list节点重命名为node
	node* _pnode;  // 节点的指针作为类的成员变量

	// 构造
	__list_iterator(const node* p)
		:_pnode(p)
	{
    
    }

	// 重载解引用
	T& operator*()
	{
    
    
		return _pnode->_data;
	}

	// 重载前置++
	__list_iterator<T>& operator++()
	{
    
    
		_pnode = _pnode->_next;
		return *this;
	}

	// 重载后置++
	__list_iterator<T> operator++(int)
	{
    
    
		__list_iterator<T> tmp(*this);
		_pnode = _pnode->_next;
		return tmp;
	}

	// 重载前置--
	__list_iterator<T> operator--()
	{
    
    
		_pnode = _pnode->_prev;
		return *this;
	}

	// 重载后置--
	__list_iterator<T> operator--(int)
	{
    
    
		__list_iterator<T> tmp(*this);
		_pnode = _pnode->_prev;
		return tmp;
	}

	// 重载!=
	bool operator!=(const __list_iterator it)
	{
    
    
		return _pnode != it._pnode;
	}

	// 重载==
	bool operator!=(const __list_iterator it)
	{
    
    
		return _pnode == it._pnode;
	}
};

3.2const 迭代器

我们已经实现了迭代器的普通迭代器,那我们该如何实现const迭代器呢,const迭代器与不同迭代器的区别在于:const迭代器不能修改节点的数据,即operator*() 函数的返回值应该是const T&,我们的第一反应是在__list_iterator类中重载一个返回值为const T& 的operator*()函数如下:

// typedef __list_iterator<T> iterator;
// typedef list_const_iterator<T> const_iterator;
// 重载解引用
T& operator*()
{
    
    
	return _pnode->_data;
}
const T& operator*() const
{
    
    
	return _pnode->_data;
}

这个看起来可行,但实际上是可行的,因为const __list_iterator<T> const _iterator中的const修饰的是const_iterator本身,即const_iterator不能改变,这样会导致const_iterator不能进行++等等操作,而并不会限制迭代器解引用后对节点数据的改变

所以我们应该为const迭代器设计一个单独的类__list_const_iteartor

template<class T>
// 定义节点结构
struct list_node
{
    
    
	list_node<T> _prev;
	list_node<T> _next;
	T _data;

	// 构造
	list_node(const T& x)
		:_prev(nullptr)
		,_next(nullptr)
		,_data(x)
	{
    
    }
};
// typedef __list_iterator<T> iterator;
template<class T>
struct __list_iterator
{
    
    
	typedef list_node<T> node; // 将list节点重命名为node
	node* _pnode;  // 节点的指针作为类的成员变量

	// 构造
	__list_iterator(const node* p)
		:_pnode(p)
	{
    
    }

	// 重载解引用
	T& operator*()
	{
    
    
		return _pnode->_data;
	}

	// 重载前置++
	__list_iterator<T>& operator++()
	{
    
    
		_pnode = _pnode->_next;
		return *this;
	}

	// 重载后置++
	__list_iterator<T> operator++(int)
	{
    
    
		__list_iterator<T> tmp(*this);
		_pnode = _pnode->_next;
		return tmp;
	}

	// 重载前置--
	__list_iterator<T> operator--()
	{
    
    
		_pnode = _pnode->_prev;
		return *this;
	}

	// 重载后置--
	__list_iterator<T> operator--(int)
	{
    
    
		__list_iterator<T> tmp(*this);
		_pnode = _pnode->_prev;
		return tmp;
	}

	// 重载!=
	bool operator!=(const __list_iterator<T> it)
	{
    
    
		return _pnode != it._pnode;
	}

	// 重载==
	bool operator!=(const __list_iterator<T> it)
	{
    
    
		return _pnode == it._pnode;
	}
};

// typedef list_const_iterator<T> const_iterator;
template<class T>
struct __list_const_iterator
{
    
    
	typedef list_node<T> node; // 将list节点重命名为node
	node* _pnode;  // 节点的指针作为类的成员变量

	// 构造
	__list_const_iterator(const 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++(int)
	{
    
    
		__list_iterator<T> tmp(*this);
		_pnode = _pnode->_next;
		return tmp;
	}

	// 重载前置--
	__list_const_iterator<T>& operator--()
	{
    
    
		_pnode = _pnode->_prev;
		return *this;
	}

	// 重载后置--
	__list_const_iterator<T> operator--(int)
	{
    
    
		__list_iterator<T> tmp(*this);
		_pnode = _pnode->_prev;
		return tmp;
	}

	// 重载!=
	bool operator!=(__list_const_iterator<T>& it)
	{
    
    
		return _pnode != it._pnode;
	}

	// 重载==
	bool operator!=(__list_const_iterator<T>& it)
	{
    
    
		return _pnode == it._pnode;
	}
};

我们可以看到,__list_iterator类和__list_const_iterator类除了类名和operator*()函数的返回值不同之外,其他地方完全相同,这就造成了代码的冗余,C++大佬提出了一个解决方案,就是在-_list_iterator中增加一个模板参数,代码如下:

// const迭代器--增加参数模板,解决operator*()返回值问题
//typedef __list_iterator<T, T&> iterator;
//typedef __list_const_iterator<T,const T&> const_iterator
template<class T,class Ref>
struct __list_iterator
{
    
    
	typedef list_node<T> node; // 重命名list 节点
	typedef __list_iterator<T, Ref> Self;  /
	node* pnode;

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

	Ref& operator*()
	{
    
    
		return _pnode->data;
	}

	// 重载前置++
	Self& operator++()
	{
    
    
		_pnode = _pnode->_next;
		return *this;
	}

	// 重载后置++
	Self operator++(int)
	{
    
    
		__list_iterator<T> tmp(*this);
		_pnode = _pnode->_next;
		return tmp;
	}

	// 重载前置--
	Self operator--()
	{
    
    
		_pnode = _pnode->_prev;
		return *this;
	}

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

	// 重载!=
	bool operator!=(const Self& it)
	{
    
    
		return _pnode != it._pnode;
	}

	// 重载==
	bool operator!=(const Self& it)
	{
    
    
		return _pnode == it._pnode;
	}
};

所以我们可以通过传递不同的参数让编译器实例化出不同的类,从而实现了普通迭代器和const迭代器

3.3完整版迭代器

我们从迭代器的源码中可以发现,迭代器有有三个模板参数,我们通过下面的案列引出第三个模板参数,假设list第一个参数T是一个自定义类Pos,其定义如下:

struct Pos
{
    
    
	int _row;
	int _col;

	Pos(int row = 0, int col = 0)
	:_row(row)
	,_col(col)
	{
    
    }
};

那么我们通过list迭代器以下面的方式来访问Pos中的成员:

list<Pos> lt;
Pos p1(1, 1);
lt.push_back(Pos(2,2));
lt.push_back(Pos(3,3));

list<Pos>::iterator it = lt.begin();
while (it != lt.end())
{
    
    
	it->_row++;

	cout << (*it)._row << ":" << (*it)._col << endl;
	++it;
}
cout << endl;

我们可以看到,我们需要先使用解引用的方式得到Pos节点,但是Pos节点是一个结构体类型,所以我们需要通过.的方式来获取结构体的成员,但是这样使用起来非常的别扭,因为迭代器是模拟指针的行为,而结构体指针访问数据的方式是类名->变量名,那么我们是否可以使用下面的方式来实现呢?

cout<<it->_row<<":"<<it->_col<<endl;

显然,这样是不行的,因为it是一个自定义类型的对象(__list_iterator类的对象),而只有结构体指针才能像上面那样访问成员,所以我们需要运算符重载it支持->操作,具体实现如下:

template<class T,class Ref>
struct __list_iterator
{
    
    
	T* operator->()
	{
    
    
		return &_pnode->data;
	}
}

所以我么输出的代码就可以以下面的方式书写:

cout<<it->_row<<":"<<it->.col<<endl;

为什么不是两个->而是一个->呢?这是因为C++为了代码的可读性,省略了一个->。编译器会将->转换为it.operator->(),它得到节点数据的地址,也就是Pos*,所以实际上Pos*还需要通过->操作符来得到_row和_col,但是it->->_row可读性太差了,所以C++省略了一个->,让编译器对其进行处理it.operator->()->_row;

在这里插入图片描述

但是此时这里又会出现一个问题,那就是const迭代器需要operator->()的返回值为const T*,所以我们再增加一个模板参数,把第三个模板参数作为operator->()函数的返回值,使得编译器会根据传入参数的不同实例化出不同的__list_iterator类

所以我们list迭代器的完整代码如下:

// 封装迭代器
// 同一个类模板实例化出的两个类型
// 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* _pnode;

	// 构造
	__list_iterator(node* p)
		:_pnode(p)
	{
    
    }

	// 重载箭头
	Ptr operator->()
	{
    
    
		return &_pnode->_data;
	}

	// 重载*
	Ref operator*()
	{
    
    
		return _pnode->_data;
	}

	// 重载前置++
	Self& operator++()
	{
    
    
		_pnode = _pnode->_next;
		return *this;
	}

	// 重载后置++
	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& it) const
	{
    
    
		return it._pnode != _pnode;
	}

	// 重载等于
	bool operator==(const Self& it) const
	{
    
    
		return _pnode == it._pnode;
	}
};

3.4迭代器总结

1.const迭代器实现的两种方法:

(1)为const迭代器单独写一个类,次类与原来类只有类名和operator*(),operator->()函数的返回值类型不同,但是这样会造成代码的冗余

(2)给const迭代器增加Ref,Ptr两个模板参数,让编译器根据传入的Ref,Ptr的不同自动实例化出不同的迭代器类

2.对于普通类来说,类名=类型,对于类模板来说,类名!=类型,类型=类名+模板参数,但需要注意的是,在类中,不管是否存在模板,类名都等于类型,但是为了规范,我们不建议这样使用

四、模拟实现完整代码

1.list.h

#pragma once
#include <assert.h>
#include <algorithm>

namespace hdp
{
    
    
	// 定义节点结构
	template<class T>
	struct list_node
	{
    
    
		list_node<T>* _prev;
		list_node<T>* _next;
		T _data;

		// 构造
		list_node(const T& x)
			:_prev(nullptr)
			,_next(nullptr)
			,_data(x)
		{
    
    }
	};

	// 封装迭代器
	// 同一个类模板实例化出的两个类型
	// 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;   // 重命名为list节点
		typedef __list_iterator<T, Ref, Ptr> Self;
        // 成员变量
		node* _pnode;

		// 构造
		__list_iterator(node* p)
			:_pnode(p)
		{
    
    }

		// 重载箭头
		Ptr operator->()
		{
    
    
			return &_pnode->_data;
		}

		// 重载*
		Ref operator*()
		{
    
    
			return _pnode->_data;
		}

		// 重载前置++
		Self& operator++()
		{
    
    
			_pnode = _pnode->_next;
			return *this;
		}

		// 重载后置++
		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& it) const
		{
    
    
			return it._pnode != _pnode;
		}

		// 重载等于
		bool operator==(const Self& it) const
		{
    
    
			return _pnode == it._pnode;
		}
	};


	// 跟普通迭代器的区别:遍历,不能用*it修改数据
	/*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;
		}
	};*/


	//vector<int>
	//vector<string>
	//vector<vector<int>>
	
	// 类名  类型
	// 普通类  类名 等价于 类型
	// 类模板  类名  不等价于  类型
	// 如:list模板 类名list  类型list<T> 
	// 类模板里面可以用类名代表类型,但是建议不要那么用

	// 定义list类
	template<class T>
	class list
	{
    
    
        // 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()
		{
    
    
			return iterator(_head->_next);
		}

		iterator end()
		{
    
    
			// iterator it(_head);
			// return it;
            // 匿名对象构造
			return iterator(_head);
		}

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

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

		// 创建哨兵节点
		void empty_initialize()
		{
    
    
			_head = new node(T());
			_head->_next = _head;
			_head->_prev = _head;

			_size = 0;
		}

		// 构造 不是list<T>的原因,构造函数名和类名相同,而list<T>是类型
		list()
		{
    
    
			empty_initialize();
		}

		// 迭代器构造
		template<class InputIterator>
		list(InputIterator first, InputIterator last)
		{
    
    
			empty_initialize();
			while (first != last)
			{
    
    
				push_back(*first);
				++first;
			}
		}

		// 拷贝构造
		// lt2(lt1)
		list(const list<T>& lt)
		{
    
    
			empty_initialize();

			for (const auto& e : lt)
			{
    
    
				push_back(e);
			}

		}

		// 拷贝构造现代写法
		list(list<T>& lt)
		{
    
    
			empty_initialize();

			list<T> tmp(lt.begin(), lt.end());
			swap(tmp);
		}
        
		// 赋值重载
		list<T>& operator=(const list<T>& lt)
		{
    
    
			if (this != &lt)
			{
    
    
				clear();
				for (const auto& e : lt)
				{
    
    
					push_back(e);
				}
			}

			return *this;
		}

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

		// 交换
		void swap(list<T>& lt)
		{
    
    
			std::swap(_head, lt._head);
			std::swap(_size, lt._size);
		}

		size_t size() const
		{
    
    
			return _size;
		}

		bool empty() const
		{
    
    
			// return _head->_next == _head;
			// return _head->_prev == _head;
			return _size == 0;
		}

		// 析构
		~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)
		{
    
    
			//node* newnode = new node(x);
			//node* tail = _head->_prev;
			 _head tail newnode
			//tail->_next = newnode;
			//newnode->_prev = tail;
			//newnode->_next = _head;
			//_head->_prev = newnode;

			insert(end(), x);
		}

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

		// 尾删
		void pop_back()
		{
    
    
			//earse(end()->prev);
			erase(--end());
		}

		// 头删
		void pop_front()
		{
    
    
			erase(begin());
		}

		// 在pos之前插入数据
		iterator insert(iterator pos, const T& x)
		{
    
    
			node* newnode = new node(x);
			node* cur = pos._pnode;
			node* prev = cur->_prev;
			// prev newnode cur
			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;

			++_size;

			return iterator(newnode);
		}

		// 删除pos位置的数据
		iterator erase(iterator pos)
		{
    
    
			assert(pos != end());

			node* prev = pos._pnode->_prev;
			node* next = pos._pnode->_next;

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

			delete pos._pnode;
			--_size;

			return iterator(next);
		}
	private:
		node* _head;
		size_t _size = 0; // 保存节点个数
	};

}

2.test.cpp

#include "list.h"
#include <iostream>
using namespace hdp;
using std::cout;
using std::endl;
#include <list>

// 测试迭代器
void list_test1()
{
    
    
	list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);

	// iterator 1、内嵌类型 2、行为像指针一样
	list<int>::iterator it = lt.begin();
	while (it != lt.end())
	{
    
    
		cout << *it << " ";
		++it;
	}
	cout << endl;

	for (auto e : lt)
	{
    
    
		cout << e << " ";
	}
	cout << endl;
}

void test_list2()
{
    
    
	list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);
	lt.push_front(5);
	lt.push_front(6);

	for (auto e : lt)
	{
    
    
		cout << e << " ";
	}
	cout << endl;
	cout << lt.size() << endl;

	lt.pop_back();
	lt.pop_back();
	lt.pop_front();
	lt.pop_front();

	for (auto e : lt)
	{
    
    
		cout << e << " ";
	}
	cout << endl;
	cout << lt.size() << endl;
}

struct Pos
{
    
    
	int _row;
	int _col;

	Pos(int row = 0, int col = 0)
		:_row(row)
		, _col(col)
	{
    
    }
};


void print_list(const list<Pos>& lt)
{
    
    
	list<Pos>::const_iterator it = lt.begin();
	while (it != lt.end())
	{
    
    
		//it->_row++;

		cout << it->_row << ":" << it->_col << endl;

		++it;
	}
	cout << endl;
}

void list_test3()
{
    
    
	list<Pos> lt;
	Pos p1(1, 1);
	lt.push_back(p1);
	lt.push_back(p1);
	lt.push_back(p1);
	lt.push_back(Pos(2, 2));
	lt.push_back(Pos(3, 3));

	// int* p  -> *p
	// Pos* p  -> p->
	list<Pos>::iterator it = lt.begin();
	//list<Pos>::iterator it2 = it;
	while (it != lt.end())
	{
    
    
		it->_row++;

		//cout << (&(*it))->_row << ":" << (*it)._col << endl;
		cout << it->_row << ":" << it->_col << endl;
		//cout << it.operator->()->_row << ":" << it->_col << endl;

		++it;
	}
	cout << endl;

	print_list(lt);
}

int main()
{
    
    
	//list_test1();
	//test_list2();
	list_test3();
	return 0;
}

五、vector和list的区别

vector与list都是STL中非常重要的序列式容器,由于两个容器的底层结构不同,导致其特性以及应用场景不同,其主要不同如下:

vector list
底层结构 动态顺序表,一段连续空间 带头结点的双向循环链表
随机访问 支持随机访问,访问某个元素效率O(1) 不支持随机访问,访问某个元素效率O(N)
插入和删除 任意位置插入和删除效率低,需要搬移元素,时间复杂度为O(N),插入时有可能需要增容,增容:开辟新空间,拷贝元素,释放旧空间,导致效率更低 任意位置插入和删除效率高,不需要搬移元素,时间复杂度为O(1)
空间利用率 底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高 底层节点动态开辟,小节点容易造成内存碎片,空间利用率低,缓存利用率低
迭代器 原生态指针 对原生态指针(节点指针)进行封装
迭代器失效 在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效,删除时,当前迭代器需要重新赋值否则会失效 插入元素不会导致迭代器失效,删除元素时,只会导致当前迭代器失效,其他迭代器不受影响
使用场景 需要高效存储,支持随机访问,不关心插入删除效率 大量插入和删除操作,不关心随机访问

猜你喜欢

转载自blog.csdn.net/qq_67582098/article/details/129940484