C语言函数值传递

之前在某高教等级考试书中有这么一题,代码如下:

#include <stdio.h>
#include <stdlib.h>
void fun(float *p1,float *p2,float *s)
{
	s=(float *)calloc(1,sizeof(float));
	*s=*p1+*p2++;
}
int main()
{
	float a[2]={1.1,2.2},b[2]={10.0,20.0},*s=a;
	fun(a,b,s);
	printf("%5.2f\n",*s);
	return 0;
}

结果如下:

居然还是未调用函数之前的结果,嗯,我们先把它放在这里。

在学习C语言中我们都知道有这样的例子,交换数据:

#include <stdio.h>
void swap(int a,int b)
{
	int t;
	printf("(2)a=%d  b=%d\n",a,b);  /*调用函数过后形参的值*/
	t=a;a=b;b=t;
	printf("(3)a=%d  b=%d\n",a,b);  /*调用函数内交换后形参的值*/
}
int main()
{
	int a=10,b=20;
	printf("(1)a=%d  b=%d\n",a,b);  /*实参a,b未调用函数时的值*/
	swap(a,b);
	printf("(4)a=%d  b=%d\n",a,b);  /*实参a,b调用后的值*/
	return 0;
}

结果如下:


很明显,交换不成功,我们都知道该怎么改这段程序,把形参改为指针嘛,程序如下:

#include <stdio.h>
void swap(int *a,int *b)
{
	int t;
	printf("(2)a=%d  b=%d\n",*a,*b);   /*调用函数过后形参的值*/
	t=*a;*a=*b;*b=t;
	printf("(3)a=%d  b=%d\n",*a,*b);   /*调用函数内交换后形参的值*/

}
int main()
{
	int a=10,b=20;
	printf("(1)a=%d  b=%d\n",a,b);    /*实参a,b未调用函数时的值*/
	swap(&a,&b);
	printf("(4)a=%d  b=%d\n",a,b);    /*实参a,b调用后的值*/
	return 0;
}

结果如下:


这次显然就交换成功了,你可能会说这么简单的东西还用提?道理我都懂啊,之前之所以交换不成功是因为函数调用完,形参就被释放掉了,而采用指针就可以把实参地址中的数据进行改动,然后交换成功(我之前就这么理解的)。嗯,说的好像有几分道理,你真的懂了吗?我把上面的例子稍微改动一下:

#include <stdio.h>
void swap(int *a,int *b)
{
	printf("(2)a=%d  b=%d\n",*a,*b);   /*调用函数过后形参的值*/
	int *t=a;a=b;b=t;
	printf("(3)a=%d  b=%d\n",*a,*b);   /*调用函数内交换后形参的值*/

}
int main()
{
	int a=10,b=20;
	int *p1=&a,*p2=&b;
	printf("(1)a=%d  b=%d\n",*p1,*p2);    /*实参a,b未调用函数时的值*/
	swap(p1,p2);
	printf("(4)a=%d  b=%d\n",*p1,*p2);    /*实参a,b调用后的值*/
	return 0;
}

当然,这个例子不是真正意义上的交换,即a和b的值互换,我们可以看出它的目的是想把指a的指针和指b的指针指向互换,然后进行引用并显示,达到“互换”的功能。

结果如下:


没成功,我们解读一下swap函数中这段程序(如下):设置了一个指针t存下形参a的指向,a获取了b的指向,b再从t拿到a原来的指向,没错啊,这么下来应该原来的指针p1和p2指向就互换成功了啊,问题出在哪呢?

int *t=a;a=b;b=t;

我来解释:

1.首先要明确C语言中的函数和数学上的函数是有很大不同的,我们都知道数学中函数是这样的:y=f(x),你想要使用这个函数就把什么东西带入表达式一算就好了,自变量x就是个形式,就没有存在过,只有被替换的命运;那么C语言中函数的形参呢?

void swap(int a,int b)

比如上面这一条,千万不要还把a和b还看作是自变量x那样,程序是确确实实执行了这两个变量的设置(如下),也分配好了它们俩的存储空间,也就是说形参是真实存在的。

int a;int b;

我之前的思维就是把实参当人看,形参不当人看了,结果学了指针后问题全出来了。在后面的学习中我们知道,这里的形参实参都是局部变量,作用域为所在函数从定义变量处到函数尾部这一区间,这里形参的作用域为swap函数,实参的自然就是主函数了,这也是为什么这里形参和实参能取一样名字的原因。(PS:当全局变量与局部变量重名的时候,起作用的是局部变量,全局变量被屏蔽掉

2.C语言函数只有值传递。啥是值传递,有的人说是把实参的值复制或者拷贝到形参,这样说当然是对的,但是其真实的过程你可能就不清楚了,我们拿出下面这两条程序:

void swap(int a,int b)
swap(a,b);

一个是函数声明,一个是函数调用,真实的调用过程是什么样的呢?

int a1=a;int b1=b;

我解释过了形参可以和实参同名,这里为了方便起见,把形参表示为a1,b1。真实的过程就是这样,就是这么两条赋值语句,你完全可以看到,这么赋值完之后和原来的实参还有一毛钱关系么,就好比一个接力赛,接力棒一传,大家大路朝天各走一边,谁也管不了谁。那我们换成指针看看,你也应该会了:

void swap(int *a,int *b)
swap(&a,&b);

真实的过程就是:

int *a1=&a;int *b1=&b;

我这两个形参指针直接就指向两个实参,你这两实参就受我操控,我让你是多少就是多少,所以最后能交换成功。

我们再换成我改的例子:

void swap(int *a,int *b)
swap(p1,p2);

真实的过程就是:

int *a=p1;int *b=p2;

这里的*号不是引用而是指针变量的说明,也就是这样:

a=p1;b=p2;

注意以上四个变量均为指针,这个赋值表示什么你应该清楚吧,形参指针a和形参指针b分别被赋予p1和p2的指向,那p1和p2的指向是谁呢,是主函数里的a和b,就是说形参指针a,b指向我主函数的a,b了。还是刚才的过程,调用结束后两组指针大路朝天,各走一边,我连swap函数里的过程看都不用看:

int *t=a;a=b;b=t;

你在里面怎么折腾都是在折腾形参的指向,跟我原来的指针p1和p2有关系么,你管的着我的指向么。当然,这里对于指针建议大家多画图分析,更清晰明了:


这是刚开始调用的指针指向情况,执行完函数后:


我们回过头看最开始的那个动态分配的问题,你发现你啥都懂了,我直接画图解释了:


这里形参指针s还是用了s1表示,这里还有个问题,其实还应该有两个指针,即数组a,b退化后的指针,关于数组的退化我可能后面会单独讲一下,这里我就不画了,容易引起歧义。总之,刚开始调用的时候就是上面这个样子,调用结束后:


你不妨可以在子函数中加一条printf语句,显示形参指针s引用的值,当然结果是11.1。

那么上面不成功的例子应该怎么去改呢,我们采用二级指针的方式。以动态分配那个问题为例:

#include <stdio.h>
#include <stdlib.h>
void fun(float *p1,float *p2,float **s)
{
	*s=(float *)calloc(1,sizeof(float));
	**s=*p1+*p2++;
}
int main()
{
	float a[2]={1.1,2.2},b[2]={10.0,20.0},*s=a;
	fun(a,b,&s);
	printf("%5.2f\n",*s);
	return 0;
}

有些人就怕二级指针,其实我也挺怕的,但是理解了之后它就一点不可怕了。二级指针就是指向指针的指针,我们可以用它来操作一级指针的指向。我们还是来按照上面的思路来分析:

float **s1=&s;

两个**号还是说明符,说明我是指向指针的指针,这时形参s已经可以操纵实参s了,接下来

*s=(float *)calloc(1,sizeof(float));

这个*号就是引用了,引用了实参指针s,使它指向calloc分配区域,接下来

**s=*p1+*p2++;

两个*号就是两次引用了,引用实参指针s再由实参指针引用到calloc区域,就可以对calloc中的数据进行操作了。可能你觉得不太清晰,这里就突出画图的重要性了:


调用结束后


那个交换的例子你可以拿过去修改练习,最好要结合画图,这很重要。

以上啰嗦了这么多,只是为了解决一个简单的问题,然而这么一个简单的问题却出过面试题,因此我们一定要重视基础,如函数传参这个过程,你如果模棱两可,问题很快暴露出来。而且指针这个东西更要重视,画图分析是一个很好的方法。嗯,就说这么多吧。



  • 32
    点赞
  • 76
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值