C 语言复习与提高--- V. 函数

V. 函数

C 程序全部由函数构成,它提供所有程序活动的场所。

一、模块化设计: 要学会合理地划分程序中的各个模块。在设计一个大型的商业程序时,可以采用一种好的策略,即把一个大的程序分割成一些相对独立、且便于管理和阅读的小块程序。关键是把它分解成若干个小块(模块),使每个模块都能比较容易地进行编码。最终要精心实现的就是这些模块并以此构成完整的程序。这就是软件工程中的“自顶向下”的模块化设计模式。

1、模块化设计模式的最大的优点是:使代码容易地被重复使用。

2、模块化设计模式需要注意的问题:划分函数的标准是什么?注意程序的内聚性和耦合性。可能在程序中被重复调用的代码应该单独定义成一个模块。

把相关的语句组织在一起,并给它们注明相应的名称,利用这种方法把程序分块,这种形式的组合就是函数。

函数的使用通过函数调用机制实现。函数调用指定了被调用函数的名字和调用函数所需的信息(参数列表)。

程序员编写完成指定任务的函数是用户自定义函数;标准库函数是 C 提供的可以用在任何程序中的公共函数。

3、在一个完整的 C 程序中,有且只有一个 main() 作为程序的入口点。其它函数都是直接或间接地被 main() 调用。

二、函数的定义和声明:

1、return_type function_name(type var1, type var2, …, type varN) { /* 函数体 */ }

/** 在 C89 中,允许返回类型为 int 型的函数省略前面的 int 关键字,但 C99 和 C++ 均不允许。 * 注意,在现代的大型商业项目中,不允许省略返回类型为 int 型函数前面的 int 关键字。 * 否则编译器将无法进行参数检查 * *另外注意,C99 和 C++ 增加了 inline 关键字。 */

2、所有函数在使用前都必须作出声明:从 C89 开始使用函数原型。

(1、)return_type function_name(type, type, …, type); /* 声明一个函数 */

/** 参数名可选。但当发生错误时,编译器能够按照参数名辨别出任何类型的失配。 * 声明是一条语句,必须以分号结尾。 */

(2、)从技术上讲,原型并不是必须的,但是非常有用。在 C++ 和 JAVA 中都是必须要用的。它有助于事先捕获 bug。 原型能使编译器提供更强的类型检查,它能在用于调用一个函数的变量与参数类型之间找出有问题的类型转换,并能捕捉到用于调用函数的变量号与该函数中的参数号之差。

(3、)若在程序中首次使用一个函数前就已经定义了该函数,则函数的定义可作为其声明使用。 但在大型商业软件的开发中(一般都有很多文件),程序应包含每个函数的单独声明(这实际上就是 C 代码的编写方法)。

(4、)main() 不需要声明,它是程序执行的入口点。

(5、)可变长参数列表:return_type function_name(type var1, ...); /* 使用 ... */ /** 必须至少定义一个参数 * int f1(...); 这是非法的 */

原理:不管参数有多少,看成字符串,压栈,再逐一取出。 [例]printf() 就是最典型的例子。

3、需要注意的问题:

(1、)若函数无参数,声明时使用 void 关键字:int f1(void); /* 若用参数调用则出错 */ 注意与 C++ 的区别:void f1(); /* void 不是必须的 */

(2、)移植问题: 在 C 的早期版本中,函数声明只需要告诉编译器函数的返回类型就可以了。如: int Max(); /* 基本被淘汰 */ 在移植到 C++ 时,必须在编译前增加变量类型部分:int Max(int, int);

(3、)ANSI C 支持两种形式的参数定义:

--> 经典形式:C89 和 C99 都支持,但 C++ 不支持。所以移植时会出问题。 ANSI C 已经明令废弃这种方式。

int Max(a, b) int a, b; { /* 处理 */ }

--> 现代形式:C89、C99 和 C++ 都支持(C++ 只支持现代方式)。

int Max(int a, int b) { /* 处理 */ }

(4、)声明的位置:

--> float f1(float), f2(float); void f3(void); int main() { /* 一般 main() 都放在最开始的位置 */ /* 处理 */

return 0; }

--> int main() { float f1(float), f2(float); void f3(void); /* 在函数体中声明其它函数的语句应放在最前面(所有动作之前)*/ /* 这种方式增加了函数管理的难度,很少使用 */

/* 处理 */

return 0; }

三、函数的调用:函数有各种表现形态,但其实质都是函数调用。要想熟练使用函数,必须先掌握函数调用机制。

1、在计算机语言中,有两种方法向子程序传递变量:

- 值调用(Call by Value):是 C 的一般方法,把变量值复制到子程序的形参中。参数的修改对变量没有影响。

- 引用调用(Call by Reference):把变量的地址复制到子程序的形参中,子程序通过地址访问实际变量。参数的修改能够影响用于子程序调用的变量的值。

[例1]值调用 [例2]引用调用 #include <stdio.h> 请自己写出引用调用的例子 int Max(); 看看自己是否真正理解了引用调用 int main() { int a=1, b=2; printf("the big one: %d/n", Max(a, b)); return 0; }

int Max(int a, int b) { return (a>b?a:b); }

2、递归(recursive):函数的自我调用,是用自己定义自己的过程。

函数调用自身时,在栈区为局部变量和参量分配内存空间,但并不复制函数代码,只重新分配相应的变量。每个递归调用返回时,其局部变量和参数的空间都被释放,在函数中的调用点继续执行。

递归程序的优点:能生成某些算法的更清晰、更简洁的版本。另外就是,某些专门的问题(如 AI)本质上是递归的,特别适合递归解,比迭代更便于思考。

多数递归不能明显改善内存效率。很多程序的递归版本比等价的迭代版本运行得慢,原因在于反复调用的开销太大。

过分递归可能溢出堆栈,冲垮程序的其它数据或代码。所以要保证递归深度不会失控。

编写递归时,必须在适当的位置放置条件判断语句,用于强制返回。否则,函数可能在被调用后永远也不返回。在开发时可用 printf() 和 getchar() 监视运行的推进并寻找错误。

[例]itoa() 函数:int --> char(ASCII 码)的转换。 #include <stdio.h> void itoa();

int main() { char *s; int n; scanf("%d", &n); itoa(n, s); puts(s);

return 0; }

void itoa(int n, char *s) { int i=0, j, power=1; if(n<0) { s[i++]='-'; n=-n; }

for (j=n; j/10; j/=10) power*=10; s[i++]=j+'0';

if(n/10) itoa(n-j*power, s+i); else s[i]='/0'; }

四、函数的返回值:

1、在 C89 中,若非 void 型函数执行不含值的 return 语句,则返回无用值。这点很糟糕。

2、编译时,函数基本分为三类:

(1、)计算(纯函数):专门设计成对变量实施操作后返回一个基于操作的值。

(2、)处理后返回操作的结果(成功或失败):如 fopen()。

(3、)无明显的返回值:这类函数是严格意义上的过程,没有返回值。应该被定义成 void 型,可防止误用。

[注意]早期 C 版本没有定义 void 关键字,所以在早期程序中,不返回值的函数只能缺省定义为 int 型。

3、有时返回值的意义不大,如 printf() 返回写出的字节数。

 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值