C++ std::vector 超详细指南:基础实践(手搓vector)

目录

一.基本概念

二.类的常用接口说明

1.类对象的常见构造

2. vector类空间变化

1).size()(获取数据个数)

2).capacity()(获取容量大小)

3).empty()(判断是否为空)

4).resize()(改变vector的size) 

5).reserve()(改变vector的capacity)

6).注意

 3.vector iterator (迭代器)的使用

1).begin()+end()(获取第一个数据位置的iterator/const_iterator, 获取最后一个数据的下一个位置的iterator/const_iterator)

2).rbegin()+rend()(获取最后一个数据位置的reverse_iterator,获取第一个数据前一个位置)

 4.vector 增删查改

 1).push_back()(尾插)

 2).pop_back()(尾删) 

 3).insert()(在position之前插入val)

 4).erase()(删除position位置的数据)

 5).swap()(交换两个vector的数据空间)

 6).operator[ ]()(像数组一样访问)

三.vector 迭代器失效问题

1.导致迭代器失效的常见操作

1).插入元素(push_back/insert/emplace_back) 

2).删除元素(erase/pop_back)

3).容量调整(resize/reserve/shrink_to_fit)

2.典型错误场景 

1).循环中删除元素 

2).插入后使用旧迭代器

四.vector类的模拟实现 

1.vector.h 

2.vector.cpp(内容实现在.h里。我就不分离了,如需分离可自行实现)


一.基本概念


  • vector是STL提供的动态数组容器

  • 使用vector要包含头文件:#include <vector>

  • 特性:

    • 支持随机访问(O(1)时间复杂度)

    • 动态扩容(自动管理内存)

    • 尾部插入/删除高效(O(1)时间复杂度)

    • 连续内存空间存储元素

二.类的常用接口说明


1.类对象的常见构造

 我们可以通过监视窗口查看构造完初始化的值

#include <vector>
#include <iostream>
using namespace std;

int main() {
    // 空vector
    vector<int> v1;
    cout << "v1 size: " << v1.size() << endl;  // 0

    // 包含5个默认初始化的int(0)
    vector<int> v2(5);
    cout << "v2: ";
    for(int n : v2) cout << n << " ";  // 0 0 0 0 0 

    // 包含3个值为10的元素
    vector<int> v3(3, 10);
    cout << "\nv3: ";
    for(int n : v3) cout << n << " ";  // 10 10 10

    // 拷贝构造
    vector<int> v4(v3);
    cout << "\nv4: ";
    for(int n : v4) cout << n << " ";  // 10 10 10

    // 用数组初始化
    int arr[] = {1,3,5,7};
    vector<int> v5(arr, arr + sizeof(arr)/sizeof(int));
    cout << "\nv5: ";
    for(int n : v5) cout << n << " ";  // 1 3 5 7

    // C++11初始化列表
    vector<int> v6{2,4,6,8};
    cout << "\nv6: ";
    for(int n : v6) cout << n << " ";  // 2 4 6 8
}
构造函数声明接口说明
vector()无参构造
vector(size_type n, const value_type& val =value_type())构造并初始化n个val
vector (const vector& x)拷贝构造
vector (InputIterator first, InputIterator last);

使用迭代器进行初始化构

2. vector类空间变化

1).size()(获取数据个数)


vector<int> v(10, 1);  
cout << v.size() << endl;     //10

 2).capacity()(获取容量大小)


	vector<int> v(10, 1);
	cout << v.capacity() << endl;      //10

3).empty()(判断是否为空)


	vector<int> v;
	cout << v.empty()<<endl;      //1

4).resize()(改变vector的size) 



#include <iostream>
#include <vector>
using namespace std;
int main()
{
	vector<int> v(10, 1);

	v.resize(15, 2);
	cout << v.size() << endl;    //15
	cout << v.capacity() << endl;     //15

	v.resize(25, 3);
	cout << v.size() << endl;     //25
	cout << v.capacity() << endl;     //25

	v.resize(5);
	cout << v.size() << endl;     //5
	cout << v.capacity() << endl;     //25

}

5).reserve()(改变vector的capacity)


	vector<int> v(10, 1);
	v.reserve(20);
	cout << v.size() << endl;     //10 
	cout << v.capacity() << endl;     //20

6).注意

  • capacity的代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍增长的,g++是按2倍增长的。这个问题经常会考察,不要固化的认为
  •  reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问题
  • resize在开空间的同时还会进行初始化,影响size 
// 如果已经确定vector中要存储元素大概个数,可以提前将空间设置足够
// 就可以避免边插入边扩容导致效率低下的问题了
void TestVectorExpandOP()
{
	vector<int> v;
	size_t sz = v.capacity();
	v.reserve(100); // 提前将容量设置好,可以避免一遍插入一遍扩容
	cout << "making bar grow:\n";
	for (int i = 0; i < 100; ++i)
	{
		v.push_back(i);
		if (sz != v.capacity())
		{
			sz = v.capacity();
			cout << "capacity changed: " << sz << '\n';
		}
	}
}

 3.vector iterator (迭代器)的使用

1).begin()+end()(获取第一个数据位置的iterator/const_iterator, 获取最后一个数据的下一个位置的iterator/const_iterator)


vector<int> v(10, 1);
v.resize(15, 2);
vector<int>::iterator beg =v.begin();
vector<int>::iterator end =v.end()-1;  //end是 \0 无法访问
cout << *beg<<" "<<*end;     //1   2

 2).rbegin()+rend()(获取最后一个数据位置的reverse_iterator,获取第一个数据前一个位置)


	vector<int> v(10, 1);
	v.resize(15, 2);
	vector<int>::reverse_iterator rbeg =v.rbegin();
	vector<int>::reverse_iterator rend =v.rend()-1;
	cout << *rbeg<<" "<<*rend;     //2   1

 4.vector 增删查改

 1).push_back()(尾插)


	vector<string> v1;
	string s1("xxxx");
	v1.push_back(s1);
	v1.push_back("yyyyy");
	for (const auto& e : v1)
	{
		cout << e << " ";      //xxxx yyyyy
	}
	cout << endl;

 2).pop_back()(尾删) 


	vector<string> v1;
	string s1("xxxx");
	v1.push_back(s1);
	v1.push_back("yyyyy");
	v1.pop_back();
	for (const auto& e : v1)
	{
		cout << e << " ";      //xxxx
	}
	cout << endl;

 3).insert()(在position之前插入val)


	vector<int> v(10, 1);
	v.insert(v.begin(), 0);
	v.insert(v.begin() + 3, 10);
	for (auto e : v)
	{
		cout << e << " ";     //0 1 1 10 1 1 1 1 1 1 1 1
	}
	cout << endl;

  3).erase()(删除position位置的数据)


	vector<int> v(10, 1);
	v.insert(v.begin(), 0);
	v.insert(v.begin() + 3, 10);
	for (auto e : v)
	{
		cout << e << " ";     //0 1 1 10 1 1 1 1 1 1 1 1
	}
	cout << endl;
	v.erase(v.begin());  //参数必须是迭代器
	v.erase(v.begin(), v.begin()+2);
	for (auto e : v)
	{
		cout << e << " ";     //10 1 1 1 1 1 1 1 1
	}

   4).swap()(交换两个vector的数据空间)


vector<int> v(10, 1);
vector<int> c(10, 2);
v.swap(c);

for (auto e : v)
{
	cout << e << " ";     //2 2 2 2 2 2 2 2 2 2
}
cout << endl;
for (auto e : c)
{
	cout << e << " ";    //1 1 1 1 1 1 1 1 1 1
}

 5).operator[ ]()(像数组一样访问)


	vector<int> v(10, 1);
	for (size_t i = 0; i < v.size(); i++)
	{
		cout << v[i] << " ";    //1 1 1 1 1 1 1 1 1 1
	}

三.vector 迭代器失效问题


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

1.导致迭代器失效的常见操作

1). 插入元素(push_back/insert/emplace_back) 

  • 扩容触发:当插入元素导致容量不足时,vector会重新分配内存,原有所有迭代器、指针和引用均失效。

  • 中间插入:使用insert在中间插入元素,插入位置之后的迭代器失效(元素后移)

vector<int> v = {1, 2, 3};
auto it = v.begin() + 1; // 指向2

// 插入导致扩容
v.push_back(4); // 可能触发扩容,所有迭代器失效
// cout << *it; // 未定义行为(可能崩溃或输出垃圾值)

// 中间插入
auto new_it = v.begin() + 1;
v.insert(new_it, 5); // 插入后元素变为[1,5,2,3,4]
// 原new_it之后的迭代器(指向2的位置)失效

2). 删除元素(erase/pop_back

  • 尾部删除pop_back仅使最后一个元素的迭代器失效。

  • 中间删除erase会使被删除元素及其之后的所有迭代器失效(元素前移)

vector<int> v = {1, 2, 3, 4, 5};
auto it1 = v.begin() + 2; // 指向3
auto it2 = v.begin() + 3; // 指向4

v.erase(it1); // 删除3,元素变为[1,2,4,5]
// it1和it2均失效(原位置后的元素前移)

3).容量调整(resize/reserve/shrink_to_fit

  • 扩容resizereserve导致容量增加时,若触发内存重分配,所有迭代器失效。

  • 缩容shrink_to_fit可能缩小容量,导致迭代器失效。

vector<int> v = {1, 2, 3};
auto it = v.begin();

v.reserve(100); // 若当前capacity < 100,触发重分配
// it失效

v.resize(2);    // 不改变容量,仅修改size
// it可能仍有效(取决于是否重分配)

v.shrink_to_fit(); // 请求缩容到size=2
// 可能触发重分配,it失效

2.典型错误场景 

1).循环中删除元素 

vector<int> v = {1, 2, 3, 4, 5};

// 错误写法:erase后继续使用失效的迭代器
for (auto it = v.begin(); it != v.end(); ++it) {
    if (*it % 2 == 0) {
        v.erase(it); // erase后it失效,后续++it导致未定义行为
    }
}

// 正确写法:利用erase返回值更新迭代器
for (auto it = v.begin(); it != v.end(); ) {
    if (*it % 2 == 0) {
        it = v.erase(it); // erase返回下一个有效迭代器
    } else {
        ++it;
    }
}

2).插入后使用旧迭代器

vector<int> v = {1, 2, 3};
auto it = v.begin() + 1; // 指向2

v.push_back(4); // 可能触发扩容,it失效
// cout << *it; // 危险!

// 正确做法:插入后重新获取迭代器
it = v.begin() + 1; // 重新定位

四.vector类的模拟实现 

1.vector.h 

#pragma once
#include <iostream>
#include <vector>
using namespace std;

namespace bit
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef T* const_iterator;
		// C++11 强制生成默认构造
		vector() = default;
		vector(const vector<T>& v)
		{
			reserve(v.size());
			for (auto& e : v)
			{
				push_back(e);
			}
		}
		vector(size_t n, const T& val = T())
		{
			reserve(n);
			for (size_t i = 0; i < n; i++)
			{
				push_back(val);
			}
		}

		vector(int n, const T& val = T())
		{
			reserve(n);
			for (int i = 0; i < n; i++)
			{
				push_back(val);
			}
		}
		void clear()
		{
			_finish = _start;
		}
		// v1 = v3
		/*vector<T>& operator=(const vector<T>& v) //传统写法
		{
			if (this != &v)
			{
				clear();
				reserve(v.size());
				for (auto& e : v)
				{
					push_back(e);
				}
			}
			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)
			{
				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;
		}
		void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t old_size = size();
				T* tmp = new T[n];
				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, T val = T())
		{
			if (n < size())
			{
				_finish = _start + n;
			}
			else
			{
				reserve(n);
				while (_finish < _start + n)
				{
					*_finish = val;
					++_finish;
				}
			}
		}
		size_t size() const
		{
			return _finish - _start;
		}
		size_t capacity() const
		{
			return _end_of_storage - _start;
		}
		bool empty() const
		{
			return _start == _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);
			assert(pos <= _finish);
			// 扩容
			if (_finish == _end_of_storage)
			{
				size_t len = pos - _start;
				reserve(capacity() == 0 ? 4 : capacity() * 2);
				pos = _start + len;
			}
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}
			*pos = x;
			++_finish;
			return pos;
		}
		void erase(iterator pos)
		{
			assert(pos >= _start);
			assert(pos < _finish);

			iterator it = pos + 1;
			while (it != end())
			{
				*(it - 1) = *it;
				++it;
			}
			--_finish;
		}
		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;
	};
}

template<class Container>
void print_container(const Container& v)
{
	for (auto e : v)
	{
		std::cout << e << " ";
	}
	std::cout << std::endl;
}

 2.vector.cpp(内容实现在.h里。我就不分离了,如需分离可自行实现)

#include"vector.h"

namespace bit
{
	template<class T>
	void vector<T>::reserve(size_t n)
	{
		if (n > capacity())
		{
			size_t old_size = size();
			T* tmp = new T[n];
			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;
		}
	}
	template<class T>
	void vector<T>::resize(size_t n, T val)
	{
		if (n < size())
		{
			_finish = _start + n;
		}
		else
		{
			reserve(n);
			while (_finish < _start + n)
			{
				*_finish = val;
				++_finish;
			}
		}
	}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/982207.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

文件上传漏洞:upload-labs靶场11-20

目录 pass-11 pass-12 pass-13 pass-14 pass-15 pass-16 pass-17 pass-18 pass-19 pass-20 pass-11 分析源代码 &#xff0c;发现上传文件的存放路径可控 if(isset($_POST[submit])){$ext_arr array(jpg,png,gif);$file_ext substr($_FILES[upload_file][name],st…

AGI 之 【Dify】 之 使用 Docker 在 Windows 端本地部署 Dify 大语言模型(LLM)应用开发平台

AGI 之 【Dify】 之 使用 Docker 在 Windows 端本地部署 Dify 大语言模型&#xff08;LLM&#xff09;应用开发平台 目录 AGI 之 【Dify】 之 使用 Docker 在 Windows 端本地部署 Dify 大语言模型&#xff08;LLM&#xff09;应用开发平台 一、简单介绍 二、Docker 下载安…

Redis的持久化-RDBAOF

文章目录 一、 RDB1. 触发机制2. 流程说明3. RDB 文件的处理4. RDB 的优缺点 二、AOF1. 使用 AOF2. 命令写⼊3. 文件同步4. 重写机制5 启动时数据恢复 一、 RDB RDB 持久化是把当前进程数据生成快照保存到硬盘的过程&#xff0c;触发 RDB 持久化过程分为手动触发和自动触发。 …

常见网络协议考察知识点

说说http,https协议&#xff1b; HTTPS&#xff08;Secure Hypertext Transfer Protocol&#xff09;安全超文本传输协议&#xff1a; 它是一个安全通信通道&#xff0c;它基于HTTP开发&#xff0c;用于在客户计算机和服务器之间交换信息&#xff0c;它使用安全套接字层(SSL)…

上海市闵行区数据局调研云轴科技ZStack,共探数智化转型新路径

为进一步深化人工智能、大模型技术的应用&#xff0c;推动区域数字经济高质量发展&#xff0c;2025年2月27日&#xff0c;上海市闵行区数据局局长吴畯率队赴上海云轴科技股份有限公司&#xff08;以下简称“云轴科技ZStack”&#xff09;开展专题调研。此次调研旨在深入了解企业…

数据结构秘籍(四) 堆 (详细包含用途、分类、存储、操作等)

1 引言 什么是堆&#xff1f; 堆是一种满足以下条件的树&#xff1a;&#xff08;树这一篇可以参考我的文章数据结构秘籍&#xff08;三&#xff09;树 &#xff08;含二叉树的分类、存储和定义&#xff09;-CSDN博客&#xff09; 堆中的每一个结点值都大于等于&#xff08…

MySQL增量更新数据:高效同步策略与PanguSync实战指南

Mysql增量更新数据软件下载https://pan.baidu.com/s/1WesHaKGO7uQMhPNE-BTDmg?pwdabcd#list/path%2F 在数据驱动的商业环境中&#xff0c;实时数据同步已成为企业数字化转型的关键。本文将深入探讨MySQL增量更新的核心技术&#xff0c;并详细解析如何通过PanguSync工具实现高…

【Wireshark 02】抓包过滤方法

一、官方教程 Wireshark 官网文档 &#xff1a; Wireshark User’s Guide 二、显示过滤器 2.1、 “数据包列表”窗格的弹出过滤菜单 例如&#xff0c;源ip地址作为过滤选项&#xff0c;右击源ip->prepare as filter-> 选中 点击选中完&#xff0c;显示过滤器&#…

run方法执行过程分析

文章目录 run方法核心流程SpringApplicationRunListener监听器监听器的配置与加载SpringApplicationRunListener源码解析实现类EventPublishingRunListener 初始化ApplicationArguments初始化ConfigurableEnvironment获取或创建环境配置环境 打印BannerSpring应用上下文的创建S…

前端知识一

&#xff08;ref函数&#xff09;1.为什么vue3中使用ref来创建响应式数据&#xff0c;而不是直接声明一个变量 import { ref } from "vue";const count ref(0); // 创建一个响应式的计数器&#xff0c;初始值为0function increment() {count.value; // 增加计数器的…

STM32---FreeRTOS中断管理试验

一、实验 实验目的&#xff1a;学会使用FreeRTOS的中断管理 创建两个定时器&#xff0c;一个优先级为4&#xff0c;另一个优先级为6&#xff1b;注意&#xff1a;系统所管理的优先级范围 &#xff1a;5~15 现象&#xff1a;两个定时器每1s&#xff0c;打印一段字符串&#x…

数据结构知识学习小结

一、动态内存分配基本步骤 1、内存分配简单示例&#xff1a; 个人对于示例的理解&#xff1a; 定义一个整型的指针变量p&#xff08;着重认为它是一个“变量”我觉得可能会更好理解&#xff09;&#xff0c;这个变量用来存地址的&#xff0c;而不是“值”&#xff0c;malloc函…

swift4-汇编分析枚举内存布局

一、枚举的内存原理 1.1 常规case enum TestEnum { case test1, test2, test3 } var t TestEnum.test1 t .test2 t .test3枚举是常规的case的情况-是采取一个字节来存枚举变量通过拿到枚举的内存地址&#xff0c;看地址里面存的枚举值情况窥探枚举内存存储情况 var t Te…

Anolis服务器Arm64架构服务器配置(其他版本服务器解决方式思路一质)

Anolis服务器Arm64架构服务器配置 1.nginx配置1.1.尝试安装nginx1.2.资源准备1.2.1.查看服务器系统版本:1.2.2.下载依赖包:1.3.正式安装nginx1.3.1.下载nginx并上传服务器1.3.2.开始安装nginx1.4.防火墙配置1.4.1.直接关闭防火墙:不推荐,但省事1.4.2.命令介绍1.4.3.配置开启…

threejs:着色器onBeforeCompile给导入的模型添加光带扫描效果

模型材质属性丢失 上一篇博客我们学习了用着色器给模型添加光带扫描效果&#xff0c;今天来学习给导入的模型添加光带扫描效果&#xff0c;目标是给如下图的立筒仓加光带扫描。 首先我们试试原来的方法还是否有效。 import * as THREE from three;// 引入gltf模型加载库GLTFL…

MySQL零基础教程16—表连接进阶

复习表别名 之前已经学习过&#xff0c;查询的时候可以使用as来对检索的列进行重命名&#xff0c;这样可以让sql更加简介&#xff0c;增强易读性&#xff08;as可以省略&#xff09; 此外&#xff0c;使用表别名还可以支持在一条select语句中&#xff0c;一个表是被多次使用 …

K8s控制器Deployment详解

回顾 ReplicaSet 控制器,该控制器是用来维护集群中运行的 Pod 数量的&#xff0c;但是往往在实际操作的时候&#xff0c;我们反而不会去直接使用 RS&#xff0c;而是会使用更上层的控制器&#xff0c;比如说 Deployment。 Deployment 一个非常重要的功能就是实现了 Pod 的滚动…

rabbitmq-amqp事务消息+消费失败重试机制+prefetch限流

1. 安装和配置 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency><dependency> <groupId>com.fasterxml.jackson.core</groupId> <arti…

探秘基带算法:从原理到5G时代的通信变革【八】QAM 调制 / 解调

文章目录 2.7 QAM 调制 / 解调2.7.1 概述2.7.2 星座图星座图的结构与性能发射端的信息编码与接收端的解码差分编码的分类与实现差分编码的模4格雷加法器公式16QAM星座图与映射关系 2.7.3 信号表达式正交振幅调制的基本原理与系统分析相位误差对QAM性能的影响多电平正交振幅调制…