成员函数的设计,以及模版类的理解
构造函数
默认构造函数和析构函数
private:
//为了避免编译器不对内置类型处理,这里统一给缺省值,省去初始化列表
iterator _start = nullptr;//顺序表开始的地方
iterator _finish = nullptr;//顺序表size的位置
iterator _endofstorage = nullptr;//顺序表capacity的位置
//构造函数
vector()
{
}
~vector()
{
delete[] _start;
_start = _finish = _endofstorage = nullptr;
}
构造函数初始化的目的:成员变量初始化,而成员变量是三个指针,他们的初始化就是置空,因为避免在每个代码上面写初始化列表置空,就直接给成员变量缺省值,用缺省值给它初始化了。所以构造函数是空的,为什么不删除掉,因为删除了,但是文件里面又实现了其他构造函数这会导致编译器不会生成默认的构造函数,所以要写。
析构函数:就是对开辟出来的空间释放掉,然后将三个指针置空。
vector的拷贝构造
默认的拷贝构造是浅拷贝,会导致两个指针指向同一块空间,所以需要自己写拷贝构造
//拷贝构造
vector(const vector<T>& v)
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
reserve(v.capacity());//开辟和v一样大的空间
for (auto& vdata : v)
{
//遍历每一个v中的数据,把数据追加到新空间
push_back(vdata);
}
}
赋值重载
//拷贝构造
vector(const vector<T>& v)
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
reserve(v.capacity());//开辟和v一样大的空间
for (auto& vdata : v)
{
//遍历每一个v中的数据,把数据追加到新空间
push_back(vdata);
}
}
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endofstorage, v._endofstorage);
}
//现代写法:
vector<T>& operator=(vector<T> temp)
{
swap(temp);
return *this;
}
现代实现赋值重载的原理:利用传值不会改变形参的原理,temp是被拷贝元素的拷贝,然后调用swap,把要构造的对象和被拷贝的对象交换值,这样拷贝构造了对象,也没有改变被拷贝的对象。
一些知识补充:
int b = test_refer();
int& c = b;
cout << b << endl;
cout << sizeof(c) << endl;//这是int类型
cout << sizeof(&c) << endl;//这是取地址c,所以是指针
//总结:引用类型本质还是被引用的类型,而不是指针
//现代写法:
vector<T>& operator=(vector<T> temp)
{
swap(temp);
cout << sizeof(vector<T>&) << endl;
cout << sizeof(*this) << endl;
cout << sizeof(this) << endl;
return *this;//为什么这里要写*this
// 因为这里返回引用的本质不是返回指针而是那边变量的别名
// 其实类型还是原类型
}
迭代器区间构造函数和n个value的构造函数
构造函数:
//类模板实现构造函数:迭代器区间写成模版的原因就是可以用各种迭代器区间去初始化
template <class InputIterator>
vector(InputIterator first, InputIterator last)
{
while (first != last)
{
push_back(*first);
++first;
}
}
//n个value的初始化
vector(size_t n, const T val = T())
{
reserve(n);
for (int i = 0; i < n; i++)
{
push_back(val);
}
}
测试用例:
//测试n个value的构造函数
void vector_test8()
{
vector<int> v1(10, 1);
vector<string> v2(10, "xxxx");
for (auto v : v1)
{
cout << v << " ";
}
for (auto v : v2)
{
cout << v << " ";
}
}
为什么这里的v1会报错?
因为对于下面的那个n个value的构造函数来说,其实上面的类模板给更加匹配int, 因为上面直接套模版类型就是两个 int int的函数,而下面则是size_t和一个模版类型int,那为什么string类没有事呢?因为如果是string的话,上面迭代器区间的构造的参数类型是 string和string ,而下面的是 int和string,明显下面更加匹配
解决方法:写多一个重载函数
//n个value的初始化
vector(size_t n, const T val = T())
{
reserve(n);
for (int 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);
}
}
正反向迭代器
正向迭代器:原生指针
疑问:为什么这里的vector的反向迭代器会报错,而list的却没有报错。
因为这里的返回是传值返回,传值返回的返回值具有常性不能对他进行进行–,而C++对自定义类型做了特殊处理,让具有常性的自定义类型临时对象,可以进入到非const的成员函数
push_back 的实现以及一些坑
这段代码错误在哪里错?
为什么会错?
void push_back(T data)
{
//扩容
if (_finish == _endofstorage)
{
size_t cp = capacity() == 0 ? 4 : capacity() * 2;
//size_t sz = size();
iterator temp = new T[cp]; //开新空间
if (_start)//如果不是空顺序表,则需要把旧空间的内容拷贝到新空间
{
memcpy(temp, _start, size() * sizeof(T));//拷贝新数据到旧空间
delete[] _start;
}
_start = temp;
_finish = _start + size();
_endofstorage = _start + cp;
//reserve(cp);
}
*_finish = data;
_finish++;
}
测试用例
void vector_test3()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(6);
v1.push_back(5);
v1.push_back(4);
v1.push_back(3);
v1.push_back(2);
for (auto v : v1)
{
cout << v << " ";
}
}
正确代码:
//尾插
void push_back(T data)
{
//扩容
if (_finish == _endofstorage)
{
size_t cp = capacity() == 0 ? 4 : capacity() * 2;
size_t sz = size();
iterator temp = new T[cp]; //开新空间
if (_start)//如果不是空顺序表,则需要把旧空间的内容拷贝到新空间
{
memcpy(temp, _start, sz * sizeof(T));//拷贝新数据到旧空间
delete[] _start;
}
_start = temp;
_finish = _start + sz;
_endofstorage = _start + cp;
//reserve(cp);
}
*_finish = data;
_finish++;
}
pop_back的实现
void pop_back()
{
_finish--;
}
reserve的实现
void reserve(size_t n)
{
if (n <= capacity())
{
return;
}
size_t sz = size();
//旧空间数据拷贝到新空间
iterator temp = new T[n];
if (sz)
{
memcpy(temp, _start, sizeof(T) * sz);
}
//删除旧空间
delete[] _start;
//指向新空间
_start = temp;
_finish = _start + sz;
_endofstorage = _start + n;
}
resize的实现
resize代码:
void resize(size_t n, const T& value = T())
{
if (n <= size())
{
_finish = _start + n;
}
else
{
if (n > capacity())
{
reserve(n);
}
size_t oldSz = size();//旧空间size大小
while (oldSz < n)
{
_start[oldSz] = value;
oldSz++;
}
_finish = _start + oldSz;
}
}
resize的实现逻辑:
但是为什么要写成 const T& value = T()呢?
因为这里的T不但大可能是基本类型,也有可能有自定义类型,所以得出结论:const T& value 是用来对vector在resize后未初始化的元素的变量,而这个变量的类型是模版的,是可变的,是自定类型,也可能是基本类型,所以这里的缺省值是一个匿名对象
补充知识:匿名对象的定义方法以及生存周期:
结论:匿名对象的特点就是不用取名字,但是他的生命周期只有这一行,我们可以看到下一行他就会自动调用析构函数,匿名对象赋值,会延长匿名对象的生命周期,详细请看:https://blog.csdn.net/u014583317/article/details/108705360
但是这里还有一个疑问 对于自定义类型,具有构造函数,这条语句的意思就是把T类型的匿名对象变量赋值到value类型变量中,但是对于基本类型呢?他们具备构造函数吗?但是是具备的。
总结:这里的缺省传参就是在没有传参的时候,去调用该类型T(模版)的默认构造函数
insert的实现
代码:
iterator insert(iterator pos, const T& x)
{
assert(pos >= _start);
assert(pos <= _finish);
if (_finish == _endofstorage)
{
size_t posOffset = pos - _start;
reserve(capacity() == 0 ? 4 : capacity() * 2);
pos = _start + posOffset;
}
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
end--;
}
*pos = x;
_finish++;
return pos;
}
为什么assert(pos)的范围是闭区间 start 到 finish?
插入挪动数据逻辑:
把插入位置之后的数据从前往后挪动(包括要插入数据的原位置)
循环逻辑:end+1 = end
结束条件:完成全部挪动
insert迭代器失效的问题
insert扩容后pos还是指向旧空间
insert外部迭代器失效的问题
erase的实现
bug日志:
erase代码实现:
iterator erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
//iterator it = pos;
//while (it < _finish)
//{
// *it = *(it + 1);
// it++;
//}
iterator it = pos + 1;
while (it < _finish)
{
*(it - 1) = *it;
it++;
}
_finish--;
return pos;
}
erase的迭代器失效:
测试用例:
//测试erase迭代器失效
void vector_test4()
{
//测试用例:
// 1 2 3 4 5 6
// 1 2 3 4 5
// 2 2 3 4 5
// 删除偶数
//测试用例1:
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);
//for (size_t i = 0; i < v1.size(); i++)
//{
// if (v1[i] % 2 == 0)
// {
// v1.erase(v1.begin() + i);
// }
//}
auto it = v1.begin();
while (it != v1.end())
{
if (*it % 2 == 0)
{
v1.erase(it);
}
++it;
}
for (auto v : v1)
{
cout << v << " ";
}
cout << endl;
//测试用例2:
vector<int> v2;
v2.push_back(1);
v2.push_back(2);
v2.push_back(3);
v2.push_back(4);
v2.push_back(5);
for (size_t i = 0; i < v2.size(); i++)
{
if (v2[i] % 2 == 0)
{
v2.erase(v2.begin() + i);
}
}
for (auto v : v2)
{
cout << v << " ";
}
cout << endl;
//测试用例3:
vector<int> v3;
v3.push_back(2);
v3.push_back(2);
v3.push_back(3);
v3.push_back(4);
v3.push_back(5);
for (size_t i = 0; i < v3.size(); i++)
{
if (v3[i] % 2 == 0)
{
v3.erase(v3.begin() + i);
}
}
for (auto v : v3)
{
cout << v << " ";
}
cout << endl;
测试用例的报错原因:
解决方法1:
记录偏移量删除法:
for (size_t i = 0; i < v1.size(); i++)
{
if (v1[i] % 2 == 0)
{
v1.erase(v1.begin() + i);
}
}
为什么可以?因为这里的自增变量和删除传参用的都不是会变化的迭代器,而是独立于外的i
解决方法2:
利用erase的返回值,erase的返回值是删除位置的迭代器
auto it = v1.begin();
while (it != v1.end())
{
if (*it % 2 == 0)
{
it = v1.erase(it);
}
else
{
it++;
}
}
结论:insert和erase之后的迭代器都会失效,不能再访问
深拷贝问题
测试用例:
void vector_test5()
{
vector<string> v1;
v1.push_back("11111111111");
v1.push_back("21111111111");
v1.push_back("31111111111");
v1.push_back("41111111111");
v1.push_back("51111111111");
v1.push_back("61111111111");
for (auto v : v1)
{
cout << v << " ";
}
cout << endl;
}
报错原因:在vector析构时发生报错,因为这里的扩容发生深拷贝的问题
解决方案:利用自定义类型赋值重载实现深拷贝
void reserve(size_t n)
{
if (n <= capacity())
{
return;
}
size_t sz = size();
//旧空间数据拷贝到新空间
iterator temp = new T[n];
if (sz)
{
//memcpy(temp, _start, sizeof(T) * sz);会导致浅拷贝
//利用自定义类型赋值重载实现深拷贝
for (int i = 0; i < size(); i++)
{
temp[i] = _start[i];
}
//删除旧空间
delete[] _start;
}
//指向新空间
_start = temp;
_finish = _start + sz;
_endofstorage = _start + n;
}