MyList 双链表

1.改进完善迭代器

2.了解xmonery这个类,改进自己内存管理。


#define ZB_MYLIST_
#ifdef ZB_MYLIST_
#include "debug.h"
#include <xstddef>

class AllocMyList
{
public:
	AllocMyList(){}
	~AllocMyList(){}
	void* Allocator(size_t len) { return new char[len]; }
	void Free(void *ptr, size_t len = 0) { delete[] ptr; }
	void Swip(){}

	template<class _Objty,
	class... _Types>
		void construct(_Objty *_Ptr, _Types&&... _Args)
	{	// construct _Objty(_Types...) at _Ptr
		::new ((void *)_Ptr) _Objty(_STD forward<_Types>(_Args)...);
	}
};

template<typename T, typename Alloc = AllocMyList>
class MyList;

template<typename T>
struct Node
{
	typedef T value_type;
	typedef Node<T>* node_printer;
public:
	Node()
		:Data(), pPrev(nullptr), pNext(nullptr){}

	Node(value_type data) 
		:Data(data), pPrev(nullptr), pNext(nullptr){}

	Node(value_type data, node_printer prev, node_printer next)
		:Data(data), pPrev(prev), pNext(next){}

	bool operator==(Node& Rigth)
	{
		return (this->Data == Rigth.Data &&
				this->pPrev == Rigth.pPrev &&
				this->pNext == Rigth.pNext);
	}

	bool operator!=(Node& Right)
	{
		return !(*this == Right);
	}

	value_type Data;
	node_printer pPrev;
	node_printer pNext;
};


template<typename T>
class TIterator
{
	typedef T value_type;
	typedef Node<T> node_type;
	typedef Node<T>* node_printer;
	typedef MyList<T>* hash_printer;
	typedef TIterator<T> iterator;
public:
	TIterator():m_pNode(nullptr), m_pSelf(nullptr) {}

	TIterator(node_printer pNode, hash_printer pList)
		:m_pNode(pNode), m_pSelf(pList){}

	node_printer GetNode()
	{
		return m_pNode;
	}

	value_type GetData()
	{
		return m_pNode->Data;
	}

	iterator& operator++()
	{
		ASSERT (m_pSelf->GetCount == 0 ||
			m_pNode->pNext == nullptr);

		this->m_pNode = this->m_pNode->pNext;
		return *this;
	}

	iterator operator++(int)
	{
		TIterator temp = this;
		++this;
		return temp;
	}

	iterator& operator--()
	{
		ASSERT(m_pSelf->size() == 0 ||
			m_pNode->pNext == nullptr);

		return *this;
	}

	iterator operator--(int)
	{
		iterator temp = this;
		--this;
		return temp;
	}
	bool operator==(TIterator& Right)
	{
		return (this->m_pNode == Right.m_pNode);
	}

	bool operator!=(TIterator& Right)
	{
		return !(*this == Right);
	}
private:
	node_printer m_pNode;
	hash_printer m_pSelf;
};

template<typename T, typename Alloc = AllocMyList>
class MyList
{
	typedef T value_type;
	typedef T* value_printer;
	typedef T& reference;
	typedef Node<T> node_type;
	typedef Node<T>* node_printer;
	typedef TIterator<T> iterator;
	typedef size_t size_type;
public:
	MyList()
	{ 
		MyHead = Buy();
		MySize = 0;
	}

	MyList(size_type nCount)
	{
		
	}
	
	MyList(size_type nCount, value_type Val)
	{
		Construct_n(nCount, Val);
	}

	// 返回当前对象个数  
	size_type size() const { return (this->MySize); }
	size_type max_size() const { return ((size_type)(-1) / sizeof(value_type)); }//*
	// 提供访问函数  
	reference front(){ return (*begin()); }
	reference back() { return (*(end() - 1)); }
	// 获取几种迭代器  
	iterator begin() const
	{ 
		return iterator(this->MyHead->pNext, this);
	}

	iterator end() const
	{ 
		return iterator(this->MyHead, this); 
	}

protected:
	static node_printer& Nextnode(node_printer pNode)
	{
		return ((node_printer&)pNode->pNext);
	}

	static node_printer& Prevnode(node_printer pNode)
	{
		return ((node_printer&)pNode->pPrev);
	}

	static reference Myval(node_printer pNode)
	{
		return ((reference)pNode->Data);
	}

private:
	void Construct_n(size_type nCount, value_type Val)
	{
		Insert_n(begin(), nCount, Val);
	}
	
	void Insert_n(iterator Where, size_type nCount, value_type Val)
	{
		size_type nSaveCount = nCount;
		_TRY_BEGIN
		for (; nCount > 0; --nCount)
		{
			Inser(Where, Val);
		}
		_CATCH_ALL
		for (;nCount < nSaveCount; ++nCount)
		{
			iterator Before = Where;
			uncheck_erase(--Before);
		}
		_RERAISE;
		_CATCH_END
	}

	void uncheck_erase(iterator Where)
	{
		node_printer pNode = Where.GetNode();
		this->Nextnode(this->Prevnode(pNode)) = this->Nextnode(pNode);
		this->Prevnode(this->Nextnode(pNode)) = this->Prevnode(pNode);
		this->MySize--;
	}

	void Inser(iterator Where, value_type Val)
	{
		node_printer pNode = Where.GetNode();
		node_printer new_node = BuyNode(Prevnode(pNode), pNode, Val);
		IncSize(1);
		this->Prevnode(pNode) = new_node;
		this->Nextnode(this->Prevnode(new_node)) = new_node;
	}

	Alloc& Getal()
	{
		return (this->alloc);
	}

	Alloc& Getal() const
	{
		return (this->alloc);
	}

	node_printer BuyNode(node_printer prev, node_printer next, value_type data)
	{
		node_printer temp = static_cast<node_type*>(this->Getal().Allocator(sizeof(data)));
		if (*next == node_type())
		{
			prev = temp;
			next = temp;
		}
		_TRY_BEGIN
		this->Getal().construct(std::addressof(Prevnode(temp)), prev);
		this->Getal().construct(std::addressof(Nextnode(temp)), next);
		_CATCH_ALL
		this->Getal().Free(temp);
		_RERAISE;
		_CATCH_END
		return temp;
	}

	void IncSize(size_type nCount)
	{
		if (max_size() - this->MySize - 1 < nCount)
			ASSERT("Mylist<T> is too long");
		this->MySize += nCount;
	}
private:
	Alloc alloc;
	node_printer MyHead;
	size_type MySize;
};

#endif//ZB_MYLIST_



<think>我们正在讨论如何销毁双向链表。根据提供的引用,销毁双向链表的关键是确保所有节点(包括哨兵节点)都被正确释放,避免内存泄漏。销毁过程通常需要遍历链表,逐个释放节点,最后释放哨兵节点。 引用[4]中提到了一种使用一级指针的销毁方法: 1. 从哨兵节点(phead)的下一个节点开始遍历。 2. 使用一个临时指针del指向当前要释放的节点。 3. 在循环中,先保存下一个节点的地址,然后释放当前节点。 4. 循环条件:当前节点不是哨兵节点(即循环链表,回到哨兵节点时停止)。 5. 循环结束后,释放哨兵节点。 但是,引用[4]也指出,使用一级指针时,在函数内部将哨兵节点置为NULL(phead=NULL)实际上只是修改了形参,并不能改变实参。因此,为了在销毁后让链表指针变为NULL,通常需要传递二级指针。 引用[3]中展示的销毁函数使用了二级指针: 函数原型:int DestoryDouList(LinkNode **ppHead) 步骤: 1. 从哨兵节点的下一个节点开始遍历。 2. 释放每个节点(包括前驱节点?注意代码中释放的是当前节点的前驱节点,这似乎有误,但可能是特定实现)。 3. 最后释放哨兵节点,并将头指针置为NULL。 然而,引用[3]中的销毁代码可能存在问题: ```c int DestoryDouList(LinkNode **ppHead) { LinkNode *pTmpNode = NULL; pTmpNode = (*ppHead)->pNext; while (pTmpNode != NULL) { free(pTmpNode->pPre); // 释放当前节点的前驱节点(即上一个节点?) pTmpNode = pTmpNode->pNext; } free(pTmpNode); *ppHead = NULL; return 0; } ``` 这个实现假设链表不是循环的(因为循环链表不会出现NULL),而且释放的是当前节点的前驱节点,这样可能会导致重复释放或者释放顺序混乱。 相比之下,引用[4]中的方法更清晰: ```c void Destroy(ListNode* phead) { assert(phead); ListNode* del = phead->next; while (del != phead) { // 循环直到回到哨兵节点 ListNode* next = del->next; free(del); del = next; } free(phead); // 注意:这里phead是局部变量,置空无意义,所以需要外部将链表指针置空 } ``` 但是,这个函数没有将外部的链表指针置空,所以外部指针可能成为野指针。因此,我们需要传递二级指针来修改外部指针。 修正后的销毁函数(使用二级指针): ```c void Destroy(ListNode** pphead) { assert(pphead && *pphead); // 确保头指针有效 ListNode* phead = *pphead; ListNode* cur = phead->next; while (cur != phead) { // 遍历除哨兵节点外的所有节点 ListNode* next = cur->next; free(cur); cur = next; } free(phead); // 释放哨兵节点 *pphead = NULL; // 将外部的链表指针置为NULL } ``` 注意:双向链表可以是循环的(如带头双向循环链表)或者非循环的。上述代码适用于带头双向循环链表。如果是非循环链表,则循环条件应为`cur != NULL`(但通常双向链表会设计成循环的,特别是带头节点时)。 根据引用[2],带头双向循环链表有一个哨兵节点(哑节点),它的prev和next在空链表时都指向自己。因此,销毁时我们需要遍历整个链表(哨兵节点除外),最后释放哨兵节点。 总结销毁步骤: 1. 检查链表指针是否有效(非空)。 2. 如果是循环链表,则从哨兵节点的下一个节点开始遍历,直到再次遇到哨兵节点。在遍历过程中,逐个释放节点。 3. 释放哨兵节点。 4. 将链表指针置为NULL(避免野指针)。 因此,正确销毁双向链表(带头双向循环链表)的方法如下(使用二级指针): 代码实现: ```c #include <stdlib.h> #include <assert.h> typedef int DataType; // 假设数据类型为int typedef struct ListNode { DataType data; struct ListNode* prev; struct ListNode* next; } ListNode; void ListDestroy(ListNode** pphead) { // 检查参数 if (pphead == NULL || *pphead == NULL) { return; } ListNode* phead = *pphead; // 从第一个有效节点(哨兵节点的下一个)开始遍历 ListNode* cur = phead->next; while (cur != phead) { // 循环链表,回到头节点时结束 ListNode* next = cur->next; free(cur); cur = next; } // 释放哨兵节点 free(phead); *pphead = NULL; // 置空外部指针 } ``` 注意:如果链表为空(即只有哨兵节点),那么循环不会执行(因为cur就是phead),直接释放哨兵节点并置空。 如果使用一级指针,则无法在函数内部将外部的链表指针置空,所以调用销毁函数后,需要手动将链表指针置为NULL。但这样容易忘记,所以推荐使用二级指针。 例如,使用一级指针时: ```c void ListDestroy(ListNode* phead) { if (phead == NULL) return; ListNode* cur = phead->next; while (cur != phead) { ListNode* next = cur->next; free(cur); cur = next; } free(phead); // 这里不能修改外部指针,所以调用后需要使用者手动置空 } ``` 调用方式: ```c ListNode* list = ...; // 初始化链表 ListDestroy(list); // 销毁链表,但list指针不会变为NULL list = NULL; // 手动置空 ``` 因此,为了安全和方便,建议使用二级指针版本。 相关问题: 1. 为什么销毁双向链表需要二级指针? 2. 如何避免在销毁链表时出现内存泄漏? 3. 带头双向循环链表与普通双向链表在销毁时有什么不同?</think>### 如何正确销毁双向链表 销毁双向链表的关键是**遍历所有节点并逐一释放内存**,最后释放哨兵节点(头节点),同时避免内存泄漏和野指针问题。以下是具体步骤和代码实现: #### 核心步骤 1. **遍历链表**:从头节点的下一个节点开始,逐个释放节点内存。 2. **保存下一节点**:在释放当前节点前,保存其下一节点的地址。 3. **处理哨兵节点**:释放所有普通节点后,单独释放哨兵节点。 4. **置空指针**:将链表头指针置为 `NULL`(需使用二级指针)。 #### 代码实现(带头双向循环链表) ```c typedef struct ListNode { int data; struct ListNode* prev; struct ListNode* next; } ListNode; // 销毁链表函数(二级指针版) void DestroyList(ListNode** ppHead) { if (ppHead == NULL || *ppHead == NULL) return; ListNode* pHead = *ppHead; // 哨兵节点 ListNode* cur = pHead->next; // 从第一个有效节点开始 // 遍历释放所有普通节点 while (cur != pHead) { // 循环链表:回到头节点时结束 ListNode* next = cur->next; // 保存下一节点 free(cur); // 释放当前节点 cur = next; // 移动到下一节点 } free(pHead); // 释放哨兵节点 *ppHead = NULL; // 头指针置空,避免野指针 } ``` #### 关键说明 1. **循环条件**: `while (cur != pHead)` 确保遍历完整的循环链表(回到哨兵节点时终止)。 2. **二级指针必要性**: 一级指针无法修改外部头指针(如 `list = NULL`),需用二级指针避免野指针[^4]。 3. **哨兵节点处理**: 普通节点释放后需单独释放哨兵节点(`free(pHead)`)。 4. **空链表处理**: 检查 `*ppHead == NULL` 可防止销毁空链表时崩溃。 #### 调用示例 ```c ListNode* myList = CreateList(); // 创建链表 // ... 链表操作 ... DestroyList(&myList); // 传入二级指针 // 此时 myList == NULL ``` ### 相关问题 1. **为什么销毁链表需要二级指针?一级指针有什么风险?** > 一级指针只能释放节点内存,但无法修改外部头指针。若外部指针未置空,可能误访问已释放内存(野指针),导致未定义行为[^4]。 2. **双向链表销毁和单链表销毁有何区别?** > 双向链表需维护 `prev` 和 `next` 指针,但销毁时只需顺序释放节点,无需额外操作指针(内存释放后指针自然失效)。单链表通常只需遍历 `next` 指针[^2]。 3. **如何验证链表销毁后无内存泄漏?** > 使用内存检测工具(如 Valgrind)检查程序退出时是否残留未释放内存。或添加调试日志,确认每个节点的 `free()` 都被执行。 4. **循环链表和非循环链表在销毁时处理有何不同?** > 循环链表终止条件为 `cur != pHead`(回到起点),非循环链表终止条件为 `cur != NULL`。循环链表需额外处理哨兵节点[^2][^4]。 > 引用说明: > [^1]: 销毁需遍历所有节点,循环结束条件为 `cur != phead`,每次保存下一节点地址以便迭代。 > [^2]: 哨兵节点简化边界处理,但销毁时需单独释放,并注意其 `prev`/`next` 的自循环特性。 > [^4]: 必须使用二级指针修改外部头指针,否则外部指针可能成为野指针。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值