【C++】模拟实现STL容器:list

news2024/11/24 2:06:27


目录

一、list的介绍

二、list的排序

三、迭代器

1、list的迭代器失效问题

2、迭代器的功能分类

3、list迭代器的模拟实现

3.1普通迭代器

3.2const迭代器

4、迭代器价值

5、迭代器operator->的重载

四、模拟实现时遇到的困惑及注意点

1、调用拷贝构造时,链表内节点数据为什么已经是深拷贝了?

2、类名和类型的区别

五、vector和list的优缺点

1、vector

2、list

六、模拟实现list整体代码


一、list的介绍

列表是一种顺序容器,它允许在序列中的任何位置执行常量时间插入和删除操作,并允许在两个方向上进行迭代。

它的底层是一个带头双向循环链表。附一篇博主用C语言写的带头双向循环链表:【数据结构】带头双向循环链表的实现

二、list的排序

list不能用算法库的sort进行排序。算法库中的sort的底层是一个快排,需满足三数取中,需要传入随机访问迭代器,所以list并不适用。

当然list中提供了一个自己的sort,它的底层是一个归并排序。但是这个sort比vector使用算法库的sort还慢,甚至比list的数据先push_back到vector到再用算法库的sort还要慢。

三、迭代器

1、list的迭代器失效问题

insert,迭代器不失效

earse失效

2、迭代器的功能分类

1、单向迭代器:只能++,不能--。例如单链表,哈希表;

2、双向迭代器:既能++也能--。例如双向链表;

3、随机访问迭代器:能++--,也能+和-。例如vector和string。

迭代器是内嵌类型(内部类或定义在类里)

3、list迭代器的模拟实现

3.1普通迭代器

迭代器的实现需要支持解引用(为了取数据),支持++--。

博主模拟实现string和vector时,直接将原生指针typedef成迭代器,是因为数组的结构正好满足迭代器的行为(注:string和vector可以用原生指针实现,但是vs中采用自定义类型封装的方式实现),但是list中的节点地址是不连续的,不能使用原生指针,需要使用类进行封装+运算符重载实现。

//用类封装迭代器
template <class T>
struct __list_iterator
{
    typedef list_node<T> node;
    //用节点的指针进行构造
    __list_iterator(node* p)
        :_pnode(p)
    {}
    //迭代器运算符的重载
    T& operator*()
    {
        return _pnode->_data;
    }
    __list_iterator<T>& operator++()//返回值不要写成node* operator++(),因为迭代器++肯定返回迭代器啊,你返回节点指针类型不对
    { 
        //return _pnode->_next;
        _pnode=_pnode->_next;
        return *this;//返回的是迭代器
    }
    bool operator!=(const __list_iterator<T>& it)
    {
        return _pnode != it._pnode;
    }
public:
    node* _pnode;//封装一个节点的指针
};

3.2const迭代器

const迭代器的错误写法:

typedef __list_iterator<T> iterator;
const list<T>::iterator it=lt.begin();

因为typedef后,const修饰的是迭代器it,只能调用operator*(),调不了operator++()。(重载operator++()为const operator++()也不行,因为const版本++还是改变不了)

正确写法:想实现const迭代器,不能在同一个类里面动脑筋,需要再写一个const版本迭代器的类。

//用类封装const迭代器
template <class T>
struct __list_const_iterator
{
    typedef list_node<T> node;
    //用节点的指针进行构造
    __list_const_iterator(node* p)
        :_pnode(p)
    {}
    //迭代器运算符的重载
    const T& operator*()const
    {
        return _pnode->_data;
    }
    __list_const_iterator<T>& operator++()//返回值不要写成node*,因为迭代器++肯定返回迭代器啊,你返回节点指针类型不对
    {
        //return _pnode->_next;//返回类型错误的
        _pnode = _pnode->_next;
        return *this;//返回的是迭代器
    }
    __list_const_iterator<T>& operator--()
    {
        _pnode = _pnode->_prev;
        return *this;
    }
    bool operator!=(const __list_const_iterator<T>& it)const
    {
        return _pnode != it._pnode;
    }
public:
    node* _pnode;//封装一个节点的指针
};

typedef __list_const_iterator<T> const_iterator;

当然,这样写__list_iterator和__list_const_iterator这两个类会出现代码重复。STL库中是通过类模板多给一个参数来实现,这样,同一份类模板就可以生成两种不同的类型的迭代器(以下为仿STL库的模拟实现): 

//用类封装普通/const迭代器
template <class T,class Ref>
struct __list_iterator
{
    typedef list_node<T> node;
    typedef __list_iterator<T,Ref> Self;
    //用节点的指针进行构造
    __list_iterator(node* p)
        :_pnode(p)
    {}
    //迭代器运算符的重载
    Ref operator*()
    {
        return _pnode->_data;
    }
    Self& operator++()//返回值不要写成node*,因为迭代器++肯定返回迭代器啊,你返回节点指针类型不对
    { 
        //return _pnode->_next;//返回类型错误的
        _pnode=_pnode->_next;
        return *this;//返回的是迭代器
    }
    Self& operator--()
    {
        _pnode = _pnode->_prev;
        return *this;
    }
    bool operator!=(const Self& it)
    {
        return _pnode != it._pnode;
    }
public:
    node* _pnode;//封装一个节点的指针
};

typedef __list_iterator<T, T&> iterator;
typedef __list_iterator<T, const T&> const_iterator;

4、迭代器价值

1、封装底层实现,不暴露底层实现的细节;

2、多种容器提供统一的访问方式,降低使用成本;

C语言没有运算符重载和引用等语法,是实现不了迭代器的。

5、迭代器operator->的重载

迭代器的用法就是模拟指针的行为,如果现在有一个指向结构的指针,那么就需要用到->来解引用。

//*的重载:返回节点的数据
Ref operator*()
{
    return _pnode->_data;
}
//->的重载:返回数据的指针
T* operator->()
{
    return &_pnode->_data;
}

但是operator->使用T*做返回值类型,这样无论是普通迭代器和const迭代器都能修改,所以operator->的返回值类型应该改为泛型:

template <class T, class Ref,class Ptr>
Ptr operator->()
{
    return &_pnode->_data;
}
typedef __list_iterator<T, T&, T*> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;

四、模拟实现时遇到的困惑及注意点

1、调用拷贝构造时,链表内节点数据为什么已经是深拷贝了?

2、类名和类型的区别

普通类:类名等于类型

类模板:类名不等价于类型,例如list类模板类名是list,类型list<int>等。

所以我们平常写函数形参和返回值时,总会带上形参和返回值的类型:

// 赋值运算符重载写法2(赋值运算符重载都可以这么干)
list<T>& operator=(list<T> lt)
{
    swap(lt);
    return *this;
}

但是C++在类模板里面可以用类名代替类型: 

// 赋值运算符重载写法2(赋值运算符重载都可以这么干)
list& operator=(list lt)
{
    swap(lt);
    return *this;
}

建议写代码的时候严格区分类型和类名,让自己和别人能够看的很明白。(了解下C++有这种坑语法即可)

五、vector和list的优缺点

vector和list像左右手一样,是互补关系,缺一不可。vector的优点正是list的缺点,list的优点也是vector的缺点,实际选用容器时,按照需求择优选用。

1、vector

vector的优点(结构牛逼):

1、支持下标的随机访问;

2、尾插尾删效率高(当然扩容的那一次尾插尾删会较慢);

3、CPU高速缓存命中高(数据从缓存加载至CPU中,会加载连续的一段数据,vector因为结构连续,高速缓存命中高)。

vector的缺点:

1、非尾插尾删效率低;

2、扩容有消耗,并存在一定的空间浪费。

vector迭代器失效问题:

insert/erase均失效。(如果string的insert和erase形参是迭代器,那么也会失效,但是大部分接口是下标传参,不考虑失效问题,只有几个接口是迭代器传参,需要注意迭代器失效问题)

2、list

list的优点:

1、按需申请释放,无需扩容;

2、任意位置插入删除时间O(1);(这里说的是插入删除,不要加上查找的时间)

list的缺点:

1、不支持下标的随机访问;

2、CPU高速缓存命中率低;

3、每一个节点除了存储数据外,还需要额外存储两个指针。

list迭代器失效问题:

insert不失效,erase失效。

六、模拟实现list整体代码

#pragma once
#include <iostream>
#include <algorithm>
#include <assert.h>
#include <vector>
using std::cout;
using std::endl;
namespace jly
{
	//链表节点的类
	template <class T>
	struct list_node
	{
		list_node(const T& x = T())//给一个缺省值,变成默认构造函数
			:_next(nullptr)
			, _prev(nullptr)
			, _data(x)
		{}

		list_node* _next;
		list_node* _prev;
		T _data;
	};
	//用类封装普通/const迭代器
	template <class T, class Ref,class Ptr>
	struct __list_iterator
	{
		typedef list_node<T> node;
		typedef __list_iterator<T, Ref,Ptr> Self;
		//用节点的指针进行构造
		__list_iterator(node* p)
			:_pnode(p)
		{}
		//迭代器运算符的重载
		Ref operator*()
		{
			return _pnode->_data;
		}
		Ptr operator->()
		{
			return &_pnode->_data;
		}
		Self& operator++()//返回值不要写成node*,因为迭代器++肯定返回迭代器啊,你返回节点指针类型不对
		{
			//return _pnode->_next;//返回类型错误的
			_pnode = _pnode->_next;
			return *this;//返回的是迭代器
		}
		Self operator++(int)//后置++
		{
			_pnode = _pnode->_next;
			return Self(_pnode->_next);
		}
		Self& operator--()
		{
			_pnode = _pnode->_prev;
			return *this;
		}
		Self operator--(int)//后置--
		{
			_pnode = _pnode->_prev;
			return Self(_pnode->_prev);
		}
		bool operator!=(const Self& it)const
		{
			return _pnode != it._pnode;
		}
		bool operator==(const Self& it)const
		{
			return _pnode == it._pnode;
		}
	public:
		node* _pnode;//封装一个节点的指针
	};
	//用类封装const迭代器
	//template <class T>
	//struct __list_const_iterator
	//{
	//	typedef list_node<T> node;
	//	//用节点的指针进行构造
	//	__list_const_iterator(node* p)
	//		:_pnode(p)
	//	{}
	//	//迭代器运算符的重载
	//	const T& operator*()const
	//	{
	//		return _pnode->_data;
	//	}
	//	__list_const_iterator<T>& operator++()//返回值不要写成node*,因为迭代器++肯定返回迭代器啊,你返回节点指针类型不对
	//	{
	//		//return _pnode->_next;//返回类型错误的
	//		_pnode = _pnode->_next;
	//		return *this;//返回的是迭代器
	//	}
	//	__list_const_iterator<T>& operator--()
	//	{
	//		_pnode = _pnode->_prev;
	//		return *this;
	//	}
	//	bool operator!=(const __list_const_iterator<T>& it)const
	//	{
	//		return _pnode != it._pnode;
	//	}
	//public:
	//	node* _pnode;//封装一个节点的指针
	//};

	//链表类(控制哨兵位)
	template <class T>
	class list
	{
	public:
		typedef list_node<T> node;
		typedef __list_iterator<T, T&,T*> iterator;
		typedef __list_iterator<T, const T&,const T*> const_iterator;
		//typedef __list_const_iterator<T> const_iterator;
		//构造函数
		void empty_initialize()//用于初始化哨兵位
		{
			_head = new node;
			_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;
			}
		}
		//析构函数
		~list()
		{
			clear();
			delete _head;
			_head = nullptr;
		}
		//拷贝构造
		list(const list<T>& lt)
		{
			先初始化*this
			//empty_initialize();
			//for (const auto& e : lt)//取lt的数据给e
			//{
			//	push_back(e);
			//}

			//工具人写法
			list<T> tmp(lt.begin(),lt.end());
			empty_initialize();
			swap(tmp);
		}
		void swap(list<T>& lt)
		{
			std::swap(_head, lt._head);//交换头指针
			std::swap(_size,lt._size);
		}
		赋值运算符重载写法1
		//list<T>& operator=(const list<T>& lt)
		//{
		//	//防止自己给自己赋值
		//	if (this != &lt)
		//	{
		//		clear();
		//		for (const auto& e : lt)
		//		{
		//			push_back(e);
		//		}
		//	}
		//	return *this;
		//}
		// 赋值运算符重载写法2(赋值运算符重载都可以这么干)
		list<T>& operator=(list<T> lt)
		{
			swap(lt);//直接交换
			return *this;
		}
		//插入删除
		void push_back(const T& x)
		{
			/*node* newNode = new node(x);
			node* tail = _head->_prev;
			newNode->_prev = tail;
			newNode->_next = _head;
			tail->_next = newNode;
			_head->_prev = newNode;*/
			insert(end(), x);
		}
		void pop_back()
		{
			erase(--end());
		}
		void push_front(const T& x)//头插
		{
			insert(begin(), x);
		}
		void pop_front()
		{
			erase(begin());
		}
		iterator insert(iterator pos, const T& x)
		{
			node* newNode = new node(x);
			node* prev = pos._pnode->_prev;
			node* cur = pos._pnode;
			newNode->_prev = prev;
			newNode->_next = cur;
			prev->_next = newNode;
			cur->_prev = newNode;
			//return iterator(newNode);
			pos._pnode = newNode;
			++_size;
			return pos;
		}
		iterator erase(iterator pos)
		{
			assert(!empty());
			node* prev = pos._pnode->_prev;
			node* next = pos._pnode->_next;
			prev->_next = next;
			next->_prev = prev;
			delete pos._pnode;
			--_size;
			//return iterator(next);
			pos._pnode = next;
			return pos;
		}
		//链表小接口
		bool empty()const
		{
			return _head->_next == _head;
		}
		void clear()
		{
			/*assert(!empty);
			node* cur = _head->_next;
			while (cur != _head)
			{
				node* next = cur->_next;
				delete cur;
				cur = next;
			}*/
			iterator it = begin();
			while (it != end())
			{
				it = erase(it);//erase返回删除元素的下一个
			}
		}
		size_t size()const
		{
			return _size;
		}
		//普通begin(),end()迭代器
		iterator begin()
		{
			//return iterator(_head->_next);
			return __list_iterator<T, T&,T*>(_head->_next);
		}
		iterator end()
		{
			return __list_iterator<T, T&,T*>(_head);
		}
		//const begin(),end()迭代器
		const_iterator begin()const
		{
			//return const_iterator(_head->_next);
			return __list_iterator<T, const T&,const T*>(_head->_next);
		}
		const_iterator end()const
		{
			return __list_iterator<T, const T&,const T*>(_head);
		}
	private:
		node* _head;//哨兵位
		size_t _size;//用于统计节点个数,空间换时间
		//不加这个私有变量,统计节点个数时间O(N),有这个私有变量,时间O(1),但是每个节点的体积变大
	};


	//测试函数
	struct Pos
	{
		int _row;
		int _col;

		Pos(int row = 0, int col = 0)
			:_row(row)
			, _col(col)
		{}
	};
	void test()
	{
		list<Pos> i;
		i.push_back(Pos(1, 2));
		i.push_back(Pos(2, 5));
		i.push_back(Pos(4, 3));
		list<Pos>::iterator it = i.begin();
		while (it != i.end())
		{
			cout << (&( * it))->_row;//*it取数据,再取地址、解引用得到_row,多此一举
			cout << it->_row;//同第三种写法,编译器为了可读性,省略了一个->
			cout << it.operator->()->_row;//it.operator->()是显示调用,->_row是解引用得到_row
			it++;
		}
	}
	void test1()
	{
		list<std::vector<int>> i;
		std::vector<int> v1(1, 2);
		std::vector<int> v2(2, 4);
		std::vector<int> v3(3, 5);
		i.push_back(v1);
		i.push_back(v2);
		i.push_back(v3);
		list<std::vector<int>> m(i);
		i = m;
		cout << m.size();
	}
}

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

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

相关文章

Spring Cloud Gateway核心过滤器之请求限流详解

环境&#xff1a;SpringBoot2.4.13 Spring Cloud Gateway3.0.1 概述 RequestRateLimiter GatewayFilter工厂使用一个RateLimiter实现来确定当前请求是否允许继续。如果不是&#xff0c;返回HTTP 429 - Too Many Requests(默认情况下)的状态。 该过滤器接受一个可选的keyReso…

基于51单片机的贪吃蛇游戏设计

1绪 论 1.1本课题研究的背景及意义 随着当今社会的发展&#xff0c;人们的生活节奏变得越来越快&#xff0c;人们开始逐渐的融入全球化的世界。人们已经不再局限于一小块天地&#xff0c;加班&#xff0c;出差已经占据了现代人生活的绝大部分。这个时候&#xff0c;一款简单易携…

ChatGPT 体验和思考

一、体验 1、辅助写代码 2、检查代码&#xff08;遗憾的是&#xff0c;不一定是对的&#xff09; 3、分析代码 4、帮你了解/入门一项陌生的技术 小结&#xff1a; AI 会慢慢成为程序员的得力帮手&#xff0c;但目前来看&#xff0c;似乎还是不够成熟。 当然&#xff0c;大多…

《软件工程》2013年期末试卷

北京信息科技大学《软件工程》2013年期末试卷

青岛山水新城二期景观设计 全套设计

目录 1前言 2 1.1 青岛市地理位置与自然概况 2 1.2 山水新城小区概况 3 1.3设计的目的和意义 4 1.4小区园林景观设计现状和发展趋势 5 2本论 6 2.1设计依据 6 2.1.1 平面规划图 6 2.1.2 国家及地方有关规定及标准 6 2.2 设计指导思想 6 2.2.1 “以人为本”的设计理念 6 2.2.2 “…

PHP毕业设计毕设辅导课(1):PHP 基础语法

PHP 基础语法 PHP 脚本在服务器上执行&#xff0c;然后将纯 HTML 结果发送回浏览器。 PHP 基本的语法和输出 PHP 脚本可以放在文档中的任何位置。 <?php // PHP 代码 ?>PHP 脚本以 <?php 开始&#xff0c;以 ?> 结束&#xff0c;代码示例如下&#xff1a; …

[附源码]Python计算机毕业设计Django天狗电子商城系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

用Prophet在Python中进行时间序列预测

预测通常被认为是报告的发展。报告可以帮助我们回答&#xff0c;发生了什么事&#xff1f;预测有助于回答下一个逻辑问题&#xff0c;将会发生什么&#xff1f; 最近我们被客户要求撰写关于时间序列预测的研究报告&#xff0c;包括一些图形和统计输出。 Prophet的目的是“使专…

[附源码]Python计算机毕业设计Django酒店客房管理信息系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;我…

问题解决:记录一次Linux服务器根目录突然爆满

一、出问题了 过了个双休来到公司&#xff0c;同时发现Linux终端的服务器状态中根目录空间直接爆满100%&#xff0c;周五走之前根目录仅仅使用了59%&#xff0c;同时项目服务的后台不停的有日志打印&#xff0c;而且测试的小伙伴说系统登录不上去了。下面记录一下个人排查并解…

CORTEX-A7芯片中断系统基本原理与控制方法

大家好&#xff0c;今天主要和大家聊一聊&#xff0c;如何使用高端芯片的中断系统的方法。 目录 第一&#xff1a;中断的基本简介 ​第二&#xff1a;GIC控制器介绍 第一&#xff1a;中断的基本简介 中断系统是一个处理器重要的组成部分&#xff0c;中断系统极大的提高了CPU的…

Webpack5 快速入门

1. webpack 介绍 2. webpack 基本使用 3. webpack 5大核心概念 4. webpack 配置文件 5. webpack 运行脚本 6. webpack 处理样式资源 一、处理 css 资源 二、处理 less 资源 三、处理 scss 资源 四、处理 stylus 资源 7. webpack 处理图片资源 8. webpack 文件输出目录…

[附源码]计算机毕业设计基于Springboot校园租赁系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

【STM32学习(4)】STM32简述定时器

一、什么是定时器 有计时和定时功能的仪器组件——对于芯片来说&#xff0c;定时器含有计时和定时功能&#xff0c;片内模块——TIM 二、STM32F4xx系列定时器分类 片内外设定时器&#xff08;14个&#xff09; 高级定时器&#xff1a;TIM1、TIM8通用定时器&#xff1a;TIM2…

SASE和零信任--傻傻分不清楚

零信任和SASE&#xff0c;分别来自于两家世界级咨询公司Forrester与Gartner。 首先&#xff0c;Forrester提出零信任&#xff0c;成为近十年来最重要的安全创新理念。然后&#xff0c;Gartner提出SASE&#xff08;安全访问服务边缘&#xff09;&#xff0c;在零信任的基础上面…

[附源码]计算机毕业设计天狗电子商城系统Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

【浅学Java】SpringBoot 配置文件

SpringBoot 配置文件1. 配置文件的作用2. 配置文件的格式.properties配置文件的格式.properties配置文件的缺点.yml配置文件的格式.yml配置文件的优点3. 配置文件的分类3.1 两种类型配置文件3.2 用户自定义配置项4. 配置信息读取4.1 自定义配置信息读取4.2 系统配置信息读取4.3…

MySQL高可用方案之MHA

目录 一、简介 二、MHA特点 三、搭建MySQL MHA 1、安装MHA 2、在所有服务器上配置无密码认证 3、在manager节点上配置MHA 4、 manager节点编辑配置文件&#xff0c;管理 mysql 节点服务器 5、在Master服务器上手动开启vip 6、在 manager 节点上测试 ssh 无密码认证 7、…

安装JDK8绿色版

前言&#xff1a;官网提供的JDK8只有安装包&#xff0c;没有绿色免安版&#xff0c;而我们开发时需要根据需求使用不同的JDK版本&#xff0c;使用安装包安装过程会写入注册表&#xff0c;不方便便携式使用&#xff0c;还会附带安装Java 8 Update&#xff0c;会自动更新。而绿色…

详细介绍NLP中文分词原理及分词工具

基于词表的分词方法 正向最大匹配算法FMM 从左到右扫描文本&#xff0c;得到词的最大匹配。 案例分析&#xff1a; 用正向最大匹配法对“秦皇岛今天晴空万里”进行中文分词&#xff0c;见下表。 词典 &#xff1a;“秦皇岛”“岛”“今天”“天晴”“晴空万里”“万里”………