与您习惯的其他语言非常相似,C 也有函数的概念。函数可以接受各种参数并返回一个值。不过,C 语言的参数和返回值类型是预先声明的。

我们来看一个函数。这是一个接受 int 作为参数并返回一个 int 的函数。

#include <stdio.h>

int plus_one(int n)  // The "definition"
{
    return n + 1;
}
 

plus_one 前面 int 的表示返回类型。

int n 表示该函数接受一个 int 参数,值存储在参数  n 中。参数是一种特殊类型的局部变量,参数将被复制到其中。

我将在这里强调将参数复制到参数中的这一点。如果您知道形参是实参的副本,而不是实参本身,那么 C 中的很多事情就更容易理解。稍后会详细介绍。

继续执行程序 main(),我们可以看到对函数的调用,我们将返回值分配给局部变量 j

int main(void)
{
    int i = 10, j;
    
    j = plus_one(i);  // The "call"

    printf("i + 1 is %d\n", j);
}

请注意我在使用函数之前必须先定义函数。如果没有这样做,编译器在编译时还不知道它。

main() 并且会给出未知的函数调用错误 implicit declaration of function plus_one。有一种更合适的方法可以使用函数原型来完成提前声明函数,稍后会讨论。

这里 main() 也是一个函数,它返回值类型是 int。你可能也会遇到返回类型是 void

void 用于函数的参数时,指示该函数不接受任何参数。void 用于函数的返回类型时表示该函数没有返回值。

#include <stdio.h>

// hello 函数不接受任何参数,也没有返回值

void hello(void)
{
    printf("Hello, world!\n");
}

int main(void)
{
    hello();  // Prints "Hello, world!"
}

按值传递

我之前提到过,当您将参数传递给函数时,会创建该参数的副本并将其存储在相应的参数中。

如果参数是变量,则会创建该变量值的副本并将其存储在参数中。通常,C 语言会对整个参数表达式进行求值并确定其值,并将值复制到参数中。

无论如何,参数中的值属于它自己。它独立于您在进行函数调用时用作参数的任何值或变量。

#include <stdio.h>

void increment(int a)
{
    a++;
}

int main(void)
{
    int i = 10;

    increment(i);

    printf("i == %d\n", i);  // 这将会打印什么
}

我们声明变量 i 值是 10,我们将它传递给函数 increment()。那里会将值会增加 1,所以当我们打印它时,它一定是 11。但它不是 11

这是因为您传递给函数的表达式会被复制到其相应的参数上。该参数是副本,而不是原始参数。

所以 i 在 main 函数是 10。 我们将它传递给 increment 函数。相应的参数在 increment 函数是 a。

此时复制就发生了,就像将 i 的值赋给变量 a 一样,类似于 a = i。 所以此时 a 是 10。在 main 函数中,i 也是 10。

然后我们将 a 递增到 11。但 i 并没有被递增,i 的值仍然是 10。最后退出 increment 函数。它的所有局部变量都被丢弃(再见,a!),我们返回到 main 函数,那里 i 仍然是 10

main 函数,我们使用 printf 函数打印它,得到 10,然后退出 main 函数。

函数原型

在上面的章节我们提到您必须在使用函数之前定义函数,否则编译器不会知道函数的存在,并且抛出错误 implicit declaration of function。

严格来说这并不完全正确。您可以提前通知编译器您将使用指定参数列表的函数。这样,你就可以在调用该函数之前声明函数原型,就可以在任何地方(甚至在不同的文件中)定义该函数。

函数原型非常简单。它只是函数定义第一行的副本,并在末尾添加分号即可。例如,此代码调用稍后定义的函数:

#include <stdio.h>

int foo(void);  // 函数原型

int main(void)
{
    int i;
    
    // 此时我们可以在函数未定义之前调用函数,因为我们已经声明函数的原型

    i = foo();
    
    printf("%d\n", i);  // 3490
}

int foo(void)  // 这是函数的定义
{
    return 3490;
}

如果您在使用函数之前没有声明它,无论是使用原型还是其定义,那么您将执行称为隐式声明的操作。

第一个 C 标准 (C89) 允许这样做,该标准对此有规则,但现在不再允许。并且没有合理的理由在新的代码中使用它。

空参数列表

你应该始终使用 void 指示函数不带参数,您可能会时不时在旧代码中看到函数参数留空的代码,但您不应该在新代码中编写这样的代码。

void 有两个上下文:

  • 省略定义函数的所有参数
  • 省略原型中的所有参数
void foo()  // Should really have a `void` in there
{
    printf("Hello, world!\n");
}

虽然规范阐明此示例中的行为就像指定 void 一样,但 void 类型的存在是有原因的,请使用它。

void foo();
void foo(void);  // Not the same!

但在函数原型的情况下,使用和不使用 void 之间存在显着差异,省略 void 原型向编译器表明没有函数参数的附加信息。它有效地关闭所有类型检查。当你有一个空的参数列表时,请使用 void 定义原型。