簡單寫一下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);
	}
}

再見。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章