文章目录
- 一、形参与实参
- 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