【STL】vector

向量 ( v e c t o r vector vector)是 C C C++ 标准模板库( S T L STL STL)中封装的顺序容器 ( S e q u e n c e C o n t a i n e r Sequence\ Container Sequence Container),是一个能够存放任意类型动态数组,能够增加和压缩数据。

文章目录

  • [前言 ------ 序列式容器(sequence containers)](#前言 —— 序列式容器(sequence containers))
    • [1. 容器的概观](#1. 容器的概观)
    • [2. 容器的分类](#2. 容器的分类)
    • [3. 序列式容器(sequence containers)](#3. 序列式容器(sequence containers))
  • [一、vector 的介绍](#一、vector 的介绍)
  • [二、vector 的使用(常用接口)](#二、vector 的使用(常用接口))
    • [1. 常见构造](#1. 常见构造)
    • [2. iterator 的使用](#2. iterator 的使用)
    • [3. 容量操作](#3. 容量操作)
    • [4. 增删查改](#4. 增删查改)
  • [三、vector 迭代器失效问题(重点)](#三、vector 迭代器失效问题(重点))
  • [四、vector 的深度剖析](#四、vector 的深度剖析)
    • [1. 内置类型构造函数](#1. 内置类型构造函数)
    • [2. 使用 memcpy 拷贝问题](#2. 使用 memcpy 拷贝问题)
    • [3. 动态二维数组理解](#3. 动态二维数组理解)
  • [五、vector 的模拟实现](#五、vector 的模拟实现)
  • 总结

前言 ------ 序列式容器(sequence containers)

1. 容器的概观

一句话总结:容器,置物之所也。(存放数据 ------ 数据结构)

研究数据的特定排序方式,以利于查找排序 或其他特殊目的,这一专门学科我们称为数据结构 ( D a t a S t r u c t u r e s Data\ Structures Data Structures)。几乎可以说,任何特定的数据结构都是为了实现某种特定的算法。

S T L STL STL 容器即是将运用最广的一些数据结构实现出来:

a r r a y array array(数组 )、 l i s t list list(链表 )、 t r e e tree tree( )、 s t a c k stack stack(堆栈 )、 q u e u e queue queue(队列 )、 h a s h t a b l e hashtable hashtable(哈希表 )、 s e t set set(集合 )、 m a p map map(映射 ) ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ······ ⋅⋅⋅⋅⋅⋅ 等等。

2. 容器的分类

根据 " " " 数据在容器中的排列 " " " 特性,将这些数据结构分为序列式 ( s e q u e n c e sequence sequence)和关联式 ( a s s o c i a t i v e associative associative)两种:

序列式容器( S e q u e n c e C o n t a i n e r s Sequence\ Containers Sequence Containers) 关联式容器( A s s o c i a t i v e C o n t a i n e r s Associative\ Containers Associative Containers)
a r r a y array array【C C C++ 11 11 11 R B − t r e e RB-tree RB−tree【非公开
v e c t o r vector vector s e t set set
h e a p heap heap【以算法形式呈现 ( x x x _ h e a p ) (xxx\_heap) (xxx_heap) m a p map map
p e i o r i t y _ q u e u e peiority\_queue peiority_queue m u l t i s e t multiset multiset
l i s t list list m u l t i m a p multimap multimap
f o r w a r d _ l i s t forward\_list forward_list【C C C++ 11 11 11 h a s h t a b l e hashtable hashtable【C C C++ 11 11 11
d e q u e deque deque u n o r d e r e d _ s e t unordered\_set unordered_set【C C C++ 11 11 11
s t a c k stack stack【配接器 u n o r d e r e d _ m a p unordered\_map unordered_map【C C C++ 11 11 11
q u e u e queue queue【配接器 u n o r d e r e d _ m u l t i s e t unordered\_multiset unordered_multiset【C C C++ 11 11 11
⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ······ ⋅⋅⋅⋅⋅⋅ u n o r d e r e d _ m u l t i m a p unordered\_multimap unordered_multimap【C C C++ 11 11 11

各种容器之间存在内含( c o n t a i n m e n t containment containment)关系。例如:

  1. h e a p heap heap 内含一个 v e c t o r vector vector

  2. p r i o r i t y _ q u e u e priority\_queue priority_queue 内含一个 h e a p heap heap

  3. s t a c k stack stack 和 q u e u e queue queue 都含一个 d e q u e deque deque

  4. s e t / m a p / m u l t i s e t / m u l t i m a p set/map/multiset/multimap set/map/multiset/multimap 都内含一个 R B − t r e e RB-tree RB−tree

  5. u n o r d e r e d _ x unordered\_x unordered_x 都内含一个 h a s h t a b l e hashtable hashtable

3. 序列式容器(sequence containers)

所谓序列式容器,其中的元素都可序( o r d e r e d ordered ordered),但未必有序( s o r t e d sorted sorted)。

C C C++ S T L STL STL 中主要提供了以下序列式容器

  1. a r r a y array array(定长数组

  2. v e c t o r vector vector(变长数组

  3. l i s t list list(双向链表

  4. f o r w a r d _ l i s t forward\_list forward_list(单向链表

  5. d e q u e deque deque(双端队列

  6. s t a c k stack stack(

  7. q u e u e queue queue(队列

  8. p r i o r i t y _ q u e u e priority\_queue priority_queue(优先队列

其中, s t a c k stack stack 和 q u e u e queue queue 由于只是将 d e q u e deque deque 改头换面而成,技术上被归类为一种配接器 ( a d a p t e r adapter adapter),即其改变了容器的接口 ,被称为容器配接器 ( c o n t a i n e r a d a p t e r container\ adapter container adapter)。

a r r a y array array 和 f o r w a r d _ l i s t forward\_list forward_list 都是 C C C++ 11 11 11 才引入的新容器


一、vector 的介绍

v e c t o r vector vector 其实就是顺序表在实际应用中非常的重要且非常的好用 。因此我们要熟悉其常用的接口,会使用,再到了解其底层,知其然且知其所以然。一些不常用的或者忘记的内容可以直接去查 C C C++ 官方文档v e c t o r vector vector 的文档介绍


二、vector 的使用(常用接口)

下面只列举了 v e c t o r vector vector 最常用的几个接口,更多详细信息可以自行查官方文档:v e c t o r vector vector

可以看出 v e c t o r vector vector 真正采用模板:
t e m p l a t e < c l a s s T , c l a s s A l l o c = a l l o c a t o r < T > > c l a s s v e c t o r ; template<class\ T,\ class\ Alloc = allocator<T>>class\ vector; template<class T, class Alloc=allocator<T>>class vector;

1. 常见构造

构造 ( c o n s t r u c t o r ) (constructor) (constructor) 函数声明 接口说明
v e c t o r ( ) vector() vector()(重点 无参构造
v e c t o r ( s i z e _ t y p e n , c o n s t v a l u e _ t y p e & v a l = v a l u e _ t y p e ( ) ) vector(size\_type\ n,const\ value\_type\&\ val = value\_type()) vector(size_type n,const value_type& val=value_type()) 构造并初始化 n n n 个 v a l val val
v e c t o r ( c o n s t v e c t o r & x ) vector(const\ vector\&\ x) vector(const vector& x)(重点 拷贝构造
v e c t o r ( I n p u t I t e r a t o r f i r s t , I n p u t I t e r a t o r l a s t ) vector(InputIterator\ first, InputIterator\ last) vector(InputIterator first,InputIterator last) 使用迭代器进行初始化构造
cpp 复制代码
void test1()
{
	//1.无参构造
	vector<int> v1;
	
	//2.带参构造
	vector<int> v2(10);
	
	//3.构造+初始化(10个1)
	vector<int> v3(10, 1);
	
	//4.迭代器构造
	vector<int> v4(v2.begin() + 2, v2.end() - 3);
	
	//5.拷贝构造
	vector<int> v5(v3);

	cout << "v1:";
	for (auto i : v1) cout << i;
	cout << endl << "v2:";
	
	for (auto i : v2) cout << i;
	cout << endl << "v3:";
	
	for (auto i : v3) cout << i;
	cout << endl << "v4:";
	
	for (auto i : v4) cout << i;
	cout << endl << "v5:";
	
	for (auto i : v5) cout << i;
	cout << endl;
}

2. iterator 的使用

i t e r a t o r iterator iterator 的使用 接口说明
b e g i n ( ) begin() begin() + + + e n d ( ) end() end()重点 获取第一个数据位置的iterator/const_iterator;获取最后一个数据的下一个位置的 iterator/const_iterator
r b e g i n ( ) rbegin() rbegin() + + + r e n d ( ) rend() rend() 获取最后一个数据位置的reverse_iterator/const_reverse_iterator;获取第一个数据前一个位置的reverse_iterator/const_reverse_iterator
cpp 复制代码
void test2()
{
	vector<int> v;
	for (int i = 0; i < 10; i++) v.push_back(i);
	
	//正向迭代器
	vector<int>::iterator it = v.begin();
	cout << "iterator:        ";
	while (it != v.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;

	vector<int>::const_iterator cit = v.begin();
	cout << "const_iterator:  ";
	while (cit != v.end())
	{
		cout << *cit << " ";
		cit++;
	}
	cout << endl;

	//反向迭代器
	vector<int>::reverse_iterator rit = v.rbegin();
	cout << "reverse_iterator:";
	while (rit != v.rend())
	{
		cout << *rit << " ";
		rit++;
	}
	cout << endl;
}

3. 容量操作

容量空间 接口说明
s i z e size size 获取数据个数
c a p a c i t y capacity capacity 获取容量大小
e m p t y empty empty 判断是否为空
c l e a r clear clear 清空 v e c t o r vector vector 的有效数据( c a p a c i t y capacity capacity 不变, s i z e size size 置为 0 0 0)
r e s i z e resize resize重点 改变 v e c t o r vector vector 的 s i z e size size
r e s e r v e reserve reserve重点 改变 v e c t o r vector vector 的 c a p a c i t y capacity capacity
cpp 复制代码
void print(const vector<int>& v)
{
	cout << "size:" << v.size() << endl;
	cout << "v:";
	for (int i = 0; i < v.size(); i++) cout << v[i] << " ";
	v.empty() == true ? cout << endl << "empty true" : cout << endl << "empty false";
	cout << endl << endl;
}

void test3()
{
	vector<int> v;
	for (int i = 0; i < 10; i++) v.push_back(i);
	print(v);

	v.resize(5);	
	print(v);

	v.resize(8, 10);
	print(v);
	
	v.resize(12);	
	print(v);

	v.resize(0);	
	print(v);
}
cpp 复制代码
void test4()
{
	vector<int> v;
	int size = v.capacity();
	cout << "making v grow:" << endl;
	for (int i = 0; i < 100; i++)
	{
		v.push_back(i);
		if (size != v.capacity())
		{
			size = v.capacity();
			cout << "capacity changed:" << size << endl;
		}
	}
}

v s vs vs 下使用的 S T L STL STL 基本是按照 1.5 1.5 1.5 倍方式扩容:

直接使用 r e s e r v e reserve reserve 预留空间来避免 / / / 减少扩容:

cpp 复制代码
void test5()
{
	vector<int> v;
	//使用reserve预留空间,减少扩容
	v.reserve(100);
	int size = v.capacity();
	cout << "making vector grow:" << endl;
	for (int i = 0; i < 100; i++)
	{
		v.push_back(i);
		if (size != v.capacity())
		{
			size = v.capacity();
			cout << "capacity changed:" << size << endl;
		}
	}
}

r e s e r v e reserve reserve 既不会缩容,也不会改变大小:

cpp 复制代码
void test6()
{
	vector<int> v(10, 1);
	cout << "size:    " << v.size() << endl;
	cout << "capacity:" << v.capacity() << endl << endl;

	//会增容且不改变大小
	v.reserve(20);
	cout << "size:    " << v.size() << endl;
	cout << "capacity:" << v.capacity() << endl << endl;

	//不会缩容且不改变大小
	v.reserve(5);
	cout << "size:    " << v.size() << endl;
	cout << "capacity:" << v.capacity() << endl << endl;
}

总结:

  1. c a p a c i t y capacity capacity 的代码在 v s vs vs 和 g g g++ 下分别运行会发现: v s vs vs 下 c a p a c i t y capacity capacity 是按 1.5 1.5 1.5 倍增长的, g g g++ 是按 2 2 2 倍增长的。

注意: v s vs vs 是 P J PJ PJ 版本 S T L STL STL, g g g++ 是 S G I SGI SGI 版本 S T L STL STL(具体增长多少是根据具体的需求定义的,思维不要固化)。

  1. r e s e r v e reserve reserve 只负责开辟空间 ,如果确定知道需要用多少空间, r e s e r v e reserve reserve 可以缓解 v e c t o r vector vector 增容的代价缺陷问题。

  2. r e s i z e resize resize 在开空间 的同时还会进行初始化 ,影响 s i z e size size。

4. 增删查改

v e c t o r vector vector 增删查改 接口说明
p u s h _ b a c k push\_back push_back重点 尾插
p o p _ b a c k pop\_back pop_back重点 尾删
f i n d find find 查找(注意这个是算法模块实现,不是 v e c t o r vector vector 的成员接口
i n s e r t insert insert 在 p o s pos pos 位置之前,插入 v a l val val
e r a s e erase erase 删除 p o s pos pos 位置的数据
s w a p swap swap 交换两个 v e c t o r vector vector 的数据空间
o p e r a t o r [ ] operator[\ ] operator[ ]重点 像数组一样访问

使用 p u s h _ b a c k ( ) push\_back() push_back() 和 p o p _ b a c k ( ) pop\_back() pop_back() 插入删除:

cpp 复制代码
void test7()
{
	vector<int> v;

	//尾插
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);

	for (int i = 0; i < v.size(); i++) cout << v[i] << " ";
	cout << endl;

	//尾删
	v.pop_back();
	v.pop_back();

	for (int i = 0; i < v.size(); i++) cout << v[i] << " ";
	cout << endl;
}

使用 i n s e r t ( ) insert() insert() 和 e r a s e ( ) erase() erase() 在指定位置插入删除(只能使用迭代器):

cpp 复制代码
void test8()
{
	//C++11新语法:使用列表方式初始化
	vector<int> v{ 1,2,3,4,5 };
	for (auto i : v) cout << i << " "; cout << endl << endl;

	// 在指定位置前插入值为val的元素,比如:3之前插入30,如果没有则不插入
	// 注意:vector没有提供find方法,如果要查找只能使用STL提供的全局find
	
	// 1.先使用find查找3所在位置
	vector<int>::iterator pos = find(v.begin(), v.end(), 3);
	
	// 如果没有找到会返回 v.end()
	if (pos != v.end())
	{
		// 2.在pos位置之前插入10
		v.insert(pos, 10);					
	}
	for (auto i : v) cout << i << " "; cout << endl << endl;
	
	// 在0位置(头删)删除元素
	v.erase(v.begin());
	for (auto i : v) cout << i << " "; cout << endl << endl;

	//删除所有元素
	v.erase(v.begin(), v.end());
	v.empty() == true ? cout << "empty true" << endl : cout << "empty false" << endl;
}

注意: C C C++ 11 11 11 之后支持列表初始化:vector<int> v{ 1,2,3,4,5 };,非常方便。


三、vector 迭代器失效问题(重点)

迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装 ,比如:v e c t o r vector vector 的迭代器 i t e r a t o r iterator iterator 就是原生态指针 T ∗ T^* T∗typedef T value_type; typedef value_type* iterator;)。因此迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间 ,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃)。

对于 v e c t o r vector vector 可能会导致其迭代器失效的操作有:

  1. 会引起其底层空间改变的操作 ,都有可能是迭代器失效,比如:resizereserveinsertassignpush_back 等。
cpp 复制代码
vector<int> v{ 1,2,3,4,5,6 };
for (auto i : v) cout << i << " ";
cout << endl;

int x; cin >> x;
auto p = find(v.begin(), v.end(), x);
if (p != v.end())
{
	//insert以后pos就失效了,不要直接访问
	//v.insert(p, 10);
	//*p *= 10;
	//要访问就要更新这个失效迭代器的值
	p = v.insert(p, 10);	//insert要返回新pos
	*(p+1) *= 11;
}
for (auto i : v) cout << i << " ";
cout << endl;

因此,如 i n s e r t insert insert 操作在标准库中的实现是返回一个迭代器指向插入位置的元素 ,就是为了避免迭代器失效 的问题,所以以后如果要访问 p o s pos pos 位置元素,要更新一下再访问pos = insert(pos,val);


  1. 指定位置元素的删除操作 ------ ------ ------ erase
cpp 复制代码
vector<int> v{ 1,2,3,4 };

auto pos = find(v.begin(), v.end(), 3);

// 删除pos位置的数据,导致pos迭代器失效。
v.erase(pos);

cout << *pos << endl; // 此处会导致非法访问

e r a s e erase erase 删除 p o s pos pos 位置元素后, p o s pos pos 位置之后的元素会往前搬移 ,没有导致底层空间的改变,理论上讲迭代器不应该会失效,但是:如果 p o s pos pos 刚好是最后一个元素,删完之后 p o s pos pos 刚好是 e n d end end 的位置,而 e n d end end 位置是没有元素的,那么 p o s pos pos 就失效了。因此删除 v e c t o r vector vector 中任意位置上元素时, v s vs vs 就认为该位置迭代器失效了。

以下代码的功能是删除 v e c t o r vector vector 中所有的偶数,请问那个代码是正确的,为什么?

cpp 复制代码
//要求删除所有的偶数
void test1()
{
	vector<int> v{ 1,2,3,4 };
	
	auto it = v.begin();
	while (it != v.end())
	{
		if (*it % 2 == 0)
			v.erase(it);
		++it;
	}
	
	for(auto i : v) cout << i << " "; 
	cout << endl;
}

t e s t 1 test\ 1 test 1 报错:


cpp 复制代码
//要求删除所有的偶数
void test2()
{
	vector<int> v{ 1,2,3,4 };
	
	auto it = v.begin();
	while (it != v.end())
	{
		if (*it % 2 == 0)
			it = v.erase(it);
		else
			++it;
	}
	
	for(auto i : v) cout << i << " "; 
	cout << endl;
}

t e s t 2 test\ 2 test 2 正常运行:

因此, e r a s e erase erase 在标准库中的实现是返回一个迭代器指向删除位置的下一个元 素,就是为了避免迭代器失效 的问题,所以如果以后要访问 p o s pos pos 位置元素,要更新一下再访问pos = erase(pos);


注意: L i n u x Linux Linux 下, g g g++ 编译器对迭代器失效的检测并不是非常严格,处理也没有 v s vs vs 下极端。

cpp 复制代码
// 1. 扩容之后,迭代器已经失效了,程序虽然可以运行,但是运行结果已经不对了
int main()
{
	vector<int> v{1,2,3,4,5};
	for(size_t i = 0; i < v.size(); ++i)
		cout << v[i] << " ";
	cout << endl;
	auto it = v.begin();
	cout << "扩容之前,vector的容量为: " << v.capacity() << endl;
	// 通过reserve将底层空间设置为100,目的是为了让vector的迭代器失效
	v.reserve(100);
	cout << "扩容之后,vector的容量为: " << v.capacity() << endl;
	// 经过上述reserve之后,it迭代器肯定会失效,在vs下程序就直接崩溃了,但是linux
	下不会
	// 虽然可能运行,但是输出的结果是不对的
	while(it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	return 0;
}
bash 复制代码
程序输出:
1 2 3 4 5
扩容之前,vector的容量为: 5
扩容之后,vector的容量为: 100
0 2 3 4 5 409 1 2 3 4 5
cpp 复制代码
// 2. erase删除任意位置代码后,linux下迭代器并没有失效
// 因为空间还是原来的空间,后序元素往前搬移了,it的位置还是有效的
#include <vector>
#include <algorithm>
int main()
{
	vector<int> v{1,2,3,4,5};
	vector<int>::iterator it = find(v.begin(), v.end(), 3);
	v.erase(it);
	cout << *it << endl;
	while(it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	return 0;
}
bash 复制代码
程序可以正常运行,并打印:
4
4 5
cpp 复制代码
// 3: erase删除的迭代器如果是最后一个元素,删除之后it已经超过end
// 此时迭代器是无效的,++it导致程序崩溃
int main()
{
	vector<int> v{1,2,3,4,5};
	// vector<int> v{1,2,3,4,5,6};
	auto it = v.begin();
	while(it != v.end())
	{
		if(*it % 2 == 0)
		v.erase(it);
		++it;
	}
	for(auto e : v)
		cout << e << " ";
	cout << endl;
	return 0;
}
bash 复制代码
========================================================
// 使用第一组数据时,程序可以运行
[sly@VM-0-3-centos 20220114]$ g++ testVector.cpp -std=c++11
[sly@VM-0-3-centos 20220114]$ ./a.out
1 3 5
=========================================================
// 使用第二组数据时,程序最终会崩溃
[sly@VM-0-3-centos 20220114]$ vim testVector.cpp
[sly@VM-0-3-centos 20220114]$ g++ testVector.cpp -std=c++11
[sly@VM-0-3-centos 20220114]$ ./a.out
Segmentation fault

从上述三个例子中可以看到: S G I S T L SGI\ STL SGI STL 中,迭代器失效后,代码并不一定会崩溃,但是运行结果肯定不对,如果 i t it it 不在 b e g i n begin begin 和 e n d end end 范围内,肯定会崩溃的。

与 v e c t o r vector vector 类似, s t r i n g string string 在插入 + + + 扩容操作 + + + e r a s e erase erase 之后,迭代器也会失效

cpp 复制代码
string s("hello");
auto it = s.begin();
// 放开之后代码会崩溃,因为resize到20会string会进行扩容
// 扩容之后,it指向之前旧空间已经被释放了,该迭代器就失效了
// 后序打印时,再访问it指向的空间程序就会崩溃
//s.resize(20, '!');
while (it != s.end())
{
	cout << *it;
	++it;
}
cout << endl;
it = s.begin();
while (it != s.end())
{
	it = s.erase(it);
	// 按照下面方式写,运行时程序会崩溃,因为erase(it)之后
	// it位置的迭代器就失效了
	// s.erase(it);
	++it;
}

虽然 s t r i n g string string 也会有迭代器失效的问题,但是我们一般使用 s t r i n g string string 都是通过下标访问,不经常用迭代器。

迭代器失效解决办法:在使用前,对迭代器重新赋值即可。


四、vector 的深度剖析

《 S T L STL STL源码剖析》 这本书中,根据 S G I S T L SGI\ STL SGI STL 源代码 <stl_vector.h> 分析出 v e c t o r vector vector 的实现如下:

v e c t o r vector vector 所采用的数据结构非常简单:线性连续空间

它以两个迭代器 s t a r t start start 和 f i n i s h finish finish 分别指向配置得来的连续空间中目前已被使用的范围 ,并以迭代器 e n d _ o f _ s t o r a g e end\_of\_storage end_of_storage 指向整块连续空间(含备用空间)的尾端:

cpp 复制代码
template <class T, class Alloc = alloc>
class vector
{
public:
	typedef T 			value_type;
	typedef value_type*	iterator;
...
protected:
	iterator start;				//表示目前使用空间的头
	iterator finish;			//表示目前使用空间的尾
	iterator end_of_storage;	//表示目前可用空间的尾
...
};

运用 s t a r t start start, f i n i s h finish finish, e n d _ o f _ s t o r a g e end\_of\_storage end_of_storage 三个迭代器,便可轻松提供首尾标识 begin()+end()大小 size()容量 capacity()空容器判断 empty()注标运算子 [] ··· 等机能:

cpp 复制代码
template <class T, class Alloc = alloc>
class vector
{
...
public:
	typedef T 			value_type;
	typedef value_type*	iterator;
	typedef value_type& reference;
	typedef size_t 		size_type;

	iterator begin() { return start; }
	iterator end() { return finish; }

	size_type size() const { return end() - begin(); }
	size_type capacity() const { return end_of_storage - begin(); }

	bool empty() const { return begin() == end(); }
	reference operator [] (size_type n) { return *(begin() + n); }
...
};

1. 内置类型构造函数

对于模板类型变量缺省值 的时候为了考虑类类型变量 ,因此缺省值会直接给默认构造函数(匿名对象) ,如 r e s i z e resize resize 函数:void resize (size_t n, T val = T());

此时,对于类类型可以有默认构造函数,那么 T T T 也有可能是内置类型 ,这个时候编译器会自动优化

cpp 复制代码
/*内置类型*/

int i(1);						//构造函数
int j = int();					//默认构造->匿名对象->拷贝构造
int k = int(2);					//构造函数->匿名对象->拷贝构造
cout << i << " " << j << " " << k << endl;

char ic('a');					//构造函数
char jc = char();				//默认构造->匿名对象->拷贝构造
char kc = char('b');			//构造函数->匿名对象->拷贝构造
cout << ic << " " << jc << " " << kc << endl;

double id(1.1);					//构造函数
double jd = double();			//默认构造->匿名对象->拷贝构造
double kd = double(2.2);		//构造函数->匿名对象->拷贝构造
cout << id << " " << jd << " " << kd << endl;

/*自定义类型*/

string is("hello");				//构造函数
string js = string();			//默认构造->匿名对象->拷贝构造
string ks = string("world");	//构造函数->匿名对象->拷贝构造
cout << is << " " << js << " " << ks << endl;

可见,为了适配模板类型 函数给缺省值 时要调用构造函数 初始化,对于内置类型 ,编译器自动优化(相当于给内置类型一个构造函数)。

2. 使用 memcpy 拷贝问题

当我们自己实现一个 v e c t o r vector vector 的成员函数 r e s e r v e reserve reserve 时,会用到 m e m c p y memcpy memcpy 将原来需要扩容 的空间直接拷贝重新动态开辟 的一块空间 t m p tmp tmp:

cpp 复制代码
void reserve(size_t n)
{
	if (n > capacity())
	{
		size_t old_size = size();

		T* tmp = new T[n];
		memcpy(tmp, _start, sizeof(T) * old_size);

		delete[] _start;
		_start = tmp;
		_finish = tmp + old_size;
		_end_of_storage = tmp + n;
	}
}

这时就造成了这么一个问题 ------ m e m c p y memcpy memcpy 是浅拷贝 ,如果拷贝前的空间有指针指向其他的空间 (需要深拷贝 ),如: s t r i n g string string 类型,在拷贝完后原来指针所指向的空间会直接释放掉,因此拷贝到的指针指向的空间就被释放掉了

cpp 复制代码
namespace bite	//bite代表自己实现的vector的reserve版本(使用memcpy拷贝)
{
	int main()
	{
		vector<string> v;
		v.push_back("1111");
		v.push_back("2222");
		v.push_back("3333");
		return 0;
	}
}

问题分析:

  1. m e m c p y memcpy memcpy 是内存的二进制格式拷贝 ,将一段内存空间中内容原封不动的拷贝到另外一段内存

    空间中(浅拷贝)。

  2. 如果拷贝的是内置类型的元素, m e m c p y memcpy memcpy 既高效又不会出错,但如果拷贝的是自定义类型元素,并且自定义类型元素中涉及到资源管理时 (需要深拷贝 ),就会出错,因为 m e m c p y memcpy memcpy 的拷贝实际是浅拷贝




结论:如果对象中涉及到资源管理时,千万不能使用 m e m c p y memcpy memcpy 进行对象之间的拷贝,因为 m e m c p y memcpy memcpy 是浅拷贝,否则可能会引起内存泄漏甚至程序崩溃。

3. 动态二维数组理解

vector<T>模板类型 ,因此 T T T 不仅可以是内置类型 ,也可以是自定义类型 ,如:stringvector ⋅ ⋅ ⋅ ··· ⋅⋅⋅ 等。当 T T T 这个类型为 vector<T> v 的时候,vector<vector<T>> vv 就代表了 v v vv vv 里面每一个元素存放的都是一个 vector<T> v(地址),即可以理解为二维数组

vector<vector<int>> vv(n) 构造一个 v v vv vv 动态二维数组, v v vv vv 中总共有 n n n 个元素,每个元素都是 v e c t o r vector vector 类型的,每行没有包含任何元素 ,如果 n n n 为 5 5 5 时如下所示:

v v vv vv 中元素填充完成之后,如下图所示:

二维数组的遍历( 3 3 3 种方式):

1. o p e r a t o r [ ] operator[\ ] operator[ ] 遍历:

cpp 复制代码
vector<vector<int>> vv1(5, vector<int>(5, 1));	//匿名对象赋值
for (int i = 0; i < vv1.size(); i++)
{
	for (int j = 0; j < vv1[i].size(); j++)
	{
		cout << vv1[i][j] << " ";
		//cout << vv1.operator[](i).operator[](j) << " ";
	}
	cout << endl;
}
cout << endl;

2. 迭代器遍历:

cpp 复制代码
vector<vector<int>> vv2(5, vector<int>(5, 2));	//匿名对象赋值
for (auto it = vv2.begin(); it != vv2.end(); ++it)
{
	for (auto _it = it->begin(); _it != it->end(); ++_it)
	{
		cout << *_it << " ";
	}
	cout << endl;
}
cout << endl;

3. 范围 f o r for for 遍历:

cpp 复制代码
vector<vector<int>> vv3(5, vector<int>(5, 3));	//匿名对象赋值
for (auto& v : vv3)
{
	for (auto& i : v)
	{
		cout << i << " ";
	}
	cout << endl;
}
cout << endl;

运行结果如下:


五、vector 的模拟实现

因为 v e c t o r vector vector 隶属于 C C C++ S T L STL STL 容器 ,即标准模板 库。因此,由于模板不能分开写到两个文件里 ,所以之前对于类的声明和定义分离写到两个文件中是不可取的。从今往后,只要带模板的类,都只能写到一个文件中 ,但是声明和定义可以分离:声明写到类里面(类外要重新写模板参数 ),定义写到类外面,或者把所有东西都写到类里面(类内部默认为内联 i n l i n e inline inline)。

因此,总共分为两个文件: v e c t o r . h vector.h vector.h 用来存放 v e c t o r vector vector 的定义和实现; t e s t . c p p test.cpp test.cpp 用来测试。

注意:这里两个文件中的 v e c t o r vector vector 都是自己定义的,为了和 s t d : : v e c t o r std::vector std::vector 作区分,我们要单独创建一个命名空间,这里我用的是 n a m e s p a c e y b c namespace\ ybc namespace ybc 。

  1. v e c t o r . h vector.h vector.h
cpp 复制代码
#pragma once
#include<iostream>
#include<vector>
#include<list>
#include<string>
#include<assert.h>

using namespace std;

namespace ybc
{
	template<class T>

	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;

		/*vector()
		{}*/

		// C++11 前置生成默认构造
		vector() = default;

		//类模板的成员函数,还可以继续是函数模板
		template<class InputIterator>	//任意类型的迭代器初始化
		vector(InputIterator first, InputIterator last)
		{
			while (first != last)
			{
				this->push_back(*first);
				++first;
			}
		}

		vector(int n, const T& val = T())
		{
			reserve(n);
			for (size_t i = 0; i < n; i++)
			{
				push_back(val);
			}
		}

		vector(const vector<T>& v)
		{
			reserve(v.size());
			for (auto& i : v)
			{
				push_back(i);
				//this->push_back(i);
			}
		}

		void clear()
		{
			_finish = _start;
		}

		/*//传统写法
		vector<T>& operator = (const vector<T>& v)
		{
			if (this != &v)
			{
				clear();
				reserve(v.size());
				for (auto& i : v)
				{
					push_back(i);
					//this->push_back(i);
				}
			}
			return *this;
		}*/

		void swap(vector<T>& v)
		{
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_end_of_storage, v._end_of_storage);
		}

		//现代写法
		vector<T>& operator = (vector<T> v)
		{
			swap(v);	//拷贝构造传参直接交换(编译器甚至会优化)
			return *this;
		}

		~vector()
		{
			if (_start != nullptr)
			{
				delete[] _start;
				_start = _finish = _end_of_storage = nullptr;
			}
		}

		iterator begin()
		{
			return _start;
		}

		iterator end()
		{
			return _finish;
		}

		const iterator begin() const
		{
			return _start;
		}

		const iterator end() const
		{
			return _finish;
		}

		size_t size() const
		{
			return _finish - _start;
		}

		size_t capacity() const
		{
			return _end_of_storage - _start;
		}

		bool empty() const
		{
			return _start == _finish;
		}

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t old_size = size();

				T* tmp = new T[n];
				//memcpy(tmp, _start, sizeof(T) * old_size); //浅拷贝
				for (size_t i = 0; i < old_size; i++)
				{
					tmp[i] = _start[i];	//类类型会调用赋值重载(深拷贝)
				}
				delete[] _start;

				_start = tmp;
				_finish = tmp + old_size;
				_end_of_storage = tmp + n;
			}
		}

		//主要是用来初始化
		void resize(size_t n, const T& val = T())	//调用默认构造构造一个匿名对象给缺省值(不确定T的类型)
		{											//编译器会优化:内置类型也有构造函数的概念
			if (n < size())
			{
				_finish = _start + n;
			}
			else
			{
				reserve(n);
				while (_finish < _start + n)
				{
					*_finish = val;
					++_finish;
				}
			}
		}

		void push_back(const T& x)
		{
			//扩容
			if (_finish == _end_of_storage)
			{
				reserve(capacity() == 0 ? 4 : capacity() * 2);
			}
			*_finish = x;
			++_finish;
		}

		void pop_back()
		{
			assert(!empty());
			--_finish;
		}

		iterator insert(iterator pos, const T& x)
		{
			assert(pos >= _start && pos <= _finish);
			//扩容
			if (_finish == _end_of_storage)
			{
				//迭代器失效
				size_t len = pos - _start;
				reserve(capacity() == 0 ? 4 : capacity() * 2);
				pos = _start + len;
			}
			iterator end = _finish;
			while (end > pos)
			{
				*end = *(end - 1);
				--end;
			}
			*pos = x;
			++_finish;
			return pos;
		}

		iterator erase(iterator pos)
		{
			assert(pos >= _start && pos < _finish);
			iterator it = pos;
			while (it != end() - 1)
			{
				*it = *(it + 1);
				++it;
			}
			--_finish;
			return pos;
		}

		T& operator [] (size_t i)
		{
			assert(i < size());
			return _start[i];
		}

		const T& operator [] (size_t i) const
		{
			assert(i < size());
			return _start[i];
		}

	private:
		iterator _start = nullptr;
		iterator _finish = nullptr;
		iterator _end_of_storage = nullptr;
	};
}
  1. t e s t . c p p test.cpp test.cpp
cpp 复制代码
#include"vector.h"

namespace ybc
{
	template<class T>
	void print_vector(const vector<T>& v)
	{
		//规定:没有实例化的类模板里面取东西,编译器不能区分这里的const_iterator是类型还是静态成员变量
		//typename vector<T>::const_iterator it = v.begin()

		//1.迭代器遍历
		for (auto it = v.begin(); it != v.end(); it++)
		{
			cout << *it << " ";
		}
		cout << endl;

		//2.范围for遍历
		for (const auto& i : v)
		{
			cout << i << " ";
		}
		cout << endl;
	}

	void test1()
	{
		vector<int> v;
		//下标遍历
		for (int i = 0; i < 10; ++i)
		{
			v.push_back(i);
			cout << v[i] << " ";
		}
		cout << endl;

		print_vector(v);
		cout << endl;

		vector<double> vd;
		//下标遍历
		for (int i = 0; i < 10; ++i)
		{
			vd.push_back(i * 1.1);
			cout << vd[i] << " ";
		}
		cout << endl;

		print_vector(vd);
	}

	void test2()
	{
		std::vector<int> v{ 1,2,3,4,5,6 };
		for (auto i : v) cout << i << " ";
		cout << endl;

		int x; cin >> x;
		auto p = find(v.begin(), v.end(), x);
		if (p != v.end())
		{
			//insert以后pos就失效了,不要直接访问
			//v.insert(p, 10);
			//*p *= 10;
			//要访问就要更新这个失效迭代器的值
			p = v.insert(p, 10);	//insert要返回新pos
			*(p + 1) *= 11;
		}
		for (auto i : v) cout << i << " ";
		cout << endl;
	}

	void test3()
	{
		vector<int> v;

		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);

		for (auto i : v) cout << i << " "; cout << endl;

		//要求删除所有的偶数
		for (auto it = v.begin(); it != v.end();)
		{
			if (*it % 2 == 0)
				it = v.erase(it);
			else
				++it;
		}
		for (auto i : v) cout << i << " "; cout << endl;

	}

	void test4()
	{
		/*内置类型*/

		int i(1);		//构造函数
		int j = int();	//默认构造->匿名对象->拷贝构造
		int k = int(2);	//构造函数->匿名对象->拷贝构造

		cout << "int:   " << i << " " << j << " " << k << endl;

		char ic('a');		//构造函数
		char jc = char();	//默认构造->匿名对象->拷贝构造
		char kc = char('b');//构造函数->匿名对象->拷贝构造

		cout << "char:  " << ic << " " << jc << " " << kc << endl;

		double id(1.1);			//构造函数
		double jd = double();	//默认构造->匿名对象->拷贝构造
		double kd = double(2.2);//构造函数->匿名对象->拷贝构造

		cout << "double:" << id << " " << jd << " " << kd << endl;

		/*自定义类型*/

		string is("hello");			//构造函数
		string js = string();		//默认构造->匿名对象->拷贝构造
		string ks = string("world");//构造函数->匿名对象->拷贝构造

		cout << "string:" << is << " " << js << " " << ks << endl;
	}

	void test5()
	{
		vector<int> v;

		v.resize(10, 2);
		v.reserve(20);

		cout << "size:    " << v.size() << endl;
		cout << "capacity:" << v.capacity() << endl;
		cout << endl;

		for (auto i : v) cout << i << " "; cout << endl;
	}

	template<class Containers>
	void print(const Containers& v)
	{
		for (const auto& i : v)
		{
			cout << i << " ";
		}
		cout << endl;
	}

	void test6()
	{
		//默认构造
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);
		v1.push_back(5);
		cout << "v1:"; print(v1);

		//拷贝构造
		vector<int> v2 = v1;
		cout << "v2:"; print(v1);

		//赋值重载
		vector<int> v3;
		v3 = v1;
		cout << "v3:"; print(v1);

		//迭代器构造
		vector<int> v4(v1.begin(), v1.end());
		cout << "v4:"; print(v4);

		list<int> l{ 1,2,3,4 };
		vector<int> v5(l.begin(), l.end());
		cout << "v5:"; print(v5);

		//n个val初始化
		vector<string> v6(10, "hi");
		cout << "v6:"; print(v6);

		vector<int> v7(10, 1);	//会调用参数最匹配的构造函数:这里10和1默认为int类型
		cout << "v7:"; print(v7);

		//memcpy拷贝问题
		vector<string> v8;
		v8.push_back("111111");
		v8.push_back("222222");
		v8.push_back("333333");
		v8.push_back("444444");
		v8.push_back("555555");
		cout << "v8:"; print(v8);

		v8.push_back("666666");
		v8.push_back("777777");
		cout << "v8:"; print(v8);
	}
}

int main()
{
	//ybc::test1();

	//ybc::test2();

	//ybc::test3();

	//ybc::test4();

	//ybc::test5();

	ybc::test6();

	return 0;
}

总结

v e c t o r vector vector 是 S T L STL STL 封装好的一块能够动态开辟连续空间的模板类 。随着元素的加入,它的内部机制会自行扩充空间以容纳新元素。因此,v e c t o r vector vector 的运用对于内存的合理利用与运用的灵活性有很大的帮助 ,我们再也不必因为害怕空间不足而一开始就要求使用定长数组,或者遭受自己动态开辟释放空间的麻烦,我们可以安心使用 v e c t o r vector vector,用多少开辟多少

相关推荐
快来卷java8 分钟前
深入剖析雪花算法:分布式ID生成的核心方案
java·数据库·redis·分布式·算法·缓存·dreamweaver
阿巴~阿巴~10 分钟前
C/C++蓝桥杯算法真题打卡(Day11)
算法
tpoog14 分钟前
[MySQL]数据类型
android·开发语言·数据库·mysql·算法·adb·贪心算法
刚入门的大一新生34 分钟前
排序算法3-交换排序
算法·排序算法
虾球xz1 小时前
游戏引擎学习第193天
c++·学习·游戏引擎
董董灿是个攻城狮2 小时前
Transformer 通关秘籍7:词向量的通俗理解
算法
牵牛老人2 小时前
C++设计模式-迭代器模式:从基本介绍,内部原理、应用场景、使用方法,常见问题和解决方案进行深度解析
c++·设计模式·迭代器模式
卷卷的小趴菜学编程2 小时前
算法篇-------------双指针法
c语言·开发语言·c++·vscode·算法·leetcode·双指针法
钱彬 (Qian Bin)2 小时前
QT Quick(C++)跨平台应用程序项目实战教程 5 — 界面设计
c++·qt·教程·音乐播放器·qml·qt quick
UP_Continue2 小时前
排序--归并排序
数据结构