文章目录

  • 一、形参与实参
    • 1、定义
    • 2、区别
    • 3、联系
  • 二、参数传递
    • 1、定义
    • 2、分类
      • (1)值传递
      • (2)址传递
      • (3)引用传递
  • 三、一个看起来是址传递其实是值传递的例子
    • 1、问题描述
    • 2、推理过程
      • (1)事实——址传递本质上是值传递
      • (2)类比
      • (3)结论
    • 3、解决办法
      • (1)方法1——返回指针
      • (2)方法2——址传递
  • 参考资料

一、形参与实参

1、定义

==形参:==形参全称形式参数,就是定义函数时圆括号中的参数。
==实参:==实参全称实际参数,就是调用函数时圆括号中的参数。

如下所示:

#include //计算从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  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 = 4a = 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 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 = 4a = 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 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 = 4a = 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:1input a number:2input a number:3input a number:4input 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释放元素:4after free:00866A58

执行完,H不是NULL!,并且还保持原样!

函数内部的变化并没有影响函数外部的H,那这又是值传递咯?

为什么对于如下两个函数,形参都是linklist H,一个是址传递,一个却是值传递?

int list_tail_insert(linklist H, data_t value);//址传递int list_free(linklist H);//值传递

2、推理过程

(1)事实——址传递本质上是值传递

还是看之前址传递中提及的例子:

#include 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 = 4a = 6,b = 4

说址传递本质还是值传递,是因为调用函数只能改变a,b的值,并不能改变&a, &b的值。即只能改变指针指向的值的大小,不能改变指针本身。
也就是说:

void swap (int *px, int *py){*px=3;//可以改变px指向的值的大小,属于址传递px=NULL;//不可以改变px的指向,属于值传递int temp=*px;*px=*py;*py=temp;printf("*px = %d, *py = %d\n", *px, *py);}

你看,对于一个典型的址传递的例子,内部也同时出现了址传递和值传递的情况。

(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:00000000H 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:00000000H 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