函数声明与签名

函数声明

函数在被调用之前其名称必须是可见的,一种可行的方法就是把函数定义在要调用函数的前面。一个复杂的程序有成百上千个函数,小心翼翼的去安排这种排列关系,几乎是一个不可能的过程。一些情况下,可能还存在函数互相调用的情况,如A调用B,而B又调用A这种情况。

void funa(int a)
{
    if (a < 2)
        funb(); // 在调用前需要看到funb的名称
}

void funb(int a)
{
    if (b > 3)
        funa(); // 在调用前需要看到funa的名称
}

此时,将哪个函数定义在对方的上面都是不合适的。显然,我们需要有效的方式来组织程序,这可以通过函数声明来解决。函数声明用于向程序引入可见的的函数名,这解决了程序在编译阶段必须看到名字的问题。函数声明既不是定义,也不是调用,因而可以不用考虑声明的先后顺序,即使他们存在相互调用关系。函数声明也称为函数原型,其语法如下:

返回类型 函数名(类型1 参数1名称, 类型2 参数2名称, 更多...);

int get_age();
double area(double radius);
char next_char(int handle);

一个函数可以没有返回值,即返回类型是void,此时void不可省略;函数也可以没有参数列表,即空参列表,同样可用void表达,此时void关键字可以省略。

void foo1(void); // 函数foo1没有返回值没有参数
void foo2();     // 本质上同foo1一样,没有可传递的参数时void关键字可以省略。

函数声明相当于一个规格说明书,指出了函数调用规范。函数声明或定义中的参数,称为形式参数,即形式上有这么个参数;形参的名称可以被省略,但并不建议这么做。

void foo3(int a);              // foo3没有返回值,接受一个int型参数。
double foo4(char c, double d); // 函数foo4接受2个参数,并需要返回一个double值。
int maxnum(int, int);          // 省略参数名,但不建议这么做。

函数的名称及其参数类型列表组合在一起(不包括返回类型),相当于给函数定义了一个唯一标识,这称为函数签名。同样的函数签名在程序中只允许实现(定义)一次,但可以多次声明。

ZYF

// 函数原型                --> 编译器可能生成的签名
void foo();               --> foo_void        // 这里用下划线分隔名称与参数
int max(int a, int b);    --> max_int_int
int yes(char, int b);     --> yes_char_int
void foo(void)            --> foo_void        // 再次声明函数foo

记得:函数声明引入了对应的名称,现在这个名称已经可以使用了。

#include <stdio.h>

// 函数声明
void foo();
int maxnum(int a, int b);

// 函数定义
double bar()
{
    return 3.14;
}

int main()
{
    // 调用函数foo
    foo();

    // 调用max函数,返回值被丢弃。
    maxnum(6, 9);

    // 调用max函数,返回值作为printf函数的参数被输出
    printf("max: %d\n", maxnum(3, 5));

    // 调用bar函数
    bar();

    return 0;
}

在程序中,我们并没有对函数 foo、maxnum 进行定义,但却在main函数中调用了它们。此时执行编译操作时,编译器根据函数声明对调用的完整性进行检查,比如参数个数是否正确,参数类型是否匹配等(你可以在maxnum调用中少给一个参数测试),一切无误后,编译器给出生成成功信息。

glimix.com

glimix.com

由于没有对应的函数实现,生成代码时,链接器会提示找不到对应的函数实现。 在输出窗口中,我们看到,所有未能链接的函数名前面加入了一个下划线(_)前缀,这是编译器背后按一定的命名规则实现的。

glimix.com

glimix.com

函数定义

为了使函数能被成功连接,就需要实现函数的定义,函数定义是由函数声明去掉分号,加一组用大括号包含的语句组构成,这称为函数体。

void foo2();    // 函数原型
void foo2()     // 函数实现
{
    printf( "foo" );
}

ZYF

函数原型与函数定义的签名必须一致,但函数声明与函数定义时的参数名称可以不一样。只要签名匹配就行。当函数返回类型非void时,必须使用关键字return返回一个返回值,return语句将导致整个函数退出,return之后的语句将不再执行。

// 函数原型与函数实现的签名一致,max_int_int
// 函数参数名称不一样,这是可行的,但建议使用同等的名称。
int maxnum(int a, int b);

int maxnum(int c, int d)
{
    if (c > d) return c;
    else return d;
}

void foo()
{
    printf( "before return\n" );
    return;
    printf( "after return\n" ); // 不会被执行
}

调用函数

现在,我们就可以使用函数了,这被称为调用函数。调用函数时写下函数名称,传递适当的参数即可,这些参数称之为函数的实参,即真正的,实际的数据。

int main()
{
    // 调用函数foo
    foo();

    // 调用maxnum函数,返回值被丢弃。
    maxnum(6, 9);

    // 调用maxnum函数,返回值作为printf函数的参数被输出
    printf("maxnum: %d\n", maxnum(3, 5));

    return 0;
}

glimix.com

练习

1 自己编写函数声明与定义,并测试。

2 定义一个接收三个参数的函数,但函数体中始终不使用第二个参数,看编译器表现,并做改进。

陕ICP备2025078817号-1 陕公网安备61011202001108号