C++-list模拟实现

###模拟实现list需要先看源码,了解基本的结构和设计思路

###list底层是双向链表,每一个节点包含数据域和两个指针(一个指向后一个节点,一个指向前一个节点),并且list带有一个哨兵位;

###链表的结构的实现并不困难,这里阻碍实现的是迭代器;像string和vector底层结构是连续的,可以直接++、--、+几、-几来实现随机访问每一个数据,它们的迭代器的实现(迭代器的基本操作)也容易,但是list底层双向指针不能++、--来访问到下一个数据,这个时候要单独封装一个迭代器类模板实现需要的迭代器;

一、list的节点结构

	//节点结构
	template<class T>
	struct list_node
	{
		T _data;
		list_node<T>* _next;
		list_node<T>* _prev;

		list_node(const T& data = T())
			:_data(data),
			 _next(nullptr),
			 _prev(nullptr)
		{}
	};

这里需要给默认构造函数,插入数据的时候要申请节点,节点要初始化,没给值就用缺省参数,即内置类型会是0、空指针、0.0一类的,自定义类型会调用它们自己的默认构造函数;

二、迭代器结构

//list迭代器
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* node)
		:_node(node)
	{}
	//解引用迭代器获取数据
	Ref operator*()const
	{
		return _node->_data;
	}
	//返回数据地址,一般用于数据是自定义类型的,使用这个可以访问数据内部的成员变量
	Ptr operator->()const
	{
		return &(_node->_data);
	}
	//前置
	Self& operator++()
	{
		_node = _node->_next;
		return *this;
	}
	Self& operator--()
	{
		_node = _node->_prev;
		return *this;
	}
	//后置
	Self& operator++(int)
	{
		Self tmp(*this);//浅拷贝也可
		_node = _node->_next;
		return tmp;
	}
	Self& operator--(int)
	{
		Self tmp(*this);
		_node = _node->_prev;
		return tmp;
	}
	//判等
	bool operator==(const Self& lt)const
	{
		return _node == lt._node;
	}
	bool operator!=(const Self& lt)const
	{
		return _node != lt._node;
	}
};

组成list的基本结构是节点,不连续,要通过重载运算符的方式来使用迭代器;构造时要传一个节点指针,表示这个迭代器指向的节点是哪个;

模板参数多了Ref和Ptr,用于作为返回值类型,这样写原因:要实现两个类模板,这两个类模板中很多内容都是一样的,只是部分函数的返回值不一样;而Ref和Ptr可以接收不同的类型,将Ref和Ptr作为返回值,那么就可以将这两个类模板实现为一个了。

三、list结构

list的成员变量就是一个哨兵位;

迭代器和链表节点的类都设计成struct是因为要在list里面使用它们的成员变量;

private:
	typedef list_node<T> Node;
	Node* _head;
	size_t _size;

这是类中私有变量的内容,双向链表的实现需要一个哨兵位(list源码中的实现也是如此),而_size单纯是为了计数方便而设置的。

public:

中的内容就是list的一些接口:

1、迭代器模板参数传参的变化

//用不同的参数去调用相同的模板
typedef list_iterator<T, T&, T*> iterator;
typedef list_iterator<T, const T&, const T*> const_iterator;

这个是literator中的解引用调用时有两种,非const和const,而迭代器的类模板就的模板参数就可以接收不同类型的,在list的类模板中传两份过去,在使用时就可以根据需要去调用const或者非const的;

2、初始化

默构造认

list()
{
	empty_init();
}
void empty_init(const T& val=T())//若是空链表先要给一个哨兵位并且初始化
{
	_head = new Node(val);
	_head->_next = _head;
	_head->_prev = _head;
	_size = 0;
}

首先要有节点,那么当链表中没有节点时,就要先给哨兵位,并且让哨兵位的前后指针都指向自己,完成双向的实现;

拷贝构造:

list(list<T>& lt)//拷贝构造
{
	empty_init();
	for (auto& it : lt)
	{
		push_back(it);
	}
}

是空链表就要先给哨兵位,再利用迭代器遍历要被拷贝的链表,范围for自动解引用,那么每次的it都只是要被拷贝的链表lt中的每个节点的值,这样就实现了拷贝构造(每个节点的值都一样);

赋值重载:

list<T>& operator=(list<T> tmp)//赋值重载
{
	swap(tmp);
	return *this;
}
void swap(list<T>& tmp)
{
	std::swap(_head, tmp._head);
	std::swap(_size, tmp._size);
}

这里使用传统写法,将调用赋值重载时等式右边的链表拷贝一份存在tmp中,那么tmp就是被赋值的链表的一份临时拷贝,这是将tmp和调用赋值重载时等式左边的链表进行交换,这里的交换调用库中的swap,将tmp和赋值对象的成员变量进行交换,那么赋值对象就是tmp了;

n个val去初始化:

//n个 val 构造
list(int n, const T& val = T())
{
	empty_init();
	for (int i = 0; i < n; i++)
	{
		push_back(val);
	}
}

和拷贝构造类似;

一段迭代器构造:

//一段迭代器构造
template <class InputIterator>
list(InputIterator first, InputIterator last)
{
	empty_init();
	while (first != last)
	{
		push_back(*first);
		first++;
	}
}

迭代器所指的容器是随机的,但是数据要是相同类型,其他和拷贝构造类似;

初始化列表去初始化:

list(initializer_list<T> lt)//不能加引用&
{
	empty_init();
	for (auto& e : lt)
	{
		push_back(e);
	}
}

初始化列表是数组转换的,这里可以把lt就当成数组看;

//initializer_list<int> l = { 1,2,3,4,5 };
list<int> l1 = {1,2,3,4,5};
list<int> l2 = { 6,7,8,9,10 };

但是不能加引用,initializer_list和数组实际上不一样,底层是转换过的;

析构:

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

首先把节点都删了,这里的clear有一个技巧,就是erase,erase是删除所传迭代器指向的节点,并且删除完之后erase会返回这个所传迭代器的下一个节点的迭代器,那么每次删完it后it就会更新为下一个,那么最后就会删完;

删除玩之后要删除哨兵位。

3、迭代器接口

//迭代器
iterator begin()
{
	return _head->_next;//隐式类型转换
}
iterator end()
{
	return _head;
}
const_iterator cbegin()const
{
	return _head->_next;
}
const_iterator cend()const
{
	return _head;
}

这里的const_iterator的模板参数就是前面后两个参数带有const的那个。

4、容量

// 容量
size_t size()const
{
	return _size;
}
bool empty()const
{
	return _size == 0;
}

5、修改

//修改
iterator insert(iterator pos,const T& val)
{
	Node* newnode = new Node(val);//对于类类型的变量,new会自动调用默认构造函数
	Node* prev = pos._node->_prev;
	prev->_next = newnode;
	newnode->_prev = prev;
	newnode->_next = pos._node;
	pos._node->_prev = newnode;
	_size++;
	return pos;
}
iterator erase(iterator pos)
{
	assert(pos._node != _head);//确保链表里面有节点
	Node* next = pos._node->_next;
	Node* prev = pos._node->_prev;

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

	delete pos._node;
	_size--;
	return next;//返回下一个节点的迭代器,这个位置的节点被释放,返回也不能使用
}
void push_back(const T& val)
{
	insert(end(), val);
}
void push_front(const T& val)
{
	insert(begin(), val);
}
void pop_back()
{
	assert(_head->_next != _head);
	erase(--end());
}
void pop_front()
{
	assert(_head->_next != _head);
	erase(begin());
}

insert每次插入完之后要返回传过来的那个节点的迭代器,后面的都进行复用insert和erase。 

6、元素获取

////////////////////////////////////////////////////////////////////////////
//元素获取
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;

}
<think>首先,用户的问题是关于C++ list数据结构的模拟实现方法。用户意图是了解如何使用C++模拟实现list数据结构,这基于系统指令中的引用部分。 系统指令要求: - 所有行内数学表达式使用$...$格式。 - 独立公式使用$$...$$格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识。 - 回答结构清晰,帮助用户逐步解决问题。 参考的引用: - 引用[1]:提到了list模拟实现的框架,包括节点结构体模板、迭代器结构体模板、list结构体模板和测试函数。 - 引用[2]:详细描述了节点结构体的实现,包括数据域和指针域。 - 引用[3]:解释了__list_iterator结构体,用于封装迭代器,包括指针和运算符重载。 - 引用[4]:讨论了迭代器的解引用操作符重载。 我需要构建一个清晰的回答,逐步解释如何模拟实现C++list数据结构。listC++标准库中是一个双向链表。 回答结构: 1. **介绍**:简要说明list是什么,以及模拟实现的目的。 2. **核心组成部分**: - 节点结构体(ListNode):存储数据和前后指针。 - 迭代器类(iterator):封装节点指针,提供迭代功能。 - list类(list):管理链表,包括头尾指针和大小。 3. **详细实现**: - 节点结构体的定义。 - 迭代器类的定义,包括运算符重载(如++、--、*、->等)。 - list类的定义,包括构造函数、析构函数、插入、删除等操作。 4. **代码示例**:提供关键部分的代码片段,使用代码块格式。 5. **总结**:简要回顾实现要点。 6. **相关问题**:生成3-5个相关问题。 使用中文回答。 在引用部分末尾添加引用标识:[^1]、[^2]等。 生成相关问题: - 基于用户可能的兴趣,如性能分析、优化、应用场景等。 现在,组织内容: 1. **介绍**: - C++中的list是一个双向链表容器。模拟实现可以帮助理解其内部机制。 - 模拟实现主要包括三个部分:节点结构体、迭代器类和list类本身。 2. **节点结构体(ListNode)**: - 每个节点包含三个部分:数据域(_data)、指向前一个节点的指针(_prev)、指向后一个节点的指针(_next)。 - 使用模板以支持泛型。 - 示例代码: ```cpp template<class T> struct ListNode { ListNode<T>* _prev; ListNode<T>* _next; T _data; ListNode(const T& data = T()) : _prev(nullptr), _next(nullptr), _data(data) {} }; ``` 引用[2]描述了类似的实现。 3. **迭代器类(iterator)**: - 迭代器用于遍历链表,封装节点指针。 - 重载运算符:++(前置和后置)、--(前置和后置)、*(解引用)、->(成员访问)、==、!=等。 - 示例代码: ```cpp template<class T> struct __list_iterator { ListNode<T>* _node; __list_iterator(ListNode<T>* node) : _node(node) {} // 前置++ __list_iterator& operator++() { _node = _node->_next; return *this; } // 后置++ __list_iterator operator++(int) { __list_iterator tmp = *this; _node = _node->_next; return tmp; } // 前置-- __list_iterator& operator--() { _node = _node->_prev; return *this; } // 后置-- __list_iterator operator--(int) { __list_iterator tmp = *this; _node = _node->_prev; return tmp; } T& operator*() { return _node->_data; } T* operator->() { return &_node->_data; } bool operator!=(const __list_iterator& other) const { return _node != other._node; } bool operator==(const __list_iterator& other) const { return _node == other._node; } }; ``` 引用[3]和[4]提供了相关信息。 4. **list类(list)**: - 包含头节点指针(通常是一个哨兵节点,用于简化操作)。 - 成员函数:构造函数、析构函数、push_back、push_front、insert、erase、size等。 - 示例代码: ```cpp template<class T> class list { public: typedef __list_iterator<T> iterator; list() { _head = new ListNode<T>(); _head->_next = _head; _head->_prev = _head; _size = 0; } ~list() { clear(); delete _head; } iterator begin() { return iterator(_head->_next); } iterator end() { return iterator(_head); } void push_back(const T& value) { insert(end(), value); } void push_front(const T& value) { insert(begin(), value); } iterator insert(iterator pos, const T& value) { ListNode<T>* newNode = new ListNode<T>(value); ListNode<T>* cur = pos._node; ListNode<T>* prev = cur->_prev; prev->_next = newNode; newNode->_prev = prev; newNode->_next = cur; cur->_prev = newNode; _size++; return iterator(newNode); } iterator erase(iterator pos) { if (pos == end()) return pos; ListNode<T>* cur = pos._node; ListNode<T>* prev = cur->_prev; ListNode<T>* next = cur->_next; prev->_next = next; next->_prev = prev; delete cur; _size--; return iterator(next); } size_t size() const { return _size; } void clear() { iterator it = begin(); while (it != end()) { it = erase(it); } } private: ListNode<T>* _head; // 哨兵节点 size_t _size; }; ``` 5. **总结**: - 模拟实现list涉及节点、迭代器和list类。 - 关键点是使用哨兵节点简化边界条件处理。 6. **相关问题**: - 1. 如何优化list实现的性能? - 2. list与vector在C++中的主要区别是什么? - 3. 在实际项目中,list有哪些常见应用场景? - 4. 如何处理list中的异常安全? - 5. 迭代器的失效机制在list中是如何工作的? 确保在回答中引用相关引用: - 当提到框架时,引用[1]。 - 当描述节点结构时,引用[2]。 - 当讨论迭代器时,引用[3]和[4]。 最终回答结构: - 开头介绍。 - 详细部分分步骤。 - 代码块。 - 总结。 - 相关问题。</think>### C++ list数据结构模拟实现方法 在C++中,`std::list`是一个双向链表容器,其模拟实现涉及三个核心部分:**节点结构体**、**迭代器类**和**list类**本身。下面我将逐步解释实现方法,确保内容清晰易懂。实现基于模板以支持泛型编程,并使用哨兵节点简化边界处理。关键代码使用C++语法,并遵循标准库的设计思想。 #### 1. **节点结构体(ListNode)** 节点是链表的基本单元,存储数据、前驱和后继指针。使用结构体模板定义: ```cpp template<class T> struct ListNode { ListNode<T>* _prev; // 指向前驱节点 ListNode<T>* _next; // 指向后继节点 T _data; // 存储数据 // 构造函数:初始化指针为nullptr,数据域使用默认值 ListNode(const T& data = T()) : _prev(nullptr), _next(nullptr), _data(data) {} }; ``` - **作用**:每个节点维护双向链接,支持高效插入和删除。 - **细节**:构造函数使用`T()`提供默认初始化,确保兼容各种数据类型[^2]。 #### 2. **迭代器类(__list_iterator)** 迭代器封装节点指针,提供类似指针的接口,支持遍历操作。重载运算符是关键: ```cpp template<class T> struct __list_iterator { ListNode<T>* _node; // 指向当前节点 // 构造函数:基于节点指针初始化 __list_iterator(ListNode<T>* node = nullptr) : _node(node) {} // 前置++:移动到下一个节点 __list_iterator& operator++() { _node = _node->_next; return *this; } // 后置++:返回当前迭代器后移动 __list_iterator operator++(int) { __list_iterator tmp = *this; // 拷贝当前状态 _node = _node->_next; return tmp; // 返回移动前的迭代器 } // 前置--:移动到前一个节点 __list_iterator& operator--() { _node = _node->_prev; return *this; } // 后置--:返回当前迭代器后移动 __list_iterator operator--(int) { __list_iterator tmp = *this; _node = _node->_prev; return tmp; } // 解引用:返回节点数据的引用 T& operator*() { return _node->_data; } // 箭头操作符:返回节点数据的指针 T* operator->() { return &_node->_data; } // 比较操作符:检查迭代器是否相等 bool operator==(const __list_iterator& other) const { return _node == other._node; } bool operator!=(const __list_iterator& other) const { return _node != other._node; } }; ``` - **作用**:实现类似标准迭代器的行为,包括`++`、`--`、`*`和`->`等操作。 - **细节**:后置`++`和`--`通过拷贝构造保留原状态,确保符合STL规范[^3][^4]。 #### 3. **list类(list)** list类管理整个链表,包括哨兵节点、大小维护和基本操作(如插入、删除)。使用哨兵节点(头节点)简化逻辑: ```cpp template<class T> class list { public: typedef __list_iterator<T> iterator; // 定义迭代器类型 // 构造函数:初始化哨兵节点(自环) list() { _head = new ListNode<T>(); // 创建哨兵节点 _head->_next = _head; // 初始化为自环 _head->_prev = _head; _size = 0; // 大小初始为0 } // 析构函数:释放所有节点 ~list() { clear(); // 清空链表 delete _head; // 删除哨兵节点 } // 迭代器接口 iterator begin() { return iterator(_head->_next); // 首元素是哨兵的下一个 } iterator end() { return iterator(_head); // 尾后位置是哨兵自身 } // 插入元素到指定位置前 iterator insert(iterator pos, const T& value) { ListNode<T>* newNode = new ListNode<T>(value); // 创建新节点 ListNode<T>* cur = pos._node; // 当前位置节点 ListNode<T>* prev = cur->_prev; // 前驱节点 // 链接新节点 prev->_next = newNode; newNode->_prev = prev; newNode->_next = cur; cur->_prev = newNode; _size++; // 更新大小 return iterator(newNode); // 返回新节点的迭代器 } // 删除指定位置元素 iterator erase(iterator pos) { if (pos == end()) return pos; // 不能删除哨兵 ListNode<T>* cur = pos._node; ListNode<T>* prev = cur->_prev; ListNode<T>* next = cur->_next; // 绕过当前节点 prev->_next = next; next->_prev = prev; delete cur; // 释放节点 _size--; // 更新大小 return iterator(next); // 返回下一个位置的迭代器 } // 添加元素到尾部 void push_back(const T& value) { insert(end(), value); } // 添加元素到头部 void push_front(const T& value) { insert(begin(), value); } // 清空链表 void clear() { iterator it = begin(); while (it != end()) { it = erase(it); // 逐个删除 } } // 获取链表大小 size_t size() const { return _size; } private: ListNode<T>* _head; // 哨兵节点(不存储数据) size_t _size; // 链表大小 }; ``` - **作用**:提供链表的核心功能,包括构造、析构、插入、删除和遍历。 - **细节**: - 哨兵节点`_head`形成自环,`begin()`指向第一个数据节点,`end()`指向哨兵,简化边界处理。 - `insert`和`erase`操作时间复杂度为$O(1)$,符合链表特性。 - `clear`和析构函数确保无内存泄漏[^1]。 #### 4. **测试示例** 简单测试代码: ```cpp #include <iostream> int main() { list<int> myList; myList.push_back(10); myList.push_front(20); auto it = myList.begin(); std::cout << *it << std::endl; // 输出20 it = myList.erase(it); std::cout << *it << std::endl; // 输出10 return 0; } ``` #### 总结 模拟实现C++ list的关键是: - **节点结构体**:管理数据和指针。 - **迭代器类**:封装遍历逻辑,通过运算符重载提供STL兼容接口。 - **list类**:使用哨兵节点简化操作,实现高效插入、删除和大小维护。 这种实现帮助深入理解链表底层机制,但实际开发中推荐使用标准库`std::list`以获得优化和安全性[^1][^3]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值