首页 > 编程语言 >C++STL之list容器:基本使用及模拟实现

C++STL之list容器:基本使用及模拟实现

时间:2024-09-02 21:54:46浏览次数:12  
标签:node iterator STL list back C++ lt push

目录

有了vector,为何还需list

list的使用

1,push_back、push_front、pop_back、pop_front的使用

2,正向、反向、const正向、const反向迭代器的使用

正向、反向迭代器的使用

const正向、const反向迭代器的使用

3,operator= 赋值

4,insert、erase 任意位置的插入、删除

5,迭代器失效(★)

情景一:

情景二:

小结:

list的模拟实现

源码的引入

1.基本结构的搭建

__list_node

 list

__list_node 的完善

push_back的实现

__list_itertaor

基本结构的整体代码

迭代器是如何执行起来的

2.迭代器的进一步理解

3.const 迭代器的实现

const迭代器的实现

4.insert 和 erase 的实现

5. pop_back、push_front、pop_front 的实现

6.拷贝构造

7.析构函数

8.operator= 赋值

9.深浅拷贝问题

10.整体代码

list.h

test.cpp

常见面试题

1,vector 和 list的区别?

2,vector 和 list 底层是如何实现的 ?

3,vector 是如何增容的?

4,什么是迭代器失效?


有了vector,为何还需list

为什么会有 list ?

因为 list 补充 vector 的缺点而存在的。

vector的缺点:

1、头部和中部的插入删除效率低。0(N), 因为需要挪动数据。

2、插入数据空间不够需要增容。增容需要开新空间、拷贝数据、释放旧空间,会付出很大的代价。

vector的优点:

支持下标的随机访问。 间接的就很好的支持排序、二分查找、堆算法等等。

list的产生就是为了解决vector的缺陷而存在的。

list的优点:

1、list头部、中间插入不再需要挪动数据,效率高。0(1)。

2、list插入数据是新增节点,不需要增容。

list的缺点:

不支持随机访问。

所以实际使用中vector和list是相辅相成的两个容器。


list的使用

list 的使用方法在一定程度上与 vector 类似。若熟悉 vector 的使用,那么在使用 list 时也会感到有一定的相似性。以下是list最常使用的接口,比较简单。

1,push_back、push_front、pop_back、pop_front的使用

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include <list>
void test1() 
{
	list<int> lt;
	//push_back 尾插
	lt.push_back(1); 
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);
	lt.push_back(5);
	//pop_back 尾删
	lt.pop_back();
	//push_front 头插
	lt.push_front(0);
	lt.push_front(-1);
	//pop_front 头删
	lt.pop_front();
	for (auto e : lt) 
	{
		cout << e << " ";
	}
	cout << endl;
}
int main() 
{
	test1();
	return 0;
}

2,正向、反向、const正向、const反向迭代器的使用

正向、反向迭代器的使用

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include <list>
void test2() 
{
	list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);
	lt.push_back(5);
	lt.push_back(6);
	//1,正向迭代器
	list<int>::iterator it = lt.begin();
	while (it != lt.end()) 
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	//反向迭代器
	list<int>::reverse_iterator rit = lt.rbegin();
	while (rit != lt.rend())
	{
		cout << *rit << " ";
		++rit;
	}
	cout << endl;

}
int main() 
{
	test2();
	return 0;
}

const正向、const反向迭代器的使用

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include <list>
//1,const正向迭代器
void Print_list(const list<int>& lt) //const对象在传参的时候会产生
{
	list<int>::const_iterator it = lt.begin();  // lt是 const 会去调用const的begin
	while (it != lt.end())
	{
		//*it = 1;  不能修改
		cout << *it << " ";
		++it;
	}
	cout << endl;
}
//2,const反向迭代器
void Print_reverse_list(const list<int>& lt) 
{
	list<int>::const_reverse_iterator rit = lt.rbegin();
	while (rit != lt.rend()) 
	{
		cout << *rit << " ";
		++rit;
	}
	cout << endl;
}
void test3() 
{
	list<int> lt;
	lt.push_back(10);
	lt.push_back(20);
	lt.push_back(30);
	lt.push_back(40);
	lt.push_back(50);
	lt.push_back(60);
	//1,const正向迭代器
	Print_list(lt);
	//2,const反向迭代器
	Print_reverse_list(lt);
}
int main() 
{
	test3();
	return 0;
}

3,operator= 赋值

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include <list>
void test4() 
{
	list<int> lt1;
	lt1.push_back(1);
	lt1.push_back(2);
	lt1.push_back(3);
	lt1.push_back(4);
	lt1.push_back(5);
	list<int> lt2;
	lt2.push_back(10);
	lt2.push_back(20);
	lt2.push_back(30);
	lt2.push_back(40);
	//赋值
	lt1 = lt2;
	//只要一个容器支持迭代器就可以支持范围for的操作
	for (auto e : lt1) 
	{
		cout << e << " ";
	}
	cout << endl;
}
int main() 
{
	test4();
	return 0;
}

4,insert、erase 任意位置的插入、删除

list不支持随机访问,vector支持,list空间都不连续,加的不是你想要的位置。

list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.push_back(5);
lt.push_back(6);
//4前插入 30
lt3.insert(lt3.begin() + 3, 30);  //这样是错误的写法

怎么写呢,需要用到算法里面的 find 

insert 任意位置的插入

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include <list>
#include <algorithm>
void test5()
{
	list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);
	lt.push_back(5);
	lt.push_back(6);
	//4 前插入 30
	//lt3.insert(lt3.begin() + 3, 30); //错误写法

	list<int>::iterator pos = find(lt.begin(), lt.end(), 4);
	//这里必须做一个判断,因为如果没有找到就不会去调用insert
	// 如果不做判断返回的是end的位置,由于没有找到值为 4 的元素,
	// pos会指向end(),直接插入就会在末尾插入新元素。
	if(pos != lt.end())  
	{
		lt.insert(pos, 30);
	}
	//打印
	for (auto e : lt) 
	{
		cout << e << " ";
	}
	cout << endl;
}

int main() 
{
	test5();
	return 0;
}

erase 任意位置的删除

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include <list>
#include <algorithm>
void test5()
{
	list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);
	lt.push_back(5);
	lt.push_back(6);
	list<int>::iterator pos = find(lt.begin(), lt.end(), 4);
	if(pos != lt.end())  
	{
		//为什么重新获取pos
		//因为迭代器已经失效了,erase会返回删除元素的下一个位置,所以需要重新获取
		pos = lt.erase(pos); 
	}
	//打印
	for (auto e : lt) 
	{
		cout << e << " ";
	}
	cout << endl;
}

int main() 
{
	test5();
	return 0;
}

5,迭代器失效(★)

情景一:

为什么 vector 报错了,而 list 不报错呢?

迭代器我们都想象成指针一样的东西。

在vector中,进行插入操作的时候,pos迭代器已经不是 3下标了,或者扩容,迭代器还指向旧地址空间,指针直接失效了。

在list中,insert 不会失效,因为这个链表,pos迭代器指向不受增容的影响。

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include <list>
#include <vector>
#include <algorithm>
void test6()
{
	list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);
	lt.push_back(5);
	lt.push_back(6);
	list<int>::iterator pos = find(lt.begin(), lt.end(), 4);
	if (pos != lt.end())
	{
		lt.insert(pos, 30); // 插入以后pos迭代器失效了吗?
		lt.erase(pos); //删除 4
	}
	//打印
	for (auto e : lt) 
	{
		cout << e << " ";
	}
	cout << endl;
}
void test7()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	v.push_back(6);
	vector<int>::iterator pos = find(v.begin(), v.end(), 4);
	if (pos != v.end())
	{
		v.insert(pos, 30); // 插入以后pos迭代器失效了吗?
		v.erase(pos); //删除 4
	}	
	//打印
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
}
int main() 
{
	test6();
	//test7();
	return 0;
}

图解如下: vector 和 list 的对比

如何解决:

 重新获取迭代器 pos

情景二:

删除所有的偶数,当 erase 之后,it 指向释放的空间,对野指针进行++或者解引用操作会导致程序崩溃。

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include <list>
#include <vector>
#include <algorithm>
void test8() 
{
	list<int> lt;
	lt.push_back(3);
	lt.push_back(2);
	lt.push_back(1);
	lt.push_back(5);
	lt.push_back(4);
	lt.push_back(6);
	list<int>::iterator it = lt.begin();
	while (it != lt.end())
	{
		if (*it % 2 == 0)
		{
			lt.erase(it); // it 变成了野指针,
			cout << *it << endl;  //失效了以后也不能进行解引用操作
		}
		++it;  //对野指针进行++
	}
}
int main() 
{
	test8();
	return 0;
}

如何解决:  

代码中,已经修复了迭代器失效问题,为什么不能直接输出呢

  1. 迭代器指向的不确定性:虽然erase返回了下一个有效的迭代器,但我们不能确定这个迭代器指向的元素一定是可以安全输出的。例如,如果在删除一个偶数元素后,下一个元素仍然是偶数,并且也满足删除条件,那么这个新的迭代器实际上指向的是即将被删除的元素。在这种情况下输出该元素的值是不安全的,属于未定义行为。
  2. 可能的迭代器失效场景:即使在当前情况下看起来似乎没有问题,但在更复杂的场景中,比如容器中存在其他并发操作或者其他条件可能导致迭代器在输出之前就已经失效。
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include <list>
#include <vector>
#include <algorithm>
void test8() 
{
	list<int> lt;
	lt.push_back(3);
	lt.push_back(2);
	lt.push_back(1);
	lt.push_back(5);
	lt.push_back(4);
	lt.push_back(6);
	list<int>::iterator it = lt.begin();
	while (it != lt.end())
	{
		if (*it % 2 == 0)
		{
			it = lt.erase(it); // erase会返回下一个位置
            //cout<<*it <<endl;  这里不能直接输出
		}
		else
		{
			++it;
		}
	}
	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;
}
int main() 
{
	test8();
	return 0;
}

小结:

对于 vector 而言,insert 和 erase 都会失效。

对于 list 而言,insert不会失效,erase 才会失效,erase之后空间被释放了指针还指向这块空间。失效了是指不能去取访问它,去访问失效迭代器才会有问题。


list的模拟实现

源码的引入

这段源码是主干部分,我们看完该源码是如何搭建的主干部分的,接下来我们就实现一个类似的。

其中有个注意的地方:

self& operator++(){
node =(link_type)((*node).next);  //node是void*类型,转成__list_node* 类型
return *this;
}

1,对node进行解引用拿到该对象(节点/结构体),对该对象进行 . 操作拿到里面的数据或者指针,然后再强转成 __list_node* 类型。

即:(*node).data,, node是节点的指针,解引用返回节点,.data 取节点里面的值

2, (link_type)((*node).next),需要得到下一个节点的指针也就是节点指向下一个位置。拿到该节点,返回类型是void*,  强转成节点类型 __list_node<T>*

在 list 中,原生指针的行为已经不是我们想要的了,我们可以用自定义类型去封装原生指针,通过运算符重载来构建我们想要的行为。

其它细节在接下来的实现中会一一叙述。


template<class T>
struct __list_node {
	typedef void* void_pointer;
	void_pointer next;
	void_pointer prev;
	T data;
};

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;

typedef __list_node<T>* link_type;
link_type node;
__list_iterator(link_type x): node(x){}


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;}
self& operator++(){
node =(link_type)((*node).next);
return *this;
}


self operator++(int) {
	self tmp = *this;
	++*this;
	return tmp;
}

self& operator--()
{
	node=(link type)((*node).prev);
	return *this;
}
self operator--(int){
	self tmp *this;
	--*this:
	return tmp;
}
};

template<class T,class Alloc=alloc>
class list
{
protected:
	typedef void* void_pointer;
	typedef __list_node<T> list_node;
	typedef list_node* link_type;
public:
	typedef T value_type;
public:
	typedef list_iterator<T, T&, T*>   iterator;
protected:
	link_type node;
}


1.基本结构的搭建

__list_node

源码是定义了void*类型,需要强转,这里我们直接这样定义,我们不需要强转类型。

struct __list_node  //对象<类型>
{
	__list_node<T>* _next;
	__list_node<T>* _prev;
	T _data;
};

 list

我们发现,在 list 初始化的时候,当我们 new 一个节点的时候需要进行初始化该节点,所以__list_node,需要进行初始化,即实现构造函数

	template<class T>
	class list
	{
		typedef __list_node<T> Node;   //给节点取别名为 Node
	public:
		//带头双向循环链表
		list()
		{
			_head = new Node;  //除了开空间,还会去调用Node的构造函数完成初始化
			_head->_next = _head;
			_head->_prev = _head;
		}
	private:
		Node* _head;  //一个指针用来初始化虚拟头结点
};

__list_node 的完善

template<class T>
struct __list_node  //对象<类型>
{
	__list_node<T>* _next;
	__list_node<T>* _prev;
	T _data;
	//Node类型的构造函数完成初始化
	__list_node(const T& x = T()) //初始化头结点可以给值,也可以给缺省
		:_data(x)
		, _next(nullptr)
		, _prev(nullptr)
	{}
};

push_back的实现

这是我们在数据结构链表所学的,一摸一样的,有节点和没有节点的情况下都是满足的。

	template<class T>
	class list
	{
		typedef __list_node<T> Node;
	public:
		//带头双向循环链表
		list()
		{
			_head = new Node;  //除了开空间,还会去调用Node的构造函数完成初始化
			_head->_next = _head;
			_head->_prev = _head;
		}
		void push_back(const T& x)
		{
			Node* tail = _head->_prev;
			Node* newnode = new Node(x);
			tail->_next = newnode;
			newnode->_prev = tail;
			newnode->_next = _head;
			_head->_prev = newnode;
		}
	private:
		Node* _head;
};

有了这些,我们需要打印链表插入的数据,需要用到迭代器。

__list_itertaor

template<class T>
struct __list_iterator
{
	typedef __list_node<T> Node;
	Node* _node; //核心还是一个指针
	__list_iterator(Node* node)   //构造函数用于初始化
		:_node(node)
	{}
	// *it
	T& operator*()
	{
		return _node->_data;
	}
    __list_iterator<T>& operator++()   //返回值还是一个迭代器
    {
	    _node = _node->_next;
	    return *this;
    }
    // it != end()
    bool operator!=(const __list_iterator<T>& it)
    {
	    return _node != it._node;
    }
};

有了迭代器,我们需要在 list 中使用 iterator,一个取begin(), 一个取end()

	template<class T>
	class list
	{
		typedef __list_node<T> Node;
	public:
		typedef __list_iterator<T> iterator;
		iterator begin()
		{
			return iterator(_head->_next); // __list_iterator(_head->_next)
		}
		iterator end()
		{
			return iterator(_head);
		}
		//带头双向循环链表
		list()
		{
			_head = new Node;  //除了开空间,还会去调用Node的构造函数完成初始化
			_head->_next = _head;
			_head->_prev = _head;
		}
		void push_back(const T& x)
		{
			Node* tail = _head->_prev;
			Node* newnode = new Node(x);
			tail->_next = newnode;
			newnode->_prev = tail;
			newnode->_next = _head;
			_head->_prev = newnode;
		}
	private:
		Node* _head;
};

基本结构的整体代码

list.h

#pragma once
namespace my_list 
{
	template<class T>
	struct __list_node  //对象<类型>
	{
		__list_node<T>* _next;
		__list_node<T>* _prev;
		T _data;
		//Node类型的构造函数完成初始化
		__list_node(const T& x = T()) //初始化头结点可以给值,也可以给缺省
			:_data(x)
			, _next(nullptr)
			, _prev(nullptr)
		{}
	};
	template<class T>
	struct __list_iterator
	{
		typedef __list_node<T> Node;
		Node* _node; //核心还是一个指针
		__list_iterator(Node* node)   //构造函数用于初始化
			:_node(node)
		{}
		// *it
		T& operator*()
		{
			return _node->_data;
		}
		__list_iterator<T>& operator++()   //返回值还是一个迭代器
		{
			_node = _node->_next;
			return *this;
		}
		// it != end()
		bool operator!=(const __list_iterator<T>& it)
		{
			return _node != it._node;
		}
	};
	template<class T>
	class list
	{
		typedef __list_node<T> Node;
	public:
		typedef __list_iterator<T> iterator;
		iterator begin()
		{
			return iterator(_head->_next); // __list_iterator(_head->_next),类似于vector(1);
		}
		iterator end()
		{
			return iterator(_head);
		}
		//带头双向循环链表
		list()
		{
			_head = new Node;  //除了开空间,还会去调用Node的构造函数完成初始化
			_head->_next = _head;
			_head->_prev = _head;
		}
		void push_back(const T& x)
		{
			Node* tail = _head->_prev;
			Node* newnode = new Node(x);
			tail->_next = newnode;
			newnode->_prev = tail;
			newnode->_next = _head;
			_head->_prev = newnode;
		}
	private:
		Node* _head;
	};
	void test_list()
	{
		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())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;
	}
}

test.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include "list.h"
int main() 
{
	my_list::test_list();
	return 0;
}

迭代器是如何执行起来的

2.迭代器的进一步理解

源码中的 typedef __list_iterator<T, T&, T*> iterator;

iterator就是__list_iterator<T, T&, T*> ,因为 list 迭代器不像 vector 空间是连续的,所以不能构建指针,只能去自定义一个类去实现该迭代器。

int* p1 = new int;    *p1 拿到了 int

Date* p2 = new Date;  *p2;拿到这个对象,(*p2)._year或者 p2->_year,拿到里面的数据。

//定义一个简单的日期类
struct Date
{
	int _year = 0;
	int _month = 1;
	int _day = 1;
};
void test_list()
{
	list<Date> lt;
	lt.push_back(Date());
	lt.push_back(Date());
	list<Date>::iterator it = lt.begin();
	while (it != lt.end())
	{    //Date是别人写的,就没写输出,如何应对?
		//cout << *it << " "; //解引用出来是Date自定义类型,不支持输出,没有重载operator输出
		cout << it->_year << "-" << it->_month << "-" << it->_day << endl; //我们没有重载operator->,所以需要去实现operator->
		++it;
	}
	cout << endl;
}

还可以这样输出,先拿到 it 指向的对象 Date,再去访问该对象里面的数据

cout << (*it)._year << "-" << (*it)._month << "-" << (*it)._day << endl;

3.const 迭代器的实现

以下代码在print_list中,lt 是 const,调用begin的时候,只能调 const 的 begin,所以给begin 加 const。

下面的代码看似没有问题,实际呢,遍历这个迭代器的时候还把我改了,这个迭代器不合理啊

const对象去调用const 返回的是普通迭代器,普通迭代器是可读可写的。

void print_list(const list<int>& lt)
{
	list<int>::iterator it = lt.begin(); //const &lt 传给 list* this 加const=> const list * this
	while (it != lt.end())
	{
		*it = 1;
		cout << *it << " ";
		++it;
	}
	cout << endl;
}
void test_list()
{
	list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);
	print_list(lt);
}

const迭代器的实现

const迭代器的唯一需求只有一个,可以遍历,可以读,可以加加,但是不能改写迭代器指向的数据/内容,不是迭代器本身不能修改,而是迭代器指向的内容不能修改。

迭代器指向的内容是通过 * 和 -> 来修改的.

*it 和 it->怎么实现呢??

把整个迭代器类拷贝出来,改两个地方,* 和 -> 的返回类型变成const T& 和 const T*

把迭代器拷贝一份, 把类名改成__list_ const _iterator,并给operator* 和operator->,加上const

但是仔细想想,当我们拷贝完之后就改两个地方,其它都一样,这样整体代码重复率就很高,代码显得很挫啊。所以我们别这样搞,我们这里可以增加两个模板参数来处理这种问题。

把迭代器的模版参数修改成:template<class T, class Ref, class Ptr>, 因为迭代器只需修改两个地方,我们就增加两个参数,一个是接收引用,一个是接收指针。

这模板参数部分开始第一次接触看来很复杂,其实很简单,就是对着图里的这个步骤走下,就很好理解了。

#pragma once
namespace my_list 
{
	template<class T>
	struct __list_node  //对象<类型>
	{
		__list_node<T>* _next;
		__list_node<T>* _prev;
		T _data;
		//Node类型的构造函数完成初始化
		__list_node(const T& x = T()) //初始化头结点可以给值,也可以给缺省
			:_data(x)
			, _next(nullptr)
			, _prev(nullptr)
		{}
	};
	template<class T, class Ref, class Ptr>
	struct __list_iterator
	{
		typedef __list_node<T> Node;
		Node* _node; //核心还是一个指针
		__list_iterator(Node* node)   //构造函数用于初始化
			:_node(node)
		{}
		// *it
		Ref operator*()
		{
			return _node->_data;
		}
		//operator->
		Ptr operator->()
		{
			return &_node->_data;
		}
		__list_iterator<T, Ref, Ptr>&operator++()   //返回值还是一个迭代器,里面模板参数也变成了三个
		{
			_node = _node->_next;
			return *this;
		}
		// it != end()
		bool operator!=(const __list_iterator<T, Ref, Ptr>& it)
		{
			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 iterator(_head->_next); 
		}
		iterator end() 
		{
			return iterator(_head);
		}
		const_iterator begin() const
		{
			return const_iterator(_head->_next);
		}
		const_iterator end() const
		{
			return const_iterator(_head);
		}
		//带头双向循环链表
		list()
		{
			_head = new Node;  //除了开空间,还会去调用Node的构造函数完成初始化
			_head->_next = _head;
			_head->_prev = _head;
		}
		void push_back(const T& x)
		{
			Node* tail = _head->_prev;
			Node* newnode = new Node(x);
			tail->_next = newnode;
			newnode->_prev = tail;
			newnode->_next = _head;
			_head->_prev = newnode;
		}
	private:
		Node* _head;
	};
	void print_list(const list<int>& lt)
	{
		list<int>::const_iterator it = lt.begin(); //const &lt 传给 list* this 加const=> const list * this
		while (it != lt.end())
		{
			//*it = 1;  现在就不能修改了
			cout << *it << " ";
			++it;
		}
		cout << endl;
	}
	void test_list()
	{
		list<int> lt;
		lt.push_back(1);
		lt.push_back(2);
		lt.push_back(3);
		lt.push_back(4);
		print_list(lt);
	}
}

 补充一点:

类型决定了对空间的解释权

假设有 Node* cur,  iterator it

当他们都指向同一个节点,那么在物理内存中他们都存的是这个节点地址,物理上是一样的
但是他们的类型不一样,他们的意义就不一样。
比如*cur是一个指针的解引用,取到的值是节点。
比如 *it 是去调用迭代器的operator*,返回值是节点中存的值。


4.insert 和 erase 的实现

接下来的的实现都很简单,因为都是数据结构所学的。

insert的实现

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

erase的实现

void erase(iterator pos) 
{
	assert(pos != end());
	Node* cur = pos._node;
	Node* prev = cur->_prev;
	Node* next = cur->_next;
	delete cur;
	prev->_next = next;
	next->_prev = prev;
}

5. pop_back、push_front、pop_front 的实现

复用上面的代码

	void pop_back() 
	{
		//erase(iterator(_head->_prev));
		erase(--end());
	}
	void push_front(const T& x) 
	{
		insert(begin(), x);
	}
	void pop_front() 
	{
		erase(begin());
	}

6.拷贝构造

拷贝构造也是构造函数的一种,用来初始化,需要先创建一个新的头节点,然后把数据全尾插到该对象中,在list中可直接使用迭代器,在里面可以直接使用, 不需要指定哪个类域和加类域符号,即 list<int>:: 。

	//拷贝构造
	list(const list<T>& lt) 
	{
		_head = new Node;
		_head->_next = _head;
		_head->_prev = _head;
		//const_iterator it = lt.begin();
		//while (it != lt.end())
		//{
		//	//this->push_back(*it);
		//	push_back(*it);
		//	++it;
		//}
		for (auto e : lt) 
		{
			push_back(e);
		}
	}

7.析构函数

clear 用来清除所有的数据,释放所有的节点,但是不处理头节点。

		void clear() 
		{
			iterator it = begin();
			while (it != end()) 
			{
				erase(it++);
			}
		}
		~list() 
		{
			clear();
			delete _head;
			_head = nullptr;
		}

8.operator= 赋值

传统写法中,先清空所有的数据,然后把赋值过来的数据全部尾插到该对象中。

现代写法中,传参的时候就,直接拷贝构造一份一摸一样的链表,拷贝构造是深拷贝(已经实现了),然后把该对象和这个拷贝出来的两者头结点一交换,就达到了目的。

//operator= 赋值
传统写法
//list<T>& operator=(const list<T>& lt) 
//{
//	if (this != &lt) 
//	{
//		clear(); //清空所以的数据
//		for (auto e : lt)
//		{
//			push_back(e);
//		}
//	}
//	return *this;
//}
//赋值-现代写法
list<T>& operator=(list<T> lt)
{
	//clear();  不加也可以
	swap(_head, lt._head);
	return *this;
}

9.深浅拷贝问题

和vector 深浅拷贝问题都是一样的,我们需要手动实现,完成深拷贝。operator= 也是同理。


10.整体代码

list.h

#pragma once
#include <assert.h>
namespace my_list 
{
	template<class T>
	struct __list_node  //对象<类型>
	{
		__list_node<T>* _next;
		__list_node<T>* _prev;
		T _data;
		//Node类型的构造函数完成初始化
		__list_node(const T& x = T()) //初始化头结点可以给值,也可以给缺省
			:_data(x)
			,_next(nullptr)
			,_prev(nullptr)
		{}
	};
	//迭代器
	// __list_iterator<T, T&, T*> --> iterator;
	// __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)
		{}
		// *it
		Ref operator*()
		{
			return _node->_data;
		}
		//operator->
		Ptr operator->()
		{
			return &_node->_data;
		}
		//++it
		Self& operator++()
		{
			_node = _node->_next;
			return *this;
		}
		//it++ 后置
		Self operator++(int)
		{
			Self tmp(*this);
			//_node = _node->_next;
			++(*this);
			return tmp;
		}
		//--it
		Self& operator--()
		{
			_node = _node->_prev;
			return *this;
		}
		Self operator--(int)
		{
			Self tmp(*this);
			//_node = _node->_prev;
			--(*this);
			return *this;
		}
		// it != end()
		bool operator!=(const Self& it)
		{
			return _node != it._node;
		}
		// it ==
		bool operator==(const Self& it)
		{
			return _node == it._node;
		}
	};
	template<class T>
	class list 
	{
		typedef __list_node<T> Node;
	public:
		typedef __list_iterator<T, T&, T*> iterator;
		//const 迭代器
		typedef __list_iterator<T, const T&, const T*> const_iterator;
		iterator begin() 
		{
			return iterator(_head->_next); // __list_iterator(_head->_next),类似于vector(1);
		}
		iterator end() 
		{
			return iterator(_head);
		}
		const_iterator begin() const 
		{
			return const_iterator(_head->_next);
		}
		const_iterator end() const
		{
			return const_iterator(_head);
		}
		//带头双向循环链表
		list() 
		{
			_head = new Node;  //除了开空间,还会去调用Node的构造函数完成初始化
			_head->_next = _head;
			_head->_prev = _head;
		}
		//拷贝构造
		list(const list<T>& lt) 
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
			//const_iterator it = lt.begin();
			//while (it != lt.end())
			//{
			//	//this->push_back(*it);
			//	push_back(*it);
			//	++it;
			//}
			for (auto e : lt) 
			{
				push_back(e);
			}
		}
		//operator= 赋值
		//list<T>& operator=(const list<T>& lt) 
		//{
		//	if (this != &lt) 
		//	{
		//		clear(); //清空所以的数据
		//		for (auto e : lt)
		//		{
		//			push_back(e);
		//		}
		//	}
		//	return *this;
		//}
		//赋值-现代写法
		list<T>& operator=(list<T> lt)
		{
			//clear();  不加也可以
			swap(_head, lt._head);
			return *this;
		}
		//析构函数
		void clear() 
		{
			iterator it = begin();
			while (it != end()) 
			{
				erase(it++);
			}
		}
		~list() 
		{
			clear();
			delete _head;
			_head = nullptr;
		}
		void push_back(const T& x) 
		{
			/*Node* tail = _head->_prev;
			Node* newnode = new Node(x);
			tail->_next = newnode;
			newnode->_prev = tail;
			newnode->_next = _head;
			_head->_prev = newnode;*/
			insert(end(), x);
		}
		void pop_back() 
		{
			//erase(iterator(_head->_prev));
			erase(--end());
		}
		void push_front(const T& x) 
		{
			insert(begin(), x);
		}
		void pop_front() 
		{
			erase(begin());
		}
		void insert(iterator pos, const T& x) 
		{
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* newnode = new Node(x);
			// prev newnode cur
			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;
		}
		void erase(iterator pos) 
		{
			assert(pos != end());
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* next = cur->_next;
			delete cur;
			prev->_next = next;
			next->_prev = prev;
		}
	private:
		Node* _head; //虚拟头结点dummyHead
	};

	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())    //调用的是 operator!=()
		{
			cout << *it << "->";
			++it;
		}
		cout << "nullptr" << endl;
	}

	//定义一个日期类
	struct Date
	{
		int _year = 0;
		int _month = 1;
		int _day = 1;
	};
	void test_list2() 
	{
		list<Date> lt;
		lt.push_back(Date());
		lt.push_back(Date());
		list<Date>::iterator it = lt.begin();
		while (it != lt.end())
		{    //Date是别人写的,就没写输出,如何应对?
			//cout << *it << " "; //解引用出来是Date自定义类型,不支持输出,没有重载operator输出
			//(*it)._year 这样也可以
			cout << it->_year << "-" << it->_month << "-" << it->_day << endl;
			cout << (*it)._year << "-" << (*it)._month << "-" << (*it)._day << endl;
			++it;              
		}
		cout << endl;
	}

	void print_list(const list<int>& lt) 
	{
		list<int>::const_iterator it = lt.begin(); //const &lt 传给 list* this 加const-> const list * this
		while (it != lt.end()) 
		{
			//*it = 1;
			cout << *it << " ";
			++it;
		}
		cout << endl;
	}
	void test_list3() 
	{
		list<int> lt;
		lt.push_back(1);
		lt.push_back(2);
		lt.push_back(3);
		lt.push_back(4);
		lt.push_back(5);
		lt.push_back(6);
		lt.pop_back();
		lt.pop_front();
		print_list(lt);
	}
	//拷贝构造
	void test_list4() 
	{
		list<int> lt1;
		lt1.push_back(1);
		lt1.push_back(2);
		lt1.push_back(3);
		lt1.push_back(4);
		list<int> lt2(lt1);
		print_list(lt2);
	}
}

test.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include "list.h"
int main() 
{
	/*my_list::test_list1();
	my_list::test_list2();*/
	//my_list::test_list3();
	my_list::test_list4();
	return 0;
}

常见面试题

1,vector 和 list的区别?

vector是一个可动态增长的数组

优点:随机访问 operator[] ->很好的支持排序、二分查找、堆算法的等等

缺点:头部或者中间的插入删除效率低 +空间不够了以后增容的代价较大

list是一个带头双向循环的链表

优点:任意位置插入删除数据效率高,0(1)

缺点:不支持随机访问

总结:vector和list是两个相辅相成,互补的容器

2,vector 和 list 底层是如何实现的 ?

vector 可随机访问,当vector插入数据的时候,需要挪动数据,空间不够的时候,开新的空间,拷贝数据,释放旧空间,这个过程代价有点大,list 不支持随机访问,需要遍历整个链表找到某个值。节点的内存不是连续分配的,是随机分配的,链表插入数据,不需要挪动数据,不需进行大量的内存赋值和重新分配操作,每个节点需要单独分配空间,节点被插入或者删除的时候,效率较高 O(1)

3,vector 是如何增容的?

容量不够的时候,开新空间,拷贝数据,释放旧空间。

4,什么是迭代器失效?

迭代器失效就是在对容器进行特定操作(如插入、删除元素等)后,原来能用的迭代器可能就不能用了。

vector中的迭代器,当vector需要增容的时候,开了新空间,迭代器还指向旧空间,迭代器便失效了

list 中进行了erase操作,去使用该迭代器的时候,此时,操作该迭代器就相对于操作野指针,因为该空间已经释放了。

标签:node,iterator,STL,list,back,C++,lt,push
From: https://blog.csdn.net/m0_63207201/article/details/141801426

相关文章

  • C++内存管理
    感谢观看!!!文章目录一、C/C++内存分布二、C语言中动态内存管理方式三.C++中动态内存管理四.operatornew与operatordelete函数五.new和delete的实现原理六.定位new表达式(placement-new)七.常见面试题一.C/C++内存分布 我们先来看下面的一段代码和相关问题 ......
  • ArrayList
    ArrayList目录ArrayList1ArrayList1.1ArrayList的构造方法和添加方法1.2ArrayList存储字符串并遍历1.3ArrayList存储学生对象并遍历2综合练习2.1学生管理系统实现步骤2.2学生类的定义2.3ArrayList02类的定义1ArrayList集合和数组的区别:​ 共同点:都是存储数据的......
  • 【新】如何编写一个C++程序来整蛊你的好基友?
    【新版】如何编写一个C++程序来整蛊你的好基友呢?如何编写一个C++程序来整蛊你的好基友整蛊按照危险性来排序3星类1.一直输出,换行2.一直输出,不换行3.给控制台换一个颜色(较有威慑力)颜色代码4.扫盘(配上第三个效果更好,可以用来装B)4星类(含部分解药)弹窗类弹窗代码按下反......
  • Stream List转Map
    需要注意的是:toMap如果集合对象有重复的key,会报错Duplicatekey....如:Student,Student1的id都为1002。可以用(k1,k2)->k1来设置,如果有重复的key,则保留key1,舍弃key2Map<Integer,Student>map=appleList.stream().collect(Collectors.toMap(Student::getId,a->a,(k1,k......
  • C# TcpClient bind,listen,accept,send receive
    //serverusingSystem;usingSystem.Net;usingSystem.Net.Sockets;usingSystem.Text;namespaceConsoleApp63{internalclassProgram{staticvoidMain(string[]args){intport=11000;TcpServerserver......
  • 使用C++,仿照string类,实现myString
    类由结构体演化而来,只需要将struct改成关键字class,就定义了一个类C++中类和结构体的区别:默认的权限不同,结构体中默认权限为public,类中默认权限为private默认的继承方式不同,结构体的默认继承方式为public,类的默认继承方式为private//定义格式class类名{public:......
  • 使用C++手动封装一个顺序表,包含成员数组一个,成员变量N个
    实现顺序表的判空,判满,添加数据,求实际长度,任意位置的插入/删除,访问数组中的任意一个元素,以及让顺序表自动扩容。首先需要实现一个顺序表需要使用结构体构造其基本组成部分,以及基本函数接口,采用内部声明外部定义的方式。//使用C++手动封装一个顺序表,包含成员数组一个,成员变量N......
  • 使用C++编写程序,提示并输入一个字符串,统计其中的英文字符,数字,空格以及其他字符的数量
    由于c++兼容c语言的程序,所以子函数使用了c语言的内容#include<iostream>#include<string.h>usingnamespacestd;voidCount(constcharstr[]){intletter=0,num=0,space=0,etc=0;while(*str!='\0'){if((*str>='a'&&*......
  • c++vscode多文件实现通讯录管理系统
    c++vscode多文件实现通讯录管理系统作为c++入门级别的实战项目,此通讯管理系统项目不仅仅是对c++入门阶段学习成果的检验,也是对c++基础知识的回顾,体会c++在实战制作中的思路,是入门c++单文件实现通讯录系统的改进一、多文件通讯录管理系统简介系统需求通讯录是一个可......
  • JAVA List<Map<String, Object>> sort 多个排序写法
     基本方法/***排序=**@paramlist*@paramsort_key*@return*/publicstaticList<Map<String,Object>>sort(List<Map<String,Object>>list,Stringsort_key,Booleanasc,Stringsort_key2,Boole......