首頁 > 軟體

C++模擬實現vector的範例程式碼

2022-08-19 22:02:58

一、迭代器

定義

vector型別的迭代器就是原生態的指標,對T*進行重新命名即可

typedef T* iterator;
typedef const T* const_iterator;

普通迭代器

iterator begin()
{
    return start;
}
iterator end()
{
    return finish;
}

const型別迭代器

const型別迭代器可以存取const成員變數

const iterator cbegin()const
{
    return start;
}
const iterator cend()const
{
    return finish;
}

二、構造類

建構函式

構造空物件

在初始化列表中對三個成員變數進行初始化

vector()
    :start(nullptr)
    , finish(nullptr)
    , endOfStorage(nullptr)
{}

n個T型別

開闢空間以後,對finish進行自增,在空間填充元素

vector(size_t n, const T& value = T())
    :start(new T[n])
    , finish(start)
    , endOfStorage(start + n)
{
    for (int i = 0; i < n; i++)
    {
        *finish++ = value;
    }
}

過載前一個建構函式,將第一個引數設定為int型別

vector(int n, const T& value = T())
    :start(new T[n])
    , finish(start)
    , endOfStorage(start + n)
{
    for (int i = 0; i < n; i++)
    {
        *finish++ = value;
    }
}

之所以要對這種型別的建構函式進行過載,是因為在呼叫建構函式時,如果實參傳兩個整型數位,編譯器會預設為int型別資料,進行推演之後與前面的size_t型別不匹配,則會呼叫下面的區間構造的方法,導致程式報錯,如圖:

迭代器構造

將構造方法中迭代器的型別寫成模板型別,這樣便可以接收其它型別的迭代器,如:T型別為char,Iterator迭代器為string型別,便可以從字串中擷取字元,構造vector<char>型別的物件。

//寫成函數模板,可以接受任意型別的迭代器
template<typename Iterator>
vector(Iterator first, Iterator last)
{
    size_t n = ZH::distance(first, last);//獲取長度
    start = new T[n];
    finish = start;
    endOfStorage = start + n;
    while (first != last){
        *finish = *first;
        first++;
        finish++;//完成賦值的同時也移動了finish的位置
    }
}

將distance方法寫到另一個.hpp標頭檔案中

template<typename Iterator>
//此處的Iterator是模板引數,表示可以傳任意型別的迭代器
size_t distance(Iterator first, Iterator last)
{
    //獲取元素個數,暫時只考慮底層空間連續的情況
    int count = 0;
    while (first != last)
    {
        first++;
        count++;
    }
    return count;
}

拷貝建構函式

拷貝建構函式的形參必須是const類物件的參照,必須使用const型別的迭代器才能存取,複用迭代器構造的方法定義一個臨時變數temp,交換temp與當前物件

//此處拷貝建構函式的形參是const型別
vector(const vector<T>& v)
    :start(nullptr)
    , finish(nullptr)
    , endOfStorage(nullptr)
{
    //▲用const型別的迭代器存取const變數
    vector<T> temp(v.cbegin(), v.cend());
    this->swap(temp);
}

賦值運運算元過載

形參設定為類型別物件,呼叫賦值運運算元過載函數時,形參會拷貝實參,交換當前物件與形參的值。

vector<T>& operator=(const vector<T> v)
{
    this->swap(v);
    return *this;
}

解構函式

釋放空間,將三個迭代器賦值為空

~vector()
{
    delete[]start;
    start = nullptr;
    finish = nullptr;
    endOfStorage = nullptr;
}

三、容量相關操作

size、capacity

size_t size()
{
    return finish - start;
}
size_t capacity()
{
    return endOfStorage - start;
}

empty

判斷fiinsh與start是否相等即可,相等則為空

size_t empty()
{
    return finish == start;
}

resize

定義一個變數儲存舊的size的值‘判斷是減小還是增加size;判斷是否需要擴容,需要則呼叫reserve函數,從舊空間的結束位置開始,給新增加的空間填充元素;最後改變finish的值。

void resize(size_t newsize, const T& value = T())
{
    size_t oldsize = size();
    if (newsize > oldsize){
        if (newsize > capacity()){
            reserve(newsize);
        }
        for (size_t i = oldsize; i < newsize; i++)
        {
            start[i] = value;
        }
    }
    finish = start + newsize;//不用考慮增加或減小
}

reserve

reserve的步驟:申請新空間,拷貝舊空間的元素,釋放舊的空間。

void reserve(size_t newcapacity)
{
	size_t oldcapacity = capacity();
	if (newcapacity > oldcapacity)
	{
		size_t n = size();//儲存size()的值
		T* temp = new T[newcapacity];
		//start不為空時才進行拷貝舊空間元素和釋放的操作
		if (start)
		{
			//memcpy淺拷貝,當vector中存放的物件內部設計資源管理
			// 會有記憶體漏失和野指標問題
			//memcpy(temp, start, sizeof(T) * n);

			for (size_t i = 0; i < n; i++)
			{
				temp[i] = start[i];//呼叫賦值運運算元過載
			}
			delete[] start;
		}
		start = temp;
		//▲此處不能用satart+size(),因為size方法中有finish-start,而start值已經改變
		finish = start + n;
		endOfStorage = start + newcapacity;
	}
}

易錯點:

判斷start的值是否為空 ,如果原來的start為空,則不需要再拷貝元素和釋放

淺拷貝問題

finish更新問題

size()的方法內部finish-start,而此時start已經發生改變,finish還是舊的,所以要提前定義一個臨時變數儲存size()的值

三、元素存取

[ ]過載

過載成普通型別和const型別,const型別可以存取const成員

T& operator[](size_t index)
{
	assert(index < size());
	return start[index];
}

const T& operator[](size_t index)const
{
	assert(index < size());
	return start[index];
}

front

返回動態陣列第一個元素

T& front()
{
    return start[0];
}
const T& front()const
{
    return start[0];
}

back

返回最後一個位置前一個元素

T& back()
{
    return *(finish - 1);
}
const T& back()const
{
    return *(finish - 1);
}

四、修改類介面

push_back

插入前先判斷空間是否已滿,空間若滿則進行擴容,擴容時,要原來的空間容量為0的情況;將value放置到末尾位置,並將finish向後移動一個單位

void push_back(const T& value)
{
    if (finish == endOfStorage)
    {
        //因為原來的capacity可能為0,所以要+3
        reserve(capacity() * 2 + 3);
    }
    *finish++ = value;
}

pop_back

尾刪,先判斷物件是否為空,若不為空則將finish位置前移一個單位

void pop_back()
{
    if (empty())
    {
        return;
    }
    finish--;
}

insert

任意位置插入,insert的返回值為新插入的第一個元素位置的迭代器;因為插入可能會進行擴容,導致start的值改變,所以先定義一個變數儲存pos與start的相對位置;判斷是否需要擴容;從插入位置開始,將所有元素向後搬移一個位置;將pos位置的值置為要插入的值;更新finish的值。

//第二個引數用const修飾,常數參照
//不用const修飾則為非常數參照
iterator insert(iterator pos, const T& value)
{
    int index = pos - start;
    assert(pos >= start && pos < finish);
    //判斷空間是否足夠
    if (finish == endOfStorage)
    {
        reserve(capacity() * 2);
    }
    pos = start + index;
    for (auto it = finish; it > pos; it--)
    {
        *it = *(it - 1);
    }
    *pos = value;
    finish++;
    return pos;
}

erase

判斷下標合法性;從pos位置下一個位置開始,將所有元素向前搬移一個位置;更新finish的值

iterator erase(iterator pos)
{
	assert(pos >= start && pos < finish);

	auto it = pos;
	while (it < finish - 1)
	{
		*it = *(it + 1);
		it++;
	}
	finish--;
	return pos;
}

clear

清空所有元素,令finish=start即可

void clear()
{
    finish = start;
}

swap

vector內建的swap函數,呼叫標準庫中的swap交換vector的三個成員變數的值

void swap(vector<T>& v)
{
    std::swap(start, v.start);
    std::swap(finish, v.finish);
    std::swap(endOfStorage, v.endOfStorage);
}

五、成員變數

private:
    iterator start;
    iterator finish;
    iterator endOfStorage;

vector內部有三個成員變數,start表示起始位置,finish表示有效元素的末尾位置,endOfStorage表示空間的末尾位置;通過這三個成員變數可以得到size和capacity等值,如圖:

以上就是C++模擬實現vector的範例程式碼的詳細內容,更多關於C++ vector的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com