C語言中的參數傳遞機制詳解。本站提示廣大學習愛好者:(C語言中的參數傳遞機制詳解)文章只能為提供參考,不一定能成為您想要的結果。以下是C語言中的參數傳遞機制詳解正文
C中的參數傳遞
本文嘗試討論下C中實參與形參的關系,即參數傳遞的問題。
C語言的參數傳遞
值傳遞
首先看下列代碼:
#include <stdio.h> int main(){ int n = 1; printf("實參n的值:%d,地址:%#x\n", n, &n); void change(int i);//函數聲明 change(n); printf("函數調用後實參n的值:%d,地址:%#x\n", n, &n); return 0; } void change(int i){ printf("形參i的值:%d,地址:%#x\n",i,&i); i++; printf("自增操作後形參i的值:%d,地址:%#x\n",i,&i); }
編譯後執行結果如下:
實參n的值:1,地址:0x5fcb0c 形參i的值:1,地址:0x5fcae0 自增操作後形參i的值:2,地址:0x5fcae0 函數調用後實參n的值:1,地址:0x5fcb0c
可以看到,在調用函數 change 時,會在內存中單獨開辟一個空間用於存放形式參數 i ,實參 n 的值會復制給形參 i 。對於形參的任何操作都不會影響到主調函數中的實參 n 。這種參數傳遞方式是便是典型的值傳遞。
上例中的參數類型是int型,實際上,對於整形(int、short、long、long long)、浮點型(float、double、long double)、字符型(char)等基本類型的參數都是值傳遞。
指針參數
在下面的例子中,函數的參數類型是一個指針:
#include <stdio.h> void change(int * i){ printf("形參i的值:%#x,地址:%#x\n",i,&i); (*i)++;//通過指針修改其所指空間的值 i++;//讓指針指向下一塊內存空間 printf("自增操作後形參i的值:%#x,地址:%#x\n",i,&i); } int main(){ int n = 1; int * p = &n; printf("n的值:%d,地址:%#x\n", n, &n); printf("實參p的值:%#x,地址:%#x\n", p, &p); change(p); printf("函數調用後實參p的值:%#x,地址:%#x\n", p, &p); printf("函數調用後n的值:%d,地址:%#x\n", n, &n); return 0; }
編譯後執行結果如下:
n的值:1,地址:0x5fcb0c 實參p的值:0x5fcb0c,地址:0x5fcb00 形參i的值:0x5fcb0c,地址:0x5fcae0 自增操作後形參i的值:0x5fcb10,地址:0x5fcae0 函數調用後實參p的值:0x5fcb0c,地址:0x5fcb00 函數調用後n的值:2,地址:0x5fcb0c
指針是C語言中的一種特殊類型,它本身占用一定的內存空間,而存儲的值卻是某個內存地址。上例中,函數change的參數是指向int型變量的指針,實參p是指向變量n的一個指針,在調用函數change時,指針型的形參i也會得到一塊內存空間,其值由實參p復制而來,都是主調函數中變量n的地址。對於指針類型,一般不會太關注其本身而更多的是考慮它所指向的值,所以,在change函數內是通過指針來操作指針所指的內容(主調函數中的變量n)。這樣,便實現了在被調函數內操作主調函數中數據的效果。
雖然通過指針型參數傳遞可以達到讓被調函數內的操作作用於主調函數內數據的效果,但從實參和形參的角度來看,這種參數傳遞並沒有和一般的傳遞方法有什麼本質的區別,也是值傳遞方式。
當參數傳遞方式是值傳遞時,形參和實參都存儲在各自的內存空間中,相互獨立互不影響,它們之間唯一的聯系便是在形參初始化時會使用實參的值。
數組參數
在C語言中,數組名可以看作一個指向數組首元素的指針常量,那麼當數組作為參數是又是如何傳遞呢?實際上,當函數形參是數組類型時,作為形參的數組名便不再代表數組,而是被編譯器解析成一個指針。通過下面的例子可以看出,數組類型的形參只是在函數簽名中看上去是一個數組,但在函數體內,它已經徹底淪為一個指針。
#include <stdio.h> void arrArg(int arr[]){ printf("形參arr的值:%#x,地址:%#x\n", arr, &arr); printf("sizeof(arr):%d, sizeof(int *):%d\n",sizeof(arr), sizeof(int *)); printf("sizeof(arr[0]):%d,sizeof(int):%d\n", sizeof(arr[0]), sizeof(int)); printf("*arr:%d,arr[0]:%d\n", *arr, arr[0]); printf("*(arr+1):%d,arr[1]:%d\n", *(arr+1), arr[1]); printf("arr+1:%#x,&arr[1]:%#x\n",arr+1, &arr[1]); arr++; printf("自增操作後形參arr的值:%#x,地址:%#x\n",arr, &arr); printf("自增操作後,*arr:%d, arr[0]:%d\n",*arr, arr[0]); } int main(){ int a[] = {1,2,3}; printf("實參a的值:%#x,地址:%#x\n",a,&a); printf("sizeof(a):%d,sizeof(a[0]):%d\n",sizeof(a),sizeof(a[0])); arrArg(a); return 0; }
編譯後執行結果如下:
實參a的值:0x5fcb00,地址:0x5fcb00 sizeof(a):12,sizeof(a[0]):4 形參arr的值:0x5fcb00,地址:0x5fcae0 sizeof(arr):8, sizeof(int *):8 sizeof(arr[0]):4,sizeof(int):4 *arr:1,arr[0]:1 *(arr+1):2,arr[1]:2 arr+1:0x5fcb04,&arr[1]:0x5fcb04 自增操作後形參arr的值:0x5fcb04,地址:0x5fcae0 自增操作後,*arr:2, arr[0]:2
可以看出,雖然形參arr被聲明為int型數組,但在函數內部,arr已不再擁有數組的性質,而擁有了指向int型的指針的性質:
arr的大小是指針的大小(8),而非數組的大小(數組的大小是所有元素大小的總和,上例中:4×3=12)
arr可以被修改,而數組名不可以被修改
arr與a的內存地址不同但值相同(都是數組首元素地址),所以,在函數被調用時,真正被傳遞的值是數組首元素的地址,並以此地址初始化了一個指向實參數組首元素的指針作為形參。因為實際的形參是一個指針,所以這種情況也是屬於值傳遞。同時,與指針參數效果一樣,在函數內對“數組”的修改會直接作用於外部。
由此我們看到,數組作為參數與指針作為參數並沒有什麼區別,因為編譯器會自動將形參中的數組名轉換為一個指針。所以,以下面代碼中的兩個函數簽名是等效的:
void func(int arr[]){ } void func(int * p){ }
實際上,如果我們同時定義了它們,編譯器會報“redefinition”錯誤而無法編譯。
此外需要注意的是,雖然我們可以將形參聲明成一個數組,但在編譯器看來它只是一個指針而已,因此形參中有關數組長度的信息會被自動忽略,在實際調用時實參數組的長度與形參指定的數組長度沒有任何關系,而且在函數內也無法通過 sizeof(arr)/sizeof(arr[0]) 得到數組的長度。
//形參arr的指定長度5無意義,會被編譯器忽略,未防止誤解建議不寫 void arrArg(int arr[5]){ // sizeof(arr)是指針類型的大小而非數組大小,因此len不是數組長度 int len = sizeof(arr)/ sizeof(arr[0]); }
自定義類型參數
數組這種“組合”類型在作為參數傳遞時被解析為指針方式傳遞,那麼當結構體(struct)、共同體(union)以及枚舉類型作為參數傳遞時是否也是如此呢?
枚舉類型本質上是int,所以當形參被聲明為枚舉類型時,在編譯器看來就是int型,以下面代碼中的兩個函數簽名是等效的,編譯器會報“redefinition”錯誤:
enum week{ Mon, Tues, Wed, Thurs, Fri, Sat, Sun }; void func(enum weekday){} void func(int i){}
下面是結構體和共同體分別作為參數時的示例:
#include <stdio.h> struct Date{ int year; int month; int day; }; void changeStruct(struct Datedate){ printf("形參date的地址:%#x\n", &date); date.year ++; date.month = 12; date.day = 31; printf("形參date的值:%d-%d-%d\n",date.year,date.month,date.day); } union Data{ int i; float f; double d; }; void changeUnion(unionDatadata ){ printf("形參data的地址:%#x\n", &data); printf("形參data的值,d.i:%d, d.f:%f, d.d:%f\n",data.i,data.f,data.d); data.d = 2017.4; printf("函數調用後,形參data的值,d.i:%d, d.f:%f, d.d:%f\n",data.i,data.f,data.d); } int main(){ printf("結構體參數傳遞示例:\n"); struct Datedate = {2017,4,2}; printf("實參date的地址:%#x\n", &date); printf("實參date的值:%d-%d-%d\n",date.year,date.month,date.day); changeStruct(date); printf("函數調用後,實參date的值:%d-%d-%d\n",date.year,date.month,date.day); printf("共用體參數傳遞示例:\n"); unionDatadata={.i=2017}; printf("實參data的地址:%#x\n", &data); printf("實參data的值,d.i:%d, d.f:%f, d.d:%f\n",data.i,data.f,data.d); changeUnion(data); printf("函數調用後,實參data的值,d.i:%d, d.f:%f, d.d:%f\n",data.i,data.f,data.d); return 0; }
編譯後運行,輸出如下:
結構體參數傳遞示例: 實參date的地址:0x5fcb00 實參date的值:2017-4-2 形參date的地址:0x5fcae0 形參date的值:2018-12-31 函數調用後,實參date的值:2017-4-2 共用體參數傳遞示例: 實參data的地址:0x5fcaf0 實參data的值,d.i:2017, d.f:0.000000, d.d:0.000000 形參data的地址:0x5fcab0 形參data的值,d.i:2017, d.f:0.000000, d.d:0.000000 函數調用後,形參data的值,d.i:-1717986918, d.f:-0.000000, d.d:2017.400000 函數調用後,實參data的值,d.i:2017, d.f:0.000000, d.d:0.000000
我們發現,C中的自定義類型作為參數傳遞時,和基本類型一樣,也是按值傳遞。
小結
C語言中,參數類型為字符型、整型、浮點型以及枚舉型、結構體(struct)和共同體(union)時,都是常規的值傳遞。而指針作為一種特殊的類型,其值為一個地址,所指向的基類型可以任意其他類型(包括void)甚至可以指向函數,所以,指針作為參數時雖然本質上屬於值傳遞,但因為傳遞的是一個地址,可以讓被調函數通過對指針所指內容的操作直接作用於外部數據,屬於參數傳遞中特殊的值傳遞方式。雖然可以把形參聲明為數組,但實際的形參卻是指針,因此,數組作為參數的傳遞方式也是特殊的值傳遞。
我們通過依次考察C語言中各種數據類型發現: C語言中只有值傳遞!