第五章 函数与指针
我不愿有一个塞满东西的头脑,而宁愿有一个思想开阔的头脑----蒙田
对于许多问题的了解,很多人是在经意或不经意的情况下获得的。虽然人类具有智慧,但并不能单独完成所有的事情,必须要依赖于别人。你可能会请一个修理工来修理你的汽车,雇一个园丁来修剪你的草坪,或者需要一个仓库来存储每月的货物。对计算机程序(简单的程序除外)也会有完全类似地情形。它不能处理所有的工作,它需要一些其它独立的实体来代替它完成工作。在C语言中,这种实体称为函数。这一章中将研究这些函数。你会看到函数的许多不同特点,从最简单的函数开始,着力说明C语言函数强有力的功能。
5.1 什么是函数?
函数是一个独立的语句块,该语句块完成一个单一的任务。使用函数就好比你去雇一个人来为你完成某项特定的工作。与这个人打交道有时非常简单,有时又非常复杂。
看一个最简单的函数调用:
#include <stdio.h>
void message();
int main(void)
{
message();
printf("\nCry,and you stop the monotony!");
system("pause");
}
void message()
{
printf("\nSmile,and the world smiles with you...");
}
5.2 关于函数
●任何函数都可以被其他函数调用,甚至main()函数也可以被其他函数调用。
●函数可以被调用任意多次。例如:
#include <stdio.h>
void message();
int main(void)
{
message();
message();
system("pause");
}
void message()
{
printf("\nJewel Thief");
}
●程序中函数定义的顺序和函数被调用的顺序不需要完全相同。但是,切记,还是建议按照函数调用的顺序来进行函数的定义,这会程序易于理解。
●函数可以被另一个函数调用,但函数不可以在另一个函数中定义。例如:
int main(void)
{
printf("\nI am in main");
void argentina()
{
printf("\nI am in argentina");
}
}这是错误的。
5.3 为什么要使用函数?
●写成函数可以避免反复书写相同的代码。
●通过使用函数,编写程序及跟踪程序所进行的操作会变得很容易。如果程序的操作可以分成一些独立的活动,而每一个活动都放在不同的函数中,于是可以独立地对每一个函数进行编写和检查。将代码分成函数模块,会使得程序更加容易设计和更易于理解。
5.4 在函数间传递值
/*通过键盘输入三个数,然后求这三个数的和*/
#include <stdio.h>
int calsum(int x,int y,int z);
int main(void)
{
int a,b,c,sum;
printf("\n输入a,b,c的值");
scanf("%d%d%d",&a,&b,&c);
sum=calsum(a,b,c);
printf("\nsum=%d",sum);
system("pause");
}
int calsum(int x,int y,int z)
{
int d;
d=x+y+z;
return d;
}
下面是程序中值得注意的几点:
●变量a,b,c称为实参,变量x,y,z称为形参。传给被调函数的参数可以是任意数量,但形参和实参的类型、顺序及数量必须一致。
在被调函数中,也可以使用相同的变量名a,b,c来代替x,y,z。但编译器仍会将它们按不同的变量来处理,因为它们位于不同的函数中。
●在函数原型的声明中也可以不需要使用变量名。因此函数的原型也可以这样表示:
int calsum(int,int,int);
●return语句有两个作用:
执行return语句时,会立即将控制返回到主调函数。
‚将return后面圆括号中的值返回给主调函数。
函数中return语句的出现次数是没有限制的。同时,return语句也并不总是一定要出现在被调函数的最后。如:
int fun()
{
int n;
printf("\nEnter any number");
scanf("%d",&n);
if(n>=10&&n<=90)
return(n);
else
return(n+32);
}
●函数每次仅能返回一个值,因此下面的语句是非法的:
return(a,b);
return(x,12);
有一种方法可以不受这个限制,这个方法会在后面学习指针的时候再讨论。
●如果形参的值在被调函数中发生了改变,主调函数中实参的值不会发生改变。
5.5 函数调用约定
被调函数的形参和函数内定义的局部变量时内存中的堆栈创建的。当控制从函数返回时,堆栈就会被清空。有不同的函数调用约定。在所有的函数调用约定中,最常用的是标准函数调用约定。在这种调用约定中,参数按从右至左的顺序进行传递,堆栈由被调函数进行清空。
假设有如下形式的调用函数:
fun(a,b,c,d);
这种形式的函数调用,其参数的传递时从左至右还是从右至左无关紧要。但是,在有些函数调用中,参数传递的顺序是要重点考虑的。例如:
I nt a=1;
printf("%d%d%d",a,++a,a++);
令人惊讶的是,它输出的是3 3 1。这是因为C语言的调用约定是从右至左。
5.6 传值调用与传引用调用
调用函数,传递的是变量的值,称为传值调用。
若传递地址,那就是传引用调用。
5.7 指针简介
对初学者而言,C语言的哪一个特征最难理解呢?答案很容易:指针。其他的语言也有指针,但很少使用,没有像C语言这样对指针使用频繁。为什么呢?这是因为C语言可以灵活地使用指针,正是这一点才使C语言成为一门优秀的语言。
我想声明一个指针,并为它分配一些空间,但却不行,这样的代码有声明问题?
char *p;
*p=malloc(10);
因为这里声明的指针是p而不是*p。
5.8 /*通过传引用调用,让函数一次返回多个值*/
#include <stdio.h>
void areaperi(int,float *,float *);
int main(void)
{
int radius;
float area,perimeter;
printf("\n输入半径:");
scanf("%d",&radius);
areaperi(radius,&area,&perimeter);
printf("Area=%f",area);
printf("\nPerimeter=%f",perimeter);
system("pause");
}
void areaperi(int r,float *a,float *p)
{
*a=3.14*r*r;
*p=2*3.14*r;
}
5.9 结论
●如果想让实参的值在被调函数中不发生变化,可以通过传值的方式传递实参。
●如果想让实参的值在被调函数中发生变化,就应通过传引用的方式传递实参。
●如果要求函数一次返回多个值,那么可以间接使用传引用调用来返回这些值。
5.10 递归
在C语言中,函数也可以调用自身。如果在函数体内的语句中,出现了函数调用自身的情况,则这个函数就称为递归。
/*用递归函数计算阶乘*/
#include <stdio.h>
int rec(int);
int main(void)
{
int a,fact;
printf("\nEnter any number");
scanf("%d",&a);
fact=rec(a);
printf("输入这个数的阶乘为%d",fact);
system("pause");
}
int rec(int x)
{
int f;
if(x==1)
return(1);
else
f=x*rec(x-1);
return(f);
}
初看起来,可能会觉得递归有点奇怪和复杂,但它通常是编写算法最直接的方式,而且一旦熟悉了递归,就会觉得它是最清晰的一种操作方式。
例题:
1,为变量n读取一个整型值,读取n个实数,找出其中的最大值和最小值
#include <stdio.h>
float maximum(float x,float y);
float minmum(float x,float y);
int main()
{
int i,n;
float max,min,x;
printf("输入n的值:");
scanf("%d",&n);
printf("输入%d个值:",n);
scanf("%f",&x);
max=min=x;
for(i=2;i<=n;i++)
{
scanf("%f",&x);
max=maximum(max,x);
min=minmum(min,x);
}
printf("最大值为%f\n",max);
printf("最小值为%f\n",min);
system("pause");
}
float maximum(float x,float y)
{
if(x>y)
return x;
else
return y;
}
float minmum(float x,float y)
{
if(x<y)
return x;
else
return y;
}
扩展阅读:关于函数
每个函数都应该设计得尽可能简单,简单的函数才容易维护。设计函数应遵循以下原则:
●实现一个函数只是为了做好一件事,不要把函数设计成用途广泛、面面俱到的,这样的函数肯定会超长,而且往往不可重用,维护困难。
●函数内部的缩进层次不宜过多,一般以少于4层为宜。若果缩进层次太多就说明设计得太复杂了,应考虑分割成更小的函数来调用。
●函数不要写得太长,建议在24行的标准终端上不超过两屏,太长会造成阅读困难,如果一个函数超过两屏就应该考虑分割函数了。但是如果一个函数在概念上是简单的,只是长度很长,这倒没关系。例如函数由一个大的switch组成,其中有非常多的case,这是可以的,因为各case分支互不影响,整个函数的复杂度只等于其中一个case的复杂度。
●执行函数就是执行一个动作,函数名通常应包含动词,例如: get_current、radix_tree_insert。
●比较重要的函数定义上方必须加注释,说明此函数的功能、参数、返回值、错误码等。
●另一种度量函数复杂度的办法是看有多少个局部变量,5到10个局部变量已经很多了,再多就很难维护了,应该考虑分割成多个函数。
●有的时候我们把函数叫做接口(Interface),调用函数就是使用这个接口,使用接口的前提是必须和接口保持一致。
●6 用if else判断奇偶,可以把它分装成一个函数:
void print_parity(int x)
{
if (x % 2 == 0)
printf("x is even.\n");
else
printf("x is odd.\n");
}