C++ | list的模拟实现

目录

前言

一、list的基本框架

二、构造与析构函数

三、迭代器(list实现的核心知识点)

四、容量与访问相关接口

五、修改相关接口

六、本文源码


前言

        上篇文章我们学习使用了list的常用接口,这篇文章主要来模拟实现list;主要难点是关于list迭代器的实现;这部分内容可能具有挑战;

一、list的基本框架

        我们将我们自己模拟实现的list封装进我们的命名空间中,防止于stl库中的list发生命名冲突,引起命名污染;注意以下代码,我们不仅构建了一个list类,我们还构建了一个结点的类,这个lilst类的成员变量就是哨兵位头节点指针;

namespace MySpace
{
	template<class T>
	struct _list_node
	{
		// 结点的默认构造
		_list_node(const T& data = T())
			:_next(nullptr)
			,_prev(nullptr)
			,_data(data)
		{}
		// 结点的成员变量
		_list_node<T>* _next;
		_list_node<T>* _prev;
		T _data;
	};

	template<class T>
	class list
	{
        // 给结点进行重命名
		typedef _list_node<T> Node;
	public:

	private:
		Node* _head;
	};
}

二、构造与析构函数

        list的所有构造函数都必须先构建一个哨兵位头节点,因此,我们将构建初始化哨兵位头节点封装在一个empty_init函数中;

		// 初始化链表(头节点的创建)
		void empty_init()
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
		}

        我们在设计这么多构造函数时,可采用现代写法,是代码更加整洁,提高代码复用性;这里重载了一个int版本的构造n个相同的data的构造函数,是为了让当两个参数都为int型时,不会匹配到迭代器那个版本;

		// 默认构造
		list()
		{
			empty_init();
		}
		// 用n个data构造
		list(size_t n, const T& data = T())
		{
			empty_init();
			for (size_t i = 0; i < n; i++)
			{
				Node* new_node = new Node(data);
				// 链接新节点
				Node* prev = _head->_prev;
				Node* next = _head;

				prev->_next = new_node;
				new_node->_prev = prev;
				new_node->_next = next;
				next->_prev = new_node;
			}
		}
		// 用n个data构造(重载int版本,与vector一样,可能会匹配到迭代器那个函数)
		list(int n, const T& data = T())
		{
			empty_init();
			for (size_t i = 0; i < n; i++)
			{
				Node* new_node = new Node(data);
				// 链接新节点
				Node* prev = _head->_prev;
				Node* next = _head;

				prev->_next = new_node;
				new_node->_prev = prev;
				new_node->_next = next;
				next->_prev = new_node;
			}
		}
		// 迭代器区间构造
		template<class InputIterator>
		list(InputIterator first, InputIterator last)
		{
			// 初始化链表(哨兵位头节点)
			empty_init();
			// 依次尾插
			while (first != last)
			{
				Node* new_node = new Node(*first);

				Node* prev = _head->_prev;
				Node* next = _head;

				prev->_next = new_node;
				new_node->_prev = prev;
				new_node->_next = next;
				next->_prev = new_node;
				++first;
			}
		}
		// 拷贝构造
		list(const list& lt)
		{
			empty_init();
			// 这里我就直接复用后面实现的push_back了(这里迭代器也未实现,实现迭代器后这段代码才能成功运行)
			for (auto& e : lt)
			{
				push_back(e);
			}
		}
		// 赋值重载(现代写法)
		list& operator=(list lt)
		{
			// 这里的swap也是后面专门为list实现的swap
			swap(lt);
			return *this;
		}

        关于析构函数,我们要先将所有保存数据的结点依次释放后,再来释放哨兵位头节点;关于clear函数,我们后面会实现,这里先暂时复用一下;

// 析构函数
		~list()
		{
			// 复用后面实现的清空链表的成员函数
			clear();
			delete _head;
			_head = nullptr;
		}

三、迭代器(list实现的核心知识点)

        关于list的迭代器的实现,这部分知识稍微偏难,list的迭代器并不能仅仅用typedef重定义,因为list储存数据的空间并不是连续的,而vector储存数据的空间是连续的,当我们对typdef重定义的vector的迭代器+1时,他会指向下一个数据,而如果list而是单纯typedef重定义时,我们对其+1,并不一定会指向下一个数据的位置,因为储存的空间不是连续的,因此,这里我们用一个类对其封装起来,通过运算符重载,对其+1行为进行重定义;使我们通过迭代器访问数据时,可以像访问数组一样,对其+1或-1;

// 迭代器(与前面string和vector不同,这里的迭代器不能仅仅只使用typedef来实现)
// 因为list储存数据不是一段连续的物理空间,因此我们需要对++等运算符重载
template<class T, class Ref, class Ptr>
struct __list_iterator
{
	typedef _list_node<T> Node;
	typedef __list_iterator<T, Ref, Ptr> self;
	// 构造函数
	__list_iterator(Node* node = Node)
		:_node(node)
	{}
	// 运算符重载
	Ref operator*() const
	{
		return _node->_data;
	}
	Ptr operator->() const
	{
		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& it) const
	{
		return _node == it._node;
	}
	bool operator!=(const self& it) const
	{
		return _node != it._node;
	}
	// 成员变量
	Node* _node;
};

        上述接口值得一提的是->的重载;这个运算符的重载可能会让很多人感到蒙圈;这里实际上省略了一个->;我们可通过下面一段代码理解;

struct A
{
	A(int a = 0, int b = 0)
		:_a(a)
		,_b(b)
	{}
	int _a;
	int _b;
};
int main()
{
	MySpace::list<A> lt1;
	lt1.push_back(A(1, 1));
	lt1.push_back(A(2, 2));
	lt1.push_back(A(3, 3));
    // 当list的成员为自定义类型,且我们想访问其成员变量时,有如下方法;
	// 方法一:通过 *().访问成员_a
	auto it = lt1.begin();
	while (it != lt1.end())
	{
		cout << (*it)._a << ":" << (*it)._b << endl;
		it++;
	}
	cout << endl;
	// 方法二:通过重载后 -> 访问成员
	it = lt1.begin();
	while (it != lt1.end())
	{
		// 这里实际省略了一个->
		cout << it->_a << ":" << it->_b << endl;
		// 不省略是这么写
		cout << it.operator->()->_a << ":" << it.operator->()->_a << endl;

		it++;
	}
	return 0;
}

        而在list类中,我们对这个迭代器类型进行重定义;并且提供一些对外必要的函数接口;如begin、end等;

	public:
		typedef __list_iterator<T, T&, T*> iterator;
		typedef __list_iterator<T, const T&, const T*> const_iterator;

		//迭代器相关成员函数
		iterator begin()
		{
			return iterator(_head->_next);
		}
		const_iterator begin() const
		{
			return const_iterator(_head->_next);
		}
		iterator end()
		{
			return iterator(_head);
		}
		const_iterator end() const
		{
			return const_iterator(_head);
		}

四、容量与访问相关接口

        这一类接口实现起来就没有这么困难了;以下为实现代码;

		// capacity
		size_t size() const
		{
			size_t sz = 0;
			const_iterator it = begin();
			while (it != end())
			{
				sz++;
				it++;
			}
			return sz;
		}

		bool empty()
		{
			return _head->_next == _head;
		}

		// access
		T& front()
		{
			return (_head->_next)->_data;
		}
		const T& front() const
		{
			return (_head->_next)->_data;
		}
		T& back()
		{
			return (_head->_prev)->_data;
		}
		const T& back() const
		{
			return (_head->_prev)->_data;
		}

五、修改相关接口

        最后就是一些修改相关接口了,主要实现一些比较核心的修改接口;接口之间可以相互复用;

		// modify
		void push_back(const T& data)
		{
			//Node* new_node = new Node(data);

			//Node* prev = _head->_prev;
			//Node* next = _head;

			//prev->_next = new_node;
			//new_node->_prev = prev;
			//new_node->_next = next;
			//next->_prev = new_node;
			insert(end(), data);
		}
		void push_front(const T& data)
		{
			insert(begin(), data);
		}
		void pop_back()
		{
			erase(--end());
		}
		void pop_front()
		{
			erase(begin());
		}

		// 某个位置插入一个值
		iterator insert(iterator pos, const T& data)
		{
			Node* new_node = new Node(data);

			Node* prev = pos._node->_prev;
			Node* cur = pos._node;

			prev->_next = new_node;
			new_node->_prev = prev;
			new_node->_next = cur;
			cur->_prev = new_node;

			return iterator(new_node);
		}

		// 删除某个位置上的值
		iterator erase(iterator pos)
		{
			assert(pos._node != _head);

			Node* prev = pos._node->_prev;
			Node* next = pos._node->_next;

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

			delete pos;
			pos = next;
			return iterator(pos);
		}
		// 清空链表所有数据(除哨兵位头节点外)
		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				Node* del = it._node;
				it++;
				delete del;
			}
		}
		// 交换两个链表的头节点
		void swap(list<T>& lt)
		{
			std::swap(_head, lt._head);
		}

六、本文源码

        本文源码依旧上传至了gitee中,可通过下方链接免费获取;

list模拟实现

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
引用和提供了关于实现vector的两种方法。其中,引用展示了一个使用reserve和push_back方法的示例,而引用展示了一个使用new和memcpy函数的示例。这两种方法都是常见的实现vector的方式。 在第一种方法中,通过reserve函数可以预留足够的内存空间,然后使用push_back函数逐个将元素添加到vector中。这种方法的好处是可以避免不必要的内存重分配,提高了效率。 而第二种方法使用new操作符在堆上分配内存空间,并使用memcpy函数将已有的vector对象的数据复制到新的内存空间中。通过这种方式,可以实现深拷贝,即两个vector对象拥有独立的内存空间。这种方法的好处是可以在不修改原始vector对象的情况下创建一个新的vector对象。 除了以上两种方法,还可以使用其他方式实现vector类。例如,可以使用动态数组来实现vector的底层数据结构,然后通过成员函数实现vector的各种操作,如增加、删除、查找等。 总结来说,c语言模拟实现vector的关键是动态内存管理和对元素的增删改查操作。可以使用预留空间和逐个添加元素的方式,也可以使用动态数组和复制数据的方式来实现vector类。具体的实现方式可以根据需求和实际情况选择。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [C++——vector模拟实现](https://blog.csdn.net/weixin_49449676/article/details/126813526)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值