課程地址
????前4個(gè)lecture主要就講了指針肮帐,不同類型之間轉(zhuǎn)換時(shí)的內(nèi)存是如何變化的。下面將通過描述通用swap函數(shù)的執(zhí)行過程記錄下我學(xué)習(xí)課程的收獲。
????顧名思義翘骂,swap函數(shù)是將傳入的兩個(gè)對(duì)象的值進(jìn)行交換棋弥,在c++中核偿,有現(xiàn)成的使用模板實(shí)現(xiàn)的swap函數(shù)可以直接調(diào)用,當(dāng)然自己實(shí)現(xiàn)一個(gè)也不難顽染,使用引用可以很方便地實(shí)現(xiàn)對(duì)象之間內(nèi)容的交換宪祥,但是在c中,就只能使用指針實(shí)現(xiàn)家乘。話不多說蝗羊,直接開始吧。
版本1.0
void swap(int *pa,int *pb)
{
int temp = *pa;
*pa = *pb;
*pb = temp;
}
接下來我們來看看調(diào)用函數(shù)時(shí)到底發(fā)生了什么仁锯,首先我們應(yīng)該明白的是傳入函數(shù)的參數(shù)是什么耀找,兩個(gè)指針,指針是什么?指針本質(zhì)上可以理解為存地址的變量(你心里可能會(huì)產(chǎn)生疑問:既然指針都是存地址野芒,那為什么指針還有類型之分蓄愁,難道地址還有不同的類型?狞悲,后面會(huì)解釋這個(gè)問題)撮抓,它和int,double類型的變量本質(zhì)上其實(shí)沒有什么區(qū)別摇锋,都是變量丹拯,都用來存儲(chǔ)信息,但是荸恕,為什么指針能讓人望而生畏乖酬?慢慢看下去你就會(huì)發(fā)現(xiàn)它的獨(dú)特之處。
下面我們通過圖來理解下swap 1.0函數(shù)里到底發(fā)生了什么融求,
????a,b表示兩個(gè)想要被交換的int類型的數(shù)咬像,pa,pb表示兩個(gè)指針變量生宛,這里為什么使用箭頭將pa县昂,pb指向a,b的起始地址陷舅,因?yàn)閜a七芭,pb的值,其實(shí)就是a蔑赘,b變量在內(nèi)存空間的起始地址狸驳。在函數(shù)中
int temp = *pa
,我們定義了一個(gè)變量temp缩赛,將pa指針指向的值取出(可以理解為先將pa變量中存儲(chǔ)的地址取出耙箍,再用該地址去取值,即得到圖中的a的值)酥馍,這個(gè)過程也叫解引用辩昆,存入temp中,然后同樣將pb指向的值取出旨袒,放到pa指向的值中汁针,最后將變量temp中存的值存入pb指向的值中,也就是下圖中的1砚尽,2施无,3步:????這是1.0版的swap,理解了指針的含義函數(shù)的執(zhí)行過程還是不難理解必孤。我們發(fā)現(xiàn)猾骡,這個(gè)函數(shù)它只能用來交換int類型的值,如果我想交換兩個(gè)float類型的數(shù),怎么辦兴想,沒辦法只能再寫一個(gè)一樣的函數(shù)幢哨,將函數(shù)中出現(xiàn)的int全都變?yōu)閒loat,但是可以看出這種解決方式并不優(yōu)雅嫂便,會(huì)使得代碼重復(fù)累贅捞镰,所以我們換一種方式來解決這個(gè)問題。
swap2.0
void swap(void *vp1,void *vp2,int size)
{
int buffer[size];
memcpy(buffer,vp1,size);
memcpy(vp1,vp2,size);
memcpy(vp2,buffer,size);
}
????為了解決我們swap1.0中的存在的問題毙替,我們需要一個(gè)能交換任意類型數(shù)據(jù)的函數(shù)岸售,所以這次我們不能將傳入的參數(shù)的類型限制為特定的類型,而是使用void*類型蔚龙,即可以傳入任意類型的指針,最后一個(gè)參數(shù)是一個(gè)int類型的size映胁,這個(gè)有什么用?后面會(huì)解釋木羹。回想一下解孙,之前swap1.0的實(shí)現(xiàn)思路是聲明一個(gè)變量坑填,用于臨時(shí)存儲(chǔ)值,從而交換兩個(gè)指針指向的值弛姜,在swap2.0中脐瑰,我們將傳入的參數(shù)改為了void*,難道不能像swap1.0那樣直接定義個(gè)臨時(shí)變量實(shí)現(xiàn)值的交換嗎廷臼?答案是不能苍在,為什么?原因主要有以下兩點(diǎn):
1.不能聲明void類型的變量
2.在swap1.0中荠商,我們傳入的參數(shù)是int*類型的指針pa寂恬,當(dāng)我們解引用時(shí),編譯器知道這個(gè)指針是int*型(即知道它里面存儲(chǔ)的是一個(gè)int類型變量的地址)莱没,再一次強(qiáng)調(diào)指針類型的變量存的就是地址初肉,所以解引用時(shí)它將pa中存的地址取出,作為起始地址饰躲,從該地址開始接著后面取4個(gè)字節(jié)(int類型的值使用4個(gè)字節(jié)存儲(chǔ))牙咏,然后將其解釋為一個(gè)int類型的數(shù)值,這樣我們就得到的pa解引用的值嘹裂。但是在swap2.0中妄壶,指針沒有了類型,我們?cè)谌〕龅刂泛蠹睦牵瑹o法知道應(yīng)該取出該地址后的幾個(gè)字節(jié)盯拱,所以不能使用swap1.0的思路實(shí)現(xiàn)swap2.0。
????上面的原因2也回答了之前的問題,為什么都是存地址狡逢,指針卻有類型之分宁舰,原因就在于有類型的指針在解引用時(shí)指針的類型能讓編譯器知道應(yīng)當(dāng)取出幾個(gè)字節(jié),知道應(yīng)當(dāng)取出幾個(gè)字節(jié)后我們就能使用memcpy函數(shù)實(shí)現(xiàn)內(nèi)存間交換奢浑。
????目前為止我們的swap函數(shù)通用性是有了蛮艰,但是調(diào)用時(shí)也應(yīng)當(dāng)時(shí)刻注意,要交換的到底是什么數(shù)據(jù)雀彼,當(dāng)你將它用于交換字符串變量時(shí)壤蚜,可能會(huì)出現(xiàn)錯(cuò)誤(可以先自己嘗試寫一下交換字符串變量的函數(shù)調(diào)用再看下面的解釋)。
char * husband = "husband";
char * wife = "wife";
swap2(husband,wife,sizeof(char*));
可能一開始會(huì)寫出這樣的代碼徊哑,并且輸出結(jié)果可能就是你想要的袜刷,然后你覺得就是這樣的,但是這段代碼從邏輯上就是錯(cuò)的莺丑,下面我們看看是為什么可能輸出對(duì)的結(jié)果著蟹,以及為什么它是錯(cuò)的。
一開始可能覺得字符串指針存的就是字符串首地址梢莽,我們想交換字符串的話直接傳入字符串的首地址萧豆,傳入字符串長(zhǎng)度,swap2就能實(shí)現(xiàn)數(shù)據(jù)交換昏名,但是這樣的話交換的內(nèi)容是什么涮雷,是字符串,這里有一個(gè)問題是直接交換字符串所在的內(nèi)存的話轻局,我們?nèi)绾未_定交換的數(shù)據(jù)塊的大小洪鸭,如果兩個(gè)字符串長(zhǎng)度不一樣如何處理?所以這樣是行不通的仑扑,但是為什么這樣可能得到正確的結(jié)果卿嘲,因?yàn)槟銈魅氲膕izeof(char*)的值為8(在64位操作系統(tǒng)中),而初始化的兩個(gè)字符串長(zhǎng)度恰好又沒有超過8夫壁,所以可能輸出正確的結(jié)果(更大的看可能是程序崩掉)拾枣。正確的交換方式應(yīng)當(dāng)是這樣: swap2(&husband,&wife,sizeof(char*));
,想一下為什么盒让,這樣調(diào)用的話我們交換的是什么梅肤,一開始husband,wife兩個(gè)變量分別存儲(chǔ)的是兩個(gè)字符串的首地址的值,而我們傳入husband邑茄,wife的地址姨蝴,即交換兩個(gè)變量中存的值,也就是字符串的首地址肺缕,交換完成后左医,husband變量中存的就是“wife”的首地址授帕,這樣我們交換字符串的目的也就達(dá)到了。
完整代碼如下:
#include <stdio.h>
#include <assert.h>
#include <string.h>
int swap1(int *pa,int *pb)
{
int temp = *pa;
*pa = *pb;
*pb = temp;
}
void swap2(void *vp1,void *vp2,int size)
{
int buffer[size];
memcpy(buffer,vp1,size);
memcpy(vp1,vp2,size);
memcpy(vp2,buffer,size);
}
int main(int argc, char const *argv[])
{
int a = 23,b = 44;
//swap1 test
printf("a : %d b : %d\n", a , b);
swap1(&a,&b);
printf("after swap a : %d b : %d\n\n", a , b);
swap1(&a,&b);
//swap2 test for int
printf("a : %d b : %d\n", a , b);
swap2(&a,&b,sizeof(int));
printf("after swap a : %d b : %d\n\n", a , b);
swap2(&a,&b,sizeof(int));
//swap2 test for double
double d1 = 32.1,d2 = 44.1;
printf("d1 : %.2lf d2 : %.2lf\n", d1 , d2);
swap2(&d1,&d2,sizeof(double));
printf("after swap d1 : %.2lf d2 : %.2lf\n\n", d1 , d2);
swap2(&d1,&d2,sizeof(double));
//swap2 test for string
char * husband = (char*)"husband";
char * wife = (char*)"wife";
printf("husband : %10s wife : %10s\n", husband,wife);
swap2(&husband,&wife,sizeof(char*));
printf("husband : %10s wife : %10s\n\n", husband,wife);
return 0;
}
參考博客:
void及void指針含義的深刻解析