模拟实现STL-Vector

本文深入探讨了STL Vector的resize和reserve方法,解析它们与capacity和size之间的关系。通过模拟实现,阐述如何优化数据拷贝过程,提升容器性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

vector: resize( )/reserve() 与 capacity/size 关系:

#include <iostream>
#include <vector>
using namespace std;


//capacity 变化规律
void TestVector( )
{
	vector<int> v1;

	v1.push_back( 1 );
	cout << v1.capacity( ) << endl;//  1

	v1.push_back( 2 );
	cout << v1.capacity( ) << endl;//  2

	v1.push_back( 3 );
	cout << v1.capacity( ) << endl;//  3

	v1.push_back( 4 );
	cout << v1.capacity( ) << endl;//  4
		
	v1.push_back( 5 );
	cout << v1.capacity( ) << endl;//  6
		
	v1.push_back( 6 );
	cout << v1.capacity( ) << endl;//  6

	v1.push_back( 7 );
	cout << v1.capacity( ) << endl;//  9

	v1.push_back( 8 );
	cout << v1.capacity( ) << endl;//  9	
	
	v1.push_back( 9 );
	cout << v1.capacity( ) << endl;//  9
	//STL 中 Vector 的 push_back 调用的是 Insert
	//STL 中 capacity = capacity + capacity / 2
	//但如果对于初始容量为0或1则用另一种增容方法(先用上面的方法增容,但操作完后容量没变时,使用第二种方法)
	//if ( capacity < size( ) + 1 )
	//capacity = size( ) + 1;
	v1.push_back( 10 );
	cout << v1.capacity( ) << endl;//  13
}


//reserve
void TestVector( )
{
	vector<int> v1;
	//但像上面那样的话, 不断地在增容, 代价非常大   -->  resize  /  reserve
	v1.reserve( 6 );

	v1.size( );//0
	v1.capacity( );//6
}

//多次reserve
void TestVector( )
{
	vector<int> v1;

	v1.reserve( 6 );
	v1.reserve( 3 );
	//再给的容量比现在容量小, 容量不会减小

	v1.size( );//0
	v1.capacity( );//6
	//说明不会缩小容量, 不会有什么影响.
}

//resize
void TestVector( )
{
	vector<int> v1;

	v1.resize( 6 );
	//resize会改变 size
	//它会给初始值  
	//再插入数据,会插入在前6个数据(0)的后面

	v1.size( );//6
	v1.capacity( );//6

	//此时, vector里面的值 全为0( T( ) )
	//void resize( size_type n, value_type val = value_type( ) );
	//如可以这样给 v1.resize( 6, 10 );  则 前六个数据均为10
}


void TestVector( )
{
	vector<int> v1;

	v1.resize( 6, 10 );
	v1.resize( 3, 5 );


	v1.size( );//3   	-->  10, 10, 10  注意,第二次resize 给的 5值并不会覆盖前三个 元素10
	v1.capacity( );//6
	//说明 resize( ) 会改变 元素个数
	//不管是 reserve( ) 还是 resize( )  容量只要增加了就不会减少
}

//reserve   影响capacity
//resize	影响size + capacity

//vector支持operator[]
//for ( size_t i = 0; i < v.size( ); ++i )
//	cout << v[i] << " ";
//注意, 如果是 list 则不要写 l.size( ) 因为每次计算都会遍历它一遍,效率太低


模拟实现vector:

 

//Vector 没有头插头删 因为代价太大
//List 双向循环链表


#pragma once


#include <iostream>
using namespace std;
#include "TypeTraits.h"							//类型萃取
//template <typename T>
//struct __VectorIterator
//{
//	//给一个指向数组中 数据的指针
//};
//
//template <typename T>
//class Vector
//{
//public:
//
//
//protected:
//	T* _a;
//	size_t _size;
//	size_t _capacity;
//};

//以上是原版的设计, 不是 STL 的设计
//STL 中 Iterator 是一个 T*(原生类型)的指针

Vector 没有头插头删 因为代价太大
List 双向循环链表
//
//
//#pragma once
//
//
//#include <iostream>
//using namespace std;
//#include "TypeTraits.h"							//类型萃取
template <typename T>
struct __VectorIterator
{
	//给一个指向数组中 数据的指针
};

template <typename T>
class Vector
{
public:


protected:
	T* _a;
	size_t _size;
	size_t _capacity;
};
//
以上是原版的设计, 不是 STL 的设计
STL 中 Iterator 是一个 T*(原生类型)的指针


template <typename T>
class Vector
{
	//typedef T* Iterator;						//如果写在这, 没有访问限定符, 默认为私有成员   (即使是 typedef 也受访问限定符 限制)
	//delete/free NULL 都不会出错 , 系统会检查
public:
	typedef T* Iterator;
	typedef const T* ConstIterator;


	Vector( )
		: _start( 0 )
		, _finish( 0 )
		, _endOfStorage( 0 )
	{}//如上图

	//Vector 的 拷贝构造  做了些什么事情?  深拷贝  -->  1.容量有多少拷多少/2.空间有多少拷多少  -->  STL是怎么做的?
	Vector( const Vector<T>& v )
		: _start( 0 )							//先别急着调用 _Expand, 先把成员变量初始化掉, 不然直接调用时它们是随机值, 算的Size( ) 等状态都不对
		, _finish( 0 )
		, _endOfStorage( 0 )
	{
		//1.开空间
		/*this->*/_Expand( /*v.Capacity( )*/v.Size( ) );//两种方式

		//2.拷贝
		//内置类型, memcpy拷贝更快一点  -->  类型萃取(_Expand( ) 也可用类型萃取优化)  strcpy针对字符串, memcpy 针对内置类型(void*  一个字节一个字节浅拷贝)
		//也就是通过类型萃取,内置类型与自定义类型用不同方式拷贝,为了安全性和效率
		//for ( size_t i= 0; i < v.Size( ); ++i )
		//{
		//	_start[i] = v[i];					//前一个是原生指针 调用[].  第二个是对象, 调用 operator[]( )
		//}

		Copy( _start, v._start, v.Size( ) );

		_finish = _start + v.Size( );			//注意, 必须是 v.Size( ).  因为当前 this 指针对象的 Size( ) 是通过 _finish 计算的, 而 _finish 才在计算中.原_finish 为0.
		_endOfStorage = _start + v.Size( );		//我们这里采用的是: 是空间有多少,拷贝多少.所以 _endOfStorage其实与_finish是相等的
	}

	~Vector( )
	{
		if ( NULL != _start )
		{
			delete[] _start;

			_start = _finish = _endOfStorage = NULL;
		}
	}

	void Resize( size_t n, const T& val = T( ) )
	{
		if ( n > Capacity( ) )
			_Expand( n );

		if ( n < Size( ) )
		{
			Iterator nPos = _start + n;			//从这个数据往后进行析构(包括这个数据)

			while ( nPos != _finish )
				nPos->~T( );					//调用它析构函数

			_finish = _start + n;				//把 size 减小, 即把 _finish 的指向改变。 容量不变
		}
		else//n >= size   -->   1.n < capacity    2. n > capacity ( 但我们已经扩容过,所以 n 一定小于capacity )
		{
			Iterator pos = _finish;

			while ( pos != ( _start + n ) )
			{
				//*pos = val;					这样不对, 因为这样 解引用 pos  则 pos 必须是已经存在值(必须是一个已经存在的对象, 但此时,它有可能为未初始化空间)。  如果 pos 是string , operator=( )重载是对两个已经存在对象而言,这里就有问题  
				//STL中 空间配置器(内存池(只开空间,没有构造))分配空间, 这样就不对了。  必须用 new 定位表达式。   
				//但是我们这 这段空间 是 new(调用过构造函数初始化) 出来的, 有初始值. 所以可以这样做
				//new 定位表达式  -->  有空间,未初始化时   则把这个对象构造到这块空间上去.    operator=( ) 时

				*pos = val;
			}
		}
	}

	void Reserve( size_t n )
	{
		_Expand( n );
	}

	void PushBack( const T& x )					//PushBack 不会改变 x
	{
		if( _finish >= _endOfStorage )
		{
			//_Expand( Capacity( ) * 2 + 3 );	//以前做法

			//STL 做法
			size_t capacity = Capacity( ) + Capacity( ) / 2;

			if ( capacity < Size( ) + 1 )
				capacity = Size( ) + 1;

			_Expand( capacity );
		}

		*_finish = x;
		++_finish;
	}

	//--_finish
	void PopBack( )
	{}

	//Vector 的 Insert 也有迭代器失效( 插入数据时, 空间不够, 增容, 旧空间析构, 但迭代器还指向旧空间, 所以失效 ), 解决
	void Insert( Iterator pos, const T& x )
	{}//挪动数据

	//解决 迭代器失效(返回下一个位置的迭代器)
	Iterator Erase( Iterator pos )
	{}//挪动数据

	inline size_t Capacity( ) const
	{
		return _endOfStorage - _start;
	}

	inline size_t Size( ) const
	{
		return _finish - _start;				//Size( ) 要小心算, 你看_Expand.  _start 已经不是以前的 _start, 而是新空间位置.( 用旧的 _finish - 新的 _start )  所以值要提前保存.

	}

	Iterator Begin( )
	{
		return _start;							//原生指针是 一种 天然的迭代器 ( 像指针一样  ++/--/*/-> )
	}

	Iterator End( )
	{
		return _finish;
	}

	ConstIterator Begin( ) const
	{
		return _start;
	}

	ConstIterator End( ) const
	{
		return _finish;
	}

	const/*因为是引用, 所以这也要加const*/T& operator[]( size_t index ) const//operator[]可读可写 const对象只读不能修改
	{
		//assert( index < Size( ) );			//断言: 真,无影响。  假, 报错。
		if ( index >= Size( ) )
			//如果不用断言
			throw out_of_range( "out of range" );

		return _start[index];
	}

	T& operator[]( size_t index )
	{
		if ( index >= Size( ) )
			throw out_of_range( "out of range" );

		return _start[index];
	}

	void _Expand( size_t n )
	{
		if ( n < Capacity )
			return;

		//realloc  为什么内置类型可以, 自定义类型不可以?

		size_t size = Size( );

		T* tmp = new T[n];						//失败会抛出异常. 所以不用考虑失败时我们该做什么.

		//拷贝    -->   如图
		//用类型萃取进行优化
		//for ( size_t i = 0; i < Size( )/*size*/; ++i )//因为每次 Size( ) 都需要建立栈帧 有开销, 所以 将Size( )方法  改为 内联函数   或者用一个变量 保存Size( )的结果
		//tmp[i] = _start[i];
		//但我们这里不这样拷贝,我们用类型萃取,进行优化

		Copy( tmp, _start, size );

		delete[] _start;//本来应该用空间配置器来管理空间,但我们现在没有, 所以用 new 和 delete[]
		
		_start = tmp;
		_finish = _start + size;				//不能用 Size( )
		_endOfStorage = _start + n;
	}

protected:
	Iterator _start;
	Iterator _finish;
	Iterator _endOfStorage;
};


void TestVector( )
{
	Vector<int> v1;

	//疯狂增容 , 每一步都增容
	//为什么 这么设计 ( 每次增加 1/2 ) 呢 , 因为 空间不是找系统要, 有内存池(找它要,它分配空间), 代价不是很大.
	//如果每次 2倍2倍的增加, 有可能增加的太多, 考虑到有可能有空间的浪费.
	v1.PushBack( 1 );
	v1.PushBack( 2 );
	v1.PushBack( 3 );
	v1.PushBack( 4 );
	
	//v1.Print( );								不要这样实现, 别让它成为成员函数
	PrintVector( v1 );

	//Vector<int>::Iterator it = v1.Begin( );
	别 在类里面 封装 Print( ) 函数 ,不然 ,我要 奇数, 要偶数, 你还提供好多 Print( )?.  所以我用迭代器把访问权限 给你, 你 自己去 用 迭代器打印(在类外面实现 你自己想要的 Print函数)
	//while ( v1.End( ) != it )
	//{
	//	cout << *it << " ";
	//	++it;
	//}

	//cout << endl;
}


//不要把 Print 封装成 类的成员函数
//非const(如对象) 也可以传给 const(如调用const成员函数)  权限范围在缩小 , 我可以改变传给你 不能改变, 是可以给你的
void PrintVector( const Vector<int>&/*给引用(别让它深拷贝)因为给 引用, 所以加 const*/ v )
{
	//Vector<int>::ConstIterator it = v.Begin( );	//But const 对象必须调用 const 成员函数   const 修饰 v. 本来 v 不能改变, 但普通迭代器可以改变数据( 遍历时可以修改 ),   所以这, 用 const迭代器

	//while ( v.End( ) != it )
	//{
	//	//*it = 10;								//出错!  it是ConstIterator.    const T*   -->  解引用后   const修饰指针指向的值
	//	cout << *it << " ";
	//	++it;
	//}

	//cout << endl;

	//这种遍历方式太麻烦,我们换种方式
	//类里面, 调用函数 ,可以直接用名字(因为有隐含的this指针), 但类外面, 必须通过对象调用
	//类里面, 写 不能修改的成员函数 时, 最好写成 const 成员函数( 在()后加const(修饰this 指针指向的内容) )   -->  因为 const/非const 对象都可以调用它(有参数this指针)
	//但是对于其中一定会改变数据的方法。 就别加const修饰
	//对于 可读,可修改的 就  提供一个 const的成员函数, 一个非const成员函数.
	for ( size_t i = 0; i < v.Size( ); ++i )
	{
		cout << v[i] << " ";
	}

	cout << endl;
}


int main( )
{
	TestVector( );

	return 0;
}





利用类型萃取优化 数据拷贝:

#pragma once


struct __TrueType
{};

struct __FalseType
{};


template <typename T>
struct TypeTraits
{
	typedef __FalseType IsPodType;				//我们默认把T当作自定义类型, 因为内置类型用 自定义类型处理方式没问题, 反过来就错了
};

template <>
struct TypeTraits<int>
{
	typedef __TrueType IsPodType;
};

template <>
struct TypeTraits<char>
{
	typedef __TrueType IsPodType;
};

template <>
struct TypeTraits<double>
{
	typedef __TrueType IsPodType;
};

//如 指针数组  指针也算原生类型
template <class T>
struct TypeTraits<T*>
{
	typedef __TrueType IsPodType;
};


template <typename T>
T* Copy( T* dst, T* src, size_t n )
{
	memcpy( dst, src, n * sizeof( T )/*memcpy 参数为字节数(void*),(一个一个强转后拷贝)*/ );		//内置

	for ( size_t i = 0; i < n; ++i )			//自定义
		dst[i] = src[i];						//operatpr=( )

	return dst;
}


//STL 版本
//处理内置类型
template <typename T>
T* __Copy( T* dst, T* src, size_t n, __TrueType )
{
	memcpy( dst, src, n * sizeof( T ) );

	return dst;
}

//处理自定义类型
template <typename T>
T* __Copy( T* dst, T* src, size_t n, __FalseType )//没给参数 ,因为我只是区分, 构成重载即可
{
	for ( size_t i = 0; i < n; ++i )
		dst[i] = src[i];

	return dst;
}

//形参 可以 不接受 实参
template <typename T>
T* Copy( T* dst, T* src, size_t n )
{
	return __Copy( dst, src, n, TypeTraits<T>::IsPodType( ) );
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值