C++——vector容器的基本使用和模拟实现
1、vector的介绍
- vector是表示可变大小数组的序列容器。
- 就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素 进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自 动处理。
- 本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小 为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是 一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大小。
- vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。
- 因此,vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增 长。
- 与其它动态序列容器相比(deques, lists and forward_lists), vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起lists和 forward_lists统一的迭代器和引用更好。
注意:string与vector类似,但也有不同之处。string是存放字符的动态数组,vector是存放一个T类型的动态数组,本质都是一个顺序表。并且我们使用vector时需要加头文件#include<vector>
2、vector的常用接口
构造函数
void test_vector1()
{vector<int> v1;vector<int> v2(10, 8);//开十个空间都赋值8vector<int> v3(++v2.begin(), --v2.end());//用自身的迭代器区间构造vector<int> v4(v3);//拷贝构造string s("hello world");vector<char> v5(s.begin(), s.end());//用string的迭代器区间构造
}
尾插和尾删
vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.pop_back();v.pop_back();v.pop_back();v.pop_back();
由于头删和头插效率太低了,vector并没有提供相应的接口。
遍历和[]访问
vector可支持[]下标访问,并且可以改变vector中的值,至于遍历,我们可以用下标遍历,也可以使用范围for来遍历。
//遍历for (size_t i = 0; i < v.size(); i++){v[i] += 1;//可修改cout << v[i] << " ";}cout << endl;//vector<int>::iterator it = v.begin();//迭代器 it指向vector的第一个数据while (it != v.end()){*it -= 1;cout << *it << " ";it++;}cout << endl;//范围for 本质也是迭代器for (auto e : v)//把v中的每个数据遍历的时候给给e{cout << e << " ";}cout << endl;//int a[] = { 1,2,3 };原生指针就是天然的迭代器,数组支持范围for,会被替换指针//for (int* p = a; p < a + sizeof(a) / sizeof(int); p++)//{}
reserve和resize的使用
v.reserve(100); //扩容并只改变capacity空间v.resize(100); //扩容不仅改变capacity空间并且改变sizev.resize(100, 5); //扩容+初始化 或 删除数据(类比string)v.resize(2); //截取数据但不改变capacity
find和assign的使用
vector<int>::iterator ret = find(v.begin(), v.end(), 3);//auto ret = find(v.begin(), v.end(), 3);//如果没找到find返回第二个参数//如果找到了返回该数下标vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.assign(10, 5);//给v的前十个值上全部赋值为5,并且会覆盖原来的值
clear、capacity、size、max_size的使用
cout <<"max_size:"<< v.max_size() << endl;//数据所能存放的最大容量cout <<"capacity:"<< v.capacity() << endl;//当前容量cout <<"size:"<< v.size() << endl;//当前数据个数v.clear();//只清理size的值不改变capacitycout << "-------------------";cout << "max_size:" << v.max_size() << endl;cout << "capacity:" << v.capacity() << endl;cout << "size:" << v.size() << endl;
insert、erase的使用
vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);//v.assign(10, 5);//覆盖之前已有的值vector<int>::iterator ret = find(v.begin(), v.end(), 3);//auto ret = find(v.begin(), v.end(), 3);//如果没找到find返回第二个参数if (ret != v.end()){cout << "找到了" << endl;v.insert(ret, 30);//v.erase(ret);//不能在这删,因为ret失效了//这个迭代器失效问题,我们后面会细讲}v.insert(v.begin(), 30);//在v.begin()的前面插入30也就是头插vector<int>::iterator pos = find(v.begin(), v.end(), 300);if (pos != v.end())//找不到的时候就会返回v.end//因此找不到的时候不会进入{v.erase(pos);//删除pos下标的值}
增容方式
int main()
{size_t sz;std::vector<int> foo;sz = foo.capacity();std::cout << "making foo trow;\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下vector是以大约1.5增容的。而Linux下g++是以2倍增容的。
vector相关OJ题
1、只出现一次的数
力扣
class Solution {
public:int singleNumber(vector<int>& nums) {int value=0;for(auto e : nums){value^=e;//将nums中的值全部互相异或,这样的话相同的数据刚好抵消}return value;//返回剩下的那个与其他值不相等的数据}
};
2、杨辉三角形
力扣
杨辉三角形
class Solution {
public:vector<vector<int>> generate(int numRows){vector<vector<int>> vv;vv.resize(numRows);//先给外层vector开内存for (size_t i = 0; i < numRows; i++){vv[i].resize(i + 1);//给每一个小vector开内存vv[i][0] = vv[i][vv[i].size() - 1] = 1;//小vector中的头尾设为1}for (size_t i = 0; i < vv.size(); i++){for (size_t j = 0; j < vv[i].size(); j++){if (vv[i][j] == 0){vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];}}}return vv;}
};
3、电话号码的字母组合(重点题,代码中有详细注释)
//回溯力扣题:《电话号码的字母组合》
class Solution {string arr[10] = { "","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz" };//将0-9(其中0和1不给值)中的每个数字映射一个字符串
public://最关键的一个函数体,会反复使用void _letterCombinations(const string& digits, size_t i,string combinStr, vector<string>& strV)//传入digits的引用并且加const为了防止改变digits并且减少拷贝,digits就是我们传入的字符串//传入i为了遍历digits中的每个字符串,因为digits字符串的每个字符还代表这一个字符串:例如2->"abc"//combinStr是为了去存放每次递归后的字符串组合//strV为了存放所有情况遍历完全之后的字符串总和{if (i == digits.size())//由于i是从0开始的,倘若此时i已经与digits的长度相等那么说明i已经越界了也代表digits的字符串已经遍历完全了{strV.push_back(combinStr);//将我们组合好的combinStr尾插到strV中,strV也就是最终我们需要返回的那个字符串return;//立即返回,只要执行该语句,该函数将不再执行接下来的语句,因为此时就需要返回}string str = arr[digits[i] - '0'];//定义一个新的字符串str并且让str中存入我们需要遍历的字符串//digits[i]是访问到我们输入字符串中的某个字符//digits-'0'为了得到arr的下标 使其得到digits字符串中每个字符中的字符串//最后让它赋值给str我们就可以遍历str将其进行组合for (size_t j = 0; j < str.size(); j++)//遍历str进行组合,并且继续递归调用_letterCombination函数{_letterCombinations(digits, i + 1, combinStr + str[j], strV);//传入digits 为了继续访问它的字符中的字符串//传入i+1 为了访问digits中的下一个字符中的字符串 并且这样的话i不会发生改变 以便下次可以直接继续使用i+1//传入 combinStr+str[j] 为了将我们得到的str中的字符尾插到combinStr中存放起来,等遍历结束可以尾插到strV中//传入 strV 如果遍历结束可直接把combinStr中的值直接传入strV中}}vector<string> letterCombinations(string digits)//digits是我们所选择的字符串{string combinStr;//定义一个combinStr字符串vector<string> strV;//定义一个vector容器里面的值是string值if (digits.empty())//为了考虑我们的digits为空的情况下return strV;//若为空 直接返回//开始递归调用此函数_letterCombinations(digits, 0, combinStr, strV);return strV;//递归调用完成后返回}
};
图解:
3、vector的模拟实现
vector的成员变量
private:iterator _start; // 指向数据块的开始iterator _finish; // 指向有效数据的尾iterator _endofStorage; // 指向存储容量的尾
构造函数
默认构造
vector():_start(nullptr), _finish(nullptr), _endofstorage(nullptr){}
迭代器构造
//一个类模板的成员函数,又可以是一个函数模板template<class InputIterator>vector(InputIterator first, InputIterator last):_start(nullptr),_finish(nullptr),_endofstorage(nullptr){while (first != last){push_back(*first);first++;}}
拷贝构造
//v2(v1)//现代写法vector(const vector<T>& v){vector<T> tmp(v.begin(),v.end());//没有模拟swap之前可用下列写法/*swap(_start, tmp._start);swap(_finish, tmp._finish);swap(_endofstorage, tmp._endofstorage);*///this->swap(tmp);swap(tmp);}
v2(v1)传统写法//vector(const vector<T>& v)// //拷贝构造(深拷贝)//{// _start = new T[v.capacity()];// _finish = _start + v.size();// _endofstorage = _start + v.capacity();// memcpy是浅拷贝// memcpy(_start, v._start, v.size() * sizeof(T));//}
这里传统写法不是很推荐,因为会用到memcpy,因为memcpy会用到浅拷贝。
析构函数
~vector(){if (_start){delete[] _start;_start = _finish = _endofstorage = nullptr;}}
赋值运算符重载函数
//v1=v2//现代写法vector<T>& operator=(vector<T> v){//未模拟swap时用下列方法/*swap(_start, v._start);swap(_finish, v._finish);swap(_endofstorage, v._endofstorage);*/swap(v);return *this;}
迭代器
public:typedef T* iterator;typedef const T* const_iterator;
begin()和end()
const_iterator begin() const{return _start;}const_iterator end() const{return _finish;}iterator begin(){return _start;}iterator end(){return _finish;}
容量函数
size()和capacity()
size_t size() const{return _finish - _start;}size_t capacity() const{return _endofstorage - _start;}
reserve
void reserve(size_t n){if (n > capacity()){//扩容size_t sz = size();T* tmp = new T[n];if (_start){//memcpy是浅拷贝//memcpy(tmp, _start, sizeof(T) * size());for (size_t i = 0; i < sz; i++){//T 是int,一个一个拷贝没问题//T 是string,一个一个拷贝调用是T的深拷贝复制,也没问题tmp[i] = _start[i];}delete[] _start;}_start = tmp;_finish = _start + sz;/*_finish = tmp + size();_start = tmp;*/_endofstorage = _start + n;}}
resize
void resize(size_t n, const T& val = T()){if (n < size()){_finish = _start + n;}else{if (n > capacity()){reserve(n);}while (_finish != _start + n){*_finish = val;_finish++;}}}
empty
bool empty()const
{return _start == _finish;
}
增删查改
push_back
void push_back(const T& x){if (_finish == _endofstorage){扩容//size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;size_t sz = size();//T* tmp = new T[newCapacity];//if (_start)//{// memcpy(tmp, _start, sizeof(T) * size());// delete[] _start;//}_start = tmp;_finish = _start + sz;//_finish = tmp + size();//_start = tmp;//_endofstorage = _start + newCapacity;reserve(capacity() == 0 ? 4 : capacity() * 2);} *_finish = x;_finish++;}
pop_back
void pop_back(){assert(_finish > _start);//检查是否为空_finish--;}
insert
iterator insert(iterator pos, const T& x){assert(pos >= _start);assert(pos <= _finish);//满了就扩容if (_finish == _endofstorage){//扩容//扩容会导致pos失效,扩容需要更新一下possize_t len = pos - _start;//计算一下扩容前pos与_start直接的距离以便以计算新posreserve(capacity() == 0 ? 4 : capacity() * 2);pos = _start + len;//得到新pos的位置}//移动数据iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;end--;}*pos = x;_finish++;return pos;}
earse
iterator erase(iterator pos){assert(pos >= _start);assert(pos <= _finish);iterator begin = pos + 1;while (begin < _finish){*(begin - 1) = *begin;begin++;}_finish--;return pos;}
swap
void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstorage, v._endofstorage);}
下标访问
T& operator[](size_t i){assert(i < size());return _start[i];}
完整代码
#pragma oncenamespace mwb
{template<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;vector():_start(nullptr), _finish(nullptr), _endofstorage(nullptr){}// v2(v1)传统写法//vector(const vector<T>& v)// //拷贝构造(深拷贝)//{// _start = new T[v.capacity()];// _finish = _start + v.size();// _endofstorage = _start + v.capacity();// memcpy是浅拷贝// memcpy(_start, v._start, v.size() * sizeof(T));//}//一个类模板的成员函数,又可以是一个函数模板template<class InputIterator>vector(InputIterator first, InputIterator last):_start(nullptr),_finish(nullptr),_endofstorage(nullptr){while (first != last){push_back(*first);first++;}}void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstorage, v._endofstorage);}//v2(v1)//现代写法vector(const vector<T>& v){vector<T> tmp(v.begin(),v.end());/*swap(_start, tmp._start);swap(_finish, tmp._finish);swap(_endofstorage, tmp._endofstorage);*///this->swap(tmp);swap(tmp);}//v1=v2//现代写法vector<T>& operator=(vector<T> v){/*swap(_start, v._start);swap(_finish, v._finish);swap(_endofstorage, v._endofstorage);*/swap(v);return *this;}~vector(){if (_start){delete[] _start;_start = _finish = _endofstorage = nullptr;}}const_iterator begin() const{return _start;}const_iterator end() const{return _finish;}iterator begin(){return _start;}iterator end(){return _finish;}T& operator[](size_t i){assert(i < size());return _start[i];}size_t size() const{return _finish - _start;}size_t capacity() const{return _endofstorage - _start;}void reserve(size_t n){if (n > capacity()){//扩容size_t sz = size();T* tmp = new T[n];if (_start){//memcpy是浅拷贝//memcpy(tmp, _start, sizeof(T) * size());for (size_t i = 0; i < sz; i++){//T 是int,一个一个拷贝没问题//T 是string,一个一个拷贝调用是T的深拷贝复制,也没问题tmp[i] = _start[i];}delete[] _start;}_start = tmp;_finish = _start + sz;/*_finish = tmp + size();_start = tmp;*/_endofstorage = _start + n;}}void resize(size_t n, const T& val = T()){if (n < size()){_finish = _start + n;}else{if (n > capacity()){reserve(n);}while (_finish != _start + n){*_finish = val;_finish++;}}}iterator insert(iterator pos, const T& x){assert(pos >= _start);assert(pos <= _finish);//满了就扩容if (_finish == _endofstorage){//扩容//扩容会导致pos失效,扩容需要更新一下possize_t len = pos - _start;//计算一下扩容前pos与_start直接的距离以便以计算新posreserve(capacity() == 0 ? 4 : capacity() * 2);pos = _start + len;//得到新pos的位置}//移动数据iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;end--;}*pos = x;_finish++;return pos;}iterator erase(iterator pos){assert(pos >= _start);assert(pos <= _finish);iterator begin = pos + 1;while (begin < _finish){*(begin - 1) = *begin;begin++;}_finish--;return pos;}void push_back(const T& x){if (_finish == _endofstorage){扩容//size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;size_t sz = size();//T* tmp = new T[newCapacity];//if (_start)//{// memcpy(tmp, _start, sizeof(T) * size());// delete[] _start;//}_start = tmp;_finish = _start + sz;//_finish = tmp + size();//_start = tmp;//_endofstorage = _start + newCapacity;reserve(capacity() == 0 ? 4 : capacity() * 2);} *_finish = x;_finish++;}void pop_back(){assert(_finish > _start);//检查是否为空_finish--;}private:iterator _start;iterator _finish;iterator _endofstorage;};
}
C++——vector容器的基本使用和模拟实现相关推荐
- 详解vector容器(应用+模拟实现,vector相关练习题)
vector容器 动态的顺序表,数组. vector操作 vector操作及其概念 构造 vector<int>v1;vector<int>v2(10, 5);vector&l ...
- C++【vector容器模拟实现函数解析】
文章目录 vector容器&&模拟实现函数解析 一.vector介绍使用 二.vector 迭代器失效问题 三.vector容器模拟实现及函数解析 3.1vector构造函数指针初始化 ...
- C++(学习) —— Vector容器,类的静态成员的使用练习(Singer类)
举办歌手大奖赛. 设计歌手类,包括:编号.姓名.各评委打分等属性. 要求功能: 可以打印当前最高.最低分选手属性: 打印已出场人数: 可以按照平均分由高到低打印已出场选手属性: 写出main函数中模拟 ...
- C++:vector容器中使用pair该如何访问成员
(显然,vector 的索引从 0 开始,这和普通数组一样.通过使用索引,总是可以访问到 vector 容器中现有的元素.) 如果是简单的访问vector里边的成员的话,是这样的: #include ...
- vector容器中erase(删除)的使用
erase函数可以用于删除vector容器中的一个或者一段元素,在删除一个元素的时候,其参数为指向相应元素的迭代器,而在删除一段元素的时候,参数为指向一段元素的开头的迭代器以及指向结尾元素的下一个元素 ...
- vector容器 begin()与end()函数、front()与back()的用法
begin函数: 函数原型: iterator begin(); const_iterator begin(); 功能: 返回一个当前vector容器中起始元素的迭代器. end函数: 函数原型: i ...
- vector容器中数据的排序方法
前言 在项目中经常会遇到对vector容器中数据排序的情况,有时候vector中数据还常常是pair关联容器.此处给出一个使用demo,使用了C++中的lambda表达式. #include" ...
- vector 容器 动态数组总结
vector 容器 动态数组总结 二话不说直接上代码 #include <vector> #include <algorithm> #include <iostream& ...
- vector容器的用法
转自一篇博客^-^: 1 基本操作 (1)头文件#include<vector>. (2)创建vector对象,vector<int> vec; (3)尾部插入数字:vec.p ...
最新文章
- Mozilla在Firefox Nightly 92 版本测试兼容性影响
- R语言libPaths函数获取或者设置包安装的路径实战
- webstrom常用键
- Linux系统时间和时序,什么是时序竞态 Linux系统时序竞态问题分析
- 基于FFmpeg-4.0 SDK的PCM编码成AAC
- Cocos2d-x v2.2.2版本+Win7+VS2010环境搭建
- (十七)WebGIS中距离及面积测量的原理和实现以及坐标转换的简单介绍
- 微软高管解读财报:努力创新云基础架构
- 廖雪峰JS教程学习记录----Map和Set
- 2017.6.4 problem b 失败总结
- The dialect was not set. Set the property hibernate.dialect
- 巧用编辑器正则表达式,批量修改删除超链接
- No module named ‘gym‘
- [struts2]继承ActionSupport类
- Clover_v2.3k版本合集 四叶草EFI文件、Pkg安装包
- 【小程序开发模板】微信小程序开发模板平台
- Linux下制作动图的软件,GIF制作教程 | 如何制作高清动图_什么值得买
- java 定义整数数组_定义一个由整数组成的数组,要求求出其中的奇数个数和偶数个数...
- Linux程序员语录,Linux和Git之父 Linus Torvalds 的那些经典语录
- Vue3 tailwindui
热门文章
- 前端工程师和后端工程师的区别?
- 派学车融资、YY学车倒闭,互联网驾培旱涝两重天
- Deepin编译安装 mongoc++ 驱动程序
- C#与ABB机械手通信控制动作
- GD库图片裁剪指定形状解决办法(PHP GD库 海报)
- git 快速清理本地分支_Git删除本地多个分支
- 独立钻石棋(Diamond Chess)算法
- 开发大数据“读心术”,易车深挖用户价值
- 2022-2028年全球光纤功率计行业供需分析及发展前景研究报告
- 新农慕课python答案第零周答案_智慧树慕课答案农产品加工工艺学参考答案公众号...