前言:

在上一篇文章中我們詳細的向大家介紹了vector的一些核心接口的使用,那麼本篇文章就來深度的剖析一下vector的底層實現。

文章目錄

  • 一、vector的基本成員變量
  • 二、vector核心接口的實現
  • 2.1構造相關接口的實現
  • 2.2迭代器相關的接口實現
  • 2.3空間相關的接口的實現
  • 2.3.1memcpy深層次的淺拷貝問題
  • 2.4元素訪問相關的接口實現
  • 2.5vector修改相關的接口實現
  • 三,插入刪除引起的迭代器失效問題
  • 四、總結

一、vector的基本成員變量

在模擬實現vector之前我們首先要了解vector的基本成員變量,然後在逐步進入到vector的一些核心接口的實現。如何知道這些成員變量呢?下面通過源碼一探究竟:

【C++】STL:vector的使用及模擬實現_c++ vector3 operate_#開發語言


有了上面的認識,那麼我們模擬實現的vector的成員變量就仿照源碼來實現:

#include<iostream>
using namespace std;
namespace my_vector
{
	template<class T>
	class vector
	{
	public:
	//vector的迭代器使用的是一個原生指針,因為原生指針本身就能完成迭代器相關的++ * --等這些操作
		typedef T* iterator;
		typedef const T* const_iterator
	private:
		iterator _start;//指向空間頭部的指針
		iterator _finish;//指向最後一個有效數據的下一個位置
		iterator _endofstorage;//指向空間的末尾
};

以上就是模擬實現的vector的基本框架,成員變量就是_start_finish_endofstorage這三個指針。下面就正式的進入vector的模擬實現,模擬vector的五大步驟:

1、構造相關接口
2、迭代器相關接口
3、空間相關
4、元素訪問
5、vector的修改操作

二、vector核心接口的實現

2.1構造相關接口的實現

構造相關的接口主要有以下幾種:

  • 默認構造
  • 使用n個元素構造
  • 拷貝構造
  • 初始化列表構造
  • 迭代器區間構造
  • operator= 運算符重載

注意:以下實現的接口均是在vector類的內部實現,不像string那樣聲明和定義可以分離到兩個文件。因為我們要自己實現一個vector的模板,而模板的聲明和定義是不能分離到兩個不同的文件的,同一個文件下可以。

//vector的默認構造
	vector()
	:_start(nullptr)
	,_finish(nullptr)
	,_endofstorage(nullptr)
{}

//使用n個val初始化
//這裏的T()是調用T對應的構造 是為了能夠讓任意類型都能夠調用 如果T是內置類型 就會調用內置類型的構造 如果T是自定義類型就調用自定義類型自己的默認構造
	vector(size_t n, T val = T())
	{
		resize(n, val);//resize接口後面會講
	}

//拷貝構造

```cpp
//拷貝構造 v2(v1)
vector(const vector<T>& v)
{
	//開與v1一樣大的空間
	reserve(v.size());//reserve接口後面會介紹
	//底層也是調用push_back
	for (auto e : v)
	{
		push_back(e);//尾插,後面會介紹
	}
}

//使用初始化列表構造 示例:vector<int> v1={1,2,3,4,5}; 花括號的內容其實是轉化成了initializer_list對象 這裏不理解的可以看上一篇文章!
vector(initializer_list<T> il) 
{
	//根據所給的對象il開空間然後調用push_back
	reserve(il.size());
	for (auto e : il)
	{
		//底層是this指針調用的pus_back this指針存的是要構造的vector對象的地址
		push_back(e);
	}
}


//迭代器區間構造 搞成函數模板支持泛型 形參可以是任意容器的迭代器
template<class Inputiterator>
vector(Inputiterator first, Inputiterator last)
{
	//底層調的還是push_back
	while (first != last)
	{
		push_back(*(first));
		first++;
	}
}


//賦值重載 底層使用交換函數交換底層的成員變量
vector operator=(const vector<T>& v)
{
	swap(v);
	return *this;
}

void swap(const vector<T>& v)
{
	std::swap(_start,v._start);
	std::swap(_finish,v._finish);
	std::swap(_endofstorage,v._endofstorage);
}

//析構
~vector()
{
	if (_start)
	{
		delete[] _start;
		_start = _finish = _endofstorage = nullptr;
	}
}

2.2迭代器相關的接口實現

vector的迭代器主要實現的是普通迭代器和const迭代器:

//普通迭代器
iterator begin()
{
	return _start;
}
iterator end()
{
	//返回的是finish不是endofstorage
	return _finish;
}
//const迭代器
const_iterator begin() const
{
	return _start;
}
const_iterator end() const
{
	//返回的是finish不是endofstorage
	return _finish;
}

反向迭代器就是與正向迭代器相反,rbegin()返回end()-1rend()返回begin()-1

2.3空間相關的接口的實現

與空間相關的接口有:

1、size() ——> 記錄有效數據個數
2、capccity() ——> 記錄總的空間大小
3、empty() ——> 判空
4、resize() ——> 擴容 影響size
5、eserve() ——> 擴容 不影響size

注意:vector中空間相關的接口就屬reserve接口最為重要,它主要負責vector的擴容邏輯,而resize接口也可以擴容但是兩者有本質的區別,通過下面的底層實現你就能一探究竟:

//size和capacity通過兩個指針相減可以計算它們之間的數據個數
size_t size() const
{
	return _finish - _start;
}

size_t capacity()
{
	return _endofstorage - _start;
}

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

void resize(size_t n, T val = T())
{
	if (n > size())
	{
		reserve(n);
		//n>size後面的n-size個空間使用val來填充
		while (_finish != _start + n)
		{
			*_finish = val;
			++_finish;
		}
	}
	else//n<size 縮容影響size 有效數據直接縮到n處
	{
		_finish = _start + n;
	}
}


void reserve(size_t n)
{
	//在start更新前要保存一下size
	auto oldsize = size();
	if (n > size())
	{
		//開闢新空間 拷貝舊數據
		iterator tmp = new T[n];
		//拷貝舊數據
		if (_start)
		{
			//memcpy深層次的拷貝問題 原因對於自定義類型是淺拷貝 delete的時候析構兩次
			//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 + oldsize;
		_endofstorage = _start + n;
	}
}

2.3.1memcpy深層次的淺拷貝問題

注意這裏有一個很容易留坑的點:就是memcpy生層次的淺拷貝問題:

【C++】STL:vector的使用及模擬實現_c++ vector3 operate_迭代器_02

怎麼才能解決呢?調用賦值重載完成深拷貝就可以。

【C++】STL:vector的使用及模擬實現_c++ vector3 operate_#開發語言_03

2.4元素訪問相關的接口實現

元素訪問相關的接口最常使用的就是operator[],而vector的迭代器使用的是原生指針,那麼operator[]無非就是訪問某個下標的元素,下面看代碼:

//支持讀和寫操作
const T& operator[](size_t i)
{
//判斷下標是否合法
	assert(i < size());
	return _start[i];//實際上轉化為指針的解引用: *(_start+i)
}

//加上const後只讀
const T& operator[](size_t i) const
{
	assert(i < size());

	return _start[i];
}

2.5vector修改相關的接口實現

vector修改相關的接口無非就是插入刪除,插入有尾插,任意位置插入,刪除有尾刪,以及任意位置的刪除,實現這些插入,刪除函數時有較多的細節需要注意。下面給出代碼再一點點的剖析。

//尾插
void push_back(const T& x)
{
	//插入要考慮空間是否足夠
	if (_finish == _endofstorage)
	{
		reserve(capacity() == 0 ? 4 : 2 * capacity());
	}
	*_finish = x;
	++_finish;
}
//尾刪
void pop_back()
{
	assert(!empty());
	--_finish;
}


iterator insert(iterator pos, const T& x)
{
	assert(pos <= _finish);
	assert(pos >= _start);

	//插入首先判斷空間是否足夠
	if (_finish == _endofstorage)
	{
		//法一 計算相對位置    
		size_t n = pos - _start;
		//擴容 異地擴容會導致迭代器失效 在這裏就是野指針
		reserve(capacity() == 0 ? 4 : 2 * capacity());
		//擴容後更新pos
		pos = _start + n;
	}
	//挪動數據
	iterator end = _finish;
	while (end >= pos)
	{
		*(end + 1) = *(end);
		end--;
	}
	*(pos) = x;
	_finish++;

	return pos;
}


//版本二 返回迭代器
iterator erase(iterator pos)
{
	//檢查pos的合法性
	assert(pos <= _finish);
	assert(pos >= _start);

	//刪除要判斷容器是否為空
	if (!empty())
	{
		//刪除也會引發迭代器失效
		iterator it = pos;
		while (it < _finish)
		{
			//
			*(it) = *(it + 1);
			++it;
		}
		--_finish;
	}

	return pos;
}

三,插入刪除引起的迭代器失效問題

void test_vector9()
{
	my_vector::vector<int> v{ 1,2,3,4};
	auto it = v.begin();
	v.push_back(5);                   //這裏會擴容
	while (it != v.end())
	{
		cout << *it << " ";
		*it = 100;
		++it;
	}
	cout << endl;

}

【C++】STL:vector的使用及模擬實現_c++ vector3 operate_迭代器_04

注意:上面的插入元素的過程中會引發迭代器失效導致打印的全是隨機值(有時候會奔潰),至於為什麼會失效我們畫圖來分析:

1、插入引起的迭代器失效

【C++】STL:vector的使用及模擬實現_c++ vector3 operate_迭代器_05

void test_vector10()
{
//刪除v中所有的偶數 
	my_vector::vector<int> v{1,2,3,4,5,6};
	auto it = v.begin();
	while (it != v.end())
	{
		if (*it % 2 == 0)
		v.erase(it);
		++it;//erase刪除的迭代器如果是最後一個元素++it導致程序崩潰
	} 
	for (auto e : v)
		cout << e << " ";
	cout << endl;
	return 0;
}

【C++】STL:vector的使用及模擬實現_c++ vector3 operate_#1024程序員節_06

2、刪除引起的迭代器失效

【C++】STL:vector的使用及模擬實現_c++ vector3 operate_#開發語言_07