4.swap
void swap(list<T>& x) { std::swap(_head, x._head); // 交换两个链表的头结点指针 }
这是 list
类的成员函数 swap
,它用于交换两个链表的内容。
在这个函数中,通过调用标准库函数 std::swap
,将当前链表的头结点指针 _head
与另一个链表 x
的头结点指针 _head
进行交换。这个操作会导致两个链表的内容被互相交换,但是实际上并没有复制链表中的元素,只是交换了链表的结构。
这种交换操作通常在需要交换两个对象的内容时使用,它可以在不复制数据的情况下实现两个对象之间的内容互换,从而提高了效率。
需要注意的是,这个函数只是交换了链表的头结点指针,而链表中的元素顺序没有改变。
5.析构函数定义
~list() { clear(); // 清空链表中的所有元素 delete _head; // 删除头结点 _head = nullptr; // 将头结点指针置为空指针 }
这是 list
类的析构函数,用于销毁链表对象。
在这个析构函数中,首先调用了成员函数 clear()
来清空链表中的所有元素,确保在删除链表之前释放所有资源。然后,使用 delete
关键字释放头结点的内存。最后,将头结点指针 _head
置为空指针,以避免出现野指针。
这样,在链表对象被销毁时,它所占用的内存将会被正确地释放,从而防止内存泄漏。
6.clear()
void clear() { iterator it = begin(); // 获取链表的起始迭代器 while (it != end()) // 遍历链表 { it = erase(it); // 使用 erase() 函数删除当前元素,并将迭代器指向下一个元素 } }
这是 list
类的 clear()
成员函数,用于清空链表中的所有元素。
在这个函数中,首先获取链表的起始迭代器 begin()
,然后通过一个循环遍历链表中的所有元素。在循环内部,调用了 erase()
函数来删除当前迭代器指向的元素,并将迭代器更新为指向被删除元素的下一个元素。这样可以确保链表中的所有元素都被逐个删除。
需要注意的是,erase()
函数在删除元素后会返回指向下一个元素的迭代器,因此在每次循环迭代时更新迭代器是必要的,以便继续正确地遍历链表。
总之,这个 clear()
函数用于释放链表中的所有元素,并确保链表变为空链表。
7.push_back
void push_back(const T& x) { Node* tail = _head->_prev; // 获取当前链表的末尾节点 Node* newnode = new Node(x); // 创建一个新节点,保存新元素 x tail->_next = newnode; // 更新末尾节点的下一个指针,指向新节点 newnode->_prev = tail; // 新节点的前一个指针指向原末尾节点 newnode->_next = _head; // 新节点的下一个指针指向头节点 _head->_prev = newnode; // 头节点的前一个指针指向新节点,完成插入操作 }
这是 list
类的 push_back()
成员函数,用于在链表的末尾添加一个新元素。
在这个函数中,首先获取当前链表的末尾节点(尾节点的 _prev
指向链表的最后一个元素),然后创建一个新的节点 newnode
,并将新元素 x
存储在新节点中。接着,更新末尾节点的 _next
指针,使其指向新节点,然后更新新节点的 _prev
指针,使其指向原末尾节点。同时,将新节点的 _next
指针指向头节点,完成循环链表的连接。最后,更新头节点的 _prev
指针,使其指向新节点,确保链表的连接是完整的。
需要注意的是,这个函数在链表末尾添加了一个新元素,不影响其他元素的相对位置。
8.push_front
void push_front(const T& x) { insert(begin(), x); // 调用 insert 函数,在头部插入新元素 x }
这个函数简单地调用了 insert
函数,将新元素 x
插入到链表的头部。
9.insert
iterator insert(iterator pos, const T& x) { Node* cur = pos._node; // 获取当前位置的节点 Node* prev = cur->_prev; // 获取当前位置节点的前一个节点 Node* newnode = new Node(x); // 创建一个新节点,保存新元素 x prev->_next = newnode; // 更新前一个节点的下一个指针,指向新节点 newnode->_prev = prev; // 新节点的前一个指针指向前一个节点 newnode->_next = cur; // 新节点的下一个指针指向当前位置节点 cur->_prev = newnode; // 当前位置节点的前一个指针指向新节点,完成插入操作 return iterator(newnode); // 返回指向新节点的迭代器 }
在 insert
函数中,首先获取当前位置节点 pos
和其前一个节点 prev
,然后创建一个新节点 newnode
并将新元素 x
存储在新节点中。接着,更新前一个节点的 _next
指针,使其指向新节点,然后更新新节点的 _prev
指针,使其指向前一个节点。同时,将新节点的 _next
指针指向当前位置节点 cur
,完成插入操作。最后,更新当前位置节点的 _prev
指针,使其指向新节点,确保链表的连接是完整的。最后,函数返回一个指向新节点的迭代器,表示插入操作完成后的位置。
10.erase
iterator erase(iterator pos) { assert(pos != end()); // 断言:确保 pos 不等于链表的结束迭代器 Node* cur = pos._node; // 获取当前位置的节点 Node* prev = cur->_prev; // 获取当前位置节点的前一个节点 Node* next = cur->_next; // 获取当前位置节点的后一个节点 prev->_next = next; // 更新前一个节点的下一个指针,指向后一个节点 next->_prev = prev; // 更新后一个节点的前一个指针,指向前一个节点 delete cur; // 删除当前位置的节点 return iterator(next); // 返回指向后一个节点的迭代器,表示删除操作完成后的位置 }
这部分代码实现了从链表中删除指定位置元素的功能。
在 erase
函数中,首先使用断言来确保删除位置 pos
不是链表的结束迭代器。然后,获取当前位置节点 pos
、其前一个节点 prev
和后一个节点 next
。接着,更新前一个节点的 _next
指针,使其指向后一个节点,同时更新后一个节点的 _prev
指针,使其指向前一个节点。这样,当前位置节点就从链表中断开了。最后,释放当前位置节点的内存空间,并返回一个指向后一个节点的迭代器,表示删除操作完成后的位置。
11.pop_back()和pop_front()
void pop_back() { erase(--end()); // 通过 erase 函数删除链表末尾的元素 }
pop_back
函数通过将链表的结束迭代器向前移动一个位置,然后调用 erase
函数来删除该位置的元素,实现了从链表末尾删除一个元素的操作。
void pop_front() { erase(begin()); // 通过 erase 函数删除链表头部的元素 }
pop_front
函数直接调用 erase
函数,删除链表头部的元素,实现了从链表头部删除一个元素的操作。
这两个函数分别对应于从链表的末尾和头部删除元素的操作,通过调用 erase
函数来完成删除操作,从而保持了链表的连接性。
12.empty()、size()、front()和back()
bool list<T>::empty() const { return begin() == end(); // 判断 begin() 是否等于 end() 来确定是否为空 } typename list<T>::size_t list<T>::size() const { size_t count = 0; for (const_iterator it = begin(); it != end(); ++it) { ++count; } return count; // 遍历链表来计算元素个数 } typename list<T>::reference list<T>::front() { assert(!empty()); // 如果链表为空,则抛出断言 return *begin(); // 返回链表的第一个元素 } typename list<T>::const_reference list<T>::front() const { assert(!empty()); // 如果链表为空,则抛出断言 return *begin(); // 返回链表的第一个元素 } typename list<T>::reference list<T>::back() { assert(!empty()); // 如果链表为空,则抛出断言 return *(--end()); // 返回链表的最后一个元素 } typename list<T>::const_reference list<T>::back() const { assert(!empty()); // 如果链表为空,则抛出断言 return *(--end()); // 返回链表的最后一个元素 }
上述代码实现了 empty()
函数用于判断链表是否为空,size()
函数用于获取链表元素个数,front()
函数用于获取链表第一个元素,以及 back()
函数用于获取链表最后一个元素。注意函数中的断言用于确保在链表为空时不执行非法操作。
13.resize
template<class T> void list<T>::resize(size_t n, const T& val) { if (n < size()) { iterator it = begin(); std::advance(it, n); // 定位到新的尾部位置 while (it != end()) { it = erase(it); // 从尾部截断,删除多余的元素 } } else if (n > size()) { insert(end(), n - size(), val); // 插入足够数量的默认值元素 } }
如果 n
小于当前链表的大小size()
,意味着你想要缩小链表的大小。在这种情况下,函数会迭代遍历链表,从尾部开始删除多余的元素,直到链表的大小等于 n
。
首先,函数会使用 begin()
获取链表的起始迭代器,然后使用 std::advance
函数将迭代器向前移动 n
个位置,使其指向新的尾部位置。
接下来,函数使用 erase
函数将从新尾部位置到链表末尾的所有元素删除,从而使链表的大小减小到 n
。
如果 n
大于当前链表的大小,表示你想要增大链表的大小。在这种情况下,函数会插入足够数量的值为 val
的元素到链表的末尾,直到链表的大小等于 n
。
函数会使用 insert
函数在链表的末尾插入 n - size()
个值为 val
的元素,从而使链表的大小增大到 n
。
14.赋值运算符重载
list<T>& operator=(list<T> lt) { swap(lt); // 交换当前对象和传入对象的内容 return *this; // 返回当前对象的引用 }
这是一个赋值运算符重载函数,它采用了拷贝并交换(Copy and Swap)的技巧来实现赋值操作。
这是一个成员函数,用于将一个 list<T>
类型的对象赋值给当前的对象。
参数 lt 是通过值传递的,所以会调用 list<T>
的拷贝构造函数创建一个临时副本。
然后,通过 swap(lt)
调用 swap
函数来交换当前对象和临时副本的内容。在交换后,临时副本的数据会被赋值给当前对象,而临时副本会被销毁。由于交换是高效的操作,可以避免大量的数据复制。
最后,函数返回当前对象的引用,以支持连续赋值操作。
这种方式不仅避免了手动管理内存的麻烦,还确保了异常安全,因为临时副本在函数结束时会被正确销毁。
list模拟实现全部代码
#pragma once #include <assert.h> #include <iostream> namespace xzq { template<class T> struct list_node { T _data; list_node<T>* _next; list_node<T>* _prev; list_node(const T& x = T()) :_data(x) , _next(nullptr) , _prev(nullptr) {} }; template<class T, class Ref, class Ptr> struct __list_iterator { typedef list_node<T> Node; typedef __list_iterator<T, Ref, Ptr> iterator; typedef bidirectional_iterator_tag iterator_category; typedef T value_type; typedef Ptr pointer; typedef Ref reference; typedef ptrdiff_t difference_type; Node* _node; __list_iterator(Node* node) :_node(node) {} bool operator!=(const iterator& it) const { return _node != it._node; } bool operator==(const iterator& it) const { return _node == it._node; } Ref operator*() { return _node->_data; } Ptr operator->() { return &(operator*()); } // ++it iterator& operator++() { _node = _node->_next; return *this; } // it++ iterator operator++(int) { iterator tmp(*this); _node = _node->_next; return tmp; } // --it iterator& operator--() { _node = _node->_prev; return *this; } // it-- iterator operator--(int) { iterator tmp(*this); _node = _node->_prev; return tmp; } }; template<class T> class list { typedef list_node<T> Node; public: typedef __list_iterator<T, T&, T*> iterator; typedef __list_iterator<T, const T&, const T*> const_iterator; typedef __reverse_iterator<iterator, T&, T*> reverse_iterator; typedef __reverse_iterator<const_iterator, const T&, const T*> const_reverse_iterator; const_iterator begin() const { return const_iterator(_head->_next); } const_iterator end() const { return const_iterator(_head); } iterator begin() { return iterator(_head->_next); } iterator end() { return iterator(_head); } reverse_iterator rbegin() { return reverse_iterator(end()); } reverse_iterator rend() { return reverse_iterator(begin()); } void empty_init() { _head = new Node; _head->_next = _head; _head->_prev = _head; } template <class InputIterator> list(InputIterator first, InputIterator last) { empty_init(); while (first != last) { push_back(*first); ++first; } } list() { empty_init(); } void swap(list<T>& x) { std::swap(_head, x._head); } list(const list<T>& lt) { empty_init(); list<T> tmp(lt.begin(), lt.end()); swap(tmp); } list<T>& operator=(list<T> lt) { swap(lt); return *this; } ~list() { clear(); delete _head; _head = nullptr; } void clear() { iterator it = begin(); while (it != end()) { it = erase(it); } } void push_back(const T& x) { Node* tail = _head->_prev; Node* newnode = new Node(x); tail->_next = newnode; newnode->_prev = tail; newnode->_next = _head; _head->_prev = newnode; //insert(end(), x); } void push_front(const T& x) { insert(begin(), x); } iterator insert(iterator pos, const T& x) { Node* cur = pos._node; Node* prev = cur->_prev; Node* newnode = new Node(x); prev->_next = newnode; newnode->_prev = prev; newnode->_next = cur; cur->_prev = newnode; return iterator(newnode); } void pop_back() { erase(--end()); } void pop_front() { erase(begin()); } 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); } bool list<T>::empty() const { return begin() == end(); // 判断 begin() 是否等于 end() 来确定是否为空 } typename list<T>::size_t list<T>::size() const { size_t count = 0; for (const_iterator it = begin(); it != end(); ++it) { ++count; } return count; // 遍历链表来计算元素个数 } typename list<T>::reference list<T>::front() { assert(!empty()); // 如果链表为空,则抛出断言 return *begin(); // 返回链表的第一个元素 } typename list<T>::const_reference list<T>::front() const { assert(!empty()); // 如果链表为空,则抛出断言 return *begin(); // 返回链表的第一个元素 } typename list<T>::reference list<T>::back() { assert(!empty()); // 如果链表为空,则抛出断言 return *(--end()); // 返回链表的最后一个元素 } typename list<T>::const_reference list<T>::back() const { assert(!empty()); // 如果链表为空,则抛出断言 return *(--end()); // 返回链表的最后一个元素 } void resize(size_t n, const T& val = T()) { if (n < size()) { iterator it = begin(); std::advance(it, n); while (it != end()) { it = erase(it); // 从尾部截断,删除多余的元素 } } else if (n > size()) { insert(end(), n - size(), val); // 插入足够数量的默认值元素 } } private: Node* _head; }; }
list 和 vector的区别
通过模拟实现 list
和 vector
,你可以更好地理解它们之间的区别和特点。这两者是 C++ 标准库中的序列式容器,但它们在内部实现和使用场景上有很大的区别。
底层实现:
list
通常是一个双向链表,每个节点都包含了数据和指向前一个和后一个节点的指针。这使得在任何位置进行插入和删除操作都是高效的,但随机访问和内存占用可能相对较差。
vector
是一个动态数组,元素在内存中是连续存储的。这使得随机访问非常高效,但插入和删除操作可能需要移动大量的元素,效率较低。
插入和删除操作:
在
list
中,插入和删除操作是高效的,无论是在容器的任何位置还是在开头和结尾。这使得list
在需要频繁插入和删除操作时非常适用。在
vector
中,插入和删除操作可能需要移动元素,特别是在容器的中间或开头。因此,当涉及大量插入和删除操作时,vector
可能不如list
效率高。
随机访问:
list
不支持随机访问,即不能通过索引直接访问元素,必须通过迭代器逐个遍历。
vector
支持随机访问,可以通过索引快速访问元素,具有良好的随机访问性能。
内存占用:
由于
list
使用链表存储元素,每个节点都需要额外的指针来指向前后节点,可能会导致更多的内存占用。
vector
在内存中是连续存储的,通常占用的内存较少。
迭代器失效:
在
list
中,插入和删除操作不会导致迭代器失效,因为节点之间的关系不会改变。在
vector
中,插入和删除操作可能导致内存重新分配,从而使原来的迭代器失效。
综上所述,如果你需要频繁进行插入和删除操作,而对于随机访问性能没有特别高的要求,那么使用 list
是一个不错的选择。而如果你更注重随机访问性能,可以选择使用 vector
。当然,在实际开发中,还需要根据具体情况权衡使用哪种容器。
结语
有兴趣的小伙伴可以关注作者,如果觉得内容不错,请给个一键三连吧,蟹蟹你哟!!!
制作不易,如有不正之处敬请指出
感谢大家的来访,UU们的观看是我坚持下去的动力
在时间的催化剂下,让我们彼此都成为更优秀的人吧!!!
(创作128天纪念日,暂时还没有想好怎么分享,下次再写喽)