C|从函数抽取和函数调用的副作用的视角来理解函数传参
函数是C语言一种非常重要的语法机制,一方面是任务分解、模块化的需要;另一方面也是代码重用的需要。
C语言要求函数不能嵌套定义,但可以嵌套调用。一方面,主调函数与被调函数之间可以保持独立性(互不影响,不改变彼此的内部状态,也就是局部变量的值);另一方面,被调函数也可以通过一定的语法机制来产生副作用来修改主调函数的状态(局部变量的值)。
主调函数利用一种副本机制传递参数给被调函数。在被调函数调用时,会将参数值先压栈(在栈上创建一个副本),如果是值就将值压栈,如果是地址就将地址压栈。值的副本机制让彼此相互独立(即使返回一个值或地址给主调函数,相互之间也没有影响彼此状态),如果是址的副本机制就不一样了,如果在被调函数中解引用这个地址做左值的话,就可以更改这个地址(指针)所指向的内存空间的值,如果这个地址是主调函数的变量,更改的就是主调函数的局部变量,如果这个地址是外部变量(全局变量),更改的就是外部变量的值。这种址的副本机制就是被调函数使用指针变量做参数,主调函数给一个局部变量的地址传给被调函数,称为一种传址的语法机制来产生副作用。
上述的传参(传值和传址)也可以从函数抽取的角度来理解。当一个函数的代码过长,或有一些代码块有重复利用的需要时,可以将这些代码抽取为函数,在抽取时,需要考虑这些代码在上下文中有没有赋值给其它变量?有没有副作用(有没有修改其它变量的值)?考虑这些因素来设计抽取(或封装)函数的返回值和传参的类型(传址还是传址)。如果有赋值给其它变量,就需要考虑函数返回,如果有副作用,就要考虑用指针来传参:
// 理解函数传参:函数调用的副作用
#if 0 // 未抽取或封装函数的版本
#include <stdio.h>
int main()
{
int a = 3;
int b = 5;
// 取较大值,后续抽取或封装为函数
int max = (a>b ? a : b); // a,b做右值,无副作用,后续按传值或const传值来提取函数
// 交换数据,后续抽取或封装为函数;
int t = a;
a = b;
b = t; // // a,b做右值,无副作用,后续按传值或const传值来提取函数
printf("%d %d %d\n",a,b,max); // 5 3 5
getchar();
return 0;
}
#else // 有抽取或封装函数的版本
#include <stdio.h>
int Max(int a,int b) // 有赋值,函数需要返回值
{
return a>b?a:b;
}
void Swap(int *a,int *b)
{
int t = *a;
*a = *b;
*b = t;
}
int Max(const int *a,const int *b) // 有副作用,用指针传参(传址)
{
return *a>*b?*a:*b;
}
int main()
{
int a = 3;
int b = 5;
// 取较大值,后续抽取或封装为函数;
int max = Max(a,b);
max = Max(&a,&b);
// 交换数据,后续抽取或封装为函数;
Swap(&a,&b);
printf("%d %d %d\n",a,b,max);// 5 3 5
getchar();
return 0;
}
#endif
可以看出,函数之间有彼此的作用域,但利用传址可以穿透彼此之间的作用域来产生副作用。传对象(变量)的地址到不同的作用域,可依据地址修改地址所指向对象的内容,根本原因是地址空间是开放的。
如果提取成宏函数,则无需考虑返回值以及参数和副作用,因为只是一个简单的查找替换关系(包括参数替换):
#include <stdio.h>
#define MAX(a,b) (((a)>(b)) ? (a) : (b))
#define SWAP(a,b) int t=a;\
a=b;\
b=t;
int main()
{
int a = 3;
int b = 5;
int max = MAX(3,5);
SWAP(a,b);
printf("%d %d %d\n",a,b,max); // 5 3 5
getchar();
return 0;
}
-End-
[注:本文部分图片来自互联网!未经授权,不得转载!每天跟着我们读更多的书]
互推传媒文章转载自第三方或本站原创生产,如需转载,请联系版权方授权,如有内容如侵犯了你的权益,请联系我们进行删除!
如若转载,请注明出处:http://www.hfwlcm.com/info/69487.html