【C++】List模拟实现

List模拟实现

1.1 创建结点

	template<class T>
	struct  ListNode
	{
		ListNode<T>* _next;//指向后一个节点
		ListNode<T>* _prev;//指向前一个节点
		T _data; // 存数据
    // 这里用匿名对象当缺省值
		ListNode(const T& data = T())
			:_next(nullptr)
			, _prev(nullptr)
			, _data(data)
		{}
	};

因为要频繁的调用ListNode里面的数据选择了struct ,struct具有公有性。 

	template<class T>
	class list
	{
		typedef ListNode<T> Node;
	private:
		Node* _head;
	};

1.2 实现遍历功能

在class list类中添加了构造以及插入函数 

		list()
		{
			_head = new Node();
			_head->_next = _head;
			_head->_prev = _head;
		}
		void push_back(const T& x)
		{
			Node* newnode = new Node(x);
			Node* tail = _head->_prev;
			
			tail->_next = newnode;
			newnode->_prev = tail;
			newnode->_next = _head;
			_head->_prev = newnode;
		}

当创建一个list对象首先发生构造,创建头结点,之后就是插入数据 


迭代器 

 在main函数中我们要这样使用迭代器,进行循环 

		list<int> lit;
		lit.push_back(1);
		lit.push_back(2);
		lit.push_back(3);
		lit.push_back(4);
		lit.push_back(5);
		list<int>::iterator it = lit.begin();
		// it.operator!=(lit.end());
		while (it != lit.end())
		{
			cout << *it << " ";
			++it;
		}

我们先创建一个对象lit,在这个对象中,我们在各个节点中push_back数据,在push_back内部我们使其各个分散的节点连接一起,最后定义了迭代器进行循环 

在循环的过程中,我们使用下面的迭代器可以吗?

typedef Node* iterator;

不能,因为每个节点是独立的空间地址,每个节点是不连续的,我们无法保证++来移动指针,使其下个地址就是下个节点的位置。 

那应该怎么办?

因为,我们要求这个迭代器++就能指向下一个节点位置,我们只能创建个类 

在这里我们创建一个类,来模拟自定义类型Node的指针,为其添加类似于内置类型的功能(++,--)最后封装成iterator

	template<class T>
	class ListIterator
	{
		typedef ListNode<T> Node;
        typedef ListIterator<T> Self;
	public:
		ListIterator(Node* node)
			:_node(node)
		{}
		// ++it
		Self& operator++()
		{
			_node = _node->_next;
			return *this;
		}
		bool operator!=(const Self& it)
		{
			return _node != it._node;
		}
		T& operator*()
		{
			return _node->_data;
		}
		T* operator->()
		{
			return &_node->_data;
		}
	private:
		Node* _node;
	};

 循环遍历首先要++,移动指针的位置,使其指向下一个节点

在上面的main函数中

list<int>::iterator it = lit.begin();

it接受第一个节点的地址后

之后进行++it<------>it.operator(第一个节点地址)

		Self& operator++()
		{
			_node = _node->_next;
			return *this;
		}

 把一个节点地址修改为下一个节点的地址进行返回

循环遍历的循环条件是

it != lit.end()

迭代器的区间一般又是左闭右开

所以iterator end返回的是头节点

指向的位置

什么意思呢?就是指向的位置是左闭右开

 之后就是访问里面的数据

 如果_data里面存储的是内置类型可以直接解引用使用operator*

如果_data里面存储的是自定义类型这样就要使用operator->去访问里面的数据(若是自定义类型不是一定要用operator->去访问)

	struct Pos
	{
		int _row;
		int _col;

		Pos(int row = 0,int col = 0)
			:_row(row)
			,_col(col)
		{}
	};

 例如我们自定义一个这样的类型

我们push_back存储数据,该如何遍历里面的数据呢?

		list<Pos> lt1;
		lt1.push_back(Pos(100, 100));
		lt1.push_back(Pos(200, 200));
		lt1.push_back(Pos(300, 300));
cout << it.operator->()->_col << ":" << it.operator->()->_row << endl;

我们可以向上面一样访问

operator->返回的是匿名对象存数据的地址,之后再访问Pos类里面的内容(只是调用了一次operator->)

系统默认优化这样写也行

cout << it->_col << ":" << it->_row << endl;

上面的代码可以这么理解,迭代器就是一个指针,指针访问数据

也可以这么写

cout << (*it)._col << ":" << (*it)._row << endl;

我们这里operator*采用的是引用返回,方便修改数据

operator->采用的是T*返回,也能修改数据。

如果不想修改数据,又该如何操作 

const 迭代器 

假如是一个const list我们又该如何遍历链表

	void Func(const list<int>& lt)
	{
		list<int>::iterator it = lt.begin();
		while (it != lt.end())
		{
			// *it += 10;
			cout << *it << " ";
			++it;
		}
		cout << endl;
	}

 首先const的list需要一个const迭代器

 这个const迭代器的功能是 *it内容的数据不能修改,it可以++

直接在iterator前面+const是不可以的,因为it不能进行++了

 如何操作?再设计一个const迭代器的类

首先,要明白如何控制*it的内容不被修改------->就是迭代器中的返回的数据不能修改

也就是控制T&与 T*的返回类型,在他们前面都加上const

		T& operator*()
		{
			return _node->_data;
		}
		T* operator->()
		{
			return &_node->_data;
		}

之后再list类中补充一下const list有关迭代器返回函数

		typedef ListConstIterator<T> const_iterator;//const迭代器类
        const_iterator begin() const
		{
			return const_iterator(_head->_next);
		}
		const_iterator end() const
		{
			return const_iterator(_head);
		}

 我们发现,虽然我们实现了const list的遍历,但是代码过于繁琐

 我们通过模板让编译器控制返回的类型这样可以减少代码的繁琐,

于是分别定义了两个模板在list类中,通过调用不同的名称来控制某个模板

		typedef ListIterator<T, T&, T*> iterator;
		typedef ListIterator<T, const T&, const T*> const_iterator;

迭代器的模板,接受的类型一个是T一个是Ref一个是Ptr,若是使用的iterator,迭代器模板所接受的就是T,T&,T*,若是使用的是const_iterator,迭代器模板所接受的就是T,const T&,const T*

	template<class T,class Ref,class Ptr>
	struct ListIterator
	{
		typedef ListNode<T> Node;
		typedef ListIterator<T, Ref, Ptr> Self;
		Node* _node;
		ListIterator(Node* node)
			:_node(node)
		{}
		Ref operator*()
		{
			return _node->_data;
		}
		Ptr operator->()
		{
			return &_node->_data;
		}
		bool operator!=(const Self& it)
		{
			return _node != it._node;
		}
		Self& operator++()
		{
			_node = _node->_next;
			return *this;
		}
    };

最后把迭代器补充完整

后置++

// it++
Self& operator++(int)
{
	Self tmp(*this);
	_node = _node->_next;
	return tmp;
}

注意:这里实现的是浅拷贝,为什么这里不实现深拷贝?

我们这是需要指针,而不是所指向的内容,使用深拷贝太浪费空间

前置--,与后置-- 

		// --it
		Self& operator--()
		{
			_node = _node->_prev;
			return *this;
		}
		// it--
		Self& operator--(int)
		{
			Self tmp(*this);
			_node = _node->_prev;
			return _node;
		}

operator== 

		bool operator==(const Self& it)
		{
			return _node == it._node;
		}

operator->

		T* operator->()
		{
			return &_node->_data;
		}

 insert

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

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

			return iterator(newnode);
		}

 

insert这里没有迭代器失效,因为pos指向的位置没有改变

erase

		iterator erase(iterator pos)
		{
			assert(pos != end());

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

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

			delete cur;

			return iterator(next);
		}

这里迭代器失效了,因为pos的位置已经delete了,pos是野指针

 尾删,头插,尾删

		void pop_back()
		{
			erase(--end());
		}

		void push_front(const T& x)
		{
			insert(begin(), x);
		}

		void pop_front()
		{
			erase(begin());
		}

析构

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

这里我们采用一个clear函数来清除节点的数据,因为清除每个节点后,it失效,erase指向下一个节点的位置,所以要重新赋值一下it,防止野指针

 拷贝构造

		void empty_init()
		{
			_head = new Node();
			_head->_next = _head;
			_head->_prev = _head;
		}
		list()
		{
			empty_init();
		}
		//lt2(lt1)
		list(const list<T>& lt)
		{
			empty_init();
			for (const auto& e : lt)
			{
				push_back(e);
			}
		}

我们这里创建一个empty_init用来初始化头结点,因为拷贝与拷贝构造都需要使用所以创建了一个函数,使其方便调用。在拷贝构造过程中,我们使用了for (const auto& e : lt)循环,为啥使用&?如果这里的T是string等其他自定义类型,会先构造生成一个临时变量,使其效率低下,所以要添加&

赋值拷贝

		list<T>& operator=(list<T> lt)
		{
			swap(_head, lt._head);
			return *this;
		}

 这里采用的是是现代写法。

 要是我想使用C++11中的这种初始化方式应该如何模拟?

		list<int> lit = {1,2,3,4};

 initializer_list<value_type> il

 

这个底层其实就是把{ }中的内容变成常量数组 

底层有2个指针,一个指向开始的位置,一个指向结束的地方 

 

		list(initializer_list<T> il)
		{
			empty_init();
			for (const auto& e : il)
			{
				push_back(e);
			}
		}

在mian函数中 初始化lit为1234之后传给形参il经过initializer_list类的实现,已经成为一个il为1234的常量数组了,之后把il中的数据插入到list链表中

  • 10
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 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、付费专栏及课程。

余额充值