C++学习笔记---011
- C++之vector的应用及模拟实现
-
- 1、vector的介绍
- 2、vector的应用
-
- [2.1、拷贝构造 + 逆置迭代器 + 基础循环遍历](#2.1、拷贝构造 + 逆置迭代器 + 基础循环遍历)
- [2.2、capacity + size](#2.2、capacity + size)
- [2.3、下标+[] 和 at](#2.3、下标+[] 和 at)
- 2.4、其他接口
- 2.5、vector兼容复杂类型
- 3、vector深度剖析及模拟实现
- 4、vector的各种关键性问题
-
- 4.1、vector的insert迭代器失效问题
- 4.2、erase删除偶数引发的思考问题
- 4.3、vector的类模板的冲突问题
- 4.4、单参数的构造函数,支持隐式类型转换
- [4.5、vector的vector string扩容问题](#4.5、vector的vector string扩容问题)
- 5、vector的实现完整代码
C++之vector的应用及模拟实现
前言:
前面篇章学习了C++对于string类的基本使用以及常用接口的认识,接下来继续学习,C++的vector顺序表容器的应用以及模拟实现等知识。
/知识点汇总/
1、vector的介绍
std::vector
template < class T, class Alloc = allocator > class vector; // generic template
Vectors are sequence containers representing arrays that can change in size.
vector 是 C++ 标准模板库(STL)中的一个动态数组类模板。它提供了一组方法,使得数组的使用更加灵活和方便。以下是关于 vector 的一些关键特点:1.动态大小 :vector 可以根据需要在运行时动态地增长或缩小。当你向 vector 添加元素时,如果当前的大小不足以容纳新元素,vector 会自动分配更多的内存。
2.连续存储 :vector 中的元素在内存中是连续存储的,这意味着可以通过下标操作符([])直接访问任何元素,并且访问时间复杂度为 O(1)。
3.随机访问迭代器 :vector 提供了随机访问迭代器,这使得你可以使用迭代器在 vector 中进行高效的遍历和元素访问。
4.方法丰富 :vector 提供了许多成员函数,如 push_back() 用于在尾部添加元素,pop_back() 用于删除尾部元素,size() 用于获取元素数量,empty() 用于检查是否为空,begin() 和 end() 用于获取迭代器以遍历元素等。
5.效率:由于 vector 在内存中连续存储,且其大小可以动态调整,因此在许多情况下,vector 比静态数组更加高效和灵活。
2、vector的应用
有了string类的经验,我们就可以直接使用vector的常用接口,主要写一下一些关键点即可。
2.1、拷贝构造 + 逆置迭代器 + 基础循环遍历
cpp
void test_vector1()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
//for循环
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循环
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
//拷贝构造 + 逆置迭代器
vector<int> v1(10, 1);
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
//逆置迭代器循环
vector<int>::reverse_iterator rit = v1.rbegin();
while (rit != v1.rend())
{
cout << *rit << " ";
++rit;
}
cout << endl;
}
2.2、capacity + size
比对与Linux下的扩容机制
reserve和resize以及shrink_to_fit(只有shrink_to_fit会缩容)
cpp
void test_vector2()
{
size_t sz;
vector<int> v;
//v.reserve(100);//可预先扩容,但不初始化
sz = v.capacity();
cout << "making v grow: " << endl;
for (int i = 0; i < 100; i++)
{
v.push_back(i);
if (sz != v.capacity())
{
sz = v.capacity();
cout << "capacity changed: " << sz << endl;
}
}
vector<int> v2;
v2.resize(10,1);//扩容,且可初始化
for (auto e : v2)
{
cout << e << " ";
}
cout << endl;
//缩容?
cout << "xxxxxxxxxxxxxxxxxxxx" << 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 << "xxxxxxxxxxxxxxxxxxxx" << 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 << "xxxxxxxxxxxxxxxxxxxx" << endl;
cout << "size = " << v.size() << endl;
cout << "capacity = " << v.capacity() << endl;
v.shrink_to_fit();//常与resize搭配使用,缩容至数据的大小,通常情况下也不需要缩容
cout << "size = " << v.size() << endl;
cout << "capacity = " << v.capacity() << endl;
}
2.3、下标+[] 和 at
下标+[] 和 at区别在于检查断言的方式不同。
下标+[]是断言检查的 ;而at访问是抛异常(带捕获)检查。
cpp
void test_vector3()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
//下标+[]
for (size_t i = 0; i < v.size(); i++)
{
cout << v[i] << " ";
}
cout << endl;
//at
for (size_t i = 0; i < v.size(); i++)
{
v.at(i)++;
cout << v.at(i) << " ";
}
cout << endl;
//data
int* p = v.data();
*p = 10;
++p;
++p;
*p = 20;
p[2] = 50;
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
//assign
v.assign(5, 123);
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
//push_back
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
//erase
v.erase(v.begin(), v.end() - 5);
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
//pop_back
v.pop_back();
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
//insert
v.insert(v.end(),5);
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
2.4、其他接口
find -- 算法库,不在类成员函数中。
算法头文件 --
尾插和尾删 -- push_back和pop_back
erase/insert
data/assign
...
cpp
void test_vector4()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
//vector<int>::iterator pos = find(v.begin(), v.end(), 3);
//等价
auto pos = find(v.begin(), v.end(), 3);
if (pos != v.end())//找到返回pos位置,找不到返回end()位置
{
v.insert(pos, 30);
}
//头插
v.insert(v.begin(), 0);
//指定2位置插入
v.insert(v.begin() + 2, 0);
//插入一段区间
string s("abcd");
v.insert(v.begin(), s.begin(), s.end());//隐式类型转换
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
2.5、vector兼容复杂类型
思考为什么string类型需要自己写find,而vector不用自己写?
因为string形式复杂,需要兼容子串的查找,而vector只需要找指定的值而已。
cpp
void test_vector5()
{
vector<string> v;//对象数组
string s1("水果");
v.push_back("a");
v.push_back(s1);//方式1
v.push_back(string("苹果"));//方式2 -- 匿名对象
v.push_back("西瓜");//方式3 --- 隐式类型转换
//vector存vector
vector<vector<int>> vv;//实例化两层,理解为二维数组的形式
}
3、vector深度剖析及模拟实现
3.1、vector的构造函数和析构函数
cpp
//构造函数
vector()
:_start(nullptr)
,_finish(nullptr)
,_end_of_storage(nullptr)
{}
//const T& value = T()这个value参数由于不能直接给默认为0,因为要考虑一些特殊类,比如日期类,不可能有0天,即,0月0号
//所以直接用 T()匿名对象解决这样的问题
vector(size_t n, const T& value = T())
{
reserve(n);
for(size_t i = 0; i < n; i++)
{
push_back(value);
}
}
//迭代器区间构造
//类模板的成员函数可以是函数模板
template <class InputIterator>
vector(InputIterator first, InputIterator last)
{
while (first != last)
{
push_back(*first);
++first;
}
}
//initializer_list构造函数
vector(initializer_list<T> il)
{
reserve(il.size());
for (auto& e : il)
{
push_back(e);
}
}
//析构函数
~vector()
{
delete[] _start;
_start = _finish = _end_of_storage = nullptr;
}
3.2、vector的push_back/pop_back接口函数
cpp
//尾插
void push_back(const T& val)
{
//扩容
if (_finish == _end_of_storage)
{
//保存size
size_t old_size = size();
size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
//开辟新空间+拷贝+释放
T* tmp = new T[newcapacity];
memcpy(tmp, _start, size() * sizeof(T));
delete[] _start;
//更新
_start = tmp;
_finish = tmp + old_size;
_end_of_storage = tmp + newcapacity;
//复用1:
//size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
//reserve(newcapacity);
}
*_finish = val;
++_finish;
//复用2:
//insert(end(), val);
}
//尾删
void pop_back()
{
//assert(!empty());
//--_finish;
//复用
erase(--end());
}
3.3、vector的capacity和size
cpp
//size大小
size_t size() const
{
return _finish - _start;
}
//容量
size_t capacity() const
{
return _end_of_storage - _start;
}
//size大小
size_t size() const
{
return _finish - _start;
}
//容量
size_t capacity() const
{
return _end_of_storage - _start;
}
3.4、vector的迭代器接口函数
cpp
typedef T* iterator;
typedef const T* const_iterator;
//运算符重载
T& operator[](size_t pos)
{
assert(pos < size());
return _start[pos];
}
//运算符重载const对象
const T& operator[](size_t pos) const
{
assert(pos < size());
return _start[pos];
}
//begin
iterator begin()
{
return _start;
}
//end
iterator end()
{
return _finish;
}
//const_begin
const_iterator begin() const
{
return _start;
}
//const_end
const_iterator end() const
{
return _finish;
}
3.5、vector的reserve/resize接口函数
cpp
//扩容
void reserve(size_t n)
{
if (n > capacity())
{
T* tmp = new T[n];
//保存size
size_t old_size = size();
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,const T& val = T())//参数的匿名对象(相当于调用默认构造),决定执行那种情况,且不能赋0
{
if (n > size())
{
//检查容量
reserve(n);
//插入
while (_finish < _start + n)
{
*_finish = val;
++_finish;
}
}
else
{
//删除
_finish = _start + n;
}
}
3.6、vector的insert/empty/erase接口函数
cpp
//插入
void insert(iterator pos, const T& val)
{
assert(pos >= _start);
assert(pos <= _finish);
//扩容
if (_finish == _end_of_storage)
{
size_t len = pos - _start;
//复用
size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);
//如果扩容及时更新pos---更新到相对位置
//本质是扩容时,pos仍然指向旧空间,所以保留相对起始位置,更新pos位置。
pos = _start + len;
}
//挪动数据
iterator it = _finish - 1;
while (it >= pos)
{
*(it + 1) = *it;
--it;
}
//放入数据
*pos = val;
_finish++;
}
//删除
void erase(iterator pos)
{
assert(pos >= _start);
assert(pos <= _finish);
//挪动覆盖
iterator it = pos + 1;
while (it != _finish)
{
*(it - 1) = *it;//迭代器失效问题
++it;
}
--_finish;
}
3.7、vector的拷贝构造函数以及赋值构造函数
cpp
//拷贝构造
vector(const vector<T>& v)
{
//写法1:方便,但是存在一个弊端
//1.T为string时,不兼容
//解决加引用&和reserve预先开辟空间
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(_end_of_storage, v._end_of_storage);
}
//vector<T>& operator=(const vector<T>& v)
vector<T>& operator=(vector<T> v)
{
swap(v);
return *this;
}
4、vector的各种关键性问题
4.1、vector的insert迭代器失效问题
cpp
//插入
void insert(iterator pos, const T& val)
{
assert(pos >= _start);
assert(pos <= _finish);
//扩容
if (_finish == _end_of_storage)
{
size_t len = pos - _start;
//复用
size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);
//如果扩容及时更新pos---更新到相对位置
//本质是扩容时,pos仍然指向旧空间,所以保留相对起始位置,更新pos位置。
pos = _start + len;
}
//挪动数据
iterator it = _finish - 1;
while (it >= pos)
{
*(it + 1) = *it;
--it;
}
//放入数据
*pos = val;
_finish++;
}
//迭代器失效,insert()常见问题
//本质属于野指针问题
void test_vector8()
{
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(6);
v.push_back(7);
v.push_back(8);
//打印
cout << "v:" << endl;
print_vector(v);
vector<int>::iterator it = v.begin() + 3;
v.insert(it, 60);//insert之后,it迭代器就失效了,失效后就不要使用了
//打印
cout << "v:" << endl;
print_vector(v);
cout << *it << endl;//insert之后,it迭代器就失效了,失效后就不要使用了。
}
cpp
//要么就手动更新
it = v.begin() + 3;
cout << *it << endl;
cpp
void test_vector8()
{
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(6);
v.push_back(7);
v.push_back(8);
//打印
cout << "v:" << endl;
print_vector(v);
vector<int>::iterator it = v.begin() + 3;
v.insert(it, 60);//insert之后,it迭代器就失效了,失效后就不要使用了
//打印
cout << "v:" << endl;
print_vector(v);
//要么就手动更新
it = v.begin() + 3;
cout << *it << endl;
}
1.如果vector的当前容量不足以容纳新元素,它可能会分配一个新的、更大的内存块,并将所有现有元素复制(或移动)到新内存块中。这个过程涉及元素的复制或移动操作,因此所有指向vector元素的迭代器、引用和指针都会失效,因为它们现在指向的是旧内存块中的位置,而旧内存块可能已经被释放。
2.即使不需要重新分配内存,insert操作也可能需要移动插入点之后的所有元素,以便为新元素腾出空间。这些移动操作同样会导致指向这些元素的迭代器失效。
4.2、erase删除偶数引发的思考问题
与insert类似erase的操作也存在一定的迭代器失效问题。
以删除序列中的偶数为例。
场景一:正确处理得到结果 ---- 是巧合
cpp
void test_vector9()
{
//场景1:--- 结果对了,是巧合
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
//打印
cout << "v:" << endl;
print_vector(v);
//删除偶数
vector<int>::iterator it = v.begin();
while (it != v.end())
{
if (*it % 2 == 0)
{
v.erase(it);
//it2 = v.erase(it2); --- 正解
}
else
//else
++it;
}
//打印
cout << "v:" << endl;
print_vector(v);//1 3 5
}
场景二:
cpp
void test_vector9()
{
//场景2:--- 偶数未被完全正确删除
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(4);
v1.push_back(5);
//打印
cout << "v1:" << endl;
print_vector(v1);
//删除偶数
vector<int>::iterator it1 = v1.begin();
while (it1 != v1.end())
{
if (*it1 % 2 == 0)
{
v1.erase(it1);
//it2 = v.erase(it2); --- 正解
}
++it1;
}
//打印
cout << "场景二:" << endl;
print_vector(v1);//1 3 4 5 --- 发现有个偶数4未被删除
}
这里靠else也能处理错误,但是仍然存在不妥善的地方。没有解决本质问题。
cpp
void test_vector9()
{
//场景2:--- 偶数未被完全正确删除
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(4);
v1.push_back(5);
//打印
cout << "v1:" << endl;
print_vector(v1);
//删除偶数
vector<int>::iterator it1 = v1.begin();
while (it1 != v1.end())
{
if (*it1 % 2 == 0)
{
v1.erase(it1);
//it2 = v.erase(it2); --- 正解
}
else//加上else
++it1;
}
//打印
cout << "场景二:" << endl;
print_vector(v1);//1 3 5 --- 未解决本质问题
}
场景三:
cpp
void test_vector9()
{
//场景3: --- assert崩溃
/**/
vector<int> v2;
v2.push_back(1);
v2.push_back(2);
v2.push_back(3);
v2.push_back(4);
v2.push_back(5);
v2.push_back(6);
//打印
cout << "v2:" << endl;
print_vector(v2);
//删除偶数
vector<int>::iterator it2 = v2.begin();
while (it2 != v2.end())
{
if (*it2 % 2 == 0)
{
v.erase(it2);
//it2 = v.erase(it2); --- 正解
}
//else//加else,能够巧合处理
++it2;
}
//打印
cout << "场景三:" << endl;
print_vector(v2);
}
我们不能总是靠巧合解决问题,否则以后可能留有一些不可预知的隐患。
所以,需要理解造成迭代器失效的本质。
本质是 由于erase也跟insert一样会使it迭代器失效。
其次,erase删除it下标的元素后,会把后面的元素向前挪动,导致,数据错位以及非法访问。
那么如何解决此类问题呢?
解决办法:参考官方文档
cpp
//删除
void erase(iterator pos)
{
assert(pos >= _start);
assert(pos <= _finish);
//挪动覆盖
iterator it = pos + 1;
while (it != _finish)
{
*(it - 1) = *it;
++it;
}
--_finish;
}
//解决迭代器失效问题
iterator erase(iterator pos)
{
assert(pos >= _start);
assert(pos <= _finish);
//挪动覆盖
iterator it = pos + 1;
while (it != _finish)
{
*(it - 1) = *it;
++it;
}
--_finish;
return pos;
}
iterator erase(iterator position);
返回一个删除元素下一个位置的迭代器
所以此类问题,要正确的按照规则使用。
场景四:解决问题
cpp
void test_vector9()
{
//场景4: --- 解决问题,正确使用方式,本质就是更新迭代器
//std::vector<int> v3;
vector<int> v3;
v3.push_back(1);
v3.push_back(2);
v3.push_back(3);
v3.push_back(4);
v3.push_back(5);
v3.push_back(6);
//删除偶数
//std::vector<int>::iterator it3 = v3.begin();
vector<int>::iterator it3 = v3.begin();
while (it3 != v3.end())
{
if (*it3 % 2 == 0)
{
it3 = v3.erase(it3); //正解
}
else
++it3;
}
//打印
cout << "场景四:" << endl;
for (auto e : v3)
{
cout << e << " ";
}
cout << endl;
}
小结:
迭代器失效,不要直接使用了,如果要继续使用,需要正确按照规则重新更新后再使用。否则会有很多坑的问题。
4.3、vector的类模板的冲突问题
编译器的特殊情况,匹配不到合适的函数重载。因为编译器采用的现有现用的原则。
cpp
void test_vector5()
{
//编译器的特殊情况,匹配不到合适的函数重载。因为编译器采用的现有现用的原则。
//vector<int> v1(10, 1);
打印
//cout << "v1:" << endl;
//print_vector(v1);
//造成编译错误的本质原因,是编译器识别到两个构造函数都能匹配。导致的冲突。
//解决办法:重载达成兼容
vector<int> v1(10, 1);
//打印
cout << "v1:" << endl;
print_vector(v1);
vector<int> v2(10u, 2);
//打印
cout << "v2:" << endl;
print_vector(v2);
vector<int> v3(10, 'a' - 94);
//打印
cout << "v3:" << endl;
print_vector(v3);
}
造成编译错误的本质原因,是编译器识别到两个构造函数都能匹配。导致的冲突。
解决办法 :第一种就是函数重载兼容参数类型,第二种就是直接告诉编译器去找那个构造函数。
解决方法一:函数重载
cpp
vector(size_t n, const T& value = T())
{
reserve(n);
for (size_t i = 0; i < n; i++)
{
push_back(value);
}
}
//函数重载,解决与类的模板成员函数的冲突
vector(int n, const T& value = T())
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
resize(n, value);
}
//迭代器区间构造
//类模板的成员函数可以是函数模板
template <class InputIterator>
vector(InputIterator first, InputIterator last)
{
while (first != last)
{
push_back(*first);
++first;
}
}
cpp
void test_vector5()
{
//造成编译错误的本质原因,是编译器识别到两个构造函数都能匹配。导致的冲突。
//解决办法:重载达成兼容
vector<int> v1(10, 1);
//打印
cout << "v1:" << endl;
print_vector(v1);
vector<int> v2(10u, 2);
//打印
cout << "v2:" << endl;
print_vector(v2);
vector<int> v3(10, 'a' - 94);
//打印
cout << "v3:" << endl;
print_vector(v3);
}
解决方法二:typename 告诉编译器或者使用auto自动识别类型
以const迭代器遍历为例:
cpp
//const迭代器遍历
//vector<int>::const_iterator cit = v.begin();
//vector<T>::const_iterator cit = v.begin();//error,无法识别前缀的类型
//解决方法1:auto自动识别匹配
//解决方法2:typename+type --- 告诉编译器是这个类型。
typename vector<T>::const_iterator cit = v.begin();
auto cit = v.begin();
while (cit != v.end())
{
cout << *cit << " ";
cit++;
}
cout << endl;
4.4、单参数的构造函数,支持隐式类型转换
单参数的构造函数,支持隐式类型转换,这个知识点经常巧妙的在一些场景适用。
1.单参数的构造函数,支持隐式类型转换
相当于initializer_list --》隐式类型转换为vector
有点类似于string中的 :string str = "abcdef";//字符常量直接隐式类型转换为string,所以本质:构造 + 拷贝构造 --》编译器优化 直接构造
2.但是对于string& str = "abcdef";就无法执行隐式类型转换,因为隐式类型转换会产生临时变量,而临时变量具有常性。
所以为了兼容需要用const关键字修饰解决,即const string& str = "abcdef";
cpp
//初始化容器
void test_vector6()
{
std::vector<int> v1 = { 1,2,3,4,5,6,7,8,9 };
//打印
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
auto x = { 1,2,3 };
cout << typeid(x).name() << endl;//class std::initializer_list<int>
cout << sizeof(x) << endl;
//说明支持把花括号形式的数据给initializer_list列表
initializer_list<int> y = { 1,2,3,4,5,6,7,8,9 };
//单参数的构造函数,支持隐式类型转换
//相当于initializer_list --》隐式类型转换为vector<int>
//有点类似于string中的:
//string str = "abcdef";//字符常量直接隐式类型转换为string
//所以本质:构造 + 拷贝构造 --》编译器优化 直接构造
//但是对于string& str = "abcdef";就无法执行隐式类型转换,因为隐式类型转换会产生临时变量,而临时变量具有常性。
//所以为了兼容需要用const关键字修饰解决,即const string& str = "abcdef";
vector<string> v;
string str1 = "12345";
const string& str = "54321";
v.push_back(str1);
v.push_back(string("2222"));
v.push_back("333333");
//initializer_list构造
vector<int> v2 = { 1,2,3,4,5,6 }; //隐式类型转换+优化
//打印
for (auto e : v2)
{
cout << e << " ";
}
cout << endl;
vector<int> v3({ 5,4,3,2,1 }); //直接构造
//打印
for (auto e : v3)
{
cout << e << " ";
}
cout << endl;
//见一见C++11的多种初始化
vector<int> v4{ 3,2,1 }; //初始化不加=
//打印
for (auto e : v4)
{
cout << e << " ";
}
cout << endl;
//见一见C++11的多种初始化
int i = 1;
int j = { 1 };
int k{ 1 };
}
4.5、vector的vector string扩容问题
解决memcpy隐藏问题
首先要清楚memcpy的底层是怎么进行的拷贝,简单地说就是:
memcpy 是用于从源内存地址的起始位置开始拷贝 n 个字节到目标内存地址的起始位置。
memcpy 的底层拷贝原理主要是基于循环或特殊的指令来逐个或批量地拷贝字节 。
现代编译器和处理器通常会对 memcpy 进行优化,以提高其性能。例如,它们可能会使用特殊的指令(如 SIMD 指令)来一次性拷贝多个字节,而不是逐个字节地拷贝。
那么简单地使用循环逐个字节拷贝可能会导致数据被错误地覆盖。
cpp
//尾插
void push_back(const T& val)
{
//扩容
if (_finish == _end_of_storage)
{
//保存size
size_t old_size = size();
size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
//开辟新空间+拷贝+释放
T* tmp = new T[newcapacity];
memcpy(tmp, _start, size() * sizeof(T));
delete[] _start;
//更新
_start = tmp;
_finish = tmp + old_size;
_end_of_storage = tmp + newcapacity;
}
*_finish = val;
++_finish;
}
void test_vector7()
{
vector<string> v;
v.push_back("111");
v.push_back("222");
v.push_back("333");
v.push_back("111");
v.push_back("222");
//打印
cout << "v:" << endl;
print_vector(v);
}
解决办法也很简单就是,不用memcpy处理呗。
复用reserve实现:本质也是不使用memcpy.
cpp
//扩容
void reserve(size_t n)
{
if (n > capacity())
{
T* tmp = new T[n];
//保存size
size_t old_size = size();
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 push_back(const T& val)
{
//扩容
if (_finish == _end_of_storage)
{
//复用1:
size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);
}
*_finish = val;
++_finish;
}
复用insert实现: 本质还是复用的reserve,也是不用memcpy。
cpp
//插入
void insert(iterator pos, const T& val)
{
assert(pos >= _start);
assert(pos <= _finish);
//扩容
if (_finish == _end_of_storage)
{
size_t len = pos - _start;
//复用
size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);
//如果扩容及时更新pos---更新到相对位置
//本质是扩容时,pos仍然指向旧空间,所以保留相对起始位置,更新pos位置。
pos = _start + len;
}
//挪动数据
iterator it = _finish - 1;
while (it >= pos)
{
*(it + 1) = *it;
--it;
}
//放入数据
*pos = val;
_finish++;
}
//尾插
void push_back(const T& val)
{
//复用2:
insert(end(), val);
}
5、vector的实现完整代码
cpp
#define _CRT_SECURE_NO_WARNINGS 1
//vector深度剖析及模拟实现
#include <iostream>
#include <vector>
#include <assert.h>
using namespace std;
namespace bit
{
template<class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
//构造函数
vector()
:_start(nullptr)
,_finish(nullptr)
,_end_of_storage(nullptr)
{}
//const T& value = T()这个value参数由于不能直接给默认为0,因为要考虑一些特殊类,比如日期类,不可能有0天,即,0月0号
//所以直接用 T()匿名对象解决这样的问题
//vector(int n, const T& value = T())
// :_start(nullptr)
// ,_finish(nullptr)
// ,_end_of_storage(nullptr)
//{
// resize(n, value);//复用resize初始化
//}
vector(size_t n, const T& value = T())
{
reserve(n);
for(size_t i = 0; i < n; i++)
{
push_back(value);
}
}
//函数重载,解决类的模板成员函数的冲突
vector(int n, const T& value = T())
:_start(nullptr)
,_finish(nullptr)
,_end_of_storage(nullptr)
{
resize(n, value);
}
//迭代器区间构造
//类模板的成员函数可以是函数模板
template <class InputIterator>
vector(InputIterator first, InputIterator last)
{
while (first != last)
{
push_back(*first);
++first;
}
}
//initializer_list构造函数
vector(initializer_list<T> il)
{
reserve(il.size());
for (auto& e : il)
{
push_back(e);
}
}
//拷贝构造
vector(const vector<T>& v)
{
//写法1:方便,但是存在一个弊端
//1.T为string时,不兼容 -- 解决加引用&和reserve预先开辟空间
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(_end_of_storage, v._end_of_storage);
}
//vector<T>& operator=(const vector<T>& v)
vector<T>& operator=(vector<T> v)
{
swap(v);
return *this;
}
//析构函数
~vector()
{
delete[] _start;
_start = _finish = _end_of_storage = nullptr;
}
//size大小
size_t size() const
{
return _finish - _start;
}
//容量
size_t capacity() const
{
return _end_of_storage - _start;
}
//尾插
void push_back(const T& val)
{
扩容
//if (_finish == _end_of_storage)
//{
// 保存size
// //size_t old_size = size();
// //size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
// 开辟新空间+拷贝+释放
// //T* tmp = new T[newcapacity];
// //memcpy(tmp, _start, size() * sizeof(T));
// //delete[] _start;
// 更新
// //_start = tmp;
// //_finish = tmp + old_size;
// //_end_of_storage = tmp + newcapacity;
// //复用1:
// size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
// reserve(newcapacity);
//}
//*_finish = val;
//++_finish;
//复用2:
insert(end(), val);
}
//尾删
void pop_back()
{
//assert(!empty());
//--_finish;
//复用
erase(--end());
}
//判空,memcpy为string类,导致了浅拷贝问题
bool empty()
{
return _start == _finish;
}
//扩容
//void reserve(size_t n)
//{
// if (n > capacity())
// {
// T* tmp = new T[n];
// //保存size
// size_t old_size = size();
// memcpy(tmp, _start, size() * sizeof(T));//有一些隐藏坑
// //memcpy为string类,导致了浅拷贝问题
// delete[] _start;
// //更新
// _start = tmp;
// _finish = tmp + old_size;
// _end_of_storage = tmp + n;
// }
//}
//扩容
void reserve(size_t n)
{
if (n > capacity())
{
T* tmp = new T[n];
//保存size
size_t old_size = size();
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,const T& val = T())//参数的匿名对象(相当于调用默认构造),决定执行那种情况,且不能赋0
{
if (n > size())
{
//检查容量
reserve(n);
//插入
while (_finish < _start + n)
{
*_finish = val;
++_finish;
}
}
else
{
//删除
_finish = _start + n;
}
}
//插入
void insert(iterator pos, const T& val)
{
assert(pos >= _start);
assert(pos <= _finish);
//扩容
if (_finish == _end_of_storage)
{
size_t len = pos - _start;
//复用
size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);
//如果扩容及时更新pos---更新到相对位置
//本质是扩容时,pos仍然指向旧空间,所以保留相对起始位置,更新pos位置。
pos = _start + len;
}
//挪动数据
iterator it = _finish - 1;
while (it >= pos)
{
*(it + 1) = *it;
--it;
}
//放入数据
*pos = val;
_finish++;
}
//删除
//void erase(iterator pos)
//{
// assert(pos >= _start);
// assert(pos <= _finish);
// //挪动覆盖
// iterator it = pos + 1;
// while (it != _finish)
// {
// *(it - 1) = *it;
// ++it;
// }
// --_finish;
//}
//解决迭代器失效问题
iterator erase(iterator pos)
{
assert(pos >= _start);
assert(pos <= _finish);
//挪动覆盖
iterator it = pos + 1;
while (it != _finish)
{
*(it - 1) = *it;
++it;
}
--_finish;
return pos;
}
//运算符重载
T& operator[](size_t pos)
{
assert(pos < size());
return _start[pos];
}
//运算符重载const对象
const T& operator[](size_t pos) const
{
assert(pos < size());
return _start[pos];
}
//begin
iterator begin()
{
return _start;
}
//end
iterator end()
{
return _finish;
}
//const_begin
const_iterator begin() const
{
return _start;
}
//const_end
const_iterator end() const
{
return _finish;
}
private:
iterator _start = nullptr;
iterator _finish = nullptr;
iterator _end_of_storage = nullptr;
};
//遍历打印
//void print_vector(const vector<int>& v)
//模板
//template<typename T>
template<class T>
void print_vector(const vector<T>& v)
{
//循环遍历
//for (size_t i = 0; i < v.size(); i++)
//{
// cout << v[i] << " ";
//}
//cout << endl;
//const迭代器遍历
//vector<int>::const_iterator cit = v.begin();
//vector<T>::const_iterator cit = v.begin();//error,无法识别前缀的类型
//解决方法1:auto自动识别匹配
//解决方法2:typename+type --- 告诉编译器这是个类型。
//typename vector<T>::const_iterator cit = v.begin();
//auto cit = v.begin();
//while (cit != v.end())
//{
// cout << *cit << " ";
// cit++;
//}
//cout << endl;
//范围for
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
//push_back + 成员变量
void test_vector()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
//打印
print_vector(v);
//迭代器遍历
vector<int>::iterator it = v.begin();
while (it != v.end())
{
cout << *it << " ";
it++;
}
cout << endl;
vector<double> v2;
v2.push_back(1.2);
v2.push_back(2.2);
v2.push_back(3.2);
//打印
print_vector(v2);
//迭代器失效问题 -- 野指针
//更新迭代器
v2.insert(v2.begin(), 0.25);
//打印
print_vector(v2);
v2.insert(v2.begin(), 0.25);
//打印
print_vector(v2);
v2.insert(v2.begin(), 0.25);
//打印
print_vector(v2);
v2.erase(v2.begin());
//打印
print_vector(v2);
v2.erase(v2.begin() + 4);
//打印
print_vector(v2);
}
//内置类型也存在构造函数与析构函数的说法,不显示用而已
//内置类型被迫升级-- 默认构造(const T& val = T())
void test_vector2()
{
int i = 1;//1
int j = int();//0
int k = int(2);//2
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
//打印
print_vector(v);
//resize
v.resize(15);
//打印
print_vector(v);
v.resize(3);
//打印
print_vector(v);
v.resize(13,1);
//打印
print_vector(v);
}
//拷贝构造+赋值
void test_vector3()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
//打印
cout << "v:" << endl;
print_vector(v);
//拷贝构造
vector<int> v1(v);
//打印
cout << "v1:" << endl;
print_vector(v1);
vector<int> v3;
v3.push_back(1);
v3.push_back(2);
v3.push_back(3);
//打印
cout << "v3:" << endl;
print_vector(v3);
vector<int> v2(v1);//拷贝构造
//打印
cout << "v2:" << endl;
print_vector(v2);
v2 = v3;//赋值构造
//打印
cout << "v2:" << endl;
print_vector(v2);
}
//拷贝构造+赋值
void test_vector4()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
//打印
cout << "v:" << endl;
print_vector(v);
vector<int> v2(v.begin(), v.end() - 2);
//打印
cout << "v2:" << endl;
print_vector(v2);
string str("abc");
vector<int> v3(str.begin(), str.end());
//打印
cout << "v3:" << endl;
print_vector(v3);
}
void test_vector10()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
//打印
cout << "v:" << endl;
print_vector(v);
vector<int>::iterator it = v.begin();
//要么就手动更新
it = v.begin() + 3;
cout << *it << endl;
}
}