与您习惯的其他语言非常相似,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
定义原型。