list讲解及模拟实现

接口分析

list底层是双向带头循环链表。头节点不保存有效数据。

list的遍历方法中就没有方括号了,只能使用迭代器。

没有reserve接口了,因为链表是按需申请的。

remove相当于find+erase,如果这个元素不存在就不会做任何操作,也不会报错。

sort默认是从小到大进行排序,为什么不用算法库里的sort?因为list的迭代器与算法库里给的迭代器类型不同,算法库里sort需要的是随机迭代器,但是list只是双向迭代器。【迭代器按照功能分类:单向迭代器++,比如单向链表和哈希;双向迭代器++、–,比如list,底层用归并排序;随机迭代器++、–、+、-,比如vector和string,底层可以用三数取中法快排】

unique去重,要求list必须要先有序。

底层实现

迭代器

版本1 一个模板参数

【iterator实际是内置类型,行为像指针的东西】

list要实现迭代器,就要有*++以及!=的功能,即要实现运算符重载成员函数,因此要封装一个类去实现这个功能。begin就是head的next,end就是head。

//封装一个迭代器类 以实现*、++和!=的功能
template<class T>
struct _list_iterator
{
	typedef list_node<T> node;
	node* _pnode;//成员变量

	_list_iterator(node* pn)
		:_pnode(pn)
	{}

	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
{
	typedef list_node<T> node;
		
public:
	typedef _list_iterator<T> iterator;//在链表类中重命名 为迭代器
	//迭代器
	iterator begin()
	{
		return iterator(_head->_next);//用匿名对象返回
	}
	iterator end()
	{
			return iterator(_head);//用匿名对象返回
	}
	//...
};

以下代码测试均能通过

list<int>::iterator it = l1.begin();
while (it != l1.end())
{
	cout << *it << " ";
	++it;
}
cout << endl;
for (auto e : l1)
{
	cout << e << " ";
}

以上这个写法如果要加上const版本迭代器,则需要再写一个类封装,即两个类一样,只不过*运算符重载不一样。

//普通迭代器
template<class T>
struct _list_iterator
{
	typedef list_node<T> node;
	node* _pnode;//成员变量

	_list_iterator(node* pn)
		:_pnode(pn)
	{}

	T& operator*()//与模板const迭代器的唯一区别
	{
		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* pn)
		:_pnode(pn)
	{}

	const T& operator*()//与普通版本的iterator的区别 就是返回的是const T&
	{
		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
{
public:
	typedef _list_iterator<T> iterator;//普通迭代器
	typedef _list_const_iterator<T> const_iterator;//const迭代器
}
版本2 两个模板参数 新增->重载以及后置++、–

大佬写的,给模板多传了参数,能实现同一个类模板实例化出两个类型,同时实现普通迭代器和const迭代器

template<class T, class Ref>//Ref可以是T&或const T&
struct _list_iterator
{
	typedef list_node<T> node;
	typedef _list_iterator<T, Ref> self;
	node* _pnode;//成员变量

	_list_iterator(node* pn)
		:_pnode(pn)
	{}

	Ref operator*()
	{
		return _pnode->_data;
	}
    
    T* operator->()//这个写法无法实现const版本
    {
        return &_pnode->_data;//相当于&(_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)
    {
    	return _pnode != it._pnode;
    }
};
---------------------------
template<class T>
class list
{		
public:
	typedef _list_iterator<T, T&> iterator;//普通迭代器
	typedef _list_iterator<T, const T&> const_iterator;//const迭代器
}
版本3 三个模板参数
template<class T, class Ref, class Ptr>//Ref可以是T&或const T&
struct _list_iterator
{
	typedef list_node<T> node;
	typedef _list_iterator<T, Ref> self;
	node* _pnode;//成员变量
    
    	_list_iterator(node* pn)
		:_pnode(pn)
	{}

	Ref operator*()
	{
		return _pnode->_data;
	}
    
    Ptr operator->()//多传一个参数以实现const版本
    {
        return &_pnode->_data;//相当于&(_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)
    {
    	return _pnode != it._pnode;
    }
};
----------------------------
template<class T>
class list
{		
public:
	typedef _list_iterator<T, T&, T*> iterator;//普通迭代器
	typedef _list_iterator<T, const T&, const T*> const_iterator;//const迭代器
}
实现运算符->重载 普通/const

如下应用场景

struct Pos
{
	int _row;
    int _col;

	Pos()
    	:_row(0)
        ,_col(0)
    {}

	Pos(int row, int col)
    	:_row(row)
        ,_col(col)
	{}
};
void test3()
{
	list<Pos> lt;//链表里存坐标
    Pos p1(1, 1);
    lt.push_back(p1);
    lt.push_back(Pos(1, 2));
    lt.push_back(Pos(1, 3));
    lt.push_back(Pos(1, 4));

	list<Pos>::iterator it = lt.begin();
	while (it != lt.end())
	{
		//重载了操作符->后,相当于it->->_row
		cout << it->_row << ", " << it->_col << " | ";
		cout << it.operator->()->_row << ", " << it.operator->()->_col << " | ";
		cout << (*it)._row << ", " << (*it)._col << " | ";
		cout << (&(*it))->_row << ", " << (&(*it))->_col << " | ";
		++it;
	}
	cout << endl;
}

it->相当于it.operator->(),我们得到的返回值是Pos* tmpit->_row本来是it->->_row【因为编译器为了可读性,做了特殊处理,省略了一个->】,就是tmp->_row

不允许的写法it->->_row,干扰了编译器识别;

允许的写法it->_row it.operator->()->_row

insert

在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;

	return iterator(newnode);
}

erase

删除该节点数据

iterator erase(iterator pos)
{
	assert(pos != end());
	node* cur = pos._pnode;
	node* prev = cur->_prev;
	node* next = cur->_next;
	//链接prev和next
	prev->_next = next;
	next->_prev = prev;
	
	delete cur;
	return iterator(next);
}

clear 和 析构函数

clear是删除所有有效数据节点,但保留头节点;而析构函数会把数据节点和头节点一起删除

~list()
{
	clear();

	delete _head;
	_head = nullptr;
}

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

拷贝构造函数

要用深拷贝

//拷贝构造函数--传统写法
list(const list<T>& lt)//建议写成上面用类型的
//list(const list& lt)//stl库给的,因为在类中可以用类名表示类型
{
	empty_init();

	for (auto e : lt)
	{
		push_back(e);
	}
}
--------------------------------
//现代写法需要的辅助函数封装
void swap(list<T>& lt)
{
	std::swap(_head, lt._head);
}

template<class InputIterarot>
list(InputIterarot first, InputIterarot last)
{
	empty_init();

	while (first != last)
	{
		push_back(*first);
		++first;
	}
}
//拷贝构造函数--现代写法
list(const list<T>& lt)
{
	//给this一个初始值,避免随机值,导致tmp析构出问题
	empty_init();
	//迭代器区间初始化
	list<T> tmp(lt.begin(), lt.end());
	//交换,出了函数tmp就会销毁
	swap(tmp);
}

复制构造函数

//复制构造函数--传统写法
list<T>& operator=(const list<T>& lt)//建议写成上面用类型的
//list& operator=(const list& lt)//stl库给的,因为在类中可以用类名表示类型
{
	//判断是否为自己给自己赋值
	if (this != &lt)
	{
		clear();
		for (const auto& e : lt)
		{
			push_back(e);
		}
	}
	return *this;
}
---------------------------------------
//复制构造函数--现代写法
list<T>& operator=(const list<T> lt)//注意这个不要加引用,这样得到的lt是经过复制构造后的
{
	//判断是否为自己给自己赋值
	swap(lt);
	return *this;
}

严格区分类模板的类型和类名

类模板中类型不等于类名,普通类中类型就是类名。

比如list是类名,list才是类型。构造函数是特殊的成员函数,函数名与类名相同。

有两个例外:拷贝构造list(const list& x);以及运算符重载list& operator(const list& x);我们知道函数的参数和返回值应该写 类型,参数应该写成list<T>,这两个为什么不是这样呢?

答:因为官方库认为在类模板内可以用类名表示类型,但这个写法不好。建议规范用类型list(const list<T>& lt);list<T>& operator=(const list<T>& lt);

vector和list对比

vector cpu缓存命中率高。

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

注意,string的迭代器也会失效,和vector类似,不过一般不关心string的失效,因为它的接口设计用的都是pos下标,不是迭代器。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值