【问题标题】:Why is a function declared near the top of a source file?为什么在源文件顶部附近声明函数?
【发布时间】:2012-01-11 21:49:49
【问题描述】:

我正在通过 K&R 学习编程。到目前为止进展顺利,但我不清楚第 1.8 节(函数)中的一行代码的作用。

在 1.8 节中,作者向您展示了如何创建一个函数来将一个整数乘以另一个整数的幂。

我已经粘贴了下面的代码,就像书中写的那样。一切都很好。但我不知道他们为什么将这一行放在顶部:

int power(int m, int n);

这本书没有提到它,只是说程序会将整数 m 提高到 n 次方。如果我从代码中删除该行,程序仍会按应有的方式输出。

如果我理解正确的话,那一行

int power(int base, int n)

创建函数,下面的大括号定义函数。然后main下的大括号调用函数输出图表。

所以这一切似乎都说得通。但我看不到最上面的一行是做什么的。

这可能是无关紧要的,但我似乎更有可能遗漏了什么。谁能告诉我为什么那条线在那里?

#include <stdio.h>

int power(int m, int n);

/* test power function */
main()
{
int i;

    for (i = 0; i < 10; ++i)
        printf("%d %d %d\n", i, power(2,i), power(-3, i));
    return 0;
}

/* power: raise base to n-th power; n >= 0 */

int power(int base, int n)
{
    int i, p;

    p = 1;
    for (i = 1; i <= n; ++i)
        p = p * base;
    return p;
}

【问题讨论】:

    标签: c kernighan-and-ritchie


    【解决方案1】:

    第一行是函数的声明。底部的代码块是函数的定义

    从 1999 版本的 ISO C 标准开始,在没有可见声明的情况下调用函数是非法的(违反约束);声明必须在调用之前。

    对于像这样的简单程序,您可以在 main() 的定义之前编写 power() 的完整定义(因为定义也提供声明),但对于更复杂的情况(例如递归调用)您通常需要提供单独的声明。

    对于较大的程序,通常将所有函数声明收集在头文件(例如foo.h)中,并将相应的定义收集在源文件中(例如foo.c) )。 #include "foo.h" 指令用于使声明在其他文件中可见。你会在本书后面看到这种东西。

    (在 1990 年及更早版本的 C 中,这是 K&R2 所涵盖的内容,在某些情况下您可以在没有可见声明的情况下调用函数——但无论如何提供显式声明仍然是一个非常好的主意。)

    顺便说一句,主程序的声明应该是int main(void) 而不仅仅是main()

    术语:“原型”是指定参数类型的函数声明。

    int power(int base, int n);    /* a declaration that's also a prototype */
    int power(int, int);           /* likewise */
    int power();                   /* a declaration but not a prototype */
    

    (参数名称在定义中是必需的,但在独立声明中是可选的。)

    作为一种特殊情况,没有参数的函数原型使用(void),因为空括号已经意味着非原型声明。所以int main(void) 是原型,但int main() 不是。

    非原型声明是“过时的”,这意味着它们理论上可以从未来的语言标准中删除。但它们自 1989 年以来就已经过时,即使在 2011 年新的 ISO C 标准中,委员会也认为不适合删除它们。

    【讨论】:

    • 注意:关闭返回类型 main 可能与关闭声明适用于 OP 的原因相同,在过去,如果没有类型,C 有“隐式 int”规则指定时,它被隐式假定为int。所以 power 的隐式类型匹配它的真实类型,因此它可以工作(由于向后兼容性,即使不是大多数编译器,它仍然可以工作)。
    • @DanielFischer:在 C90 及更早版本中确实如此(我故意掩盖了这一点); “隐式 int”规则在 C99 中被删除。利用它仍然不是一个好主意。
    • 是的,这就是我说“过去”的原因。
    • 这是有道理的。谢谢。所以程序仍然运行,因为编译器找到了定义,把它当作声明,一切都解决了,因为 power() 被用作 int?但是,这只是因为我的编译器向后兼容?另外,感谢关​​于 main(void) 的提示。我正在关注他们在书中写的内容;也许该约定在发布时并不存在。
    • 问题中出现的程序运行是因为您有一个明确的power() 声明——并且它与稍后出现的定义兼容。但是,如果您删除声明,只要编译器不严格执行 1999 ISO C 标准强加的新规则,它仍然有效。 (顺便说一句,有一个更新的 2011 ISO C 标准,但还没有实现。)
    【解决方案2】:
    int power(int m, int n);
    

    power 函数原型形式的声明。函数声明告知编译器函数的参数个数、函数参数的类型和函数返回值的类型。

    在 C 中,您不能在声明之前使用函数标识符。

    【讨论】:

    • “声明它是因为在声明之前不能在 C 中调用函数” - 不正确。调用此类函数有一些特殊性,但它是合法的(这就是为什么 OP 的示例在没有原型的情况下也可以工作)。
    • @jpalecek 你能扩展一下吗?从 C99 开始,不再允许隐式函数声明,函数的定义是包含函数体的函数声明。
    • @jpalecek “OP 的示例在没有原型的情况下也可以工作”。好吧,如果没有函数原型,OP 的程序将不再有效。
    • 是的,但是知道 K&R,这是允许的。我猜 OP 没有使用 c99 编译器。
    • 他写道:“如果我从代码中删除该行,程序仍然可以正常输出。”
    【解决方案3】:

    这是一个前向声明,它使函数接口公开,因为函数在main()下面实际实现之前被使用。

    头文件,您 #include 提供了类似的公开可调用 API 的功能——但代码通常在库中提供,而不是通过与此单个文件中相同的编译单元K&R Intro 章节的示例。

    【讨论】:

      【解决方案4】:

      如果你没有在顶部包含那一行,当程序到达power(2,i) 时,主电源还没有被声明。程序是从上往下读的,把声明放在最上面,编译器就知道“有一个定义来了”。

      【讨论】:

        【解决方案5】:

        那一行只是函数原型。这是一个前向声明,它允许代码能够使用具有该签名的某些函数,该签名将在所有内容链接在一起时存在。没有它,main() 函数将尝试使用 power() 函数,但编译器还没有意识到它,因为它实际上并没有在源文件中稍后定义。

        【讨论】:

          【解决方案6】:

          您所指的顶部那一行是一个函数原型。它唯一的作用是让编译器可以检查您的工作,即通过传递正确的类型和数量的参数来确保您正确使用该函数。这就是它的全部目的。这就是为什么您可以删除它并且代码仍然可以编译的原因 - 您通过删除它所做的只是删除编译器的引用,因此它无法检查您的工作。如果您确实删除了它,那么您可能会传递错误类型的参数并导致难以发现的运行时错误或程序崩溃。但是将其保留允许编译器在编译时标记此类错误,从而为您节省一些麻烦。拯救某人一些悲伤是一件好事。

          后来,根据 C99 标准,他们决定强制提供函数原型(或者先定义函数)才能编译代码,从而迫使您让编译器检查您的工作。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2015-06-27
            • 2015-08-04
            • 2012-12-31
            • 1970-01-01
            • 2020-03-03
            • 2019-02-08
            • 2012-02-18
            • 2011-07-03
            相关资源
            最近更新 更多