一、经典的vector类问题
C++ STL中的vector就类似于C语言当中的数组,但是vector又拥有很多数组没有的接口,使用起来更加方便。
相比于STL中的string,vector可以定义不同的数据类型。
1.1 前期准备
迭代器的本质可以暂时看作是指针,模拟实现vector,需要定义三个指针:指向起始位置_start,指向最后一个有效元素的下一个位置_finish,指向容器最后一个位置_end_of_storage
namespace zl { //自定义命名空间
template<class T> //定义模板类型
class vector{
public:
typedef T* iterator; //迭代器 等价于 T 类型指针
private:
iterator _start; //空间起始位置
iterator _finish; //最后一个有效元素的下一个位置
iterator _end_of_storage; //最后一个容量空间位置
};
}
二、vector的默认成员函数
2.1 构造函数
2.1.1 无参构造
构造一个空vector,size和capacity为0,将_start,_finish,_end_of_storage都置为空指针即可
vector()
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{}
2.1.2 构造具有n个对象值为val的容器(数据类型为模板类型T)
vector(size_t n, const T& val = T())
{
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
reserve(n);
for (size_t i = 0; i < n; ++i)
{
push_back(val);
}
}
vector(int n, const T& val = T())
{
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
reserve(n);
for (int i = 0; i < n; ++i)
{
push_back(val);
}
}
2.1.3 拷贝构造
传统写法
(1)新开辟一块和v同样容量的空间,更新_startt,_finish,_end_of_storage
(2)将v中的数据拷贝到新开辟的空间中
vector(const vector<T>& v)
{
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
//reserve(v.capacity());
_start = new T[v.capacity()];
//memcpy(_start, v._start, sizeof(T) * v.size());
for (size_t i = 0; i < v.size(); ++i)
{
_start[i] = v._start[i];
}
_finish = _start + v.size();
_end_of_storage = _start + v.capacity();
}
现代写法
使用范围for进行遍历,变量e是v中元素的拷贝,如果v中元素是需要深拷贝的自定义类型,会调用拷贝构造函数构造e,从而使e和v中元素所指向的空间不一样(auto& e:v也可以,因为push_back在实现的时候还会调用深拷贝类型的赋值运算符重载)
vector(const vector<T>& v)
{
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
reserve(v.capacity());
for (auto e : v)
{
push_back(e);
}
}
新写法
vector(const vector<T>& v)
{
vector<T> tmp(v.begin(), v.end()); //定义临时对象--调用迭代器构造方法
swap(tmp); //进行资源交换
}
2.2 swap(operator=需要用)
调用全局的swap,交换指针,其为内置类型,无需深拷贝,代价相比于直接调用库里面函数交换比较小
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_storage, v._end_of_storage);
}
2.3 复制重载operator=
传统写法
(1)释放原空间,新开一块容量和v一样大的空间,更新_start,_finish,_end_of_storage
(2)将v中的数据拷贝到新空间中
vector<T>& operator=(const vector<T>& v)
{
if (this != &v)
{
delete[]_start; // 释放原空间
_start = new T[v.capacity()]; // 开辟新空间
for (size_t i = 0; i < v.size(); i++) // 拷贝数据
{
_start[i] = v[i];
}
_finish = _start + v.size(); // 更新_finish
_endofstorage = _start + v.capacity(); // 更新_capacity
}
return *this;
}
现代写法
(1)调用拷贝构造函数生成tmp对象
(2)分别交换tmp和this的_start,_finish,_end_of_storage
vector<T>& operator=(const vector<T>& v)
{
if (this != &v) // 防止自己给自己赋值
{
vector<T> tmp(v); // 拷贝构造tmp对象
swap(tmp); // 交换_start,_finish, _endofstorage
}
return *this;
}
vector<T>& operator=(vector<T> v) // 拷贝构造v对象
{
swap(v); // 交换_start,_finish, _endofstorage
return *this;
}
2.4 析构函数
将空间释放掉,_start,_finish,_end_of_storage置为空指针
~vector()
{
delete[] _start;
_start = _finish = _end_of_storage = nullptr;
}
三、vector的三种遍历方式
3.1 size和capacity
size返回容器中有效数据的个数,用_finish-_start即可
size_t size() const
{
return _finish - _start;
}
capacity返回容器的容量,用_end_of_storage-_start即可
size_t capacity() const
{
return _end_of_storage - _start;
}
3.2 operator[]遍历
返回底层数组对应位置的引用即可,需要用const和非const两个版本
T& operator[](size_t pos)
{
assert(pos < size());
return _start[pos];
}
const T& operator[](size_t pos) const
{
assert(pos < size());
return _start[pos];
}
3.3 迭代器iterator遍历和范围for
迭代器也需要写const和非const两个版本,且一个容器只要支持迭代器,就可以用范围for操作,因为范围for本质也是迭代器
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin() const
{
return _start;
}
const_iterator end() const
{
return _finish;
}
四、vector相关的增容和删除
4.1 reserve
(1)当n大于对象当前的capacity时,将capacity扩大到n或大于n
(2)当n小于对象当前的capacity时,什么也不做
增容前就需要算出size的大小
因为增容涉及新和旧空间的问题,比如_finish=tmp+size(),而size的求法是用_finish-_start,但_start已指向新空间,可_finish还是指向旧空间,两块不连续的空间相减就是随机值了,故要在增容之前就把大小算出来,不然增容之后的大小就是随机值了。
reserve需要更新_finish
因为_finish是指针,如果一个对象它上来就reserve保存一定容量,然后直接扩容,但是插入数据肯定涉及*_finish操作,若不更新_finish,_finish初始化为nullptr就非法访问内存了,故reserve中也要更改_finish。
不能使用memcpy拷贝数据
因为增容中使用了memcpy,memcpy导致了问题的出现,因为memcpy是按字节拷贝的,它本质上造成了浅拷贝问题,memcpy使新旧空间都指向了一份数据,旧空间释放后,它指向的对应数据也被释放,而新空间指针还是指向这份旧空间,这就造成了非法访问,所以新空间与旧空间不应该指向同一份数据,应该不用memcopy,写成深拷贝(将旧空间的每个值赋值给新空间对应的每个值就好了,就很实现深拷贝了,指向不同的数据)
void reserve(size_t n)
{
if (n > capacity())
{
size_t sz = size();
T* tmp = new T[n];
if (_start)
{
//memcpy(tmp, _start, sizeof(T) * size());
for (size_t i = 0; i < sz; ++i)
{
tmp[i] = _start[i];
}
delete[] _start;
}
_start = tmp;
_finish = tmp + sz;
_end_of_storage = tmp + n;
}
}
4.2 resize
(1)当n<size时,直接将_finish=_start+n(将有效数据长度缩小)即可
(2)当size<n<=capacity时,将有效数据的长度增加到n,增加出来的有效数据内容是val
(3)当n>capacity时,先调用上面的reserve函数进行增容,再将有效数据的长度增加到n,增加出来的有效数据内容是val
void resize(size_t n,T val=T())
{
if (n < size())
{
//删除数据
_finish = _start + n;
}
else
{
if (n > capacity())
reserve(n);
while (_finish != _start + n)
{
*_finish = val;
++_finish;
}
}
}
4.3 push_back
尾插数据,首先要检查是否已满,已满则进行增容,增容后尾插即可
void push_back(const T& x)
{
if (_finish == _end_of_storage)
{
reserve(capacity() == 0 ? 4 : capacity() * 2);
}
*_finish = x;
++_finish;
}
4.4 pop_back
尾插时,首先要判断容器是否为空,若为空,则断言报错,不为空,_finish–即可
void pop_back()
{
assert(!empty());
--_finish;
}
4.5 insert
(1)容量不够,先增容,增容前先记录pos-_start的值,否则增容之后,pos还指向原来已经被释放的空间
(2)将pos位置往后的数据往后挪一位,在pos位置插入值val
为什么需要提前计算出len
有迭代器失效的问题,因为pos是迭代器类型,扩容前指向旧空间的某一位置,而reserve调用后会扩容,而我们是扩容完才插入数据的,此时pos无效,因为旧空间已经释放了,它这个迭代器还指向那里就失效了,故我们要更新pos位置,使它指向新空间,所以要先算len,即原pos的位置
//迭代器失效:扩容引起,野指针问题
iterator insert(iterator pos, const T& val)
{
assert(pos >= _start);
assert(pos <= _finish);
if (_finish == _end_of_storage)
{
size_t len = pos - _start;
reserve(capacity() == 0 ? 4 : capacity() * 2);
//扩容后更新pos,解决pos失效的问题
pos = _start + len;
}
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
--end;
}
*pos = val;
++_finish;
return pos;
}
4.6 erase
erase是返回被删除数据的下一个位置,当要被删除的数据被删除了,erase原地不动的话,就已自动指向了下一位置,因为那个数据被删除了文章来源:https://www.toymoban.com/news/detail-759678.html
iterator erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
iterator start = pos + 1;
while (start != _finish)
{
*(start - 1) = *start;
++start;
}
--_finish;
return pos;
}
4.7 empty
判断是否为空文章来源地址https://www.toymoban.com/news/detail-759678.html
bool empty()
{
return _start == _finish;
}
五、完整代码
5.1 vector.h
#pragma once
#include <assert.h>
namespace zl
{
template<class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
vector()
/*:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)*/
{}
vector(size_t n, const T& val = T())
{
/*:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)*/
reserve(n);
for (size_t i = 0; i < n; ++i)
{
push_back(val);
}
}
vector(int n, const T& val = T())
{
/*:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)*/
reserve(n);
for (int i = 0; i < n; ++i)
{
push_back(val);
}
}
/*vector(const vector<T>& v)
{
reserve(v.capacity());
for (auto e : v)
{
push_back(e);
}
}*/
//vector(const vector<T>& v)
//{
// //reserve(v.capacity());
// _start = new T[v.capacity()];
// //memcpy(_start, v._start, sizeof(T) * v.size());
// for (size_t i = 0; i < v.size(); ++i)
// {
// _start[i] = v._start[i];
// }
// _finish = _start + v.size();
// _end_of_storage = _start + v.capacity();
//}
vector(const vector<T>& v)
{
vector<T> tmp(v.begin(), v.end());
swap(tmp);
}
//void swap(vector& v)
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_storage, v._end_of_storage);
}
//v1=v2
//vector& operator=(vector v)
vector<T>& operator=(vector<T> v)
{
swap(v);
return *this;
}
//[first,last)
template<class InputIterator>
vector(InputIterator first, InputIterator last)
{
while (first != last)
{
push_back(*first);
++first;
}
}
~vector()
{
delete[] _start;
_start = _finish = _end_of_storage = nullptr;
}
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin() const
{
return _start;
}
const_iterator end() const
{
return _finish;
}
void resize(size_t n,T val=T())
{
if (n < size())
{
//删除数据
_finish = _start + n;
}
else
{
if (n > capacity())
reserve(n);
while (_finish != _start + n)
{
*_finish = val;
++_finish;
}
}
}
void reserve(size_t n)
{
if (n > capacity())
{
size_t sz = size();
T* tmp = new T[n];
if (_start)
{
//memcpy(tmp, _start, sizeof(T) * size());
for (size_t i = 0; i < sz; ++i)
{
tmp[i] = _start[i];
}
delete[] _start;
}
_start = tmp;
_finish = tmp + sz;
_end_of_storage = tmp + n;
}
}
void push_back(const T& x)
{
if (_finish == _end_of_storage)
{
reserve(capacity() == 0 ? 4 : capacity() * 2);
}
*_finish = x;
++_finish;
}
void pop_back()
{
assert(!empty());
--_finish;
}
//迭代器失效:扩容引起,野指针问题
iterator insert(iterator pos, const T& val)
{
assert(pos >= _start);
assert(pos <= _finish);
if (_finish == _end_of_storage)
{
size_t len = pos - _start;
reserve(capacity() == 0 ? 4 : capacity() * 2);
//扩容后更新pos,解决pos失效的问题
pos = _start + len;
}
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
--end;
}
*pos = val;
++_finish;
return pos;
}
iterator erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
iterator start = pos + 1;
while (start != _finish)
{
*(start - 1) = *start;
++start;
}
--_finish;
return pos;
}
size_t capacity() const
{
return _end_of_storage - _start;
}
size_t size() const
{
return _finish - _start;
}
bool empty()
{
return _start == _finish;
}
T& operator[](size_t pos)
{
assert(pos < size());
return _start[pos];
}
const T& operator[](size_t pos) const
{
assert(pos < size());
return _start[pos];
}
private:
iterator _start= nullptr;
iterator _finish= nullptr;
iterator _end_of_storage= nullptr;
};
void func(const vector<int>& v)
{
for (size_t i = 0; i < v.size(); ++i)
{
cout << v[i] << " ";
}
cout << endl;
vector<int>::const_iterator 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);
func(v1);
for (size_t i = 0; i < v1.size(); ++i)
{
cout << v1[i] << " ";
}
cout << endl;
v1.pop_back();
v1.pop_back();
v1.pop_back();
//v1.pop_back();
//v1.pop_back();
//v1.pop_back();
vector<int>::iterator it = v1.begin();
while (it != v1.end())
{
cout << *it << " ";
++it;
}
cout << endl;
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
}
//template<class T>
//void f()
//{
// T x = T();
// cout << x << endl;
//}
//void test_vector2()
//{
// //内置类型有没有构造函数
// //int i = int();
// //int j = int(1);
// int* pi = int* ();
// f<int>();
// f<int*>();
// f<double>();
//}
void test_vector2()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
cout << v1.size() << endl;
cout << v1.capacity() << endl;
v1.resize(10);
cout << v1.size() << endl;
cout << v1.capacity() << endl;
func(v1);
v1.resize(3);
func(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(5);
func(v1);
v1.insert(v1.begin(), 0);
func(v1);
auto pos = find(v1.begin(), v1.end(), 3);
if (pos != v1.end())
{
//v1.insert(pos, 30);
pos=v1.insert(pos, 30);
}
func(v1);
//insert以后我们认为pos失效了,不能再使用
//(*pos)++;
func(v1);
}
void test_vector4()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
//v1.push_back(5);
func(v1);
auto pos = find(v1.begin(), v1.end(), 3);
if (pos != v1.end())
{
v1.erase(pos);
}
//erase以后,pos是否失效
//pos失效了,不能访问并且进行强制检查
//失效,不要访问,行为结果未定义 根据具体编译器实现有关,在g++中没报错 但是在vs下进行报错
//(*pos)++;
func(v1);
}
void test_vector5()
{
vector<int> v1;
v1.push_back(10);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
func(v1);
//要求删除所有的偶数
vector<int>::iterator it = v1.begin();
while (it != v1.end())
{
if (*it % 2 == 0)
{
it=v1.erase(it);
}
else
{
++it;
}
}
func(v1);
}
void test_vector6()
{
//vector<int> v1(10u,5); //u表示这个字面量为无符号
vector<int> v1(10,5);
func(v1);
vector<int> v2(v1.begin() + 1, v1.end() - 1);
for (auto e : v2)
{
cout << e << " ";
}
cout << endl;
std::string s1("hello");
vector<int> v3(s1.begin(), s1.end());
for (auto e : v3)
{
cout << e << " ";
}
cout << endl;
int a[] = {30,20,10};
vector<int> v4(a,a+3);
for (auto e : v4)
{
cout << e << " ";
}
cout << endl;
v1.insert(v1.begin(), 10);
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
sort(v1.begin(), v1.end());
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
for (auto e : a)
{
cout << e << " ";
}
cout << endl;
//sort(a, a + sizeof(a) / sizeof(int)); //默认是升序< 降序>
//greater<int> g;
//sort(a, a + sizeof(a) / sizeof(int), g);
sort(a, a + sizeof(a) / sizeof(int), greater<int> ());
sort(a, a + sizeof(a) / sizeof(int));
for (auto e : a)
{
cout << e << " ";
}
cout << endl;
}
void test_vector7()
{
vector<int> v1(10, 5);
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
vector<int> v2(v1);
for (auto e : v2)
{
cout << e << " ";
}
cout << endl;
vector<std::string> v3(3, "1111111111111111");
for (auto e : v3)
{
cout << e << " ";
}
cout << endl;
vector<std::string> v4(v3);
for (auto e : v4)
{
cout << e << " ";
}
cout << endl;
}
}
5.2 test.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<algorithm>
#include<functional>
#include<string>
#include<vector>
using namespace std;
#include "vector.h"
class A
{
public:
A()
{
cout << "A()" << endl;
}
~A()
{
cout << "A()" << endl;
}
};
int main()
{
/*vector<int>::iterator it;
cout << typeid(it).name() << endl;*/
//zl::test_vector1();
//zl::test_vector2();
//zl::test_vector3();
//zl::test_vector4();
//zl::test_vector5();
//zl::test_vector6();
zl::test_vector7();
//A a1;
A(); //匿名对象生命周期只在当前一行,因为这行之后没人会用它了
//const A& xx = A(); //const引用会延长匿名对象的生命周期到引用对象域结束,因为以后用xx就是用匿名对象
//A a2;
return 0;
}
到了这里,关于C++初阶-vector类的模拟实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!