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的使用:

构造与赋值

  1. 构造函数

    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)
  2. 赋值操作

    lt = lt2;                    // 深拷贝赋值
    lt.assign(3, 100);           // 替换内容为 3 个 100
    lt.assign(lt2.begin(), lt2.end()); // 通过迭代器赋值


元素访问

  1. 首尾元素front()、back()

    int front = lt.front();      // 首元素(不可修改空链表)
    int back = lt.back();        // 尾元素(不可修改空链表)

  2. 迭代器begin()、rbegin()

    list<int>::iterator it = lt.begin(); // 正向迭代器
    list<int>::reverse_iterator rit = lt.rbegin(); // 反向迭代器


修改操作

  1. 插入

    lt.push_front(0);            // 头部插入
    lt.push_back(4);             // 尾部插入auto pos = lt.begin();
    lt.insert(pos, 99);          // 在迭代器位置前插入
    lt.insert(pos, 3, 88);       // 插入 3 个 88
  2. 删除

    lt.pop_front();              // 删除头部元素
    lt.pop_back();               // 删除尾部元素lt.erase(pos);               // 删除迭代器位置元素
    lt.erase(pos, lt.end());     // 删除迭代器范围lt.remove(4);               // 删除所有值为 4 的节点
  3. 清空链表(clear)

    lt.clear();                  // 清空所有元素

容量查询

bool isEmpty = lt.empty();      // 是否为空
size_t size = lt.size();        // 元素个数(O(n) 时间复杂度!)
lt.resize(10);                 // 调整链表大小

链表特有操作

  1. 拼接(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());

    注意:被转移的节点会被清空

  2. 合并(Merge)

    // 前提:lt 和 lt2 必须已按相同顺序排序(升序或降序)
    lt.sort();                   // 默认升序
    lt2.sort();
    lt.merge(lt2);               // 合并后 lt2 为空
  3. 排序(Sort)

    lt.sort();                   // 默认升序
    lt.sort(greater<int>());     // 降序(需包含 <functional>)
  4. 去重(Unique)

    lt.sort();                   // 必须先排序!
    lt.unique();                 // 删除连续重复元素
  5. 反转(Reverse)

    lt.reverse();                // 反转链表顺序

C++11 新增方法

  1. 原地构造(Emplace)

    lt.emplace_front(10);        // 在头部直接构造对象(避免拷贝)
    lt.emplace_back(20);         // 尾部构造
    lt.emplace(pos, 30);         // 指定位置构造

  2. 移动语义

    list<int> lt5 = std::move(lt); // 移动构造(高效转移资源)


注意:

  1. merge 前必须排序

    // 错误示例:lt 是降序,lt1 是升序
    lt.sort(greater<int>());    // 降序
    lt1.sort();                 // 升序
    lt.merge(lt1);             // 未定义行为!// 正确做法:统一顺序
    lt.sort();                 // 升序
    lt1.sort();
    lt.merge(lt1);
  2. unique 必须配合 sort

    // 错误用法:未排序时去重
    lt.unique();               // 只能删除连续重复// 正确用法
    lt.sort();
    lt.unique();               // 删除所有重复
  3. 迭代器失效

    • 在插入/删除操作后,指向被修改位置的迭代器会失效,需重新获取。


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){}
};

设计要点分析

  1. 使用struct而非class的深层考量:

    • 默认public访问权限,便于链表类直接操作节点指针

    • 符合C++标准库实现惯例,增强代码可读性

    • 节点本身无需封装,属于链表内部实现细节

  2. 三指针设计哲学:

    • _next_prev构成双向链接的基石

    • _data采用模板类型,支持任意数据类型存储

    • 默认构造函数的巧妙设计:同时支持无参构造和值初始化

  3. 内存布局可视化:

    +------+    +------+    +------+
    | 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;  // 指针地址比较
}

实现细节剖析

  1. 前置与后置++的差异:

    1. 性能考量:避免不必要的拷贝

    2. 语法区分:int参数占位符

  2. 解引用操作的注意事项:

    • 返回引用以实现左值操作

    • 与const迭代器的兼容性设计

  3. 比较大小是否需要重载?不需要,一个类是否要重载一个运算符,需要看他是否有意义,节点后面的地址不能保证比前面的地址大,所以没什么意义。不需要重载

三、链表类的完整实现

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;
}

运作原理

  1. 按值传递参数:调用 operator= 时,lt 是传入对象的副本(触发拷贝构造函数)。

  2. 交换资源:通过 swap 将当前对象的 _head 和 _size 与副本 lt 交换。

  3. 自动析构副本:函数结束时,临时副本 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 时:

  1. 通过 _node 找到数据节点

  2. 获取 _data 的内存地址(0x4000)

  3. 通过指针访问结构体成员 _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,数据想要全部公有的时候用structstruct 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.++itSelf& operator++(){//++是怎么+到下一个节点的,即交换值_node = _node->_next;return *this;}//2.it++Self operator++(int){//1.调用拷贝构造,这里是一个浅拷贝,把一个迭代器给另外一个迭代器,it1,it2都指向一个节点Self tmp(*this);_node = _node->_next;//2.迭代器是否需要写析构?不需要,因为资源是属于链表的return tmp;}//1. --itSelf& 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 = lt3list<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位置插入datavoid 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 nextprev->_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;//如果这里有自定义类型AA 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);}}

结语:

       随着这篇关于题目解析的博客接近尾声,我衷心希望我所分享的内容能为你带来一些启发和帮助。学习和理解的过程往往充满挑战,但正是这些挑战让我们不断成长和进步。我在准备这篇文章时,也深刻体会到了学习与分享的乐趣。

       在此,我要特别感谢每一位阅读到这里的你。是你的关注和支持,给予了我持续写作和分享的动力。我深知,无论我在某个领域有多少见解,都离不开大家的鼓励与指正。因此,如果你在阅读过程中有任何疑问、建议或是发现了文章中的不足之处,都欢迎你慷慨赐教。

        你的每一条反馈都是我前进路上的宝贵财富。同时,我也非常期待能够得到你的点赞、收藏,关注,这将是对我莫大的支持和鼓励。当然,我更期待的是能够持续为你带来有价值的内容,让我们在知识的道路上共同前行。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/31583.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

复试难度,西电卓越工程师学院(杭研院)考研录取情况

01、卓越工程师学院各个方向 02、24卓越工程师学院&#xff08;杭研院&#xff09;近三年复试分数线对比 PS&#xff1a;卓越工程师学院分为广研院、杭研院 分别有新一代电子信息技术、通信工程、集成电路工程、计算机技术、光学信息工程、网络信息安全、机械&#xff0c;这些…

【JavaEE】线程池

【JavaEE】线程池 一、引言1.1 什么是线程池1.2 为什么要使用线程池 二、ThreadPoolExecutor类2.1 构造方法2.1.1 corePoolSize和maximumPoolSize2.1.2 KeepAliveTime和unit2.1.3 BlockingQueue<Runnable> workQueue2.1.4 ThreadFactory threadFactory2.1.5 RejectedExec…

GaussDB安全配置指南:从认证到防御的全方面防护

一、引言 随着企业数据规模的扩大和云端化进程加速&#xff0c;数据库安全性成为运维的核心挑战之一。GaussDB作为一款高性能分布式数据库&#xff0c;提供了丰富的安全功能。本文将从 ​认证机制、权限控制、数据加密、审计日志​ 等维度&#xff0c;系统性地讲解如何加固 Ga…

Ubuntu 22.04 升级到 Ubuntu 24.04 全流程指南

&#x1f4cc; 1. 前言 Ubuntu 24.04 是最新的 LTS 版本&#xff0c;带来了内核更新、性能优化以及更强的安全性。本指南详细记录了从 Ubuntu 22.04 升级到 24.04 的完整过程&#xff0c;包括 升级前的准备、遇到的问题及如何选择最佳选项&#xff0c;避免升级失败或系统损坏。…

Git和GitHub基础教学

文章目录 1. 前言2. 历史3. 下载安装Git3.1 下载Git3.2 安装Git3.3 验证安装是否成功 4. 配置Git5. Git基础使用5.1 通过Git Bash使用5.1.1 创建一个新的仓库。5.1.1.1 克隆别人的仓库5.1.1.2 自己创建一个本地仓库 5.1.2 管理存档 5.2 通过Visual Studio Code使用 6. Git完成远…

【leetcode hot 100 234】回文链表

错误解法一&#xff1a;正序查找的过程中&#xff0c;将前面的元素倒叙插入inverse链中&#xff0c;找到偶数中点时&#xff0c;对称查找。 /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* Li…

Manus:成为AI Agent领域的标杆

一、引言 官网&#xff1a;Manus 随着人工智能技术的飞速发展&#xff0c;AI Agent&#xff08;智能体&#xff09;作为人工智能领域的重要分支&#xff0c;正逐渐从概念走向现实&#xff0c;并在各行各业展现出巨大的应用潜力。在众多AI Agent产品中&#xff0c;Manus以其独…

IDEA(十一)调整新版本的工具栏显示Git操作(pull、commit、push、revert等)

目录 一、背景二、操作步骤2.1 开启新 UI 样式2.2 设置 Tool Window 工具栏 一、背景 好久没有更新 IDEA 了&#xff0c;更新之后发现 IDEA 的工具栏消失了。一番操作之后&#xff0c;终于把 IDEA 的工具栏的设置调整好了&#xff0c;在此进行记录调整步骤&#xff0c;供大家学…

Mysql快速学习——《一》: Mysql的基础架构

了解mysql的基础架构, 理解大概的实现思想, 更有利与我们知之所以然, 是我们学习mysql起来思路更清晰, 效率更高. 思维导图: mysql 基础架构 mysql基础架构.png 1. 连接器 Mysql作为服务器&#xff0c;一个客户端的Sql连接过来就需要分配一个线程进行处理&#xff0c;这个线程…

[Pytorch报错问题解决]AttributeError: ‘nn.Sequential‘ object has no attribute ‘append‘

问题 运行深度学习代码的时候遇到了以下报错问题&#xff1a; Traceback (most recent call last):File "/home/anaconda3/envs/Text2HOI/lib/python3.9/site-packages/torch/autograd/grad_mode.py", line 28, in decorate_contextreturn func(*args, **kwargs)Fi…

小支从学习到认证:NebulaGraph 图数据库认证之旅

前言 在数据爆炸的当下&#xff0c;图数据库凭借其独特的优势&#xff0c;成为处理复杂数据关系的有力工具。NebulaGraph 作为图数据库领域的佼佼者&#xff0c;以高性能、可扩展性和易用性赢得了广泛认可。对于想要在这一领域深入发展的专业人士来说&#xff0c;从学习到获得 …

windows:curl: (60) schannel: SEC_E_UNTRUSTED_ROOT (0x80090325)

目录 1. git update-git-for-windows 报错2. 解决方案2.1. 更新 CA 证书库2.2. 使用 SSH 连接&#xff08;推荐&#xff09;2.3 禁用 SSL 验证&#xff08;不推荐&#xff09;2.4 使用pull不使用update 1. git update-git-for-windows 报错 LenovoLAPTOP-EQKBL89E MINGW64 /d/…

mac使用Homebrew安装miniconda(mac搭建python环境),并在IDEA中集成miniconda环境

一、安装Homebrew mac安装brew 二、使用Homebrew安装miniconda brew search condabrew install miniconda安装完成后的截图&#xff1a; # 查看是否安装成功 brew list环境变量&#xff08;无需手动配置&#xff09; 先执行命令看能不能正常返回&#xff0c;如果不能正常…

2025人工智能AI新突破:PINN内嵌物理神经网络火了

最近在淘金的时候发现基于物理信息的神经网络&#xff08;简称PINN&#xff09;也是个研究热点&#xff0c;遂研读了几篇经典论文&#xff0c;深觉这也是个好发论文的方向&#xff0c;所以火速整理了一些个人认为很值得一读的PINN论文和同学们分享。 为了方面同学们更好地理解…

Android MVC、MVP、MVVM三种架构的介绍和使用。

写在前面&#xff1a;现在随便出去面试Android APP相关的工作&#xff0c;面试官基本上都会提问APP架构相关的问题&#xff0c;用Java、kotlin写APP的话&#xff0c;其实就三种架构MVC、MVP、MVVM&#xff0c;MVC和MVP高度相似&#xff0c;区别不大&#xff0c;MVVM则不同&…

大空间多人互动技术、大空间LBE、VR大空间什么意思?如何实现?

什么是VR? 虚拟现实&#xff08;Virtual Reality&#xff0c;简称VR&#xff09;是一种通过计算机技术模拟生成的三维虚拟环境&#xff0c;用户可以通过佩戴VR头显等设备&#xff0c;沉浸在这个虚拟世界中&#xff0c;并与其中的物体、场景进行互动。VR技术的核心在于提供身临…

3D匹配算法简述

一.什么是3D匹配 •形状、大小一致的源点云与目标点云之间的刚体变换。 •源点云通过平移与旋转操作变换到目标点云位置使之重合。 •源点云与目标点云坐标系之间的变换。 典型的应用流程为&#xff1a; 3D相机数据采集----点云生成----点云处理----目标点云提取----3D模板…

【今日EDA行业分析】2025年3月8日

今日行业分析&#xff1a;半导体行业技术博弈与国产 EDA 的突破与挑战 一、引言 在半导体行业的复杂生态系统中&#xff0c;EDA&#xff08;电子设计自动化&#xff09;技术处于核心地位&#xff0c;是连接芯片设计、制造与应用的关键纽带。当下&#xff0c;全球半导体领域正经…

NAT NAPT

NAT NAT&#xff08;Network Address Translation&#xff0c;网络地址转换&#xff09; 主要用于在不同网络&#xff08;如私有网络和公共互联网&#xff09;之间进行 IP 地址转换&#xff0c;解决IP 地址短缺问题&#xff0c;并提供一定的安全性。 IPv4 地址是 32 位&#xf…

固定表头、首列 —— uniapp、vue 项目

项目实地&#xff1a;也可以在 【微信小程序】搜索体验&#xff1a;xny.handbook 另一个体验项目&#xff1a;官网 一、效果展示 二、代码展示 &#xff08;1&#xff09;html 部分 <view class"table"><view class"tr"><view class&quo…