C++ -- STL【list的模拟实现】

目录

2、list的迭代器

3、list的初始化与销毁

[3.1 构造函数与拷贝构造](#3.1 构造函数与拷贝构造)

[3.2 赋值重载与析构函数](#3.2 赋值重载与析构函数)

4、list的容量操作

5、list的访问操作

6、list的修改操作

[6.1 迭代器失效](#6.1 迭代器失效)

7、源码

8、模版的按需实例化


list 的底层其实就是我们之前在数据结构学习的双向循环链表,它由一个节点指针 _head 以及记录有效节点个数的 _size。

下面是 list 的成员变量以及主体架构:

cpp 复制代码
namespace tata
{
    template<class T>
    struct _list_node//节点
    {
    	_list_node<T>* _prev;
    	_list_node<T>* _next;
    	T _data;
    	_list_node(const T& x = T())
    		:_prev(nullptr)
    		,_next(nullptr)
    		,_data(x)
    	{
    		;
    	}
    };
	template<class T>
	class list
    {
	public:
    	typedef _list_node<T> node;
        //...成员函数
	private:
        node* _head;
        size_t _size;
	};
}

2、list的迭代器

首先我们来模拟实现一下迭代器 iterator,而在list中迭代器 iterator 与 vector,string 中的迭代器都不同,因为 list 每个节点在物理空间都不连续,所以我们不可能直接使用原生指针,而是要对原生指针进行封装形成一个 list_iterator 类。然后再这个类中进行运算符重载 ++,--,*,->,==,!= 等操作符。

而我们知道迭代器又分为 iterator 与 const_iterator 两种,常规情况下我们这两个类都要实现。但他们功能就显得过于冗余了,所以我们可以采用增加模版参数的方法简化。第一个模版参数 T 代表节点类型,第二个模版参数 Ref 代表迭代器引用,第三个模版参数 Ptr 代表迭代器指针类型。

cpp 复制代码
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* n)
        :_node(n)//通过节点构建迭代器
    {
        ;
    }
    Ref operator*()
    {
        return _node->_data;
    }
    Ptr operator->()
    {
        return &(_node->_data);
    }
    Self& operator++()//前置++
    {
        _node = _node->_next;
        return *this;
    }
    Self operator++(int)//后置++
    {
        Self tmp(*this);
        _node = _node->_next;
        return tmp;
    }
    Self& operator--()//前置--
    {
        _node = _node->_prev;
        return *this;
    }
    Self operator--(int)//后置--
    {
        Self tmp(*this);
        _node = _node->_prev;
        return tmp;
    }
    bool operator==(const Self& s)
    {
        return _node == s._node;
    }
    bool operator!=(const Self& s)
    {
        return _node != s._node;
    }
};

然后再 list 中对不同迭代器类型进行 typedef 简化。

cpp 复制代码
typedef _list_iterator<T, T&, T*> iterator;
typedef _list_iterator< T, const T&, const T*> const_iterator;

我们看到 operator++、operator--,这里 return *this 会调用迭代器的拷贝构造,但是我们不用写,因为编译器会自动生成一份浅拷贝的拷贝构造。那浅拷贝会不会有问题呢?不会,因为我们要的就是浅拷贝。

接下来我们来实现 begin() 与 end(),其中 begin() 指向的是列表的起始位置即第一个有效节点,而 end() 指向有效长度最后的下一位即头节点的位置,这些我们直接通过相应节点构造迭代器即可。

cpp 复制代码
iterator begin()
{
    return iterator(_head->_next);
}
iterator end()
{
    return iterator(_head);
}

实现完普通迭代器之后,我们可以顺便重载一个 const_iterator 的版本。

cpp 复制代码
const_iterator begin()const
{
    return const_iterator(_head->_next);
}
const_iterator end()const
{
    return const_iterator(_head);
}

我们知道在 list 中还有一个反向迭代器,这个我们在之后会统一实现。

3、list的初始化与销毁

3.1 构造函数与拷贝构造

我们之前在学习 list 时知道其初始化方式有很多,可以通过默认构造函数给其初始化,n 个 val 初始化,也可以通过迭代器区间初始化。

首先我们写一个默认构造函数,构造出一个头节点指向自己。

cpp 复制代码
void empty_initialize()
{
    _head = new node;
    _head ->_next = _head;
    _head ->_prev = _head;
    _size = 0;
}
list()
{
    empty_initialize();
}

这里有个问题就是 empty_initialize 是 非const 成员函数,那定义 const 成员是否还能来调用呢?答案自然是可以的 ,因为 const 变量在定义的时候是不具有 const 属性的,定义完成之后才有。比如说:

cpp 复制代码
//如果在定义之前就具有const属性,那么n就无法赋值
const int n = 10;
const list<int> l1;

接下来我们来实现迭代器初始化,而因为我们可以通过其他容器的迭代器对其初始化,所以要通过模版来实现。

cpp 复制代码
template<class Iterator>
list(Iterator first, Iterator last)
{
    empty_initialize();
    while (first != last)
    {
        push_back(*first);
        ++first;
    }
}

最后我们来实现 n 个 val 初始化,这个构造我们可以直接复用 resize()函数。

cpp 复制代码
list(size_t n, const T& val = T())
{
    empty_initialize();
    resize(n, val);
}
list(int n, const T& val = T())
{
    empty_initialize();
    resize(n, val);
}

至于为什么要同时重载 int 与 size_t 两种不同类型,那是为了防止在传两个 int 类型的参数时被编译器交给模版 InputIterator 识别,然后报错。

C++11 引入 initializer_list 可以使用花括号进行初始化,我们要支持一个 initializer_list 的 list 构造即可实现花括号初始化。

cpp 复制代码
	list(initializer_list<T> il)
	{
		empty_init();
		for (auto& x : il)//支持迭代器就是支持范围for
		{
			push_back(x);
		}
	}

支持迭代器就支持范围 for,将 initializer_list 存储的数据拿来尾插即可。

拷贝构造也十分简单,直接拷贝就行。

cpp 复制代码
list(const list<T>& lt)
{
    empty_initialize();
    list tmp(lt.begin(),lt.end());
    swap(tmp);//交换
}

首先通过构造出一个与原列表 it 相同的列表 tmp,然后让 this 所指向的列表与其交换,这样出了作用域之后销毁的就是原 this 所指向的列表。

3.2 赋值重载与析构函数

赋值运算符重载与拷贝构造的实现就非常类似了,直接实现即可。

cpp 复制代码
list<T>& operator=(list<T> lt)
{
    swap(lt);
    return *this;
}

最后我们实现析构函数,只需要清理资源即可。

cpp 复制代码
~list()
{
    clear();//先清空其他节点
    delete _head;//释放头节点
    _head = nullptr;
}

4、list的容量操作

首先 size() 直接返回有效元素的个数,empty() 判断有效元素个数是否为 0。

cpp 复制代码
size_t size()
{
    return _size;
}
bool empty()
{
    return _size == 0;
}

clear() 要清理掉除头节点外的所有节点,我们可以直接复用 earse 删除即可。

cpp 复制代码
void clear()
{
    iterator it = begin();
    while (it != end())
    {
        it = erase(it);
    }
}

当使用 resize(n) 时,如果 n 大于当前列表的大小,那么会在列表末尾添加足够数量的默认值元素,使列表大小达到 n 。如果 n 小于当前列表的大小,那么会从列表末尾删除一些元素,使列表大小变为 n 。

cpp 复制代码
void resize(size_t n, const T& val = T())
{
    if (n < _size)
    {
        while (_size != n)
        {
            pop_back();//尾删
        }
    }
    else
    {
        while (_size != n)
        {
            push_back(val);//尾插
        }
    }
}

5、list的访问操作

因为列表并不支持重载 operator[],我们只能实现 front() 与 back() 函数。可读可写与可读不可写。并且使用引用返回,减少不必要的拷贝。

cpp 复制代码
// 可读可写
T& front()
{
    return *(begin());
}
T& back()
{
    return *(--end());
}
// 可读不可写
const T& front()const
{
    return  *(begin());
}
const T& back()const
{
    return  *(--end());
}

6、list的修改操作

首先我们将实现两个常用的修改函数:push_back() 与 pop_back()。这两个函数也都可以复用 insert() 与 earse()

cpp 复制代码
void push_back(const T& x)
{
    insert(end(), x);
}
void pop_back()
{
    erase(--end());
}

然后我们同理可以实现 push_front() 与 pop_front()。

cpp 复制代码
void push_front(const T&x)
{
    insert(begin(), x);
}
void pop_front()
{
    erase(begin());
}

随后我们来实现数组的交换 swap() 函数,我们知道 list 的交换其实就是指针 _head,与有效个数 _size 的交换。

cpp 复制代码
void swap(list<T>& lt)
{
    std::swap(_head, lt._head);
    std::swap(_size, lt._size);
}

6.1 迭代器失效

接下来我们实现 insert() 与 erase() 两个函数。虽然插入并没有使迭代器失效,但为了与其他容量的功能视频,迭代器应指向新插入的节点。

cpp 复制代码
// 在pos位置前插入值为val的节点,返回新节点的迭代器
iterator insert(iterator pos, const T& x)
{
	node* newnode = new node(x);
	node* cur = pos._node;     // 目标位置节点
	node* prev = cur->_prev;   // 目标位置前驱节点
	prev->_next = newnode;     // 新节点

    // 调整指针:prev <-> newnode <-> cur
	newnode->_prev = prev;
	newnode->_next = cur;
	cur->_prev = newnode;

	++_size;
	return iterator(newnode);   // 返回指向新节点的迭代器
}

erase 为了防止迭代器失效,需要返回删除元素下一个元素的迭代器。

cpp 复制代码
iterator erase(iterator pos)
{
    node* cur = pos._node;
    node* prev = cur->_prev;
    node* next = cur->_next;
    prev->_next = next;
    next->_prev = prev;
    delete cur;
    cur = nullptr;
    --_size;
    return iterator(next);
}

7、源码

cpp 复制代码
#pragma once
#include<assert.h>
namespace tata
{
	template<class T>
	struct _list_node
	{
		_list_node<T>* _prev;
		_list_node<T>* _next;
		T _data;
		_list_node(const T& x = T())
			:_prev(nullptr)
			,_next(nullptr)
			,_data(x)
		{
			;
		}
	};
	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* n)
			:_node(n)
		{
			;
		}
		Ref operator*()
		{
			return _node->_data;
		}
		Ptr operator->()
		{
			return &(_node->_data);
		}
		Self& operator++()//前置++
		{
			_node = _node->_next;
			return *this;
		}
		Self operator++(int)//后置++
		{
			Self tmp(*this);
			_node = _node->_next;
			return tmp;
		}
		Self& operator--()//前置--
		{
			_node = _node->_prev;
			return *this;
		}
		Self operator--(int)//后置--
		{
			Self tmp(*this);
			_node = _node->_prev;
			return tmp;
		}
		bool operator==(const Self& s)
		{
			return _node == s._node;
		}
		bool operator!=(const Self& s)
		{
			return _node != s._node;
		}
	};
	template<class T>
	class list
	{
	public:
		typedef _list_node<T> node;
		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);
		}
		void empty_initialize()
		{
			_head = new node;
			_head ->_next = _head;
			_head ->_prev = _head;
			_size = 0;
		}
		list()
		{
			empty_initialize();
		}
		template<class Iterator>
		list(Iterator first, Iterator last)
		{
			empty_initialize();
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}
		list(size_t n, const T& val = T())
		{
			empty_initialize();
			resize(n, val);
		}
		list(int n, const T& val = T())
		{
			empty_initialize();
			resize(n, val);
		}
		list(const list<T>& lt)
		{
			empty_initialize();
			list tmp(lt.begin(),lt.end());
			swap(tmp);//交换
		}
		list<T>& operator=(list<T> lt)
		{
			swap(lt);
			return *this;
		}
		iterator insert(iterator pos, const T& x)
		{
			node* newnode = new node(x);
			node* cur = pos._node;
			node* prev = cur->_prev;
			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;
			++_size;
			return iterator(newnode);
		}
		iterator erase(iterator pos)
		{
			node* cur = pos._node;
			node* prev = cur->_prev;
			node* next = cur->_next;
			prev->_next = next;
			next->_prev = prev;
			delete cur;
			cur = nullptr;
			--_size;
			return iterator(next);
		}
		// 可读可写
		T& front()
		{
			return *(begin());
		}
		T& back()
		{
			return *(--end());
		}
		// 可读不可写
		const T& front()const
		{
			return  *(begin());
		}
		const T& back()const
		{
			return  *(--end());
		}
		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());
		}
		size_t size()
		{
			return _size;
		}
		bool empty()
		{
			return _size == 0;
		}
		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				it = erase(it);
			}
		}
		void resize(size_t n, const T& val = T())
		{
			if (n < _size)
			{
				while (_size != n)
				{
					pop_back();//尾删
				}
			}
			else
			{
				while (_size != n)
				{
					push_back(val);//尾插
				}
			}
		}
		void swap(list<T>& lt)
		{
			std::swap(_head, lt._head);
			std::swap(_size, lt._size);
		}
		~list()
		{
			clear();//先清空其他节点
			delete _head;//释放头节点
			_head = nullptr;
		}
	private:
		node* _head;
		size_t _size;
	};
}

8、模版的按需实例化

编译器对模版是按需实例化,调用了才会实例化,不调用就不会实例化。

相关推荐
再睡一夏就好1 小时前
进程调度毫秒之争:详解Linux O(1)调度与进程切换
linux·运维·服务器·c++·算法·哈希算法
无限进步_1 小时前
C语言双向循环链表实现详解:哨兵位与循环结构
c语言·开发语言·数据结构·c++·后端·算法·链表
咬_咬1 小时前
C++仿muduo库高并发服务器项目:EventLoop模块
服务器·c++·muduo·eventloop
Bona Sun1 小时前
单片机手搓掌上游戏机(十九)—pico运行doom之硬件连接
c语言·c++·单片机·游戏机
言言的底层世界2 小时前
c/c++基础知识点
开发语言·c++·经验分享·笔记
Bona Sun2 小时前
单片机手搓掌上游戏机(二十二)—pico运行doom之固件和rom上传
c语言·c++·单片机·游戏机
十五年专注C++开发2 小时前
嵌入式软件架构设计浅谈
c语言·c++·单片机·嵌入式
ULTRA??2 小时前
C++20模块( import 核心用法)
c++·c++20
小年糕是糕手2 小时前
【C++】类和对象(五) -- 类型转换、static成员
开发语言·c++·程序人生·考研·算法·visual studio·改行学it