📝前言:
上篇文章我们讲解了Vector的常见用法,这篇文章我们来模拟实现一下vector
。由于之前我们已经模拟实现过了string,所以这篇文章在相似的实现上不过多赘述,主要讲点不同于string
的,需要注意的地方。
🎬个人简介:努力学习ing
📋个人专栏:C++学习笔记
🎀CSDN主页 愚润求学
🌄其他专栏:C语言入门基础,python入门基础,python刷题专栏
文章目录
一,实现的功能汇总
具体接口和方法主要包括:
- 构造和析构
- 迭代器
- 容量操作
- 运算符重载
- 修改操作
cpp
namespace tr
{
template<class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
// 迭代器
iterator begin(); // end()
const_iterator begin() const; // end()
// 构造和析构
vector();
vector(int n, const T& value = T());
template<class InputIterator>
vector(InputIterator first, InputIterator last);
vector(const vector<T>& v);
vector<T>& operator= (vector<T> v);
~vector();
// 容量操作
size_t size() const;
size_t capacity() const;
void reserve(size_t n);
void resize(size_t n, const T& value = T());
// 运算符重载
T& operator[](size_t pos);
const T& operator[](size_t pos)const;
// 修改操作
void push_back(const T& x);
void pop_back();
void swap(vector<T>& v);
iterator insert(iterator pos, const T& x);
// 返回删除的位置的下一个元素的迭代器
iterator erase(iterator pos);
private:
iterator _start = nullptr; // 指向数据块的开始
iterator _finish = nullptr; // 指向有效数据的尾
iterator _end_of_storage = nullptr; // 指向存储容量的尾
};
}
汇总如下:
二,具体操作实现
构造,析构
构造函数我们主要实现4中不同的,对应Vector的常见用法这篇文章中提到的四个构造。
对于析构函数,无论有没有写初始化列表,变量都会通过走初始化列表来定义。
cpp
vector() // 什么都不用写,因为声明给了缺省值,直接走初始化列表定义了
{}
vector(int n, const T& value = T())
{
reserve(n);
while (n--)
{
push_back(value);
}
}
template<class InputIterator>
vector(InputIterator first, InputIterator last)
{
while (first != last)
{
push_back(*first);
++first;
}
}
// 拷贝构造,注意浅拷贝和深拷贝的问题
vector(const vector<T>& v)
{
reserve(v.capacity()); // reserve会开空间且会修改end_of_storage
for (auto x : v)
{
push_back(x);
}
}
vector<T>& operator= (vector<T> v)
{
// 因为这里的v只是外部的一个拷贝,交换以后,v在函数结束时也会自动销毁,但是v的内容以及所指向的空间却给了*this
swap(v);
return *this;
}
~vector()
{
delete[] _start; // vector 是一个数组,开辟的是连续的存储空间(非单个),需要用delete[]
_start = _finish = _end_of_storage = nullptr;
}
需要注意的是:因为vector
可能会存储自定义类型等需要开辟空间的类型,所以在拷贝的时候要注意实现深拷贝,而不是简单的浅拷贝。
vector(InputIterator first, InputIterator last)
用函数模版实现。(在类模版里面可以再定义函数模板)用函数模板是为了:可以接受vector
以外的迭代器,比如用string
的区间迭代器来初始化vector
迭代器
模拟实现vector
的迭代器是一个原生指针。
迭代器的位置可以通过起始迭代器位置加上偏移量来表示,呈现出一种相对位置的概念。
cpp
typedef T* iterator;
typedef const T* const_iterator;
iterator begin() // end()
{
return _start;
}
const_iterator begin() const // end()
{
return _start;
}
注意:const_iterator begin() const
是const
类型的迭代器,为了提供const
版本的begin()
给const
成员使用。
容量操作
在Vector的常见用法中提到了迭代器失效问题,我们在模拟实现时,遇到会造成底层容器变化的操作也需要额外注意这个问题。
memcpy
只是浅拷贝,因为memcpy
是字节复制,对于内置类型有效。但是对于需要开辟空间的自定义类型,字节复制只是简单的把原来的指针的值复制给了新的指针变量,导致两个指针指向同一片空间,无法达到深拷贝的效果。
cpp
size_t size() const
{
return _finish - _start;
}
size_t capacity() const
{
return _end_of_storage - _start;
}
// 注意,在这里我们要先记录old_size,
// 因为当_start改变了 后续计算_finish时使用size()时,是使用新的_start和旧_finish,无法反映原来的size
void reserve(size_t n)
{
if (n > capacity())
{
size_t old_size = size();
iterator _tmp = new T[n];
// 拷贝元素
for (size_t i = 0; i < old_size; i++)
{
_tmp[i] = _start[i]; // 比如这里是元素是string,实际上这里调用的是operator=,所以能达到深拷贝的效果
}
delete[] _start;
_start = _tmp;
_finish = _start + old_size;
_end_of_storage = _start + n;
}
}
void resize(size_t n, const T& value = T())
{
if (n > capacity())
{
reserve(n);
}
if (n > size())
{
for (iterator end = _finish; end != (_start + n); end++) // 最后end指向的是有效元素的下一个位置
{
*end = value;
}
}
_finish = _start + n;
}
运算符重载
这里主要重载[]
运算符,也需要提供const
版本给const
成员函数使用。
cpp
T& operator[](size_t pos)
{
return _start[pos];
}
const T& operator[](size_t pos)const
{
return _start[pos];
}
修改操作
传引用减少拷贝。
cpp
void push_back(const T& x)
{
if (_finish == _end_of_storage)
{
size_t n = capacity() == 0 ? 4 : capacity() * 2;
reserve(n);
}
*_finish = x;
_finish++;
}
void pop_back()
{
erase(end() - 1);
}
void swap(vector<T>& v)
{
std::swap(_start, v._start); // 记得带std::
std::swap(_finish, v._finish);
std::swap(_end_of_storage, v._end_of_storage);
}
iterator insert(iterator pos, const T& x)
{
assert(pos < _finish);
if (_finish == _end_of_storage)
{
size_t old_size = size();
size_t n = capacity() == 0 ? 4 : capacity() * 2;
reserve(n); // 扩容会导致迭代器失效
pos = _start + old_size;
}
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
end--;
}
*pos = x;
_finish++;
return pos;
}
// 返回删除的位置的下一个元素的迭代器
iterator erase(iterator pos)
{
assert(pos < _finish);
for (iterator end = pos; end < _finish - 1; end++)
{
*(end) = *(end + 1);
}
_finish--;
return pos;
}
注意:在erase
中*(end) = *(end + 1)
,后面元素覆盖前面的,这不会造成迭代器失效,因为没有改变容器的内存布局。(迭代器失效指的是迭代器所指向的元素不再有效,或者迭代器的行为变得不可预)
在这里我们任然可以使用这个迭代器来遍历这块连续的空间,行为可预测。
三,代码汇总及测试代码
my_vector.h
文件:
因为vector
是一个类模板,不建议声明和定义分离,会导致链接错误。
cpp
// vector 模拟实现
#include<iostream>
#include<assert.h>
#pragma once
namespace tr
{
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 // 提供const版本的begin()给const成员使用
{
return _start;
}
const_iterator end() const
{
return _finish;
}
// construct and destroy
vector() // 什么都不用写,因为声明给了缺省值,直接走初始化列表定义了
{}
vector(int n, const T& value = T())
{
reserve(n);
while (n--)
{
push_back(value);
}
}
template<class InputIterator> // 在类模版里面可以再定义函数模板
// 这里用函数模板是因为:可以接受除了vector以外的迭代器,比如用string的区间迭代器来初始化vector
vector(InputIterator first, InputIterator last)
{
while (first != last)
{
push_back(*first);
++first;
}
}
// 拷贝构造,注意浅拷贝和深拷贝的问题
vector(const vector<T>& v)
{
reserve(v.capacity()); // reserve会开空间且会修改end_of_storage
for (auto x : v)
{
push_back(x);
}
}
vector<T>& operator= (vector<T> v)
{
// 因为这里的v只是外部的一个拷贝,交换以后,v在函数结束时也会自动销毁,但是v的内容以及所指向的空间却给了*this
swap(v);
return *this;
}
~vector()
{
delete[] _start;
_start = _finish = _end_of_storage = nullptr;
}
// capacity
size_t size() const
{
return _finish - _start;
}
size_t capacity() const
{
return _end_of_storage - _start;
}
// 注意,在这里我们要先记录old_size,
// 因为当_start改变了 后续计算_finish时使用size()时,是使用新的_start和旧_finish,无法反映原来的size
void reserve(size_t n)
{
if (n > capacity())
{
size_t old_size = size();
iterator _tmp = new T[n];
// 拷贝元素
// memcpy只是浅拷贝,因为memcpy是字节复制,对于内置类型有效,但是对于需要开辟空间的就无法达到深拷贝的效果
for (size_t i = 0; i < old_size; i++)
{
_tmp[i] = _start[i]; // 比如这里是元素是string,实际上这里调用的是operator=,所以能达到深拷贝的效果
}
delete[] _start;
_start = _tmp;
_finish = _start + old_size;
_end_of_storage = _start + n;
}
}
void resize(size_t n, const T& value = T())
{
if (n > capacity())
{
reserve(n);
}
if (n > size())
{
for (iterator end = _finish; end != (_start + n); end++) // 最后end指向的是有效元素的下一个位置
{
*end = value;
}
}
_finish = _start + n;
}
///access///
T& operator[](size_t pos)
{
return _start[pos];
}
const T& operator[](size_t pos)const
{
return _start[pos];
}
///modify/
void push_back(const T& x)
{
if (_finish == _end_of_storage)
{
size_t n = capacity() == 0 ? 4 : capacity() * 2;
reserve(n);
}
*_finish = x;
_finish++;
}
void pop_back()
{
erase(end() - 1);
}
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_storage, v._end_of_storage);
}
iterator insert(iterator pos, const T& x)
{
assert(pos < _finish);
if (_finish == _end_of_storage)
{
size_t old_size = size();
size_t n = capacity() == 0 ? 4 : capacity() * 2;
reserve(n); // 扩容会导致迭代器失效
pos = _start + old_size;
}
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
end--;
}
*pos = x;
_finish++;
return pos;
}
// 返回删除的位置的下一个元素的迭代器
iterator erase(iterator pos)
{
assert(pos < _finish);
for (iterator end = pos; end < _finish - 1; end++)
{
*(end) = *(end + 1);
}
_finish--;
return pos;
}
private:
iterator _start = nullptr; // 指向数据块的开始
iterator _finish = nullptr; // 指向有效数据的尾
iterator _end_of_storage = nullptr; // 指向存储容量的尾
};
}
test_my_vector
文件:
cpp
#include <iostream>
#include "my_vector.h"
using namespace tr;
// 简单的测试函数
void testVector() {
// 测试默认构造函数
vector<int> v1;
std::cout << "v1 size: " << v1.size() << ", capacity: " << v1.capacity() << std::endl;
// 测试带参数的构造函数
vector<int> v2(5, 10);
std::cout << "v2 size: " << v2.size() << ", capacity: " << v2.capacity() << std::endl;
for (size_t i = 0; i < v2.size(); ++i) {
std::cout << v2[i] << " ";
}
std::cout << std::endl;
// 测试区间构造函数
int arr[] = { 1, 2, 3, 4, 5 };
vector<int> v3(arr, arr + 5);
std::cout << "v3 size: " << v3.size() << ", capacity: " << v3.capacity() << std::endl;
for (size_t i = 0; i < v3.size(); ++i) {
std::cout << v3[i] << " ";
}
std::cout << std::endl;
// 测试拷贝构造函数
vector<int> v4(v3);
std::cout << "v4 size: " << v4.size() << ", capacity: " << v4.capacity() << std::endl;
for (size_t i = 0; i < v4.size(); ++i) {
std::cout << v4[i] << " ";
}
std::cout << std::endl;
// 测试赋值运算符
vector<int> v5;
v5 = v2;
std::cout << "v5 size: " << v5.size() << ", capacity: " << v5.capacity() << std::endl;
for (size_t i = 0; i < v5.size(); ++i) {
std::cout << v5[i] << " ";
}
std::cout << std::endl;
// 测试push_back和pop_back
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
std::cout << "v1 size after push_back: " << v1.size() << ", capacity: " << v1.capacity() << std::endl;
v1.pop_back();
std::cout << "v1 size after pop_back: " << v1.size() << ", capacity: " << v1.capacity() << std::endl;
// 测试insert
v1.insert(v1.begin(), 0);
std::cout << "v1 size after insert: " << v1.size() << ", capacity: " << v1.capacity() << std::endl;
for (size_t i = 0; i < v1.size(); ++i) {
std::cout << v1[i] << " ";
}
std::cout << std::endl;
// 测试erase
v1.erase(v1.begin());
std::cout << "v1 size after erase: " << v1.size() << ", capacity: " << v1.capacity() << std::endl;
for (size_t i = 0; i < v1.size(); ++i) {
std::cout << v1[i] << " ";
}
std::cout << std::endl;
// 测试resize
v1.resize(5, 5);
std::cout << "v1 size after resize: " << v1.size() << ", capacity: " << v1.capacity() << std::endl;
for (size_t i = 0; i < v1.size(); ++i) {
std::cout << v1[i] << " ";
}
std::cout << std::endl;
}
int main() {
testVector();
return 0;
}
运行结果:
🌈我的分享也就到此结束啦🌈
要是我的分享也能对你的学习起到帮助,那简直是太酷啦!
若有不足,还请大家多多指正,我们一起学习交流!
📢公主,王子:点赞👍→收藏⭐→关注🔍
感谢大家的观看和支持!祝大家都能得偿所愿,天天开心!!!