函数定义

函数声明

当程序调用一个无法见到原型的函数时, 编译器会认为该函数返回一个整型值. 对于那些并不返回整型值的函数, 这种认定可能会引发错误.

所有的函数都应该具有原型, 尤其那些返回值不是整型的函数

函数的参数

C 函数所有参数均以 传值调用 方式进行传递, 也就是函数将获得参数值的一份 拷贝.

如果参数是一个数组名, 并在函数中通过下标修改了该数组参数, 那么么将修改调用 程序中的数组元素. 数组并不会被赋值, 这个行为被称为 传址调用.

数组名的值实际上是一个指针, 传递给函数就是这个指针的拷贝. 下标引用实际上是 间接访问的以一种形式.

  1. 传递给函数的标量参数都是传值调用
  2. 传递给函数的数组参数在行为上就像他们是通过传址调用那样.

ADT 和黑盒

由于C可以限制函数和数据定义的作用域, 所以它可以用于设计和实现 抽象数据类型(ADT, abstract data type). 这个技巧也称为 黑盒(black box) 设计.

抽象数据类型的基本想法很简单----模块具有功能说明和接扩说明, 前者说明模块 所执行的任务, 后者定义模块的作用. 模块的用户不需知道模块实现的任何细节.

限制模块的访问是通过 static 关键字的合理使用实现的, 可以对那些非接口的函数 和数据访问

递归

C 通过运行时堆栈支持递归函数的实现. 递归函数就是直接或间接的调用自身的函数

追踪递归函数

当函数调用时, 它的变量空间是创建与运行时堆栈上的. 以前调用的函数变量仍保留 在堆栈上, 但他们被新函数的变量所掩盖, 因此不能访问.

递归函数调用自身时也是如此. 每进行一次新的调用, 都将创建一批变量, 它们将 掩盖递归函数前一次调用所创建的变量.

当递归函数不在调用自身, 函数将会返回, 并开始销毁堆栈上的变量值.

递归与迭代

递归是一种强有力的技巧, 但和其他技巧一样, 它可能被误用, 必须阶乘.

递归函数将设计一些运行时开销----参数必须压到堆栈中, 为局部变量分配内存空间, 寄存器的值不惜保存等. 等递归函数每次调用返回时, 上述这些操作必须还原, 恢复 成原来的样子.

递归计算阶乘:

/*
 * 用递归计算 n 的阶乘
 */
long
factorial(int n)
{
    if (n <= 0)
        return 1;
    else
        return n * factorial(n -1 );
}
仔细观察上面函数, 会发现递归调用的函数是执行的最后一项任务. 这个函数是 尾部递归(tail recursion) 的一个例子. 由于函数在递归调用返回之后不在执行 任何任务, 所以尾部递归可以方便地转换成一个简单循环, 完成相同任务.

迭代计算阶乘:

long factorial(int n)
{
    int result = 1;
    while (n > 1){
        result *= n;
        n -= 1;
    }
    return result;
}

递归方法计算斐波那契数

long
fibonacci( int n )
{
    if (n < = 2)
        return 1;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

有时候循环不如递归形式符合抽象定义, 但它的效率提高了几十万倍.

使用递归方式实现一个函数之前, 先问问自己使用递归带来的好处是否低得上它的 代价, 而且必须小心, 这个代价可能比初看上去要大的多.

long
fibonacci( int n)
{
    long result;
    long previous_result;
    long next_older_result;

    result = previous_result = 1;
    
    while (n > 2){
        n -= 1;
        next_older_result = previous_result;
        previous_result = result;
        result = previous_result + next_older_result;
    }
    return result;
}

可变参数列表

可变参数列表是通过宏来实现的, 这些宏定义与 stdarg.h 文件, 是标准库的一部分.

头文件声明了一个类型 va_list 和三个宏 ---- va_start, va_arg 和 va_end. 可以声明一个类型为 va_list 的变量, 与几个宏配合使用, 访问参数值.

参数列表使用省略号提示此处可能传数量和类型未确定的参数.

函数声明一个 var_arg 的变量, 用于访问参数列表未去顶的部分. 这个变量通过调用 va_start 来初始化. 它的第一个参数是 va_list 变量的名字, 第二个参数是省略号前最后一个有名字的参数. 初始化过程把 var_arg 变量设置为 指向可变参数部分的第一个参数

使用 va_arg 宏访问参数, 它接受两个参数: va_list 变量和参数列表中一个参数的 类型.

访问完毕最后一个可变参数后, 需要调用 va_end.

可变参数的限制

  1. 可变参数必须从头到按照顺序做个访问. 可以访问几个后半途终止.
  2. 这些宏无法判断实际存在的参数的数量
  3. 这些红无法判断每个参数的类型