綜述
本節(jié)課的主要內(nèi)容是關(guān)于泛型數(shù)據(jù)的拷貝,雖然是使用C語言實(shí)現(xiàn),并且沒有用到C++中的模板這種泛型編程技術(shù),但是效果卻非常好。本節(jié)內(nèi)容緊接上節(jié)所將的字節(jié)位拷貝的知識,充分利用了字節(jié)拷貝技術(shù)。
筆記
由于內(nèi)容和例子不斷深入,實(shí)際核心內(nèi)容則比較集中,因此這里只進(jìn)行總結(jié)討論。
引例
本節(jié)所有的例子都是針對于數(shù)據(jù)交換來進(jìn)行的,從最簡單的例子開始,不斷深入。 開始是關(guān)于一個(gè)最簡單的整數(shù)數(shù)據(jù)的交換實(shí)例:
void swap(int a, int b){
int tmp = a;
a = b;
b = tmp;
}
此例子非常簡單,只需要構(gòu)造一個(gè)簡單的中間臨時(shí)變量tmp用來存放a的值,并且交換賦值相關(guān)的數(shù)據(jù),就可以達(dá)到交換的目的。
但是,此實(shí)例有一個(gè)缺陷,就是值傳遞,而不是引用傳遞,這樣,傳遞的值雖然改變,但是只想原始變量的單元卻沒有改變,具體來說就是:
a = 23;
b = 34
swap(a, b);
執(zhí)行上面一段語句會(huì)發(fā)現(xiàn),其實(shí)a,b的值并沒有交換,原因和C/C++的參數(shù)的值傳遞以及指針傳遞有關(guān)系。函數(shù)調(diào)用的時(shí)候,只會(huì)拷貝a,b的值,因此調(diào)用swap的時(shí)候,交換的是形參,實(shí)際參數(shù)的值并沒有改變。
要實(shí)現(xiàn)真正參數(shù)傳遞的效果,需要用指針的形式來實(shí)現(xiàn):
void swap(int *vp1, int *vp2) {
int a = *vp1;
*vp1 = *vp2;
*vp2 = *vp1;
}
再次調(diào)用swap(&a, &b)的時(shí)候,就會(huì)修改掉原來的值,因?yàn)檫@里傳遞過去的就是指針,所以,對vp1,vp2的操作 就是對指向單元a,b的操作,所以能夠修改對應(yīng)的值。
泛型交換與拷貝
上面的例子,只是對某種特定的類型進(jìn)行交換,比如int類型,如果想對double類型等進(jìn)行交換,只需要修改其類型為double即可,其他類型類似。 但是考慮到需要對多種不同類型進(jìn)行交換,是否存在一種通用的方法呢? 在C++中,可以用模板template技術(shù),然而這里,回想起上節(jié)課中講到的字節(jié)操作,能否利用字節(jié)的拷貝來實(shí)現(xiàn)呢?答案是肯定的。
void swap(void *vp1, void *vp2, int size){
char buffer[size];
memcpy(buffer, vp1, size);
memcpy(vp1, vp2, size);
memcpy(vp2, buffer, size);
}
調(diào)用的時(shí)候,字號需要給定某個(gè)類型,即可實(shí)現(xiàn)。比如,通過:
double a = 23.0, b = 34.0;
swap(&a, &b, sizeof(double));
當(dāng)然,對于結(jié)構(gòu)體也可以通過這種形式來進(jìn)行拷貝。
關(guān)于上面例子的幾點(diǎn)說明:
- 這里聲明數(shù)組的方式,使用的大小size是可變的,這只在某些編譯器中支持,這里只是為了說明字符拷貝的方式,例子的重點(diǎn)在于交換。當(dāng)然可以使用malloc或者new來動(dòng)態(tài)分配大小可變的空間。
- 這里使用memcpy(dest, src, size)這個(gè)函數(shù)來進(jìn)行內(nèi)存單元的拷貝,注意此函數(shù)并不關(guān)心你的數(shù)據(jù)類型,單純的進(jìn)行單元的拷貝而已,所以雖然編譯可能通過,但是還需要自己進(jìn)行判斷和控制。
- 這個(gè)例子的亮點(diǎn)就在于void*的使用,通過它能夠?qū)崿F(xiàn)泛型,即針對于int,short,char,struct等類型都能夠保證能夠拷貝交換成功。
- template和這里的區(qū)別和優(yōu)缺點(diǎn)。注意到使用模板的話,編譯后,會(huì)為每種類型都生成一種代碼,比如int對應(yīng)的,float對應(yīng)的,這樣如果調(diào)用次數(shù)很多的話,代碼體積會(huì)增大,冗余過多。而這里編譯出來就一套代碼,更加簡潔。
- 這里傳遞一個(gè)參數(shù)大小size是因?yàn)椋捎诜盒椭羔榲oid*的存在,所以編譯器并不知道要拷貝多少個(gè)字節(jié),所以,需要你手動(dòng)控制并指定一個(gè)大小。
存在的問題
由于編譯器會(huì)很容易的放過void*帶來的錯(cuò)誤,所以如果兩個(gè)類型不同的數(shù)據(jù)調(diào)用此函數(shù),就會(huì)出現(xiàn)問題:
double a = 23.0;
int b = 345;
swap(&a, &b, sizeof(double));
這里,double和int類型占據(jù)的數(shù)據(jù)空間的大小是不同的,因此,如果單純的直接調(diào)用這個(gè)函數(shù),就會(huì)出錯(cuò),簡單的結(jié)果就是,截?cái)嗫截惢蛘叨嗫截悢?shù)據(jù)。 比如,int類型拷貝到double數(shù)據(jù)空間的時(shí)候,只有前面2個(gè)字節(jié)拷貝了,后面的原來double數(shù)據(jù)的兩個(gè)字節(jié)仍然保留了;或者說double拷貝到int的時(shí)候,可能會(huì)多拷貝兩個(gè)字節(jié)到int后面的數(shù)據(jù),造成出錯(cuò)。具體的方式,與后面一個(gè)參數(shù)sizeof(double)或者sizeof(int)有關(guān)系。
初學(xué)者容易犯的錯(cuò)誤
- 使用void * tmp = vp1,而不是前面鎖講到的char buffer[size],這個(gè)錯(cuò)誤的原因是由于不理解void不是一個(gè)類型,不像int,double等屬于一個(gè)類型,所以錯(cuò)誤。void 只能用作函數(shù)參數(shù),返回值才可行。但是可以使用 void * tmp = (int )&a這類的用法,因?yàn)榫唧w的類型即可以賦值給一般的類型,你只有給定了一個(gè)具體的類型,才能讓編譯器知道規(guī)則,才能編譯通過。
- 指針的拷貝,何時(shí)使用引用地址的問題。一個(gè)簡單的例子出發(fā),
char * husband = strdup("Fred"); char * wife = strdup("Wilma");
如果想交換兩人所指向的空間內(nèi)容,正確的做法是:
swap(&hustband, &wife, sizeof(char *))
也就是說,這里要交換的是指針的地址,交換之后,husband的內(nèi)容發(fā)生了變化,內(nèi)容變成了原來wife的內(nèi)容,由于本身是地址,所以內(nèi)容變了,實(shí)際上所只想的地址也變了,現(xiàn)在husband指向原來wife所指向的地址,而wife指向原來husband指向的地址。
一個(gè)錯(cuò)誤的例子就是,swap(husband, wife, &sizeof(char *)),這樣,交換的實(shí)際上是他們鎖指向的內(nèi)容,即存放Fred和Wilama的單元中的內(nèi)容會(huì)交換,而且,由于char *是四個(gè)字節(jié),因此交換的就只有四個(gè)字節(jié)的內(nèi)容。 為何會(huì)如此呢?因?yàn)樯厦娴睦樱热缫粨Qa,b單元的內(nèi)容,傳入的就是a,b的地址&a, &b,同樣,這里我直接傳入指針,當(dāng)然交換的是他們指向的單元的內(nèi)容,即兩個(gè)字符串。 所以要交換兩個(gè)指針的內(nèi)容,就要交換他們的地址,即指針的地址,指針的指針。
另外一個(gè)例子
思考一個(gè)下面線性搜索的例子,
int * lsearch(int key ,int* array, int size){
for (int i = 0; i < size; i++)
{
if(array[i] == key)
return i;
}
}
上面的這段代碼,直接返回的就是找到索引的那個(gè)下標(biāo)。
利用位比較的方式來實(shí)現(xiàn)
同樣,為了應(yīng)用上面我們學(xué)到的知識,這里想要泛型比較,搜索,如何實(shí)現(xiàn)? 例如,對于這里的int,能否用一個(gè)struct,一個(gè)double或者其他類型。 答案仍然是肯定的,只不過,我們需要對其中編譯器的工作,比較的大小進(jìn)行控制而已。
int *lsearch(void *key, void *base, int size, int elementSize){
for (int i = 0; i < size; i++) {
void * elemeAddr = (char *)base + i * elementSize;
if (memcmp(key, elemeAddr, elementSize) == 0)
return elemeAddr;
}
}
這里的幾個(gè)說明點(diǎn)就是,首先,傳入?yún)?shù)的size就是要比較的數(shù)組的大小,類型我們不知道,就用void *類型,然后要傳入每一個(gè)類型的大小,elementSize,這個(gè)標(biāo)記了每一個(gè)數(shù)組成員的大小,正因?yàn)橛羞@個(gè)我們才可以精準(zhǔn)的定位到具體的單元,利用for循環(huán)來比較每一個(gè)單元和key的關(guān)系。而這里比較用的memcmp來進(jìn)行,比較的字節(jié)數(shù)就是elementSize,傳入兩個(gè)指針即可,而比較的指針就是數(shù)組的每一個(gè)單元的地址,即elemeAddr而已。
---Written by markdown and HTML file is generated by markdown.
posted on 2012-06-24 16:57
deercoder 閱讀(3289)
評論(0) 編輯 收藏 引用 所屬分類:
大學(xué)公開課