关于函数参数&局部变量&返回值的思考
函数的参数、局部变量、返回值的思考,是基于以前的编码过程中出现的问题,做的文字性总结,少量代码做补充!
在进行以下之前,先述说下函数的运行及调用:
很少有哪个完整的程序,是在一个func中完成所有的功能而不调用其他函数接口的。函数接口的功能,要紧凑且单一化,便于代码模块化及分析维护
如:main()->funcA() call: funcAa()->funcAb()->funcAc()
| result: <-funcAa()<-funcAb()<-funcAc()
|
V
funcB() call: funcBa()->funcBb()->funcBc()
| result: <-funcBa()<-funcBb()<-funcBc()
|
V
funcC() call: funcCa()->funcCb()->funcCc()
| result: <-funcCa()<-funcCb()<-funcCc()
|
V
...
每一个函数接口的完成,包括main(),都是由多个子函数接口完成的。不断的调用,也就是函数存在了嵌套。
有一个问题:如,funcA()在调用子接口funcAa()时,尚未返回。如从funcAa()执行完后,需回到funcA执行一半的位置继续执行,即是说:从funcAa()退出时,需要funcA()恢复现场!
funcA()恢复现场,即需要知道funcA()执行的位置,局部变量值,输出参数的指针地址、系统运行时寄存器等等
比如,系统寄存器只有一套,一旦在funcAa()中作了修改,返回到funcA中时,怎么取回funcA()的相应值呢?
是的,这些值都是在系统栈上面保存的:所有函数退出后将不需要保存的值,都是在栈上保存:比如系统寄存器值、局部变量值等
再一个问题:既然,如上第一个问题答案:函数嵌套是需要消耗系统栈空间的,那嵌套一层,需要消耗的栈多少?;嵌套两层,需要消耗栈多少?;嵌套N层呢?
答案是:嵌套的越多,消耗的栈越多。栈的消耗,是每层嵌套累加的!!!
最后一个问题:系统的栈需要设置多少合适呢?
答案是:越大越好!是的,越大越好。但问题是:系统的内存空间是有限的。
所以:系统栈的大小设置,简单来说,就是:系统的最大嵌套发生时,消耗栈的资源总和,比总和稍多点就OK
1、参数的数量:编译器没有限制参数的数量
a,从函数嵌套消耗的资源来考虑:参数越多,嵌套需要消耗的栈资源也就越多
b,从函数可读性考虑:参数越多,显得程序越凌乱。可考虑用结构体代替
2、参数的分类:
a、根据参数的作用域,分为形参和实参
例如:在main()中调用funcA(int age, int *pName)
形参:函数定义中,如funcA(int age, int *pName)中,age和pName叫做funcA的形参
作用域:被调函数中,如funcA()
实参:函数调用时,假如在main()中调用,如funcA(YeasOld, pName_zhangQiang),变量YeasOld和字符串数组指针pName_zhangQiang,就是funcA()的实参
作用域:funcA()的主调函数,如main()
作用:单向的值传递;此处注意,【1】是单向的,【2】是本身的值传递
也可以理解为:【用实参值初始化形参符号】
参数匹配:如funcA(int age, int *pName)中有两个参数,则主调函数中输入的[实参]必须与[形参]的 个数一致,类型一致,顺序一致
b、根据参数的数据类型:参数可以是任意类型,如常量、变量、表达式、函数等
实参按处理情况,可分为两种:
如:int a, char pName[]={0}, struct xxx sYYY={},typedef int (*func)(void)
1、传值调用:如a、pName数组、结构体sYYY做实参,这种也叫值传递
2、引用调用:如&a、&pName[0]、&sYYY做实参,这种也叫地址调用
解析:
a、pName是属于传值调用,如在changeString()中的用法,是无法修改pName的值的【注:即使通过pS[]的不断赋值,显示结果可修改,但实参pName的值仍旧是没有改变的】
changeString中,变量pS的值,是实参pName的值(等效于初始化), 即pName_A的地址。如此,将pS作为数组操作,就可以修改pName_A的值
因,pS也是一个变量,是可以做赋值的。如pS=pName_C,但pS变量地址是映射在寄存器(栈空间)上,函数退出,则pS值无法保存
b、&pName是属于引用调用,如在changeStringPoint()中的用法,可以通过*ppS=&pName_D[0]这种赋值,改变实参pName的值
changeStringPoint中,变量ppS的值,是实参pName的地址,即用pName地址将ppS做了初始化
因,ppS也是一个变量,是可以做赋值的。一旦ppS=xxx这种赋值出现,则ppS本身丢失了与实参pName的联系,将失去引用调用的意义
且因,ppS也是一个指针,是可以做指针操作的。即ppS是pName的地址,则按照指针的特点,*ppS=&pName_D[0]的操作,实际是修改了实参pName的值,使之指向&pName_D[0]
最后,ppS这个变量,根pS同样的,都是映射在寄存器(栈空间)上,函数退出,则ppS值无法保存。
【如下示例代码】
/*
传值调用:
引用调用:
*/
#include <stdio.h>
char pName_A[]="my name is A";
char pName_B[]="my name is B";
char pName_C[]="my name is C";
char pName_D[]="my name is D";
int changeStringPoint(char **ppS)
{
*ppS = &pName_D[0];
return 0;
}
int changeString(char *pS)
{
printf("changePre: %s\n", pS);
pS[0] = 'M';
printf("changePost: %s\n", pS);
pS = pName_C;
printf("changeLast: %s\n", pS);
return 0;
}
int main()
{
char *pName = NULL;
printf("pName: %s\n", pName);
pName = pName_A;
printf("pName: %s\n", pName);
changeString(pName);
printf("pNamePost: %s\n", pName);
changeStringPoint(&pName);
printf("pNamePointPost: %s\n", pName);
return 0;
}
3、返回值的数量:最多只允许一个返回值类型,或者无返回值
4、返回值的类型:事实上,return 语句时系统是在内部自动创建了一个临时变量,然后将 return 要返回的那个值赋给这个临时变量
因此,可以做为值赋给临时变量的,都可以作为返回值类型
但,返回值这块经常会出错,碰到的出错,都是因为返回值中,包含了函数局部变量地址(也属于函数栈空间指针的一种)、指向函数栈空间的指针,被释放调的堆指针
则,当函数返回后,需要拷贝return的临时变量时,如访问到了无效的指针,则出错
关于函数返回值具体怎么操作的,此处不做分析,容后再补上,以作记录用

浙公网安备 33010602011771号