傳遞指針可以讓多個函數訪問指針所引用的對象,而不用把對象聲明為全局可訪問,要在某個函數中修改數據,需要用指針傳遞數據,當數據是需要修改的指針的時候,就要傳遞指針的指針,傳遞參數(包括指針)的時候,傳遞的是它們的值,也就是說,傳遞給函數的是參數值的一個副本 本文將討論C語言中指針傳遞給函數與從函數返回指針 用指針傳遞數據 用指針傳遞數據的一個主要原因是函數可以修改數據 下面的代碼實現一個常見的交換函數: #include<stdio.h> void swap(int* a, int* b) { int tmp; tmp = *a; *a = *b; *b = tmp; } int main() { int m, n; m = 5; n = 10; printf("m=%d, n=%d\n",m, n); swap(&m, &n); printf("m=%d, n=%d\n",m, n); return 0; } 如果不通過指針傳遞參數,交換就不會發生,具體的原理參見任何一本C語言教材 傳遞指向常量的指針 傳遞指向常量的指針是C中常用的技術,效率很高,因為避免某種情況下復制大量內存,如果不希望數據被修改,就要傳遞指向常量的指針 我們不能修改通過指向常量的指針傳進來的值: #include<stdio.h> void passconstant(const int* num1, int*num2) { *num2 = *num1; } int main() { const int a = 100; int b = 5; printf("a=%d, b=%d\n",a, b); passconstant(&a, &b); printf("a=%d, b=%d\n",a, b); return 0; } 下面的代碼會產生錯誤(第二個形參和實參的類型不匹配,試圖修改第一個參數所引用的常量): #include<stdio.h> void passconstant(const int* num1, int* num2) { *num1 = 100; *num2 = 200; } int main() { const int limit = 100; passconstant(&limit, &limit); return 0; } C語言中堆和棧的區別 預備知識—程序的內存分配 一個由C編譯的程序占用的內存分為以下幾個部分: 1、棧區(stack)— 由編譯器自動分配釋放 ,存放函數的參數值,局部變量的值等。其操作方式類似於數據結構中的棧。 2、堆區(heap) — 一般由程序員分配釋放, 若程序員不釋放,程序結束時可能由OS回收 。注意它與數據結構中的堆是兩回事,分配方式倒是類似於鏈表。 3、全局區(靜態區)(static),全局變量和靜態變量的存儲是放在一塊的,初始化的全局變量和靜態變量在一塊區域, 未初始化的全局變量和未初始化的靜態變量在相鄰的另一塊區域。程序結束後由系統釋放。 4、文字常量區 —常量字符串就是放在這裡的, 程序結束後由系統釋放 5、程序代碼區—存放函數體的二進制代碼。 下面就說說C語言程序內存分配中的堆和棧,內存分配一般情況下程序存放在Rom或Flash中,運行時需要拷到內存中執行,內存會分別存儲不同的信息,如下圖所示: 內存中的棧區處於相對較高的地址以地址的增長方向為上的話,棧地址是向下增長的,棧中分配局部變量空間,堆區是向上增長的用於分配程序員申請的內存空間。另外還有靜態區是分配靜態變量,全局變量空間的;只讀區是分配常量和程序代碼空間的;以及其他一些分區。 堆棧的區別,來看一個經典例子: #include<stdio.h> #include<stdlib.h> int a = 0; //全局初始化區 char *p1; //全局未初始化區 int main() { int b; //棧 char s[] = "abc"; //棧 char *p2; //棧 char *p3 = "123456"; //123456\0在常量區,p3在棧上。 static int c =0; //全局(靜態)初始化區 p1 = (char*)malloc(10); //堆 p2 = (char*)malloc(10); return 0; } 不知道你是否有點明白了,堆和棧的第一個區別就是申請方式不同:棧(英文名稱是stack)是系統自動分配空間的,例如我們定義一個 char a;系統會自動在棧上為其開辟 空間。而堆(英文名稱是heap)則是程序員根據需要自己申請的空間,例如malloc(10);由於棧上的空間是自動分配自動回收的,所以棧上的數據的生存周期只是在函數的運行過程中,運行後就釋放掉,不可以再訪問。而堆上的數據只要程序員不釋放空間,就一直可以訪問到,不過缺點是一旦忘記釋放會造成內存洩露。還有其他的一些區別網上的總結的不錯這裡轉述一下: 1.申請後系統的響應 棧:只要棧的剩余空間大於所申請空間,系統將為程序提供內存,否則將報異常提示棧溢出。 堆:首先應該知道操作系統有一個記錄空閒內存地址的鏈表,當系統收到程序的申請時,會遍歷該鏈表,尋找第一個空間大於所申請空間的堆結點,然後將該結點從空閒結點鏈表中刪除,並將該結點的空間分配給程序,另外,對於大多數系統,會在這塊內存空間中的首地址處記錄本次分配的大小,這樣,代碼中的 delete語句才能正確的釋放本內存空間。另外,由於找到的堆結點的大小不一定正好等於申請的大小,系統會自動的將多余的那部分重新放入空閒鏈表中,也就是說堆會在申請後還要做一些後續的工作這就會引出申請效率的問題。 2.申請效率的比較 棧:由系統自動分配,速度較快。但程序員是無法控制的。 堆:是由new分配的內存,一般速度比較慢,而且容易產生內存碎片,不過用起來最方便。 3.申請大小的限制 棧:在Windows下,棧是向低地址擴展的數據結構,是一塊連續的內存的區域。這句話的意思是棧頂的地址和棧的最大容量是系統預先規定好的,在 WINDOWS下,棧的大小是2M(也有的說是1M,總之是一個編譯時就確定的常數),如果申請的空間超過棧的剩余空間時,將提示overflow。因此,能從棧獲得的空間較小。 堆:堆是向高地址擴展的數據結構,是不連續的內存區域。這是由於系統是用鏈表來存儲的空閒內存地址的,自然是不連續的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限於計算機系統中有效的虛擬內存。由此可見,堆獲得的空間比較靈活,也比較大。 4.堆和棧中的存儲內容 棧: 在函數調用時,第一個進棧的是主函數中函數調用後的下一條指令(函數調用語句的下一條可執行語句)的地址,然後是函數的各個參數,在大多數的C編譯器中,參數是由右往左入棧的,然後是函數中的局部變量。注意靜態變量是不入棧的。 當本次函數調用結束後,局部變量先出棧,然後是參數,最後棧頂指針指向最開始存的地址,也就是主函數中的下一條指令,程序由該點繼續運行。 堆:一般是在堆的頭部用一個字節存放堆的大小。堆中的具體內容有程序員安排。 堆和棧的區別可以引用一位前輩的比喻來看出: 使用棧就象我們去飯館裡吃飯,只管點菜(發出申請)、付錢、和吃(使用),吃飽了就走,不必理會切菜、洗菜等准備工作和洗碗、刷鍋等掃尾工作,他的好處是快捷,但是自由度小。使用堆就象是自己動手做喜歡吃的菜肴,比較麻煩,但是比較符合自己的口味,而且自由度大。 局部變量指針 如果不了解程序棧如何工作,就很容易犯返回指向局部數據指針的錯誤,看下面的例子: #include<stdio.h> #include<stdlib.h> int* allocateArray(int size, int value) { int arr[size]; for(int i = 0; i < size; i++) { arr[i] = value; } return arr; } int main() { int* vector = allocateArray(5, 45); for(int i = 0; i < 5; i++) { printf("%d\n", vector[i]); } return 0; } 一旦函數返回,返回的數組地址也就無效,因為函數的棧幀從棧中彈出了 有一種方法是把arr變量聲明為static,這樣會把變量的作用域現在在函數內部,但是分配在棧幀的外面,避免其他函數覆寫變量值 #include<stdio.h> #include<stdlib.h> int* allocateArray(int size, int value) { static int arr[10]; for(int i = 0; i < size; i++) { arr[i] = value; } return arr; } int main() { int* vector = allocateArray(5, 45); for(int i = 0; i < 5; i++) { printf("%d\n", vector[i]); } return 0; } 返回指針 從函數返回對象經常使用以下兩種技術: 使用malloc在函數內部分配內存並返回其地址,調用者負責釋放返回的內存 傳遞一個對象給函數,讓函數修改它,這樣分配和釋放對象的內存都是調用者的責任 #include<stdio.h> #include<stdlib.h> int* allocateArray(int size, int value) { int* arr = (int*)malloc(size * sizeof(int)); for(int i = 0; i < size; i++) { arr[i] = value; } return arr; } int main() { int* vector = allocateArray(5, 45); for(int i = 0; i < 5; i++) { printf("%d\n", vector[i]); } free(vector); return 0; } 下面這個版本的allocateArray函數傳遞了一個數組指針、數組的長度和用來初始化數組元素的值,返回指針只是為了方便 #include<stdio.h> #include<stdlib.h> int* allocateArray(int *arr, int size, int value) { if(arr != NULL) { for(int i = 0; i < size; i++) { arr[i] = value; } } return arr; } int main() { int* vector = (int*)malloc(5 * sizeof(int)); allocateArray(vector, 5, 45); for(int i = 0; i < 5; i++) { printf("%d\n", vector[i]); } free(vector); return 0; } 傳遞指針的指針 將指針傳遞給函數的時候,傳遞的是值,如果希望修改原指針而不是指針的副本,就需要傳遞指針的指針 #include<stdio.h> #include<stdlib.h> void allocateArray(int **arr, int size, int value) { *arr = (int*)malloc(size * sizeof(int)); if(arr != NULL) { for(int i = 0; i < size; i++) { *(*arr + i) = value; } } } int main() { int* vector = NULL; allocateArray(&vector, 5, 45); for(int i = 0; i < 5; i++) { printf("%d\n", vector[i]); } free(vector); return 0; } 二叉樹遞歸實現與二重指針 二叉樹的諸多操作往往是通過遞歸調用來實現的,這就決定,不能只通過main函數實現全部過程,其中還需要調用main外定義的函數。也因此,對main調用外定義的函數的參數傳遞,就有了嚴格的要求。在網上查找很多關於二叉樹建立的程序,但直接拷貝在自己計算機上運行卻發現不少錯誤,無法編譯通過。以下有關代碼編譯通過,不涉及二叉樹的全部操作,著重通過C語言實現二叉樹的創建過程說明遞歸實現與二重指針的相關問題。 1、二叉樹的定義 二叉樹的定義結構通常為如下形式: typedef struct Node { char ch; struct Node *lchild,*rchild; }Node,*BTree; Node一般可用來定義二叉樹節點,而*BTree可用來定義指向二叉樹(根節點)的指針 2、內存動態分配 采用內存動態分配需要用到malloc函數。值得注意的是,該函數在成功開辟新的內存時,默認返回void*指針,因此需要強制轉換成Node*形式,其調用形式如(Node*)malloc(sizeof(Node)) 3、遞歸調用 因為遞歸調用的需要,二叉樹的一些操作需要獨立作為一個函數。但是,這些函數是在main中調用,因此傳遞的參數和返回的值的處理是非常重要的。另外注意,對二叉樹的操作,首先就需要知道二叉樹的入口,即指向二叉樹的指針,也即指向二叉樹根節點的指針。因此,所傳遞的參數,則為指向根節點的指針。又因為涉及分配內存的操作,必須傳遞二級指針,如下程序,CreateTree函數可以是由返回值,也可以不具有返回值(因為傳遞的是地址)。在main函數中作了測試,返回的值為二叉樹根節點的值。 void CreateTree(Node** pTree) { char ch; scanf("%c",&ch); if(chr == '#') { (*pTree) = NULL; } else { if(!((*pTree) = (Node*)malloc(sizeof(Node)))) { exit(OVERFLOW); } (*pTree)->ch = chr; CreateTree(&((*pTree)->lchild)); CreateTree(&((*pTree)->rchild)); } }