(超详细)常见容器的底层实现结构 and 常用接口梳理 and 迭代器失效问题

囊括《 vector , list, dqueue, string, stack, queue ,priority_queue ,map ,unorderedmap,set》

<1> vector

底层: 数组
扩容方式: vs下以2倍速度增长,g++ 下以1.5倍速度增长。 (两种环境下STL的版本不同,前者为PJ版本,后者为SGI版本)

初始化方式:

1)vector<int> v;  // vector 为空,size = 0,没有任何元素2)vector<int> v(v2)  ; //
 		等价于 vector<int> v = v2; // v 将具有和 v2 一样的元素以及容量3)vector<int> v = {1,2,3,4,5,6}; // 等价于 vector<int> v{1,2,3,4,5,6} v被初始化为,列表中元素的拷贝。4)vector<int> v1{1,2,3,4,5,6};
 		vector<int> v2(v1.begin() + 2, v1.end() - 1); // v2被初始化为两个迭代器指定范围的元素,特备适合用来获取一个序列的子序列 注:左闭右开5)vector<int> v(7);   //默认值初始化,v中将包含七个元素,每个元素进行缺省的值初始化,对于int来说,就是被赋值为了0。 元素数量大致可以预知时,而元素需要动态获取时,可以采用这种初始化方式。6)vector<int> v(7,3); // 与上面一样,v中将含有7个元素,不同的是,该七个元素被初始化为了3;

常用接口:

迭代器相关:
		iterator begin();  // 正向迭代器:返回一个迭代器,指向vector对象的第一个元素
		iterator end();    // 正向迭代器:返回一个迭代器,指向vector对象的最后一个元素的后边
		reverse_iterator rbegin(); 	// 反向迭代器:返回一个迭代器,指向vector对象的最后一个元素
		reverse_iterator rend();	// 反向迭代器:返回一个迭代器,指向vector对象的第一个元素的前面
	容量相关:
		size_t size(); 	// 获取当前对象中元素的个数
		size_t capacity(); 	//获取当前对象的容量,是已经分配给当前对象的空间
		void resize(size_t sz,T c = T()); 	//将当前对象的个数设置为sz个,sz比size大,则默认使用0填充,或者指定使用c来填充
		void reserve(size_t n); 	//保证当前对象容量至少有为个
		bool empty() const; 	//判空,判断当前对象元素是否为空,为空返回true,否则返回 false
	元素获取:
		front() cosnt; // 获取第一个元素
		back() const;  // 获取最后一个元素
		v[n];          //使用下标随机访问
	元素修改:
		void push_back(const T& x); 	//尾插,插入元素之前会先检查是否有足够空间,不够则会进行扩容
		void pop_back(); 	//尾删,不会改变容量
		void insert(iterator pos,size_t n,const T& x); 	//指定位置插入元素,插入n个x
		void insert(iterator pos,IpIter first,IpIter last); //指定位置插入迭代器所指范围的元素  
		iterator erase(iterator pos); 	//删除迭代器所指位置的元素
		iterator erase(iterator first,iterator end); 	//删除迭代器所指范围内的元素
		swap(); //交换两个对象的内容
		clear(); //清空对象,即size设置为0
	随机查找位置:
		vector本身没有find方法,其find是依靠algorithm来实现的。
		find(iterator begin(),iterator end(), num); // 返回一个迭代器指针 
		例子:
			vector<int> v{ 1,2,3,4,5,6 };
			vector<int>:: iterator it = find(v.begin(),v.end(),5);

迭代器失效问题:
向容器中添加或者删除元素的操作可能使指向容器的指针、引用、迭代器失效。一个失效的指针、引用、迭代器将不再表示任何元素。
(1)在向容器添加元素后,如果储存空间 “未重新分配” ,指向插入位置之前的元素的迭代器、指针、引用有效,但指向插入位置之后的将会失效。
(2)在从容器删除元素之后,指向被删元素之前元素的迭代器、引用、指针仍有效。尾后迭代器也就失效。
两种列子:

1.遍历时插入元素
iter = v.insert(iter,n); //正常遍历,想要指向下一个元素,就要跳过当前和被添加的元素
iter += 2;
2.遍历时删除元素
注意:erase函数返回的是删除之后的有效的迭代器
iter = v.eares(iter);

<2>list

底层实现:双向循环链表,再某些版本中是采用双向循环链表(例 SGI STL)
特点:可以在常规时间内,再序列已知的人任何位置插入或者删除元素,这就是我们使用list 而不适用vector 或者 queue的原因。
初始化方式

(1)list<string> ls(20)  等价于  list<string> ls{20}; 	//初始化为含有20个元素,且都为空的链表
	(2) list<int> ls(10,1); 	//和vector一样,这里的ls将含有十个元素,且每一个元素的值是1
	(3) list<int> ls(ls1); 		//容器含有拷贝函数,生成另一个list的副本。
	(4) list<int> ls(ls1.begin(),ls1.end()); // 可以用另一个list的开始迭代器和结束迭代器来初始化当前list。

常用接口:

迭代器:与vector大致相同
容量操作:
	size_t size();
	bool empty();
	void clear();
元素访问:
	reference front(); //返回第一个元素的引用
	reference back();  //返回最后一个元素的引用
元素修改:
	void push_front(const value_type& val);  //在链表头部插入只为val的节点
	void pop_front();   //删除链表头部结点
	void push_back()//尾插
	void pop_back();	//尾删
	insert(插入):
		iterator insert(iterator pos,const value_type& val);	//在pos之前插入一个元素val
		iterator insert(iterator pos,size_type n,const value_type& val);	//在pos位置之前插入n个val
		iterator insert(iterator pos,iterator first,iterator last); 	// pos之前插入[first,last)迭代器范围内的所有元素,左闭右开
	earse(删除):
		iterator erase(const_iterator pos); 	//删除pos位置的元素
		iterator erase(const_iterator first,const_iterator last);	//删除[first,last) 迭代器范围内的元素,左闭右开
	void swap(lsit& x);	//交换两个list的元素
	void resize(size_type n,value_type val = value_type()); 	//将现有的list中的有效元素变为n个,大于出当前size的用val填充

迭代器失效问题:
因为vector的元素是顺序存储的,所以删除一个元素会导致后面的所有元素会向前移动一个位置,所以当前位置之后的迭代器就会失效。(影响后面所有)
但是,list使用了不连续分配的内存,删除会使得当前位置的迭代器失效,但是不会失效其他的迭代器。
因此:对于list删除引起的迭代器失效的解决方式是为:
1.与vector相同,erase会返回下一个有效的迭代器
2.或者erase(iter++); //提前使得iter为下一个有效元素的迭代器
注:list的插入不会是的迭代器失效

<3>deque(双端队列)

底层结构:采用分块的线性存储结构(块的大小一般为512B,成为deque块),支持随机访问,可以通过下标来访问元素。所有的块儿通过map块进行管理,每个map数据项记录deque块的首地址,所以允许较为快速的随机访问。
特点:头部尾部都可以快速的插入元素,不需要移动其他元素
构造函数:

	deque<T> deqT;				//默认构造形式
	deque(beg, end);			//构造函数将[beg, end)区间中的元素拷贝给本身。
	deque(n, elem);				//构造函数将n个elem拷贝给本身。
	deque(const deque &deq);	//拷贝构造函数
主要操作函数:	
	push_back(T elem);			//在容器尾部添加一个数据
	push_front(T elem);			//在容器头部插入一个数据
	pop_back();					//删除容器最后一个数据
	pop_front();				//删除容器第一个数据


	insert(iterator pos, T elem);						//在pos位置插入一个elem元素的拷贝,返回新数据的位置。
	insert(iterator pos,size_t n, T elem);				//在pos位置插入n个elem数据,无返回值。
	insert(iterator pos,iterator beg, iterator end);	//在pos位置插入[beg,end)区间的数据,无返回值。

	clear();//移除容器的所有数据
	erase(iterator beg,iterator end);					//	删除[beg,end)区间的数据,返回下一个数据的位置。
	erase(iterator pos);								//	删除pos位置的数据,返回下一个数据的位置

迭代器失效问题:
插入操作:
1、在队前或队后插入元素时(push_back(),push_front()),由于可能缓冲区的空间不够,需要增加map中控器,而中控器的个数也不够,所以新开辟更大的空间来容纳中控器,所以可能会使迭代器失效;但指针、引用仍有效,因为缓冲区已有的元素没有重新分配内存。
2、在队列其他位置插入元素时,由于会造成缓冲区的一些元素的移动(源码中执行copy()来移动数据),所以肯定会造成迭代器的失效;并且指针、引用都会失效。
删除操作:
1、删除队头或队尾的元素时,由于只是对当前的元素进行操作,所以其他元素的迭代器不会受到影响,所以一定不会失效,而且指针和引用也都不会失效;
2、删除其他位置的元素时,也会造成元素的移动,所以其他元素的迭代器、指针和引用都会失效。

<4> priority_queue 优先级队列(堆)

底层:本质上是通过一个堆实现的,包括队列的所有特性,只是在基础上添加了一个内部排序,它本质是一个堆实现的。
定义方式:

priority_queue<Type,Container,Functional>  Type就是数据类型,Container是容器类型(如vector,queue等等,但不能使用list,默认使用vector),Functional就是比较方式。
	//升序,小顶堆,堆顶元素最小
	priority_queue<int,vector<int>,greater<int> > q;
	//降序,大顶堆,堆顶元素最大
	priority_queue<int,vector<int>,less<int> > q;
注意:
1.定义priority_queue时,若不指定容器类型,则默认使用vector,若不指定比较方式,则默认生成大堆,底层按照小于号进行比较。
例:
	priority_queue<int> q; 	//定义为大堆,底层容器使用vector
2.当数据类型为自定义类型时,则需要在该类中对 > 或者 < 运算符进行重载,使其可以进行比较。 
3.有些情况下,用户也需要提供比较器规则(重写仿函数)
例:
		class Less
		{
			public:
			bool operator()(const Date* pLeft,const Date* pRight)
			{
				return *pLeft < *pRight;
	 		}
		};
 
		void TestPriority_queue()
		{
			//自己制定的比较规则
 			priority_queue<Date*,vector<Date*>,Less> q;
 			q.push(&Date(2019,10,11)); 
 			q.push(&Date(2019,10,12)); 
 			q.push(&Date(2019,10,13)); 
 			cout<<*q.top()<<endl;
		}

接口: 和queue基本一致,包括 push(),pop(),empty(),size() 等;

<5> map && unordered_map && mutile_map

[1] map

底层实现:红黑树
基本特点:map是一类关联式容器,它的特点是删除和插入节点对迭代器的影响比较小,除了当前操作节点,对其他节点没有什么影响。
对于迭代器来说,只可以修改节点的value值,而不能修改key
功能:
1.有自动排序功能(默认升序less,降序为greater)
2.去重功能,不能有key值相同的节点
3.快速插入节点
4.快速删除
5.快速查找(重要)

插入元素方式:

map<int,int> mp;
1.//赋值方式插入
  //插入时若key存在,则相当于修改当前key 的 vaule 为后面数值
  //插入时若key不存在,则相当于相当于插入(1,1)
mp[1] = 1;  //相当于插入(1,1)
2. insert();
由于map的每一个元素都是一个键值对,因此插入时需要插入一个pair
因此: mp.insert(pair<int,int>(1,1));
注意:insert在插入时,若插入的key值不存在,则会将当前键值对插入进去,否则无论vaule值是否不同,都不进行插入。

删除元素方式:

erase(T key);			//通过key值删除
erase(iterator ite);	//通过迭代器删除

查找接口:
iterator find(T key); //查找当前map中是否含有当前key,如果有返回指向该key位置的迭代器,如果没有则返回迭代器end();
迭代器失效问题:
由于map的底层是红黑树,因此每一个节点都是独立不连续的,所以使用迭代器来删除当前结点,只会是的当前的迭代器失效。插入不会使得迭代器失效。
因此为了防止该问题,我们在遍历时删除元素应如下处理:
mp.insert(iter++); //提前使得迭代器++到下一个有效的位置;

[2]multimap

底层及特点:基本与map相同,不同之处在于multimap可含有重复的key。
其次功能上的不同在于:multimap没有重载[],因此不支通过[]访问元素的value
其他常用接口:
count(T key); //返回某一个key出现的次数

[3]unordered_map

底层及特点:与map不同,unordered_map 采用哈希的方式实现,与map不同之处在于其没有排序功能
<6> set && unordered_set && multimap
对应特点与map类相似,区别在于set的元素不是键值对,是单一的数据类型。

<7> string(字符串)

对于字符串string我们最应掌握的就是一系列字符串接口的使用:
构造函数:

	string s;
	string s("hello") 等价于 string s = "hello";
	string s(s1);   //拷贝构造
容量类的接口:
	size();	//返回字符串有效字符串的长度
	capacity();	//返回总空间的大小
	empty();	//判空
	clear();	//清除有效字符,不改变空间大小

	resize(8)   //重新设置s字符串的有效个数为8,更改的是size
	resize(10,'x')   //重新设置字符串有效字符的长度为10,并且多余位置用x填充
遍历方式:
	1.operator[]    //类似与数组方括号操作
	2.iterator  begin() -> end()  //迭代器遍历
	3.范围for

修改操作:
追加操作:

	append("hello");	//在字符串末尾追加一个字符串
		append(2,'x');		//在字符串末尾追加两个字符x
		append(string s,size_t pos,size_t n); //在当前字符串末尾追加,从s的pos位置开始,复制n个
		也可以使用 += 等运算符进行上面操作。
提取子串:
	substr(size_t pos,size_t n); 	//从pos位置开始,提取n个字符
	c_str() ;		//返回const char* 

查找接口:
	size_t find(char c); 	//查找某一个字符在字符串中第一次出现的位置
	size_t find(string s);	//查找某一个子串在当前字符串中第一次出现的位置

	size_t rfind(char c); 	//从末尾开始,查找某一个字符在字符串中第一次出现的位置
	size_t rfind(string s);	//从末尾开始,查找某一个子串在当前字符串中第一次出现的位置

删除接口:

erase(size_t pos,size_t n);		//从pos位置开始,删除n个字符,若不够则直接删除到字符串结束
	erase(iterator iter);		//删除当前迭代器所指向位置的字符
	erase(iterator begin,iterator end);		//删除迭代器所指范围内的字符

插入接口:
	insert(size_t pos,string s);	//从字符串的pos位置,开始插入字符串s
	insert(size_t pos,size_t n,char c);	//从pos位置开始插入n个字符c
	s1.insert(size_t pos,string s2,size_t indes,size_t n);		//从s1的0位置开始,把字符串s2的第2个位置复制3个字符插入

替换接口:
	replace(size_t pos,size_t n,string s);	//从索引为pos的位置替换n个字符换为 s
	replace(iterator begin,iterator end,string s); //将迭代器所指范围内的字符替换为 s

交换函数:
	s.swap(s1);
	swap(s1,s2);
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值