当前位置: 首页 > news >正文

【C++】vector 的使用和底层

目录

总述

构造函数

迭代器

reserve、resize

元素访问

增删查改

查找(算法库)

模拟实现

注意事项

1.

2. 扩容引发迭代器失效

3. 深拷贝的自定义类型对象

4.


STL 的设计有通用性,string 和 vector 的使用有很多相似之处,二者用法重复的地方不在过多说明

总述


顺序表,由数组实现
Alloc 是空间配置器,内存池。以后会讲空间配置器跟这里是如何配合的

vector<char> strV;
string str;

vector 不能替代 string:
        1. string要求最后有\0,更好兼容c接口
        2. string有很多他的专用接口函数

构造函数

1. 无参构造 可以自己写内存池,但我们用它的就好
2. 构造并初始化 n 个 val
3. 用迭代器进行初始化构造
4. 拷贝构造

实例化可以给任意类型;迭代器区间可以是其他类型的迭代器

#include <string>
#include <vector>
#include <iostream>
using namespace std;int main()
{vector<int> v1(5, 1);vector<string> v2(5, "***");for (auto e : v1){cout << e << " "; // 1 1 1 1 1}cout << endl;for (auto e : v2){cout << e << " "; // *** *** *** *** ***}cout << endl;vector<int> v3(v1.begin(), v1.end());for (auto e : v3){cout << e << " "; // 1 1 1 1 1}cout << endl;string str("hello world");vector<char> v4(str.begin(), str.end());for (auto e : v4){cout << e; // hello world}cout << endl;// 迭代器底层可能是指针int a[] = { 16,2,77,29 };vector<int> v5(a, a + sizeof(a) / sizeof(a[0]));for (auto e : v5){cout << e << " "; // 16 2 77 29}cout << endl;return 0;
}

单参数的构造函数支持隐式类型转换:
        string 支持单参数的构造函数,参数是 const char
==> const char* 的字符串可以隐式类型转换成 string

所以可以 v.push_back("张三");

vector<vector<int>> vv;
vector<string> v;string name1("张三");
v.push_back(name1);
v.push_back(string("张三")); // 匿名对象
v.push_back("张三");
class A
{int  _a;
public:A(int a):_a(a){}
};A a1(1);
A a1 = 1;

迭代器

构造函数中有一些迭代器举例

都是 [  ) 的区间

vector 的3种遍历方法:↓

#include <string>
#include <vector>
#include <iostream>
using namespace std;int main()
{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;vector<int>::iterator it = v.begin();while (it != v.end()){cout << *it << " ";it++;}cout << endl;for (auto e : v){cout << e << " ";}cout << endl;return 0;
}

排序:↓

#include <vector>
#include <iostream>
#include <algorithm> // 算法头文件
using namespace std;int main()
{int a[] = { 16,2,77,29,3 };vector<int> v1(a, a + sizeof(a) / sizeof(a[0]));sort(v1.begin(), v1.end()); // 升序//sort(v1.rbegin(), v1.rend()); // 降序for (auto e : v1){cout << e << " ";}cout << endl;// 迭代器底层可能是指针sort(a, a + sizeof(a) / sizeof(a[0]));for (auto e : a){cout << e << " ";}cout << endl;return 0;
}

降序的函数写法:↓

	int a[] = { 16,2,77,29,3 };vector<int> v1(a, a + sizeof(a) / sizeof(a[0]));// 降序//greater<int> gt;//sort(v1.begin(), v1.end(), gt);sort(v1.begin(), v1.end(), greater<int>()); // 匿名对象for (auto e : v1){cout << e << " ";}cout << endl;
	// 给空格也排序了string str("hello world");sort(str.begin(), str.end()); //  dehllloorwcout << str << endl;	

reserve、resize

和 string 一样,reserve 只改变 _capacity;resize 也改变有效数据个数 _size

	vector<int> v1;//v1.reserve(10);v1.resize(10);for (size_t i = 0; i < 10; i++){v1[i] = i;// 如果用 v1.reserve(10); 应该这样写// v1.push_back(i);}for (auto e : v1){cout << e << " ";}cout << endl;

使用 reserve 会发生越界访问,程序直接挂掉。
因为 reserve 只改变了 _capacity,此时 s 的 有效数据个数_size == 0
重载的 [ ] 是按有效数据来的,会断言检查

元素访问

对越界的检查:
        operator [ ]:assert断言
        at:抛异常


data 类似于 c_str
value 是 T,返回 T* ==> 返回 T类对象 数组的指针

增删查改

当前值清了,重新赋值

int a[] = { 29,3,33,43,3,2,3,3,2 };
vector<int> v1(a, a + sizeof(a) / sizeof(int));v1.assign(5, 1);
for (auto e : v1)
{cout << e << " "; // 1 1 1 1 1
}
cout << endl;

尾插:push_back
尾删:pop_back
没有直接提供头插、头删,可以用 insert、erase

string 是以下标插入。往后都是迭代器

在某个位置插入1个、n个值;迭代区间

    int a[] = { 16,2,77,29,3,33 };vector<int> v1(a, a + sizeof(a) / sizeof(int));// 头删v1.erase(v1.begin());// 头插 v1.insert(v1.begin(), 100);// 删除第3个数据v1.erase(v1.begin() + 2);for (auto e : v1){cout << e << " "; // 100 2 29 3 33}cout << endl;
	vector<int> v1(3, 100); // 100 100 100vector<int>::iterator it = v1.begin();std::vector<int> v2(2, 400);v1.insert(it + 2, v2.begin(), v2.end()); // 100 100 400 400 100int a[] = { 501,502,503 };v1.insert(v1.begin(), a, a + 3);for (auto e : v1){cout << e << " "; // 501 502 503 100 100 400 400 100}cout << endl;

查找(算法库)

想删除3,但是不知道3在哪,用算法库里的find

迭代器失效

	int a[] = { 29,3,33,43,3,2,3,3,2 };vector<int> v1(a, a + sizeof(a) / sizeof(int));//vector<int>::iterator pos = find(v1.begin(), v1.end(), 3);auto pos = find(v1.begin(), v1.end(), 3);//if (pos != v1.end())//{//	v1.erase(pos);//}// 删除所有的3 -- 涉及迭代器失效!后面解决while (pos != v1.end()){v1.erase(pos);// pos = find(pos + 1, v1.end(), 3); // 迭代器失效pos = find(v1.begin(), v1.end(), 3); // 成功,但是效率低}for (auto e : v1){cout << e << " ";}cout << endl;

模拟实现

模版不能声明、定义分离,模版进阶时讲

vector.h

#pragma once
#include <assert.h>
#include <iostream>
#include <string>
using namespace std;namespace qtw
{template<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;iterator begin(){return _start;}iterator end(){return _finish;}const_iterator begin() const{return _start;}const_iterator end() const{return _finish;}vector()//:_start(nullptr)//,_finish(nullptr)//,_endofstorage(nullptr)// 成员变量给了缺省值,这里就不用在初始化列表初始化了{ }vector(size_t n, const T& val = T()){resize(n, val);}vector(int n, const T& val = T()) // 为什么加这个构造函数? 注意事项 4.{resize(n, val);}// [first, last)//vector(iterator first, iterator last) 不这样写// 这样写只能使用vector的迭代器初始化(不通用)// 类模板里面还可以再套模板template<class InputIterator>vector(InputIterator first, InputIterator last){while (first != last){push_back(*first);++first;}}vector(const vector<T>& v) // 传统{_start = new T[v.capacity()];//memcpy(_start, v._start, sizeof(T) * v.size()); 深拷贝的自定义类型对象会出问题for (size_t i = 0; i < v.size(); i++){_start[i] = v._start[i];}_finish = _start + v.size();_endofstorage = _start + v.capacity();}//vector(const vector<T>& v) // 复用//{//	reserve(v.capacity());//	for (auto e : v)//	{//		push_back(e);//	}//}void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstorage, v._endofstorage);}vector<T>& operator=(vector<T> v) // 现代写法{swap(v);return *this;}~vector(){if (_finish){delete[] _start;_start = _finish = _endofstorage = nullptr;}}void reserve(size_t n){if (n > capacity()){size_t sz = size(); // 保存size,下面会被迫改变T* tmp = new T[n];if (_start){//memcpy(tmp, _start, sizeof(T) * size()); 深拷贝的自定义类型对象会出问题for (size_t i = 0; i < sz; i++){tmp[i] = _start[i];}delete[] _start;}_start = tmp; // 间接改变了size_finish = _start + sz;_endofstorage = _start + n;}}// T类型的匿名对象,调用T的默认构造。所以自己写类,默认构造要自己提供// C++模板出来后,对内置类型升级:内置类型也有构造函数void resize(size_t n, const T& val = 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 == _endofstorage){size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newcapacity);}*_finish = x;_finish++;//insert(end(), x);}size_t size() const{return _finish - _start;}size_t capacity() const{return _endofstorage - _start;}iterator& operator[](size_t pos){assert(pos < size());return *(_start + pos);}const_iterator& operator[](size_t pos) const{assert(pos < size());return *(_start + pos);}iterator insert(iterator pos, const T& x){assert(pos >= _start && pos <= _finish);if (_finish == _endofstorage){size_t len = pos - _start;size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newcapacity);pos = _start + len; // 相对位置,重置迭代器位置}iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}*pos = x;_finish++;return pos;}iterator erase(iterator pos){assert(pos >= _start && pos < _finish);iterator it = pos + 1;while (it != _finish){*(it - 1) = *it;++it;}_finish--;return pos;}private:iterator _start = nullptr;iterator _finish = nullptr;iterator _endofstorage = nullptr;};
}

注意事项

1.

		void reserve(size_t n){if (n > capacity()){size_t sz = size(); // 保存size,下面会被迫改变T* tmp = new T[n];if (_start){memcpy(tmp, _start, sizeof(T) * size());delete[] _start;}_start = tmp; // 改变了size_finish = _start + sz;_endofstorage = _start + n;}}size_t size() const{return _finish - _start;}

用 _start 计算 size;_start = tmp 这一步间接改变了 size
如果不保存 size, _finish = _start + size(); _finish 拿到的是0x00000000这个进程地址空间
所以,要么加上 size_t sz = size(); 这句,要么先算 _finish 再算 _start

2. 扩容引发迭代器失效

		iterator insert(iterator pos, const T& x){assert(pos >= _start && pos <= _finish);if (_finish == _endofstorage){size_t len = pos - _start;size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newcapacity);pos = _start + len; // 相对位置,重置迭代器位置}iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}*pos = x;_finish++;return pos;}
void qtw_test1()
{qtw::vector<int> v;v.push_back(1);	v.push_back(2);v.push_back(3); v.push_back(4);v.push_back(5); v.push_back(5);v.push_back(5);for (auto e : v) { cout << e << " "; }cout << endl; // 1 2 3 4 5 5 5v.insert(v.begin(), 100);for (auto e : v) { cout << e << " "; }cout << endl; // 100 1 2 3 4 5 5 5qtw::vector<int>::iterator p = v.begin() + 3;v.insert(p, 300);for (auto e : v) { cout << e << " "; }cout << endl; // 100 1 2 300 3 4 5 5 5// 高危行为*p += 10;for (auto e : v) { cout << e << " "; }cout << endl; // 100 1 2 300 3 4 5 5 5
}

insert扩容以后迭代器可能会失效;所以insert扩容以后就不要使用这个形参迭代器了


erase以后,迭代器失效了,不能访问

vs进行强制检查,访问会直接报错

void qtw_test2()
{std::vector<int> v1;v1.push_back(1); v1.push_back(2); v1.push_back(2); v1.push_back(3);v1.push_back(4); v1.push_back(5);v1.push_back(6);for (auto e : v) { cout << e << " "; }cout << endl;auto it = v1.begin();v1.erase(it);cout << *it << endl;++it;cout << *it << endl;for (auto e : v) { cout << e << " "; }cout << endl;
}

程序直接挂掉

怎么解决?接收返回值

void qtw_test2()
{std::vector<int> v1;v1.push_back(1); v1.push_back(2); v1.push_back(2); v1.push_back(3);v1.push_back(4); v1.push_back(5);v1.push_back(6);for (auto e : v) { cout << e << " "; }cout << endl;auto it = v1.begin();while (it != v1.end()){if (*it % 2 == 0){it = v1.erase(it); // 不接收属于迭代器失效}else{++it;}}for (auto e : v) { cout << e << " "; }cout << endl;
}

总结:vector 的 erase和insert 迭代器对象后,不能再访问这个迭代器。迭代器失效了,访问结果是未定义

3. 深拷贝的自定义类型对象

vector(const vector<T>& v) // 传统
{_start = new T[v.capacity()];memcpy(_start, v._start, sizeof(T) * v.size()); //深拷贝的自定义类型对象会出问题//for (size_t i = 0; i < v.size(); i++)//{//	_start[i] = v._start[i];//}_finish = _start + v.size();_endofstorage = _start + v.capacity();
}void reserve(size_t n)
{if (n > capacity()){size_t sz = size(); // 保存size,下面会被迫改变T* tmp = new T[n];if (_start){memcpy(tmp, _start, sizeof(T) * size()); //深拷贝的自定义类型对象会出问题//for (size_t i = 0; i < sz; i++)//{//	tmp[i] = _start[i];//}delete[] _start;}_start = tmp; // 间接改变了size_finish = _start + sz;_endofstorage = _start + n;}
}
void qtw_test5()
{qtw::vector<string> v;v.push_back("111111111111111111");v.push_back("222222222222222222");v.push_back("333333333333333333");v.push_back("444444444444444444");v.push_back("555555555555555555");for (auto& e : v) // 拷贝代价大,所以引用{cout << e << " ";}cout << endl;qtw::vector<string> v1(v);for (auto& e : v1){cout << e << " ";}cout << endl;
}

push_back "5",扩容时程序挂掉

vector是深拷贝,但vector空间上存的是string对象
string对象应该是拷贝,但memcpy是浅拷贝

delete[] _start;当T是自定义类型时,一次调用vector里每个对象的析构函数,再释放整个空间


解决方案:
        T是深拷贝的类,要调用赋值重载,实现类对象的深拷贝

4.

vector(size_t n, const T& val = T())
{resize(n, val);
}// [first, last)
//vector(iterator first, iterator last) 不这样写:只能使用vector的迭代器初始化(不通用)
// 类模板里面还可以再套模板
template<class InputIterator>
vector(InputIterator first, InputIterator last)
{while (first != last){push_back(*first);++first;}
}
void qtw_test6()
{qtw::vector<int> v(10, 1); // 报错qtw::vector<int> v1(10u, 1);qtw::vector<string> v2(10, "1111");
}

v1,v2没问题,v报错

v 调用成了迭代器,迭代器更匹配。因为上面的构造函数一个是 size_t,一个是 int。v 的(10, 1)都是 int,调成了迭代器,first、last都被识别成 int,但 int 不能解引用,报错

v1让编译器把 10识别为size_t,1还是int,便因类型不同无法调用模板。和 v2 一样别无他选


库里面怎么解决的呢?

vector(size_t n, const T& val = T())
{resize(n, val);
}vector(int n, const T& val = T())
{resize(n, val);
}// [first, last)
//vector(iterator first, iterator last) 不这样写:只能使用vector的迭代器初始化(不通用)
// 类模板里面还可以再套模板
template<class InputIterator>
vector(InputIterator first, InputIterator last)
{while (first != last){push_back(*first);++first;}
}

本篇的分享就到这里了,感谢观看,如果对你有帮助,别忘了点赞+收藏+关注
小编会以自己学习过程中遇到的问题为素材,持续为您推送文章

http://www.dtcms.com/a/392638.html

相关文章:

  • 指标体系单一只关注速度会造成哪些风险
  • 智能体落地与大模型能力关系论
  • QPS、TPS、RT 之间关系
  • Day27_【深度学习(6)—神经网络NN(4)正则化】
  • NeurIPS 2025 spotlight 自动驾驶最新VLA+世界模型 FSDrive
  • Nodejs+html+mysql实现轻量web应用
  • AI模型测评平台工程化实战十二讲(第二讲:目标与指标:把“测评”这件事说清楚(需求到蓝图))
  • 20.二进制和序列化
  • 接口自动化测试实战
  • 为企业系统无缝集成AI检测能力:陌讯AIGC检测系统API接口调用全指南
  • RESTful API
  • Linux知识回顾总结----进程间通信(上)
  • Qwen3-Next深度解析:阿里开源“最强性价比“AI模型,如何用3%参数超越全参数模型?
  • AutoResetEvent:C# 线程同步工具
  • ThinkSound - 阿里通义开源的AI音频生成模型
  • Wan2.2-S2V-14B:音频驱动的电影级视频生成模型全方位详解
  • 基于C++11手撸前端Promise——从异步编程到现代C++实践
  • 构建AI智能体:三十九、中文新闻智能分类:K-Means聚类与Qwen主题生成的融合应用
  • [vibe code追踪] 程序列表视图 | renderNodeList
  • 解决 `sudo rosdepc init` 报错:`command not found` 的完整指南
  • 大数据毕业设计选题推荐-基于大数据的气候驱动的疾病传播可视化分析系统-Hadoop-Spark-数据可视化-BigData
  • Maven 实战:多模块项目与高级打包配置
  • AI 精准绘图专栏:从描述到图像,让创意精准落地​
  • 基于C++11手撸前端Promise进阶——链式调用与组合操作(All/Race)的实现
  • 美国批准通用上市标准!加密货币ETF即将爆发?
  • 子查询及其分类
  • MySQL的存储引擎(一条sql语句的执行流程是什么样的?)
  • JavaScript学习笔记(二):遍历方法汇总
  • Ubuntu22.04显卡掉驱动,重装命令
  • 模式组合应用-享元模式