三根指针创建动态数组

三根指针动态数组

任务要求

在面向对象程序设计这门课上,老师给我们布置了一个任务,要我们完成类似于STL中的vector容器,说白了就是创建一个动态数组。

分析

创建一个的方法可以说有很多,其中常见的有利用一个数组指针+数组大小的类创建,但是该种会造成插入与删除极其不方便的问题(一般来说,用这种方式创建的数组完成删除、增添等操作的时候都要重新申请一个数组然后赋值的方式)。于是可以想出另一种方式:利用一个数组指针+数组当前的大小+数组最大容量的方式创建类。 

class myArray_
{
public:
	int m_max_size = 0;
	int m_pre_size = 0;
	int* my_array;
	
public:
	//ctor
	myArray_();
	myArray_(int size);
	
	//functions
	void push_back(int num);
	void delet(int i);	//i为下标位置,从0开始索引
	void print();
};

其中my_arrary数组指针不用解释了吧,m_max_size就是数组的最大容量,在构造函数中会将它初始化为用户给出的大小的2倍(即构造函数中形参size的2倍),目的很简单,就是面对增加操作时,尽量少的重新设置指针申请新空间然后再赋值。有了m_max_size的话,只要当前数组的数量(m_pre_size)不超过m_max_size,就不用上述操作,而是可以简单地的my_array(m_ptr_size) = num;m_pre_size ++;就可以了。

以下是简单的实现,有问题请指出。

myArray_::myArray_()
{
	m_max_size = 5;    //缺省参数的构造函数,给出最小大小为5
	m_pre_size = 0;
	my_array = new int[m_max_size];
}

myArray_::myArray_(int size)
{
	m_max_size = 2 * size;
	my_array = new int[m_max_size];
	m_pre_size = 0;
}

void myArray_::push_back(int num)
{
    //首先要判断是否需要扩容

	if (this->m_pre_size + 1 <= this->m_max_size)  //不需要扩容很简单了  
	{
		m_pre_size++;
		my_array[m_pre_size - 1] = num;
	}
	else    //需要扩容的情况,需要重新申请地址
	{
		m_max_size = 2 * m_max_size;	//实行扩容操作,将最大容器扩大为原来的2倍

		int *new_my_array = new int[m_max_size];
		for (int i = 0; i < m_pre_size - 1; i++)	//完成数组拷贝操作
		{
			new_my_array[i] = my_array[i];
		}
		m_pre_size++;
		new_my_array[m_pre_size - 1] = num;
		my_array = new_my_array;
	}
}
void myArray_::delet(int pos)
{
	if (m_pre_size == 0 || pos >=m_pre_size)    //判断数组是否为空,为空则退出
	{
		return;
	}
    
    //不为空的情况
	int* new_my_arrary = new int[m_max_size];    //重新申请地址
	for (int i = 0; i < pos ; i++)
	{
		new_my_arrary[i] = my_array[i];    //拷贝
	}
	for (int i = pos; i < m_pre_size - 1; i++)   //拷贝剩下的元素,正好漏下一个删除元素没拷贝
	{
		new_my_arrary[i] = my_array[i + 1];
	}
	m_pre_size--;
	my_array = new_my_arrary;    //别忘了把删除后的地址重新赋值回去
}

void myArray_::print()    //debug函数
{
	for (int i = 0; i < m_pre_size ; i++)
	{
		cout << my_array[i] << " ";
	}
	cout << endl;
}

这里的删除操作只是其中一种简单的实现,还可以利用swap函数对数组中要删除元素的后面所有元素进行交换,同样可以完成删除操作,相比较代码给出的方法,此种方法时间复杂度会大大减小,尤其是当删除的元素的位置靠近数组末尾时。

我的方式

当然上述方法好像并不满足题目上要求的利用三个指针的方式实现vector容器。但是不慌,有了刚才的思路,可以想到创建出三个指针:指向数组首地址的指针(数组名)+指向当前最后一个元素下一位置的指针 + 当前数组最大容量下一位置的指针,就可以完成和上面核心思想一样的操作了。三根指针的示意图如图所示:(利用的是c++风格的指针示意,有点像c++风格迭代器的样式,即指针不直接指向任何一个元素,相信大家应该都懂……)

理解清楚了这个,剩下的就不难了。利用c++指针的特性,刚才那个类的m_pre_size就是present-begin,而m_max_size就是end-begin!这不是我瞎说的,大家可以随意创建一个动态数组试一下,指针相减就是他们的位置差。所以直接可以对刚才那个类在进行一次包装就能完成该任务了!

直接看看类的属性和行为吧:

template <class T>
class MyVector
{
private:
	T* begin;
	T* present;
	T* end;

public:
	MyVector();
	MyVector(int size);
	MyVector(MyVector<T>& obj);
	~MyVector();
	void push_back(T num);
	void delet(int i);
	void print();
	T operator[](int pos);
	void BubbleSort();
	void operator=(MyVector<T>& obj);
};
template <class T>
MyVector<T>::MyVector()
{
	begin = new T[defaultSize];	//默认创建大小为10的一个数组

	present = begin;	//因为现在还是空数组,所以present指针与begin重合
	end = begin + defaultSize;	//末尾指针指向最大位置
}

template <class T>
MyVector<T>::MyVector(int size)
{
	begin = new T[size * 2];    //这里同样将数组最大空间设为给出的2倍,原因上面已经讲出
	present = begin;				//因为现在还是空数组,所以present指针与begin重合
	end = begin + size * 2;	//末尾指针指向最大位置
}
template<class T>		//copy ctor,深拷贝
MyVector<T>::MyVector(MyVector<T>& obj)
{
	int size = obj.end - obj.begin;
	begin = new T[size];
	present = begin;
	end = begin + size;
	for (int i = 0; i < (obj.present - obj.begin); i++)
	{
		begin[i] = obj.begin[i];
		present++;
	}
}
template <class T>
MyVector<T>::~MyVector()
{
	//对于析构函数来说,begin、end、preseent只能delete一次,因为实质上三者指向的都是基于begin创建的内存空间
	//所以只用delete begin,不能delete present与end,否则会造成重复delete的问题
	//执行完delete begin后,应让begin、end、presen三者都指向空即可,防止野指针的产生
	//注:此处如果只删除掉begin,不对end和present操作的话,必然会造成野指针的产生
	delete begin;
	begin = present = end = NULL;
	
}

构造函数里面的拷贝构造函数不能直接利用系统默认的,实际上那只是浅拷贝,在析构的时候会造成重复删除的问题。所以要我们自己写copy ctor,自己写的才是深拷贝。(深拷贝和浅拷贝这里就不多说了,毕竟那么多大牛已经讲得清清楚楚了)

还有就是令人头疼的析构函数……(这个东西很重要)我尝试过delete begin,present,end;begin = present = end = NULL,但是编译器总是报错。后来才想到,因为present和end指针都是在begin创建出来的内存之上的,所以我原来的那种写法会造成指针的重复删除,才导致的编译不通过,所以只需要delete掉bigin指针就行了。为了防止野指针的产生,同时也要对所以的三个指针都要 = NULL操作(实际上,如果只删除掉begin指针,而不对present和end进行任何处理的话,就一定会造成野指针的出现:利用解引用的方式(*present与*end)访问该指针指向的值,会出现意料之外的数字……)。

剩下的就是一下成员函数的实现了,没有什么需要注意的事情,就是考验对指针是否能灵活应用。(比如present- begin就是当前数组中存储数字的个数,*begin就能访问到第一个元素,*(begin + 1)就能访问到第二个元素……)

template <class T>
void MyVector<T>::push_back(T i)
{
	if (present != end)	//未满
	{
		*present = i;
		present++;
	}
	else
	{
		T* temp = new T[(end - begin) * 2];
		present = temp;
		for (int i = 0; i < (end - begin); i++)
		{
			temp[i] = begin[i];
			present++;
		}
		end = temp + (end - begin) * 2;
		begin = temp;
		delete temp;
		temp = NULL;
		*present = i;
		present++;
	}
}

template <class T>
void MyVector<T>::delet(int pos)	//i从0开始索引
{
	if (present == begin || pos >=(present -begin))
	{
		return;
	}
	T* new_begin = new T[end - begin];
	for (int i = 0; i < pos; i++)
	{
		new_begin[i] = begin[i];
	}
	for (int i = pos; i < (end - begin - 1); i++)
	{
		new_begin[i] = begin[i + 1];
	}
	end = new_begin + (end - begin);
	present = new_begin + (present - begin) - 1;
	begin = new_begin;
	
}

template <class T>
void MyVector<T>::print()
{
	T* temp = begin;
	while (temp != present)
	{
		cout << *temp << " ";
		temp++;
	}
	cout << endl;
}

 


小小小总结

c++中的指针系统虽然复杂但是很有趣,并且最能接近代码编译的真实情况。得益于完备的c++指针体系(之所以称为体系是因为指针真的有好多重载操作啊),使我们较为简单的完成了该题目。希望在以后的学习生活中能够勤于思考,敢于试错,知其然更要知其所以然,发挥出自身优势。

最后想以一句话来结尾:千里之行,始于足下。事在人为!

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值