在ctags中,vString分佈在`vString.h`和`vString.c`兩個文件中,代碼很是簡潔,加起來僅有300餘行,是初學c語言與動態字符串一個很好研究的範例。
sql
在vString.h中,vString定義以下:
安全
struct sVString { size_t length; /* size of buffer used */ size_t size; /* allocated size of buffer */ char *buffer; /* location of buffer */ };
這也差很少是絕大多數動態字符串的實現方式。函數
buffer存儲內容,size存儲實際內容長度,按理說這樣兩個成員已經能夠起到vString的做用了,可是爲了速度起見,仍是增長了length變量,使得每次增長新內容時不是分配一個單位大小,而是到達上限後一次分配多個單位。測試
爲方便起見,在定義下面的函數的時候,若是一個參數是vString,另外一個參數能夠是vString也能夠是char *的,統一隻定義了vString版本,由於vString.buffer自己就是一個傳統的char* 字符串。在這裏經過宏,構造出char* 版本:spa
#define vStringValue(vs) ((vs)->buffer) #define vStringCat(vs,s) vStringCatS((vs), vStringValue((s)))
凡是帶exxx的函數(如eFree),其實是在原函數(如Free)上加上了相應的檢查,若是有錯誤就報錯,這種技術在sqlite中也有使用。指針
帶x前綴的函數是本身實現的方便書寫的宏。
調試
#define xMalloc(n,Type) (Type *)eMalloc((size_t)(n) * sizeof (Type)) //分配n個Type類型並初始化 #define xCalloc(n,Type) (Type *)eCalloc((size_t)(n), sizeof (Type)) #define xRealloc(p,n,Type) (Type *)eRealloc((p), (n) * sizeof (Type))
#ifdef DEBUG # define DebugStatement(x) x #else # define DebugStatement(x) #endif
括號內的語句只有在定義了DEBUG宏時纔會被編譯,這樣對於調試單條語句減小了代碼量。
code
新建一個vString並返回指針。sqlite
vString *vStringNew (void) { vString *const string = xMalloc (1, vString); string->length = 0; string->size = vStringInitialSize; string->buffer = xMalloc (string->size, char); vStringClear (string); return string; }
vStringInitialSize是32,就像標準庫sort的那個常數同樣是實驗出來的。太大佔用空間,過小開始時刻須要頻繁分配內存影響速度。內存
vString *const,後置const不容許修改指針自己,涉及到指針操做在定義時就應該給予其最小的權限,這樣即便誤操做也有可能被編譯器檢查出來。
清空vString。
void vStringClear (vString *const string) { string->length = 0; string->buffer [0] = '\0'; DebugStatement ( memset (string->buffer, 0, string->size); ) }
以char*的一般規範,首位填\0,長度清空便可。注意這裏clear的是buffer的大小,對字符串自己的size沒有變更。默認狀況下不用buffer清零,由於以後使用該部分時必然是先賦值的。
自動根據buffer中實際內容大小設置size。
void vStringSetLength (vString *const string) { string->length = strlen (string->buffer); }
使用strlen,避免重複造輪子。
完全清空vString空間。
void vStringDelete (vString *const string) { if (string != NULL) { if (string->buffer != NULL) eFree (string->buffer); eFree (string); } }
預先檢查,防止重複清空。
給buffer分配新的大小。
void vStringResize (vString *const string, const size_t newSize) { char *const newBuffer = xRealloc (string->buffer, newSize, char); string->size = newSize; string->buffer = newBuffer; }
不然可能會發生實際給buffer賦值時賦值到了未定義區段,形成程序崩潰或內存被篡改等問題。
realloc有可能新的指針和舊指針同樣,也有可能不一樣。
精華。決定了一次分配多大空間。
boolean vStringAutoResize (vString *const string) { boolean ok = TRUE; if (string->size <= INT_MAX / 2) { const size_t newSize = string->size * 2; vStringResize (string, newSize); } return ok; }
若是有空間則分配當前空間的兩倍。
這個也屬於一些經驗之道,由於通常來講,一個字符串自己的大小越大,它須要更大空間可能性越大,這個方法也在這裏使用。
新增長一個字符。
注意到因爲這個函數用得很是頻繁,爲了減小調用開支,定義了一個宏版本。
void vStringPut (vString *const string, const int c) { if (string->length + 1 == string->size) /* check for buffer overflow */ vStringAutoResize (string); string->buffer [string->length] = c; if (c != '\0') string->buffer [++string->length] = '\0'; }
#define vStringPut(s,c) \ (void)(((s)->length + 1 == (s)->size ? vStringAutoResize (s) : 0), \ ((s)->buffer [(s)->length] = (c)), \ ((c) == '\0' ? 0 : ((s)->buffer [++(s)->length] = '\0'))) #endif
在string->length+1==string->size時,因爲有末尾\0實際上存儲已滿,故增大buffer大小。
注意不能添加'\0',不然結果會錯誤。
宏版本用,運算符精煉地鏈接了多個語句,用?來進行if的做用。
將s的內容增長到string的後面。
void vStringCatS (vString *const string, const char *const s) { #if 1 const size_t len = strlen (s); while (string->length + len + 1 >= string->size)/* check for buffer overflow */ vStringAutoResize (string); strcpy (string->buffer + string->length, s); string->length += len; #else const char *p = s; do vStringPut (string, *p); while (*p++ != '\0'); #endif }
後面那一段是本來註釋掉的內容,仍是一句話:strcpy這些標準庫內置的東西,在實現時都是通過反覆測試的,在絕大多數狀況下比本身寫的要快要安全,不要重複造輪子。
爲何不用strcat?
由於在這裏, string的結束地址是已知的(咱們已經記錄了length),strcat會從頭掃一遍string,效率過低。
將s的前length個字符拷貝到string中。
void vStringNCatS ( vString *const string, const char *const s, const size_t length) { const char *p = s; size_t remain = length; while (*p != '\0' && remain > 0) { vStringPut (string, *p); --remain; ++p; } vStringTerminate (string); //在buffer末尾添加'\0' }
這裏爲何不使用strncpy?動態擴容當然是一方面,但若是咱們一開始就擴容好呢?咱們看一下strncpy的狀況:
If count
is reached before the entire string src
was copied, the resulting character array is not null-terminated.
If, after copying the terminating null character from src
, count
is not reached, additional null characters are written to dest
until the total of count
characters have been written.
簡而言之:
在length<s長度時,strncpy不會添加'\0'
在length==s長度時,strncpy會添加'\0'
在length>s長度時,strncpy會添加多個'\0'
而咱們是要始終末尾有'\0'。雖然咱們能夠在末尾強制加一個'\0'快速解決問題,但在狀況3時,效率低。