目录
STL
简介:
STL(standard template libaray-标准模板库):是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架。
网上有句话说:“不懂STL,不要说你会C++”。STL是C++中的优秀作品,有了它的陪伴,许多底层的数据结构,以及算法都不需要自己重新造轮子,站在前人的肩膀上,健步如飞的快速开发。
而我们,则应该学会如何使用它,理解它,再到实现它。
vector
之前,我们进行了对string的模拟实现,string的模拟实现---by 风君子吖,对STL有了初步的了解,而vector则与string有很强的相似性,他们都是存储数据类,所以,如果你已经了解了string,相信使用vector也是易如反掌。
简介:
vector是表示可变大小数组的序列容器,而区别它与数组的地方,则是比普通数组管理更为方便,并且对自定义类型有了更好的使用管理。
vector是C++库非常重要的组成部分,其C++库对其的实现相对复杂,如果需要细致了解,应查询相应文档->https://legacy.cplusplus.com/reference/vector/vector/?kw=vector,而我们为了初步了解,则尽量简化它的实现。
1.vector的成员变量
首先,vector是一个模板类,所以它不像string只能存char类型的数据,他可以存储内置类型的数据和自定义类型的数据,这是与string第一个不同的地方。
vector的成员变量是三个迭代器,用三个迭代器来代替了string中的size和capacity,这就是与string第二个不同的地方。并且,由于vector和string一样,它是一种以顺序表存储数据的结构,所以,它的迭代器其实就是原生指针。
我们先看看vector的源代码
我们可以从源代码看出,vector的成员变量只有三种->strat 、finish、end_of_storage。
源代码十分复杂,如果要全部看懂需要较大的学习成本,对于初学者不太建议细攻源代码。
template<class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
private:
iterator _start;
iterator _finish;
iterator _end_of_storage;
};
我们从上图可以看到,start指向的是数据的起始位置,finish指向的是最后一个有效数据的后一个位置,而end_of_storage则是指向的此数组的最后的空间的后一个位置。
利用这三个迭代器,我们就可以说实现像stirng中一样的size 和 capacity.
size_t size() const
{
return _finish - _start;
}
size_t capacity() const
{
return _end_of_storage - _start;
}
2.vector的基础接口实现
<1>无参构造函数
我们先来实现一个无参的构造函数
vector()
:_start(nullptr)
,_finish(nullptr)
,_end_of_storage(nullptr)
{}
这种无参的构造函数,目前看来并没有什么意义,所以需要实现更多的接口来扩展它,暂时先这样。
<2>析构函数
~vector()
{
delete[] _start;
}
析构函数很简单,只需要调用delete释放空间即可。
<3>reserve和resize
void reserve(size_t n)
{
if (_end_of_storage - _start < n) //判断是否需要扩容
{
size_t sz = size(); //保存size的数据
T* tmp = new T[n];
if (_start) //如果_start不为空指针,则进行拷贝和delete
{
memcpy(tmp, _start, sizeof(T) * size());
delete[] _start;
}
_start = tmp;
_finish = _start + sz;
_end_of_storage = _start + n;
}
}
reserve作用:reserve 是用来给数组进行扩容时调用的函数,当插入数据时,如果空间不够,则调用它进行扩容。
注意:这种写法有隐患,如果存储的是一个二维数组,则会出现浅拷贝,我们后面再对reserve进行优化。
void resize(size_t n, T val = T()) //T()是调用匿名对象的构造函数
{
if (capacity() < n)
{
reserve(n);
}
if (size() < n)
{
while (_finish < _start + n)
{
*_finish = val;
finish++;
}
}
if (size() > n)
{
_finish = _start + n;
}
}
resize作用: resize 是用来修改该数组的有效存储数据个数的。
<4>push_back
作用:尾插
bool empty() const
{
if (_end_of_storage - _start)
{
return false;
}
return true;
}
void push_back(const T& val)
{
if (_finish == _end_of_storage)
{
reserve(empty() ? 4 : capacity() * 2); //如果数据为空,则给初始空间为4
}
*_finish = val;
finish++;
}
尾插的实现很简单,先检查容量是否已满,已满则调用reserve进行扩容。
<*>迭代器的begin和end接口
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin() const
{
return _start;
}
const_iterator end() const
{
return _finish;
}
作用:实现了begin和end接口后,我们就可以使用范围for来进行对vector的遍历。
void vector_test1()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(4);
v1.push_back(3);
v1.push_back(5);
v1.push_back(6);
for (auto& i : v1)
{
std::cout << i << " ";
}
}
<5>重载[]
T operator[](size_t pos)
{
assert(pos < size());
return _start[pos];
}
const T operator[](size_t pos) const //const版本
{
assert(pos < size());
return _start[pos];
}
作用:重载[]后,访问数据会很方便。
注意:最好用assert断言一下pos,防止非法访问导致程序崩溃。
<6>拷贝构造函数(传统写法)
//传统写法
vector(const vector<T>& v)
: _start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
if (!v.empty()) //如果v为空,则不处理
{
_start = new T[v.capacity()];
memcpy(_start, v._start, sizeof(T) * v.size());
_finish = _start + v.size();
_end_of_storage = _start + v.capacity();
}
}
如果看过我模拟实现string的文章,就知道这种写法叫做现代写法,通过一个tmp去调用构造函数来
<7>迭代器区间构造函数(重点)
文档中还有一种构造函数,称之为迭代器区间构造函数
它通过将迭代器的一个区间来实现构造,将区间内的数据作为初始化数据。
//迭代器区间构造函数
template<class InputIterator>
vector(InputIterator first, InputIterator last)
: _start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
while (first != last)
{
push_back(*first);
first++;
}
}
而它最大的特点:他是一个模版,这意味着你不止可以传T的类型,还可以传其他类型,例如,T为int,你传一个char迭代器区间,他也是可以支持的。
而它输出的结果为什么是104,101,108,108,111,细心的朋友会发现这些是它的ascll码值。
先解引用再创建一个临时变量进行隐私类型转换成int类型,就变成了ascll码值。
<8>赋值重载
vector<T>& operator=(const vector<T>& v) //赋值重载
{
if (!v.empty()) //如果v为空,则不处理
{
_start = new T[v.capacity()];
memcpy(_start, v._start, sizeof(T) * v.size());
_finish = _start + v.size();
_end_of_storage = _start + v.capacity();
}
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);
}
vector(const vector<T>& v)
: _start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
vector<T> tmp(v.begin(),v.end());
Swap(tmp);
}
vector<T>& operator=(vector<T> v) //赋值重载
{
Swap(v);
return *this;
}
现代写法简洁,所以比较提倡这种写法。
<9>insert
const iterator& insert(iterator pos, const T& val)
{
assert(pos <= _finish);
if (_finish == _end_of_storage)
{
size_t len = pos - _start; //记录pos的相对位置,防止迭代器失效。
reserve(empty() ? 4 : capacity() * 2); //如果数据为空,则给初始空间为4
pos = _start + len; //定位新pos位置
}
for (iterator end = _finish - 1; end >= pos; --end)
{
end[1] = end[0];
}
*pos = val;
_finish += 1;
return pos;
}
void insert(iterator pos, size_t n, const T& val)
{
assert(pos <= _finish);
if (size()+n > capacity())
{
size_t len = pos - _start; //记录pos的相对位置,防止迭代器失效。
reserve(size() + n);
pos = _start + len; //定位新pos位置
}
for (iterator end = _finish - 1; end >= pos; --end)
{
end[n] = end[0];
}
for (int i = 0; i < n; i++)
{
pos[i] = val;
}
_finish += n;
}
作用:在pos位置插入一个/一串数据
注意:要小心迭代器因为扩容调用reserve而失效。
<10>erase
iterator erase(iterator pos)
{
assert(pos < _finish);
for (int i = 0 ; i < _finish - pos -1 ; i++)
{
pos[i] = pos[i+1];
}
--_finish;
return pos;
}
iterator erase(iterator first, iterator last)
{
assert(last <= _finish);
assert(first <= _finish);
size_t len = last - first;
for (int i = 0; i < _finish - last; i++)
{
first[i] = last[i];
}
_finish -= len;
return first;
}
注意:最好用assert断言一下pos,防止非法访问导致程序崩溃。
<*> 扩展与优化(重点)
以杨辉三角为例,如果要求你写一个以vector<int>为模版的vector<vector<int>>的二维数组,我们上面所写的代码是否会有问题?
答案是有的,如果存储的是一个指针数组,我们的代码会有几个浅拷贝问题。
reserve拷贝的时候,赋值拷贝的时候,拷贝构造的时候。
应修改成
//传统写法
vector(const vector<T>& v)
: _start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
if (!v.empty()) //如果v为空,则不处理
{
_start = new T[v.capacity()];
//memcpy(_start, v._start, sizeof(T) * v.size()); //memcpy也是浅拷贝
for (int i = 0; i < size(); i++)
{
_start[i] = v._statr[i] //调用赋值来完成深拷贝
}
_finish = _start + v.size();
_end_of_storage = _start + v.capacity();
}
}
vector<T>& operator=(const vector<T>& v) //赋值重载
{
if (!v.empty()) //如果v为空,则不处理
{
_start = new T[v.capacity()];
//memcpy(_start, v._start, sizeof(T) * v.size()); //memcpy也是浅拷贝
for (int i = 0; i < size(); i++)
{
_start[i] = v._statr[i] //调用赋值来完成深拷贝
}
_finish = _start + v.size();
_end_of_storage = _start + v.capacity();
}
return *this;
}
void reserve(size_t n)
{
if (capacity() < n) //判断是否需要扩容
{
size_t sz = size(); //保存size的数据
T* tmp = new T[n];
if (_start) //如果_start不为空指针,则进行拷贝和delete
{
//memcpy(tmp, _start, sizeof(T)* size()); //memcpy也是浅拷贝
for (size_t i = 0; i < sz; i++)
{
tmp[i] = _start[i]; //调用赋值来完成深拷贝
}
delete[] _start;
}
_start = tmp;
_finish = _start + sz;
_end_of_storage = _start + n;
}
}
让我们来实现杨辉三角来验证我们上面的修改是否正确
class Solution {
public:
vector<vector<int>> generate(int numRows) {
vector<vector<int>> vv;
vv.resize(numRows);
for (int i = 0; i < vv.size(); i++)
{
vv[i].resize(i + 1, 0);
vv[i].front() = vv[i].back() = 1;
}
for (int i = 0; i < vv.size(); i++)
{
for (int k = 0; k < vv[i].size(); k++)
{
if (vv[i][k] == 0)
{
vv[i][k] = vv[i - 1][k] + vv[i - 1][k - 1];
}
}
}
return vv;
}
};
void vector_test5()
{
vector<vector<int>> vv = Solution().generate(5);
for (int i = 0; i < vv.size(); i++)
{
for (int k = 0; k < vv[i].size(); k++)
{
cout << vv[i][k] << " ";
}
cout << endl;
}
}
结果正确!