【C++】—— vector 的模拟实现
【C++】—— vector 的模拟实现
- 0 前言
- 1 vector 的成员变量
- 1.1 stl 库中的 vector 成员变量
- 1.2 模拟实现 vector 成员变量
- 2 迭代器
- 3 size、capacity、empty
- 4 opreator[ ]
- 5 reserve
- 5.1 初版 reserve
- 5.2 _finish 的处理
- 5.3 深拷贝
- 5.4 终版
- 6 push_back 与 pop_back
- 7 打印函数
- 7.1 初写打印
- 7.2 typename 关键字
- 7.3 打印函数进阶
- 8 insert
- 8.1 insert 初版
- 8.2 迭代器失效
- 8.2.1 情况一:类似野指针
- 8.2.2 algorithm 库中的查找函数
- 8.2.3 情况二:insert 后再对 pos 进行访问
- 8.2.3.1 不扩容的情况
- 8.2.3.1 扩容的情况
- 8.2.4、解决方法
- 8.2.4.1、想法一
- 8.2.4.2、想法二
- 8.3 总结
- 9 erase
- 10 resize
- 11 构造函数系列
- 11.1 默认构造
- 11.2 拷贝构造
- 11.3 迭代器区间构造 与 n 个 value 构造
- 11.3.1 迭代器区间构造
- 11.3.2 n 个value构造
- 11.3.3 注意事项
- 12 赋值运算符重载
- 12.1 传统写法
- 12.2 现代写法
- 13 源码
0 前言
前面,我们学习了 s t r i n g string string,并模拟实现了它,下面我们来学习 v e c t o r vector vector
v e c t o r vector vector 的底层即我们前面曾经学过的顺序表
,它属于 STL库
,为了让我们更加深入理解 v e c t o r vector vector,接下来我们将模拟实现 v e c t o r vector vector
1 vector 的成员变量
1.1 stl 库中的 vector 成员变量
我们学习数据结构时,曾经模拟实现过顺序表
。当时我们用一个指针 a a a 管理从堆开辟的空间, s i z e size size 表示当前有效数据的个数, c a p a c i t y capacity capacity 表示当前空间的大小。
但是, s t l stl stl库 中的 v e c t o r vector vector 的成员变量有所差别:它的成员变量是三个迭代器
我们知道,迭代器全部都是 t y p e d e f typedef typedef 出来的,那 v e c t o r vector vector 的迭代器是什么呢?
可以看到,其迭代器就是 原生指针!
那start
、finish
、end_of_storage
变量又是什么意思呢?面对未知的东西,我们应大胆假设,小心求证。
不管他底层如何, v e c t o r vector vector 终究逃不过扩容,那我们不难猜想:start
肯定是一个指针指向一个开始,finish
指向某个结束,至于end_of_storage
, s t o r a g e storage storage 是存储的意思,所以我们大胆猜测end_of_storage
与空间相关,可能就是空间结束位置;那这样的话finish
可能表示数据的结束
那我们怎么取求证呢?我们可以通过一些我们熟悉功能的函数来确认
可以看到, b e g i n begin begin 返回的是第一个有效数据位置,这里是start
;而 e n d end end 返回的是最后一个有效数据的下一个位置,这里返回的是finish
; c a p a c i t y capacity capacity 表示空间的大小,这里返回的是end_of_storage - begin()
。
因此我们的猜测是正确的,那么 v e c t o r vector vector 的底层结构如下:
注:finish
指向的是有效空间的下一个位置
1.2 模拟实现 vector 成员变量
我们模拟实现的 v e c t o r vector vector 的成员变量,也学习库中的形式
namespace my_vector
{template<class T>class vector{public://···private:iterator _start = nullptr;iterator _finish = nullptr;iterator _end_of_storage = nullptr;};
}
这里,为了与库中的 v e c t o r vector vector 区别开,我们使用命名空间
同时,因为顺序表应满足不同数据类型的存储,我们实现的不是某个具体类型的类,而是类模板。既然是模板,那么函数的声明与定义就不能放在不同的文件中
,因此声明和定义全在vector.h
文件中,不再需要vector.cpp
文件。
2 迭代器
因为 v e c t o r vector vector 的迭代器是一个原生指针,与 s t r i n g string 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;
}
因为 _ f i n i s h finish finish 本就就是只想最后一个有效数据的下一位,因此我们end()
我们直接返回 _finish
即可
3 size、capacity、empty
这三个函数都很简单,我们直接看代码
size_t size() const
{return _finish - _start;
}size_t capacity() const
{return _end_of_storage - _start;
}bool empty() const
{return _start == _finish;
}
4 opreator[ ]
v e c t o r vector vector 中的 o p r e a t o r opreator opreator[] 与 s t r i n g string string 中的 o p e r a t o r operator operator[] 类似,都是访问第 i i i 个数据的元素。同样, v e c t o r vector vector 的 o p e r a t o r operator operator[] 也要用 a s s e r t assert assert 断言来判断下标是否合法
T& operator[](size_t i)
{assert(i < size());return _start[i];
}const T& operator[](size_t i) const
{assert(i < size());return _start[i];
}
这里顺便提一下, v e c t o r vector vector 还提供了 at 函数 接口。
a t at at 函数与 o p e r a t o operato operato[] 功能相同,但他们检查越界的方式不同
a t at at 函数是抛异常,而 o p e r a t o r operator operator[] 是断言
5 reserve
5.1 初版 reserve
void reserve(size_t n)
{if (n > capacity()){ T* tmp = new T[n];memcpy(tmp, _start, sizeof(T) * size());delete[] _start;_start = tmp;_finish = _start + size();_end_of_storage = _start + n;}
}
在 s t r i n g string string 中,我们已经模拟实现 r e s e r v e reserve reserve 了, v e c t o r vector vector 中也是需要我们手动释放和拷贝的数据的,代码细节就不再细讲了。
那上面代码有问题吗?
有的
5.2 _finish 的处理
首先是对 _ f i n i s h finish finish 的处理上
这里我们更新_finish的方式是_start + size()
,可是 size()
是怎么计算出来的?是通过_finish - _start
计算出的,可是此时 _ s t a r t start start已经更新了,而_ f i n i s h finish finish还未更新 ,此时他们相减会出大问题。
怎么解决呢?也很好办,那就是提前记录 s i z e size size() 的值
void reserve(size_t n)
{if (n > capacity()){//记下size()的值size_t old_size = size();T* tmp = new T[n];memcpy(tmp, _start, sizeof(T) * size());delete[] _start;_start = tmp;_finish = _start + old_size;_end_of_storage = _start + n;}
}
5.3 深拷贝
还有一点,上述代码用 m e m c p y memcpy memcpy 拷贝是浅拷贝,而我们需要的是深拷贝
用 m e m c p y memcpy memcpy 拷贝有什么问题呢?
void test7()
{vector<string> v1;v1.push_back("11111");v1.push_back("11111");v1.push_back("11111");v1.push_back("11111");print_container(v1);v1.push_back("11111");print_container(v1);
}
运行结果:
程序崩溃了!
为什么呢?问题出现在了扩容!
我们知道 v e c t o r vector vector< s t r i n g string string> 中,每个 s t r i n g string string 都指向一块堆空间。
虽然 v e c t o r vector vector 是深拷贝,但是 v e c t o r vector vector 中的每个 s t r i n g string string 都是浅拷贝,而每个string都指向一块资源
。这意味着新开辟的 t m p tmp tmp 中,每个 string 都是与对应旧空间的 string 指向同一块空间
, d e l e t e delete delete 后,会调用析构函数
,将旧空间指向的资源全部释放掉,此时新空间的每个 s t r i n g string string 都是指向一块已经被释放的空间,自然会出现问题。
那如何解决呢?那就是对vector中的每个成员都进行深拷贝
。我们不能再使用 m e m c p y memcpy memcpy,而是要自己赋值
void reserve(size_t n)
{if (n > capacity()){ size_t old_size = size();T* tmp = new T[n];for (size_t i = 0; i < old_size; ++i){_tmp[i] = _start[i];}delete[] _start;_start = tmp;_finish = _start + old_size;_end_of_storage = _start + n;}
}
_tmp[i] = _start[i];
是赋值,本质是调用自定义类型的赋值重载函数,实现了每个成员的深拷贝,而[ ]
,则是前面实现的operator[]
重载函数
5.4 终版
void reserve(size_t n)
{if (n > capacity()){ size_t old_size = size();T* tmp = new T[n];for (size_t i = 0; i < old_size; ++i){_tmp[i] = _start[i];}delete[] _start;_start = tmp;_finish = _start + old_size;_end_of_storage = _start + n;}
}
6 push_back 与 pop_back
p u s h push push_ b a c k back back 直接往尾部放数据即可,但要注意空间不够要扩容
void push_back(const T& x)
{if (_finish == _end_of_storage){size_t n = size() == 0 ? 4 : 2 * size();reserve(n);}*_finish = x;++_finish;
}
p o p pop pop_ b a c k back back 也很简单,但要注意判断是否为空。
void pop_back()
{assert(!empty());--_finish;
}
7 打印函数
STL库 中, v e c t o r vector vector 是没有重载 o p e r a t o r operator operator<< 的。
为什么呢?因为库事先并不知道你要以什么形式打印:是连续打印,还是每打印一个数据就空格还是每打印一个就换行?而且自己实现一个打印函数并不困难。因此库就交给程序员自己实现。
7.1 初写打印
那么我们就自己实现一个打印函数吧。
打印函数是一个全局函数
template<class T>
void print_vector(const vector<T>& v)
{vector<T>::const_iterator it = v.begin();while (it != v.end()){cout << *it << " ";++it;}cout << endl;
}
但上述函数写的是有问题的
7.2 typename 关键字
为什么呢?
问题出现在vector<T>::const_iterator it;
中
可以直接突破类域,用类名::
方式去类中取东西,能取到两种:一种是 静态成员变量,另一种是在 类中 t y p e d e f typedef typedef 的类型。
C++规定:类模板没有被实例化之前,编译器是不会往里面去取东西的
。
编译器走到 p r i n t print print_ v e c t o r vector vector 时, v e c t o r vector vector 模板还没被实例化出来。而因为不敢去模板中取,编译器不知道 c o n s t const const_ i t e r a t o r iterator iterator 是 类型 还是 静态成员变量,如果取到静态成员变量,后面还跟着变量 it
就会出大问题。因此编译器报错。
要解决上述问题,需在前面加上 t y p e n a m e typename typename 关键字。加上 t y p e n a m e typename typename,即明确告诉编译器 c o n s t const const_ i t e r a t o r iterator iterator 是类型。(可以看做编译器的甩锅。编译器:你说了是类型的啦,我给你过了,如果他是成员变量的话那也是你的问题,不怪我)
但如果是取静态成员变量
,前面什么都不用加
,因为如果真是静态成员变量,你不会像vector<T>::const_iterator it
这样用的,直接就vector<T>::const_iterator;
这样
template<class T>
void print_vector(const vector<T>& v)
{//没有实例化的类模板里面取东西,编译器不能区分const_iterator是类型还是静态成员变量typename vector<T>::const_iterator it = v.begin();while (it != v.end()){cout << *it << " ";++it;}cout << endl;
}
这样就可以啦
当然,这还有一种更简单的方式,用 a u t o auto auto 就没有那么麻烦,因为 a u t o auto auto 直接是由v.begin()
推导而来
template<class T>
void print_vector(const vector<T>& v)
{ auto it = v.begin();while (it != v.end()){cout << *it << " ";++it;}cout << endl;
}
7.3 打印函数进阶
但上述打印函数,稍稍改进一下,就可以变成所有类型容器的打印函数,而不仅仅是 v e c t o r vector vector 类型的
template<class Container>
void print_container(const Container& v)
{auto it = v.begin();while (it != v.end()){cout << *it << " ";++it;}cout << endl;
}
需要注意的是判断条件while (it != v.end())
只能是 !=
,有些小伙伴可能会写while (it < v.end())
。对于 s t r i n g string string 与 v e c t o vecto vector 来说是没问题,因为他们本来就是原生指针;但对其他容器就不行了,比例 l i s t list list 链表,它的 i t e r a t o r iterator iterator 是一个类,不存在比较大小的概念。
8 insert
8.1 insert 初版
void insert(iterator pos, const T& x)
{assert(pos >= begin() && pos <= end());if (_finish == _end_of_storage){size_t n = size() == 0 ? 4 : 2 * size();reserve(n);}iterator i = end();while (pos != i){*i = *(i - 1);--i;}*pos = x;++_finish;
}
上述代码其实是有问题的
我们一起来看看
8.2 迭代器失效
8.2.1 情况一:类似野指针
上述代码其实是有问题的
我们先来测试一个不需要扩容的
void test1()
{vector<int> v1;v1.reserve(10);v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.insert(v1.begin() + 1, 10);print_vector(v1);
}
运行结果:
没有问题
那再来一个需要扩容的
void test1()
{vector<int> v1;v1.reserve(4);v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.insert(v1.begin() + 1, 10);print_vector(v1);
}
运行结果:
可以看到,报错了
为什么呢?这是因为 p o s pos pos 迭代器失效了
我们结合图来看就很容易看懂了。扩容后,pos 依然指向旧空间
,此时旧空间已经被释放了, p o s pos pos 迭代器就是一个野指针
,同时while (pos != i)
判断语句永远也不会成立,造成死循环。
怎么办呢?我们应记录 pos 迭代器与 _start 的相对位置
,再将 pos 映射到新空间中
void insert(iterator pos, const T& x)
{assert(pos >= begin() && pos <= end());if (_finish == _end_of_storage){size_t len = pos - _start;size_t n = size() == 0 ? 4 : 2 * size();reserve(n);pos = _start + len;}iterator i = end();while (pos != i){*i = *(i - 1);--i;}*pos = x;++_finish;
}
8.2.2 algorithm 库中的查找函数
这里,介绍 a l g o r i t h m algorithm algorithm 库中的查找函数
, f i n d find find 函数模板适用于所有的容器,只需要传一个迭代器区间给它,就能帮你查找
库中的 f i n d find find函数 是一个模板
:
需要注意的是,传值要传左闭右开区间
8.2.3 情况二:insert 后再对 pos 进行访问
8.2.3.1 不扩容的情况
我们知道, i n s e r t insert insert 函数的 p o s pos pos 形参是一个迭代器,如果我们想在指定数据之前插入数据,就可以用 find 模板找到相应数据的迭代器
现在,我用 f i n d find find函数 找到 p p p 为 5 的位置,在之前插入数据 100,并将 5 ∗ * ∗ 10
void test2()
{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);int x = 5;auto p = std::find(v1.begin(), v1.end(), x);if (p != v1.end()){v1.insert(p, 40);*(p) *= 10;}print_container(v1);
}
运行结果:
为什么是100 * 10
呢?
这是因为迭代器失效了,在 i n s e r t insert insert 以后,我们可以认为迭代器 p 就已经失效了
。我们结合图来理解
用 i n s e r t insert insert 插入数据后, p p p 指向的位置已经变了。你以为它指向的还是 5,但实际上他已经变了
8.2.3.1 扩容的情况
上述情况还不是最恐怖的,最恐怖的是扩容的情况
void test3()
{vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);int x = 2;auto p = std::find(v1.begin(), v1.end(), x);if (p != v1.end()){v1.insert(p, 100);*(p) *= 10;}print_container(v1);
}
运行结果:
现在连100 都不乘10了,为什么呢?还是迭代器失效的问题,我们来看图
p o s pos pos 的位置还是指向旧空间,这时再对 p o s pos pos 访问,就是类似于野指针
的访问。
8.2.4、解决方法
8.2.4.1、想法一
那应该怎么解决上述问题呢?
在解决完第一种情况的代码中,我们在函数内是对 p o s pos pos 进行了更新的
但是形参的改变并不影响实参,那我们可不可以用引用呢?
void insert(iterator& pos, const T& x)
这样, p o s pos pos 是引用,就能改变函数外的 p p p 了。
但这样是不行的,因为会影响传参
vector<int> v;
v1.insert(v1.begin(), 10);
像这样,我们传递第一个迭代器,但这样传参,v1.begin()
的返回值中间会产生一个临时变量
,实际传递的就是这个临时变量。临时变量是常性
,必须用 c o n s t const const 引用,普通的引用是传不进去的。
那如果我加 c o n s t const const 呢?
void insert(const iterator& pos, const T& x)
加了 c o n s t const const,里面的 p o s pos pos 就无法改变了,所以还是不行
8.2.4.2、想法二
我们来看一下库中的 i n s e r t insert insert 是怎么处理的
库中给了一个返回值,返回的是更新之后的 p o s pos pos。
iterator insert(iterator pos, const T& x)
{assert(pos >= begin() && pos <= end());if (_finish == _end_of_storage){size_t len = pos - _start;size_t n = size() == 0 ? 4 : 2 * size();reserve(n);pos = _start + len;}iterator i = end();while (pos != i){*i = *(i - 1);--i;}*pos = x;++_finish;return pos;
}
所以如果真要 i n s e r t insert insert 后继续访问 p p p,一定要更新 p p p 再访问
void test4()
{vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);int x = 2;auto p = std::find(v1.begin(), v1.end(), x);if (p != v1.end()){p = v1.insert(p, 100);*(p + 1) *= 10;}print_container(v1);
}
运行结果:
8.3 总结
用 i n s e r t insert insert 后,迭代器 p 就失效了
,最好的办法就是不再用 p p p 去访问。如果非要访问,一定要更新一下迭代器 p p p 再去访问
9 erase
iterator erase(iterator pos)
{assert(pos < end() && pos >= begin());iterator i = pos;while (i != end() - 1){*(i) = *(i + 1);++i;}--_finish;return pos;
}
同样, e r a s e erase erase 也存在迭代器失效的问题。我们使用的时候一定要注意! e r a s e erase erase之后就不要再访问迭代器,一定要访问也要先更新再访问,更新后的迭代器指向要删除数据的下一个位置
10 resize
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;}}
}
r e s i z e resize resize 的功能与实现这里就不再过多解释,这里大家直接看代码即可。
这里我们重点讲一下 const T& val = T()
的写法
这是自定义类型给缺省值的方法。因为 v e c t o r vector vector 是模板,给缺省值时不能再像 const T& val = 0
这样给,因为是模板,你不知道 T 是什么类型,像前面给 0 就表示 T 一定是 0 了,肯定是不合适的。T 可能是 i n t int int,可能是 d o u b l e double double、也可能是自定义类型。
如果是自定义类型
的对象,我们就调用它的默认构造
。上述给缺省值的方法就是给 T 的一个匿名对象并调用它的默认构造。
那如果 T 是内置类型
: i n t int int、 d o u b l e double double怎么办?他们没有默认构造这一概念啊
在此之前,我们确实是认为默认函数是没有构造函数这一概念的,但是为了兼容像模板这样的场景,内置类型也有了构造函数的概念(还有析构函数的概念)。
void test5()
{int i = int();int j(1);cout << i << endl;cout << j << endl;cout << int(2) << endl;
}
运行结果:
11 构造函数系列
11.1 默认构造
vector():_start(nullptr),_finish(nullptr),_end_of_storage(nullptr)
{}
但实际上,并不需要这么麻烦,因为我们在成员变量声明时,结了缺省值
,我们这样就可以了
vector() {}
学习类与对象时,我们知道:所有成员都会走初始化列表
。如果我们没有显式写在初始化列表,对于内置类型:没有缺省值不做任何处理
,但现在我们在成员变量声明时,结了缺省值,则用缺省值初始化
。
在C++11中,给了一个方式可以 强制生成默认构造:
vector() = default;
因为编译器只有在程序员没有自己实现任何构造函数(拷贝构造也是构造)时才会生成默认构造,如果我们自己实现了带参的构造,又想编译器生成默认构造,就可以用上述语法
11.2 拷贝构造
vector(const vector<T>& v)
{reserve(v.capacity());for (auto& e : v){push_back(e);}
}
拷贝构造,就是遍历 对象 v v v,将 v v v 的值全部 p u s h push push_ b a c k back back 进 t h i s this this 中。这里范围for
用了引用,是为了减少拷贝
,而为了避免频繁的扩容,使用 r e s e r v e reserve reserve 提前开辟空间
。
11.3 迭代器区间构造 与 n 个 value 构造
11.3.1 迭代器区间构造
在stl库
中,还提供了一个构造方法:迭代器区间构造
这里讲一个新的知识点:类模板里面的成员函数可以是函数模板
为什么要用模板呢?这样不行吗?
vector(iterator first, iterator last)
如果是这样的话,那么迭代器构造只能用 v e c t o r vector vector 迭代器构造,如果是函数模板的话可以用任意的迭代器进行构造
代码如下:
template<class InputIterator>
vector(InputIterator first, InputIterator last)
{while (first != last){push_back(*first);++first;}
}
测试:
void test6()
{list<int> l1;l1.push_back(1);l1.push_back(2);l1.push_back(3);l1.push_back(4);l1.push_back(5);l1.push_back(6);vector<int> v1(++l1.begin(), --l1.end());print_vector(v1);
}
运行结果:
可见,迭代器区间可以用任意容器迭代器进行初始化,但它要求类型是匹配的
11.3.2 n 个value构造
n n n 个 v a l u e value value 的构造很简单,不断 p u s h push push_ b a c k back back 就行
vector(size_t n, const T& val = T())
{reserve(n);while (n--){push_back(val);}
}
11.3.3 注意事项
调用 n n n 个 v a l u e value value 的构造函数时,会出现一个很奇怪的报错
void test7()
{vector<int> v(10, 1);print_vector(v);
}
为什么我调用的时 n n n 个 v a l u e value value 的构造,结果报错报到迭代器区间的构造
了呢?
这是因为编译器把vector<int> v(10, 1);
中的 10 和 1 识别成两个迭代器了。
为什么会这样呢?编译器有一个原则,那就是匹配最匹配的
我们先来看 n n n 个 v a l u e value value 的构造
vector(size_t n, const T& val = T())
对 10 和 1 来说,T 是 i n t int int 是确定的
但是 n n n 是 s i z e size size_ t t t, i n t int int 要进行类型转换
再看迭代器区间构造
vector(InputIterator first, InputIterator last)
,它实例化出来是两个 i n t int int,不用类型转换,所以编译器选择迭代器区间构造
那怎么解决呢?
stl库
中选择重载多个 n n n 个 v a l u e value value 的构造函数,让你永远有更好的选择
vector(size_t n, const T& value = T()){reserve(n);while (n--){push_back(value);}}vector(int n, const T& value = T()){reserve(n);while (n--){push_back(value);}}vector(long long n, const T& value = T()){reserve(n);while (n--){push_back(value);}}
12 赋值运算符重载
赋值运算符重载分为现代写法与传统写法,这在 s t r i n g string string 的模拟实现中讲过类似的,这里就不再细讲了,我们直接看代码
12.1 传统写法
void clear()
{_finish = _start;
}vector<T>& operator=(const vector<T>& v)
{clear();if (this != &v){reserve(v.capacity());const_iterator it = v.begin();while (it != v.end()){push_back(*it);++it;}}return *this;
}
12.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);
}
vector<T>& operator=(vector<T> tmp)
{swap(tmp);return *this;
}
注:类里面可以用类名替代类型,类外面不行
比如这样:用 类名vector
替代 类型vector<int>
void swap(vector& v)
{std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_end_of_storage, v._end_of_storage);
}
vector<T>& operator=(vector tmp)
{swap(tmp);return *this;
}
不过并不推荐
这种写法,因为对新手来说不友好。
13 源码
//vector.h#pragma once
#include<iostream>
#include<assert.h>
#include<vector>namespace my_vector
{template<class T>class vector{public:// Vector的迭代器是一个原生指针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;}size_t size() const{return _finish - _start;}size_t capacity() const{return _end_of_storage - _start;}bool empty(){return _start == _finish;}void clear(){_finish = _start;}vector():_start(nullptr), _finish(nullptr), _end_of_storage(nullptr){}// C++11 前置生成默认构造vector() = default;vector(size_t n, const T& value = T()){reserve(n);while (n--){push_back(value);}}vector(int n, const T& value = T()){reserve(n);while (n--){push_back(value);}}vector(long long n, const T& value = T()){reserve(n);while (n--){push_back(value);}}vector(const vector<T>& v){reserve(v.capacity());const_iterator it = v.begin();while (it != v.end()){ push_back(*it);it++;}}template<class InputIterator>vector(InputIterator first, InputIterator last){InputIterator tmp = first;//不能是< , list那些就不行while (tmp != last){push_back(*tmp);tmp++;}}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=(vector<T> tmp){swap(tmp);return *this;}~vector(){if(_start){delete[] _start;_start = _finish = _end_of_storage;}}T& operator[](size_t n){assert(n < size());return *(_start + n);}const T& operator[](size_t n) const{assert(n < size());return *(_start + n);}void reserve(size_t n);void push_back(const T& x);void resize(size_t n, const T& value = T());void pop_back();iterator insert(iterator pos, const T& x); iterator erase(iterator pos);private:iterator _start = nullptr; // 指向数据块的开始iterator _finish = nullptr; // 指向有效数据的尾iterator _end_of_storage = nullptr; // 指向存储容量的尾};template<class T>void vector<T>::push_back(const T& x){if (size() == capacity()){size_t new_capacity = capacity() ? 2 * capacity() : 4;reserve(new_capacity);}*_finish = x;_finish++;}template<class T>void vector<T>::reserve(size_t n){if (n > capacity()){ iterator tmp = new T[n];size_t preserve_size = size();int m = 0;iterator it = begin();while (it != end()){ *(tmp + m) = *it;++m;++it;}//memcpy(tmp, _start, sizeof(T) * size());delete[] _start;_start = tmp;_finish = _start + preserve_size;_end_of_storage = _start + n;}}template<class T>void vector<T>::resize(size_t n, const T& value){if (n <= size()){_finish = _start + n;}else{reserve(n);int m = n - size();while (m--){*_finish = value;++_finish;}}}template<class T>void vector<T>::pop_back(){assert(!empty());_finish;}/template<class T>typename vector<T>::iterator vector<T>::insert(typename vector<T>::iterator pos, const T& x){assert(pos <= _finish && pos >= _start);/*if (size() == capacity()){size_t new_capacity = capacity() ? 2 * capacity() : 4;reserve(new_capacity);}*///相对位置if (size() == capacity()){size_t len = pos - _start;reserve(capacity() ? 2 * capacity() : 4);pos = _start + len;}iterator it = end();while (it > pos){*it = *(it - 1);it--;}*pos = x;++_finish;return pos;}template<class T>typename vector<T>::iterator vector<T>::erase(typename vector<T>::iterator pos){assert(pos < end() && pos >= begin());iterator it = pos + 1;while (it != end()){*(it - 1) = *it;++it;}--_finish;return pos;}template<class T>void print_vector(const vector<T>& v){typename vector<T>::const_iterator it = v.begin();while (it != v.end()){std::cout << *it << " ";++it;}std::cout << std::endl;}template<class Container>void print_container(const Container& v){auto it = v.begin();while (it != v.end()){std::cout << *it << " ";++it;}std::cout << std::endl;}
}
相关文章:

【C++】—— vector 的模拟实现
【C】—— vector 的模拟实现 0 前言1 vector 的成员变量1.1 stl 库中的 vector 成员变量1.2 模拟实现 vector 成员变量 2 迭代器3 size、capacity、empty4 opreator[ ]5 reserve5.1 初版 reserve5.2 _finish 的处理5.3 深拷贝5.4 终版 6 push_back 与 pop_back7 打印函数7.1 初…...
MySQL 查询过慢的优化方法
1. 优化查询语句 问题:使用 SELECT * 会导致查询获取不必要的数据。 SELECT * FROM users WHERE age > 30;优化建议: 指定需要的列,这样可以减少数据传输的负担,提升查询速度。 SELECT name, email FROM users WHERE age &g…...

YoloV8修改分类(Classify)的前处理(记录)
修改原因 yolo自带的分类前处理对于长方形的数据不够友好,存在特征丢失等问题修改后虽然解决了这个问题但是局部特征也会丢失因为会下采样程度多于自带的,总之具体哪种好不同数据应该表现不同我的数据中大量长宽比很大的数据所以尝试修改自带的前处理&a…...
半监督学习能否帮助训练更好的模型?
数据科学家面临的最常见挑战之一是缺乏足够的标记数据来训练一个可靠且准确的模型。标记数据对于监督学习任务,如分类或回归至关重要。然而,在许多领域,获取标记数据既昂贵又耗时,有时甚至是不切实际的。另一方面,未标…...

VBA 获取字段标题代码轻松搞定
hi,大家好! 最近又有一段时间没和大家唠嗑了,最近也没有时间给大家开直播,天天忙,但不知道在忙啥!那今天我们来讲点啥好玩的呢? 今天是老师节,那就先祝各位老师节日快乐࿰…...
C++代码片段
for(int i1; i<shuliang; i) { int f100; cout<<a[i].name<<":"<<\n; cout<<"该舰艇现在距离基地"<<km<<"km,需要"<<km…...

Golang | Leetcode Golang题解之第388题文件的最长绝对路径
题目: 题解: func lengthLongestPath(input string) (ans int) {n : len(input)level : make([]int, n1)for i : 0; i < n; {// 检测当前文件的深度depth : 1for ; i < n && input[i] \t; i {depth}// 统计当前文件名的长度length, isFi…...

docker打包前端项目
🎉 前言 之前有出过一期打包后端项目和数据库的教程,现在填个坑,出一期打包前端项目的教程,废话不多说,我们直接进入正题。 🎉 编写Dockerfile文件 老规矩,先描述项目结构,结构图…...
调度器怎么自己写?调度器在实现时需要注意哪些细节?请写一个jvm的调度器?如何在这个调度器中添加多个任务?
如果你想自己编写一个调度器,可以按照以下步骤进行: 一、确定需求和目标 明确调度器的应用场景,例如任务调度、资源分配、进程管理等。 确定调度的对象,比如任务、作业、进程等。 定义调度的目标,如最小化完成时间、最…...

创客匠人对话|德国临床营养学家单场发售百万秘笈大公开
老蒋创客圈第66期对话标杆直播连麦,我们邀请到【梦想身型健康管理学院】平台创始人吴迪老师。为我们分享“健康管理赛道单场发售破百万!创始人背后的操盘秘笈是什么?”,深度剖析如何去展示自己的核心竞争力?如何扩大专…...

开源项目低代码表单FormCreate从Vue2到Vue3升级指南
开源项目低代码表单 FormCreate v3 版本基于 Vue 3.0 构建,尽管功能与 v2 版本大致相同,但有一些重要的变更和不兼容项需要注意。 源码地址: Github | Gitee FormCreate v3 对比 v2 版本在一些功能和配置项上做了调整,以更好地支持 Vue 3 的…...
序偶解释:李冬梅老师书线性表一章第一页
序偶的定义: 有序偶是两个对象的搜集,使得可以区分出其中一个是“第一个元素”而另一个是“第二个元素”。带有第一个元素a和第二个元素b的有序偶通常写为(a,b)。例如,在数学中,有序偶用于表示二维空间上的点。序偶的特性…...

3GPP协议入门——物理层基础(二)
物理层基础(一)在这里~ 物理层基础(一) 1.RE Resource Element,NR中最小的资源单位,时域上是一个OFDM符号长度,频域上为一个子载波宽度。 2. RB Resource Block,时域上是一个OFDM符…...

Java学习Day41:手刃青背龙!(spring框架之事务)
1.spring事务概念 在数据层和业务层保证一系列数据库操作原子性成功失败!(相比事务可以在业务层开启) 1.事务定义:关键字:Transactional(一般写在接口上) 2.事务管理器:在JdbcCon…...

el-image(vue 总)
一 加载静态资源 在第一次使用vue3开发项目时,使用require(‘图片路径’),结果浏览器报错: Uncaught (in promise) ReferenceError: require is not defined 因为require是webpack提供的一种加载能力,但…...

餐饮「收尸人」,血亏奶茶店……
最近一段时间,小柴朋友圈叫苦的餐饮人是越来越多了! 比如某天早上睡醒查看朋友圈奏折的时候,有个以前经常光顾的餐馆的老板,发了一条朋友圈:最终,还是要和自己经营了11年的小店告别了…… 配的照片是店…...

【Python进阶】学习Python从入门到进阶,详细步骤,就看这一篇。文末附带项目演练!!!
详细的Python学习路线 1. Python基础 Python安装和环境配置:学习如何在你的操作系统上安装Python,并配置开发环境。变量和数据类型:学习如何定义变量,以及Python中的基本数据类型,如整数、浮点数、字符串等。 Pytho…...

OpenCV结构分析与形状描述符(9)检测轮廓相对于其凸包的凹陷缺陷函数convexityDefects()的使用
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 查找一个轮廓的凸性缺陷。 下图显示了一个手部轮廓的凸性缺陷: convexityDefects 是 OpenCV 库中的一个函数,用于检测轮…...
HTTP 之 响应头信息(二十三)
应答头说明Allow服务器支持哪些请求方法(如GET、POST等)。Content-Encoding文档的编码(Encode)方法。只有在解码之后才可以得到Content-Type头指定的内容类型。利用gzip压缩文档能够显著地减少HTML文档的下载时间。Java的GZIPOutp…...

智能风扇的全新升级:NRK3603语音芯片识别控制模块的应用
在当今智能化生活的潮流中,如何让家电更加人性化、便捷化,已经成为消费者和制造商关注的焦点。在这股大潮中,NRK3603语音识别模块以其出色的性能和广泛的应用,为智能电风扇带来了全新的升级。 1. 芯片特性 NRK3603是一款高性能、…...

eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)
说明: 想象一下,你正在用eNSP搭建一个虚拟的网络世界,里面有虚拟的路由器、交换机、电脑(PC)等等。这些设备都在你的电脑里面“运行”,它们之间可以互相通信,就像一个封闭的小王国。 但是&#…...

Xshell远程连接Kali(默认 | 私钥)Note版
前言:xshell远程连接,私钥连接和常规默认连接 任务一 开启ssh服务 service ssh status //查看ssh服务状态 service ssh start //开启ssh服务 update-rc.d ssh enable //开启自启动ssh服务 任务二 修改配置文件 vi /etc/ssh/ssh_config //第一…...

剑指offer20_链表中环的入口节点
链表中环的入口节点 给定一个链表,若其中包含环,则输出环的入口节点。 若其中不包含环,则输出null。 数据范围 节点 val 值取值范围 [ 1 , 1000 ] [1,1000] [1,1000]。 节点 val 值各不相同。 链表长度 [ 0 , 500 ] [0,500] [0,500]。 …...
Python如何给视频添加音频和字幕
在Python中,给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加,包括必要的代码示例和详细解释。 环境准备 在开始之前,需要安装以下Python库:…...
C# SqlSugar:依赖注入与仓储模式实践
C# SqlSugar:依赖注入与仓储模式实践 在 C# 的应用开发中,数据库操作是必不可少的环节。为了让数据访问层更加简洁、高效且易于维护,许多开发者会选择成熟的 ORM(对象关系映射)框架,SqlSugar 就是其中备受…...

k8s业务程序联调工具-KtConnect
概述 原理 工具作用是建立了一个从本地到集群的单向VPN,根据VPN原理,打通两个内网必然需要借助一个公共中继节点,ktconnect工具巧妙的利用k8s原生的portforward能力,简化了建立连接的过程,apiserver间接起到了中继节…...
今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存
文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...

Linux 内存管理实战精讲:核心原理与面试常考点全解析
Linux 内存管理实战精讲:核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用,还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...

基于IDIG-GAN的小样本电机轴承故障诊断
目录 🔍 核心问题 一、IDIG-GAN模型原理 1. 整体架构 2. 核心创新点 (1) 梯度归一化(Gradient Normalization) (2) 判别器梯度间隙正则化(Discriminator Gradient Gap Regularization) (3) 自注意力机制(Self-Attention) 3. 完整损失函数 二…...

push [特殊字符] present
push 🆚 present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中,push 和 present 是两种不同的视图控制器切换方式,它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...