温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

怎么用C++模拟实现STL容器

发布时间:2022-12-07 09:43:33 来源:亿速云 阅读:107 作者:iii 栏目:开发技术

这篇文章主要介绍了怎么用C++模拟实现STL容器的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇怎么用C++模拟实现STL容器文章都会有所收获,下面我们一起来看看吧。

    一、list的介绍

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

    二、list的排序

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

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

    怎么用C++模拟实现STL容器

    三、迭代器

    1、list的迭代器失效问题

    insert,迭代器不失效

    earse失效

    2、迭代器的功能分类

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

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

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

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

    3、list迭代器的模拟实现

    普通迭代器

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

    博主模拟实现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;//封装一个节点的指针
    };

    怎么用C++模拟实现STL容器

    const迭代器

    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;
    }

    怎么用C++模拟实现STL容器

    但是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、调用拷贝构造时,链表内节点数据为什么已经是深拷贝了?

    怎么用C++模拟实现STL容器

    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();
    	}
    }

    关于“怎么用C++模拟实现STL容器”这篇文章的内容就介绍到这里,感谢各位的阅读!相信大家对“怎么用C++模拟实现STL容器”知识都有一定的了解,大家如果还想学习更多知识,欢迎关注亿速云行业资讯频道。

    向AI问一下细节

    免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

    AI