STL之list底层简单实现(七千字长文详解!)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STL之list底层简单实现(七千字长文详解!)相关的知识,希望对你有一定的参考价值。

list底层的简单实现

list_node的实现!

struct list_node

	list_node* _next;
	list_node* _prev;
	T _data;
;

list_node的构造函数

list_node(const T& x)
	:_data(x),
	_next(nullptr),
	_prev(nullptr)


list的迭代器!——重点!

以前string和vector能使用原生指针的原因是因为存储的物理结构都是连续的!所以可以直接支持原生指针能完成迭代器行为!

==所以list的迭代器是通过类的封装+函数重载实现的!==

用函数重载来实现相似的行为!

list迭代器的成员变量

template<class T>
struct __list_iterator

	typedef list_node<T> node;
	typedef __list_iterator<T> self;//方便添加模板参数
	node* _pnode;

迭代器的构造函数

__list_iterator(node* p)
	:_pnode(p)

* 重载

T& operator*()

	return _pnode->_data;

前置++ 重载

self& operator++()

	_pnode = _pnode->_next;
	return *this;

后置++ 重载

self operator++(int)

	node* cur = _pnode;
	_pnode = _pnode->_next;

	return self(cur);

前置-- 重载

self& operator--()

	_pnode = _pnode->_prev;
	return *this;

后置-- 重载

self operator--(int)

	node* cur = _pnode;
	_pnode = _pnode->_prev;
	return self(cur);

!= 重载

bool operator!=(const self& it)const

	return _pnode != it._pnode;

== 重载

bool operator==(const self& it)const

	return _pnode == it._pnode;

-- 重载

self& operator--()

	_pnode = _pnode->_prev;
	return *this;

list的const迭代器——重点

为了能够实现我们预想的行为我们真正要对修改的是返回值!

==像是对于 *的重载我们原先返回的是 它的引用==

现在我们应该返回的是 它的const T& 从而来进行权限的缩写!

那么我们是不是可以写一个如下的代码呢

const T operator*()const

	return _pnode->data;

让const调用这个const重载 让非const调用非const重载

==不行!我们不能对解引用进行重载!==

==为什么?答案是虽然上面的都可行了但是 我们会发现const的对象无法去调用++了==

==const的对象无法去调用非const的成员函数==

那我们如果实现一个const版本的++呢?

self& operator++()const

	_pnode = _pnode->_next;
	return *this;

==可行吗?当然是不可行的!因为这个const修饰的是this指针!this指针指向的内容就无法修改了!就没办法做到修改pnode了!==

那如何解决这个问题呢?——==就是不要让迭代器本身const!==

==方法1 ——重新写一个类这两个类的唯一区别就是他们的解引用是不一样的!==

一个返回引用,一个返回常引用 其他都是完全一致的!

//迭代器
template<class T>
struct __list_const_iterator

	typedef list_node<T> node;
	node* _pnode;

	__list_iterator(node* p)
		:_pnode(p)
	
	const T operator*()const
	
		return _pnode->data;
	
	//唯一的区别返回常引用
	__list_iterator<T>& operator++();
	__list_iterator<T> operator++(int);
	__list_iterator<T>& operator--();
	__list_iterator<T> operator--(int);
	bool operator!=(const __list_iterator& it);
	bool operator==(const __list_iterator& it);
    Ptr operator->()
;

然后使用这个类去实现const版本的list函数调用接口例如begin和end

typedef __list_const_iterator const_iterator
const_iterator begin()const

	return const_iterator(_head->_next);

const_iterator end()const

	return const_iterator(_head);

然后我们就可以通过这个类来实现const的迭代器!使用的时候const和非const调用的就是两个完全不同的类了!

==但是这样子实在是太繁琐了!为了一个不同而写一个类==

所以有了方法二 ——使用类模板来实现!即使是同一个类模板 但是使用的参数不一样的时候也就是完不同的类!

所以我们可以添加一个模板参数!

template<class T, class Ref>//Ref 引用
struct __list_iterator

	typedef list_node<T> node;
	typedef __list_iterator<T, Ref> self;//方便添加模板参数
	node* _pnode;

	__list_iterator(node* p)
		:_pnode(p)
	
	Ref operator*()//使用第二个模板参数
	
		return _pnode->_data;
	
	self& operator++();
	self operator++(int);
	self& operator--();
	self operator--(int);
	bool operator!=(const self& it)const;
	bool operator==(const self& it)const;
    Ptr operator->()
;
class list

	typedef list_node<T> node;
private:
	node* _head;
public:
	typedef __list_iterator<T, T&>  iterator;
	typedef __list_iterator<T, const T&> const_iterator;

typedef __list_iterator<T,T&>  iterator;//迭代器
typedef __list_iterator<T, const T&> const_iterator;//const 迭代器

这样子我们就通过复用来简洁的完成const的迭代器!

迭代器之-> 重载——重点

template<class T, class Ref,class Ptr>//多引入一个模板参数
struct __list_iterator

    typedef list_node<T> node;
	typedef __list_iterator<T, Ref,Ptr> self;
    Ptr operator->()
	
		return &_pnode->_data;
	


list的成员变量

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;//const 迭代器
private:
	node* _head;
    size_t _size;

list的构造函数(无参)

void empty_initialize()

	_head = new node(T());
	_head->_next = _head;
	_head->_prev = _head;
    _size = 0;

list()

	empty_initialize();

insert

iterator insert(iterator pos, const T& x = T())

	node* newnode = new node(x);
	node* cur = pos._pnode;//取到了这个节点的指针!
	node* prev = cur->_prev;

	newnode->_next = cur;
	newnode->_prev = prev;
	prev->_next = newnode;
	cur->_prev = newnode;
    ++_size;
	return iterator(cur);//返回当前这个新插入的元素的迭代器

begin

iterator begin()

	return iterator(_head->_next);

end

iterator end()

	return iterator(_head);

const版本的begin

const_iterator begin()const

	return const_iterator(_head->_next);

const版本的end

const_iterator end()const

	return const_iterator(_head);

push_back

原始的写法

void push_back(const T& x)

	node* newnode = new node(x);
	node* tail = _head->_prev;
	tail->_next = newnode;
	newnode->_next = _head;
	newnode->_prev = tail;
	_head->_prev = newnode;
    ++_size;

复用的写法

void push_back(const T& x)

    insert(end(), x);//在end的前面插入就是尾插

push_front

void push_front(const T& x)

	insert(begin(), x);//在begin的前面插入就是头插!

swap

void swap(list<T>& lt)

	std::swap(_head, lt._head);
    std::swap(_size, lt._size);

构造函数(带参)

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

	empty_initialize();
	while (first != last)
	
		push_back(*first);
		++first;
	

拷贝构造

传统写法

list(const list<T>& lt)

	empty_initialize();
	//先初始化!创造哨兵位
	for (const auto& e : lt)//使用使用范围for之前要先实现const迭代器
	
		push_back(e); //进行深拷贝
	
	//范围for就是会自动的从lt里面取 其迭代器然后解引用得到T类型的数据
	//如果不使用auto&  一旦T是一个自定义类型 可能会多次发生深拷贝!极大的影响效率!

现代写法

list(const list<T>& lt)

    empty_initialize();
	list<T> temp(lt.begin(), lt.end());
	swap(temp);

赋值重载

传统写法

//传统写法
iterator& operator=(const list<T>& lt)

	if (this != &lt)
	
		clear();//每次赋值前都去清除掉原理的
		for (const auto& e : lt)
		
			push_back(e);
		
	
    return *this;

现代写法

iterator& operator=(list<T> lt)

	swap(lt);
	return *this;

erase

iterator erase(iterator pos)

	assert(pos != end());//不能等于哨兵位!
	node* cur = pos._pnode;
	node* next = cur->_next;
	node* prev = cur->_prev;

	prev->_next = next;
	next->_prev = prev;
	delete cur;
    --_size;
	return iterator(next);//返回删除的节点的下一个节点的迭代器

pop_front

void pop_front()

	erase(begin());//删除掉头结点

pop_back

void pop_back()

	erase(--end());//end()是最后一个节点的下一个!所以要-- 是其指向最后一个节点!

clear

void clear()

	iterator it = begin();
	while (it != end())
	
		it = erase(it);//erase之后不能++ 因为已经发生了迭代器失效!
	

析构函数

~list()

	clear();
	delete _head;
	_head = nullptr;

size

size_t size()const

	return _size;

empty

bool empty()const

	return _size == 0;

代码展示

#pragma once
#include<iostream>
#include<assert.h>
namespace My_STL

	template<class T>
	struct list_node
	
		list_node* _next;
		list_node* _prev;
		T _data;
		list_node(const T& x)
			:_data(x),
			_next(nullptr),
			_prev(nullptr)
		
		

	;
	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* p)
			:_pnode(p)
		

		Ref operator*()
		
			return _pnode->_data;
		
		Ptr operator->()
		
			return &_pnode->_data;
		

		self& operator++()
		
			_pnode = _pnode->_next;
			return *this;
		
		self operator++(int)
		
			node* cur = _pnode;
			_pnode = _pnode->_next;

			return self(cur);
		
		self& operator--()
		
			_pnode = _pnode->_prev;
			return *this;
		
		self operator--(int)
		
			node* cur = _pnode;
			_pnode = _pnode->_prev;
			return self(cur);
		
		bool operator!=(const self& it)const
		
			return _pnode != it._pnode;
		
		bool operator==(const self& it)const
		
			return _pnode == it._pnode;
		
		

	;


	template<class T>
	class list
	
		typedef list_node<T> node;
	private:
		node* _head;
		size_t _size;
	public:
		typedef __list_iterator<T,T&,T>  iterator;
		typedef __list_iterator<T, const T&,const T*> const_iterator;
		void empty_initialize()
		
			_head = new node(T());
			_head->_next = _head;
			_head->_prev = _head;
			_size = 0;
		
		list()
		
			empty_initialize();
		
		template<class InputIterator>
		list(InputIterator first, InputIterator last)
		
			empty_initialize();
			while (first != last)
			
				push_back(*first);
				++first;
			
		
		void swap(list<T>& lt)
		
			std::swap(_head, lt._head);
			std::swap(_size, lt._size);
		

		list(const list<T>& lt)
		
			empty_initialize();
			list<T> temp(lt.begin(), lt.end());
			swap(temp);
		
		~list()
		
			clear();
			delete _head;
			_head = nullptr;
		
		iterator& operator=(list<T> lt)
		
			swap(lt);
			return *this;
		
		void clear()
		
			iterator it = begin();
			while (it != end())
			
				it = erase(it);
			
		
		iterator insert(iterator pos, const T& x = T())
		
			node* newnode = new node(x);
			node* cur = pos._pnode;
			node* prev = cur->_prev;

			newnode->_next = cur;
			newnode->_prev = prev;
			prev->_next = newnode;
			cur->_prev = newnode;
			++_size;
			return iterator(cur);
		

		iterator erase(iterator pos)
		
			assert(pos != end());
			node* cur = pos._pnode;
			node* next = cur->_next;
			node* prev = cur->_prev;

			prev->_next = next;
			next->_prev = prev;
			delete cur;
			--_size;
			return iterator(next);
		

		void push_back(const T& x)
		
			insert(end(), x);
		

		void push_front(const T& x)
		
			insert(begin(), x);
		
		void pop_front()
		
			erase(begin());
		
		void pop_back()
		
			erase(--end());
		
		iterator begin()
		
			return iterator(_head->_next);
		
		const_iterator begin()const
		
			return const_iterator(_head->_next);
		
		iterator end()
		
			return iterator(_head);
		

		const_iterator end()const
		
			return const_iterator(_head);
		
		size_t size()const
		
			return _size;
		
		bool empty()const
				
			//return _head->_next == _head->_prev;
			return _size == 0;
		
	;

;

c++之构造函数,析构函数(五千字长文详解!)

c++之类和对象——构造函数,析构函数

类的六个默认成员函数

如果一个类中什么成员都没有,简称为空类。

空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。

默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。

class date
;

构造函数

构造函数特征

  1. 函数名与类名相同。

  2. 无返回值。//甚至连void类型都不是!

  3. 对象实例化时编译器==自动调用==对应的构造函数。

  4. 构造函数可以重载。//函数名相同参数不同

    可以提供多个构造函数多种初始化方式!

class date

public:

	date(int year, int month, int day)//构造函数没有返回值!类型名就是函数名!
	
		cout << "Iint success!" << endl;
		_year = year;
		_month = month;
		_day = day;
	
    date()//构造函数支持函数重载!
	
		cout << "Iint success!" << endl;
		_year = 1;
		_month = 1;
		_day = 1;
	
    //既可以传参也可以不传参!
    
    /*	
    date(int year = 1, int month = 1, int day = 1)//构造函数没有返回值!类型名就是函数名!
	
		cout << "Iint success!" << endl;
		_year = year;
		_month = month;
		_day = day;
	我们同时也要注意函数重载和缺省值使用的时候避免调用函数的二义性!
	全缺省和无参函数同时存在会出现二义性
    */

private:
	int _year;
	int _month;
	int _day;
;


int main()

	date a(2000, 1, 1);
	date b;//在定义对象后悔自动的去调用!
	return 0;

**我们可以看到我们只是定义一个对象,但是构造函数却已经成功的调用了!**1

//关于无参的一个错误写法
date c();
//编译器不清楚这究竟是一个对象声明,还是一个函数声明!
//无参定义不可以加()

在c++下栈的写法

class stack

public:
	stack(int newcapcacity = 4)
	
		int* temp = (int*)malloc(sizeof(int) * newcapcacity);
		if (temp == nullptr)
		
			perror("malloc fail");
			exit(-1);
		
		_a = temp;
		_top = 0;
		_capacity = newcapcacity;
	
	void Push(int x)
	
		if (_top == _capacity)
		
			int newcapacity = 2 * _capacity;
			int* temp = (int*)realloc(_a, sizeof(int) * newcapacity);
			if (temp == nullptr)
			
				perror("realloc fail");
				exit(-1);
			
			_a = temp;
			_capacity = newcapacity;
		
		_a[_top++] = x;
	
private:
	int* _a;
	int _top;
	int _capacity;
;

int main()

	stack a;
	a.Push(1);
	a.Push(2);
	a.Push(3);
	a.Push(4);
	a.Push(5);
    //我们可以很明显的看出来方便了很多!
	return 0;

5.如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦 用户显式定义编译器将不再生成。(体现了默认特性!)

class date

public:
	void print()
	
		cout << _year << " " << _month << " " << _day << endl;
	
private:
	int _year;
	int _month;
	int _day;
;
int main()

	date A;
	A.print();
	return 0;

class A 

public:
    A()
    
        _a = 0;
        cout << "A()构造函数成功!" << endl;
    
private:
    int _a;
;
class Date

public:
    void print()
    
        cout << _year << "-" << _month << "-" << _day << endl;
    
private:
    //内置类型!
    int _year;
    int _month;
    int _day;
    //什么都没有做,只是增加了它的一个自定义类型!
    A _aa;
;
int main()

    Date A;
    A.print();
    return 0;

我们可以看出来虽然依旧没有处理内置函数,但是自定义类型的aa的默认构造被成功调用了!被Date的默认构造函数成功的调用了!

那这个除了初始化自定义类型的成员函数还有什么意义呢?不是还是要写一个构造函数用来初始化自定义类型吗?而且这样还会导致默认构造函数无法生成去调用自定义类型默认构造函数去初始化,这样不就是都要重写吗?这有什么意义呢?

例如下面这种类型:

class stack

public:
	stack(int newcapcacity = 4)
	
		int* temp = (int*)malloc(sizeof(int) * newcapcacity);
		if (temp == nullptr)
		
			perror("malloc fail");
			exit(-1);
		
		_a = temp;
		_top = 0;
		_capacity = newcapcacity;
	
	~stack()//这就是栈的析构函数!
	
		free(_a);
		_a = nullptr;
		_top = 0;
		_capacity = 0;
	
	void Push(int x)
	
		if (_top == _capacity)
		
			int newcapacity = 2 * _capacity;
			int* temp = (int*)realloc(_a, sizeof(int) * newcapacity);
			if (temp == nullptr)
			
				perror("realloc fail");
				exit(-1);
			
			_a = temp;
			_capacity = newcapacity;
		
		_a[_top++] = x;
	
private:
	int* _a;
	int _top;
	int _capacity;
;

class MyQuene

public:
	void Push(int x)
	
		_PushST.Push(x);
	
    //......剩下读者可以加自己实现
private:
	stack _PopST;
	stack _PushST;
;
int main()

	MyQuene mq;
	mq.Push(1);
	mq.Push(2);

	return 0;


我们可以看出mq完全不需要写构造函数,因为默认构造函数已经够用了!mq的默认构造函数去调用了stack的默认构造函数去初始化了!mq的两个变量!所以完全不需要自己去写构造函数

==那如果遇到要同时使用自定义类型和内置类型又想要使用默认构造去初始化自定义类型的时候我们应该怎么办?==

==答案是可以使用缺省值!==

class stack

    //....

class MyQuene

public:
	void Push(int x)
	
		_PushST.Push(x);
	
    //......剩下读者可以加自己实现
private:
	stack _PopST;
	stack _PushST;
    int a = 10;//答案是可以使用缺省值!这里不是初始化!
;

class Date

public:
    Date(int year = 1, int month = 1, int day = 1)
    
        _year = year;
        _month = month;
        _day = day;
    
private:
    int _year = 0;
    int _month = 0;
    int _day = 0;
;
//用时有缺省值和构造函数的话,还是以构造函数为主!

默认构造函数

class Date

public:
    Date(int year = 1, int month = 1, int day = 1)
    
        _year = year;
        _month = month;
        _day = day;
    
    Date()
    
        _year = 1;
        _month = 1;
        _day = 1;
    
private:
    int _year = 0;
    int _month = 0;
    int _day = 0;
;
int main()

    Date a;
	return 0;

class Date

public:
    Date(int year, int month = 1, int day = 1)
    
        _year = year;
        _month = month;
        _day = day;
    
private:
    int _year = 0;
    int _month = 0;
    int _day = 0;
;
int main()

    Date a;
	return 0;

因为已经自己写了显性的构造函数所以编译器不会自动生成的默认构造函数,但是又不是无参或者全缺省,导致了不存在默认构造函数!

class stack

public:
	stack(int newcapcacity)
	
		int* temp = (int*)malloc(sizeof(int) * newcapcacity);
		if (temp == nullptr)
		
			perror("malloc fail");
			exit(-1);
		
		_a = temp;
		_top = 0;
		_capacity = newcapcacity;
	
    //...
private:
	int* _a;
	int _top;
	int _capacity;
;

class MyQuene

public:
	void Push(int x)
	
		_PushST.Push(x);
	
    //......剩下读者可以加自己实现
private:
	stack _PopST;
	stack _PushST;
;
int main()

	MyQuene mq;
	mq.Push(1);
	mq.Push(2);

	return 0;

这种情况虽然MyQuene自身会生成默认构造函数,但是stack类没有默认构造也会导致这个问题!

如果我们不写stack的构造函数初始化仅仅使用编译器生成的默认构造函数,我们可以使用==全缺省==

class stack

private:
	//int* _a = nullptr;
    int* _a = (int*)malloc(sizeof(int)*4);//也可以使用的malloc!但是其实它现在不会去调用!只有使用的时候才会调用!
	int _top = 0;
	int _capacity = 0;
;

class MyQuene 

public:
	void Push(int x)
	
		_PushST.Push(x);
	
    //......剩下读者可以加自己实现
private:
	stack _PopST;
	stack _PushST;
;

析构函数

通过前面构造函数的学习,我们知道一个对象是怎么来的,那一个对象又是怎么没呢的? 析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成==对象中资源==的清理工作。

析构函数的特征

析构函数是特殊的成员函数,其特征如下

  1. 析构函数名是在类名前加上字符 ~。(按位取反,就是要告诉你和构造函数的功能是相反的!)
  2. ==无参数无返回==值类型。(没有参数意味着也==不支持重载==!只有多个参数才支持重载!)
  3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构 函数不能重载
  4. ==对象生命周期结束时==,C++编译系统系统==自动调用析构函数==
class stack

public:
	stack(int newcapcacity = 4)
	
		int* temp = (int*)malloc(sizeof(int) * newcapcacity);
		if (temp == nullptr)
		
			perror("malloc fail");
			exit(-1);
		
		_a = temp;
		_top = 0;
		_capacity = newcapcacity;
	
	~stack()//这就是栈的析构函数!
	
		free(_a);
		_a = nullptr;
		_top = 0;
		_capacity = 0;
		cout << "destructor success!" << endl;
	//完成的资源清理工作!
	void Push(int x)
	
		if (_top == _capacity)
		
			int newcapacity = 2 * _capacity;
			int* temp = (int*)realloc(_a, sizeof(int) * newcapacity);
			if (temp == nullptr)
			
				perror("realloc fail");
				exit(-1);
			
			_a = temp;
			_capacity = newcapacity;
		
		_a[_top++] = x;
	
private:
	int* _a;
	int _top;
	int _capacity;
;

int main()

	stack a;
	stack b;
    //a,b出了函数作用域就销毁!就是main函数!
    //出了之后会自动调用析构函数!主要用于销毁第三类由malloc生成的变量!
    //像是日期类就不用析构函数了,但是非要写一个是允许的!
	return 0;

  1. 如果类中没有显式定义析构函数,则C++编译器会自动生成一析构函数,一旦 用户显式定义编译器将不再生成。(体现了默认特性!)
class A

public:
	~A()
	
		free(_a);
		cout << "A()析构函数成功!" << endl;
	
	A()
	
		_a = (int*)malloc(sizeof(int) * 4);
	
private:
	int* _a;
;


class date

public:
	void print()
	
		cout << _year << " " << _month << " " << _day << endl;
	
private:
	int _year;
	int _month;
	int _day;
	A _aa;
;

以上是关于STL之list底层简单实现(七千字长文详解!)的主要内容,如果未能解决你的问题,请参考以下文章

5 千字长文+ 30 张图解 | 陪你手撕 STL 空间配置器源码

c++之构造函数,析构函数(五千字长文详解!)

c++之引用(五千字长文详解!)

【万字长文】详解每日站会的各种模式

初步认识c++之命名空间详解(千字长文带你刨析你命名空间的细节)

五千字长文详解Istio实践之熔断和限流工作原理