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

~~~~

美图

前言

本节将介绍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();					// 5

l1.resize(8, 9);			// l1: 1 2 3 4 5 8 8 8
l1.size();					// 8

l1.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();					// 5

l1.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, 2

list<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: 3

l1.sort();						// l1: 1 4 6 7 9
l2.sort();						// l2: 2 3 8

l1.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 newnode
    node* 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 next
    node* 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  newnode
    insert(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

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
STL是指标准模板库(Standard Template Library),它是C++语言的一部分,提供了一系列的模板类和函数,用于支持通用的数据结构和算法。STL的目标是提供高效、可重用和可扩展的组件,以便开发人员能够更轻松地编写高质量的代码。STL包含了许多常见的数据结构,如vector、list、set、map等,以及各种算法,比如排序、查找、遍历等。通过使用STL,开发人员可以更加高效地处理各种数据结构和算法的问题,提高代码的开发效率和质量。 在STL中,我们可以使用各种容器来存储和管理数据。例如,我们可以使用std::map来创建一个键值对的映射,其中每个键都有一个与之相关联的值。下面是一个示例代码,展示了如何创建和使用一个std::map对象: std::map<std::string, int> disMap() { std::map<std::string, int> tempMap{ {"C语言教程",10},{"STL教程",20} }; return tempMap; } std::map<std::string, int> newMap(disMap()); 在这个示例中,disMap()函数创建了一个临时的std::map对象,并初始化了其中的一些键值对。然后,使用移动构造函数将这个临时对象移动到了一个新的std::map对象newMap中。最终,我们可以通过newMap对象来访问和操作这些键值对。 综上所述,STL是C++中的标准模板库,提供了一系列的模板类和函数,用于支持通用的数据结构和算法。STL的使用可以提高代码的开发效率和质量,并且通过各种容器和算法,可以方便地处理各种数据结构和算法的问题。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [C++ STL详解超全总结(快速入门STL)](https://blog.csdn.net/qq_50285142/article/details/114026148)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [【C++实验】阅读STL源码并分析](https://blog.csdn.net/qq_35760825/article/details/125311509)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

re怠惰的未禾

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值