C語言實現乙個泛型的vector

2022-05-02 20:09:10 字數 3486 閱讀 9502

使用純$c$語言實現乙個泛型的$vector$,支援拷貝構造和移動構造。

$vector$是動態的陣列,因此我們儲存$vector$申請的記憶體塊的指標,此外我們需要兩個$size$_$t$型別的數儲存當前開闢的空間和當前已經存有的元素個數。故需要乙個我們定義以下的$vector$結構體:

struct vector

;

由於我們設計的是泛型$vector$,$t$的型別不定,所以乙個元素占用的記憶體不定,所以這個記憶體塊的大小就不能,考慮兩種方案:

在$vector$中加入乙個$u$_$size$表示元素的大小。

那我們就可以設計成這樣子:

struct vector

;

這樣子$push$_$back$或者是複製$vector$的時候就可以計算出$malloc$的空間是$n*u$_$size$。

但是有兩個問題:

考慮這個結構體:

struct t

;

在$push_back$的時候,就會有以下問題:

複製前:

複製後:

如果我們需要複製這個$vector$,也會有類似的問題:

複製前:

複製後:

顯然這個$buf$存在危險,它會被幾個指標訪問,可能會造成資料的錯誤。(c裡面沒有$unique$_$ptr$和$shared$_$ptr$)。

同時,析構的時候,$buf$這一塊記憶體極易引發記憶體洩漏或者是重複釋放。

那我們如何改進呢:

如果在$push$_$back$的時候傳入建構函式的函式指標,如:

$size$_$t$ $push$_$back(vector*$ _$vec,$ $const$ $t* $ _$dat,$ $t*(*copy$_$assign)(const$ $t*$ _$src))$。然後在$push_back$函式中呼叫$copy$_$assign$,這樣子就可以成功解決上面的問題一。析構的時候也是一樣的道理,傳入析構函式的函式指標即可。在複製和銷毀$vector$的時候,我們也是同樣的傳入這些函式指標。就解決了問題二。

這個方案已經夠好了,但是,它太麻煩了,每次都要傳函式指標。

我們為什麼不把它儲存下來呢,然後$vector$不就可以自動呼叫了嘛?

有一說一,確實。

這樣子我們設計一下這個結構體:

struct vector

;

這樣子就可以了,實際上是空間換時間的策略。

但是如果函式指標多起來了,這個$vector$結構體在初始化這些函式指標的時候,就會非常麻煩,比如說你要加上移動建構函式,或者其他函式的時候。

所以我們就把它封裝一下好了。

struct data_arg

;struct vector

;

這樣子需要修改函式指標的時候直接修改$dat$_$arg$就可以了。

我們使用乙個$void*$去指向這些元素,這樣子無論元素是什麼是什麼型別的我們都可以指向它。

我們設計以下的結構體:

struct vector

;

顯然,方案一上的兩個問題,方案二依然存在。而且無論如何,複製的時候一樣需要知道元素的大小。

所以我們就集思廣益,把方案一的操作搬下來。

struct vector

;

這樣子我們也解決這兩個問題。

$q$:這兩個方案既然都行,那我們隨便選乙個是不是就可以了。

$a$:大多數情況是這樣的。但是我們可以注意到:方案一少了一層指標定址,效率會更高,但是方案一進行移動構造的時候,它實際上相當於是$memcpy$。而方案二雖然需要進行多一層定址,但是移動的時候可以直接把指向這個元素的指標賦給$vector$。但是其他情況仍需重寫函式。所以根據應用場景靈活選擇方案即可。

接下來的舉例實現使用第二種方案。

那我們開始實現吧:

結構體定義,不說了。

struct data_arg

;struct vector

;

然後我們開始設計函式:

(注:$catch$_$exec$是我的專案的乙個異常處理的庫,看官自行忽略就好)

首先是建立:

vector* vec_init(data_arg _dat_arg)

; return ptr;

}

比較簡單,不說了。

然後實現呼叫資料的拷貝建構函式和析構函式的函式:

void* vec_new_data(vector* _vec, const void* _dat)

return _vec->dat_arg.assign(_dat);

}void* vec_delete_data(vector* _vec, void* _dat)

注:我自己的設計是如果是不含指標的結構體,就直接複製記憶體,這樣子拷貝建構函式和析構函式指標都是$null$,效率高一點。

然後是拷貝建構函式:

void* vec_assign(const void* _vec)

然後是清空和析構函式二人組:

void vec_clear(vector* _vec)

void* vec_destroy(void* _vec)

然後剩下的功能自己實現一下就完事了。

檢查容量:

void vec_check_capacity(vector* _vec)

重設容量:

size_t vec_resize(vector* _vec, size_t _size)

$push$_$back$(拷貝),$push$_$back$_$no$_$copy$(移動)

size_t vec_push_back(vector* _vec, void* _dat)

size_t vec_push_back_no_copy(vector* _vec, void* _dat)

$pop$_$back$(析構),$pop$_$back$_$no$_$delete$(不析構)

size_t vec_pop_back(vector* _vec)

size_t vec_pop_back_no_delete(vector* _vec)

注:沒有檢查邊界,看官自己加上。

感謝大家!

全部源**看這裡。這是我自己用純$c$語言實現的乙個$c$語言子集的詞法和語法分析器專案,裡面需要使用泛型$vector$,故設計。

C語言實現泛型程式設計

泛型程式設計讓你編寫完全一般化並可重複使用的演算法,其效率與針對某特定資料型別而設計的演算法相同。在c語言中,可以通過一些手段實現這樣的泛型程式設計。這裡介紹一種方法 通過無型別指標void 看下面的乙個實現交換兩個元素內容的函式swap,以整型int為例 void swap int i1,int ...

c語言 實現乙個棧

include include include include 入棧出站操作 typedef struct nodenode,pnode typedef struct stack pstack,stack void init pstack void push pstack,int bool isem...

用泛型實現乙個堆疊類

在linkedstack類中定義了乙個node靜態內部類,根據類載入機制,node類會在linkedstack初始化時被載入。public class linkedstack node t item,node next boolean end 棧頂元素 private node top newnod...