談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)++; }
問題列表:
1、如何改變外部變量?
2、啥時候我們需要使用**?
前言:
先看Inc,我們知道int i是一個值類型,為了能夠達到修改它的目的,我們需要將i的實際地址通過值傳遞的方式從調用函數傳遞給被調用函數,因為對相同的地址的變量進行操作,所以我們的Inc(&i)將如我們所愿,順利地遞增。
以上兩個版本的changeString都是可以達到修改調用函數中的字符串的。如果按照下面的代碼將得到不正確的結果。
void errorChangeString(char *t){ t = "change!"; } int main(void){ char *s = "Hello"; errorChangeString(s); return EXIT_SUCCESS; }
在錯誤示例代碼中,假設傳遞的s則為指向字面值"Hello"的首字母'H'所在的地址值,假設這個值為0x1000。在errorChangeString中,假設"change"字面值的首字母'c'所在的地址值為0x2000,s被拷貝給了t,t的任何改動和s沒有任何關聯,因此,s仍然指向0x1000,而不是指向0x2000。
我們應如何看待char **t?
我們應如何看待char *t[]?
在我們的changeString2(char *t[])中,我們用char *t[]取代了char **t,我們知道char *t[]代表t是一個數組,數組的每一個成員都是一個char*類型的指針。我們也成為指針數組。下面讓我們看一個調用:
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; }
下面我們來看一下下面這段代碼:
void changeString2(char *t[]); //函數體見本文頂部 int main(void){ char *s="Hello"; printf("%s",s); changeString2(&s); printf("%s",s); return EXIT_SUCCESS; }
下面的示例圖則從本質上分別分析了兩者的各自的理由(非上述推理):
用typedef char *String;改良后的程序具有更高的可讀性
可以看到第三段代碼中我們在函數聲明前用typedef語句定義了typedef char *String;首先從typedef的本質來講,這種定義將導致使用它的changeString3與changeString函數具有相同的本質,但是從閱讀的習慣上來講,用String而不是用char *的方式,則顯得更加親切。首先我們從眾多起他語言中,比如C#中,C#實現了類型String/string的方式,我們知道String是一個引用類型,但我們同時也知道string類型有個顯著的特征,就是它雖然是引用類型,每次對它的操作總是像值類型一樣被復制,這時候,我們定義的任何(C#):ChangeString(string str);將不起作用,而我們需要增加ref關鍵字來告訴編譯器它是同一實例,而不進行重新申請空間重新分配等一系列復雜操作,于是ChangeString(ref string str);的語句就有類似值類型的一些地方了,同樣,在C語言中,changeString2(String *s)也達到了同樣的效果。這樣的方式也同時對我們更加了解第一種方式起到了輔助作用。(用C#來比喻可能不是太好,因為很多讀者通常都是先接觸C再有機會才接觸C#的,而且也沒有講解到本質)
void changeString3(String *s); //函數體見本文頂部 int main(void){ char *s="Hello"; printf("%s",s); changeString3(&s); printf("%s",s); return EXIT_SUCCESS; }
下一個問題:
啥時候我們需要用到**?
通過以上的幾個直觀的示例,我們大體了解了一個字符串通過一個函數參數進行修改的具體情況。這是一個很發散性的問題,我也沒有一個很肯定的100%的答案。
從void **v;(//void代表任意類型,可以是int/char/struct node等)定義的本質上來觀察這個問題,我們可以推論void **v;,當我們需要獲取并改變*v的地址值得時候,我們都有這個需要(因為單從void *v的角度講,我們只能夠獲取v的地址改變v的值,但不能改變v的地址)。那我們什么需要獲取并改變*v的值呢?從上面的分析我們不難得出,我們在需要改變v的地址的時候即有這個需要。
下面是一個鏈表的例子:
#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,¤t,i); foreach(head, printfFront, printfLast); return EXIT_SUCCESS; }
//函數輸出
0 -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9
這個程序代表了一種使用**的典型用法,也是大部分需要使用**的用法。
總結:
不論它怎么變化,怎么復雜,我們需要把握幾點:
1、C語言中,函數傳遞永遠是值傳遞,若需要按地址傳遞,則需要用到指針(類似func(void *v){...});
2、在對于需要變化外部值的時候,直接尋址的使用*,間接尋址的使用**;
3、對于復雜的表達式,善于使用嵌套的思路去分析(編譯器亦或如此),注意各符號之間的優先級。
posted on 2008-08-30 22:09 volnet 閱讀(3676) 評論(4) 編輯 收藏 引用 所屬分類: C/C++