十一、vector 类

Ⅰ . vector 的介绍和使用

01 vector 的介绍

vector 的文档介绍:vector

① vector 是表示可变大小数组的序列容器,既像数组,又不像数组

**像体现在:**同样采用连续存储空间存储元素,可以使用下标访问元素

**不像体现在:**大小是可以动态改变的

② 本质上来说,vector 使用动态分配数组来存储它的元素

当新元素插入时,为了增加存储空间,这个数组就需要被重新分配大小。具体做法是分配一个新的数组,然后将全部元素转移到这个新的数组。就时间而言,这是一个相对代价较高的任务,因为每当一个新的元素加入后,vector 并不会每次都重新分配大小。

③ vector 分配空间策略:vector 会分配一些额外的空间以适应可能的增长,因此存储空间会比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于末尾插入一个元素时是在常数时间的复杂度完成的。

因此,vector 占用了更多的存储空间,为了获得管理存储空间的能力,并且有一种有效的方式去动态增长。

④ 与其他动态序列容器相比:

vector 在访问元素时更加高效,末尾添加和删除元素相对高效

但是对于其他位置不在末尾的插入和删除操作,效率更低

vector 的底层是一个动态的顺序表

02 vector 的初始化

cpp 复制代码
#include <vector>
using namespace std;   

① 无参构造:

cpp 复制代码
vector<int> v1;   // 无参构造,创建一个int类型的,空的vector对象

② 构造并初始化:

cpp 复制代码
vector<int> v2(10, 1);   // 10个1

③ 迭代器区间初始化:

cpp 复制代码
vector<int> v3(v2.begin(), v2.end());

④ 拷贝构造:

cpp 复制代码
vector<int> v4(v2);

还可以不全部拷贝:

cpp 复制代码
vector<int> v3(++v2.begin(), --v2.end());

对于迭代器区间初始化,它这里的 InputInerator 不一定是 vector Inerator。

它是一个模板,所以你传的是谁的迭代器,它就可以实例化出谁的迭代器

有时候我们可以这样初始化:

cpp 复制代码
string s("hello world");
vector<char> v5(s.begin(), s.end());

03 调试观察

cpp 复制代码
#include<iostream>
#include<vector>
#include<string>
using namespace std;

void vector_test1()
{
	vector<int> v1;
	vector<int> v2(10, 1);
	vector<int> v3(v2.begin(), v2.end());
	vector<int> v4(v2);

	string s("hello world");
	vector<char> v5(s.begin(), s.end());
}

int main()
{
	vector_test1();

	return 0;
}

具体如下:

04 string 和 vector 的区别

string 和 vector 有什么区别呢?刚才通过监视观察,感觉 vector char 已经很像 string 了。

能不能让 vector char 去替代 string 呢?这合适吗?

答案是不能,因为 vector char 没有 \0 ,而 string 结尾有 \0

那我直接给 vector char 后面手动装一个 \0 行嘛?

你要硬是想这么玩,也不是不可以

但他们两个体系不一样,vector 是顺序表,存的是任意类型,是针对可动态增长的数组的。

而 string 就只是字符串,我随便举两个例子:

① vector 支持正常的增删查改,但是不支持 += (本身也没必要+=),也不支持比较大小。

② vector 也没有 c_str 这些东西,因为 string 作为字符串专用的类,能提供专有的接口(比如 +=,find),所以这就是 string 存在的意义。

05 关于 vector 的析构、拷贝构造和赋值构造

至于析构函数,一般情况下我们不需要管,因为它会自动调用。

拷贝构造和赋值构造,vector 的拷贝构造和赋值其实就是深拷贝。

这些我们放在 vector 模拟实现的章节里详细探讨。

Ⅱ . vector 的遍历

01 push_back()

vector 不能使用 operator+=

string 能用 += 主要是 string 不仅可以尾插一个字符还可以追加一个字符串。

但是 vector 就只支持一个一个数据的插入和删除,push_back 和 pop_back

我们发现,vector 和 string 一样,是没有提供 push_front 和 pop_front 的,因为挪动数据效率低。

如果你硬是要去头插头删,可以用 insert 和 erase 去操作

举个例子:

cpp 复制代码
void vector_Traversal_test() {
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
}

02 下标 + 方括号遍历

vector 是连续的空间,又支持 operator[] 和 size() ,所以可以用下标+方括号遍历。

看代码:

cpp 复制代码
void vector_Traversal_test() 
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);

	// 遍历
	for (size_t i = 0; i < v.size(); i++)
	{
		cout << v[i] << " ";
	}
	cout << endl;
}

运行结果如下:

当然也可以修改:

cpp 复制代码
void vector_Traversal_test() 
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);

	// 遍历
	for (size_t i = 0; i < v.size(); i++)
	{
		v[i] += 1;
		cout << v[i] << " ";
	}
	cout << endl;
}

运行结果如下:

03 访问 vector 的 at()

at() 和 operator[] 一样,也是用来访问 vector 的,但是 at() 会进行边界检查。

代码演示:

cpp 复制代码
void vector_test2()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);

	cout << v.at(0) << endl;
	cout << v.at(3) << endl;
}

运行结果如下:

注意:operator[] 会断言检查越界,at() 会抛异常

cpp 复制代码
void vector_test2()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);

	cout << v.at(0) << endl;
	cout << v.at(3) << endl;
	cout << v.at(4) << endl;
}

04 使用迭代器遍历

代码演示:

cpp 复制代码
void vector_Traversal_test() 
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);

	vector<int>::iterator it = v.begin();
	while (it != v.end())
	{
		*it -= 1;
		cout << *it << " ";
		it++;
	}
	cout << endl;
}

运行结果如下:

05 范围 for

vector 支持迭代器,也就支持范围 for,这个我们在模拟实现 string 的时候已经验证过了。

范围 for 的本质就是编译器在编译时自动替换成迭代器,这里也一样。

代码演示:

cpp 复制代码
void vector_Traversal_test() 
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);

	// 范围for
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;

	for (auto& e : v)
	{
		e += 10;
		cout << e << " ";
	}
	cout << endl;
}

运行结果如下:

Ⅲ . vector 空间

01 获取数据个数的 size()

cpp 复制代码
void vector_test3() 
{
	vector<int> v(6, 6);        // 生成6个6
	cout << v.size() << endl;
}

运行结果如下:

02 获取 vector 最大存储的 max_size()

cpp 复制代码
void vector_test4()
{
	vector<int> v(6, 6);        
	cout << v.max_size() << endl;
}

运行结果如下:

03 改变 vector 容量的 reserve()

cpp 复制代码
void vector_test5()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);

	v.reserve(100);
}

调试结果如下:仅扩容

reserve 会扩容,但是不会影响数据个数。[capacity] 4 → [capacity]100

04 改变 vector 大小的 resize()

cpp 复制代码
void vector_test6()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);

	v.resize(100);
}

调试结果如下:扩容 + 初始化

string 的 resize 如果不指定 "填充值" ,默认给的是 \0

而 vector 的 resize 如果不指定,默认给的是其对应类型的缺省值作为 "填充值"

这里是 int 就是 0,如果是指针,对应的缺省值就是空指针。

提供特定的值:

cpp 复制代码
void vector_test6()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);

	v.resize(100, 1);
}

调试结果如下:

**注意:**如果开的数据比之前更小,还会删除数据!

cpp 复制代码
void vector_test6()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);

	v.resize(2);
}

调试结果如下:

这里虽然大小变成 2,数据也只有 [0]1 和 [1]2 了,但是容量仍然为 4

05 vector 空间增长问题

① capacity 的代码在 VS 和 g++下分别运行会发现:VS下 capacity 是按 1.5 倍增长的,而 g++ 下 capacity 是按 2 倍增长的。 这个问题经常会考察,不要固化的认为,顺序表增容都是2倍,具体增长多少是根据具体的需求定义的。VS 是 PJ 版本 STL,g++ 是 SGI 版本 STL。

② reserve 只负责开辟空间,如果确定知道需要用多少空间,reserve 可以缓解 vector 增容的代价缺陷问题。

③ resize 在开空间的同时还会进行初始化,影响size

测试:

cpp 复制代码
// vector::capacity
#include <iostream>
#include <vector>
 
int main()
{
	size_t sz;
	std::vector<int> foo;
	sz = foo.capacity();
	std::cout << "making foo grow:\n";
	for (int i = 0; i < 100; ++i) {
		foo.push_back(i);
		if (sz != foo.capacity()) {
			sz = foo.capacity();
			std::cout << "capacity changed: " << sz << '\n';
		}
	}
}

VS运行结果如下:

making foo grow :

capacity changed : 1

capacity changed : 2

capacity changed : 3

capacity changed : 4

capacity changed : 6

capacity changed : 9

capacity changed : 13

capacity changed : 19

capacity changed : 28

capacity changed : 42

capacity changed : 63

capacity changed : 94

capacity changed : 141
g++ 运行结果如下:

making foo grow :

capacity changed : 1

capacity changed : 2

capacity changed : 4

capacity changed : 8

capacity changed : 16

capacity changed : 32

capacity changed : 64

capacity changed : 128

Ⅳ . vector 增删查改

01 pop_back() 尾删

代码实现:

cpp 复制代码
void vector_test7() 
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	for (auto e : v) cout << e << " "; cout << endl;

	v.pop_back();   // 尾删:3
	for (auto e : v) cout << e << " "; cout << endl;

	v.pop_back();   // 尾删:2
	for (auto e : v) cout << e << " "; cout << endl;
}

运行结果如下:

02 assign() 赋值

value 覆盖:

cpp 复制代码
void vector_test8() 
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);

	v.assign(10, 5);   // 原来是1到4,现在改成10个5
}

调试结果如下:

03 为什么 vector 不提供 find 接口

string、map、set 都有 find() 用,凭什么 vector 和 list 没有

其实,我们应该考虑的是 ------ 为什么 string、map、set 能有 find 操作。

而 vector 之所以不提供 find ,是因为如果去查找元素效率就会是 ...

凭什么不让我 vector 用 find() ,我偏要用!

可以的,"algorithm库" 里有通用的 find 操作,我们来一睹其芳容 ------

cpp 复制代码
#include <algorithm>

该 find 内部是从 beginend 进行一次遍历,其复杂度是

值得一提的是,在C++中,凡是使用迭代器区间,都是左闭右开的

再去思考 map、set 为什么有 find() 通过迭代器从头到尾遍历 map 与 set 时,

得到的结果是按 key 排序的结果,而不是插入时的顺序,所以这两个容器没有 push_back 操作。

其实,插入到 map 与 set 中的元素会被组织到一颗红黑树上,红黑树是一颗平衡二叉树,

平衡二叉树是一颗二叉排序树,对一颗二叉排序树的查找有点像二分查找,复杂度是 ,

由于 map 与 set 的数据结构能有更快的查找方法,所以在容器内提供了 find 方法

04 通用查找 find()

如果非要在 vector 里用 find() ,我们可以用通用 find。

**找到了:**返回一个迭代器区间那个值的位置;

没找到: 返回的是 ,即右边开区间的位置。

代码演示:

cpp 复制代码
void vector_test9() 
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);

	vector<int>::iterator ret = find(v.begin(), v.end(), 3);
	// auto ret = find(v.begin(), v.end(), 3);

	if (ret != v.end()) 
    {
		cout << "找到了" << endl;
	}
}

05 insert() 插入

比如我们刚才用通用 find 找到了 3 的位置,

我们想在这个位置前面插入一个数据,就可以使用 insert() 插入。

在 3 前面插入一个 30:

cpp 复制代码
void vector_test9() 
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);

	vector<int>::iterator ret = find(v.begin(), v.end(), 3);
	// auto ret = find(v.begin(), v.end(), 3);

	if (ret != v.end()) 
	{
		cout << "找到了" << endl;
		v.insert(ret, 30);
	}
	for (auto e : v) cout << e << " "; cout << endl;
}

运行结果如下:

虽然没有 vector 没有提供 push_front,但是我们也可以用 insert 去头插

代码演示:

cpp 复制代码
void vector_test10() 
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	for (auto e : v) cout << e << " "; cout << endl;

	v.insert(v.begin(), -1);   // 在起始位置插入一个-1

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

运行结果如下:

06 erase() 删除

我们我们想删除数据,我们就可以用 erase 去删除

代码演示:

cpp 复制代码
void vector_test11() 
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	for (auto e : v) cout << e << " "; cout << endl;

	vector<int>::iterator pos = find(v.begin(), v.end(), 3);
	if (pos != v.end()) 
	{   // 判断pos是否存在
		v.erase(pos);       // 删除pos
	}

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

运行结果如下:

如果没有判断会怎么样?

cpp 复制代码
void vector_test11() 
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	for (auto e : v) cout << e << " "; cout << endl;

	vector<int>::iterator pos = find(v.begin(), v.end(), 3);
	v.erase(pos);       


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

运行结果如下:

如果要删除的目标存在,不会怎么样。

怕的就是要删的目标不存在!比如我要删个 5,但是 vector 里只有 1,2,3,4 。

cpp 复制代码
vector<int>::iterator pos = find(v.begin(), v.end(), 5);
v.erase(pos);

如果有了判断,就不会翻车了,如果待删目标不存在,就不会去走 erase()

因为 pos 如果找不到就会等于 end() 上的值,我们利用这一点进行 if 判断

cpp 复制代码
	vector<int>::iterator pos = find(v.begin(), v.end(), 5);
	if (pos != v.end()) 
	{   // 检查!
		v.erase(pos);
	}

07 clear() 清空数据

cpp 复制代码
void vector_test12() 
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);

	printf("清空前:");
	for (auto e : v) cout << e << " "; cout << endl;

	v.clear();
	printf("清空后:");
	for (auto e : v) cout << e << " "; cout << endl;
}

运行结果如下:

clear只是把数据清了,但容量还在

相关推荐
WG_173 分钟前
C++多态
开发语言·c++·面试
Charles Ray2 小时前
C++学习笔记 —— 内存分配 new
c++·笔记·学习
重生之我在20年代敲代码2 小时前
strncpy函数的使用和模拟实现
c语言·开发语言·c++·经验分享·笔记
迷迭所归处7 小时前
C++ —— 关于vector
开发语言·c++·算法
CV工程师小林8 小时前
【算法】BFS 系列之边权为 1 的最短路问题
数据结构·c++·算法·leetcode·宽度优先
white__ice9 小时前
2024.9.19
c++
天玑y9 小时前
算法设计与分析(背包问题
c++·经验分享·笔记·学习·算法·leetcode·蓝桥杯
姜太公钓鲸2339 小时前
c++ static(详解)
开发语言·c++
菜菜想进步9 小时前
内存管理(C++版)
c语言·开发语言·c++
Joker100859 小时前
C++初阶学习——探索STL奥秘——模拟实现list类
c++