C++打怪升级(十一)- STL之list

~~~~

  • 前言
  • 1. list是什么
  • 2. list接口函数的使用
    • 1. 构造相关
      • 默认构造
      • n个val构造
      • 迭代器范围构造
      • 拷贝构造
    • 2 赋值运算符重载函数
    • 2 析构函数
    • 3 迭代器相关
      • begin 和 end
      • rbegin 和rend
    • 4 容量相关
      • empty
      • size
    • 5 元素访问相关
      • front
      • back
    • 6 修改相关
      • push_back
      • pop_back
      • push_front
      • pop_front
      • insert - 插入新值
      • erase
      • resize
      • assign
      • swap
      • clear
    • 7 其他操作相关
      • reverse - 逆置
      • sort - 排序
      • unique - 去重
      • merge - 合并
      • remove - 删除值为val的元素
      • remove_if - 删除满足条件的元素
      • splice - 从list对象转移元素到另一个list对象
    • 8 非成员函数
      • swap
  • 3. list的模拟实现
    • 基本框架
      • 定义节点结构
      • 定义链表结构
      • 定义迭代器结构 - 类的封装
        • 迭代器分类
        • 模板类的类型与内部类型的特殊使用方式
        • 版本1:只支持非const对象
          • 基本结构
          • 构造函数
          • *运算符重载
          • ->运算符重载
          • 前置++运算符重载
          • 后置++运算符重载
          • 前置--运算符重载
          • 后置--运算符重载
          • !=运算符重载
          • ==运算符重载
          • 代码汇总
        • 版本2:支持const对象和非const对象
          • 基本结构
          • *运算符重载
          • ->运算符重载
          • 代码汇总
        • 版本3:支持const对象和非const对象且简化写法
          • 基本结构
          • *运算符重载
          • ->运算符重载
          • 前置++
          • 后置++
          • 前置--
          • 后置--
          • !=运算符重载
          • ==运算符重载
          • 代码汇总
      • 构造函数
        • 初始化链表 - 代码复用
        • 无参构造
        • 迭代器范围构造
      • 拷贝构造函数
        • 传统写法 - 自己实现
        • 现代写法 - 复用
      • 赋值运算符重载函数
        • 传统写法 - 自己实现
        • 现代写法 - 复用
      • 析构函数
      • 正向迭代器相关
        • begin
        • end
      • 增删
        • insert
        • erase
        • push_back
        • pop_back
        • push_front
        • pop_front
        • clear
        • swap
        • resize
        • size
      • 关系运算符重载
        • ==
        • !=
  • list与vector比较
    • list与vector排序效率简要分析
    • list与vector迭代器差异再次分析 - 调试
  • 结语

美图

前言

本节将介绍list的使用,之后模拟实现list的基本功能,最后将分析与连续顺序容器(vector)的差异。


1. list是什么

list是逻辑上的顺序容器,实际list的储存中是不连续的,相邻节点之间通过指针进行链接。
list是带头节点的双向循环链表,在所有的链表中结构最复杂,使用也最方便。
list在头部和尾部的插入和删除操作、中间部分的插入删除操作都是常数时间,效率很高;与连续顺序容器相比的优势是中间位置的插入和删除操作时间复杂度是常数时间 O ( 1 ) O(1) O(1),而像vector在中间插入或删除元素往往涉及到元素的移动,时间复杂度是 O ( N ) O(N) O(N)。劣势是list作为不连续存储的顺序容器不能实现随机访问元素 O ( 1 ) O(1) O(1),需要从第一个元素开始依次查找,直到找到或到最后一个元素为止,时间复杂度是 O ( N ) O(N) O(N)
image.png


2. list接口函数的使用

1. 构造相关

初始化list对象

默认构造

声明

list ();

eg

list<int> l1;

n个val构造

声明

list (size_type n, const value_type& val = value_type());

eg

list<int> l1(5, 7); // l1: 7 7 7 7 7

迭代器范围构造

声明

template <class InputIterator>
list (InputIterator first, InputIterator last);

eg

vector<int> v{1, 2, 3, 4, 5};
list<int> l1(v.begin() + 1, v.end()); // l1: 2 3 4 5

拷贝构造

声明

list (const list& x);

eg

list<int> l1(5, 6); // 6 6 6 6 6
list<int> l2(l1);   // l2: 6 6 6 6 6 

2 赋值运算符重载函数

声明

list& operator= (const list& x);

eg

list<int> l1(4, 3); // l1: 3 3 3 3
list<int> l2(3, 6); // l2: 6 6 6
l2 = l1; // l2: 3 3 3 3

2 析构函数

释放对象申请的所有资源,为对象销毁做好准备。

~list();

3 迭代器相关

迭代器是像指针一样的类型。

begin 和 end

声明

begin

返回第一个对象的所在位置的迭代器。

iterator begin();
const_iterator begin() const;

end

返回最后一个对象所在位置的下一个位置的迭代器。

iterator end();
const_iterator end() const;

eg

list<int> l1{1,2,3,4,5};
list<int>::iterator it = l1.begin();
while (it != l1.end()) {cout << *it << " ";it++;
}
cout << endl << endl; // 结果为 1 2 3 4 5

rbegin 和rend

声明

rbegin

返回第一个对象的前一个位置的迭代器。

reverse_iterator rbegin();
const_reverse_iterator rbegin() const;

rend

返回最后一个对象的迭代器。

reverse_iterator rend();
const_reverse_iterator rend() const;

eg

list<int> l1{1,2,3,4,5};list<int>::reverse_iterator rit = l1.rbegin();
while (rit != l1.rend()) {cout << *rit << " ";rit++;
}
cout << endl << endl; // 结果为 5 4 3 2 1

4 容量相关

empty

声明
判断list对象是否为空,是返回true;反之返回false。

bool empty() const;

eg

list<int> l1;
l1.empty();      // true
l1.push_back(10);
l1.empty();      // false

size

声明
返回list对象的元素个数

size_type size() const;

5 元素访问相关

front

声明
返回第一个有效元素的引用。
当list为空时调用front函数会产生未定义的行为。

reference front();
const_reference front() const;

eg

list<int> l1{1,2,3,4,5}; // l1: 1 2 3 4 5
l1.front(); // 1

back

声明
返回最后一个元素的引用。
当list为空时调用back函数会产生未定义的行为。

reference back();
const_reference back() const;

eg

list<int> l1{1,2,3,4,5}; // l1: 1 2 3 4 5
l1.back(); // 5

6 修改相关

push_back

声明
尾插一个新元素

void push_back (const value_type& val);

eg

list<int> l1{1,2,3,4,5}; // l1: 1 2 3 4 5
l1.push_back(10); // l1: 1 2 3 4 5 10
l1.size()         // 6

pop_back

声明
尾删一个元素。
当list对象为空时调用该函数程序将会出错。

void pop_back();

eg

list<int> l1{1,2,3,4,5}; // l1: 1 2 3 4 5
l1.pop_back(); // l1: 1 2 3 4
l1.size()         // 4

push_front

声明
头插一个新元素

void push_front (const value_type& val);

eg

list<int> l1{1,2,3,4,5}; // l1: 1 2 3 4 5
l1.push_front(9); // l1: 9 1 2 3 4 5
l1.size()         // 6

pop_front

声明
头删一个元素
当list对象为空时调用该函数程序将会出错。

void pop_front();

eg

list<int> l1{1,2,3,4,5}; // l1: 1 2 3 4 5
l1.pop_front(); // l1: 2 3 4 5
l1.size()         // 4

insert - 插入新值

声明
在迭代器pos指向的元素之前插入一个值为val的元素。

iterator insert (iterator pos, const value_type& val);

eg

list<int> l1{1,2,3,4,5}; // l1: 1 2 3 4 5
list<int>::iterator pos = find(l1.begin(), l1.end(), 3);l1.insert(pos, 9); // l1: 1 2 9 3 4 5

声明
在迭代器pos指向的元素之前插入n个值为val的元素。

void insert (iterator pos, size_type n, const value_type& val);

eg

list<int> l1{1,2,3,4,5}; // l1: 1 2 3 4 5
list<int>::iterator pos = find(l1.begin(), l1.end(), 3);l1.insert(pos, 3, 6); // l1: 1 2 6 6 6 3 4 5

声明
在迭代器pos指向的元素之前插入指定迭代器范围内的所有元素。
指定的迭代器范围是左闭右开区间。

template <class InputIterator>
void insert (iterator pos, InputIterator first, InputIterator last);

eg

list<int> l1{1,2,3,4,5}; // l1: 1 2 3 4 5
vector<int> v{7, 8, 9, 0};
list<int>::iterator pos = find(l1.begin(), l1.end(), 3);l1.insert(pos, 3, 6); // l1: 1 2 7 8 3 4 5

erase

声明
删除迭代器pos指向的元素

iterator erase (iterator position);

eg

list<int> l1{1, 2, 3, 4, 5}; // l1: 1 2 3 4 5
list<int>::iterator pos = find(l1.begin(), l1.end(), 3);
if (pos != l1.end()) {l1.erase(pos);           // l1: 1 2 4 5a
}

声明
删除迭代器范围内的所有元素。

iterator erase (iterator first, iterator last);

eg

list<int> l1{1, 2, 3, 4, 5};    // l1: 1 2 3 4 5
list<int>::iterator pos = find(l1.begin(), l1.end(), 3);
l1.erase(l1.begin(), l1.end()); // l1: 空

resize

声明
设置list对象的大小为n,每个元素都使用val初始化;
如果 n 大于list对象的大小,就尾插新元素直到list对象大小恰好为n;
如果 n 小于list对象的大小,就尾删元素直到list对象大小恰好为n;
如果 n 等于list对象大小,就什么也不做。

void resize (size_type n, value_type val = value_type());

eg

list<int> l1{1,2,3,4,5};	// l1: 1 2 3 4 5
l1.size();					// 5l1.resize(8, 9);			// l1: 1 2 3 4 5 8 8 8
l1.size();					// 8l1.resize(3, 1);			// l1: 1 2 3
l1.size();					// 3

assign

用新值替换就值

声明
使用n个val赋值给list对象。
list对象的大小可能发生变化。

void assign (size_type n, const value_type& val);

eg

list<int> l1{1, 2, 3, 4, 5};	// l1: 1 2 3 4 5
l1.size();						// 5
vector<int> v{2, 2, 3, 3};
l1.assign(4, 6);				// l1: 6 6 6 6
l1.size();						// 4

声明
使用指定迭代器范围内的所有元素赋值给list对象。

template <class InputIterator>
void assign (InputIterator first, InputIterator last);

eg

list<int> l1{1, 2, 3, 4, 5};	// l1: 1 2 3 4 5
l1.size();						// 5
vector<int> v{2, 2, 3, 3};// 4
l1.assign(v.begin(), v.end());	// l1: 2 2 3 3
l1.size();						// 4

swap

声明
交换两个list对象的内容。

void swap (list& x);

eg

list<int> l1{1, 2, 3, 4, 5};
l1.size();
list<int> l2{2, 2, 3 ,3};
l2.size();l1.swap(l2);
// l1: 2 2 3 3		size: 4
// l2: 1 2 3 4 5	size: 5

clear

声明
删除list对象的所有元素,并使list对象的大小为0。

void clear();

eg

list<int> l1{1, 2, 3, 4, 5};// l1: 1 2 3 4 5
l1.size();					// 5l1.clear();					// 清空l1
l1.size();					// 0

7 其他操作相关

reverse - 逆置

声明
逆置list对象的所有元素。

void reverse();

eg

list<int> l1{1, 2, 3, 4, 5};	// l1: 1 2 3 4 5
l1.reverse();					// l1: 5 4 3 2 1
reverse(l1.begin(), l1.end());	// l1: 1 2 3 4 5

sort - 排序

声明
对list对象的所有元素进行排序,无参时采用默认排序规则;有参时传入函数指针或仿函数,使用函数或仿函数定义的规则。
排序方法是归并排序。

void sort();template <class Compare>
void sort (Compare comp);

eg

list<int> l1{1, 3, 5, 2, 4, 6};		// l1: 1 3 5 2 4 6
l1.sort();							// l1: 1 2 3 4 5 6// 传入函数指针
list<int> l2{ 1, 3, 5, 2, 4, 6 };	// l2: 1 3 5 2 4 6
l2.sort(compare<int>);				// l2: 6 5 4 3 2 1// 传入函数对象
list<int> l3{ 1, 3, 5, 2, 4, 6 };	// l3: 1 3 5 2 4 6
l3.sort(com<int>());				// l3: 6 5 4 3 2 1

从大到小排序

template<class T>
bool compare(const T& t1, const T& t2) {return t1 > t2;
}

从大到小排序

template<class T>
struct com {bool operator()(const T& t1, const T& t2) {return t1 > t2;}
};

unique - 去重

声明
无参数时,删除相邻的重复元素;
有参数时,参数是一个函数指针或函数对象,删除的不再是重复的相邻元素,而是满足传入参数所设置的条件的元素;
unique函数不对list对象所有参数进行排序,所以在使用该函数之前需要额外调用排序函数进行排序,以保证重复的元素是相邻的。

void unique();template <class BinaryPredicate>
void unique (BinaryPredicate binary_pred);

eg

list<int> l1{3, 2, 3, 1, 1, 2, 2}; // l1: 3, 2, 3, 1, 1, 2, 2// 不排序直接去重
l1.unique();					   // l1: 3, 2, 3, 1, 2list<int> l2{3, 2, 3, 1, 1, 2, 2};	// l2: 3, 2, 3, 1, 1, 2, 2// 先排序在去重
l2.sort();							// l2: 1, 1, 2, 2, 2, 3, 3
l2.unique();						// l2: 1, 2, 3

merge - 合并

声明
合并两个已经排好序的list对象;
无参时,按默认规则把x中的所有元素依次转移到本list对象中,转移的操作为把x的元素的值依次与本list对象元素的值进行比较,如果x的元素的值大于比较的值则把x的元素插入到比较的元素之后;反之就继续比较直到最后一个元素。转移完成后,x对象就为空了。
有参时,区别是传入的函数指针或函数对象定义了比较的规则,转移时不在根据默认默认的规则进行。

void merge (list& x);template <class Compare>
void merge (list& x, Compare comp);

eg

list<int> l1{1, 9, 7, 6, 4};	// l1: 1 9 7 6 4 size: 5
list<int> l2{3, 2, 8};			// l2: 3 2 8     size: 3l1.sort();						// l1: 1 4 6 7 9
l2.sort();						// l2: 2 3 8l1.merge(l2);					// l1: 1 2 3 4 6 7 8 9	size: 8// l2: 					size: 0

remove - 删除值为val的元素

声明

void remove (const value_type& val);

eg

list<int> l1{0, 1, 2, 3, 3, 4, 3}; 	// l1: 0, 1, 2, 3, 3, 4, 3	size: 7
l1.remove(3);						// l1: 0, 1, 2, 4   		size: 4

remove_if - 删除满足条件的元素

声明

template <class Predicate>
void remove_if (Predicate pred);

eg

list<int> l2{0, 1, 2, 3, 4};	// l1: 0, 1, 2, 4		size: 4
l2.remove_if(isOdd());			// l1: 1, 3    			size: 2

是偶数就返回true

struct isOdd {
bool operator()(const int& t) {return t % 2 == 0;
}
};

splice - 从list对象转移元素到另一个list对象

声明
把list对象x的元素转移到另一个list对象的pos迭代器位置之前;

// 转移x所有的元素
void splice (iterator pos, list& x);
// 转移x迭代器i指向的元素
void splice (iterator pos, list& x, iterator i);
// 转移x中指定迭代器范围的元素
void splice (iterator pos, list& x, iterator first, iterator last);

eg

list<int> l1{1, 2, 3, 4, 5};				// l1: 1, 2, 3, 4, 5
list<int> l2{6, 7, 8};						// l2: 6, 7, 8
auto pos = find(l1.begin(), l1.end(), 3);
l1.splice(pos, l2);							// l1: 1, 2, 6, 7, 8, 3, 4, 5	size: 8// l2: 							size: 0

8 非成员函数

swap

声明

template <class T>
void swap (list<T>& x, list<T>& y);

eg

list<int> l1{1, 2, 3};    		// l1: 1, 2, 3		size: 3
list<int> l2{6, 7, 8, 9};	  	// l2: 6, 7, 8, 9	size: 4
swap(l1, l2);					// 交换                            	// l1: 6, 7, 8, 9	size: 4// l2: 1, 2, 3		size: 3

3. list的模拟实现

本次list实现是简化版,主要以学习为主,着重关注的是list的基本结构和其迭代器的具体实现。特别是const的迭代器与连续顺序容器的迭代器(以Linux下G++采用的SGI版本为例,VS2019下vector的迭代器被封装为了一个类,不再是原生指针)有着巨大的差异:如vector的迭代器是由原生指针typedef而来的,而list迭代器必须封装为一个类才能达到与vector迭代器类似的行为。

基本框架

list是带头双向循环链表,本次实现参考的是Linux中g++中采用的STL的SGI版。

定义节点结构

由于list可以存放多种类型的数据,需要采用类模板的方式。
包含三个成员变量:

节点指针_prev: 指向前一个节点
节点指针_next: 指向后一个节点
_val: 节点储存该类型对应的元素

image.png
struct和class作用基本相同,只是默认成员变量和成员函数是public的。
下文定义的list类模板需要在类内访问节点的成员变量和成员函数,直接使用struct定义节点结构就可以方便实现需求。

template<class T>
struct __list_node {__list_node(const T& val):_val(val),_prev(nullptr),_next(nullptr){ }__list_node* _prev;__list_node* _next;T _val;
};

定义链表结构

链表结构list也是一个类模板,因为其节点储存的元素类型不确定;
list的成员变量有两个:

节点指针_phead: 指向头结点,头结点是整个链表的开始但不存放有效元素
_size: 表示当前链表的有效节点个数,不包括头结点

template<class T>
class list {typedef __list_node<T> node; // 对节点类实例化重定义以简化写法
public:typedef __list_iterator<T> iterator; // 迭代器类型typedef __list_const_iterator<T> const_iterator;
private:node* _phead;size_t _size;
};

定义迭代器结构 - 类的封装

迭代器分类

原生指针实现;
类的封装实现;

模板类的类型与内部类型的特殊使用方式

类名与类型的区别:
普通类:类名就是类型;
类模板:类名+模板参数才是类型,
例外是:在类内可以省略模板参数,只写类名表示类型;
但是并不推荐这种写法,应该写出具体完整的类型,防止以后给自己或他人造成困扰;
例如标准库中list的构造实现:
image.png

版本1:只支持非const对象

迭代器对于链表来说,是一个行为像指针一样的类型。
指针支持的操作有:

解引用*、->、前置++、后置++、前置–、后置–、指针比较操作

原生的节点指针:

  • 不支持++、–操作;
  • 解引用操作*得到的是整个节点而不是预期的节点的值;
  • ->操作得到的是整个节点的地址,而不是节点值的地址;
  • 比较操作是符合的,可以满足比较操作;

如string、vector可以直接借助原生指针实现迭代器类型(当然,不是所有版本的string、vector的迭代器是原生指针,可能是被封装为了一个复杂的类),这依赖于它们天生的物理结构优势-连续存储。

原生指针的迭代器基本不能满足list对迭代器的需求,所以需要把迭代器定义为一个单独的类,依赖运算符重载功能对**运算符*、->、前置++、后置++、前置–、后置–**进行重载以便于满足我们对迭代器的预期。

对迭代器的预期:

  • *得到节点内部的值;
  • ->得到节点内部值的地址;
  • 支持++:迭代器由当前节点指向下一个节点;
  • 支持–:迭代器由当前节点指向前一个节点;
基本结构

迭代器只包含一个节点指针成员变量

template<class T>
struct __list_iterator {typedef __list_node<T> node;typedef __list_iterator<T> iterator;node* _pnode;
};
构造函数

实现基本的构造函数,接受节点地址进行初始化;
由于迭代器类本身不涉及资源的申请和释放,故析构、拷贝构造、赋值运算符重载等函数都不需要显式实现,由编译器生成默认的即可。

__list_iterator(node* pnode):_pnode(pnode){ }
*运算符重载

得到节点内部的值并传引用返回;
由于这个迭代器是非const的,所以迭代器指向的节点也是非const的,故返回类型是非const的:T&。

T& operator*() {return _pnode->_val;
}
->运算符重载

返回节点内部值的地址;
由于这是非const迭代器,所以迭代器指向的节点也是非const的,故·返回类型是非const的:T*

T* operator->() {return &(_pnode->_val);
}
前置++运算符重载

迭代器指向下一个位置,返回下一个位置的迭代器;
即前置++返回++之前的值;

iterator operator++() {_pnode = _pnode->_next;return *this;
}
后置++运算符重载

迭代器指向下一个位置,返回当前位置的迭代器;
即后置++返回++之后的值

iterator operator++(int) {iterator tmp(*this);_pnode = _pnode->_next;return tmp;
}
前置–运算符重载

迭代器指向前一个位置,返回前一个位置的迭代器;

iterator operator--() {_pnode = _pnode->_prev;return *this;
}
后置–运算符重载

迭代器指向前一个位置,返回当前位置的迭代器;

iterator operator--(int) {iterator tmp(*this);_pnode = _pnode->_prev;return tmp;
}
!=运算符重载

两个迭代器不相等即两个迭代器指向的节点不相等;

bool operator!=(iterator it) {return _pnode != it._pnode;
}
==运算符重载

两个迭代器相等即两个迭代器指向的节点相等;

bool operator==(iterator it){return !(*this != it);
}
代码汇总
template<class T>
struct __list_iterator {typedef __list_node<T> node;typedef __list_iterator<T> iterator;node* _pnode;__list_iterator(node* pnode):_pnode(pnode){ }T& operator*() {return _pnode->_val;}iterator operator++() {_pnode = _pnode->_next;return *this;}iterator operator++(int) {iterator tmp(*this);_pnode = _pnode->_next;return tmp;}iterator operator--() {_pnode = _pnode->_prev;return *this;}iterator operator--(int) {iterator tmp(*this);_pnode = _pnode->_prev;return tmp;}bool operator!=(iterator it) {return _pnode != it._pnode;}bool operator==(iterator it){return !(*this != it);}
};
版本2:支持const对象和非const对象

版本1只实现了非const的迭代器,对于const对象无法使用非const迭代器,需要实现const迭代器;
也许你有疑问,为什么不是普通迭代器前加上const就是const迭代器呢?

比如:typedef const __list_iterator const_iterator;

首先清楚const迭代器的const实际上修饰的谁?
肯定不是修饰的迭代器本身,因为不管迭代器是const还是非const的都应该能满足++、–操作,而++、–迭代器一定会改变迭代器,故迭代器本身是非const的;而简单的在普通迭代器之前加上const修饰而成的迭代器将会导致迭代器本身是const的而不能进行++、–操作,故其不是const迭代器。
那const修饰的是谁呢?
const迭代器的const,修饰的其实是迭代器指向的节点,特别是包括节点内的值;
const迭代器与普通迭代器的不同在于operator()和->operator返回类型分别是const T&和const T
至于++、–、比较大小操作同普通迭代器相同;
需要一个新的类模板作为const迭代器类,以满足于不同于普通迭代器的*和->操作;

基本结构
template<class T>
struct __list_const_iterator {typedef __list_node<T> node;typedef __list_const_iterator<T> const_iterator;node* _pnode;
};
*运算符重载

返回迭代器指向节点内的值的引用;
由于是const迭代器,所以指向的节点是const的,故返回类型是const T&;

const T& operator*() {return _pnode->_val;
}
->运算符重载

返回迭代器指向节点内的值的地址;
由于是const迭代器,所以指向的节点是const的,故返回类型是const T*;

const T* operator->(){return &(_pnode->_val);
}
代码汇总
template<class T>
struct __list_const_iterator {typedef __list_node<T> node;typedef __list_const_iterator<T> const_iterator;node* _pnode;__list_const_iterator(node* pnode):_pnode(pnode) { }const T& operator*() {return _pnode->_val;}const_iterator operator++() {_pnode = _pnode->_next;return *this;}const_iterator operator++(int) {const_iterator tmp(*this);_pnode = _pnode->_next;return tmp;}const_iterator operator--() {_pnode = _pnode->_prev;return *this;}const_iterator operator--(int) {const_iterator tmp(*this);_pnode = _pnode->_prev;return tmp;}bool operator!=(const_iterator it) {return _pnode != it._pnode;}bool operator==(const_iterator it){return !(*this != it);}
};
版本3:支持const对象和非const对象且简化写法

版本1和版本2分别是普通迭代器的实现和const迭代器的实现,仔细分析发现二者的代码基本上很相似,主要区别是*和->操作返回的类型,普通迭代器返回非const的类型,const迭代器返回const的类型;

为了简化代码书写,选择合并这两个类模板,把两个不同之处分别多增加参数进行表示:

操作的返回类型分为普通类型和const类型,用参数Ref表示;
->操作的返回类型分为普通
类型和const*类型,用参数Ptr表示;

本质仍然是会生成两个迭代器类型,只是不再是由我们自己显式写出,而是由编译器根据类模板及其参数进行推导然后生成两个迭代器类型,所做的工作没有减少,只是交给类编译器承担。

基本结构
template<class T, class Ref, class Ptr>
struct __list_iterator {typedef __list_node<T> node;typedef __list_iterator<T, Ref, Ptr> Self;node* _pnode;
};

typedef __list_iterator<T, Ref, Ptr> Self;
在一开始,重定义了迭代器类型本身,简化后续书写;
如果还需要对迭代器模板参数进行改动只需要在此处进行修改即可,不需要对后续涉及到的代码进行修改,很便利。

*运算符重载
Ref operator*() {return _pnode->_val;
}
->运算符重载
Ptr operator->() {return &(_pnode->Val);
}
前置++
Self& operator++() {_pnode = _pnode->_next;return *this;
}
后置++
Self operator++(int) {Self tmp = *this;++*this;return tmp;
}
前置–
Self& operator--() {_pnode = _pnode->_prev;return *this;
}
后置–
Self operator--(int) {Self tmp(*this);_pnode = _pnode->_prev;return tmp;
}
!=运算符重载
bool operator!=(const Self& it) const{return _pnode != it._pnode;
}
==运算符重载
bool operator==(const Self& it) const {return !(*this != it);
}
代码汇总
template<class T, class Ref, class Ptr>
struct __list_iterator {typedef __list_node<T> node;typedef __list_iterator<T, Ref, Ptr> Self;node* _pnode;__list_iterator(node* pnode):_pnode(pnode) { }Ref operator*() {return _pnode->_val;}Ptr operator->() {return &(_pnode->Val);}Self& operator++() {_pnode = _pnode->_next;return *this;}Self operator++(int) {Self tmp = *this;++*this;return tmp;}Self& operator--() {_pnode = _pnode->_prev;return *this;}Self operator--(int) {Self tmp(*this);_pnode = _pnode->_prev;return tmp;}bool operator!=(const Self& it) const{return _pnode != it._pnode;}bool operator==(const Self& it) const {return !(*this != it);}
};

构造函数

初始化链表 - 代码复用

当定义一个list对象时,将会调用构造函数对该对象进行初始化:申请一个节点作为头结点,并使节点指针均指向自己。
除了无参的构造函数需要这个功能,迭代器范围做参数的构造函数、拷贝构造函数也需要该功能,为了减少代码冗余,便把申请头结点并处理的功能代码单独抽出来作为一个函数供其他需要的构造函数调用。

void initialize() {_phead = new node(T());_phead->_next = _phead;_phead->_prev = _phead;_size = 0;
}
无参构造
list() {initialize();
}
迭代器范围构造

范围: [ f i r s t , l a s t ) [first, last) [first,last)

template<class InputIterator>
list(InputIterator first, InputIterator last) {initialize();while (first != last) {push_back(*first);++first;}
}

拷贝构造函数

传统写法 - 自己实现

申请并初始化头结点
遍历链表lt,依次取节点中的元素尾插到本链表this中

list(const list<T>& lt) {initialize();node* cur = lt._phead->_next;while (cur != lt._phead) {push_back(cur->_val);cur = cur->_next;}
}
现代写法 - 复用

复用迭代器范围的构造函数先构造一个局部list对象tmp
交换tmp内指针_phead和this内指针_phead,之后除了构造函数作用域tmp对象将自动销毁

list(const list<T>& lt) {initialize();list<T> tmp(lt.begin(), lt.end());swap(tmp);
}

赋值运算符重载函数

传统写法 - 自己实现

避免自己给自己赋值,特殊判断一下,比较两个list对象用到了!=的运算符重载
赋值之前先依次delete本身的所有节点

list<T>& operator=(const list<T>& lt) {if (*this != lt) {clear();node* cur = lt._phead->_next;while (cur != lt._phead) {push_back(cur->_val);cur = cur->_next;}}return *this;
}
现代写法 - 复用

由传引用传参变为传值传参构造局部list对象lt
交换本对象this和lt内部的_phead指针

list<T>& operator=(list<T> lt) {swap(lt);return *this;
}

析构函数

delete释放所有申请的节点,包括头节点
复用了clear函数实现出头节点之外的所有节点的delete释放

~list() {clear();delete _phead;_phead = nullptr;
}

正向迭代器相关

begin

头结点不是储存数据节点,第一个储存数据的有效节点是头结点的下一个节点

iterator begin() {return iterator(_phead->_next);
}
const_iterator begin() const {return const_iterator(_phead->_next);
}
end

循环链表,最后一个节点的下一个节点是头结点

iterator end() {return iterator(_phead);
}
const_iterator end() const {return const_iterator(_phead);
}

增删

insert

在pos迭代器指向的节点之前插入一个新节点

image.png
image.png

iterator insert(iterator pos, T val) {// prev cur newnodenode* newnode = new node(val);node* cur = pos._pnode;node* prev = cur->_prev;newnode->_prev = prev;prev->_next = newnode;newnode->_next = cur;cur->_prev = newnode;++_size;return iterator(newnode);
}
erase
iterator erase(iterator pos) {assert(pos != end());// prev cur nextnode* cur = pos._pnode;node* prev = cur->_prev;node* next = cur->_next;prev->_next = next;next->_prev = prev;delete cur;--_size;return iterator(prev);
}
push_back
void push_back(const T& val){// phead  tail  newnodeinsert(end(), val);
}
pop_back
void pop_back() {erase(--end());
}
push_front
void push_front(const T& val) {insert(begin(), val);
}
pop_front
void pop_front() {erase(begin());
}
clear
void clear() {iterator it = begin();while (it != end()) {it = erase(it);}
}
swap
void swap(list<T>& lt) {std::swap(_phead, lt._phead);
}
resize
void resize(size_t n, const T& val = T()) {size_t sizes = size();if (n > sizes) {while (sizes < n) {push_back(val);++sizes;}}else if (n < sizes) {while (n < sizes) {pop_back();--sizes;}}
}
size
size_t size() const {return _size;
}

关系运算符重载

==
bool operator==(const list<T>& lt) const {return _phead == lt._phead;
}
!=
bool operator!=(const list<T>& lt) const {return !(*this == lt);
}

list与vector比较

list与vector排序效率简要分析

算法库algorithm中的sort函数底层使用快速排序实现,不支持对list进行排序。
list内部实现了自己的排序函数sort,该函数底层使用归并排序实现。
快速排序和归并排序的时间复杂度都是 O ( N ∗ l n N ) O(N*lnN) O(NlnN),但是实际上比较算法库中的sort函数和list内部的sort函数效率时,对同一组数,list内部实现的排序函数运行效率明显低于算法库中的排序函数。
使用同一组数不同情况下效率的比较:

  1. 对vector进行排序;
  2. list的元素先拷贝vector中,对vector排序再拷贝回list中;
  3. 对list进行排序;
void testSort() {int n = 100000;vector<int> v1;vector<int> v2;list<int> l1;list<int>l2;srand(time(0));for (int i = 0; i < n; ++i) {int val = rand();l1.push_back(val);l2.push_back(val);}v1.reserve(n);v2.reserve(n);for (auto& e : l1) {v1.push_back(e);}// vector  对vector进行排序int begin1 = clock();sort(v1.begin(), v1.end());int end1 = clock();// list->vector->list  list的元素先拷贝vector中,对vector排序再拷贝回list中int begin2 = clock();for (auto& e : l1) {v2.push_back(e);}sort(v2.begin(), v2.end());int i = 0;for (auto& e : l1) {e = v2[i++];}int end2 = clock();// list  对list进行排序int begin3 = clock();l2.sort();int end3 = clock();cout << "vector:             " << end1 - begin1 << endl;cout << "list->vector->list: " << end2 - begin2 << endl;cout << "list:               " << end3 - begin3 << endl;
}

image.png

list与vector迭代器差异再次分析 - 调试

相同点:

  • 迭代器的行为相似,都支持*、->、++、–、比较大小、范围for等操作;
  • 都不支持迭代器相加操作;

不同点:

  • vector迭代器是一个原生指针实现(不排除不同版本STL的vector迭代器也是类的封装实现),list迭代器是类的封装实现;
  • vector的数据连续存储,支持迭代器+1、-1、迭代器之间的相减操作,++后指向相邻的下一个位置,–后指向相邻的前一个位置;list的节点在堆上随机存储,节点之间在储存上没有先后顺序,通过节点内的指针逻辑上链接相邻的节点,不支持+1、-1、迭代器相减操作,++后指向不相邻的下一个位置,–后指向不相邻的前一个位置。

image.png


结语

本节简要介绍了list的接口函数,重点实现了list的主要接口函数,特别是封装的迭代器。list由于是节点链接,insert之后直接插入了一个新的节点,原迭代器还指向原节点,并不会导致迭代器失效问题,与连续容器不同;erase之后,删除了原有节点,原迭代器指向了被删除的节点,迭代器是失效的,与连续容器相同。


T h e The The E n d End End

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

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

相关文章

极光笔记 | EngageLab Push的多数据中心接入指南

01背景 作为一个面向全球的消息推送服务平台&#xff0c;我们一直致力于给全球用户提供安全、可靠、高效的消息推送服务&#xff0c;因此我们意识到在不同洲建立数据中心的重要性。这样做将有助于提高我们的服务可用性、降低延迟并保护用户数据的安全性。 第一&#xff0c;通过…

应用架构的演进 I 使用无服务器保证数据一致性

在微服务架构中&#xff0c;一个业务操作往往需要跨多个服务协作完成&#xff0c;包含了读取数据和更新多个服务的数据同时进行。在数据读取和写入的过程中&#xff0c;有一个服务失败了&#xff0c;势必会造成同进程其他服务数据不一致的问题。 亚马逊云科技开发者社区为开发者…

【每日一题】数位和相等数对的最大和

文章目录 Tag题目来源题目解读解题思路方法一&#xff1a;哈希表 写在最后 Tag 【哈希表】【数组】【2023-11-18】 题目来源 2342. 数位和相等数对的最大和 题目解读 在数组中找出数位和相等数对的和的最大值。 解题思路 方法一&#xff1a;哈希表 维护一个不同的数位和表…

如何为初创企业选择合适的 ERP 系统?

**ERP系统**是制造、分销、供应链、金融、会计、风险管理等多个行业必不可少的企业技术解决方案。不论垂直行业、企业规模或目标受众如何&#xff0c;将ERP作为企业管理战略的核心部分都非常重要。 对于渴望发展的小型企业和初创企业来说&#xff0c;更是如此。大型企业需要对…

【广州华锐互动】VR技术助力中小学生安全教育,让学生在虚拟世界中学会自我保护!

随着科技的不断发展&#xff0c;虚拟现实&#xff08;VR&#xff09;技术已经逐渐走进我们的生活。在教育领域&#xff0c;VR技术的应用也日益广泛&#xff0c;为传统的教育模式带来了革命性的变革。中小学生安全教育作为学生生活中的重要组成部分&#xff0c;其重要性不言而喻…

ubuntu20.04安装cv2

查看ubuntu的版本 cat /etc/lsb-release DISTRIB_IDUbuntu DISTRIB_RELEASE20.04 DISTRIB_CODENAMEfocal DISTRIB_DESCRIPTION"Ubuntu 20.04.3 LTS"更改镜像源 cp /etc/apt/sources.list /etc/apt/sources.list.bak cat > /etc/apt/sources.listdeb http://mirr…

keepalived+haproxy配置集群和负载均衡

1、简介 1.1. Keepalived Keepalived 是一个基于VRRP协议来实现的LVS服务高可用方案,可以利用其来避免单点故障。一个LVS服务会有2台服务器运行Keepalived,一台为主服务器(MASTER),一台为备份服务器(BACKUP),但是对外表现为一个虚拟IP,主服务器会发送特定的消息给备…

Python使用大连理工情感本体提取文本的情感倾向

import pandas as pd # 导入词典 df pd.read_excel(Sentiment_dictionary\大连理工情感词汇本体\情感词汇本体.xlsx) # 我们暂时只使用 [词语,词性种类,词义数,词义序号,情感分类,强度,极性] df df[[词语, 词性种类, 词义数, 词义序号, 情感分类, 强度, 极性]] df.head()# 按…

使用ADS进行serdes仿真时,Tx_Diff中EQ的设置对发送端波形的影响。

研究并记录一下ADS仿真中Tx_Diff的EQ设置。原理图如下&#xff1a; 最上面是选择均衡方法Choose equalization method&#xff1a;Specify FIR taps&#xff0c;Specify de-emphasis和none。 当选择Specify de-emphasis选项时&#xff0c;下方可以输入去加重具体的dB值&#x…

手动调用绘图事件

//添加资源文件 //沾到项目底下 画一只路飞 //对绘图事件进行更新

vue-admin-template

修改登录接口 1.f12查看请求接口 模仿返回数据写接口 修改方式1 1.在env.devolopment修改 修改方式2 vue.config.js 改成本地接口地址 配置转发 后端创建相应接口&#xff0c;使用map返回相同的数据 修改前端请求路径 修改前端返回状态码 utils里面的request.js

【VSCode】Visual Studio Code 配置简体中文环境教程

介绍 Visual Studio Code&#xff08;简称 VS Code&#xff09;是一款轻量级的代码编辑器&#xff0c;它支持多种编程语言&#xff0c;并且具有丰富的功能和插件扩展。如果你更喜欢使用简体中文界面&#xff0c;那么本教程将向你展示如何在 VS Code 中配置简体中文环境。 步骤…

网络编程TCP/UDP

1 网络通信概述 1.1 IP 和端口 所有的数据传输&#xff0c;都有三个要素 &#xff1a;源、目的、长度。 怎么表示源或者目的呢&#xff1f;请看图 所以&#xff0c;在网络传输中需要使用“IP 和端口”来表示源或目的。 1.2 网络传输中的 2 个对象&#xff1a;server 和 clie…

【Qt之QStandardItemModel】使用,tableview、listview、treeview设置模型

1. 引入 QStandardItemModel类提供了一个通用的模型&#xff0c;用于存储自定义数据。 以下是其用法&#xff1a;该类属于gui模块&#xff0c;因此在.pro中&#xff0c;需添加QT gui&#xff0c;如果已存在&#xff0c;则无需重复添加。 首先&#xff0c;引入头文件&#xff…

基于Python实现大型家用电器和电子产品在线商店购买数据分析【500010098】

导入模块 import pandas as pd import numpy as np import matplotlib.pyplot as plt获取数据 df pd.read_csv( r"./data/kz.csv",sep,)数据描述 该数据包含2020年4月至2020年11月从大型家用电器和电子产品在线商店购买的数据。 数据说明 event_time&#xff1a…

什么是CDN?什么是安全加速CDN?有什么优势?

安全加速CDN(Content Delivery Network)是一种网络架构&#xff0c;它通过在全球范围内部署服务器并缓存静态和动态内容来提供更快的Web页面加载和更好的用户体验。安全加速CDN可以保护网站免受DDoS攻击、恶意软件和其他安全威胁&#xff0c;从而提高网站的可用性和稳定性。它通…

基于ssm+vue交通事故档案系统

摘要 摘要是对文章、论文或其他文本的主要观点、结论和关键信息的简洁概括。由于你没有提供具体的文章或主题&#xff0c;我将为你创建一个通用的摘要。 本文介绍了一种基于SSM&#xff08;Spring Spring MVC MyBatis&#xff09;和Vue.js的交通事故档案管理系统的设计与实现…

Unity之NetCode多人网络游戏联机对战教程(9)--NetworkAnimator组件

文章目录 前言NetworkAnimatorAnimator的Trigger属性服务器权威模式&#xff08;Server Authoritative Mode&#xff09;客户端权威模式 (Owner Authoritative Mode)学习文档 前言 这个组件是NetCode常用的组件之一&#xff0c;NetworkAnimator跟NetworkTransform一样&#xf…

string类的总结

目录 1.为什么要学习string类 2.string的标准库 3.string类的常用接口说明 1.string类对象的常见构造 2.string类对象的容量操作 3.string类对象的3种遍历方法 3.1 [ ] 下标 3.2 基于范围的for循环 3.3 迭代器 4 string类对象的元素访问 4.1 operator[]&#xff1a; 4.…

抖音直播间涨粉助手,其开发流程与需要的技术和代码分享

先来看实操成果&#xff0c;↑↑需要的同学可看我名字↖↖↖↖↖&#xff0c;或评论888无偿分享 一、直播间涨人气的15种方法 直播间的人气就像水池中的水&#xff0c;想要有源源不断的流量&#xff0c;就要想办法把水龙头的水流开到最大&#xff0c;也就是要增加直播间曝光率&…