旧游无处不堪寻
无寻处,惟有少年心
C Primer Plus(四)

C 的设计思想是,把函数用作构件块来组织程序。前面我们用过了 C 标准库的函数,如 printf()、scanf()、getchar()、putchar() 和 strlen()。本篇我们进一步学习函数。

函数签名


前面说过函数原型指明了函数的返回值类型和函数接受的参数类型。这些信息称为该函数的签名(signature)。
要正确地使用函数,程序在第 1 次使用函数之前必须知道函数的类型。方法之一是,把完整的函数定义放在第 1 次调用函数的前面。然而,这种方法增加了程序的阅读难度。而且,要使用的函数可能在 C 库或其他文件中。因此,通常的做法是提前声明函数,把函数的信息告知编译器。

ANSI C 标准库中,函数被分成多个系列,每一系列都有各自的头文件。这些头文件中除了其他内容,还包含了本系列所有函数的声明。例如,stdio.h 头文件包含了标准 I/O 库函数(如 printf() 和 scanf())的声明。math.h 头文件包含了各种数学函数的声明。

一些函数(如 printf() 和 scanf())接受许多参数。例如对于 printf(),第 1 个参数是字符串,但是其余参数的类型和数量都不固定。对于这种情况,ANSI C 允许使用部分原型。例如:

int printf(const char *, ...);

这种原型表明,第 1 个参数是一个字符串,可能还有其他未指定的参数。C 库通过 stdarg.h 头文件提供了一个定义这类(形参数量不固定的)函数的标准方法。

递归


C 允许函数调用它自己,这种调用过程称为递归(recursion)。可以使用循环的地方通常都可以使用递归。有时用循环解决问题比较好,但有时用递归更好。递归方案更简洁,但效率却没有循环高。
要点:

  • 每级函数调用都有自己的变量
  • 每次函数调用都会返回一次
  • 递归函数中位于递归调用之前的语句,均按被调函数的顺序执行
  • 递归函数中位于递归调用之后的语句,均按被调函数相反的顺序执行
  • 递归函数必须包含能让递归调用停止的语句

尾递归

最简单的递归形式是把递归调用置于函数的末尾,即正好在 return 语句之前。这种形式的递归被称为尾递归(tail recursion),因为递归调用在函数的末尾。尾递归是最简单的递归形式,因为它相当于循环。
既然用递归和循环来计算都没问题,那么到底应该使用哪一个?一般而言,选择循环比较好。首先,每次递归都会创建一组变量,所以递归使用的内存更多,而且每次递归调用都会把创建的一组新变量放在栈中。递归调用的数量受限于内存空间。其次,由于每次函数调用要花费一定的时间,所以递归的执行速度较慢。

多源代码程序编译


Unix/Linux

假设 file1.c 和 file2.c 是两个内含 C 函数的文件,下面的命令将编译两个文件并生成一个名为 a.out 的可执行文件:

cc file1.c file2.c

另外,还生成两个名为 file1.o 和 file2.o 的目标文件。如果后来改动了 file1.c,而 file2.c 不变,可以使用以下命令编译第 1 个文件,并与第 2 个文件的目标代码合并:

cc file1.c file2.o

DOS

绝大多数 DOS 命令行编译器的工作原理和 Unix/Linux 的 cc 命令类似,其中一个区别是,目标文件的扩展名是 .obj,而不是 .o。

头文件


把函数原型放在头文件中,就不用在每次使用函数文件时都写出函数的原型。C 标准库就是这样做的。另外,程序中经常用C预处理器定义符号常量。好的做法是把 #define 指令放进头文件,然后在每个源文件中使用 #include 指令包含该文件即可。

& 地址运算符


指针是 C 语言中最重要也是最复杂的只是之一,它用于存储变量地址。我们首先来介绍一下 & 运算符。
& 运算符给出变量的存储地址。如 age 是变量名,则 &age 为该变量的地址:

int age = 18;

printf("%d, %p\n", age, &age);
//18, 0b71

* 间接运算符

有时,我们需要修改其他函数的变量,就需要使用到指针,从根本上看,指针(pointer)是一个值为内存地址的变量(或数据对象)。
假设 ptr 指向 bah,如下所示:

ptr = &bah;

然后使用间接运算符 *(indirection operator)找出储存在 bah 中的值,该运算符有时也称为解引用运算符(dereferencing operator)。

val = *ptr; 

声明指针

看完上面知识点,我们该如何声明指针变量呢?下面是一些指针的声明示例:

int *pi;
char *pc;
float *pf;

类型说明符表明了指针所指向对象的类型,星号(*)表明声明的变量是一个指针。