目录
大佬文章推荐
Vector类
基本概念:提供了一个动态的、可以自动调整大小的数组,可以存储任意类型的元素
类原型:template <class T,class Alloc = allocator<T> >class vector
- class Alloc:表示
vector
用于分配和管理内存的分配器类型,默认值是allocator<T>
allocator<T>:
是标准C++库中用于内存管理的默认分配器,可以理解为内存池
接口介绍时只会介绍常见的接口,一些比较少见的接口需要去官网查看
嵌套vector:vector是一个数组模板,除了有对象数组,还可以有链表数组、树数组、以及嵌套vector数组
树数组:vector<tree> tc
链表数组:vector<list> lc
嵌套vector数组:vector<vector<int>> cc
- 外部vector的T是vector<int>*,内部vector的T是int*
- vector<int>*可以指向多个vector<int>,int*可以指向多个int
- 外部vector实例化出了对象数组,内部vector实例化出了整型数组
- 二维数组a[ i ][ j ]的本质是:*(*(a + i)+ j)
- 嵌套vector数组cc[ i ][ j ]的本质是:cc.operator[ ](i).operator[ ](j)
- 相比于普通的二维数组,嵌套vector数组可以更合理的有选择性的开辟空间,普通得二维数组开辟时就需要指定内存空间,而嵌套vector数组可以在后续通过指针进行补充
扩容与缩容
相关接口:resize、reserve、shrink_to_fit
resize:改变 vector
中实际元素的数量size,若count > size则补齐相差的默认字符(如果vector中存放的是内置类型,则补齐的值是0,如果是自定义类型那么会调用该自定义类型的默认构造,构造几个对象进行存放),若count < size则删除vector中超出的字符,若count == size则什么也不做
void resize(size_type count);
void resize(size_type count, const value_type& value);
- const value_type& value:用于指定默认值
#include <iostream>
#include <vector>
#include <string>
int main()
{
std::vector<int> vea = { 1, 2, 3 };
// 将 vector 的大小调整为 5,新增两个元素
vea.resize(5);
// 输出结果
for (int n : vea)
{
std::cout << n << " "; // 输出: 1 2 3 ? ?
}
std::cout << std::endl << std::endl;
std::vector<std::string> veb = { "hello", "world" };
// 将 vector 的大小调整为 4,新增两个元素
veb.resize(4);
// 输出结果
for (const auto& str : veb)
{
std::cout << "\"" << str << "\" "; // 输出: "hello" "world" "" ""
}
std::cout << std::endl << std::endl;
std::vector<int> vec = { 1, 2, 3 };
// 将 vector 的大小调整为 5,新增的元素初始化为 100
vec.resize(5, 100);
// 输出结果
for (int n : vec)
{
std::cout << n << " "; // 输出: 1 2 3 100 100
}
std::cout << std::endl;
return 0;
}
reserve:当你预计 vector
会增长到一定数量时,可以使用 reserve()
提前分配足够的内存,以避免 vector
多次自动扩展带来的内存重新分配和拷贝开销,只影响vector的capacity,若new_cap < capacity则什么都不做
void reserve(size_type new_cap);
shrink_to_fit:用于“请求”将当前 vector
的容量(capacity
)变为当前的vector中实际存储的字符数量(size
),它提供了一种机制来释放不再需要的多余内存,但它只是一个非强制性的请求,标准库没有保证 shrink_to_fit
一定会执行内存收缩,它只是请求收缩,编译器可以选择忽略这一请求
void shrink_to_fit();
扩容机制:扩容1.5倍然后取整
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v;
size_t sz = v.capacity();
cout << "making v grow:\n";
for (int i = 0; i < 100; i++)
{
v.push_back(i);
//如果尾插后扩容了,就会执行下面的代码
if (sz != v.capacity())
{
sz = v.capacity();//同时将sz重新获取v的容量
cout << "capacity change:" << sz << endl;//打印扩容后v的大小
}
}
cout << "-----------------------" << endl;
cout << "reserve尝试对原空间进行缩容 >" << endl;
cout << "原size >" << v.size() << endl;
cout << "原capacity >" << v.capacity() << endl;
v.reserve(10);
cout << "后size >" << v.size() << endl;
cout << "后capacity >" << v.capacity() << endl;
cout << "-----------------------" << endl;
cout << "resize尝试对原空间进行缩容 >" << endl;
cout << "原size >" << v.size() << endl;
cout << "原capacity >" << v.capacity() << endl;
v.resize(10);
cout << "后size >" << v.size() << endl;
cout << "后capacity >" << v.capacity() << endl;
cout << "-----------------------" << endl;
cout << "shrink_to_fit尝试对原空间进行缩容 >" << endl;
cout << "原size >" << v.size() << endl;
cout << "原capacity >" << v.capacity() << endl;
v.shrink_to_fit();
cout << "后size >" << v.size() << endl;
cout << "后capacity >" << v.capacity() << endl;
return 0;
}
查找与插入
相关接口:find、insert、push_back
find:std::vector
并不直接提供查找函数,但我们可以通过标准库中的算法 std::find
来查找元素,或者通过 std::find_if
进行条件查找
template<class InputIt, class T>
InputIt find(InputIt first, InputIt last, const T& value);
template<class InputIt, class UnaryPredicate>
InputIt find_if(InputIt first, InputIt last, UnaryPredicate p);
first
和last
:表示要查找类的范围,通常用begin()
和end()
value
:要查找的值find_if中的p
:一个返回bool
值的条件函数或 Lambda 表达式
#include <iostream>
#include <vector>
#include <algorithm> // std::find
int main()
{
std::vector<int> vea = { 10, 20, 30, 40, 50 };
// 使用 std::find 查找 30
auto it = std::find(vea.begin(), vea.end(), 30);
if (it != vea.end())
{
std::cout << "找到元素: " << *it << std::endl;
}
else
{
std::cout << "未找到元素" << std::endl;
}
std::vector<int> veb = { 10, 20, 30, 40, 50 };
// 查找第一个大于 25 的元素
auto iit = std::find_if(veb.begin(), veb.end(), [](int n) { return n > 25; });
if (iit != veb.end())
{
std::cout << "找到大于 25 的元素: " << *iit << std::endl;
}
else
{
std::cout << "未找到符合条件的元素" << std::endl;
}
return 0;
}
insert:可以在指定位置插入单个元素
iterator insert(iterator pos, const T& value);
iterator insert(iterator pos, size_type count, const T& value);
template <class InputIt>
iterator insert(iterator pos, InputIt first, InputIt last);
pos
:插入位置的迭代器value
:要插入的值- count:要插入值得数量
- first、last:表示要插入的元素范围,
first
是起始迭代器,last
是结束迭代器
push_back:会将一个元素添加到 std::vector
的末尾,如果容器的当前容量不足以容纳新的元素,容器会自动扩展容量(重新分配内存)添加元素后,size
会增加,而 capacity
可能会根据需要增长
void push_back(const T& value);
void push_back(T&& value); // C++11 之后
const T& value
:复制一个已有的元素value
T&& value
:移动一个元素value
(右值引用),可以避免不必要的拷贝,提高效率
#include <iostream>
#include <vector>
int main()
{
std::vector<int> vec;
// 输出初始容量
std::cout << "初始容量: " << vec.capacity() << std::endl;
// 向 vector 中添加元素
for (int i = 0; i < 10; ++i)
{
vec.push_back(i);
std::cout << "添加元素 " << i << " 后的容量: " << vec.capacity() << std::endl;
}
// 输出 vector 中的元素
for (int n : vec)
{
std::cout << n << " "; // 输出: 0 1 2 3 4 5 6 7 8 9
}
std::cout << std::endl;
return 0;
}
补充:push_back常用于插入string类型或者其它自定义类型对象,下面是三种插入string的例子
#include <iostream>
#include <vector>
#include<string>
#include <algorithm>
using namespace std;
int main()
{
//对象数组
vector<string> v;
//版本一:插入有名对象
string s1("苹果");
v.push_back(s1);
//版本二:插入匿名对象
v.push_back(string("香蕉"));
//版本三:插入隐式类型转换
v.push_back("草莓");//版本一二三效果相同
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
return 0;
}
隐式转换过程:将 "草莓"
这个 C 风格字符串(cosnt char*)隐式地转换为一个临时的 string
对象(string类型),临时的 std::string
对象被复制到 vector
(拷贝构造)
,然后临时对象销毁
短字符串优化SSO
基本概念:是 C++ 标准库 std::string
的一个常见实现优化技术。当 std::string
处理较短的字符串时,不进行动态内存分配(即不使用堆内存),而是将字符串直接存储在 std::string
对象内部的一个小缓冲区中,如果没有 SSO ,std::string
通常会对所有字符串(无论长短)都在堆上分配内存,这样在创建或销毁大量短字符串时会频繁地进行堆内存分配和释放操作,增加了程序的开销和性能损耗
std::string s1 = "a";
std::string s2 = "short";
- 没有 SSO 的实现会在堆上为这些短字符串分配内存,导致频繁的内存操作,尤其是在处理大量短字符串时,堆分配的成本就显得非常高
std::string
的内部结构(简化版):
- 指针:指向堆上动态分配的内存(用于存储较长的字符串)
- 内部小缓冲区:用于存储短字符串。当字符串长度较短时,字符串会存储在这个小缓冲区中,而不需要进行堆内存分配
注意事项:SSO 的缓冲区大小通常依赖于具体的编译器实现,不同的编译器对 SSO 的支持和缓冲区大小是不同的。比如,Visual Studio 实现中常见的 SSO 缓冲区大小是 15 或 28 字节(包括结束的 '\0'
字符)
#include <iostream>
#include <string>
void print_string_info(const std::string& str)
{
std::cout << "String: " << str << std::endl;
std::cout << "Length: " << str.length() << std::endl;
std::cout << "Capacity: " << str.capacity() << std::endl;
std::cout << "----------------------------------" << std::endl;
}
int main()
{
// 短字符串(测试vs的string的小缓冲区大小)
std::string shortStr1 = "short";
print_string_info(shortStr1);
// 长字符串
std::string longStr = "This is a long string that exceeds the SSO limit";
print_string_info(longStr);
return 0;
}
vector类的模式实现
成员变量与重命名
typedef T* iterator;
typedef const T* const_iterator;
iterator _start = nullptr;//指向当前数组有效字符中的首字符的指针
iterator _finish = nullptr;//指向当前数组有效字符中的尾字符下一个位置的指针
iterator _endofstorage = nullptr;//指向数组空间末尾的指针
构造函数
//无参默认构造函数
vector()
{}
//使用迭代器区间构造函数(类模板的成员函数可以是函数模板)
template <class InputIterator>
vector(InputIterator first, InputIterator last)
{
while (first != last)//遍历迭代器区间
{
push_back(*first);
++first;
}
}
//使用指定个数和插入值得内容的构造函数(unsigned int,T)
vector(size_t n, const T& val = T())//仍然是匿名对象
{
reserve(n);
for (size_t i = 0; i < n; i++)
{
push_back(val);
}
}
//为了解决类型匹配提供的额外构造函数(int,T)
vector(int n, const T& val = T())//仍然是匿名对象
{
reserve(n);
for (int i = 0; i < n; i++)
{
push_back(val);
}
}
//使用C++11提供的initializer_list类型的构造函数
vector(initializer_list<T> il)
{
reserve(il.size());
for (auto& e : il)//initializer_list类模板提供了迭代器,所以可以使用范围for
{
push_back(e);
}
}
补充:类模板的成员函数可以是函数模板,这也是上面有一个可以用迭代器区间构造vector的构造函数
template <typename T>
class MyClass
{
public:
// 类模板的成员函数可以是一个函数模板
template <typename U>
void myFunc(const U& value);
};
析构函数
//析构函数
~vector()
{
delete[] _start;//因为所有的空间都是向堆申请的以new[]的方式,所以delete时也要delete[]
_start = _finish = _endofstorage = nullptr;
}
拷贝构造函数
//拷贝构造
vector(const vector<T>& v)
{
reserve(v.capacity());//因为this指针的缘故,这里实际是const vector<T>* this->reserve(v.capacity*());
for (auto& e : v)
{
push_back(e);
}
}
关于push_back的补充:push_back(e)
会调用 T
类型的 拷贝构造函数 来完成每个元素的复制。因为 push_back
是一个深拷贝操作,它会为每个元素分配新内存并调用 T
的拷贝构造函数,即使引用的vector中的元素发生变化或被销毁,当前 this
指向的 vector
中的元素仍然是安全的独立拷贝
迭代器接口
//迭代器类型本质就是对指针类型的封装
typedef T* iterator;
typedef const T* const_iterator;
//迭代器
iterator begin()
{
return _start;
}
//迭代器
iterator end()
{
return _finish;
}
//const版本迭代器(const成员函数,表示不会更改传递进来的对象数据)
const_iterator begin()const
{
return _start;
}
//const版本迭代器(const成员函数,表示不会更改传递进来的对象数据)
const_iterator end()const
{
return _finish;
}
各类运算符重载
//赋值函数,=运算符重载(v1 = v3)
vector<T>& operator=(vector<T> v)//如果是传引用传参,那么交换时v3的内容会被换为未初始化的随机值
{
swap(v);//swap(this,v)
return *this;//this的类型是vector<T>*,解引用后相当于把当前交换后得到的v1对象返回
}
//[]运算符重载
T& operator[](size_t pos)
{
assert(pos < size());//越界判断
return _start[pos];//返回pos位置的值
}
//const版本的[]运算符重载
const T& operator[](size_t pos) const
{
assert(pos < size());//越界判断
return _start[pos];//还是返回pos位置的值
}
其余小接口
//交换函数
void swap(vector<T>& v)//(vector<T>* this,vector<T>& v)
{
std::swap(_start, v._start);//调用std库中的swap函数,不加std会调用自定义实现的swap,就会出现参数不匹配问题
std::swap(_finish, v._finish);//(this->_finish,v._finish)
std::swap(_endofstorage, v._endofstorage);
}
//计算有效字符个数
size_t size()const
{
return _finish - _start;//两指针相减返回的是两指针之间的元素数量
}
//计算空间大小
size_t capacity()const
{
return _endofstorage - _start;
}
插入接口
//扩容接口
void reserve(size_t n)
{
if (n > capacity())//当n大于capacity时才会进行扩容
{
T* tmp = new T[n];//临时指针tmp
size_t old_size = size();
//memcpy(tmp, _start, size() * sizeof(T));//浅拷贝不能用
for (size_t i = 0; i < old_size; i++)
{
tmp[i] = _start[i];//tmp + i = _start + i
}
delete[] _start;//删除_start指向的旧空间
_start = tmp;//令_start指向新空间
_finish = tmp + old_size;//更新此时_finish的指向,这里的不能写tmp + size因为size的计算方式是_finish - _start,如果是+size就会变成:_finish = tmp + _finish - _start,tmp又等于tmp所以最后式子就会变成_finish = _finish
_endofstorage = tmp + n;//更新此时_endofstorage的指向
}
}
//重新调整vector数组中的有效字符个数
void resize(size_t n, const T& val = T())
{
if (n > size())//如果n大于数组有效数据个数就进行扩容判断
{
reserve(n);//判断到底要不要扩容
// 插入
while (_finish < _start + n)//循环插入(即使不需要扩容也要循环插入)
{
*_finish = val;//插入指定的有效字符
++_finish;
}
}
else//否则就缩容
{
_finish = _start + n;//仅改变指针指向不删除有效数据
}
}
//尾插
void push_back(const T& val)
{
insert(end(), val);
}
//尾删
void pop_back()
{
erase(--end());
}
//是否为空的判断
void empty()
{
return _start == _finish;
}
//在指定位置前插入
void insert(iterator pos, const T& val)//传入的pos是一个指针
{
assert(pos >= _start);
assert(pos <= _finish);//越界判断
//if (_finish == _endofstorage)//直接扩容会导致迭代器失效问题(开辟空间后pos仍指向原来的就空间),具体情况查看下面的图片
//{
// reserve(capacity() == 0 ? 4 : capacity() * 2);
//}
if (_finish == _endofstorage)//扩容前存放pos的相对位置,开辟空间后令pos指向新空间的相对位置
{
size_t len = pos - _start;//存放相对位置
reserve(capacity() == 0 ? 4 : capacity() * 2);//到这里是必定扩容的,只是看要扩多少
//更新pos
pos = _start + len;
}
iterator it = _finish - 1;
while (it >= pos)
{
*(it + 1) = *it;
--it;
}
*pos = val;//更新指定位置存放的值
++_finish;//由于只是插入一个元素所以将_finish向后移动一个即可
}
//在指定位置删除
//prev pos it
iterator erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);//越界判断
iterator it = pos + 1;
while (it < _finish)
{
*(it - 1) = *it;
++it;
}
--_finish;
return pos;
}
问题:为什么不用memcpy进行拷贝?
解释:memcpy的拷贝是按字节的浅拷贝,由于vector中存放的是string类型的对象,如果是短字符串还可以直接拷贝(因为是存放在小缓冲区中的),但如果是长字符串那么拷贝的就是指向堆中常量字符串的指针,此时会有两个指针指向同一字符串,当delete[] _start时,的确删除了_start指向的空间中的所有内容(包括指向堆中字符串的指针),但tmp中的存放的指向堆中字符串的指针此时指向的是未定义的内容,这些指针此时是野指针
结论:对于vector<T>,如果T是string或者vector等类型,用memcpy拷贝会出现野指针问题
- 浅拷贝:将一个对象的值复制到另一个对象,但对于被复制的对象内部存在指针或引用等情况时,只会简单地进行值传递,即新旧对象会共享相同的内存地址,当其中一个修改了这块内存中的内容时,另一个也会受到影响
- 深拷贝:创建一个新的独立副本,并且递归地将所有层次上的动态分配资源都复制过去。即使原始数据结构包含了指针或者其他引用类型,在深度复制后每个副本都有自己独立分配并管理其所需资源,即新旧对象不会共享相同的内存地址
解决办法:循环赋值
原因:T是int就直接将int拷贝,而T是string时,对于每个 string
对象,它们会调用自己的赋值运算符重载函数来执行深拷贝操作
问题:什么是迭代器失效问题?
解释:指在某些情况下,容器的迭代器由于容器内部结构的改变而变得无效,导致迭代器指向的内容可能变得不可预测或错误,上面insert接口中,如果对数组进行了扩容那么就可能导致迭代器失效问题
打印接口
//打印函数+函数模板
template<class T>
void print_vector(const vector<T>& v)
{
for (size_t i = 0; i < v.size(); i++)//简单的for循环遍历
{
cout << v[i] << " ";
}
cout << endl;
}
完整代码
vector.h文件
#pragma once
#include <iostream>
#include <assert.h>
#include<algorithm>
#include<vector>
using namespace std;
namespace bit
{
template<class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
//无参默认构造函数
vector()
{}
//使用迭代器区间构造函数(类模板的成员函数可以是函数模板)
template <class InputIterator>
vector(InputIterator first, InputIterator last)
{
while (first != last)//遍历迭代器区间
{
push_back(*first);
++first;
}
}
//使用指定个数和插入值得内容的构造函数
vector(size_t n, const T& val = T())//仍然是匿名对象
{
reserve(n);
for (size_t i = 0; i < n; i++)
{
push_back(val);
}
}
//使用C++11提供的initializer_list类型的构造函数
vector(initializer_list<T> il)
{
reserve(il.size());
for (auto& e : il)//initializer_list类模板提供了迭代器,所以可以使用范围for
{
push_back(e);
}
}
//为了解决类型匹配提供的额外构造函数
vector(int n, const T& val = T())//仍然是匿名对象
{
reserve(n);
for (int i = 0; i < n; i++)
{
push_back(val);
}
}
//析构函数
~vector()
{
delete[] _start;//对于new时是[]的delete时也记得用[]
_start = _finish = _endofstorage = nullptr;
}
//拷贝构造
vector(const vector<T>& v)
{
reserve(v.capacity());//后面的push_back还要扩容,提前为v2开辟好v1一样大小的空间就可以避免可能的多次开辟空间
for (auto& e : v)//T为string时,基于迭代器的范围for,*it被给予e,迭代器获取的数据给了e,这时是一个深拷贝
{
push_back(e);
}
}
//迭代器
iterator begin()
{
return _start;
}
//迭代器
iterator end()
{
return _finish;
}
//const版本迭代器
const_iterator begin()const
{
return _start;
}
//const版本迭代器
const_iterator end()const
{
return _finish;
}
//赋值函数,=运算符重载(v1 = v3)
vector<T>& operator=(vector<T> v)//如果是传引用传参,那么交换时v3的内容会被换为未初始化的随机值
{
swap(v);//swap(this,v)
return *this;//this的类型是vector<T>*,解引用后相当于把当前交换后得到的v1对象返回
}
//[]运算符重载
T& operator[](size_t pos)
{
assert(pos < size());//越界判断
return _start[pos];//返回pos位置的值
}
//const版本的[]运算符重载
const T& operator[](size_t pos) const
{
assert(pos < size());//越界判断
return _start[pos];//还是返回pos位置的值
}
//交换函数
void swap(vector<T>& v)//(vector<T>* this,vector<T>& v)
{
std::swap(_start, v._start);//调用std库中的swap函数,不加std会调用自定义实现的swap,就会出现参数不匹配问题
std::swap(_finish, v._finish);//(this->_finish,v._finish)
std::swap(_endofstorage, v._endofstorage);
}
//计算有效字符个数
size_t size()const
{
return _finish - _start;//两指针相减返回的是两指针之间的元素数量
}
//计算空间大小
size_t capacity()const
{
return _endofstorage - _start;
}
//扩容接口
void reserve(size_t n)
{
if (n > capacity())//当n大于capacity时才会进行扩容
{
T* tmp = new T[n];//临时指针tmp
size_t old_size = size();
//memcpy(tmp, _start, size() * sizeof(T));//浅拷贝不能用
for (size_t i = 0; i < old_size; i++)
{
tmp[i] = _start[i];//tmp + i = _start + i
}
delete[] _start;//删除_start指向的旧空间
_start = tmp;//令_start指向新空间
_finish = tmp + old_size;//更新此时_finish的指向,这里的不能写tmp + size因为size的计算方式是_finish - _start,如果是+size就会变成:_finish = tmp + _finish - _start,tmp又等于tmp所以最后式子就会变成_finish = _finish
_endofstorage = tmp + n;//更新此时_endofstorage的指向
}
}
//重新调整vector数组中的有效字符个数
void resize(size_t n, const T& val = T())
{
if (n > size())//如果n大于数组有效数据个数就进行扩容判断
{
reserve(n);//判断到底要不要扩容
// 插入
while (_finish < _start + n)//循环插入(即使不需要扩容也要循环插入)
{
*_finish = val;//插入指定的有效字符
++_finish;
}
}
else//否则就缩容
{
_finish = _start + n;//仅改变指针指向不删除有效数据
}
}
//尾插
void push_back(const T& val)
{
insert(end(), val);
}
//尾删
void pop_back()
{
erase(--end());
}
//是否为空的判断
void empty()
{
return _start == _finish;
}
//在指定位置前插入
void insert(iterator pos, const T& val)//传入的pos是一个指针
{
assert(pos >= _start);
assert(pos <= _finish);//越界判断
//if (_finish == _endofstorage)//直接扩容会导致迭代器失效问题(开辟空间后pos仍指向原来的就空间),具体情况查看下面的图片
//{
// reserve(capacity() == 0 ? 4 : capacity() * 2);
//}
if (_finish == _endofstorage)//扩容前存放pos的相对位置,开辟空间后令pos指向新空间的相对位置
{
size_t len = pos - _start;//存放相对位置
reserve(capacity() == 0 ? 4 : capacity() * 2);//到这里是必定扩容的,只是看要扩多少
//更新pos
pos = _start + len;
}
iterator it = _finish - 1;
while (it >= pos)
{
*(it + 1) = *it;
--it;
}
*pos = val;//更新指定位置存放的值
++_finish;//由于只是插入一个元素所以将_finish向后移动一个即可
}
//在指定位置删除
//prev pos it
iterator erase(iterator pos)
{
assert(pos >= _start);
assert(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;//指向数组空间末尾的指针
};
//打印函数+函数模板
template<class T>
void print_vector(const vector<T>& v)
{
for (size_t i = 0; i < v.size(); i++)//简单的for循环遍历
{
cout << v[i] << " ";
}
cout << endl;
}
//vector功能测试函数1:尾插 + 指定位置插入 + 删除指定位置 + 扩容/缩容
void test_vector1()
{
vector<int> v1;//实例化整型
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
print_vector(v1);
vector<double> v2;//实例化浮点型
v2.push_back(1.1);
v2.push_back(2.2);
v2.push_back(3.3);
v2.push_back(4.4);
v2.push_back(5.5);
print_vector(v2);
v2.insert(v2.begin(), 11.11);//头插
print_vector(v2);
v2.insert(v2.begin() + 3, 22.22);//在第三个位置插入
print_vector(v2);
v2.erase(v2.begin());//头删
print_vector(v2);
v2.erase(v2.begin() + 4);//删除第三个位置
print_vector(v2);
v1.resize(10);
print_vector(v1);
v1.resize(3);
print_vector(v1);
}
//vector功能测试函数2:拷贝构造
void test_vector2()
{
vector<int> v1;//实例化整型
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
vector<int> v2(v1);
print_vector(v2);
}
//vector功能测试函数3:赋值
void test_vector3()
{
vector<int> v1;//实例化整型
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
vector<int> v3;//实例化整型
v3.push_back(10);
v3.push_back(20);
v3.push_back(30);
v1 = v3;
print_vector(v1);
}
//vector功能测试函数4:迭起器区间模板
void test_vector4()
{
vector<int> v1;//实例化整型
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
print_vector(v1);
//vector<int> v2(v1.begin(), v1.end());//将v1的全部内容拷贝至v2
//print_vector(v2);
vector<int> v2(v1.begin() + 1, v1.end() - 1);//将v1的全部内容除了首尾数据外拷贝至v2
print_vector(v2);
string str("abcd");
vector<int> v3(str.begin(), str.end());//将string类类型的数据以整型存放,发生隐式转换,存放的是字符串中每个字符的ASCII码值,打印出来的也是
print_vector(v3);
}
//vector功能测试函数5:构造函数的选择
void test_vector5()
{
vector<int> v1(10, 1);//预期调用第二种默认构造函数,实际调用第一种,报错,图④
print_vector(v1);
/* vector<int> v2(10u, 1);//x86环境和x64环境运行结果不同的缘故这里不予以使用
print_vector(v2);*/
vector<char> v3(10, 'a');
print_vector(v3);
}
//vector功能测试函数6:C++11支持的新特性
void test_vector6()//图⑤
{
/* initializer_list<int> x = { 1,2,3,4,5,6,7,8,9,10 };
vector<int> v1 = { 1,2,3,4,5,6,7,8,9,10 };
vector<int> v1 = x;
cout << typeid(x).name() << endl;*/
//隐式类型转换 + 优化(构造 + 拷贝构造 = 直接构造):在只有{1,2,3,4,5,6,7,8,9,10}时它并没有明确指定类型所以是“无类型”,当它被赋值给vector<int> x,它会隐式类型转换为initializer_list<int>类型然后,调用initializer_list的构造函数,最后将构造出的结果赋值v1
vector<int> v1 = { 1,2,3,4,5,6,7,8,9,10 };
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
//直接构造(第三种构造方式)
vector<int> v2({ 10,20,30,40,50,60,70,80,90,100 });
for (auto e : v2)
{
cout << e << " ";
}
cout << endl;
//C++11还支持这样的写法:
//vector<int> v1{ 1,2,3,4,5,6,7,8,9,10 };
//int i = 1;
//int j = { 1 };
//int k{ 1 };
}
//vector功能测试函数7:memcpy拷贝的问题,图⑥
void test_vector7()
{
vector<string> v;
v.push_back("11111");
v.push_back("22222");
v.push_back("33333");
v.push_back("44444");
v.push_back("55555");
for (auto& e : v)
{
cout << e << " ";
}
cout << endl;
}
//vector功能测试函数8:insert后导致迭代器失效(),图⑦
void test_vector8()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
v1.push_back(6);
v1.push_back(7);
v1.push_back(8);
print_vector(v1);
//insert后,it就失效了
vector<int>::iterator it = v1.begin() + 3;
v1.insert(it, 40);
print_vector(v1);
//it = v1.begin()+3//更新
cout << *it << endl;//打印随机值
//解决方案,不再使用it,将它置空或者将他更新,it = v1.begin()+3
}
//vector功能测试函数9:删除偶数,图⑧
void test_vector9()
{
情况一:只有一个偶数
//vector<int> v1;
//v1.push_back(1);
//v1.push_back(2);
//v1.push_back(3);
//v1.push_back(4);
//v1.push_back(5);
//
删除偶数
//vector<int>::iterator it1 = v1.begin();
//while (it1 != v1.end())
//{
// if (*it1 % 2 == 0)
// {
// v1.erase(it1);
// }
// ++it1;
//}
//print_vector(v1);
情况二:有两个相同偶数
//vector<int> v2;
//v2.push_back(1);
//v2.push_back(2);
//v2.push_back(3);
//v2.push_back(4);
//v2.push_back(4);
//v2.push_back(5);
删除偶数
//vector<int>::iterator it2 = v2.begin();
//while (it2 != v2.end())
//{
// if (*it2 % 2 == 0)
// {
// v2.erase(it2);
// }
// ++it2;
//}
//print_vector(v2);
情况三:有两个不同偶数
//vector<int> v3;
//v3.push_back(1);
//v3.push_back(2);
//v3.push_back(3);
//v3.push_back(4);
//v3.push_back(4);
//v3.push_back(5);
删除偶数
//vector<int>::iterator it3 = v3.begin();
//while (it2 != v3.end())
//{
// if (*it3 % 2 == 0)
// {
// v3.erase(it3);
// }
// ++it3;
//}
//print_vector(v3);
//情况一:只有一个偶数
std::vector<int> v1;//没实现vector库中提供的erase功能所以后续举例子直接用std提供的vector即可
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
//删除偶数
std::vector<int>::iterator it1 = v1.begin();
while (it1 != v1.end())
{
if (*it1 % 2 == 0)
{
it1 = v1.erase(it1);
}
else
{
++it1;
}
}
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
//情况二:有两个相同偶数
std::vector<int> v2;
v2.push_back(1);
v2.push_back(2);
v2.push_back(3);
v2.push_back(4);
v2.push_back(4);
v2.push_back(5);
//删除偶数
std::vector<int>::iterator it2 = v2.begin();
while (it2 != v2.end())
{
if (*it2 % 2 == 0)
{
it2 = v2.erase(it2);
}
else
{
++it2;
}
}
for (auto e : v2)
{
cout << e << " ";
}
cout << endl;
//情况三:有两个不同偶数
std::vector<int> v3;
v3.push_back(1);
v3.push_back(2);
v3.push_back(3);
v3.push_back(4);
v3.push_back(4);
v3.push_back(5);
//删除偶数
std::vector<int>::iterator it3 = v3.begin();
while (it3 != v3.end())
{
if (*it3 % 2 == 0)
{
it3 = v3.erase(it3);
}
else
{
++it3;
}
}
for (auto e : v3)
{
cout << e << " ";
}
cout << endl;
}
}
test.cpp文件
#include "vector.h"
int main()
{
//bit::test_vector1();
//bit::test_vector2();
//bit::test_vector3();
//bit::test_vector4();
//bit::test_vector5();
//bit::test_vector6();
//bit::test_vector7();
//bit::test_vector8();
bit::test_vector9();
return 0;
}a
迭代器失效问题
在test_vector9的测试函数中如果数组有两个偶数 或 两个相邻偶数 或 数组末尾有偶数,那么会出现下面三种情况:
情况一:it起始指向1,it不与end相遇就循环,1不是偶数,++it,it指向2与end不相遇,2是偶数删除2的同时令后面的数据向前覆盖(3占据原来2的位置),++it,it指向4,4是偶数删除4的同时令后面的数向前覆盖(5占据原来4的位置),++it,it与end相遇循环结束,成功删除2和4
情况二:it起始指向1,it不与end相遇就循环,1不是偶数,++it,it指向2与end不相遇,2是偶数删除2的同时令后面的数据向前覆盖(3占据原来2的位置),++it,it指向4,4是偶数删除4的同时令后面的数据向前覆盖(第二个4占据第一个4的位置),++it,it指向5,5不是偶数,++it,it与end相遇循环结束,由于it跳过了第二个偶数4所以第二个偶数4被留下
情况三:it起始指向1,it不与end相遇就循环,1不是偶数,++it,it指向2与end不相遇,2是偶数删除2的同时令后面的数据向前覆盖(3占据原来2的位置),++it,it指向4,4是偶数删除4的同时令后面的数据向前覆盖(5占据原来4的位置),++it,it指向4,4是偶数删除4的同时令后面的数据向前覆盖(end此时指向第二个4的位置),但此时仍会++it,it指向原来end指向的位置,至此二者永远不会相遇
注意事项:end指向的是有效字符的下一个位置
插入和删除导致迭代器失效的常见原因:
- insert导致失效的原因是开辟了新空间后,迭代器扔指向原空间
- erase导致失效的原因是销毁的空间不是连续的空间,迭代器找不到下一块小空间的位置
迭代器失效的两种解决方案:
- 直接放弃该迭代器,将其置空
- 更新迭代器
为了解决上述问题,vector库中的erase提供了迭代器类型的返回值
返回值:返回原指定删除位置的位置为当前迭代器指向的位置
it = v.erase(it)
再配上一个else语句,用于防止情况二越过第二个4的情况,只有在it指向奇数时才会向后移动:
if(*it % 2 == 0)
{
it = v.erase(it);
}
else
{
++it;
}
~over~