C Primer Plus(6) 中文版 第9章 函数 9.2 ANSI C 函数原型

发布于:2023-01-11 ⋅ 阅读:(428) ⋅ 点赞:(0)

9.2 ANSI C 函数原型
在ANSI C标准之前,声明函数的方案有缺陷,因为只需要声明函数的类型,不用声明任何参数。
因此,如果使用的参数不对或类型不匹配,编译器根本不会察觉出来。
9.2.1 问题所在 
/* misuse.c -- uses a function incorrectly */
#include <stdio.h>
int imax();      /* old-style declaration */

int main(void)
{
    printf("The maximum of %d and %d is %d.\n",
           3, 5, imax(3));
    printf("The maximum of %d and %d is %d.\n",
           3, 5, imax(3.0, 5.0));
    return 0;
}

int imax(n, m)
int n, m;
{
    return (n > m ? n : m);

/* 输出:

*/ 

运行错误的原因在于没有使用函数原型。
由于不同系统的内部机制不同,所以出现问题的具体情况也不同。
下面介绍的是使用PC和VAX的情况。主调函数把它的参数存储在被称为栈(stack)的临时存储区,被调函数从栈中读取这些参数,这两个过程并未相互协调。主调函数根据函数调用中的实际参数决定传递的类型,而被调函数根据它的形式参数读取值。因此,函数调用imax(3)把一个整数放在栈中。当imax()函数开始执行时,它从栈中读取两个整数。而实际上只存放了一个待读取的整数,所以读取的第2个值是当时恰好在栈中的其他值。
第2次调用imax()函数时,它传递的是float类型的值。这次把两个double类型的值放在栈中(回忆一下,当float类型被作为参数传递时会被升级为double类型)。在我们的系统中,两个double类型的值就是两个64位的值,所以128位的数据放在栈中。当imax()从栈中读取两个int类型的值时,它从栈中读取前64位。在我们的系统中,每个int类型的变量占用32位。这些数据对应两个整数。因此,只是会占用前64位。 
9.2.2 ANSI的解决方案
针对参数不匹配的问题,ANSI C标准要求在函数声明时还要声明变量的类型,即使用函数原型(function prototype)来声明函数的返回类型、参数的数量和每个参数的类型。可以使用两种函数原型来声明:
int imin( int, int );
int imin( int n, int m );
第1种形式使用以逗号分隔的类型列表,第2种方式在类型后面添加了变量名。注意,这里的变量名是假名,不必与函数定义的形式参数名一致。
有了这些信息,编译器可以检查函数调用是否与函数原型匹配。参数的数量是否正确?参数的类型是否匹配?以imax()为例,如果两个参数都是数字,但是类型不匹配,编译器会把实际参数的类型转换成形式参数的类型。例如,imax( 3.0, 5.0 )会被转换成imax( 3, 5 )。
函数原型替换函数声明
/* proto.c -- uses a function prototype */
#include <stdio.h>
int imax(int, int);        /* prototype */
int main(void)
{
    printf("The maximum of %d and %d is %d.\n",
           3, 5, imax(3));
    printf("The maximum of %d and %d is %d.\n",
           3, 5, imax(3.0, 5.0));
    return 0;
}

int imax(int n, int m)
{
    return (n > m ? n : m);

/* 输出:

*/

编译器给出调用的imax()函数参数太少的错误信息。
为了探索类型不匹配的问题,我们用imax( 3, 5 )替换imax( 3 ),然后再次编译该程序。这次编译器没有给出任何错误信息。输出如下:

如上文所述,第2次调用中的3.0和5.0被转换成了3和5,以便函数能正确地处理输入。
虽然没有错误信息,编译器还是给出了警告:double转换成int可能会导致丢失数据。例如,下面的函数调用:
imax( 3.9, 5.4 )
相当于: 
imax( 3, 5 )
错误和警告的区别是:错误导致无法编译,而警告仍然允许编译。一些编译器在进行类似的类型转换时不会通知用户,因为C标准对此未做要求。
9.2.3 无参数和未指定参数
下面的函数原型:
void print_name();
一个不支持ANSI C的编译器会假定用户没有用函数原型来声明函数,他将不会检查参数。为了表明函数确实没有参数,应该在圆括号中使用void关键字:
void print_name( void );
支持ANSI C的编译器解释为print_name()不接受任何参数。然后在调用该函数时,编译器会检查以确保没有使用参数。
一些函数接受(如,printf()和scanf())许多参数。例如对于printf(),第1个参数是字符串,但是其余参数的类型和数量都不固定。对于这种情况,ANSI C允许使用部分原型。例如,对于printf()可以使用下面的原型:
int printf( const char *, ... );
这种原型表明,第1个参数是一个字符串,可能还有其他未指定的参数。
C库通过stdarg.h头文件提供了一个定义该类(形参数量不固定的)函数的标准方法。
9.2.4 函数原型的优点
函数原型是C语言的一个强有力的工具,它让编译器捕获在使用函数时可能出现的许多错误或疏漏。如果编译器没有发现这些问题,就很难觉察出来。是否必须使用函数原型?不一定。你也可以使用旧式的函数声明(即不声明任何形参),但是这样弊大于利。
有一种方法可以省略函数原型却保留函数原型的优点。首先要明白,之所以使用函数原型,是为了让编译器在第1次执行到该函数之前就知道如何使用它。因此,把整个函数定义放在第一次调用该函数之前,也有相同的作用。此时,函数定义相当于函数原型。对于较小的函数,这种用法很普遍。对于较大的函数,这会降低程序的可读性。 
int imax( int a, int b ){
    return a > b ? a : b;

int main( void ){
    int x, z;
    ...
    z = imax( x, 50 );
    ...
}

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到