【C语言】形参实参以及参数传递
文章目錄
- 一、形參與實參
- 1、定義
- 2、區別
- 3、聯系
- 二、參數傳遞
- 1、定義
- 2、分類
- (1)值傳遞
- (2)址傳遞
- (3)引用傳遞
- 三、一個看起來是址傳遞其實是值傳遞的例子
- 1、問題描述
- 2、推理過程
- (1)事實——址傳遞本質上是值傳遞
- (2)類比
- (3)結論
- 3、解決辦法
- (1)方法1——返回指針
- (2)方法2——址傳遞
- 參考資料
一、形參與實參
1、定義
==形參:==形參全稱形式參數,就是定義函數時圓括號中的參數。
==實參:==實參全稱實際參數,就是調用函數時圓括號中的參數。
如下所示:
#include <stdio.h> //計算從m加到n的值 int sum(int m, int n) {int i;for (i = m+1; i <= n; ++i) {m += i;}return m; } int main() {int a, b, total;printf("Input two numbers: ");scanf("%d %d", &a, &b);total = sum(a, b);printf("a=%d, b=%d\n", a, b);printf("total=%d\n", total);return 0; }函數定義處的 m、n 是形參,函數調用處的 a、b 是實參。
2、區別
(1)形參本質是一個占位符,是一個名字,是虛擬變量,不占用內存;實參本質是一個變量,它包含實實在在的數據,占用內存。
(2)實參在函數外部有效,而形參在函數內部有效。
3、聯系
(1)實參和形參在數量上、數據類型上、順序上必須嚴格一致,否則會發生“類型不匹配”的錯誤。當然,如果能夠進行自動類型轉換,或者進行了強制類型轉換,那么實參類型也可以不同于形參類型。
(2)函數調用中發生的數據傳遞是單向的,只能把實參的值傳遞給形參,而不能把形參的值反向地傳遞給實參;換句話說,一旦完成數據的傳遞,實參和形參就再也沒有瓜葛了,所以,在函數調用過程中,形參的值發生改變并不會影響實參。
(3)形參和實參雖然可以同名,但它們之間是相互獨立的,互不影響
二、參數傳遞
1、定義
發生函數調用時,實參的值會傳遞給形參,這一過程叫做參數傳遞。
2、分類
(1)值傳遞
什么是值傳遞?看下面這個例子
#include <stdio.h>void swap (int x, int y) {int temp;temp = x;x = y;y = temp;printf ("x = %d, y = %d\n", x, y); }int main (void) {int a = 4, b = 9;swap (a, b);//這里調用swap函數printf ("a = %d, b = %d\n", a, b);return 0; } 輸出結果: x = 9, y = 4 a = 4, b = 9調用swap時實際完成的操作如下:
int x=a; //← int y=b; //←注意這里,頭兩行是調用函數時的隱含操作 int tmp; tmp=x; x=y; y=tmp; printf ("x = %d, y = %d\n", x, y);其實函數在調用時是隱含地把實參a,b 的值分別賦值給了x,y(這就是所謂的值傳遞),之后在你寫的 swap 函數體內再也沒有對a,b進行任何的操作了。交換的只是x,y變量。并不是a,b。因此a,b的值并沒有改變。
(2)址傳遞
址傳遞本質上還是值傳遞,它是值傳遞的一種特殊情況。
看下面的例子:
#include <stdio.h> void swap (int *px, int *py) {int temp=*px;*px=*py;*py=temp;printf("*px = %d, *py = %d\n", *px, *py); }int main(void) {int a=4;int b=6;swap (&a,&b);printf("a = %d,b = %d\n", a, b);return 0; } 輸出結果: *px = 6, *py = 4 a = 6,b = 4對于void swap (int *px, int *py),請注意參數px,py都是指針。
調用時:swap (&a, &b); 它將 a 的地址 &a 代入到 px,b 的地址 &b 代入到 py。
調用 swap 函數時實際執行過程如下:
px=&a; py=&b; //請注意這兩行,它是調用 swap 的隱含動作。 int temp=*px; *px=*py; *py=temp; printf("*px=%d,*py=%d\n",*px, *py);指針 px,py 的值已經分別是 a,b 變量的地址值了。接下來,對 *px,*py 的操作當然也就是對 a,b 變量本身的操作了。所以函數里頭的交換就是對 a,b 值的交換了,因此 a,b 的值改變了。這就是所謂的址傳遞(將 a,b 的地址傳遞給 px,py)。
現在可以理解為什么址傳遞是值傳遞的一種了。他們都是做的變量賦值。即
值傳遞和址傳遞都是在調用函數時將實參的值賦值給形參,但址傳遞時實參和形參都是地址。
注意:這里不要理解為 “對于址傳遞,調用函數是將實參的地址賦值給形參。”這是錯誤的,因為這里實參為&a,&b本身就是地址,因此就是將實參的值賦值給形參。
對于值傳遞,調用函數并不能改變變量a,b的值;對于址傳遞,調用函數并不能改變&a, &b的值。
他們本質上是一樣的。
(3)引用傳遞
引用傳遞效果和址傳遞一樣,但比址傳遞簡單。
如下所示:
#include <stdio.h> void swap (int &x, int &y) {int temp = x;x = y;y = temp;printf ("x = %d, y = %d\n", x, y); }int main (void) {int a = 4;int b = 6;swap (a, b);printf ("a = %d, b = %d\n", a, b);return 0; }輸出結果: x = 6, y = 4 a = 6, b = 4引用傳遞中形參改變會使實參也改變。
為什么?
因為變量和變量的引用操作的是同一塊內存。
三種傳遞方式的實參形參區別如下:
| 實參 | 普通變量 | 地址 | 普通變量 |
| 形參 | 普通變量 | 地址 | 引用 |
| 形參的改變會否影響實參 | 不會 | 會 | 會 |
三、一個看起來是址傳遞其實是值傳遞的例子
1、問題描述
這里我新建一個鏈表,使用尾插法插入4個節點,存儲數據1,2,3,4。遍歷鏈表后刪除整個鏈表。
節點定義如下:
typedef struct node {data_t data;struct node* next; }listnode, * linklist;創建鏈表函數如下:
linklist list_create()//創建鏈表 {//第一步:申請內存linklist H;H = (linklist)malloc(sizeof(listnode));//這里用llistnode和用linklist到底有什么區別?//答:申請內存大小不一樣,若實際使用的內存超過申請的內存,free時就會報錯if (H == NULL){printf("malloc failed\n");return H;}//第二步:賦初值H->data = 0;//頭結點數據域沒有用到,默認放0H->next = NULL;//返回頭結點return H; }尾部插入節點函數如下:
int list_tail_insert(linklist H, data_t value) {linklist p;linklist temp;//為找到尾節點而定義的臨時節點指針if (H == NULL){printf("H is NULL\n");return -1;//若鏈表還沒創建就不能有后續操作,提升程序健壯性}//第一步:建立一個新節點if ((p = (linklist)malloc(sizeof(listnode))) == NULL){printf("malloc failed\n");return -1;}p->data = value;p->next = NULL;//因為是尾節點,所以指針域為NULL//第二步:找到尾節點,尾節點的指針域為NULLtemp = H;//從頭開始找while (temp->next){temp = temp->next;}//temp從循環出來已經是指向尾節點了//第三步:建立新節點與鏈表尾部的連接temp->next = p;return 0; }鏈表刪除函數如下:
int list_free(linklist H) {linklist p;if (H == NULL){printf("H is NULL\n");return -1;}while (H != NULL){p = H;H = H->next;printf("釋放元素:%d\n", p->data);free(p);p = NULL;}puts("");//換行???return 0; }鏈表遍歷函數如下:
int list_show(linklist H) {linklist p;if (H == NULL){printf("H is NULL\n");return -1;}p = H;while (p->next != NULL){printf("%d ", p->next->data);p = p->next;}puts("\n");return 0; }執行結果如下:
第一步:創建鏈表完成 input a number:1 input a number:2 input a number:3 input a number:4 input a number:-1 第二步:輸入數據完成 第三步:遍歷鏈表: 1 2 3 4第四步:刪除整個鏈表 before free:00866A58 釋放元素:0 釋放元素:1 釋放元素:2 釋放元素:3 釋放元素:4after free:00866A58首先看int list_tail_insert(linklist H, data_t value)函數,只看第一個形參,應該是址傳遞,因為linklist H是結構體指針,它也可以寫成linklist *H,這就跟我們常見的int fun(int *a)是一樣一樣的了,傳入的是一個指針。從結果上看也是如此,即函數內H的變化也會引起函數外H的變化,這符合址傳遞的特點。
因此int list_tail_insert(linklist H, data_t value)函數是址傳遞無疑。
再看另一個int list_free(linklist H)函數,除了函數名其他的跟上面的函數簡直是一模一樣,那它也是址傳遞咯。
真的嗎?
list_free中有這樣一個操作:
while (H != NULL){p = H;H = H->next;printf("釋放元素:%d\n", p->data);free(p);p = NULL;}要跳出該循環,H只能為NULL。
那好我們看看list_free函數執行前后,H是不是變成NULL了。
在main中我們做如下操作:
printf("第四步:刪除整個鏈表\n");printf("before free:%p\n", H);list_free(H);printf("after free:%p\n", H);執行結果:
第四步:刪除整個鏈表 before free:00866A58 釋放元素:0 釋放元素:1 釋放元素:2 釋放元素:3 釋放元素:4 after free:00866A58執行完,H不是NULL!,并且還保持原樣!
函數內部的變化并沒有影響函數外部的H,那這又是值傳遞咯?
為什么對于如下兩個函數,形參都是linklist H,一個是址傳遞,一個卻是值傳遞?
int list_tail_insert(linklist H, data_t value);//址傳遞 int list_free(linklist H);//值傳遞2、推理過程
(1)事實——址傳遞本質上是值傳遞
還是看之前址傳遞中提及的例子:
#include <stdio.h> void swap (int *px, int *py) {int temp=*px;*px=*py;*py=temp;printf("*px = %d, *py = %d\n", *px, *py); }int main(void) {int a=4;int b=6;swap (&a,&b);printf("a = %d,b = %d\n", a, b);return 0; } 輸出結果: *px = 6, *py = 4 a = 6,b = 4說址傳遞本質還是值傳遞,是因為調用函數只能改變a,b的值,并不能改變&a, &b的值。即只能改變指針指向的值的大小,不能改變指針本身。
也就是說:
你看,對于一個典型的址傳遞的例子,內部也同時出現了址傳遞和值傳遞的情況。
(2)類比
這兩個函數傳遞參數都是linklist H,或者說是linklist *H,兩者等價。
int list_tail_insert(linklist H, data_t value);//址傳遞 int list_free(linklist H);//值傳遞一個是址傳遞,對應的是上述中的*px=3; 想改變指針指向的值,這樣做能影響函數外部參數。
一個是值傳遞,對應的是px=NULL; 想改變指針本身,這樣做不能影響函數外部參數。
也就是說list_tail_insert函數中發生的是類似于px=3;的操作,即H=something,那我們找找該函數哪里發生了這種操作:
int list_tail_insert(linklist* H, data_t value) {linklist p;linklist temp;if (H == NULL)//不是這里{printf("H is NULL\n");return -1;}if ((p = (linklist)malloc(sizeof(listnode))) == NULL){printf("malloc failed\n");return -1;}p->data = value;p->next = NULL;temp = H;//不是這里while (temp->next){temp = temp->next;//不是這里}temp->next = p;//不是這里return 0; }該函數就沒有改變H指向的值的代碼,因為H作為頭指針不需要改變!
然后我們再看list_free,說它是值傳遞,那函數中發生的應該就是類似于px=NULL;這樣 的操作,我們找一找:
int list_free(linklist H) {linklist p;if (H == NULL){printf("H is NULL\n");return -1;}while (H != NULL){p = H;H = H->next;//就是這里了,想改變指針H本身的指向,//這樣做不能影響函數外部參數printf("釋放元素:%d\n", p->data);free(p);p = NULL;}puts("");return 0; }(3)結論
list_tail_insert函數中因為沒有對H進行操作,所以不管它是址傳遞還是值傳遞都不會對H產生影響,變動的是鏈表節點,函數中對節點的操作,在函數外也會同步。
list_free就是值傳遞了,因為它想改變H,想使H==NULL,但因為是值傳遞,所以不能如愿,函數內的操作不能影響函數外參數。
3、解決辦法
(1)方法1——返回指針
改成這樣
linklist list_free(linklist H) {linklist p;if (H == NULL){printf("H is NULL\n");return NULL;}while (H != NULL){p = H;H = H->next;printf("釋放元素:%d\n", p->data);free(p);p = NULL;}puts("");//換行???return NULL; }再執行此操作
printf("第四步:刪除整個鏈表\n");printf("before free:%p\n", H);H = list_free(H);printf("after free:%p\n", H);list_show(H);第四步:刪除整個鏈表 before free:00647DD8 釋放元素:0 釋放元素:1 釋放元素:2 釋放元素:3 釋放元素:4after free:00000000 H is NULL結果H就為NULL了。
(2)方法2——址傳遞
int list_free(listnode** H) {linklist p;if (*H == NULL){printf("H is NULL\n");return -1;}while (*H != NULL){p = *H;*H = (*H)->next;printf("釋放元素:%d\n", p->data);free(p);p = NULL;}puts("");return 0; }執行操作:
printf("第四步:刪除整個鏈表\n");printf("before free:%p\n", H);list_free(&H);printf("after free:%p\n", H);list_show(H);第四步:刪除整個鏈表 before free:00FB7DD8 釋放元素:0 釋放元素:1 釋放元素:2 釋放元素:3 釋放元素:4after free:00000000 H is NULL結果正常!
參考資料
1、http://c.biancheng.net/view/1853.html
2、https://blog.csdn.net/qq_29350001/article/details/53740305
3、https://baike.baidu.com/item/引用傳遞/2880658?fr=aladdin
4、http://www.makeru.com.cn/course/details/12749
總結
以上是生活随笔為你收集整理的【C语言】形参实参以及参数传递的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2023年会议教学庭审录像机产品分析
- 下一篇: CSS进阶班笔记(五)