為了做到能操作所有類型的數(shù)據(jù),我參看了幾個(gè)類似的C語言的庫,基本上就是兩種做法:一是使用宏,二是使用void*指針。最后我選擇了后者,原因很簡單,我不是一個(gè)很推崇在代碼中大量使用宏的人,一方面覺得這樣作會(huì)讓代碼的可讀性降低,另一方面我也確實(shí)不是寫宏的高手。
在CGL中,有以下的幾個(gè)typedef都把void*定義為某種類型:
typedef void* container_t;
typedef void* point_t;
typedef void* data_t;
分別作一個(gè)解釋,container_t表示的是指向容器的指針,point_t表示的是通用指向某容器的指針,不論是指向數(shù)組成員的指針還是一個(gè)鏈表結(jié)點(diǎn)的指針都可以"泛化"的表示為"pos_t",而data_t表示的是存放數(shù)據(jù)的指針,之所以要對同樣可以表示為是void*的指針分三個(gè)類型的typedef,目的是為了在代碼中一目了然,看到類型的名字就能知道是作什么用的了。
container_t的含義很好理解,現(xiàn)在對后面兩種類型作一下解釋。
原本pos_t不叫pos_t的,而是被定義為iter_t,因?yàn)樵赟TL中迭代器其實(shí)就是一個(gè)行為很像指針的東東,可以解引用,可以遞增指向下一個(gè)元素,遞減指向前一個(gè)元素,等等。但是需要注意的時(shí)候,由于C++中可以重載操作符,如*,++,--這樣的操作符都可以被重載以至于一個(gè)iterator的行為看上去和一個(gè)普通的指針沒有什么區(qū)別。但是在CGL中,是完全采用的C語言實(shí)現(xiàn)的,沒有辦法做到重載這些操作符,所以我專門提供了一個(gè)叫做iteraotr_t的結(jié)構(gòu)體,里面有函數(shù)指針成員可以實(shí)現(xiàn)以上這些重載操作符所需要作的事情(后面會(huì)有專門的一節(jié)來講述這個(gè)結(jié)構(gòu)體以及CGL中迭代器的設(shè)計(jì)),所以如果有一個(gè)類型為iter_t一個(gè)類型為iterator_t會(huì)不會(huì)讓人混淆呢?至少我偶爾回頭看我的代碼的時(shí)候是會(huì)弄混的,因此我決定把iter_t更名為pos_t也就是位置的意思。
data_t用于保存存放數(shù)據(jù)的指針,這里有幾個(gè)問題需要交待一下。首先是這樣作的弊端,雖然這樣避免前面提到的大量使用宏的缺點(diǎn),但是卻浪費(fèi)了存儲(chǔ)的空間以及會(huì)帶來一些使用上的不方便。先說浪費(fèi)了存儲(chǔ)空間,以往存放一個(gè)數(shù)據(jù)只需要一個(gè)與該數(shù)據(jù)相同大小的空間就可以了,但是現(xiàn)在還需要多使用一個(gè)data_t指針指向分配好的空間,無形之中浪費(fèi)了一個(gè)指針的空間。再說使用的不方便,以往處理數(shù)據(jù)的時(shí)候如果沒有特別的要求可以直接傳值,而現(xiàn)在必須傳指針,因?yàn)镃GL的函數(shù)不認(rèn)什么int,double,char類型,只處理指針。換句話說,假如f是CGL中的一個(gè)函數(shù),如果要調(diào)用傳入一個(gè)整型參數(shù)5,你必須這樣作:
int nVal = 5;
f(&nVal);
而一般傳值就可以做到的調(diào)用是f(5)就可以了,這樣會(huì)造成使用上的不方便。
至于數(shù)據(jù)的賦值,我采用的C庫中memcpy函數(shù),只要傳入指向數(shù)據(jù)的指針和數(shù)據(jù)的尺寸就可以,比較數(shù)據(jù)是否相等則采用C庫中的memcmp函數(shù),所需要知道的參數(shù)和memcpy一樣,而當(dāng)需要比較數(shù)據(jù)的大小時(shí),這個(gè)比較頭疼,因?yàn)镃庫中沒有根據(jù)指針和數(shù)據(jù)的大小進(jìn)行比較的函數(shù),我在后面會(huì)解釋我現(xiàn)在處理此類問題的辦法。
再來說說其他的兩個(gè)typedef:
typedef char* base_t;
typedef char bool_t;
最后的一個(gè)bool_t很好理解,就是一般的bool型嘛,之所以用char很簡單,char類型所需要的字節(jié)數(shù)最少,省空間。而base_t這個(gè)類型的含義是一個(gè)系統(tǒng)中最基本的數(shù)據(jù)類型,或者可以這么理解,別的數(shù)據(jù)類型所占有的字節(jié)數(shù)都可以表示為這個(gè)類型的算術(shù)操作,以上的言語也許晦澀了一些,我用例子來說明。
看CGL中一個(gè)函數(shù)的實(shí)現(xiàn):
static point_t cgls_iter_advance(piterator_t pIter, size_t n)
{
base_t tTmp;

CGL_ASSERT(NULL != pIter);
CGL_ASSERT(0 <= n);

tTmp = (base_t)(pIter->tPoint);
pIter->tPoint = tTmp + pIter->nValSize * n;
return pIter->tPoint;
}

這個(gè)函數(shù)的作用是把迭代器pIter中保存的指向容器中數(shù)據(jù)的指針tPoint向前移動(dòng)n個(gè)位置,大家知道指針的移動(dòng)和它所指向的數(shù)據(jù)類型的大小有密切的關(guān)系,換句話說一個(gè)指針向前走n個(gè)位置所要移動(dòng)的字節(jié)數(shù)為n * 它所指向的數(shù)據(jù)的尺寸,在上面的函數(shù)中,tPoint這個(gè)指針?biāo)赶虻臄?shù)據(jù)的尺寸存放在pIter的成員變量nValSize中,你也許會(huì)問直接使用sizeof(*tPoint)不就可以得到這個(gè)數(shù)值了么?別忘了我們前面說過所有的指針類型都是void*,而對void*指針是不能進(jìn)行解引用操作的,所以我們需要一個(gè)變量來存放數(shù)據(jù)的尺寸。
注意到函數(shù)中的兩個(gè)操作:
tTmp = (base_t)(pIter->tPoint);
pIter->tPoint = tTmp + pIter->nValSize * n;
結(jié)合著base_t的定義,可以解釋為把void*指針tPoint強(qiáng)制轉(zhuǎn)化為char*,而tPoint向前走的位置為tTmp + nValSize*n,對于tTmp而言,它的類型是base_t也即是char*,sizeof(char) = 1,因此采用char*來保存以及進(jìn)行指針的加減操作是最自然的操作,只要我們知道需要前進(jìn)的步數(shù)(n),每部的幅度(nValSize),就可以通過把指針強(qiáng)制轉(zhuǎn)化為base_t也就是char*來達(dá)到我們所要到達(dá)的位置。
以上,是我對目前CGL中幾個(gè)typedef的解釋。可以看到的是,設(shè)計(jì)中總是存在著這樣那樣的折中,很多地方的處理也是不完美的,我選擇的是不向宏妥協(xié)而是自己對指針進(jìn)行處理和操作。