C++ 链表List使用与实现:拷贝交换与高效迭代器细致讲解
目录
list的使用:
构造与赋值
元素访问
修改操作
容量查询
链表特有操作
拼接(Splice)
C++11 新增方法
注意:
stl_list的模拟实现:
一、链表节点设计的艺术
1.1 结构体 vs 类的选择
二、迭代器实现的精髓
2.1 为什么需要自定义迭代器?(迭代器在本文章后面还会更新)
2.2 运算符重载的细节实现
三、链表类的完整实现
3.1 构造函数与哨兵节点
3.2 迭代器范围获取
3.3 核心操作实现解析
插入操作:insert()
尾插:push_back()
头插:push_front()
删除操作:erase()
尾删:pop_back()
头删:pop_front()
辅助函数实现size()、empty()
析构函数销毁链表x~list()、清除数据clear()
拷贝构造list(const list& lt)
赋值list& operator==()、swap()
为什么参数要「按值传递」?
运作原理:
优势:
四、关键问题深度探讨
自定义类型(更新原iterator类)编辑
3.方法三:重载 operator->()
内存布局可视化
PrintList()、const_iterator
方法二:方法一使代码冗余,我们需要学习一种方法能够解决这个问题。
完整代码:
本文章主要简要讲解list容器的使用与详细讲解list模拟实现以及相关问题上,所用到完整代码在文章末尾
list的使用:
构造与赋值
-
构造函数
list<int> lt; // 空链表 list<int> lt2(5, 10); // 5 个元素,每个初始化为 10 list<int> lt3(lt2.begin(), lt2.end()); // 通过迭代器范围构造 list<int> lt4 = {1, 2, 3}; // 初始化列表(C++11)
-
赋值操作
lt = lt2; // 深拷贝赋值 lt.assign(3, 100); // 替换内容为 3 个 100 lt.assign(lt2.begin(), lt2.end()); // 通过迭代器赋值
元素访问
-
首尾元素front()、back()
int front = lt.front(); // 首元素(不可修改空链表) int back = lt.back(); // 尾元素(不可修改空链表)
-
迭代器begin()、rbegin()
list<int>::iterator it = lt.begin(); // 正向迭代器 list<int>::reverse_iterator rit = lt.rbegin(); // 反向迭代器
修改操作
-
插入
lt.push_front(0); // 头部插入 lt.push_back(4); // 尾部插入 auto pos = lt.begin(); lt.insert(pos, 99); // 在迭代器位置前插入 lt.insert(pos, 3, 88); // 插入 3 个 88
-
删除
lt.pop_front(); // 删除头部元素 lt.pop_back(); // 删除尾部元素 lt.erase(pos); // 删除迭代器位置元素 lt.erase(pos, lt.end()); // 删除迭代器范围 lt.remove(4); // 删除所有值为 4 的节点
-
清空链表(clear)
lt.clear(); // 清空所有元素
容量查询
bool isEmpty = lt.empty(); // 是否为空
size_t size = lt.size(); // 元素个数(O(n) 时间复杂度!)
lt.resize(10); // 调整链表大小
链表特有操作
-
拼接(Splice)
// 将 lt2 的全部内容移动到 lt 的 pos 位置前 lt.splice(pos, lt2); // lt2 会被清空 // 移动单个元素 auto it2 = lt2.begin(); //pos为目标位置,it2为需要移动的单个元素,拿走it2,再尾差pos,改变节点的指向 lt.splice(pos, lt2, it2); // 移动范围 lt.splice(pos, lt2, lt2.begin(), lt2.end());
注意:被转移的节点会被清空
-
合并(Merge)
// 前提:lt 和 lt2 必须已按相同顺序排序(升序或降序) lt.sort(); // 默认升序 lt2.sort(); lt.merge(lt2); // 合并后 lt2 为空
-
排序(Sort)
lt.sort(); // 默认升序 lt.sort(greater<int>()); // 降序(需包含 <functional>)
-
去重(Unique)
lt.sort(); // 必须先排序! lt.unique(); // 删除连续重复元素
-
反转(Reverse)
lt.reverse(); // 反转链表顺序
C++11 新增方法
-
原地构造(Emplace)
lt.emplace_front(10); // 在头部直接构造对象(避免拷贝) lt.emplace_back(20); // 尾部构造 lt.emplace(pos, 30); // 指定位置构造
-
移动语义
list<int> lt5 = std::move(lt); // 移动构造(高效转移资源)
注意:
-
merge 前必须排序
// 错误示例:lt 是降序,lt1 是升序 lt.sort(greater<int>()); // 降序 lt1.sort(); // 升序 lt.merge(lt1); // 未定义行为! // 正确做法:统一顺序 lt.sort(); // 升序 lt1.sort(); lt.merge(lt1);
-
unique 必须配合 sort
// 错误用法:未排序时去重 lt.unique(); // 只能删除连续重复 // 正确用法 lt.sort(); lt.unique(); // 删除所有重复
-
迭代器失效
-
在插入/删除操作后,指向被修改位置的迭代器会失效,需重新获取。
-
stl_list的模拟实现:
一、链表节点设计的艺术
1.1 结构体 vs 类的选择
template<class T>
struct ListNode {
ListNode<T>* _next; // 后继指针
ListNode<T>* _prev; // 前驱指针
T _data; // 存储数据
// 默认构造函数初始化三要素
ListNode(const T& x = T())
:_next(nullptr),
_prev(nullptr),
_data(x)
{}
};
设计要点分析:
使用
struct
而非class
的深层考量:
默认public访问权限,便于链表类直接操作节点指针
符合C++标准库实现惯例,增强代码可读性
节点本身无需封装,属于链表内部实现细节
三指针设计哲学:
_next
和_prev
构成双向链接的基石
_data
采用模板类型,支持任意数据类型存储默认构造函数的巧妙设计:同时支持无参构造和值初始化
内存布局可视化:
+------+ +------+ +------+ | prev |<-->| prev |<-->| prev | | data | | data | | data | | next |--->| next |--->| next | +------+ +------+ +------+
二、迭代器实现的精髓
2.1 为什么需要自定义迭代器?(迭代器在本文章后面还会更新)
template<class T>
struct ListIterator {
typedef ListNode<T> Node;
typedef ListIterator<T> Self;
Node* _node; // 核心:封装节点指针
// 构造函数实现指针到迭代器的转换
ListIterator(Node* node):_node(node){}
};
关键问题解答:
原生指针的局限性:
无法直接通过++操作跳转到下一个节点
解引用操作不符合链表数据访问需求
无法正确比较链表节点的位置关系
迭代器设计模式的优势:
统一容器访问接口
隐藏底层数据结构差异
支持算法泛型编程
2.2 运算符重载的细节实现
// 前置++:直接修改自身
Self& operator++() {
_node = _node->_next; // 关键跳转逻辑
return *this;
}
// 后置++:返回临时对象
Self operator++(int) {
Self tmp(*this); // 拷贝当前状态
_node = _node->_next;
return tmp; // 返回旧值
}
//1. --it
Self& operator--()
{
_node = _node->_prev;
return *this;
}
//2. it--
Self operator--(int)
{
Self tmp(*this);
_node = _node->_prev;
return tmp;
}
// 解引用:访问节点数据
T& operator*() {
return _node->_data; // 返回引用支持修改
}
//两个迭代器是否相等?
bool operator!=(const Self& it)
{
//即比较节点的指针,节点的指针相同,他们就相同
return _node != it._node;
}
// 相等性比较的本质
bool operator==(const Self& it) {
return _node == it._node; // 指针地址比较
}
实现细节剖析:
前置与后置++的差异:
性能考量:避免不必要的拷贝
语法区分:int参数占位符
解引用操作的注意事项:
返回引用以实现左值操作
与const迭代器的兼容性设计
比较大小是否需要重载?不需要,一个类是否要重载一个运算符,需要看他是否有意义,节点后面的地址不能保证比前面的地址大,所以没什么意义。不需要重载
三、链表类的完整实现
3.1 构造函数与哨兵节点
重新定义类型名称便于阅读:
template<class T>
class list
{
typedef ListNode<T> Node;
public:
//在这里定义好类型
typedef ListIterator<T> iterator;
};
list() {
_head = new Node; // 创建哨兵节点
_head->_next = _head; // 自环初始化
_head->_prev = _head;
_size = 0; // 尺寸计数器
}
哨兵节点的精妙之处:
统一空链表和非空链表的操作
简化边界条件处理
保证迭代器end()的有效性
内存布局示意图:
[HEAD] <-> [NODE1] <-> [NODE2] <-> [HEAD]
3.2 迭代器范围获取
iterator begin() { // 1.有名对象 //iterator it(_head->_next); //return it; // 2.隐式类型转换(单参数的构造函数支持隐式类型的转换): //return _head->_next; // 3.匿名对象 return iterator(_head->_next); // 隐式转换 } iterator end() { return iterator(_head); // 哨兵即终点 }
统一使用匿名对象构造迭代器
begin()指向首元节点,end()指向哨兵节点
支持C++11范围for循环的关键
3.3 核心操作实现解析
插入操作:insert()
选定位置插入:insert()
//在pos位置插入data
void insert(iterator pos, const T& data) {
Node* current = pos._node;
Node* newnode = new Node(data); // 内存申请
// 四步链接法
Node* prev = current->_prev;
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = current;
current->_prev = newnode;
++_size; // 维护尺寸
}
尾插:push_back()
void push_back(const T& x)
{
//1.还未写insert()时写的push_back()
//Node* newnode = new Node(x);
//Node* tail = _head->_prev; //指定被插的节点
//tail->_next = newnode;
//newnode->_prev = tail;
//newnode->_next = _head;
//_head->_prev = newnode;
//2.写了insert()后写的push_back()
insert(end(), x);
}
头插:push_front()
void push_front(const T& x)
{
insert(begin(), x);
}
四步链接法示意图:
Before: [PREV] <-> [CURRENT]
After: [PREV] <-> [NEWNODE] <-> [CURRENT]
删除操作:erase()
iterator erase(iterator pos) { Node* cur = pos._node; Node* prev = cur->_prev; Node* next = cur->_next; // 重新链接相邻节点 prev->_next = next; next->_prev = prev; delete cur; // 释放内存 --_size; // 更新尺寸 return iterator(next); // 返回有效迭代器 }
迭代器失效问题:
删除操作使当前迭代器失效
返回新迭代器的必要性
正确使用模式示例:
auto it = lst.begin(); while(it != lst.end()) { if(condition) { it = lst.erase(it); // 接收返回值 } else { ++it; } }
尾删:pop_back()
//尾删 ,要--,不然删的是head(哨兵位) void pop_back() { erase(--end()); }
头删:pop_front()
//头删 void pop_front() { erase(begin()); }
辅助函数实现size()、empty()
size_t size() const { return _size; } // O(1)时间复杂度 bool empty() { return _size == 0; } // 高效判空
性能优化点:
使用_size变量避免遍历计数
empty()的常数时间复杂度
clear()的通用实现方式
析构函数销毁链表x~list()、清除数据clear()
//清除掉所有数据,有没有清除掉头结点?并没有(在析构函数中清除)
void clear()
{
iterator it = begin();
while (it != end())
{
it = erase(it);
}
}
~list()
{
clear();
delete _head;
_head = nullptr;
}
拷贝构造list(const list<T>& lt)
为了拷贝构造的实现方便我们将空链表的制作做了一个成员函数,因此默认的无参构造函数可以直接使用这个成员函数
void empty_init() { //创建一个头结点 _head = new Node; _head->_next = _head; _head->_prev = _head; _size = 0;x } //构造函数 list() { empty_init(); }
拷贝构造,默认拷贝构造是浅拷贝,所以需要我们自己实现一个深拷贝(防止指向同一个空间,析构两次同一空间导致错误)
//lt2(lt1) list(const list<T>& lt) { //首先:在这里我们先创建空链表 //也就是:lt2.empty_init()直接给lt2创建一个空链表即创建一个哨兵位头结点 empty_init(); //直接遍历一遍lt1,尾插 for (auto& e : lt)//这里一定要加引用,因为如果T是string,就浅拷贝了 { push_back(e); } }
赋值list<T>& operator==()、swap()
调用库里的swap(<algorithm>)
void swap(list<T>& lt)
{
std::swap(_head, lt._head);
std::swap(_size, lt._size);
}
//赋值
//lt1 = lt3
list<T>& operator=(list<T> lt) //lt = lt3;
{
swap(lt);
}
为什么参数要「按值传递」?
我的代码中赋值运算符的实现如下:
list<T>& operator=(list<T> lt) { // 参数按值传递 swap(lt); return *this; }
运作原理:
按值传递参数:调用
operator=
时,lt
是传入对象的副本(触发拷贝构造函数)。交换资源:通过
swap
将当前对象的_head
和_size
与副本lt
交换。自动析构副本:函数结束时,临时副本
lt
被析构,释放原对象的旧资源。优势:
天然处理自我赋值:即使
lt1 = lt1;
,参数lt
是原对象的副本,交换后旧资源由副本释放。异常安全:拷贝过程(生成
lt
)在交换前完成,若拷贝失败不会影响原对象。
小技巧:需要析构,就需要自己写深拷贝,不需要析构,默认浅拷贝
四、关键问题深度探讨
自定义类型(更新原iterator类)
出现报错的原因是:内置类型才支持流插入提取,而A是自定义类型,所以在这里不支持,需要自己写运算符重载。
1.方法一:改变提取方式
cout << (*it)._a1 << ":" << (*it)._a2 << endl;
2.方法二:写流插入
省略
3.方法三:重载 operator->()
//it-> T* operator->() { return &_node->_data;//返回的是_data的地址 }
实际上用箭头会方便很多。
编译器为了可读性,省略了一个→
cout << it->_a1 << ":" << it->_a2 << endl;
实现原理:
编译器隐藏了一个箭头实际上是:
it->->_a1
(it.operator->())->_a1, (it.operator->())返回的是data的地址即T*,在这里就是我们的自定义类型 A*,有了A*此时就可以访问直接访问结构体成员_a1,_a2。从而得到他们的值。这里将it的行为当成A* 。
内存布局可视化
+---------------------+ | ListIterator对象 | | _node: 0x1000 | +---------------------+ | v +---------------------+ +---------------------+ | ListNode<A>节点 | | A结构体实例 | | _prev: 0x2000 | | _a1: 1 | | _next: 0x3000 | | _a2: 5 | | _data: 0x4000 |--->+---------------------+ +---------------------+
当调用
it->_a1
时:
通过
_node
找到数据节点获取
_data
的内存地址(0x4000)通过指针访问结构体成员
_a1
PrintList()、const_iterator
因为现在的clt是一个const成员变量,而begin()、end()还未加const
加完const后编译通过:权限可以缩小但是不能扩大,非const成员可以调用const成员,const成员不能调用非const成员
iterator begin() const
{
return iterator(_head->_next);
}
iterator end() const
{
return iterator(_head);
}
这样真的就没有问题了吗?
按理来说,普通迭代器是能够修改的,但这里是const,是不能修改的:
实际上我们测试了一下,被修改了,说明代码有问题
原因出在这里,由于这里可以让非const对象访问,访问后返回的是普通迭代器,普通迭代器是支持被修改的
所以我们需要再重载const版本的,返回值也要记得修改
//const版本
const_iterator begin() const
{
return const_iterator(_head->_next);
}
const_iterator end() const
{
return const_iterator(_head);
}
const迭代器,需要的不是是迭代器不能被修改,而是迭代器指向的的内容不能被修改,所以不能用const iterator,因为它是避免迭代器被修改。
方法一:需要重新定义一个ListConstIterator的类,且在链表前面定义类型的时候加上const_iterator:
对于ListConstIterator类,实际上只有的这里的逻辑和ListIterator不同:*it += 10会显示报错了
图示:
方法二:方法一使代码冗余,我们需要学习一种方法能够解决这个问题。
而我们知道,凡是类型不同就可以用模版来解决
Ref-->reference,引用 Ptr -->Pointer
如图:本质相当于我们写了一个类模版,编译器实例化生成了两个类
最后达到目的。
完整代码:
#pragma once
#include<assert.h>
#include<iostream>
//#include<vector>
//#include<list>
#include<algorithm>
using namespace std;
namespace bit
{
template<class T>
//注意一下这里用的struct,数据想要全部公有的时候用struct
struct ListNode
{
ListNode<T>* _next;
ListNode<T>* _prev;
T _data;
ListNode(const T& x = T())
:_next(nullptr),
_prev(nullptr),
_data(x)
{}
private:
};
//普通迭代器
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)
{}
//我们最想实现的是对于指针的++,即运算符的重载:
//1.++it
Self& operator++()
{
//++是怎么+到下一个节点的,即交换值
_node = _node->_next;
return *this;
}
//2.it++
Self operator++(int)
{
//1.调用拷贝构造,这里是一个浅拷贝,把一个迭代器给另外一个迭代器,it1,it2都指向一个节点
Self tmp(*this);
_node = _node->_next;
//2.迭代器是否需要写析构?不需要,因为资源是属于链表的
return tmp;
}
//1. --it
Self& operator--()
{
_node = _node->_prev;
return *this;
}
//2. it--
Self operator--(int)
{
Self tmp(*this);
_node = _node->_prev;
return tmp;
}
//解引用:*it 返回的是_data
//T& operator*()
Ref operator*()
{
return _node->_data;
}
//it->
//T* operator->()
Ptr operator->()
{
return &_node->_data;//返回的是_data的地址
}
//两个迭代器是否相等?
bool operator!=(const Self& it)
{
//即比较节点的指针,节点的指针相同,他们就相同
return _node != it._node;
}
bool operator==(const Self& it)
{
return _node == it._node;
}
//比较大小是否需要重载?不需要,一个类是否要重载一个运算符,需要看他是否有意义
//节点后面的地址不能保证比前面的地址大,所以没什么意义。不需要重载
//迭代器的重点:指针,用类似指针的方式来访问容器
//begin()和end()谁能提供,链表
};
//迭代器
//template<class T>
//struct ListConstIterator
//{
// typedef ListNode<T> Node;
// typedef ListConstIterator<T> Self;
// Node* _node;
// //从本源来说迭代器还是一个节点的指针
// ListConstIterator(Node* node)
// :_node(node)
// {
// }
// //我们最想实现的是对于指针的++,即运算符的重载:
// //1.++it
// Self& operator++()
// {
// //++是怎么+到下一个节点的,即交换值
// _node = _node->_next;
// return *this;
// }
// //2.it++
// Self operator++(int)
// {
// //1.调用拷贝构造,这里是一个浅拷贝,把一个迭代器给另外一个迭代器,it1,it2都指向一个节点
// Self tmp(*this);
// _node = _node->_next;
// //2.迭代器是否需要写析构?不需要,因为资源是属于链表的
// return tmp;
// }
// //1. --it
// Self& operator--()
// {
// _node = _node->_prev;
// return *this;
// }
// //2. it--
// Self operator--(int)
// {
// Self tmp(*this);
// _node = _node->_prev;
// return tmp;
// }
// //如何控制*it不能被修改,即在这里加const
// const T& operator*()
// {
// return _node->_data;
// }
// //如何控制it->不能被修改,即在这里加const
// const T* operator->()
// {
// return &_node->_data;//返回的是_data的地址
// }
// //两个迭代器是否相等?
// bool operator!=(const Self& it)
// {
// //即比较节点的指针,节点的指针相同,他们就相同
// return _node != it._node;
// }
// bool operator==(const Self& it)
// {
// return _node == it._node;
// }
// //比较大小是否需要重载?不需要,一个类是否要重载一个运算符,需要看他是否有意义
// //节点后面的地址不能保证比前面的地址大,所以没什么意义。不需要重载
// //迭代器的重点:指针,用类似指针的方式来访问容器
// //begin()和end()谁能提供,链表
//};
template<class T>
class list
{
typedef ListNode<T> Node;
public:
//在这里定义好类型
//typedef ListIterator<T> iterator;
//typedef ListConstIterator<T> const_iterator;
typedef ListIterator<T, T&, T*> iterator;
typedef ListIterator<T,const T&,const T*> const_iterator;
void empty_init()
{
//创建空链表
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
_size = 0;
}
//构造函数
list()
{
empty_init();
}
//清除掉所有数据,有没有清除掉头结点?并没有(在析构函数中清除)
void clear()
{
iterator it = begin();
while (it != end())
{
it = erase(it);
}
}
//析构函数
~list()
{
clear();
delete _head;
_head = nullptr;
}
//lt2(lt1)
//拷贝构造,默认拷贝构造是浅拷贝,所以需要我们自己实现一个
list(const list<T>& lt)
{
//首先:在这里我们先创建空链表
//也就是:lt2.empty_init()直接给lt2创建一个空链表
empty_init();
//直接遍历一遍lt1,尾插
for (auto& e : lt)//这里一定要加引用,因为如果T是string,就浅拷贝了
{
push_back(e);
}
}
void swap(list<T>& lt)
{
std::swap(_head, lt._head);
std::swap(_size, lt._size);
}
//赋值,按值传递
//lt1 = lt3
list<T>& operator=(list<T> lt) //lt = lt3;
{
swap(lt);
return *this;
}
//迭代器
//1.原生指针能不能充当迭代器?不可以,这是建立在物理空间连续的情况下(画图解释)
//2.++Node*能不能加到下一个节点,节点的解引用能取到当前节点的数据吗?不能
//并且新new出来的地址和原来的地址也不会相同,头插和中间插入怎么保证在物理空间连续?从功能上未解决
//因此我们在此重新封装一个类(内嵌类、typedef(在类域)),想一想日期类,可以日期相加,做各种操作的是合理的,使用封装的类
//来重载运算符,重载运算符后我就可以自定义这些运算符的行为
//普通版本
iterator begin()
{
// 1.有名对象
//iterator it(_head->_next);
//return it;
// 2.隐式类型转换(单参数的构造函数支持隐式类型的转换):
//return _head->_next;
// 3.匿名对象
return iterator(_head->_next);
}
//end()是最后一个数据的下一个位置
iterator end()
{
return iterator(_head);
}
//const版本
const_iterator begin() const
{
// 1.有名对象
//iterator it(_head->_next);
//return it;
// 2.隐式类型转换(单参数的构造函数支持隐式类型的转换):
//return _head->_next;
// 3.匿名对象
return const_iterator(_head->_next);
}
//end()是最后一个数据的下一个位置
const_iterator end() const
{
return const_iterator(_head);
}
//注意这里我们都应该通过begin()、end()控制迭代器而不是控制节点了
//尾插
void push_back(const T& x)
{
//1.还未写insert()时写的push_back()
//Node* newnode = new Node(x);
//Node* tail = _head->_prev; //指定被插的节点
//tail->_next = newnode;
//newnode->_prev = tail;
//newnode->_next = _head;
//_head->_prev = newnode;
//2.写了insert()后写的push_back()
insert(end(), x);
}
//头插
void push_front(const T& x)
{
insert(begin(), x);
}
//尾删 ,要--,不然删的是head(哨兵位)
void pop_back()
{
erase(--end());
}
//头删
void pop_front()
{
erase(begin());
}
//在pos位置插入data
void insert(iterator pos, const T& data)
{
Node* current = pos._node;
Node* newnode = new Node(data);
Node* prev = current->_prev;
//prev newnode current
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = current;
current->_prev = newnode;
++_size;
}
//删除pos位置
iterator erase(iterator pos) //请注意这里的pos位置迭代器会失效,为什么?可以看我的上篇vector中目录的更新erase()
{
//拿到当前位置
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* next = cur->_next;
//prev cur next
prev->_next = next;
next->_prev = prev;
delete cur;
--_size;
return iterator(next);//解决办法就是返回下一个位置的迭代器
}
size_t size() const
{
库里面:1. 遍历一遍计数
//for ()
//{
//}
//2. 我们通过构造函数来初始化,给insert、erase加上_size便于计数
return _size;
}
bool empty()
{
return _size == 0;
}
//resize()不常用,我们这里不做模拟,没有扩容的概念,比我当前的_size大就尾插入,比当前的_size小就尾删
private:
Node* _head;
size_t _size;
};
void test_list1()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.push_back(5);
//迭代器测试代码
//这里需要遍历数据,所以要用到迭代器,于是现在我们写迭代器代码:
list<int>::iterator it = lt.begin();
cout << *it << endl;
while (it != lt.end())
{
cout << *it << " ";
++it;
}
cout << endl;
lt.push_front(10);
lt.push_front(20);
lt.push_front(30);
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
lt.pop_back();
lt.pop_front();
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
}
//迭代器进一步优化
struct A
{
int _a1;
int _a2;
A(int a1 = 0, int a2 = 0)
:_a1(a1)
,_a2(a2)
{
}
};
void test_list2()
{
list<A> lt;
//如果这里有自定义类型A
A aa1(1, 5);
A aa2 = { 1,1 };
lt.push_back(aa1);
lt.push_back(A(2, 3));
lt.push_back({ 4,5 });
lt.push_back(aa2);
list<A>::iterator it = lt.begin();
while (it != lt.end())
{
cout << it->_a1 << ":" << it->_a2 << endl;
++it;
}
}
void PrintList(const list<int>& clt)
{
list<int>::const_iterator it = clt.begin();
while (it != clt.end())
{
//*it += 10;
cout << *it << " ";
++it;
}
}
void test_list3()
{
list<int> lt1;
lt1.push_back(1);
lt1.push_back(2);
lt1.push_back(3);
lt1.push_back(4);
lt1.push_back(5);
PrintList(lt1);
cout << endl;
//拷贝构造
list<int> lt2(lt1);
PrintList(lt2);
cout << endl;
list<int> lt3 = lt1;
PrintList(lt3);
}
}
结语:
随着这篇关于题目解析的博客接近尾声,我衷心希望我所分享的内容能为你带来一些启发和帮助。学习和理解的过程往往充满挑战,但正是这些挑战让我们不断成长和进步。我在准备这篇文章时,也深刻体会到了学习与分享的乐趣。
在此,我要特别感谢每一位阅读到这里的你。是你的关注和支持,给予了我持续写作和分享的动力。我深知,无论我在某个领域有多少见解,都离不开大家的鼓励与指正。因此,如果你在阅读过程中有任何疑问、建议或是发现了文章中的不足之处,都欢迎你慷慨赐教。
你的每一条反馈都是我前进路上的宝贵财富。同时,我也非常期待能够得到你的点赞、收藏,关注,这将是对我莫大的支持和鼓励。当然,我更期待的是能够持续为你带来有价值的内容,让我们在知识的道路上共同前行。