C++ vector容器全面解析:从入门到精通

📋 文章摘要

本文全面介绍了C++标准模板库(STL)中的std::vector动态数组容器。主要内容包括:

  1. vector基本概念:动态数组特性、连续内存存储、自动扩容机制
  2. 核心接口详解:构造与赋值、迭代器与遍历、resize/reserve、元素增删改查
  3. 关键问题解析:迭代器失效原理与正确处理方法
  4. 对比分析vector<char>string的异同与应用场景
  5. 性能优化emplace_backpush_back的效率差异与使用技巧

vector作为STL中最常用的顺序容器,掌握其原理和正确用法对编写高效、安全的C++代码至关重要。


文章目录

一、vector 介绍

C++中的std::vector是标准模板库(STL)中最常用、最实用的动态数组容器。它解决了静态数组不能动态扩容的问题。vector可以自动扩容,并自动管理内存的释放。

vector的底层是一片连续的内存空间,因此它在逻辑上和物理上(内存地址)都是连续的。

cpp 复制代码
//创建一个vector
vector<int> v;

二、vector接口

与string相比,vector的接口更加精简、冗余较少,且大多数接口与string类似。这里介绍一些常用接口。

构造

cpp 复制代码
	//构造容量为10的整型vector,默认用0值初始化
	vector<int> v1(10);

	//构造容量为10的字符vector,默认用'\0'初始化
	vector<char> v2(10);

	//使用迭代器范围构造
	//构造6个元素的整型vector
	vector<int> v3(v1.begin() + 2, v1.end() - 2);

	//拷贝构造
	vector<int> v4(v3);

	//默认构造一个空的vector<string>
	vector<string> v5;

	//构造二维vector
	vector<vector<int>> v6(3, v1); //3行10列的二维数组
	//赋值示例
	v6[2][1] = 1;

赋值

cpp 复制代码
	vector<int> v1(3, 1);
	vector<int> v2 = v1;
	vector<int> v3;
	v3 = v2;

迭代器、遍历、访问

cpp 复制代码
	//begin返回首元素迭代器,end返回尾后迭代器
	//支持反向迭代器、const迭代器、const反向迭代器
	vector<int> v1(5, 9);

	//迭代器遍历
	vector<int>::iterator it = v1.begin();
	while (it != v1.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;

	//范围for循环
	for (auto i : v1)
	{
		cout << i << " ";
	}
	cout << endl;

	//支持[]和at()进行元素访问
	//for循环遍历
	for (int i = 0; i < v1.size(); i++)
	{
		cout << v1[i] << " ";
	}
	cout << endl;

	//front返回头部元素,back返回尾部元素
	for (int i = 0; i < v1.size(); i++)
	{
		v1[i] = i;
	}
	cout << v1.front() << endl;
	cout << v1.back() << endl;

resize、reserve

cpp 复制代码
	//reserve 预留容量(改变容量但不改变大小)
	vector<int> v1(10,1);

	v1.reserve(20);//执行
	cout << v1.size() << endl;
	cout << v1.capacity() << endl;

	v1.reserve(15);//不执行(当前容量已大于15)
	cout << v1.size() << endl;
	cout << v1.capacity() << endl;

	v1.reserve(5);//不执行(当前容量已大于5)
	cout << v1.size() << endl;
	cout << v1.capacity() << endl;


	//resize改变大小,新增元素可指定值初始化
	cout << v1.size() << endl;
	cout <<v1.capacity() << endl;

	v1.resize(15);//执行
	cout << v1.size() << endl;
	cout << v1.capacity() << endl;

	v1.resize(25);//执行,扩容
	cout << v1.size() << endl;
	cout << v1.capacity() << endl;

	v1.resize(5);//执行
	cout << v1.size() << endl;
	cout << v1.capacity() << endl;

元素操作

cpp 复制代码
	vector<int> v1(10);

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


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

	//删除尾部元素
	v1.pop_back();
	for (auto i : v1)
	{
		cout << v1[i] << " "; // 错误:这里应该是cout << i << " ";
	}
	cout << endl;

	//在尾部插入元素
	v1.push_back(1);
	for (auto i : v1)
	{
		cout << i << " ";
	}
	cout << endl;

	//在指定位置插入元素
	v1.insert(v1.begin(),5);
	for (auto i : v1)
	{
		cout << i << " ";
	}
	cout << endl;

	//clear清空所有元素
	v1.clear();

	// 注意:清空后size为0,需要先添加元素才能访问
	v1.resize(10);
	
	//erase删除,使用迭代器指定位置
	for (int i = 0; i < v1.size(); i++)
	{
		v1[i] = i;
	}

	for (auto i : v1)
	{
		cout << i << " ";
	}
	cout << endl;
	
	//删除头部元素
	v1.erase(v1.begin());
	for (auto i : v1)
	{
		cout << i << " ";
	}
	cout << endl;

	//删除前三个元素
	v1.erase(v1.begin(), v1.begin() + 3);
	for (auto i : v1)
	{
		cout << i << " ";
	}
	cout << endl;

	//swap交换两个vector的内容
	//...

三、迭代器失效

来看一下这两段代码。

cpp 复制代码
	//删除数据
	vector<int> v = { 1,2,3,4,5,6 };
	auto i = v.begin();
	while (i != v.end())
	{
		if (*i % 2 == 0)
		{
			v.erase(i);
		}
	}
cpp 复制代码
	//删除数据
	vector<int> v = { 1,2,3,4,5 };
	auto i = v.begin();
	++i;
	v.insert(i, 2);
	v.insert(i, 3);

运行之后都报错,为什么呢?

C++规定:当迭代器指向的元素被删除或插入操作影响后,该迭代器就失效了,我们不应再访问它。大多数编译器会进行检查。对于一些会使迭代器失效的接口(如erase、insert),它们会返回指向新位置的迭代器。再次强调:迭代器不一定是指针,它是一种抽象。

正确写法:

cpp 复制代码
	vector<int> v = { 1,2,3,4,5,6 };
	auto i = v.begin();
	while (i != v.end())
	{
		if (*i % 2 == 0)
		{
			i = v.erase(i);
		}
		else
		{
			i++;
		}
	}
cpp 复制代码
	vector<int> v = { 1,2,3,4,5 };
	auto i = v.begin();
	++i;
	i = v.insert(i, 2);
	v.insert(i, 3);

四、vector与string

vector<char>和string都是字符类型的顺序容器,且vector的接口更简洁,为什么还需要string呢?

  • 在C++历史中,string的出现早于vector,编程语言的语法需要向前兼容,不能轻易删除。
  • 在某些方面string的接口也确实更丰富。比如substr用于截取子字符串。
  • string提供c_str()和data(),能返回以'\0'结尾的C风格字符串,方便与C语言字符串接口交互;vector<char>不保证末尾有空字符,需要手动添加'\0'才能作为C字符串使用。

五、emplace_back 与 push_back

在接口中,emplace_back 和 push_back 的作用都是插入数据,用法几乎相同,但它们的底层实现是不同的且在一些特殊情况前者的效率要优于后者。


cpp 复制代码
	vector<vector<int>> vv;
	// push_back 
	
	// 构造对象尾插
	vector<int> v = { 1,2,3,4,5 };
	vv.push_back(v);
	//匿名对象尾插
	vv.push_back(vector<int>({ 1,2,3,4,5,6 }));
	// 都是一个构造 + 一个拷贝构造

	//错误示范
	//vv.push_back({1,2,3,4});//报错,但C++11 及以后支持
	//vv.push_back(1, 2, 3, 4, 5);//同上

	//emplace_back
	vv.emplace_back(1,2,3,4,5,6);
	// 只有一个构造

	//错误示范
	//vv.emplace({ 1,2,3,4,5 });//报错
相关推荐
xiaoye-duck1 小时前
Qt 入门指南:从Qt历史背景、框架认知到安装和环境搭建
开发语言·qt
Irissgwe1 小时前
c++多态
开发语言·c++·多态
lingran__1 小时前
C++_类和对象(上)
开发语言·c++
沐知全栈开发1 小时前
AngularJS 简介
开发语言
lzh200409191 小时前
手搓一个简易 Linux 进程池:巩固进程知识
linux·c++
骑士雄师1 小时前
学生管理系统python版本比对
开发语言·python
basketball6161 小时前
C++ 的 const 相关知识点总结
开发语言·c++
凯瑟琳.奥古斯特1 小时前
信号分类与特性解析
java·开发语言·职场和发展
WL_Aurora2 小时前
Python 算法基础篇之查找算法(一):顺序查找、二分查找与插值查找
开发语言·python·算法