文章目录
- list
- 迭代器的分类
- emplace_back
- 实现中间插入
- erase
- sort排序:升/降
- merge:合并链表
- unique:去重
- remove:删除
- splice:转移元素
- 实现
- list.h
-
- 1.构造函数:
- 2.实现尾插:
- 3.实现迭代器
-
- 代码+逐句讲解‼️
- [1.为什么`list_node`用 struct?](#1.为什么
list_node用 struct?) - [2. 为什么`list_iterator`用 struct?](#2. 为什么
list_iterator用 struct?) - test_list1()
- 构造函数的对比
- 4.前一部分总代码+逐句讲解‼️
- 5.实现insert
- 6.实现erase
- 7.重载->
- 8.实现const_iterator
-
- [8.1为什么要实现const迭代器const_iterator而不是const iterator?](#8.1为什么要实现const迭代器const_iterator而不是const iterator?)
- 8.2如何实现?
- 8.3‼️两个模板类的版本实现const_iterator
- 8.5‼️彻底搞懂const_iterator
-
- 1.给谁用:
- [2. 为什么有`const_iterator`?](#2. 为什么有
const_iterator?) - [3.`const_iterator` vs `const iterator`](#3.
const_iteratorvsconst iterator) - [4. 为什么`const_iterator`的`operator*`返回`const T&`,但函数末尾没加 const?](#4. 为什么
const_iterator的operator*返回const T&,但函数末尾没加 const?) - [5.函数末尾加 const 和返回值加 const 有没有必然联系?](#5.函数末尾加 const 和返回值加 const 有没有必然联系?)
- 8.6‼️通过模板实现
- 9.erase:迭代器失效及修改
- 10.更加规范的insert:
- 11.实现析构函数
- 12.拷贝构造
- 13.赋值运算符重载
- 全部代码
- 更新代码
list
不支持通过下标访问(operator ),不支持下标加减,支持++和--(访问下一个和前一个)
迭代器的分类
- 按照功能
- iterator
- reverse_iterator
- const iterator
- const reverse_iterator
- 按照性质:
- 单向(forward iterator):forward_list/unordered_map/unordered_set------>只支持++
- 双向(bidirectional iterator):list/map/set------>支持++/--
- 随机(random access iterator):string/vector/deque------>支持++/--/+/-
sort:只支持随机迭代器
template <class RandomAccessIterator> void sort (RandomAccessIterator first, RandomAccessIterator last);reverse :支持随机和双向
template <class BidirectionalIterator>
void reverse (BidirectionalIterator first, BidirectionalIterator last);find:都可(是InputIterator,是单向迭代器的子类)
emplace_back
- emplace_back和push_back的区别
cpp
struct A
{
public:
A(int a1=1,int a2=1)
:_a1(a1)
,_a2(a2)
{ }
private:
int _a1;
int _a2;
};
void test()
{
list<A>lt;
A a(1, 1);
lt.push_back(a);
lt.push_back(A(3, 4));//支持匿名对象
lt.emplace_back(a);
lt.emplace_back(A(3, 4));//支持匿名对象
lt.emplace_back( 3, 4 ); //支持直接传构造A的参数
}
实现中间插入
list 是不支持迭代器的+/-找到下标的,但我们也可以先通过其他方式找到下标,再insert

- 在下标为3的位置前面插入
cpp
void test1()
{
list<int>lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.push_back(5);
auto it = lt.begin();
int k = 3;
while (k--)
{
it++;
}
lt.insert(it, 66);
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
}
int main()
{
test1();
return 0;
}
erase
- 删除4:
- 要判断:
(it!=lt.end()),因为如果find没有找到,会返回最后一个迭代器
- 要判断:
cpp
it = find(lt.begin(), lt.end(), 4);
if(it!=lt.end())
{
lt.erase(it);
}
sort排序:升/降
-
升序:
lt.sort( ); -
降序:
greater<int>gt; lt.sort(gt);或者直接写成:
lt.sort(greater<int>())匿名对象
merge:合并链表
- A和B都是有序的才能合并
- A.merge(B)后**,B 变为空链表(而非销毁,仍可复用)。**
cpp
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>lt2;
lt2.push_back(3);
lt2.push_back(4);
lt2.push_back(5);
lt2.push_back(6);
lt.merge(lt2);
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
unique:去重
- 必须是有序的才能使用(相同的值都挨在一起)------>先sort再去重
cpp
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>lt2;
lt2.push_back(3);
lt2.push_back(4);
lt2.push_back(5);
lt2.push_back(6);
lt.merge(lt2);
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
lt.unique();
for (auto e : lt )
{
cout << e << " ";
}
cout << endl;
cpp
1 2 3 3 4 4 5 5 6
1 2 3 4 5 6
remove:删除
传的是值,不是迭代器
cpp
lt.remove(2);
lt.remove(3);
splice:转移元素
3种用法:(被转移的结构的对应元素会消失)
cpp
entire list (1) //转移整个链表
void splice (iterator position, list& x);
single element (2) //转移单个迭代器位置的元素
void splice (iterator position, list& x, iterator i);
element range (3) //转移某个范围
void splice (iterator position, list& x, iterator first, iterator last);
举例:
构建一个1,2,3,4,5,6的链表:
cpplist<int>lt; lt.push_back(1); lt.push_back(2); lt.push_back(3); lt.push_back(4); lt .push_back(5); lt .push_back(6);
- 将某个数字移到开头:
cppint x = 0; cin >> x; auto it = find(lt.begin(),lt.end(), x); ///void splice (iterator position, list& x, iterator i); lt.splice(lt.begin(), lt, it);
- 将某个数字及其之后的所有数字移到开头:
cppint x = 0; cin >> x; auto it = find(lt.begin(),lt.end(), x); ///void splice (iterator position, list& x, iterator first, iterator last); lt.splice(lt.begin(), lt, it, lt.end());
实现
三个类
✅ **三层结构 **
- 第1层:
list_node节点类(存数据和前后指针) - 第2层:
list_iterator迭代器类(专门封装节点指针,用来重载++、--、*、->运算符,让链表能用像指针一样的语法遍历) - 第3层:
list容器类(对外提供接口,管理整个链表)
✅ **迭代器单独成类的原因 ** 运算符重载要求至少有一个操作数是类对象,原生指针的++是地址 + 1,不符合链表节点不连续的内存结构,所以必须把节点指针包装成一个类,自己定义++的行为。
list.h
在namespace lcj中,创建class list
- 先实现链表节点
list_node,为一个结构体(类),其中存 内容,上一个节点指针,下一个节点指针 - 实现list类:
list
cpp
namespace lcj
{
template<class T>
class list_node
{
T _data;
list_node<T>* _prev;
list_node<T>* _next;
};
template<class T>
class list
{
typedef list_node<T> Node;
public:
private:
Node* _head;
};
}
1.构造函数:
- 创建节点
- 将节点中的前后指针加上
_head = new Node; _head->_next = _head; _head->_prev = _head;
cpp
namespace lcj
{
template<class T>
class list_node
{
T _data;
list_node<T>* _prev;
list_node<T>* _next;
};
template<class T>
class list
{
typedef list_node<T> Node;
public:
list()//////////////////////////////////////////////////////////////
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
}
private:
Node* _head;
};
}
2.实现尾插:
实现size()和empty()
为了计数方便,增加_size
cpp
template<class T>
class list
{
typedef list_node<T> Node;
public:
private:
Node* _head;
size_t _size;/////////////////////////////////////////////////////////////
};
push_back
cpp
template<class T>
class list
{
typedef list_node<T> Node;
public:
list()
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
_head->_size =0;
}
void push_back(const T& x)
{
Node* newnode = new Node(x);
Node* tail = _head->_prev; // 先拿到原来的尾节点
newnode->_next = _head; // 新节点的后继指向头节点
newnode->_prev = tail; // 新节点的前驱指向旧尾
tail->_next = newnode; // 旧尾的后继指向新节点
_head->_prev = newnode; // 头节点的前驱指向新节点
++_size;
}
size_t size() const///////////////////////////
{
return _size;
}
bool empty() const//////////////////////////////
{
return _size == 0;
}
private:
Node* _head;
size_t _size;
};
3.实现迭代器
为什么要自己实现一个迭代器,因为list的节点在内存中不是线性存储的。如果是原来迭代器的一些操作,比如++、--,可能造成访问越界,所以我们要自己实现一个类,从而实现List的迭代器对应的操作
运算符重载函数,是专门为类类型准备的------>参数至少有一个自定义类型:不能参数全是 int、double 这种内置类型, 防止你修改 C++ 原本的语法。
代码+逐句讲解‼️
cpp
template<class T>
struct list_iterator//迭代器的类
{
typedef list_node<T> Node;//简化节点名称
Node* _node;//这是整个迭代器这个类中 唯一的成员变量
//为什么要有这个成员变量呢?
//因为我们知道迭代器的作用就是返回一个节点的指针,
//所以我们创造这个成员变量,
//然后呢,在后面的操作中直接对这个指针进行修改,
// 然后可以直接返回它
typedef list_iterator Self; //简化迭代器这个名字,在迭代器最低的类中,就叫self
list_iterator(Node* node)//迭代器这个类的 构造函数------>支持list类中的这样 iterator it(_head->_next);的使用。用来在类外把对应的节点指针变成迭代器
:_node(node)
{ }
T& operator*()//*解引用的运算符重载,直接返回list对应的数据_data
{
return _node->_data;
}
Self operator++()//返回下一个节点的迭代器,返回的是迭代器这个类, // 必须返回自身引用,符合STL迭代器规范
{
_node = _node->_next;
return *this;
}
Self& operator++()
{
_node = _node->_next;
return *this;
}
Self& operator--()
{
_node = _node->_prev;
return *this;
}
bool operator!=(const Self& s)
{
return _node != s._node;
}
bool operator==(const Self& s)const
{
return _node == s._node;
}
};
struct 和class:
- struct类,不加访问限定符,纯公有
- class,不加,纯私有
1.为什么list_node用 struct?
节点类是一个纯数据结构 ,它的三个成员_data、_prev、_next,需要被list类和list_iterator类随时随地直接读写。
2. 为什么list_iterator用 struct?
它的核心成员_node指针,需要被list类的成员函数(begin()、end()、insert()、erase())直接访问。比如:
``bool operator!=(const Self& s){return _node != s._node; }
把list_node改为struct
cpp
struct list_node
{
T _data;
list_node<T>* _prev;
list_node<T>* _next;
};
test_list1()
cpp
void test_list1()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
list<int>::iterator it = lt.begin();
while (it != lt.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
push_back上面说了,
接下来,想要实现:list<int>::iterator it = lt.begin();,就要先实现
- 迭代器iterator
- begin函数
-
Iterator是我们自己定义的一个类:
struct list_iterator//迭代器,然后再list这个类中typedef出来的名称-
我们是这样创建list的迭代器的,
list<int>::iterator it = lt.begin();,但看起来这个iterator它是在list类这个类里面的,可是我们知道并不是这样,------>因为
list类内部有一个typedef(类型别名),把外部的list_iterator<T>重命名成了内部的iterator。------>编译器会自动把它翻译成:
list_iterator<int> it = lt.begin();
-
构造函数的对比
-
每一个类都有对应的构造函数
-
构造一个节点的时候
cpplist_node(const T& data = T()) :_data(data) ,_next(nullptr) ,_prev(nullptr) { } -
构造一个list的时候
cpplist() { _head = new Node(T()); _head->_next = _head; _head->_prev = _head; _size = 0; } -
构造一个迭代器的时候
因为我们在实现begin、 and的时候,一般是这样的:
iterator it(_head->_next);return it;,所以迭代器的构造函数要是带参的
cpplist_iterator(Node* node) :_node(node) { }
-
4.前一部分总代码+逐句讲解‼️
指针类型直接转化成了一个iterator这个类类型,相当于内置类型转化成类类型, 条件就是类有对应的构造函数就可以
cpp
namespace lcj
{
/////////////////////////////////////////////////////////////////////////////////////////////
template<class T>
struct list_node//节点的类
{
T _data;
list_node<T>* _prev;
list_node<T>* _next;
list_node(const T& data = T())//节点的实现,是一个默认 构造函数, 并且同时支持你传参和不传参都可以生成对应的节点
:_data(data)
,_next(nullptr)
,_prev(nullptr)
{ }
};
/////////////////////////////////////////////////////////////////////////////////////////////
template<class T>
struct list_iterator//迭代器的类
{
typedef list_node<T> Node;//简化节点名称
Node* _node;//这是整个迭代器这个类中 唯一的成员变量,是一个节点的指针。因为像是 ++、--、*这些操作,都需要通过这个指针找到对应的节点(Node* 类型),然后返回对应的迭代器
typedef list_iterator Self; //在自己这个类中简化自己这个类的名称,然后在后面的++、--中,返回时也写得更方便一些
//传参传的是(_head->_next)这样的节点指针。
//用来在类外把对应的节点指针变成list_iterator迭代器这种类
list_iterator(Node* node)//迭代器这个类的 构造函数------>支持list类中的这样 iterator it(_head->_next);的使用
:_node(node)
{ }
T& operator*()//*的运算符重载,直接返回list对应的_data
{
return _node->_data;
}
Self& operator++()//++的运算符重载,返回对应的迭代器
{
_node = _node->_next;
return *this;
}
bool operator!=(const Self& s)//通过判断迭代器类(list_iterator)创建的对象 中唯一的成员变量_node ,来判断两个迭代器类(list_iterator)是否是相同的迭代 类 对象(list_iterator)
{
return _node != s._node;
}
};
/////////////////////////////////////////////////////////////////////////////////////////////
template<class T>
class list // 链表的类
{
typedef list_node<T> Node;//简化链节的写法
public:
list()// 构造函数,生成一个只有头节点的list
{
_head = new Node(T());
_head->_next = _head;
_head->_prev = _head;
_size = 0;
}
typedef list_iterator<T> iterator;//简化迭代器的写法,符合STL
iterator begin()//通过 节点指针,创造出一个迭代器类(iterator),然后返回这个迭代器类
{
iterator it(_head->_next);
return it;//编译器会做值拷贝,把局部迭代器里的_node指针复制给外面接收的变量
}
iterator end()//指向哨兵头节点(最后一个节点的下一个节点),作为遍历结束的标志,创造出一个迭代器类(iterator),然后返回这个迭代器类
{
return _head;
}
void push_back(const T& x)
{
Node* newnode = new Node(x);//创建节点,并且调用带参构造函数
Node* tail = _head->_prev; // 先拿到原来的尾节点
newnode->_next = _head; // 新节点的后继指向头节点
newnode->_prev = tail; // 新节点的前驱指向旧尾
tail->_next = newnode; // 旧尾的后继指向新节点
_head->_prev = newnode; // 头节点的前驱指向新节点
++_size;
}
size_t size()
{
return _size;
}
bool empty() const
{
return _size == 0;
}
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);
list<int>::iterator it = lt.begin();
while (it != lt.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
}
5.实现insert
通过insert实现push_front和push_back
cpp
void insert(iterator pos, const T& x)
{
Node* cur = pos._node;///这里应该写成.而不是->,因为 pos是个类(结构体),_node是结构体的成员变量
Node* prev = cur->_prev;
Node* newnode = new Node(x);
newnode->_next = cur;
cur->_prev = newnode;
newnode->_prev = prev;
prev->_next = newnode;
++_size;
}
void push_front(const T& x)
{
insert(begin(), x);
}
void push_back(const T& x)
{
insert(end(), x);
}
6.实现erase
通过erase实现pop_back
通过erase实现pop_front
cpp
void erase(iterator pos)
{
assert(pos != end());///end是哨兵位头节点
///迭代器只是一个类,想要访问对应的节点需要访问其中的_node成员变量
Node* prev = pos._node->_prev;
Node* next= pos._node->_next;
prev->_next = next;
next->_prev = prev;
delete pos._node;
--_size;
}
void pop_back()
{
erase(--end());
}
void pop_front()
{
erase(begin());
}
7.重载->
想打印一个结构体的类,而且
如下,我定义了一个结构体struct AA,创建了对应的list:list<AA> lta,
想打印,
- 方法一:当然可以先:
list<AA>::iterator it = lta.begin();,找到对应元素的指针(AA类的指针),然后解引用,得到一个AA类,然后通过"."来访问对应的_a1和_a2 - 方法二:list的迭代器,就是一个结构AA对象的地址------>通过"->",像访问一个结构体一样访问不行吗?不行🙅♀️,因为这时候list的迭代器并不是地址,而是一个类,所以不能用"->"
cpp
struct AA
{
int _a1=1;
int _a2=2;
};
list<AA> lta;
lta.push_back(AA());
lta.push_back(AA());
lta.push_back(AA());
lta.push_back(AA());
lta.push_back(AA());
list<AA>::iterator it = lta.begin();
while (it != lta.end())
{
///使用.的方法
cout << (*it)._a1 << " : " << (*it)._a2 << endl;
❌❌❌///使用->的方法:重载
cout<< it->_a1 << " : " << _a2 << endl;
}
cout << endl;
实现
‼️operator->必须返回指向数据的指针 ,而我之前犯错:返回的了数据本身(_node->_data是T类型,不是T*)。
实际上的访问应该是这样的:
cout << it.operator->()->_a1 << " : " << it.operator->()->_a1 << endl;
it.operator->()返回对应的_data的指针,->_a1相当于就是对结构体指针进行了"->"操作
但是编辑器省略了一个"->",只需要这样写就行:
cout<< it->_a1 << " : " << it->_a2 << endl;
cpp
///list_iterator 模板
template<class T>
struct list_iterator
{
typedef list_node<T> Node;
typedef list_iterator Self;
Node* _node;
T* operator->()/////////////////////////////////////////////////////////////////
{
return & _node->_data; ///operator->必须返回指向数据的指针
}
};
..................
struct AA
{
int _a1=1;
int _a2=2;
};
void test_list1()
{
list<AA> lta;
lta.push_back(AA());
lta.push_back(AA());
lta.push_back(AA());
lta.push_back(AA());
lta.push_back(AA());
list<AA>::iterator it = lta.begin();
while (it != lta.end())
{
///使用.的方法
cout << (*it)._a1 << " : " << (*it)._a2 << endl;
///使用->的方法:重载
cout<< it->_a1 << " : " << it->_a2 << endl;
++it;
}
cout << endl;
}
8.实现const_iterator
之前我们在实现vector中实现的print_container在这里用不了了,原因是没有实现const迭代器
cpp
template<class Container>
void print_container(const Container& con)
{
//没有实例化,不是类型,过不了,得加typename或者用auto
typename Container::const_iterator it = con.begin();
while (it != con.end())
{
//*it += 10;
cout << *it << " ";
++it;
}
cout << endl;
for (auto& e : con)
{
//e *= 10;
cout << e << " ";
}
cout << endl;
}
8.1为什么要实现const迭代器const_iterator而不是const iterator?
因为const修饰iterator,是iterator不能修改,
而const_iterator代表这个迭代器指向的内容不能修改
8.2如何实现?
最基础的理解:
------>对迭代器进行修改的list_iterator 类模板 中的成员函数,只有"解引用"和"箭头访问->"两个函数可能对迭代器指向的内容进行修改*
------>那只要修改这两个函数就行了:
cpp
//原来的:
T& operator*()
{
return _node->_data;
}
T* operator->()
{
return & _node->_data; ///注意 :返回的是T* 指针,不是T的引用
}
//修改后的:
const T& operator*()
{
return _node->_data;
}
const T* operator->()
{
return & _node->_data; ///注意 :返回的是T* 指针,不是T的引用
}
❌‼️但不能直接这样改,这样导致普通迭代器的解引用和箭头访问也失效了
8.3‼️两个模板类的版本实现const_iterator
✅实现两个不同的模板类,list_iterator 模板 类 和 list_const_iterator 模板 类
cpp
///list_iterator 模板
template<class T>
struct list_iterator
{
typedef list_node<T> Node;
typedef list_iterator Self;
Node* _node;
list_iterator(Node* node)
:_node(node)
{ }
T& operator*()
{
return _node->_data;
}
T* operator->()
{
return & _node->_data; ///注意 :返回的是T* 指针,不是T的引用
}
Self& operator++()
{
_node = _node->_next;
return *this;
}
Self& operator--()
{
_node = _node->_prev;
return *this;
}
bool operator!=(const Self& s) const
{
return _node != s._node;
}
bool operator==(const Self& s)const
{
return _node == s._node;
}
};
///list_const_iterator 模板
template<class T>
struct list_const_iterator
{
typedef list_node<T> Node;
typedef list_const_iterator Self;
Node* _node;
list_const_iterator(Node* node)
:_node(node)
{ }
const T& operator*()
{
return _node->_data;
}
const T* operator->()
{
return & _node->_data; ///注意 :返回的是T* 指针,不是T的引用
}
Self& operator++()
{
_node = _node->_next;
return *this;
}
Self& operator--()
{
_node = _node->_prev;
return *this;
}
bool operator!=(const Self& s) const
{
return _node != s._node;
}
bool operator==(const Self& s)const
{
return _node == s._node;
}
};
当然,list模板中也要修改:
cpp
///list 模板
template<class T>
class list
{
typedef list_node<T> Node;
public:
typedef list_iterator<T> iterator;
typedef list_const_iterator<T> const_iterator;/////////////////////////////
iterator begin()
{
iterator it(_head->_next);
return it;
}
iterator end()
{
return _head;
}
const_iterator begin()const ////////////////////////////////////////////////////////////
{
const_iterator it(_head->_next);/////////////////////////////////////改iterator为const_iterator
return it;
}
const_iterator end()const//////////////////////////////////////////////////////////////
{
return _head;
}
const的辨析:
const_iterator:看后面
const的位置 |
作用 | 核心规则 |
|---|---|---|
| 返回值类型前 | 修饰返回值本身,表示返回值不能被修改 | ++只有返回引用 / 指针时才有意义++ ,返回值类型(如int、bool)加了没用 |
| 函数参数前 | 修饰形参,表示函数内部不能修改这个形参 | 传类对象时必须加const&,既避免拷贝又保护实参 |
| 函数末尾 | 只能加在类的成员函数 后面,修饰this 指针 | 表示这个函数不能修改类的任何成员变量 |
✅ 形参的const:
- 形参是普通变量:
void func(const int x)→ x 在函数内部不能改 - 形参是类对象:
void func(const list<int>& lt)→ lt 在函数内部不能改,且只能调用 lt 的 const 成员函数 - 形参是指针:
void func(const int* p)→ 不能通过 p 修改指向的内容,(不能解引用修改和通过"->"修改)
| 操作 | 是否允许 | 原因 |
|---|---|---|
p = &b; |
✅ 可以 | 指针本身可以指向其他地址 |
int a = *p; |
✅ 可以 | 可以读取指向的内容 |
*p = 100; |
❌ 不可以 | 不能通过 p 修改指向的内容 |
(*p)++; |
❌ 不可以 | 不能通过 p 修改指向的内容 |
p++; |
✅ 可以 | 指针本身可以移动 |
✅函数末尾的const:
- 普通成员函数的 this 指针是:
T* const this→ 指针本身不能改,指向的内容可以改 - 末尾加 const 的成员函数的 this 指针是:
const T* const this→ 指针本身和指向的内容都不能改
8.5‼️彻底搞懂const_iterator
1.给谁用:
const 类对象
cpp
// 普通对象
list<int> lt;
list<int>::iterator it = lt.begin();
*it = 100; // ✅
// const对象,不能修改
const list<int> clt = lt;
list<int>::iterator it = clt.begin(); // ❌
list<int>::const_iterator cit = clt.begin(); // ✅
*cit = 200; // ❌ const_iterator指向的内容不能改
2. 为什么有const_iterator?
const对象不能修改,而普通迭代器可以访问并修改,故普通iterator不行‼️
3.const_iterator vs const iterator
| 类型 | 迭代器本身能不能改(++、--) | 指向的内容能不能改(*it = x) |
|---|---|---|
iterator |
✅ 可以 | ✅ 可以 |
const_iterator |
✅ 可以 | ❌ 不可以 |
const iterator |
❌ 不可以 | ✅ 可以 |
const const_iterator |
❌ 不可以 | ❌ 不可以 |
4. 为什么const_iterator的operator*返回const T&,但函数末尾没加 const?
对象+const
- 普通对象(非 const 对象)既可以调用 const 成员函数,也可以调用非 const 成员函数。
- const 对象只能调用 const 成员函数
operator++会修改迭代器自己的_node成员,所以它绝对不能加 const。(不然const成员也能用了,不行)- 因为
const_iterator也只是一种特殊的迭代器啊,实现的功能和iterator是相同的,都是++/--/*
5.函数末尾加 const 和返回值加 const 有没有必然联系?
没有‼️
但有一个注意点:
cpp
class A
{
public:
int& get_a() const//❌
{
return _a;
}
private:
int _a = 10;
};
int main()
{
const A a;
a.get_a() = 20; // ❌ 竟然修改了const对象的成员!权限放大了
return 0;
}
int& get_a()函数后面加了const,代表不论是普通对象还是const对象都能调用来得到_a的引用,自然也都能直接修改_a‼️,如果是const对象,这自然是错的
✅正确写法:
cppconst int& get_a() const { return _a; }
8.6‼️通过模板实现
因为两个iterator模板重复度太高,所以直接传不同的地方对应的内容到模板里面来生成不同的类
✅ **核心设计思想 **
- 普通迭代器和 const 迭代器90% 的代码完全相同 ,只有
operator*和operator->的返回值不同 - 把这两个 "唯一不同的返回值类型" 抽成模板参数
Ref和Ptr,放在类中对应处 - 只写一套迭代器模板代码,通过传入不同的模板参数,生成两个完全不同的迭代器类
✅先来比较一下吧:
list模板
- 原来的list模板
cpp
///list 模板
template<class T>
class list
{
public:
typedef list_iterator<T> iterator;
typedef list_const_iterator<T> const_iterator;
};
- 现在的list模板
- 在 namespace 下写一个迭代器类模板
struct list_iterator(只是模具,不是真正的类),然后在list类内部通过typedef传入不同参数,生成两个实体类
- 在 namespace 下写一个迭代器类模板
cpp
///list 模板
template<class T>
class list
{
public:
/* typedef list_iterator<T> iterator;
typedef list_const_iterator<T> const_iterator;*/
typedef list_iterator<T, T& , T*> iterator;
typedef list_iterator<T,const T&,const T*> const_iterator;
};
list_iterator 模板
- 原来的
list_iterator(和list_const_iterator模板)模板- 在 namespace 下写两个独立的同级实体类模板 :
list_iterator和list_const_iterator(两份重复代码) - 实现了两个类模板,而大部分函数都一样,只有
operator*()和operator->()有区别
- 在 namespace 下写两个独立的同级实体类模板 :
太长了就不写了,上面 "++8.3两个模板类的版本实现const_iterator++ "有写,唯三的区别在下面:
(其实构造函数也是一样的,只是生成对应的_node)
cpp///list_iterator 模板 template<class T> struct list_iterator { ➡️list_iterator(Node* node) :_node(node) { } ➡️T& operator*() { return _node->_data; } ➡️T* operator->() { return & _node->_data; ///注意 :返回的是T* 指针,不是T的引用 } }; ///list_const_iterator 模板 template<class T> struct list_const_iterator { ➡️list_const_iterator(Node* node) :_node(node) { } ➡️const T& operator*() { return _node->_data; } ➡️const T* operator->() { return & _node->_data; ///注意 :返回的是T* 指针,不是T的引用 } };
- 现在的
list_iterator类模板:- 在 namespace 下写一个迭代器类模板
struct list_iterator(只是模具,不是真正的类),然后在list类内部通过typedef传入不同参数,生成两个实体类
- 在 namespace 下写一个迭代器类模板
cpp
///list_iterator 模板
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*()
{
return _node->_data;
}
➡️Ptr operator->()
{
return & _node->_data; ///注意 :返回的是T* 指针,不是T的引用
}
Self& operator++()
{
_node = _node->_next;
return *this;
}
Self& operator--()
{
_node = _node->_prev;
return *this;
}
bool operator!=(const Self& s) const
{
return _node != s._node;
}
bool operator==(const Self& s)const
{
return _node == s._node;
}
};
生成过程:
写list<int> lt;创造对象
------>实例化,T是int,编译器看到typedef list_iterator<int, int&, int*> iterator;和typedef list_iterator<int, const int&, const int*> const_iterator;
------>代入迭代器模板 list_iterator<int, int&, int*> 和list_iterator<int, const int&, const int*>,生成两个类对象
------>typedef取别名,起了iterator和const_iterator这两个短名字
9.erase:迭代器失效及修改
如果我想实现"删除偶数"这个功能,以下代码是错误的,因为erase之后it变成了野指针
❌:
cpp
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);
print_container(lt);
auto it = lt.begin();
while (it != lt.end())
{
if (*it % 2 == 0)
{
lt.erase(it);
}
++it;//此时it已经是野指针了
}
print_container(lt);
}
原来的erase:
cpp
void erase(iterator pos)
{
assert(pos != end());///end是哨兵位头节点
///迭代器只是一个类,想要访问对应的节点需要访问其中的_node成员变量
Node* prev = pos._node->_prev;
Node* next= pos._node->_next;
prev->_next = next;
next->_prev = prev;
delete pos._node;
--_size;
}
修改后的erase:
注意点:
- erase是在list这个类中,返回值是迭代器,
cpp
iterator erase(iterator pos)
{
assert(pos != end());///end是哨兵位头节点
///迭代器只是一个类,想要访问对应的节点需要访问其中的_node成员变量
Node* prev = pos._node->_prev;
Node* next = pos._node->_next;// ✅ 这就是那个next!
prev->_next = next;
next->_prev = prev;
delete pos._node;
--_size;
return next;
}
对应的"删除偶数"函数:
cpp
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);
print_container(lt);
auto it = lt.begin();
while (it != lt.end())
{
if (*it % 2 == 0)
{
it= lt.erase(it);
}
else
{
++it;
}
}
print_container(lt);
}
10.更加规范的insert:
- 返回值加上了
cpp
iterator insert(iterator pos, const T& x)
{
Node* cur = pos._node;///这里应该写成.而不是->,因为 pos是个类(结构体),_node是结构体的成员变量
Node* prev = cur->_prev;
Node* newnode = new Node(x);
newnode->_next = cur;
cur->_prev = newnode;
newnode->_prev = prev;
prev->_next = newnode;
++_size;
return newnode;
}
11.实现析构函数
- 先实现clear,用来清除后面的所有数据
- 然后在析构函数里实现哨兵位头节点的delete‼️,不要写成delete 了,因为这有这一个节点,而且list也不是连续存储的
clear函数:
- 通过erase函数的遍历来实现除头节点的链表删除
- 注意‼️:
- erase是list的成员函数,不是iterator的成员函数
- ❌
++it;多余且错误:erase返回值已指向next
cpp
void clear()
{
auto it = begin();
while (it != end())
{
it = erase(it);
}
_size = 0; /// 补充:清空后重置size
}
实现:
cpp
~list()
{
clear();
delete _head; // 修正:delete[] → delete
_head = nullptr;
}
12.拷贝构造
是一种特殊的构造函数,是构造函数的重载------符合构造函数的所有特点(函数名和类名相同,不准写void,可以重载,如果你不写编译器会自己造一个,类中有类的时候会调用内部类的拷贝构造函数)
- 默认成员函数------用于创建时初始化
- 传参
- 第一个参数是自身类型的引用 ,必须是引用,传值会报错()
- 可以有其他参数,但其他参数必须有缺省值
- 用处: 自定义类型的 ++传值传参++ 和++传值返回++都会调用拷贝构造
- 你不写,编译器自己造的 拷贝构造函数 的行为:
- 对内置类型(int ,char, int* ):只会进行浅拷贝 (一个字节一个字节 复制粘贴)
- 对自定义成员变量(类中有类------两个Stack实现MyQueue):会调用内部的类 自己的拷贝构造函数
- 什么时候不用你写:
- 像是int,char这样的普通内置类型 ,你可以不写
- 像是malloc/calloc/realloc出的 int*, char*,指向资源的内置类型,你得自己写
- ------>传引用返回的好处:传值返回会进行拷贝构造,++传引用不需要拷贝构造++。
- ------>传引用返回的弊端 :
- 如果你引用的是一个局部变量,函数结束后就销毁,就有问题(解决方法:用static让局部变量变成全局变量以成功返回)
- 当然,如果是传值返回一个内置类型,不用操心
❌错误写法:
-
拷贝构造也是构造,这时候list的
private:
Node* _head;
size_t _size;
都还没有初始化‼️
cpp
list(const list& lt)
{
for (auto& e : lt)
{
push_back(e);
}
}
✅正确写法:
cpp
list(const list& lt)
{
_head = new Node(T());
_head->_next = _head;
_head->_prev = _head;
_size = 0;
for (auto& e : lt)
{
push_back(e);
}
}
empty_init 的简洁写法:
下面的构造和拷贝构造看着有点重复度高,
cpp
list()
{
_head = new Node(T());
_head->_next = _head;
_head->_prev = _head;
_size = 0;
}
list(const list& lt)
{
_head = new Node(T());
_head->_next = _head;
_head->_prev = _head;
_size = 0;
for (auto& e : lt)
{
push_back(e);
}
}
所以简化重复部分到一个empty_init 函数中
cpp
void empty_init()
{
_head = new Node(T());
_head->_next = _head;
_head->_prev = _head;
_size = 0;
}
list()
{
empty_init();
}
list(const list& lt)
{
empty_init();
for (auto& e : lt)
{
push_back(e);
}
}
13.赋值运算符重载
-
默认成员函数------用于赋值
-
是"=" 的运算符重载
特点:
- **有限制:**必须是 类的 成员函数,不能是全局函数
- **参数:**参数建议写成const + 当前类型的 引用(减少拷贝)
- return和返回值 :Date ,(
return*this)(有返回值是为了支持连续赋值)。返回值也建议写成 当前类型的 引用 :Date& operator=(const Date& d)(提高效率) - **编译器造的不靠谱:**你不写,编译器自动生成,也是浅拷贝(按字节拷贝,和拷贝构造函数一样,像是Stack,有malloc/calloc/realloc出的 int*, char ,指向资源的内置类型,你得自己写
赋值运算符重载函数*) - **类中有类:**对自定义成员变量(类中有类------两个Stack实现MyQueue):会调用内部的类 自己的 赋值运算符重载函数
- 啥时候自己写: 如果一个类显示实现了析构(~ / Destroy),你就也得写它的赋值运算符重载函数 。
实现:
- 实现调换_head 和 _size的swap
- 现代写法:
- 传参的时候直接进行了拷贝构造,得到的是一个新的类对象
list<int> lt - 然后进行
swap lt出了作用域销毁,调用析构函数,直接把原来this的对应内容销毁了,一举两得
- 传参的时候直接进行了拷贝构造,得到的是一个新的类对象
cpp
void swap(list<T>& lt)
{
std::swap(_head, lt._head);
std::swap(_size, lt._size);
}
list<T>& operator=( list<int> lt)
{
swap(lt);
return *this;
}
全部代码

cpp
#pragma once
#include<assert.h>
#include<iostream>
using namespace std;
namespace lcj
{
///list_node 模板
template<class T>
struct list_node
{
T _data;
list_node<T>* _prev;
list_node<T>* _next;
list_node(const T& data = T())
:_data(data)
,_next(nullptr)
,_prev(nullptr)
{ }
};
///list_iterator 模板
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*()
{
return _node->_data;
}
Ptr operator->()
{
return & _node->_data; ///注意 :返回的是T* 指针,不是T的引用
}
Self& operator++()
{
_node = _node->_next;
return *this;
}
Self& operator--()
{
_node = _node->_prev;
return *this;
}
bool operator!=(const Self& s) const
{
return _node != s._node;
}
bool operator==(const Self& s)const
{
return _node == s._node;
}
};
/////list_const_iterator 模板
//template<class T>
//struct list_const_iterator
//{
// typedef list_node<T> Node;
// typedef list_const_iterator Self;
// Node* _node;
// list_const_iterator(Node* node)
// :_node(node)
// { }
// const T& operator*()
// {
// return _node->_data;
// }
// const T* operator->()
// {
// return & _node->_data; ///注意 :返回的是T* 指针,不是T的引用
// }
// Self& operator++()
// {
// _node = _node->_next;
// return *this;
// }
// Self& operator--()
// {
// _node = _node->_prev;
// return *this;
// }
// bool operator!=(const Self& s) const
// {
// return _node != s._node;
// }
// bool operator==(const Self& s)const
// {
// return _node == s._node;
// }
//};
///list 模板
template<class T>
class list
{
typedef list_node<T> Node;
public:
/* typedef list_iterator<T> iterator;
typedef list_const_iterator<T> const_iterator;*/
typedef list_iterator<T, T& , T*> iterator;
typedef list_iterator<T,const T&,const T*> const_iterator;
iterator begin()
{
iterator it(_head->_next);
return it;
}
iterator end()
{
return _head;
}
const_iterator begin()const
{
const_iterator it(_head->_next);
return it;
}
const_iterator end()const
{
return _head;
}
void empty_init()
{
_head = new Node(T());
_head->_next = _head;
_head->_prev = _head;
_size = 0;
}
list()
{
empty_init();
}
list(const list& lt)
{
empty_init();
for (auto& e : lt)
{
push_back(e);
}
}
~list()
{
clear();
delete _head;
_head = nullptr;
}
void swap(list<T>& lt)
{
std::swap(_head, lt._head);
std::swap(_size, lt._size);
}
list<T>& operator=( list<int> lt)
{
swap(lt);
return *this;
}
void clear()
{
auto it = begin();
while (it != end())
{
it = erase(it);
///++it;多余且错误‼️
}
_size = 0; /// 补充:清空后重置size
}
//void push_back(const T& x)
//{
// Node* newnode = new Node(x);
// Node* tail = _head->_prev; // 先拿到原来的尾节点
// newnode->_next = _head; // 新节点的后继指向头节点
// newnode->_prev = tail; // 新节点的前驱指向旧尾
// tail->_next = newnode; // 旧尾的后继指向新节点
// _head->_prev = newnode; // 头节点的前驱指向新节点
// ++_size;
//}
iterator insert(iterator pos, const T& x)
{
Node* cur = pos._node;///这里应该写成.而不是->,因为 pos是个类(结构体),_node是结构体的成员变量
Node* prev = cur->_prev;
Node* newnode = new Node(x);
newnode->_next = cur;
cur->_prev = newnode;
newnode->_prev = prev;
prev->_next = newnode;
++_size;
return newnode;
}
void push_front(const T& x)
{
insert(begin(), x);
}
void push_back(const T& x)
{
insert(end(), x);
}
//void erase(iterator pos)
//{
// assert(pos != end());///end是哨兵位头节点
// ///迭代器只是一个类,想要访问对应的节点需要访问其中的_node成员变量
// Node* prev = pos._node->_prev;
// Node* next= pos._node->_next;
// prev->_next = next;
// next->_prev = prev;
// delete pos._node;
// --_size;
//}
iterator erase(iterator pos)
{
assert(pos != end());///end是哨兵位头节点
///迭代器只是一个类,想要访问对应的节点需要访问其中的_node成员变量
Node* prev = pos._node->_prev;
Node* next = pos._node->_next;/// 这就是那个next!
prev->_next = next;
next->_prev = prev;
delete pos._node;
--_size;
return next;
}
void pop_back()
{
erase(--end());
}
void pop_front()
{
erase(begin());
}
size_t size()
{
return _size;
}
bool empty() const
{
return _size == 0;
}
private:
Node* _head;
size_t _size;
};
template<class Container>
void print_container(const Container& con)
{
//没有实例化,不是类型,过不了,得加typename或者用auto
typename Container::const_iterator it = con.begin();
while (it != con.end())
{
//*it += 10;
cout << *it << " ";
++it;
}
cout << endl;
for (auto& e : con)
{
//e *= 10;
cout << e << " ";
}
cout << endl;
}
struct AA
{
int _a1=1;
int _a2=2;
};
void test_list2()
{
list<AA> lta;
lta.push_back(AA());
lta.push_back(AA());
lta.push_back(AA());
lta.push_back(AA());
lta.push_back(AA());
list<AA>::iterator it = lta.begin();
while (it != lta.end())
{
///使用.的方法
//cout << (*it)._a1 << " : " << (*it)._a2 << endl;
///使用->的方法:重载
cout << it.operator->()->_a1 << " : " << it.operator->()->_a2 << endl;
cout<< it->_a1 << " : " << it->_a2 << endl;
++it;
}
cout << endl;
}
//void test_list1()
//{
// list<int> lt;
// lt.push_back(1);
// lt.push_back(2);
// lt.push_back(3);
// lt.push_back(4);
// list<int>::iterator it = lt.begin();
// while (it != lt.end())
// {
// cout << *it << " ";
// ++it;
// }
// cout << endl;
//} /// 测试
void test_list2()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.push_back(5);
print_container(lt);
list<int> lt2(lt);
print_container(lt);
auto it = lt.begin();
while (it != lt.end())
{
if (*it % 2 == 0)
{
it = lt.erase(it);
}
else
{
++it;
}
}
print_container(lt);
lt2 = lt;
print_container(lt);
}
void test_list1()
{
}
}
更新代码
cpp
#pragma once
#include<assert.h>
#include<iostream>
using namespace std;
namespace lcj
{
// List的节点类
template<class T>
struct list_node
{
list_node(const T& data = T())///初始化列表版本
:_data(data)
, _next(nullptr)
, _prev(nullptr)
{
}
/// 缺省参数版本
//T _data = T();
//list_node<T>* _prev = nullptr;
//list_node<T>* _next = nullptr;
T _data ;
list_node<T>* _prev ;
list_node<T>* _next ;
};
// List的迭代器类
template<class T, class Ref, class Ptr>
struct list_iterator
{
typedef list_node<T> Node;
typedef list_iterator<T, Ref, Ptr> Self;
list_iterator(Node* node = nullptr)
:_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;
}
bool operator!=(const Self& s) const
{
return s._node != _node;
}
bool operator==(const Self& s) const
{
return s._node == _node;
}
Node* _node;
};
// list类
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;
public:
list()
{
empty_init();
}
list(const list<T>& lt)
{
empty_init();
for (auto& e : lt)
{
push_back(e);
}
}
list<T>& operator=(list<T> lt) // 修正:原代码写的list<int>是错误的,应该是list<T>
{
swap(lt);
return *this;
}
~list()
{
clear();
//erase(_head);
delete _head;
_head = nullptr;
}
iterator begin()
{
return _head->_next;///实际上是返回后进行了隐式类型转换(有对应的构造函数就行)
}
iterator end()
{
return _head;
}
const_iterator begin() const
{
return _head->_next;
}
const_iterator end() const
{
return _head ;
}
// List Capacity
size_t size()
{
return _size;
}
bool empty() const
{
return _size == 0;
}
void push_back(const T& x)
{
insert(_head, x);
}
void pop_back()
{
//assert(end() != _head);
assert(!empty());
erase(_head->_prev);
}
void push_front(const T& x)
{
insert(_head->_next, x);
}
void pop_front()
{
//assert(end() != _head);
assert(!empty());
erase(begin());
}
// 在pos位置前插入值为x的节点,返回新节点的迭代器
iterator insert(iterator pos, const T& x)
{/// prev newnode pos
///这里错了,并不要改next,而是改"this"
//Node* newnode = new Node(x);
//Node* prev = pos._node->_prev;
//Node* next = pos._node->_next;
//newnode->_prev = prev;
//newnode->_next = next;
//prev->_next = newnode;
//next->_prev = newnode;
Node* newnode = new Node(x);
Node* prev = pos._node->_prev;
Node* pcur = pos._node;
newnode->_prev = prev;
newnode->_next = pcur;
prev->_next = newnode;
pcur->_prev = newnode;
++_size;
return newnode;
}
// 删除pos位置的节点,返回该节点的下一个位置
iterator erase(iterator pos)
{
assert(pos._node != _head);
Node* prev = pos._node->_prev;
Node* next = pos._node->_next;
prev->_next = next;
next->_prev = prev;
delete pos._node;
pos._node = nullptr;
///不要忘了改变_size
--_size;
return next;
}
void clear()
{
auto it = begin();
while (it != end())
{
it=erase(it);///不要忘了改变it
}
///不用改size,因为erase里面已经改过了
//_size = 0;///这里不要忘了改_size
}
void swap(list<T>& lt)
{
std::swap(_head, lt._head);
std::swap(_size, lt._size);
}
private:
void empty_init()
{
_head = new Node(T());///传参,还是传参靠谱至少
_head->_next = _head;///
_head->_prev = _head;///
_size = 0;
}
Node* _head = nullptr;
size_t _size = 0;
};
// 通用打印函数
template<class Container>
void print_container(const Container& con)
{
for (auto& e : con)
{
cout << e << " ";
}
cout << endl;
}
// 测试用结构体
//struct AA
//{
// int _a1 = 1;
// int _a2 = 2;
//};
//// 测试函数
//void test_list1();
//void test_list2();
//template<class Container>
//void print_container(const Container& con)
//{
// //没有实例化,不是类型,过不了,得加typename或者用auto
// typename Container::const_iterator it = con.begin();
// while (it != con.end())
// {
// //*it += 10;
// cout << *it << " ";
// ++it;
// }
// cout << endl;
// for (auto& e : con)
// {
// //e *= 10;
// cout << e << " ";
// }
// cout << endl;
//}
struct AA
{
int _a1 = 1;
int _a2 = 2;
};
void test_list3()
{
list<AA> lta;
lta.push_back(AA());
lta.push_back(AA());
lta.push_back(AA());
lta.push_back(AA());
lta.push_back(AA());
list<AA>::iterator it = lta.begin();
while (it != lta.end())
{
///使用.的方法
//cout << (*it)._a1 << " : " << (*it)._a2 << endl;
///使用->的方法:重载
cout << it.operator->()->_a1 << " : " << it.operator->()->_a2 << endl;
cout << it->_a1 << " : " << it->_a2 << endl;
++it;
}
cout << endl;
}
//void test_list1()
//{
// list<int> lt;
// lt.push_back(1);
// lt.push_back(2);
// lt.push_back(3);
// lt.push_back(4);
// list<int>::iterator it = lt.begin();
// while (it != lt.end())
// {
// cout << *it << " ";
// ++it;
// }
// cout << endl;
//} /// 测试
void test_list2()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.push_back(5);
print_container(lt);
list<int> lt2(lt);
print_container(lt2);
auto it = lt.begin();
while (it != lt.end())
{
if (*it % 2 == 0)
{
it = lt.erase(it);
}
else
{
++it;
}
}
print_container(lt);
lt2 = lt;
print_container(lt2);
}
}