• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            MyMSDN

            MyMSDN記錄開(kāi)發(fā)新知道

            談void changeString(char **s),指向指針的指針

            void changeString(char **t){
            	*t = "world";
            }
            
            void changeString2(char *t[]){
            	*t = "world2";
            }
            
            typedef char *String;
            void changeString3(String *s){
            	*s = "world3";
            }
            
            void Inc(int *i){
            	(*i)++;
            }

            問(wèn)題列表:
            1、如何改變外部變量?
            2、啥時(shí)候我們需要使用**?
            前言:
            先看Inc,我們知道int i是一個(gè)值類型,為了能夠達(dá)到修改它的目的,我們需要將i的實(shí)際地址通過(guò)值傳遞的方式從調(diào)用函數(shù)傳遞給被調(diào)用函數(shù),因?yàn)閷?duì)相同的地址的變量進(jìn)行操作,所以我們的Inc(&i)將如我們所愿,順利地遞增。
            以上兩個(gè)版本的changeString都是可以達(dá)到修改調(diào)用函數(shù)中的字符串的。如果按照下面的代碼將得到不正確的結(jié)果。

            錯(cuò)誤代碼示例:
            void errorChangeString(char *t){
            	t = "change!";
            }
            
            int main(void){
            	char *s = "Hello";
            	errorChangeString(s);
            
            	return EXIT_SUCCESS;
            }

            在錯(cuò)誤示例代碼中,假設(shè)傳遞的s則為指向字面值"Hello"的首字母'H'所在的地址值,假設(shè)這個(gè)值為0x1000。在errorChangeString中,假設(shè)"change"字面值的首字母'c'所在的地址值為0x2000,s被拷貝給了t,t的任何改動(dòng)和s沒(méi)有任何關(guān)聯(lián),因此,s仍然指向0x1000,而不是指向0x2000。

            我們應(yīng)如何看待char **t?

            若要讓所謂的t的值能夠跟著s變化,我們則需要指向指針的指針來(lái)幫忙。我們知道要讓函數(shù)傳遞(C語(yǔ)言只有值傳遞)后,仍然指向相同的值(這里指s始終為0x4000(s指向0x1000,假設(shè)它自身地址為0x4000)),則我們需要將這個(gè)傳遞的值進(jìn)行一次包裝,使我們通過(guò)形參也能夠控制相同的地址所在的變量(這里指0x4000),因此,我們對(duì)形參定義一個(gè)指針,形如 char* *t(等價(jià)于char **t),這樣*t與s就代表了相同的值而不會(huì)因?yàn)閭鬟f而無(wú)法操縱s,因此可以在被調(diào)用函數(shù)內(nèi)部使用*t來(lái)指代要操作的外部變量(通過(guò)參數(shù)傳遞),因此在內(nèi)部*t="world"(假設(shè)"world"的首地址為0x2000),則s的值就被修改為"world"的首地址。(如下圖所示)


            我們應(yīng)如何看待char *t[]?
            在我們的changeString2(char *t[])中,我們用char *t[]取代了char **t,我們知道char *t[]代表t是一個(gè)數(shù)組,數(shù)組的每一個(gè)成員都是一個(gè)char*類型的指針。我們也成為指針數(shù)組。下面讓我們看一個(gè)調(diào)用:
            void changeStrArr(char *t[]){
            	*t = "World";
            }
            int main(void){
            	char *sArr[] = {
            		"Hello"
            	};
            	printf("%s",*sArr);
            	changeStrArr(sArr);
            	printf("%s",*sArr); //printf("%s",sArr[0]);
            
            	return EXIT_SUCCESS;
            }
            這是教科書(shū)上比較常見(jiàn)的指針數(shù)組形式,甚至還會(huì)簡(jiǎn)單不少(它們的數(shù)組通常會(huì)有多個(gè)元素并用*t++來(lái)控制移位)。sArr在這里就是這個(gè)數(shù)組,因此sArr[0]即為指向該數(shù)組第一個(gè)元素的指針(因?yàn)槭侵羔様?shù)組,每一個(gè)元素都是一個(gè)指針),因此使用printf("%s",*sArr); 或者printf("%s",sArr[0]);都將標(biāo)準(zhǔn)輸出sArr的第一個(gè)元素所指向的字符串。
            下面我們來(lái)看一下下面這段代碼:
            void changeString2(char *t[]); //函數(shù)體見(jiàn)本文頂部
            int main(void){
            	char *s="Hello";
            	printf("%s",s);
            	changeString2(&s);
            	printf("%s",s); 
            	return EXIT_SUCCESS;
            }
            從這段代碼中我們主要講s換成了一個(gè)字符而不是上一段代碼中的字符指針數(shù)組sArr,從上一段代碼我們可以得知s和sArr之間的關(guān)系,*s==*sArr[0]==**sArr;(我們可以通過(guò)strcmp(q,qArr[0])或者strcmp(q,*qArr);進(jìn)行判斷,我們知道strcmp(const char *_Str1, const char *_Str2);也就是我們傳遞的q和*qArr均為字符指針也就是它們的定義通常為char *q和char **qArr)。為此我們可以將其進(jìn)行移項(xiàng),也可以得到等價(jià)表達(dá)式(規(guī)律:==兩側(cè)同時(shí)添加相同符號(hào)等式依舊不變(在*和&的邏輯里成立),同時(shí)出現(xiàn)&*,兩符號(hào)起中和作用(先從一個(gè)地址中取值,再?gòu)闹捣辞笏牡刂?,因此最終結(jié)果還是地址本身))也就是&*s==&*sArr[0]==&**sArr <=> s==sArr[0]==*sArr,這樣,再進(jìn)行一次,&s==&sArr[0]==&*sArr,也就是&s==&sArr[0]==sArr因此changeStrArr(sArr)<=>changeStrArr(&s),因此從上面的代碼段到下面代碼段的演化是成功的(changeString2和changeStrArr本質(zhì)上沒(méi)有差別)。
            下面的示例圖則從本質(zhì)上分別分析了兩者的各自的理由(非上述推理):


            用typedef char *String;改良后的程序具有更高的可讀性
            可以看到第三段代碼中我們?cè)诤瘮?shù)聲明前用typedef語(yǔ)句定義了typedef char *String;首先從typedef的本質(zhì)來(lái)講,這種定義將導(dǎo)致使用它的changeString3與changeString函數(shù)具有相同的本質(zhì),但是從閱讀的習(xí)慣上來(lái)講,用String而不是用char *的方式,則顯得更加親切。首先我們從眾多起他語(yǔ)言中,比如C#中,C#實(shí)現(xiàn)了類型String/string的方式,我們知道String是一個(gè)引用類型,但我們同時(shí)也知道string類型有個(gè)顯著的特征,就是它雖然是引用類型,每次對(duì)它的操作總是像值類型一樣被復(fù)制,這時(shí)候,我們定義的任何(C#):ChangeString(string str);將不起作用,而我們需要增加ref關(guān)鍵字來(lái)告訴編譯器它是同一實(shí)例,而不進(jìn)行重新申請(qǐng)空間重新分配等一系列復(fù)雜操作,于是ChangeString(ref string str);的語(yǔ)句就有類似值類型的一些地方了,同樣,在C語(yǔ)言中,changeString2(String *s)也達(dá)到了同樣的效果。這樣的方式也同時(shí)對(duì)我們更加了解第一種方式起到了輔助作用。(用C#來(lái)比喻可能不是太好,因?yàn)楹芏嘧x者通常都是先接觸C再有機(jī)會(huì)才接觸C#的,而且也沒(méi)有講解到本質(zhì))
            void changeString3(String *s); //函數(shù)體見(jiàn)本文頂部
            int main(void){
            	char *s="Hello";
            	printf("%s",s);
            	changeString3(&s);
            	printf("%s",s); 
            
            	return EXIT_SUCCESS;
            }
            本質(zhì)呢?因?yàn)槿魏我淮蔚?Hello",其中的"Hello"是常量,而不是變量,它的存儲(chǔ)空間在編譯時(shí)就已經(jīng)確定了,它放在了靜態(tài)常量區(qū)中,因此它的地址不會(huì)變也不能加減。因此String,也就是char *指向的是一個(gè)不可變的常量,而非變量。(例如我們一直假設(shè)char *s = "Hello",的首地址s==0x1000(s的值,不是s的地址),那么它始終是0x1000,但是s是變量,s可以拋棄0x1000指向別的字符串字面值(char literal),但是我們知道C語(yǔ)言中只有按值傳遞,因此我們必須用它的指針假設(shè)s的地址0x3000,那么,我們將0x3000進(jìn)行傳遞,這樣內(nèi)部就可以對(duì)0x3000進(jìn)行操作了,這樣可以用(0x3000)->value的方式修改value指向0x2000的地址(假設(shè)這個(gè)地址是"GoodBye"的值),這樣我們的s就被修改了。因?yàn)槲覀兊某A吭诰幾g時(shí)就已經(jīng)分配了地址,在程序加載后就長(zhǎng)久存在,知道應(yīng)用程序退出后會(huì)跟著宿主一并消失,所以我們同樣不需要free操作)。

            下一個(gè)問(wèn)題:
            啥時(shí)候我們需要用到**?
            通過(guò)以上的幾個(gè)直觀的示例,我們大體了解了一個(gè)字符串通過(guò)一個(gè)函數(shù)參數(shù)進(jìn)行修改的具體情況。這是一個(gè)很發(fā)散性的問(wèn)題,我也沒(méi)有一個(gè)很肯定的100%的答案。
            從void **v;(//void代表任意類型,可以是int/char/struct node等)定義的本質(zhì)上來(lái)觀察這個(gè)問(wèn)題,我們可以推論void **v;,當(dāng)我們需要獲取并改變*v的地址值得時(shí)候,我們都有這個(gè)需要(因?yàn)閱螐膙oid *v的角度講,我們只能夠獲取v的地址改變v的值,但不能改變v的地址)。那我們什么需要獲取并改變*v的值呢?從上面的分析我們不難得出,我們?cè)谛枰淖僾的地址的時(shí)候即有這個(gè)需要。
            下面是一個(gè)鏈表的例子:
            #include <stdio.h>
            #include <stdlib.h>
            
            typedef struct node{
            	int value;
            	struct node *next;
            } Node;
            
            Node *createList(int firstItem){
            	Node *head = (Node *)malloc(sizeof(Node));
            	head ->value = firstItem;
            	head ->next = NULL;
            	return head;
            }
            void addNode(Node *head, Node **pCurrent,int item){
            	Node *node = (Node *)malloc(sizeof(Node));
            	node ->value = item;
            	node ->next = NULL;
            
            	(*pCurrent)->next=node;
            	*pCurrent = node;
            }
            typedef void (*Handler)(int i);
            void foreach(Node *head, Handler Ffront, Handler Flast){
            	if(head->next!=NULL){
            		Ffront(head->value);
            		foreach(head->next,Ffront,Flast);
            	}
            	else
            	{
            		Flast(head->value);
            	}
            }
            void printfFront(int i){
            	printf("%d -> ",i);
            }
            void printfLast(int i){
            	printf("%dn",i);
            }
            
            
            int main(void){
            	Node *head, *current;
            	current = head = createList(0);
            	for(int i=1;i<10;i++)
            		addNode(head,&current,i);
            	foreach(head, printfFront, printfLast);
            
            	return EXIT_SUCCESS;
            }
            //函數(shù)輸出
            0 -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9
            這個(gè)程序中的關(guān)鍵部分就是當(dāng)前節(jié)點(diǎn)值current的確定,部分老師可能會(huì)圖方便采用全局變量進(jìn)行當(dāng)前值的確定,這個(gè)在拋棄型的示例中當(dāng)然無(wú)傷大雅,也很好地描述了鏈表的本質(zhì),這本沒(méi)什么關(guān)系,但是鏈表是一個(gè)常用的數(shù)據(jù)結(jié)構(gòu),并發(fā)怎么辦?操作多個(gè)鏈表怎么辦?總之我們要秉承“方法共享,數(shù)據(jù)不共享的原則”,這樣就不太容易出現(xiàn)問(wèn)題了。這里我們?cè)趍ain函數(shù)中定義了唯一的*head用于標(biāo)識(shí)鏈表的頭,并希望它始終扮演鏈表頭的角色,不然我們最后將無(wú)法找到它了。我們用一個(gè)同樣類型的節(jié)點(diǎn)current指向了當(dāng)前節(jié)點(diǎn),并始終指向當(dāng)前節(jié)點(diǎn)(隨著鏈表的移動(dòng),它將指向最后一個(gè)節(jié)點(diǎn))。由于我們的current是主函數(shù)中定義的,而它的修改是在被調(diào)函數(shù)中進(jìn)行的。因?yàn)槲覀冃枰淖兊?current的值,根據(jù)我們的分析,對(duì)于要修改值的,我們有使用**的必要,而類似只需要讀取值的head,則沒(méi)有任何需要了。
            這個(gè)程序代表了一種使用**的典型用法,也是大部分需要使用**的用法。

            總結(jié):
            不論它怎么變化,怎么復(fù)雜,我們需要把握幾點(diǎn):
            1、C語(yǔ)言中,函數(shù)傳遞永遠(yuǎn)是值傳遞,若需要按地址傳遞,則需要用到指針(類似func(void *v){...});
            2、在對(duì)于需要變化外部值的時(shí)候,直接尋址的使用*,間接尋址的使用**;
            3、對(duì)于復(fù)雜的表達(dá)式,善于使用嵌套的思路去分析(編譯器亦或如此),注意各符號(hào)之間的優(yōu)先級(jí)。

            posted on 2008-08-30 22:09 volnet 閱讀(3671) 評(píng)論(4)  編輯 收藏 引用 所屬分類: C/C++

            評(píng)論

            # re: 談void changeString(char **s),指向指針的指針[未登錄](méi) 2008-09-01 10:12 raof01

            第三條結(jié)論怎么得出的?

            分析得不錯(cuò),就是有點(diǎn)麻煩。TCPL里指針講得比較好,簡(jiǎn)明扼要。  回復(fù)  更多評(píng)論   

            # re: 談void changeString(char **s),指向指針的指針 2008-09-02 11:30 volnet

            @raof01
            我怎么能跟TCPL比呢~~·人家是之父,我是之孫孫……呵呵  回復(fù)  更多評(píng)論   

            # re: 談void changeString(char **s),指向指針的指針[未登錄](méi) 2008-09-05 13:02 raof01

            靠,別誤會(huì)了,只是說(shuō)這篇應(yīng)當(dāng)參考TCPL,寫(xiě)得簡(jiǎn)潔點(diǎn)。  回復(fù)  更多評(píng)論   

            # re: 談void changeString(char **s),指向指針的指針 2008-09-05 22:02 volnet

            @raof01
            被你說(shuō)的一暈一暈的~  回復(fù)  更多評(píng)論   

            特殊功能
             
            青青草原综合久久| 久久久久人妻一区精品色 | 97精品依人久久久大香线蕉97| 精品熟女少妇AV免费久久| 亚洲成色WWW久久网站| 青草影院天堂男人久久| 2020久久精品亚洲热综合一本| 99久久精品毛片免费播放| 久久久不卡国产精品一区二区| 伊人久久大香线蕉av不变影院| 91亚洲国产成人久久精品| 麻豆久久久9性大片| 久久精品国产亚洲一区二区| 久久精品中文无码资源站| 丁香五月综合久久激情| 欧美一区二区三区久久综合| 久久综合五月丁香久久激情| 国产婷婷成人久久Av免费高清 | 亚洲中文字幕伊人久久无码 | 婷婷久久综合| 久久综合久久综合久久| 亚洲精品乱码久久久久久中文字幕| 精品国产综合区久久久久久| 久久国产乱子伦免费精品| 久久综合视频网| 久久一区二区三区免费| 久久精品国产91久久麻豆自制| 一本色综合网久久| 亚洲国产精品无码久久九九| 99久久国产综合精品成人影院| av午夜福利一片免费看久久| 无码精品久久久久久人妻中字| 欧美日韩精品久久久久| 日韩亚洲国产综合久久久| 日本免费一区二区久久人人澡| 狠狠干狠狠久久| 国产一区二区三区久久| 久久91综合国产91久久精品| 久久天堂电影网| 狠狠精品久久久无码中文字幕| 国产精品青草久久久久福利99|