C Primer Plus 第九章——函数
函数(function)是用于完成特定任务的程序代码的自包含单元。为什么使用函数?第一,函数的使用可以省去重复代码的编写。如果程序中需要多次使用某种特定功能,那么只需编写一个合适的函数即可。第二,即使某种功能在程序中只使用一次,将其以函数的形式实现也是有必要的,因为函数使得程序更加模块化。
在使用函数之前,需要使用ANSI原型声明该函数。在函数原型中可以根据喜好省略(形参)变量名:
void show_n_char(char,int);
在原型中使用变量名并没有实际地创建变量。这只是说明char代表了一个char类型变量,依此类推。为了表明一个函数不使用参数,需要在圆括号内加入void关键字。一些函数比如printf()和scanf()使用的参数个数是变化的。对于这种情况,ANSI C允许使用不确定的函数原型。例如,对于printf()可以使用下面的函数原型声明:
int printf(char *,...);
这种原型表示第一个参数是一个字符串,而其余的参数不能确定。
函数原型是对语言的有力补充。它可以使编译器发现函数使用时可能出现的错误或疏漏。而这些问题不被发现的话,是很难跟踪调试出来的。
实际参数是赋给被称为形式参量的函数变量的具体值。因为被调函数使用的值是从调用函数中复制而来的,所以不管在被调函数中对复制数值进行什么操作,调用函数中的原值不会受到任何影响。 当函数返回值的类型和声明的类型不相同时,实际返回值是当把指定要返回的值赋给一个具有所声明的返回类型的变量时得到的数值。例如:
int what_if(int n)
{
double z=100.0/(double)n;
return z;
}
result = what_if(64)
这将把数值1.5625赋给变量z。然而,return语句返回的则是int类型的数值1。
return语句的另一作用是终止执行函数,并把控制返回给调用函数的下一语句。即使return语句不是函数的最后一个语句,其执行结果也是如此。
递归:C允许一个函数调用其本身,这种调用过程被称作递归(recursion)。递归又是很难处理,有时却很方便实用。当一个函数调用自己时,如果编程中没有设定可以终止递归的条件检测,它会无限制地进行递归调用,所以需要进行谨慎处理。
递归的几个要点:每一级的函数调用都有自己的变量(地址不同,值也不同)。每一次函数调用都会有一次返回。递归函数中,位于递归调用前的语句和各级被调函数具有相同的执行顺序。位于递归调用后的语句的执行顺序和各个被调函数的顺序相反。递归函数中必须包含可以终止递归调用的语句。
最简单的递归形式是把递归调用语句放在函数结尾即恰在return语句之前,这种形式被称作尾递归(tail recursion)或结尾递归(end recursion)。使用递归处理反序问题比使用循环更简单:
/* binary.c --以二进制形式输出整数 */
#include<stdio.h>
void to_binary(unsigned long n);
int main(void)
{
unsigned long number;
printf("Enter an integer (q to quit);\n");
while(scanf("5ul",&number)==1)
{
printf("Binary equivalent:");
to_binary(number);
putchar('\n');
printf("Enter an integer (q to quit):\n");
}
printf("Done.\n");
return 0;
}
void to_binary(unsigned long n) //递归函数
{
int r;
r=n%2;
if(n>=2)
to_binary(n/2);
putchar('0'+r);
return ;
}
使用递归既有优点也有缺点。其优点在于为某些编程问题提供了最简单的解决方法,而缺点是一些递归算法会很快耗尽计算机的内存资源。由于每次递归调用都拥有自己的变量集合,所以就需要占用较多的内存。同时,使用递归的程序难于阅读和维护。如下面的例子中采用双重递归(函数对本身进行两次调用)讨论了斐波那契数列,当n为1或2时返回1,对于其他数值返回Fibonacci(n-1)+Fibonacci(n-2),这会导致一个弱点。
long Fibonacci(int n)
{
if(n>2)
return Fibonacci(n-1)+Fibonacci(n-2);
else
return 1;
}
为了具体说明这个弱点,先假设调用函数Fibonacci(40)。第1级递归会创建变量n。接着它两次调用FIbonacci(),在第二级递归中又会创建两个变量n。上述两次调用中的每一次调用又进行了两次调用,因而在第3级调用中需要4个变量n,这时变量总数为7.因为每级调用需要的变量数是上一级变量数的两倍,所以变量的个数是以指数规律增长的!在这种情况下,指数增长的变量数会占用大量内存,这就可能导致程序瘫痪。
一个程序中的每个C函数和其他函数之间是平等关系。每一个函数都可以调用其它任何函数或被其他任何函数调用。main函数的特殊之处在于当几个函数放在一起时,计算机将从main()中的第一个语句开始执行。
多文件源代码的编译:windows和macintosh系统下的编译器是面向工程的。工程(project)描述了一个特定的程序所使用的资源。这些资源中包括源代码文件。使用这种编译器运行单文件程序时,必须创建工程。而对于多文件程序,需要使用相应的菜单命令将源代码文件加入到一个工程之中。
头文件的使用:如果程序中的函数分别放在不同的文件中,那么就必须定义常量的#define指令对每个文件都可用。而直接在每个文件中键入该指令既耗时又容易出错,比较好的解决方法时把所有#define指令和函数原型放在一个头文件中,然后在每个源代码文件中使用#include语句引用该头文件。
指针介绍:
return语句只能把一个数值传递给调用函数。若想传递数值,需要用到指针。指针是一个其数值为地址的变量(或更一般地说是一个数据对象)。
地址运算符:& 后跟一个变量名时,&给出该变量的地址。例如:&nurse表示变量nurse的地址。
间接运算符:* 后跟一个指针名或地址时,*给出存储在被指向地址中的数值。例如:
nurse=22;
ptr=&nurse; //指向nurse的指针
val= *ptr; //将ptr指向的值赋给vals
指针声明:声明一个指针不仅要给出变量名,还需要说明指针所指向变量的类型。原因是不同的变量类型占用的存储空间大小不同,而有些指针操作需要知道变量类型所占用的存储空间。同时,程序也需要了解地址中存储的是何种数据。例如,long和float两种类型的数值可能使用相同大小的存储空间,但是它们的数据存储方式完全不同。
*****************************************************************************************
下面几章都是C语言精华的内容啦,指针,数组,文件,位操作等等,终于快赶上以前看的进度了……这几天整理这个真是有点无聊……好在比较基本的内容差不多都过去了,后面开始要打起精神了!