函式呼叫時的形參與實參
大多數人在進行學習程式設計時,對於函式呼叫時函式時的傳遞的內容是一個頭大的問題。本人在學習後有了以下的見解請大家共同學習,若有失誤,請指出!
基礎知識
1.系統堆疊(即棧區域)和堆區域。 1.1、棧(Stack)是作業系統在建立某個程序時或者執行緒(在支援多執行緒的作業系統中是執行緒)為這個執行緒建立的儲存區域,該區域具有FIFO的特性,在編譯的時候可以指定需要的Stack的大小。在程式設計中,例如C/C++中,所有的區域性變數都是從棧中分配記憶體空間,實際上也不是什麼分配,只是從棧頂向上用就行,在退出函式的時候,只是修改棧指標就可以把棧中的內容銷燬,所以速度最快。但是棧區域有一個很大的確定就是由於它通常只有1到2M大小的空間,這個大小相對於記憶體中的4G或者8G空間是很小的。所以編寫遞迴程式時候,使用Stack是需要考慮到遞迴的深度。1.2、堆(Heap)是應用程式在執行的時候請求作業系統分配給自己記憶體,一般是申請/給予的過程,C/C++分別用malloc/New請求分配Heap,用free/delete銷燬記憶體。由於從作業系統管理的記憶體分配所以在分配和銷燬時都要佔用時間,所以用堆的效率低的多!但是堆的好處是可以做的很大,C/C++對分配的Heap是不初始化的。在沒有垃圾回收機制的程式語言中如果程式設計師在編寫一些長時間需要執行的程式時員(例如:伺服器程式),對於堆的使用需要萬分小心。如果某程式設計師在編寫程式時候申請了堆區域的空間但沒有去釋放(即free/delete)
形參與實參的傳遞
1、
先舉一個錯誤的新手容易犯的錯誤(該程式意用程式實現在交換兩個空間的值)
#include <stdio.h> void exchange(int one,int another); void exchange(int one,int another) { int tmp; tmp = one; one = another; num2 = tmp; } int main() { int num1; int num2; scanf("%d%d",&num1,&num2); printf("交換前的值為:%d %d\n",num1,num2); exchange(num1,mun2); printf("交換後的值為:%d %d\n",num1,num2); return 0; }
結果如下:
對於大多數的程式設計新手來說,第一次編寫類似的程式碼很容易寫成這種情況吧。下面我解釋一下,這種方法中,看似交換了num1和num2的值,但實際上。這兩個值並沒有交換。在主函式中。在堆疊區域申請了兩個整形變數空間即num1,和num2的空間。在呼叫exchange函式時候會現有現場資訊(簡稱:主現。在不涉及多執行緒中,這裡就是eip的值。)儲存在堆疊區域,棧頂指標移動向上移動一定的距離。這是為了在此執行主函式時候恢復上次的狀態。然後將num2複製一份入棧,another的值對應這個空間的值,棧頂指標向上移動一個整形空間,再將num1複製一份入棧,one的值對應這個空間的值。棧頂指標在向上移動一個整形空間。然後進入到exchange中在堆疊中申請一個整形空間,棧頂指標向上移動一個整形空間。然後進行交換one和another的交換。然後在將棧頂指標恢復到主現以前。然後執行主函式中的程式碼。上述文字如下圖。
接下來我說一種正確程式碼
#include <stdio.h> void exchange(int* one,int* another); void exchange(int* one,int* another) { int tmp; tmp = *one; *one = *another; *another = tmp; } int main() { int num1; int num2; scanf("%d%d",&num1,&num2); printf("交換前的值為 :%d %d\n",num1,num2); exchange(&num1,&num2); printf("交換後的值為 :%d %d\n",num1,num2); return 0; }
執行結果為:
上面的程式碼成功的交換了num1和num2的數值。在主函式中,在堆疊區域申請了兩個整形變數空間即num1,和num2的空間。在呼叫exchange函式時候會現有現場資訊(簡稱:主現。在不涉及多執行緒中,這裡就是eip的值)儲存在堆疊區域,棧頂指標移動向上移動一定的距離。這是為了在此執行主函式時候恢復上次的狀態。然後將num2地址的值複製一份入棧,another的值對應這個空間的值,棧頂指標向上移動一個整形空間,再將num1地址的值複製一份入棧,one的值對應這個空間的值。棧頂指標在向上移動一個整形空間。然後進入到exchange中在堆疊中申請一個整形空間,棧頂指標向上移動一個整形空間。然後將one指向空間的值賦值給tmp,然後將another所指向空間的值賦值給one所指向空間,最後將tmp的值賦值給another所指向的空間。這就完成了num1和num2的交換。然後在將棧頂指標恢復到主現以前。然後執行主函式中的程式碼。上述文字如下圖
總結提高
在上面的兩個例子中,在第一個從錯誤例子中,我們能發現一個問題,不論我們怎麼操作傳遞到子函式的形參的值,都無法修改主調函式中變數的值。但在第二個例子中,我們可以通過傳遞主調函式變數的地址值並進行“*”操作就可以改變主調函式中變數的值。這給了我們一個啟示:在呼叫函式中如果不需要修改主調函式中變數的值,傳遞那個變數的值。如果需要改變主調函式中變數的值,這時候就需要傳遞的就是該變數的地址的值。但由於傳遞的值是需要入棧,當傳遞的變數非常大的時候就會存在危險即棧溢位。所以當需要傳遞的內容比較大的時候,這時需要傳遞的仍然是變數的地址,但為了防止被調函式對變數操作。這時候需要加上const!!!
#include <stdio.h>
void exchange(const int* one,const int* another);
void exchange(const int* one,constint* another) {
int tmp;
tmp = *one;
*one = *another;
*another = tmp;
}
int main() {
int num1;
int num2;
scanf("%d%d",&num1,&num2);
exchange(&num1,&num2);
return 0;
}
編譯會提示錯誤如下:
這就對主調函式中變數的值無法操作。