目录
vector的实现和string其实比较类似,了解vector的功能可以参考C++函数网址和C++中vector类的使用,对比string可以参考C++中string类的模拟实现。
1.打印函数的模板
1.1专门打印vector类对象的模板
template<class T>
void print_vector(const vector<T>& v)
{
for (auto& e : v)
{
cout << e << " ";
}
cout << endl;
//用::从类中取对象能取到的有静态成员变量和typedef后的类型
//从没有实例化的类模板里面取东西,编译器不能区分这里的const_iterator是类型还是静态成员变量
//typename vector<T>::const_iterator it = v.begin();
//auto it = v.begin();
//while (it != v.end())
//{
// cout << *it << " ";
// it++;
//}
//cout << endl;
}
这里给模板里面传入一个vector的类对象,函数中第一种方式是通过for范围进行遍历打印,第二种方式是通过迭代器的方式进行遍历打印。需要注意的是,这里从没有实例化的类模板里面取东西,可能取到的是静态变量或类型,所以为了区分取到的是类型,这里在vector<T>::const_iterator之前加上typename来表示是从类里面取类型。
1.2适用于所有支持++的迭代器打印函数
template<class Container>
void print_container(const Container& v)
{
for (auto& e : v)
{
cout << e << " ";
}
cout << endl;
//typename vector<T>::const_iterator it = v.begin();
//auto it = v.begin();
//while (it != v.end())
//{
// cout << *it << " ";
// it++;
//}
//cout << endl;
}
这里把传入的模板当作类型参数,即可以用这里函数遍历打印任意支持++操作的容器。
2.vector类的结构
vector里面是通过三个指针来维护一个数组的
iterator _start = nullptr;
iterator _finish = nullptr;
iterator _end_of_storage = nullptr;
_start指向数组的开始位置,_finish指向有效数据的下一个位置,_end_of_storage指向的是数组容量的结束位置。
3.默认成员函数
这里的构造函数及赋值运算符重载的实现,大量使用的复用的方式,而不是传统的实现方式。
3.1构造函数
3.1.1默认构造函数
vector在这里是使用3个指针进行维护的,3个指针也给了nullptr的初始值,在初始化列表的时候就会进行初始化定义,所有这里的默认构造函数可以不写。或者使用以下两种写法也行。
//vector()
//{}
//C++11前置生成默认构造
vector() = default;
vector(const vector<T>& v)
{
reserve(v.size());
for (auto& e : v)
{
push_back(e);
}
}
3.1.2拷贝构造函数
使用reserve()函数先开辟一个与v一样大的空间,然后通过push_back()函数对v进行遍历拷贝到this中。
vector(const vector<T>& v)
{
//开辟与v一样大的存储空间
reserve(v.size()); //这里复用后面实现的reserve()函数进行空间的开辟
for (auto& e : v)
{
push_back(e);
}
}
3.1.3其他构造函数
3.1.3.1通过迭代器区间进行构造
在下述代码中将这种构造函数写成了一种函数模板的形式,是为了支持通过其他结构的迭代器对该迭代器进行初始化构造。使用该构造函数时,传入支持++操作的数据结构迭代器则能构造vector对象。
注:虽然可以通过其他数据结构的迭代器对vector对象进行初始化,但是两个容器存储的数据类型需要是相同数据类型,不然会出错。
//类模板的成员函数,还可以继续是函数模板
//任意容器迭代器初始化,要求类型是匹配的
template <class InputIterator>
vector(InputIterator first, InputIterator last)
{
while (first != last) //vector这里可以写成小于,因为是连续的空间,但是像链表的话不是连续的空间,为了支持所有结构的迭代器,这里写成!=
{
push_back(*first);
++first;
}
}
3.1.3.2fill构造函数
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);
}
}
为什么要重载成两个函数的原因如下:
当只有第一个重载时,通过下列的方式进行构造会匹配到使用迭代器区间进行构造,则会报错。
原因是因为输入的10,1默认是int类型,如匹配fill构造函数的话,int到size_t有一步类型转换,对于编译器来说这样的转换可能会发生错误,所有匹配到迭代器区间进行构造,因为传入模板的是两个int类型,这样对于fill构造函数来说是更好的匹配上了。但是由于匹配上了迭代器区间构造,在该函数中迭代器变成了int类型,不能进行解引用操作,所有会报错。
void test_vector8()
{
//这样写会匹配到迭代器区间初始化,因为迭代器区间初始化实例化出来形参是两个int类型,更加匹配
vector<int> v6(10, 1);
print_container(v6);
}
所有写两个函数构成重载是一种解决的方式,另一种解决方式是使用时写成如下代码,声明传入的是size_t类型的参数。
vector<int> v6(10u, 1); //将形参第一个加上u,更加匹配size_t类型
print_container(v6);
3.2赋值运算符重载
1.传统写法
vector<T>& operator=(const vector<T>& v)
{
if (this != &v)
{
clear();
reserve(v.size());
for (auto& e : v)
{
push_back(e);
}
}
return *this;
}
2.现代写法
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_storage, v._end_of_storage);
}
//因为是传值传参,所有交换后形参v改变,但是不影响外面的实参
//vector<T>& operator=(vector<T> v)
//类里面可以用类名替代类型 vector是类的名字, vector<T>是一种数据类型
vector& operator=(vector v)
{
swap(v);
return *this;
}
3.3析构函数
vector的结构是通过三个指针进行维护的,所以析构的时候释放申请的空间,然后将三个指针置为nullptr即可。 即使是里面存储的是自定义类型,需要申请空间的自定义类型,在析构的时候也会调用自身的析构函数。
~vector()
{
delete[] _start;
_start = nullptr;
_finish = nullptr;
_end_of_storage = nullptr;
}
4. vector类的迭代器(Iterators)
vector底层是通过数组结构实现的,所以它的迭代器和string一样,使用原生指针即可。
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;
}
5. vector类的空间操作(Capacity)
5.1size()
size_t size() const
{
return _finish - _start;
}
5.2capacity()
size_t capacity() const
{
return _end_of_storage - _start;
}
5.3empty()
bool empty() const
{
return _start == _finish;
}
5.4resize()
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;
}
}
}
分为两种情况:
1.当n < size():保留数组中的前n个数据即可。
2.当n > size(): 通过reserve()函数将空间扩大到n个,然后从_finish开始添加新插入的数据。
5.5reserve()
void reserve(size_t n)
{
//这里n大于原来的空间才需要扩容
if (n > capacity())
{
size_t old_size = size(); //先用原来的finish和start算出size,不然后面更新finish的时候会造成_start已经变为了更新后的值了
//开新空间--当存储的是自定义类型是,开辟的空间中就行调用自定义类型的默认构造进行初始化
T* tmp = new T[n];
//拷贝数据到新的vector中
//memcpy(tmp, _start, size() * sizeof(T)); 这里如果vector里面是一个需要深拷贝的类型,就会出错
for (size_t i = 0; i < old_size; i++)
{
tmp[i] = _start[i];
}
//释放旧空间,这时候_start, _finish, _end_of_storage都为空指针
delete[] _start;
//更新结构中对应的值
_start = tmp;
//这里如果没有记录扩容之前的size,使用size()是扩容之后的size了_finish = _start + size() = _start + _finish - _start,相当于_finish还是为0(空指针)
//_finish = _start + size();
_finish = tmp + old_size;
_end_of_storage = tmp + n;
}
}
5.5.1使用memcpy()进行拷贝数据造成的问题
这里如果通过memcpy进行拷贝的时候进行的也是浅拷贝,当存储的数据是内置类型的时候是没有问题的,但是如果存储的数据为自定义类型(比如string这种涉及到资源管理的类型)时,就会出错。
vecotor实现的push_back()在扩容的时候也是复用的reserve()函数,这里以vector里面存储string类型的元素为例子,当原本vector中存储了4个string对象,插入第五个时,vector要进行扩容到8个,然后将数据拷贝到新空间中,但是当使用memcpy进行数据的拷贝时,拷贝的是vector原空间中指向string对象的指针_str,然后进行到delete[]这一步的时候,vector对象中的string对象会调用自己的析构函数释放空间,此时,新vector中的string对象里面的_str被置为了空指针,所以拷贝到vector的4个string对象都已经成为了随机值。
解决方式就是将vector对象中的string对象一个一个通过string对象的赋值运算重载进行拷贝。string中的赋值运算就是释放原空间,开辟新空间与新的string对象一样大的空间,进行拷贝。
for (size_t i = 0; i < old_size; i++)
{
tmp[i] = _start[i];
}
string的赋值运算:
string operator=(const string& s)
{
//对已有的对象进行赋值运算
if (this != &s) //如果自己给自己赋值,则直接返回*this
{
//1.先释放原数组指针指向的空间
delete[] _str;
//重新申请一个与s._str指向空间相同的空间
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
6.元素访问(Element access)
6.1operator[]
//为了支持使用下标遍历
T& operator[](size_t i)
{
assert(i < size());
return _start[i];
}
const T& operator[](size_t i) const
{
assert(i < size());
return _start[i];
}
其他接口很容易实现,这里就不过多的进行描述了。
7.vector类的修改操作(Modifiers)
7.1push_back()
void push_back(const T& x)
{
if (_finish == _end_of_storage)
{
reserve(capacity() == 0 ? 4 : 2 * capacity());
}
*_finish = x;
++_finish;
}
7.2pop_back()
void pop_back()
{
assert(!empty());
--_finish;
}
7.3insert()
7.3.1第一种迭代器失效
下列这段代码有迭代器失效的问题,vector的迭代器是原始指针,当插入数据要进行扩容的时候,pos还是指向原空间的指针,但是现在vector已经是在一段新空间中了,旧的空间已经释放了,此时的pos就失效了,变成了野指针。
void insert(iterator pos, const T& x)
{
assert(pos >= _start && pos <= _finish);
//扩容
if (_finish == _end_of_storage)
{
reserve(capacity() == 0 ? 4 : 2 * capacity());
}
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
--end;
}
*pos = x;
++_finish;
}
解决方法:
记录扩容之前pos和_start的相对位置,扩容之后_start进行了更新,pos则用更新后的_start加上相对位置即可。
void insert(iterator pos, const T& x)
{
assert(pos >= _start && pos <= _finish);
//扩容
if (_finish == _end_of_storage)
{
size_t len = pos - _start;
reserve(capacity() == 0 ? 4 : 2 * capacity());
pos = _start + len;
}
//挪动数据
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
--end;
}
*pos = x;
++_finish;
}
7.3.2第二种迭代器失效
解释写在了注释中了。
void test_vector2()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
int x;
cin >> x;
//所有迭代器都是左闭右开,不会到v1.end()
auto pos = find(v1.begin(), v1.end(), x);
if (pos != v1.end())
{
v1.insert(pos, 40);
//*(pos) *= 10;
//本来想表达的意思是在x之前插入40,然后再让x*10,
//插入之后,此时的pos指向的是40
//这也是一种迭代器失效
//如果遇到扩容时,pos指向旧空间,也不是指向的40了
//所以,insert以后pos就失效了,不要访问
//insert内部进行修正,但是在外部进行访问pos,pos没有被修正
//形参的改变不影响实参
//vs下的pos这时强制检查,不能访问
//g++不进行强制检查
}
print_container(v1);
}
7.4erase()
这里的erase()函数进行删除之后,pos就自动指向了被删除元素的下一个元素。
void erase(iterator pos)
{
assert(pos >= _start && pos < _finish);
iterator it = pos + 1;
while (it != end())
{
*(it - 1) = *it;
++it;
}
--_finish;
}
7.5swap()
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_storage, v._end_of_storage);
}
7.6clear()
void clear()
{
_finish = _start;
}
8.参考代码
这里所以函数的实现和测试代码我也一并写在了vector.h里面了。
8.1vector.h
#pragma once
#include<assert.h>
#include<iostream>
#include<vector>
#include<algorithm>
#include<list>
#include<string>
using namespace std;
namespace XiaoC
{
template<class T>
class vector
{
public:
//vector()
//{}
//C++11前置生成默认构造
vector() = default;
//copy
vector(const vector<T>& v)
{
reserve(v.size());
for (auto& e : v)
{
push_back(e);
}
}
//类模板的成员函数,还可以继续是函数模板
//任意容器迭代器初始化,要求类型是匹配的
template <class InputIterator>
vector(InputIterator first, InputIterator last)
{
while (first != last) //vector这里可以写成小于,因为是连续的空间,但是像链表的话不是连续的空间,为了支持所有结构的迭代器,这里写成!=
{
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);
}
}
vector(int n, const T& val = T())
{
reserve(n);
for (int i = 0; i < n; i++)
{
push_back(val);
}
}
void clear()
{
_finish = _start;
}
//传统写法
//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);
}
//因为是传值传参,所有交换后形参v改变,但是不影响外面的实参
//vector<T>& operator=(vector<T> v)
//类里面可以用类名替代类型 vector是类的名字, vector<T>是一种数据类型
vector& operator=(vector v)
{
swap(v);
return *this;
}
~vector()
{
delete[] _start;
_start = nullptr;
_finish = nullptr;
_end_of_storage = nullptr;
}
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;
}
void reserve(size_t n)
{
if (n > capacity())
{
size_t old_size = size(); //先用原来的finish和start算出size,不然后面更新finish的时候会造成_start已经变为了更新后的值了
//开新空间
T* tmp = new T[n];
//拷贝数据到新的vector中
//memcpy(tmp, _start, size() * sizeof(T)); 这里如果vector里面是一个需要深拷贝的类型,就会出错
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;
}
}
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 : 2 * capacity());
}
*_finish = x;
++_finish;
}
void pop_back()
{
assert(!empty());
--_finish;
}
//有效迭代器pos不可能为0
void insert(iterator pos, const T& x)
{
assert(pos >= _start && pos <= _finish);
//扩容
if (_finish == _end_of_storage)
{
size_t len = pos - _start;
reserve(capacity() == 0 ? 4 : 2 * capacity());
pos = _start + len;
}
iterator end = _finish - 1;
//这里如果遇到扩容的情况,则_start指向的空间已经不是原空间了,是新开的空间,而pos指向的还是原来空间中的pos,所以会发生迭代器失效
//这种迭代器失效类似与野指针
//解决方案:在扩容前记录pos与_start的相对位置,扩容之后更新pos的值
//挪动数据
while (end >= pos)
{
*(end + 1) = *end;
--end;
}
*pos = x;
++_finish;
}
void erase(iterator pos)
{
assert(pos >= _start && pos < _finish);
iterator it = pos + 1;
while (it != end())
{
*(it - 1) = *it;
++it;
}
--_finish;
}
// 自定义给缺省值的方式,调用默认构造给缺省值
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;
}
}
}
//为了支持使用下标遍历
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;
};
//const修饰的参数要用const迭代器
//void print_vecotr(const vector<int>& v)
//{
// for (auto& e : v)
// {
// cout << e << " ";
// }
// cout << endl;
// vector<int>::const_iterator it = v.begin();
// while (it != v.end())
// {
// cout << *it << " ";
// it++;
// }
// cout << endl;
//}
//将打印函数改造成模板
template<class T>
void print_vector(const vector<T>& v)
{
for (auto& e : v)
{
cout << e << " ";
}
cout << endl;
//用::从类中取对象能取到的有静态成员变量和typedef后的类型
//从没有实例化的类模板里面取东西,编译器不能区分这里的const_iterator是类型还是静态成员变量
//typename vector<T>::const_iterator it = v.begin();
//auto it = v.begin();
//while (it != v.end())
//{
// cout << *it << " ";
// it++;
//}
//cout << endl;
}
template<class Container>
void print_container(const Container& v)
{
for (auto& e : v)
{
cout << e << " ";
}
cout << endl;
//typename vector<T>::const_iterator it = v.begin();
//auto it = v.begin();
//while (it != v.end())
//{
// cout << *it << " ";
// it++;
//}
//cout << endl;
}
void test_vector1()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
for (size_t i = 0; i < v1.size(); i++)
{
cout << v1[i] << " ";
}
cout << endl;
print_container(v1);
}
void test_vector2()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
//print_vecotr(v1);
//v1.insert(v1.begin() + 2, 20);
print_container(v1);
int x;
cin >> x;
//所以迭代器都是左闭右开,不会到v1.end()
auto pos = find(v1.begin(), v1.end(), x);
if (pos != v1.end())
{
v1.insert(pos, 40);
//*(pos) *= 10;
//本来想表达的意思是在x之前插入40,然后再让x*10,
//插入之后,此时的pos指向的是40
//这也是一种迭代器失效
//如果遇到扩容时,pos指向旧空间,也不是指向的40了
//所以,insert以后pos就失效了,不要访问
//insert内部进行修正,但是在外部进行访问pos,pos没有被修正
//形参的改变不影响实参
//vs下的pos这时强制检查,不能访问
//g++不进行强制检查
}
print_container(v1);
}
void test_vector3()
{
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);
print_container(v1);
//删除所有的偶数
auto it = v1.begin();
while (it != v1.end())
{
if (*it % 2 == 0)
{
v1.erase(it);
}
else
{
it++;
}
}
print_container(v1);
}
void test_vector4()
{
//内置类型的默认构造
int i = int();
int j = int(1);
int k(2);
vector<int> v;
v.resize(10, 1);
v.reserve(20);
print_container(v);
cout << v.size() << endl;
cout << v.capacity() << endl;
v.resize(15, 2);
print_container(v);
v.resize(25, 3);
print_container(v);
v.resize(5);
print_container(v);
}
void test_vector5()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
print_container(v1);
vector<int> v2 = v1;
print_container(v2);
vector<int> v3;
v3.push_back(10);
v3.push_back(20);
v3.push_back(30);
v1 = v3;
print_container(v1);
}
void test_vector6()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
print_container(v1);
vector<int> v2(v1.begin(), v1.begin() + 3);
print_container(v1);
print_container(v2);
list<int> lt;
lt.push_back(10);
lt.push_back(10);
lt.push_back(10);
lt.push_back(10);
lt.push_back(10);
vector<int> v3(lt.begin(), lt.end());
print_container(lt);
print_container(v3);
vector<string> v4(10, "111111");
print_container(v4);
vector<int> v5(10);
print_container(v5);
//这样写会匹配到迭代器区间初始化,因为迭代器区间初始化实例化出来形参是两个int类型,更加匹配
//vector<int> v6(10, 1);
//print_container(v6);
//解决方案1
//vector<int> v6(10u, 1); //将形参第一个加上u,更加匹配size_t类型
//print_container(v6);
//解决方案2,多重载几个构造
vector<int> v7(10, 1);
print_container(v7);
}
void test_vector7()
{
vector<string> v;
v.push_back("11111111111111111111111");
v.push_back("11111111111111111111111");
v.push_back("11111111111111111111111");
v.push_back("11111111111111111111111");
print_container(v);
v.push_back("11111111111111111111111");
print_container(v);
//这里的原因是因为扩容中用memcpy进行拷贝,是浅拷贝
//当前vector存的是string,string中的_str指向空间,memcpy拷贝的_str这个指针
//所有,当扩容之后,之前的空间释放了,_str就是野指针,打印出来就是随机值
}
}
8.2test.cpp
#define _CRT_SECURE_NO_WARNINGS
#include "vector.h"
int main()
{
//XiaoC::test_vector1();
//XiaoC::test_vector2();
//XiaoC::test_vector3();
//XiaoC::test_vector4();
//XiaoC::test_vector5();
//XiaoC::test_vector6();
//XiaoC::test_vector7();
return 0;
}