【C++】vector的实现

文章目录

  • [一. 前言](#一. 前言)
  • [二. 基本框架](#二. 基本框架)
  • [三. 具体实现](#三. 具体实现)
    • [1. 铺垫](#1. 铺垫)
    • [2. 构造函数](#2. 构造函数)
    • [3. 析构函数](#3. 析构函数)
    • [4. 拷贝构造函数](#4. 拷贝构造函数)
    • [5. 赋值运算符重载](#5. 赋值运算符重载)
    • [6. 迭代器](#6. 迭代器)
    • [7. operator[]重载](#7. operator[]重载)
    • [8. size()以及capacity()](#8. size()以及capacity())
    • [9. reserve](#9. reserve)
    • [10. resize](#10. resize)
    • [11. push_back](#11. push_back)
    • [12. empty和pop_back](#12. empty和pop_back)
    • [13. insert](#13. insert)
    • [14. erase](#14. erase)
    • [15. 用于测试的打印函数](#15. 用于测试的打印函数)
  • [四. 源代码](#四. 源代码)
    • [1. vector.h](#1. vector.h)
    • [2. test.cpp](#2. test.cpp)

一. 前言

之前已经介绍了vector的各种接口------>>>点击查看详情<<<,现在我们来模拟实现一下vector,在之前实现string类时,我们并没有使用模板,而对于接下来要实现的vector,我们将使用模板来实现。

二. 基本框架

我们前言已经提到,我们这里的vector是用模板来实现的,所以我们只会创建两个文件vector.h以及test.cpp,为了避免和库中的vector重复,我们这里也定义了自己的命名空间。
接下来我们来演示一下我们的vector底层是什么样子的,跟我们所认识的传统的顺序表是不太一样的

如上图所示,我们是定义了三个指针来模拟实现顺序表的,

_start表示vector的起始位置;

_finish表示vector最后一个有效元素的下一个位置;

_end_of_storage表示vector的容量大小

所以我们的基本框架代码如下所示

cpp 复制代码
#pragma once
#include<iostream>
#include<string>
#include<assert.h>
#include<algorithm>
using namespace std;

namespace William
{
	template<class T>
	class vector
	{
	public:
	
	private:
		iterator _start = nullptr;
		iterator _finish = nullptr;
		iterator _end_of_storage = nullptr;
	};
}

三. 具体实现

1. 铺垫

在我们基本框架那里,我们可以看到我们已经使用了模板,下面我们就先用这个模板来弄出来我们vector的迭代器

cpp 复制代码
typedef T* iterator;
typedef const T* const_iterator;
  1. 第一个是我们普通对象的迭代器
  2. 第二个是我们const对象的迭代器

2. 构造函数

cpp 复制代码
vector()
{ }

//// C++11 强制生成默认构造
//vector() = default;

template<class InputIterator>
vector(InputIterator first, InputIterator last)
{
	while (first != last)
	{
		push_back(*first);
		++first;
	}
}

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);
	}
}
  1. 在我们上面的基本框架中可以看到,我们是给了成员变量缺省值的,所以我们这里的第一个构造只是需要显式地体现出来而已,为的是在后面实现构造的重载后编译器能找到默认构造
  2. 也可以像我们注释中的那样来实现第一个构造函数 后续我们重载了一个迭代器区间构造,我们可以看到,类模板中是可以使用函数模板的
  3. 这里的push_back以及reserve成员函数是我们自己实现的后续我们会说到
  4. 我们同时实现了用n个指定值来创建vector的构造函数,其中这里参数的缺省值相信大部分朋友是第一次遇见,这里其实就是调用对应参数的默认构造函数,那么对于内置类型(例如int,double等),C++是允许这样给缺省值的,如下所示
cpp 复制代码
int i = int();
int j = int(1);
int k(2);
  1. 细心的朋友肯定会发现我们在实现用n个指定值来创建vector的构造函数时实现了两个,这是为什么呢,其实应该实现的不止两个,主要是针对下面的场景,该场景中我们的v1,v3都是没问题的,但是v2如果没有实现那个参数为int n的构造函数的话是会出错的,
cpp 复制代码
vector<int> v(5, 2);

vector<int> v1(10);
vector<int> v2(10, 1);
vector<int> v3(v.begin(), v.end());

3. 析构函数

cpp 复制代码
~vector()
{
	if (_start)
	{
		delete[] _start;
		_start = _finish = _end_of_storage = nullptr;
	}
}
  1. 当vector是空的时候我们没必要析构,所以我们需要判断一下
  2. 我们后面分配内存的时候肯定是用new[]的,所以配套的要使用delete[]

4. 拷贝构造函数

cpp 复制代码
// vector(const vector& v) 这样写也可以
vector(const vector<T>& v)
{
	reserve(v.size());
	for (auto& e : v)
	{
		push_back(e);
	}
}
  1. 这里也不是我们在string中所说的传统写法也不是现代写法,我们只需要保证正确性的基础上,怎么舒服怎么来,不过也要注意效率的
  2. 注意我们注释所说的,我们在查询vector相关文档的时候可以看到就是我们注释中的写法,不过对于我们新手来说,就一步一步来就可以

5. 赋值运算符重载

cpp 复制代码
void clear()
{
	_finish = _start;
}

/*vector<T>& operator=(const vector<T>& v)
{
	if (this != &v)
	{
		clear();

		reserve(v.size());
		for (auto& e : v)
		{
			push_back(e);
		}
	}
	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<T>& operator=(vector<T> v)
{
	swap(v);

	return *this;
}
  1. 在我们注释里面就是传统写法,这里我们展示的是现代写法
  2. 我们可以看到,现代写法只需要写一个交换函数,我们这里就类似找一个苦力来帮我们实现函数,而我们自己实现的交换函数就只需要交换我们vector的成员变量就可以了,比起库中的交换函数效率会高一点

6. 迭代器

cpp 复制代码
iterator begin()
{
	return _start;
}

iterator end()
{
	return _finish;
}

const_iterator begin() const
{
	return _start;
}

const_iterator end() const
{
	return _finish;
}
  1. 如果对这里不熟悉的朋友可以再去看看我们的string
  2. 这里分别实现了普通对象的迭代器和const对象的迭代器

7. operator[]重载

cpp 复制代码
T& operator[](size_t i)
{
	assert(i < size());
	return _start[i];
}

const T& operator[](size_t i) const
{
	assert(i < size());
	return _start[i];
}
  1. 这里为了是实现像数组那样支持下标访问
  2. 分别实现了普通对象和const对象的对应重载

8. size()以及capacity()

cpp 复制代码
size_t size()
{
	return _finish - _start;
}

size_t capacity()
{
	return _end_of_storage - _start;
}

size_t size() const
{
	return _finish - _start;
}

size_t capacity() const
{
	return _end_of_storage - _start;
}
  1. 这里就跟迭代器那里所说的一样,分别是普通对象和const对象的对应的发方法

9. reserve

cpp 复制代码
void reserve(size_t n)
{
	if (n > capacity())
	{
		size_t old_size = size();
		T* tmp = new T[n];
		//memcpy(tmp, _start, size() * sizeof(T));
		for (size_t i = 0; i < old_size; i++)
		{
			tmp[i] = _start[i];
		}
		delete[] _start;

		_start = tmp;
		_finish = _start + old_size;// 注意这里的old_size
		_end_of_storage = _start + n;
	}
}
  1. 开始步入vector的重点细节部分了,注意我们这里的old_size,我们为什么要定义这个变量呢?原因就在我们的size()函数那里,如果我们没有定义这个变量,我们在delete之后就会用更新前的_finish减去更新后的_start来作为size()的返回值,这明显是不对的
  1. 另外要注意我们是把memcpy给注释掉了,为什么呢?因为如果使用memcpy来实现扩容程序是会崩溃的,假如我们的vector是用string来实例化的,那么我们的memcpy按字节拷贝是会让新空间的指针指向之前的空间的,所以就是我们的vector是支持深拷贝的,但是我们的memcpy并不是深拷贝,析构时就会导致程序崩溃,所以我们这里选择一个一个赋值

  2. 我们这里的reserve默认不缩容

10. resize

cpp 复制代码
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;
		}
	}
}
  1. resize我们这里是可以缩容的,缩容是直接让_finish直接指向指定位置就可以,不熟悉的可以去复习一下我们的顺序表
  2. 扩容的话我们可以直接先扩到指定大小,避免频繁扩容,然后把数据依次添加上就可以了
  3. 这里我们的参数的缺省值就跟我们前面在构造函数那里说的一样

11. push_back

cpp 复制代码
void push_back(const T& x)
{
	if (_finish == _end_of_storage)
	{
		reserve(capacity() == 0 ? 4 : capacity() * 2);
	}
	*_finish = x;
	++_finish;
}
  1. 这里的尾插相信大家已经很熟悉了,都是先判断容量是否需要扩容,然后把数据添加进去就可以了
  2. 这里我们的参数也是使用的模板

12. empty和pop_back

cpp 复制代码
bool empty()
{
	return _start == _finish;
}

void pop_back()
{
	assert(!empty());
	--_finish;
}
  1. 这里就是简单的判空逻辑,尾删时我们一定要判断一下是否还有有效数据

13. insert

cpp 复制代码
iterator insert(iterator pos, const T& x)
{
	assert(pos >= _start);
	assert(pos <= _finish);

	if (_finish == _end_of_storage)
	{
		// 防止迭代器失效
		size_t len = pos - _start;
		reserve(capacity() == 0 ? 4 : capacity() * 2);
		pos = _start + len;
	}

	iterator end = _finish - 1;
	while (end >= pos)
	{
		*(end + 1) = *end;
		--end;
	}
	*pos = x;

	++_finish;

	return pos;
}
  1. 到这里,vector真正的重点来了------迭代器失效,这里insert的逻辑我们就不过多介绍了,相信大家已经很熟悉了,不熟悉的朋友可以看一下我们相关的顺序表和string的部分
  2. 首先介绍第一种迭代器失效,即我们在扩容那里的迭代器失效,我们要知道,C++中扩容我们肯定是异地扩容,原空间和新空间肯定不在一个地方,那么我们在扩容前的pos扩容后就失效了,因为它指向的并不是我们所想要指向的位置,所以这里我们需要先记录一下pos的相对位置,然后在扩容后我们直接在新的_start的位置加上对应的相对位置就是我们扩容后的pos位置,这样我们就更新了pos的值,可以避免这种迭代器失效
  3. 第二种迭代器失效就是我们在插入数据后pos指向的数据就不是原来的数据了,也就是我们当参数传入的那个迭代器就失效了,针对迭代器失效,我们的策略就是更新迭代器,所以我们这里的insert就设计了返回值,这时我们就可以用一个迭代器来接收返回的迭代器来进行相应的更新了

14. erase

cpp 复制代码
iterator erase(iterator pos)
{
	assert(pos >= _start);
	assert(pos < _finish);

	iterator it = pos + 1;
	while (it != end())
	{
		*(it - 1) = *it;
		++it;
	}

	--_finish;

	return pos;
}
  1. 这里的erase也会涉及迭代器失效的问题,所以我们这里也设计了返回值
  2. 大家一定要好好理解一下迭代器失效

15. 用于测试的打印函数

cpp 复制代码
template<class T>
void print_vector(const vector<T>& v)
{
	for (int i = 0; i < v.size(); i++)
	{
		cout << v[i] << " ";
	}
	cout << endl;

	// 从没有实例化的类模板中取东西时,要加typename否则
	// 编译器不能区分这里的迭代器是类型还是静态成员变量
	typename vector<T>::const_iterator it = v.begin();
	// 或者直接写成 auto it = v.begin();
	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
}
  1. 这个函数是我们用来测试的,可以打印指定vector中的有效数据
  2. 这里面是有一个知识点的,所以我们拿出来说一下,就是我们在注释那里所说的,由于我们这里的vector是用模板来实现的,所以在取迭代器的时候我们的vector是还没有实例化的,这时候编译器是不敢轻易地取东西的,因为分不清要取的东西是类型还是静态成员变量,所以我们这里要加上typename,如果不想加可以写成auto类型,如果不写成auto类型也不加上typename的话是通过不了编译的
  3. 这里的打印函数分别实现了下标访问的打印,迭代器实现的打印以及范围for

四. 源代码

1. vector.h

cpp 复制代码
#pragma once
#include<iostream>
#include<string>
#include<assert.h>
#include<algorithm>
using namespace std;

namespace William
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;

		vector()
		{ }

		//// C++11 强制生成默认构造
		//vector() = default;

		// vector(const vector& v) 这样写也可以
		vector(const vector<T>& v)
		{
			reserve(v.size());
			for (auto& e : v)
			{
				push_back(e);
			}
		}

		template<class InputIterator>
		vector(InputIterator first, InputIterator last)
		{
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}

		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);
			}
		}

		void clear()
		{
			_finish = _start;
		}

		/*vector<T>& operator=(const vector<T>& v)
		{
			if (this != &v)
			{
				clear();

				reserve(v.size());
				for (auto& e : v)
				{
					push_back(e);
				}
			}
			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<T>& operator=(vector<T> v)
		{
			swap(v);

			return *this;
		}

		~vector()
		{
			if (_start)
			{
				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 reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t old_size = size();
				T* tmp = new T[n];
				//memcpy(tmp, _start, size() * sizeof(T));
				for (size_t i = 0; i < old_size; i++)
				{
					tmp[i] = _start[i];
				}
				delete[] _start;

				_start = tmp;
				_finish = _start + old_size;// 注意这里的old_size
				_end_of_storage = _start + n;
			}
		}

		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;
				}
			}
		}

		size_t size()
		{
			return _finish - _start;
		}

		size_t capacity()
		{
			return _end_of_storage - _start;
		}

		size_t size() const
		{
			return _finish - _start;
		}

		size_t capacity() const
		{
			return _end_of_storage - _start;
		}

		bool empty()
		{
			return _start == _finish;
		}

		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& x)
		{
			assert(pos >= _start);
			assert(pos <= _finish);

			if (_finish == _end_of_storage)
			{
				// 防止迭代器失效
				size_t len = pos - _start;
				reserve(capacity() == 0 ? 4 : capacity() * 2);
				pos = _start + len;
			}

			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}
			*pos = x;

			++_finish;

			return pos;
		}

		iterator erase(iterator pos)
		{
			assert(pos >= _start);
			assert(pos < _finish);

			iterator it = pos + 1;
			while (it != end())
			{
				*(it - 1) = *it;
				++it;
			}

			--_finish;

			return pos;
		}

		T& operator[](size_t i)
		{
			assert(i < size());
			return _start[i];
		}

		const T& operator[](size_t i) const
		{
			assert(i < size());
			return _start[i];
		}

	private:
		iterator _start = nullptr;
		iterator _finish = nullptr;
		iterator _end_of_storage = nullptr;
	};

	template<class T>
	void print_vector(vector<T>& v)
	{
		for (int i = 0; i < v.size(); i++)
		{
			cout << v[i] << " ";
		}
		cout << endl;

		// 从没有实例化的类模板中取东西时,要加typename否则
		// 编译器不能区分这里的迭代器是类型还是静态成员变量
		typename vector<T>::iterator it = v.begin();
		// 或者直接写成 auto it = v.begin();
		while (it != v.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;

		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;
	}

	template<class T>
	void const_print_vector(const vector<T>& v)
	{
		for (int i = 0; i < v.size(); i++)
		{
			cout << v[i] << " ";
		}
		cout << endl;

		typename vector<T>::const_iterator it = v.begin();
		while (it != v.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;

		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;
	}

	void test1()
	{
		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		//v.push_back(5);

		print_vector(v);

		v.insert(v.begin() + 2, 30);
		
		print_vector(v);
		//const_print_vector(v);

		int x;
		cin >> x;
		auto pos = find(v.begin(), v.end(), x);
		if (pos != v.end())
		{
			v.insert(pos, 40);
			// insert后我们认为pos已经失效了,不要访问
		}
		print_vector(v);
	}

	void test2()
	{
		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		v.push_back(5);

		// 删除所有的偶数
		auto it = v.begin();
		while (it != v.end())
		{
			if (*it % 2 == 0)
			{
				v.erase(it);
			}
			else
			{
				++it;
			}
		}

		print_vector(v);
	}

	void test3()
	{
		int i = int();
		int j = int(1);
		int k(2);

		cout << i << " " << j << " " << k << endl;

		vector<int> v;
		v.resize(5, 1);
		cout << v.size() << " " << v.capacity() << endl;
		v.reserve(10);
		cout << v.size() << " " << v.capacity() << endl;
		print_vector(v);

		v.resize(15, 2);
		cout << v.size() << " " << v.capacity() << endl;
		print_vector(v);
	}

	void test4()
	{
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);
		v1.push_back(5);

		print_vector(v1);

		vector<int> v2;
		v2.push_back(5);
		v2.push_back(4);
		v2.push_back(3);
		v2.push_back(2);
		v2.push_back(1);

		v1 = v2;

		print_vector(v1);
	}

	void test5()
	{
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);
		v1.push_back(5);

		print_vector(v1);

		vector<int> v2(v1.begin() + 1, v1.end() - 1);

		print_vector(v2);

		vector<string> v3(3, "hello world");
		print_vector(v3);

	}

	void test6()
	{
		vector<string> v;
		v.push_back("111");
		v.push_back("111");
		v.push_back("111");
		v.push_back("111");

		print_vector(v);

		v.push_back("111");
		print_vector(v);
	}

	void test()
	{
		vector<int> v(5, 2);

		vector<int> v1(10);
		vector<int> v2(10, 1);
		vector<int> v3(v.begin(), v.end());
	}
}

2. test.cpp

cpp 复制代码
#include "vector.h"

int main()
{
	//William::test1();
	//William::test2();
	//William::test3();
	//William::test4();
	//William::test5();
	William::test6();
	return 0;
}
相关推荐
feng_you_ying_li2 小时前
封装map和set所需第二步:红黑树
c++
郝学胜-神的一滴2 小时前
图形学基础:OpenGL、图形引擎与IG的核心认知及核心模式解析
开发语言·c++·qt·程序人生·图形渲染
BigDark的笔记2 小时前
[温习C/C++]0x09 C++构造函数中调用虚函数会发生什么?
c++
kyle~2 小时前
C++---yaml-cpp YAML标准解析/生成库
c++·参数
96772 小时前
多线程编程:整个互斥的流程以及scoped_lock的用法,以及作用,以及 硬件上的原子操作和逻辑上的原子操作
开发语言·c++·算法
liuyao_xianhui2 小时前
优选算法_topk问题_快速排序算法_堆_C++
java·开发语言·数据结构·c++·算法·链表·排序算法
yunn_2 小时前
Qt智能指针
c++·qt
liuyao_xianhui2 小时前
优选算法_堆_最后一块石头的重量_C++
java·开发语言·c++·算法·链表
上天_去_做颗惺星 EVE_BLUE2 小时前
Linux Core Dump 测试操作手册
linux·c++·测试工具