简单写一下Vector

几个月前重温了数据结构与算法,对数据和结构有了不同的理解,最近又翻出来看了一下,觉得还是做一篇小笔记最好,故便有了此文。

我记得上课时老师曾讲过一句话:编程最重要的是思想,而不是代码;把思路理清了,自然代码就会写了,而且写的代码印像会深刻许多。

作为STL 开头菜,Vector显然会给初学者带来一点困扰,如果你c++学的不怎么扎实的话,其实我们仔细去剖析,你会发现vector真的就是小葱炒鸡蛋,一清二白。

首先你要理解数组、链表本质上是什么,他们其实就是一个容器,是我们开发出来存取数据的两种最基础的数据结构容器。

数组,逻辑连续,物理位置连续,很好用,存取数据非常方便,new一个就行了,不需要编写链表那么多的代码,但它有弊端:删除结点的数据时带来移位,会增加数据处理时的时间,这是我们不希望的,所以就有了链表,删除结点时只需注意一下指针的连接,不需要移位,但每次寻找数据都需要遍历链表,不像数组那么直观。

了解上述两种,再来了解关系式容器和顺序式容器。

顺序性容器 :是一种各元素之间有顺序关系的线性表,是一种线性结构的可序群集。顺序性容器中的每个元素均有固定的位置,除非用删除或插入的操作改变这个位置(数组、链表、vector)。

关联式容器: 和顺序性容器不一样,关联式容器是非线性的树结构,更准确的说是二叉树结构。各元素之间没有严格的物理上的顺序关系,也就是说元素在容器中并没有保存元素置入容器时的逻辑顺序。

今天主要回顾vector,故主讲顺序式容器,vector是stl开发人员做的一个容器,他们能直接调用微软给他们的内存接口,而我们显然是无法调用的,故我们下面的代码全是模拟vector。

好吧,不说废话了,我们开始了:vector 有一个特色,被称为动态数组(可动态扩大,缩小),这可是我们c++的数组做不到的,既然它被称为动态数组,那我们就用数组来做。

首先我们看一看stl的vector最基本的属性。

根据图片,它有两个属性:size(大小)    capacity(容量);

按我老师的风格,肯定是要我们自己去探索这两个单词的意义;这里我是做回顾,就直接写出来了:

size:代表vector有多少个数据

capacIty:代表vector最大的数据容量

不懂其分别的话,见下图即可

我用vector的重分配函数给其赋值:5个6,很明显size变为5,capaticy还是10;

我给其赋值 12个6;

很明显size变为12,capaticy变为15;这就是动态分配。

当然,这是assign函数的结果,其它函数有不同的作用,这里我们暂且不表。

既然知道了size,capacity的意义,我们就开始着手写基础的构架。

#pragma once

template <typename T>
class MyVector
{
private:
	T*pBuff;
	size_t length;
	size_t max_size;
public: 
	MyVector();
	~MyVector();

};

把最简单的模板结构给搭出来,pBuff :泛型指针,用来开辟数组

空间。

length :数据个数;(size)

max_size:容器能存储数据的最大个数。(capacity)

构造函数:

template <typename T>
MyVector<T>::MyVector()
{
	pBuff = nullptr;
	length = 0;
	max_size = 0;
}

析构函数:

MyVector<T>::~MyVector()
{
	if (pBuff)
	{
		delete[]pBuff;
	}
	pBuff = nullptr;
	length = 0;
	max_size = 0;
}

这样我们就完成了万事开头难的第一步。

最基本的无参构造写完了,肯定要写有参构造和拷贝构造啦:

故代码也就出来了:

public:
	CMyVector(int n);
	CMyVector(CMyVector const& other);




template <class T>
CMyVector<T>::CMyVector(int n)
{
	length = n;
	max_size= n;
	pBuff = new T[length];
	memset(pBuff, 0, sizeof(T)* length );
}


template <class T>
CMyVector<T>::CMyVector(CMyVector const& other)
{
	length = other.num;
	max_size= other.max_size;
	pBuff = NULL;
	if (length )
	{
		pBuff = new T[length];
		memcpy(pBuff, other.pBuff, sizeof(T)* length );
	}
}

接下来就是vector的重要代码了:被称为动态数组,能根据数据需求改变大小,究竟是怎么实现的呢?

public:
	void push_back(T const & srcDate);




template <class T>
void CMyVector<T>::push_back(T const & srcDate)//压入数据
{
	if (length>= max_size)//如果当前个数和最大个数相同,证明没有空间
	{
		//内存重分配及拷贝之前内存数据
		max_size= max_size+ ((max_size>> 1) > 1 ? (max_size>> 1) : 1);
		T* tempBuff = new T[max_size];
		memcpy(tempBuff, pBuff, sizeof(T)* length);
		if (pBuff)
			delete[]pBuff;
		pBuff = tempBuff;
	}
	pBuff[length++] = srcDate;
}

这句代码就是vector的“点睛之句”;

max_size= max_size+ ((max_size>> 1) > 1 ? (max_size>> 1) : 1);

它就为vector开辟了动态数组一说。

但是vector动态之处远远不止这一个函数之功。

动态即随意变换,故 vector座下还有assign  、pop_back等函数

其中迭代器 iterator()尤为重要;

assign函数为重分配之意,其函数也颇有意思,故在此贴出:

(1)函数原型:assign(int n, T const& srcDate)   n:重分配的数据个数,srcDate:重分配的数据。

(2)注意的点:一个合格的代码书写者第一个要考虑的就是代码的完整性,其次是安全性,再是运行速度。

         我们要考虑的有下面几种情况:

 1. 伪代码: vector<int>v; 当v为空时,运行assign(int n, T const& srcDate)函数结果如何。

2. 伪代码: vector<int>v(m); 当m<n时,运行assign(int n, T const& srcDate)函数结果如何。

3. 伪代码: vector<int>v(m); 当m>n时,运行assign(int n, T const& srcDate)函数结果如何。

4. 伪代码: vector<int>v(m); 当v容器里有数据且发生 2 或3 时,运行assign(int n, T const& srcDate)函数结果如何。

结果希望看到此篇文章的同学自行试下:

1:size:n  

     max_size:n;

2:size:n  

     max_size:当n> (max_size>>1)+max_size,取n;

                        当max_size<n< (max_size/2)+max_size  ,取 (max_size/2)+max_size;

                         当max_size<n<1+max_size, (舍弃)

3:size:n  

     max_size:m;

4,无论是否有数据,都会被替换,故有无数据运行assign函数结果均一样。

因此我们便可编写函数:

template <class T>
void CMyVector<T>::assign(int n, T const& srcDate)
{
	if (pBuff)//如果自身有内存
	{
		delete[]pBuff;//释放内存
	if (max_size< n)//最大长度小于拷贝个数
	    {
                 if ((max_size+ (max_size>> 1))<n)
		  {
		 	 max_size= n;//最大长度就是拷贝个数
		  }	 
		  else
		  {
			 max_size= max_size+ (max_size>> 1);
		  } 
            }
	}
	else
	{
		max_size= n;
	}
	pBuff = new T[max_size];//如果之前有空间在前面已经释放,所以不管怎样都重分配空间
	length= n;
	
	for (int i = 0; i < n; ++i)
	{
		pBuff[i] = srcDate;
	}
}

如果代码有错误,欢迎指正。

接下来就是pop_puck函数,非常简单,看了这个你会感叹前面的函数都是假的。

template <class T>
void CMyVector<T>::pop_back()//删除最后一个数据,不返回元素
{
	if (length)	
		length--;
}

 好了,到这里我们已经写了vector的构造、析构、添加、删除、重分配,60%的工作都差不多完成了。

当然vector还有很多函数,那些函数就不一一详述了。

一个类当然会有重载运算符,这里我就写一个函数来代表

public:
	bool operator == (CMyVector const & srcVector) const;
	bool operator != (CMyVector const & srcVector) const;

template <class T>
bool CMyVector<T>::operator==(CMyVector const & srcVector) const
{
	if (length== srcVector.length)
	{
		for (size_t i = 0; i < length; ++i)
		{
			if (pBuff[i] != srcVector.pBuff[i])
				return false;
		}
		return true;
	}
	return false;
}


template <class T>
bool CMyVector<T>::operator!=(CMyVector const & srcVector) const
{
	return !(*this == srcVector);
}

也是c++里面较为基础的东西了,相信大家使用的都如鱼得水了吧。

上面我们讲vector的时候,讲到迭代器 iterator(),那么它是什么呢?它又有什么意义?

迭代器(iterator)有时又称游标(cursor)是程序设计的软件设计模式,可在容器(container,例如链表阵列)上遍访的接口,设计人员无需关心容器的内容。

迭代器不是一种成员,它只是实现函数成员的方式。

看到这你可能还一头雾水,那我们直接用实例去理解。

在vector中,迭代器是一个结构体,有趣的是它真正参数只有一个指针,其余全是函数,故其可视为智能指针。

它的作用是给容器提供一个便捷的数据操作方式,程序、代码存在的意义就是为了“偷懒”,就是为了更快速、更有效的达到我们想要的效果,迭代器不外乎如此。

我们先看stl里的迭代器:看看他的用法:

可以清楚的看到:除了非常便捷的插入数据,迭代器的访问方式是域访问,这就代表它不是class的成员函数,

而是类中一种数据结构。

接下来看一个代码

public:
	struct MyIterator
	{
		T *pIt;
	};

这个应该很熟悉,我们当初学习结构体的时候应该学到过这个,如果c++和c都有基础,应该知道结构体和class的缘分,

 类和结构体很多时候行为是非常相似的。

上面的函数光秃秃的,就一个指针,显然我们要给它加点我们想要的东西,

public:
	struct MyIterator
	{
		T *pIt;
		MyIterator& operator=(MyIterator const& srcIt)
		{
			pIt = srcIt.pIt;
			return *this;
		}
		T operator*()
		{
			return *pIt;
		}
		MyIterator operator +(int n)
		{
			MyIterator it;
			it.pIt = pIt;
			it.pIt += n;
			return it;
		}
		bool operator != (MyIterator const &srcIt) const
		{
			return pIt != srcIt.pIt;
		}
		bool operator -(MyIterator const &srcIt) const
		{
			return pIt - srcIt.pIt;
		}
	};

是不是很熟悉这些操作,跟类没什么两样,只不过结构体函数全写成内联函数了,在c++中结构体可以就当成类用的。

如何将这个结构体与我们的vector的数据结合,其实很简单,真的,因为他们就在一起,迭代器就是嵌在容器内部的结构体(或其它)。

public:
	MyIterator begin()
	{
		MyIterator it;
		it.pIt = pBuff;
		return it;
	}
	MyIterator end()
	{
		MyIterator it;
		it.pIt = pBuff + num;
		return it;
	}

到这里,我们迭代器的基本结构就理清了,vector中自然有很多关于迭代器的函数,上面我只写了迭代器自身的一些函数,还有很多函数需要我们自己去尝试,望诸君共勉。

下面我就贴迭代器的一个模拟函数来结束这篇糟糕透顶的文章。

template <class T>
CMyVector<T>::CMyVector(MyIterator beg, MyIterator end)
{
	length=0;
        max_size= 0;
	pBuff = NULL;
	int n = 0;
	if ((n = end- beg) > 0)
	{
		max_size= length = n;
		pBuff = new T[max_size];
		for (int i = 0; i < n; ++i)
		pBuff[i] = *(beg+ i);
	}
}

再见。

猜你喜欢

转载自blog.csdn.net/yuyuchiyue/article/details/81094604