C 语言学习笔记 05 - 函数与程序结构

函数

普通函数

/* 标准形式
 * <return type> <name>(<parameters>) {
 *  ...statement
 * return <return value>;
 * }
 * 
 */


int main(void) {
    puts("Hello World");

    return 0;
}


// 多参数函数
double MultipleArg(double x, double y, double z) {
    return x + y + z;
}

C 语言中的函数形式大致如上所示,由函数名、返回值、参数等组成。

其中,x 为形式参数,而像 2f 这样的为实际参数。

在 Google 的函数命名规范中,推荐使用 PascalCase 风格命名,即首字母大写,不同单词的首字母使用大写,例如 FindNumber

函数的原型

在阅读 C90 以前的代码时,你会发现有些函数不需要填写返回值。

main() {
    return 0;
} // default return int

如果没有声明函数返回值类型,默认为 int。

如果一个函数中不提供任何参数,则默认接受所有参数,例如:

#include <stdio.h>
#include <sys/time.h>
#include <stdint-gcc.h>

uint64_t GetCurrentTime() {
    struct timeval tv;
    gettimeofday(&tv,NULL);
    return tv.tv_sec * (uint64_t)1000000 + tv.tv_usec;
}

int main() {
    GetCurrentTime(); // no error
    GetCurrentTime(1); // no error
    GetCurrentTime(1, "timezone"); // no error
}

因此,当表示不需要传参时,应当使用 void 表示,否则会造成不必要的麻烦。

函数声明

在 Java 中,我们知道你不能将欲调用的函数写在调用处的下面:

void printNumber() {
    System.out.println(getNumber()); // error
}

double getNumber() {
    return 114514.1919;
}

而在 C 中,它只关心函数的参数、名称及返回值,不关心具体实现。因此我们可以预先声明一个函数:

void Max(int, int);

// void Max(int num1, int num2) num1 and num2 are redundant

int main(void) {
    Max(1, 2); // it works
}

void Max(int num1, int num2) {
    // do something
}

我们将只含函数参数列表、函数名称及返回值的 “函数” 称为函数原型,且可以被用于函数声明。

你可以只声明函数原型,而在其他库中实现对应函数。

变长参数

在 C 语言中,也有像现代语言一样的变长参数,但支持比较原始,例如我们最熟悉的 printf 就使用了变长参数。一个支持变长参数的函数如下所示。

#include <stdio.h>
#include <stdarg.h>

void HandleVarargs(int arg_count, ...) {
    va_list args; // 定义 va_list 获取变长参数
    
    // 开始遍历
    va_start(args, arg_count);

    for (int i = 0; i < arg_count; ++i) {
        // 取出对应参数 (va_list, type)
        int arg = va_arg(args, int);
        printf("%d: %d\n", i, arg);
    }

    // 结束遍历
    va_end(args);
}

需要注意的是,C 语言中的变长参数并不类型安全,你需要手动转换。

函数递归

递归主要由规则、初始值与终止条件组成,与数学中递归的概念相似。

我们以数学中的阶乘为例:

unsigned int Factorial(unsignedd int n) {
    if (n == 0) {
        return 1;
    } else {
        return n * Factorial(n - 1); // f(n) = n*f(n-1)
    }
}

其中的终止条件即为 n = 0。

此外,还有最经典的例子——斐波那契数列:\(f(n) = f(n -1) + f(n-2)\)

以下是 C 中的实现:

unsigned int Fibonacci(unsigned int n) {
    if (n == 1 || n == 0) {
        return n;
    } else {
        return Fibonacci(n - 1) + Fibonacci(n - 2);
    }
}

但递归存在多层函数调用,内存开销很大,使用递归时应注意调用层数,或尝试使用迭代解决问题。

下面是阶乘与斐波那契的迭代实现:

unsigned int Factorial(unsigned int n) {
    unsigned int result = 1;
    for (unsigned int i = n; i > 0; --i) {
        result *= i;
    }
    return result;
}

unsigned int Fibonacci(unsigned int n) {
    if (n == 1 || n == 0) {
        return n;
    }
    
    unsigned int last = 0;
    unsigned int current = 1;

    for (int i = 0; i <= n - 2; ++i) {
        unsigned int temp = current;
        current += last;
        last = temp;
    }

    return current;
}

迭代的实现显然易读性大大低于递归,但就性能上也大大优于递归,二者各有千秋。

在下一节中,我们将介绍 C 语言的头文件

comments powered by Disqus