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.mzph.cn/news/145118.shtml

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

相关文章

二进制的形式在内存中绘制一个对象实例

一、引用类型实例的内存布局 从内存布局的角度来看&#xff0c;一个引用类型的实例由如下图所示的三部分组成&#xff1a;ObjHeader TypeHandle Fields。前置的ObjHeader用来缓存哈希值和同步状态&#xff0c;TypeHandle部分存储类型对应方法表&#xff08;Method Table&…

并发编程之生产者消费者模型

什么是生产者消费者模型 生产者消费者模型是多线程中一个比较典型的模型。 打个比方&#xff1a;你是一个客户&#xff0c;你去超市里买火腿肠。 这段话中的 "你"就是消费者&#xff0c; 那么给超市提供火腿肠的供货商就是生产者。超市呢&#xff1f;超市是不是被…

Scrapy----Scrapy简介

文章目录 概述与应用背景架构和组件功能和特点社区生态概述与应用背景 Scrapy,一个高效、灵活、且强大的Web爬取框架,被广泛应用于数据抓取和网页内容的结构化提取。它是用Python编写的,支持多平台运行,适用于数据挖掘、在线零售信息收集、历史数据存档等多种场景。Scrapy…

【ArcGIS处理】行政区划与流域区划间转化

【ArcGIS处理】行政区划与流域区划间转化 引言数据准备1、行政区划数据2、流域区划数据 ArcGIS详细处理步骤Step1&#xff1a;统计行政区划下子流域面积1、创建批量处理模型2、添加批量裁剪处理3、添加计算面积 Step2&#xff1a;根据子流域面积占比均化得到各行政区固定值 参考…

设计基于STM32F103C8T6微控制器的巡线小车

巡线小车是一种能够在一条预定线追踪路径的小车&#xff0c;广泛应用于工业自动化、物流仓储、智能家居等领域。本设计将使用STM32F103C8T6微控制器来实现一个基础的巡线小车。 硬件组成&#xff1a;1. STM32F103C8T6微控制器开发板&#xff1a;作为巡线小车的核心控制器&…

如何快速搭建Spring Boot接口调试环境并实现公网访问

文章目录 前言1. 本地环境搭建1.1 环境参数1.2 搭建springboot服务项目 2. 内网穿透2.1 安装配置cpolar内网穿透2.1.1 windows系统2.1.2 linux系统 2.2 创建隧道映射本地端口2.3 测试公网地址 3. 固定公网地址3.1 保留一个二级子域名3.2 配置二级子域名3.2 测试使用固定公网地址…

用归并排序算法merge_sort( )求解 逆序对的数量 降低时间复杂度为 nlogn

题目简述 给定一个序列有n个数&#xff0c;求n个数中逆序对的个数&#xff0c;逆序对的定义&#xff1a;i < j && a[i] > a[j]。 输入格式 第一行包含一个整数n。 第二行包含 n 个整数&#xff08;所有整数均在1~1e9范围内&#xff09;&#xff0c;表示整数数…

Maven项目指定main方法配置

例如有个maven工程 打包后 xxx.jar 而这个maven工程里可能有很多main方法,比如测试的main方法 插件指定 <build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-jar-plugin</artifactId>&…

Python武器库开发-flask篇之模板渲染(二十四)

flask篇之模板渲染(二十四) Flask 中的模板是一种将数据和 HTML 代码组合在一起的方式&#xff0c;使得我们可以生成动态的 HTML 页面。使用模板可以使我们的代码更加简洁、易于维护和复用。在真实的环境中&#xff0c;我们往往接触到的是由 html、CSS和JavaScript所做的网页&…

【教3妹学编程-算法题】购买物品的最大开销

3妹&#xff1a;2哥&#xff0c;听说你今天发工资啦&#xff1f; 请我吃饭怎么样&#xff0c;嘿嘿 2哥 : 切&#xff0c;你上周还发工资了呢&#xff0c;也没见你请我吃饭。 3妹&#xff1a;哎呀&#xff0c; 我的工资都用来双11 shopping了&#xff0c; 双11过后我都吃了1周土…

【转载】快速搭建OpenGL环境!!!Windows10 + Visual Studio 2019 搭建OpenGL环境

目录 具体链接&#xff1a; 【转自】 完成后效果如下&#xff1a; 一开始的爆红&#xff1a; 下载安装后&#xff1a; 运行成功&#xff1a; 具体链接&#xff1a; 萌新向&#xff01;&#xff01;&#xff01;Windows10 Visual Studio 2019 搭建OpenGL环境&#xff08;…

基于springboot实现体育场馆运营平台项目【项目源码】

基于springboot实现体育场馆运营管理系统演示 系统开发平台 在该数码论坛系统中&#xff0c;Eclipse能给用户提供更多的方便&#xff0c;其特点一是方便学习&#xff0c;方便快捷&#xff1b;二是有非常大的信息储存量&#xff0c;主要功能是用在对数据库中查询和编程。其功能…

2024清理mac苹果电脑内存免费工具CleanMyMac X4.15

当你使用苹果电脑时&#xff0c;内存的优化和清理变得至关重要。随着时间的推移&#xff0c;我们的电脑内存可能会变得拥挤&#xff0c;导致性能下降。清理内存可以提高电脑的速度和反应能力&#xff0c;并确保它始终在良好状态下运行。本文将向您介绍怎么清理苹果电脑内存的方…

深眸科技革新升级OCR技术,与AI视觉实现有效融合赋能各行业应用

OCR即光学字符识别&#xff0c;是通过扫描仪或工业相机等电子设备检查打印的字符&#xff0c;并通过检测暗、亮的模式确定其形状&#xff0c;然后用字符识别方法将形状翻译成计算机文字的过程。 目前&#xff0c;随着机器视觉和人工智能技术的进一步升级&#xff0c;OCR技术实…

鸿蒙原生应用开发-折叠屏、平板设备服务卡片适配

一、多设备卡片适配原则 为不同尺寸的卡片提供不同的功能 在卡片开发过程中请考虑适配不同尺寸的设备&#xff0c;特别是在折叠屏和平板设备上&#xff0c;设备屏幕尺寸的变化直接影响了卡片内容的展示。请发挥想象力设计具有自适应能力的卡片&#xff0c;避免在卡片内容不做…

代码随想录图论|130. 被围绕的区域 417太平洋大西洋水流问题

130. 被围绕的区域 **题目&#xff1a;**给你一个 m x n 的矩阵 board &#xff0c;由若干字符 ‘X’ 和 ‘O’ &#xff0c;找到所有被 ‘X’ 围绕的区域&#xff0c;并将这些区域里所有的 ‘O’ 用 ‘X’ 填充。 题目链接&#xff1a;130. 被围绕的区域 解题思路&#xff1a…

物联网AI MicroPython学习之语法 I2C总线

学物联网&#xff0c;来万物简单IoT物联网&#xff01;&#xff01; I2C 介绍 模块功能: I2C Master设备驱动 接口说明 I2C - 构建硬件I2C对象 函数原型&#xff1a;I2C(id, scl, sda, freq)参数说明&#xff1a; 参数类型必选参数&#xff1f;说明idintYI2C外设&#xff…

数据结构(c语言版本) 二叉树的遍历

要求 实现二叉树的创建&#xff0c;并输入二叉树数据 然后先序遍历输出二叉树、中序遍历输出二叉树、后序输出二叉树 例如二叉树为&#xff1a; 该二叉树的先序遍历结果为&#xff1a; A B D C E F 该二叉树的中序遍历结果为&#xff1a; B D A E C F 该二叉树的后序遍历结果…

Mybatis的Mapper接口传递多个参数的时候必须要加@Param注解吗?

答案是&#xff1a;不一定&#xff0c;取决于mybatis的版本、jdk的版本和javac的编译选项。 测试代码 Maven依赖&#xff1a; <dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId>…

linux三次握手、四次挥手

TCP协议是一个安全的、面向连接的、流式传输协议&#xff0c;所谓的面向连接就是三次握手&#xff0c;对于程序猿来说只需要在客户端调用connect()函数&#xff0c;三次握手就自动进行了。先通过下图看一下TCP协议的格式&#xff0c;然后再介绍三次握手的具体流程。 1.tcp协议…