4.2函数的概念 (5.2函数的表示方法)

5.2 函数的定义

上一节介绍了函数的概念。函数只是用特殊的方法包装的一段程序,这段程序完成特定的功能并能被重复执行。本节重点讲解函数的定义和使用的方法。

C语言有两类函数,一类称为库函数或标准函数,是相关语言预先编写好的函数,程序员可以不用了解其实现过 程,直接使用函数即可。如格式化输出函数printf与输入函数scanf、字符输入输出函数getchar与putchar、数学函数求平方根sqrt和求实数绝对值fabs等;另一类称为自定义函数,这是由程序员根据需要自己编写的函数。自定义函数由 六部分 构成,格式如下:

函数类型 函数名(形式参数列表)
{
函数体
return [<表达式>];
} 

函数名 用来代表函数,当要执行函数的程序段时,通过函数名调用即可。函数名称与变量名命名一样,即首字符必须是字母或下划线,其它字符可以字母、数字和下划线。函数名不能是关键字,且长度超过一定数量会被自动截断。给函数起名字时,函数名字与函数的功能最好有一定关联,达到见名知义的目的。当函数名由多个单词构成时,除了第一个单词外,其它单词的首字母建议为大写,这能增加程序的可读性。如:函数名字 getAverage,isPrime可分别表示求平均值、判断素数等。

函数类型 可以是前面学习的int、char、float、double等简单类型,也可以是未来要学到指针或结构体类型等。表示函数执行结束后要返回给外界的数据类型。函数类型可以省略不写,此时函数类型为int型。函数类型也可以是void型,称“空类型”或“无类型”,表示函数没有任何返回值,这样的函数称为“过程型”函数,只能用作语句调用。

函数的左花括号“{”和右花括号“}”,不能省略,分别表示函数的开始和结束。

形式参数列表 用来定义函数的形式参数或形参,是函数与外部交换数据的接口。形式参数列表中的参数可以一个也没有,直接为空或用void表示,表明函数使用时不需要接收外界“喂入”的数据。参数列表由逗号“,”把多个形式参数分隔开,一个参数如下定义:

数据类型 形式参数名

形式参数与变量一样,在函数调用时才自动生成,此时形式参数就是变量。形式参数列表为:

数据类型 形式参数名1,数据类型 形式参数名2,...

与变量定义不同的是,形式参数必须在列表中分别定义,即使形式参数的数据类型相同,也不能合并定义,如:

数据类型 形式参数名1,形式参数名2,...

在括号内是一种错误的定义方法。 如要定义个整型形式参数,只能这样定义:

int x,int y

而不能这样定义

int x,y

函数体 就是函数的程序代码部分,包括变量定义和程序语句两个部分,一般要把所需的所有变量定义完后,才开始写第一条可执行主语句。

return 语句用来表示函数结束,并能把其后的表达式值传回函数外部。该语句不一定非要用在函数体之后,可放在函数体的任何地方,以提前结束函数执行。对于void类型的“过程型”函数,return 语句之后不需要表达式。其它类型的函数return语句后要有表达式,且表达式类型要与函数类型一致。return 语句可以省略不写,此时系统会自动在函数的尾部添加一个return语句,并能返回一个未知的数值。

最后要提醒一下,函数定义的第一行,称为 函数头 。其后不能用分号“;”结束。

函数定义时要注意的细节很多,每一个都足以影响函数定义是否正确。只有把函数的六个部分完全搞清楚才能正确设计和阅读函数。先来分析一个简单的函数定义:

int add(int x,int y)
{
int r;
r=x+y;
return r;
}

上面定义一个简单函数。函数名为add,功能是实现加法运算;函数类型为int,表明函数执行完后要返回一个整型数据;函数形式参数部分定义了两个变量x和y,表明函数可以 “吃 ”进两个数,一个要给x,另一个给y;函数体是把x与y相加后给r赋值;最后用return结束函数并把计算结果r传回到函数外部。由于函数类型是int,所以可以省略函数类型;函数形参不能写成int x,y;return语句可以省略,当省略return语句时,函数不能返回正确结果。

我们可以把前面学习的程序轻松变成函数,只要函数名main改为另外的名字就可以了。

例1: 把一个整数逆序输出的程序改写为函数

#include <stdio.h>
int main(void)
{
int n,r;
printf("Enter n:");
scanf("%d",&n);
if(n==0)printf("0");
   while( n>0 )
{
n = n/10; //拆出除了个位以外的部分
          r=n%10; //取出个位
          printf("%d-",r); //显示个位
}
return 0;
}

最简单的方法是把函数名main改成另外名字,如revInt。这样就定义了把一个整数逆序的函数。但是改名后没有了名为main的函数,所以要再写一个橷的名为main的函数,通过调用revInt函数,实现与前面程序相同的功能。修改的程序如下:

#include <stdio.h>
int revInt( )
{
int n,r;
printf("Enter n:");
scanf("%d",&n);
if(n==0)printf("0");
  while( n>0 )
{
n = n/10; //拆出第一部分
          r=n%10; //拆出第二部分(n的个位)
         printf("%d-",r); //显示第二部分
   }
return 0;
}
int main( )
{
revInt( );
return 0;
}

可以看出,函数revInt中的程序代码与前面的程序一模一样。构造的新函数revInt存在通用性问题,因为函数内包括了用scanf函数,每次执行都要从键盘上输入一个整数,然而,很多时侯我们可能只用revInt完成对指定的整数逆序而不需要键盘输入。让自定义函数具有通用性是定义函数的目的这一,因此,函数的输入要尽量由形式参数给出,而输出由return语句完成。也就是说,在设计函数时,要尽可能减少键盘输入和屏幕输出。由于本例输出很特殊,即输出和处理是同时进行的,因此输出可以放在函数中。为了让return语句返回值有意义的值,我们可以再次修改程序,让return返回整数的位数。修改后的程序如下:

#include <stdio.h>
int revInt(int n)
{
int n,r;
int cnt=0; //用来统计整数的位数
if(n==0){
       printf("0");return 1;
   }
  while( n>0 )
{
n = n/10; //拆出第一部分
          r=n%10; //拆出第二部分(n的个位)
         printf("%d-",r); //显示第二部分
         cnt=cnt+1; //位数加1
}
return cnt;
}
int main( )
{
int n;
printf("Enter n:",&n);
revInt( n );
return 0;
}

C语言所有函数的定义在地位上都是平等的,即不允许一个函数内部定义另一个函数,即不能嵌套定义函数,如以下程序想在main函数中定义add函数是不合法的:

int main( )
{
int add(int x,int y){   //在函数main中定义函数add不合法
return x+y;
      }
   int x1,y1;
   scanf("%d%d",&x1,&y1);
   printf("result=%d\n",add(x1,y1));
}

接下来我们来看另外一种设计函数的方法。

例2: 把累加和程序改写为函数实现

已知累加和的程序如下,即输入一个整数n,求出n的累加和并输出。

#include<stdio.h>
int main( )
{
int sum,i;
  scanf("%d",&n);
  sum=0;i=1;
while(i<=n){
       sum=sum+i;i++;
  }
  printf("sum=%d',sum);
  return 0;
}

用前面提到的方法可以设计累加和的函数。第一步是把main改为函数名;第二步是删除程序中的输入scanf和输出printf函数;第三步是用形式参数代替输入,用return语句代替输出。

我们尝试把已有程序变为函数的第二种方法。即;通过在main中删除输入和输出代码,留下关键代码作为新函数的函数体,再补充完整变量定义即可。忽略输入、输出和变量定义后,main中累加和的关键代码为:

sum=0;i=1;
while(i<=n){
       sum=sum+i;i++;
}

通过观察关键代码,发现有三个变量sum、i、n,sum和i都被赋了初值,且在循环体中参与了运算。而变量n的初值未知,且用来作为循环的终值。可以推断,只有变量n要从外部输入,所以应把n定义为形式参数。而sum保存了计算的结果,因此要作为函数的返回值。函数代码如下:

int getSum(int n)
{ 
   int sum,i;
   sum=0;i=1;
while(i<=n){
          sum=sum+i;i++;
   }
   return sum;
}

关键代码的变量sum,i没有定义,要补充定义完整。有了函数后,原来的main中的关键代码部分可以用一行语句代替,完整程序为:

#include<stdio.h>
int getSum(int n){....}
int main( )
{
int sum,i;
  scanf("%d",&n);
  sum=getSum(n);
  printf("sum=%d',sum);
  return 0;
}

例3: 写一个函数,判断一个整数是否是素数,如果是素数则返回1,否则返回0。

本例我们用IPO模型来分析和设计函数。IPO分析模型分为三个步骤:

(1) 确定输入:依题意函数需要输入一个整数,因此可以定义一个整型变量n作为形式参数;

(2) 确定输出:函数的计算结果是0或1,因此可以定义一个变量flag保存结果,并通过return返回;

(3) 确定处理:即算法实现部分,根据前面学习的内容:判断一个整数n是否为素数,可先假设flag为1表示这个数是素数,接下来用n依次除以2到sqrt(n)中的每个整数i,只要有一个整数可以除尽n,则令flag为0,表明n不是素数,不再继续判断退出循环。对应代码为:

flag=1;
for(i=2;i<=sqrt(n);i++)
if( n%i == 0){flag=0;break;}

根据IPO分析的第(1)(2)两步可以写出函数框架:

int isPrime(int n)
{
函数体
return flag;
}

把IPO分析的第(3)步代码代替“函数体”,并完善变量定义,完整函数为:

int isPrime(int n)
{
int flag,i;
flag=1;
for(i=2;i<=sqrt(n);i++)
  if( n%i == 0){flag=0;break;} 
      return flag;
}

用三个实例分别展示了三种设计函数的方法: 一是 可以通过修改main函数名实现; 二是 可以把已经写好的关键代码抽取出来作为函数体; 三是 可以通过IPO分析从零开始设计函数。其中第二种、第三种方法会经常用到。但无论用什么方法都要注意:(1)函数是可重复使用的代码块;(2)函数相对独立且完成特定的功能;(3)函数与外界的联系越少越好,最好全部通过形式参数和返回值进行联系;(4)函数代码尽量不要有键盘输入和屏幕输出,除非专门输入或输出数据的函数。

本节介绍了函数的定义和设计方法,重点对函数形式参和返回值进行了讲解,通过实例阐述了函数的三种设计方法,最后对函数的设计给出四点指导性建议。本节就到这里,下次再见!