C++语言基础——list

目录

1. list

2. 构造函数

3. list的容量

4. list的增删

5. list的迭代器

5.1 迭代器的访问

5.2 迭代器失效

6. list的模拟实现

7. vector和list的对比

1. list

 对于list,它也是一个容器,但list的底层就是双向链表结构,list我们按照前面学习的带头节点双向循环链表。

对于list的节点有三个成员变量,prev指针:指向前一个结点,即前驱结点;next指针:指向后一个结点,即后继结点;val:结点的数据。list同样存储相同类型的元素。同样我们要给list具体的类型。

list自己只有一个head节点成员。

C++语言基础——list_第1张图片

2. 构造函数

list构造函数
构造函数名称 接口说明
list() 构造空的list
list (size_type n, const value_type& val = value_type()) 构造的list中包含n个值为val的元素
list (const list& x) 拷贝构造函数
list (InputIterator fifirst, InputIterator last) 用[fifirst, last)区间中的元素构造list
	list s1;//构造空的list
	list s2(10, 6);//构造的list中包含10个值为6的元素
	list s3(s2);//拷贝构造
	list s4(s2.begin(), s2.end());//根据迭代器构造

3. list的容量

lsit是一个个结点组成,每插入一个结点,就开辟一个结点的空间。所以list不存在扩容这种问题。所以list只有size和empty两个和容量有关的接口。

容量问题
函数名称 接口说明
bool empty() const 检测list是否为空,是返回true,否则返回false
size_t size() const 返回list中有效节点的个数
	list s1(10, 6);

	cout << s1.size() << endl;//有效元素的个数

	cout << s1.empty() << endl;//链表是否为空

4. list的增删

list的修改
接口名称 接口说明
void push_front (const value_type& val) 在list首元素前插入值为val的元素
void pop_front() 删除list中第一个元素
void push_back (const value_type& val) 在list尾部插入值为val的元素
void pop_back() 删除list中最后一个元素
iterator insert (iterator position, const value_type& val) 在list position 位置中插入值为val的元素
iterator erase (iterator position) 删除list position位置的元素
	list s1;
	//list的尾插
	s1.push_back(1);
	s1.push_back(2);

	//list的尾删
	s1.pop_back();
	s1.pop_back();

	//list任意位置插入
	s1.insert(s1.begin(), 4);//相当于头插

        //list的头插尾插和任意位置的删除相同做法。
        //insert()的任意位置要通过++操作来改变,不能直接;
        //如第五个位置 
        list::iterator cur = s1.begin();
        int i = 5;
        while(i--)
        {
            ++cur;
        }

5. list的迭代器

list是结点组成的,结点与结点之间是通过指针进行连接。简单地说那就是list的存储不是连续的,这会引来一个问题,不能随机访问。也就是说我们要访问某个结点要通过迭代器。这个迭代器里封装的是结点。此处大家可将迭代器暂时理解成类似于指针

迭代器的使用
函数声明 接口说明
begin() 返回第一个元素的迭代器
end() 返回最后一个元素下一个位置的迭代器

5.1 迭代器的访问

上述的接口只能访问第一个结点和头节点。为了访问任意结点我们要配上循环。注意:迭代器不能直接加数字来达到访问。

(1) iterator tmp = list_.begin() + 5;------------------>这是错的。
(2) iterator tmp = list_.begin();
while(5) { // 这里代表五次循环,不是真正的循环语句,只是简单书写
     ++tmp; // 因为在底层的时候,++表示 tmp = tmp->next;这就是为什么不能直接加数字。
}

5.2 迭代器失效

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

	list s1;
	s1.push_back(1);
	s1.push_back(2);
	s1.push_back(3);
	s1.push_back(4);
	cout << "push_back:";
	PrintList(s1);

	//list::iterator lit = s1.begin();//后面的删除使迭代器失效。
	s1.erase(s1.begin());
	list::iterator lit = s1.begin();//重新获取
        //或者  list::iterator lit = s1.erase(s1.begin());
	cout << *lit << endl;

6. list的模拟实现

//对比数据结构的带头双向循环链表
#pragma once
#include
using namespace std;
//结点类
//list的结点有三个成员:前驱,后继,数据
template
class ListNode
{
public:
	ListNode(const T val = T())
	:val_(val),
		prev_(nullptr),
		next_(nullptr)
	{}

	ListNode* prev_;
	ListNode* next_;
	T val_;
};


//list不能直接用[]访问,所以我们必须实现它的迭代器实现访问。
//迭代器类--->原生指针++运算,必须要是连续存储的的。但是list不是连续的。所以要自己封装迭代器
//迭代器封装的是list的结点,但本质还是指针,所以我们要实现指针能用的所有操作++,->,*等。

template //迭代器我们还是要用模板来实现
class ListIterator
{
public:
	typedef ListNode* PNode;
	typedef ListIterator Lit;
	//迭代器的构造
	ListIterator(PNode pnode = nullptr)
		:pnode_(pnode)
	{}
	//迭代器的拷贝构造
	ListIterator(const Lit& lit)
		:pnode_(lit.pnode_)
	{
		//PNode tmpsnode_(lit.pnode_);
	}
	//迭代器的运算符
		//解引用:取的是指针指向该节点的数据
	Ref operator *()
	{
		return pnode_->val_;
	}
		//指针:->
	Ptr operator ->()
	{
		return &(pnode_->val_);
	}
	//判断
	bool operator !=(const Lit& lit)
	{
		return pnode_ != lit.pnode_;
	}
	//地址是否相同
	bool operator ==(const Lit& lit)
	{
		return !(*this != lit);
	}
	//++操作。返回*this也可以,++迭代器的返回还是迭代器的本身
	Lit& operator++()
	{
		pnode_ = pnode_->next_;
		return *this;
	}
	Lit operator++(int)
	{
		Lit tmp(*this);
		pnode_ = pnode_->next_;
		return tmp;
	}
	PNode pnode_;//ListNode*
};

//前面的基本工具有了,现在来实现list类
//list类-----模板
template 
class List
{
public:
	//结点
	typedef ListNode Node;
	typedef ListNode* PNode;
	//迭代器
	typedef ListIterator iterator;//可读可写
	typedef ListIterator const_iterator;//只读
	//构造函数---空的list
	List()
	{
		Create();
	}
	//构造函数----n个val的list
	List(int n, const T& val = T())
	{
		Create();
		for (int i = 0; i < n; ++i)
		{
			PushBack(val);
		}
	}
	//拷贝构造函数
	List(const List& list)
	{
		Create();
		List tmp(list.cbegin(),list.cend());
		Swap(tmp);
	}
	//通过迭代器的构造
	template 
	List(Iterator firdt, Iterator end)
	{
		Create();
		while (firdt != end)
		{
			PushBack(*firdt);
			++firdt;
		}
	}
	/*List(const_iterator  firdt, const_iterator  end)
	{
		Create();
		while (firdt != end)
		{
			PushBack(*firdt);
			++firdt;
		}
	}*/

	T& Front()
	{
		return head_->next_->val_;
	}

	T& Back()
	{
		return head_->prev_->val_;
	}
	const T& Front() const
	{
		return head_->next_->val_;
	}

	const T& Back() const
	{
		return head_->prev_->val_;
	}

	void PushBack(const T& val)
	{
		PNode node = new Node(val);
		node->next_ = head_;//插入的结点的后继指向头结点
		node->prev_ = head_->prev_;//插入结点的前驱指向原本头结点的前驱
		head_->prev_->next_ = node;//原本头节点的前驱的后继指向新结点
		head_->prev_ = node;//头节点的前驱指向新结点

		//Insert(end(), val);
	}
	void PopBack()
	{
		head_->prev_->prev_->next_ = head_;
		head_->prev_ = head_->prev_->prev_;

		//Erase(end());
	}
	void PushFront(const T& val)
	{
		PNode node = new Node(val);
		node->next_ = head_->next_;
		node->prev_ = head_;
		head_->next_->prev_ = node;
		head_->next_ = node;
		//Insert(begin(),val);有了insert()可以直接
	}
	void PopFront()
	{
		head_->next_->next_->prev_ = head_;
		head_->next_ = head_->next_->next_;
		//Erase(begin())
	}
	iterator Insert(iterator pos, const T& val)
	{
		PNode node = new Node(val);
		node->next_ = pos.pnode_;
		node->prev_ = pos.pnode_->prev_;
		pos.pnode_->prev_ ->next_= node;
		pos.pnode_->prev_ = node;
		return iterator(node);
	}
	iterator Erase(iterator pos)
	{
		if (pos != end())
		{
			PNode savenode = pos.pnode_->next_;
			pos.pnode_->prev_->next_ = pos.pnode_->next_;
			pos.pnode_->next_->prev_ = pos.pnode_->prev_;
			delete pos.pnode_;
    			return iterator(savenode);
		}
		return pos;
	}
	void Resize(size_t n,const T& val = T())
	{
		PNode node = new Node;
		size_t size = Size();
		if (n < size)
		{
			for (size_t i = 0; i < size - n; ++i)
			{
				PopBack();
			}
		}
		else
		{
			for (size_t i = 0; i < n; ++i)
			{
				PushBack(val);
			}
		}
	}


	//迭代器的相关接口
	iterator begin()
	{
		return iterator(head_->next_);
	}
	iterator end()
	{
		return iterator(head_);
	}
        //下面是只读对象调用
	const_iterator cbegin() const
	{
		return const_iterator(head_->next_);
	}
	const_iterator cend() const
	{
		return const_iterator(head_);
	}

	List& operator=(const List lst)
	{
		Swap(lst);
		return *this;
	}
	~List()
	{
		if (head_)
		{
			PNode tmp = head_->next_;
			PNode next;
			while (tmp != head_)
			{
				next = tmp->next_;
				delete tmp;
				tmp = next;
			}
			delete head_;
			head_ = nullptr;
		}
	}



	//工具
	void Swap(List& list)
	{
		swap(head_, list.head_);
	}
	size_t Size()
	{
		iterator move = begin();
		iterator lend = end();
		size_t len = 0;
		while (move != lend)
		{
			++len;
			++move;
		}
		return len;
	}


private:
	//头节点的指针
	PNode head_;

	//ListNode的构造是全缺省的可以不用传参数
	void Create()
	{
		head_ = new Node;
		head_->prev_ = head_->next_ = head_;
	}
};

7. vector和list的对比

vector和list的对比
vector list

底层

动态顺序表,一段连续空间

带头结点的双向循环链表

访

支持随机访问,访问某个元素效率O(1)

不支持随机访问,访问某个元素效率O(N)

任意位置插入和删除效率低,需要搬移元素,时间复杂度为O(N),插入时有可能需要增容,增容:开辟新空 间,拷贝元素,释放旧空间,导致效率更低

任意位置插入和删除效率高,不需要搬移元素,时间复杂度为 O(1)

底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高

底层节点动态开辟,小节点容易造成内存碎片,空间利用率低, 缓存利用率低

原生态指针

对原生态指针(节点指针)进行封装

迭代

在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效,删 除时,当前迭代器需要重新赋值否则会失效

插入元素不会导致迭代器失效,删除元素时,只会导致当前迭代器失效,其他迭代器不受影响

使用

需要高效存储,支持随机访问,不关心插入删除效率

大量插入和删除操作,不关心随机访问

你可能感兴趣的:(#,C++入门学习系列,list)