Skip to content

函数

概述

几乎所有的编程语言都有函数(或方法)的概念,

那么什么是函数呢?

什么是函数?

简单地说,,一台大型机器中的“微型执行单元”:

  1. 输入:当你启动一个函数时,你通常会传递给它一些数据,这些数据被称为或“输入参数”。
  2. 处理:函数一旦接收到这些输入,它会根据内部的指令和逻辑进行处理和运算。
  3. 输出:处理完成后,函数通常会提供一个结果,这就是我们所说的

如下图所示:

编程语言中的函数

这实际上就是函数的三个基本特征:参数输入、功能执行以及输出返回值。

总之,函数是C语言编程中的基本组成单位。它是一个代码块,用于执行特定的任务,可以接收参数并返回一个值。

函数分类

C 程序是由函数组成的,我们写的代码都是由主函数 main()开始执行的。函数是 C 程序的基本模块,是用于完成特定任务的程序代码单元。

从函数定义的角度看,函数可分为系统函数和用户定义函数两种:

  • 系统函数,即库函数:这是由编译系统提供的,用户不必⾃⼰定义这些函数,可以直接使用它们,如我们常用的打印函数printf()。
  • 用户定义函数:用以解决用户的专门需要。

不论是来源于标准库还是由用户自定义,所有的函数在C语言中都遵循相同的基本原则和语法结构。

函数的定义

函数定义格式

函数定义的一般形式:

c
返回类型 函数名(形式参数列表){ 
    数据定义部分; 
    执行语句部分; 
}

// 或者下面这种形式

返回值类型 函数名(形参列表){
    // 函数体
}

其中:

  1. 函数的返回值类型就填一个数据类型,比如我们上面提到的int和float。
  2. 函数名是一个标识符,遵循命名规则即可。
  3. "(形参列表)"里的内容用于确定此函数需要什么参数数据输入。比如需要两个整数输入,就写"(int a, int b)"。同时在大括号里面还可以用a和b来使用传入函数的参数数据。
  4. 如果函数有返回值,那么"return + 返回值"会结束函数,并给出函数的返回值。注意函数返回值要和函数定义时的返回值类型对应。

记住以下几个概念:

  1. 被"{}"包裹的就是函数体,注意此大括号不可省略。函数体就是函数实现的具体逻辑,函数做了什么。
  2. 身体上面的就是头,包括"返回值类型 函数名(形参列表)"统称函数头。函数头是函数定义的起始部分

函数名

理论上是可以随意起名字,最好起的名字见名知意,应该让用户看到这个函数名字就知道这个函数的功能。注意,函数名的后⾯有个圆换号(),代表这个为函数,不是普通的变量名。

形参列表

在定义函数时指定的形参,,因此称它们是形式参数或虚拟参数,简称形参,表⽰它们并不是实际存在的数据,所以,形参里的变量不能赋值。

c
void max(int a = 10, int b = 20) // error, 形参不能赋值 
{ }

在定义函数时指定的形参,必须是,类型+变量的形式:

c
//1: right, 类型+变量 
void max(int a, int b){}

//2: error, 只有类型,没有变量 
void max(int, int){}

//3: error, 只有变量,没有类型 
int a, int b; 
void max(a, b){}

在定义函数时指定的形参,可有可⽆,根据函数的需要来设计,

c
// 没形参, 圆括号内容为空 
void max(){}

// 没形参, 圆括号内容为void关键字 
void max(void){}

函数体

花括号{ }里的内容即为函数体的内容,这里为,这和以前的写代码没太⼤区别,以前我们把代码写在main()函数里,现在只是把这些写到别的函数里。

返回值

函数的返回值是通过函数中的return语句获得的,return后⾯的值也可以是一个表达式。

  • 尽量保证return语句中表达式的值和函数返回类型是同一类型。

    c
    int max() // 函数的返回值为int类型 
    {
        int a = 10;
        return a;// 返回值a为int类型,函数返回类型也是int,匹配
    }
  • 如果函数返回的类型和return语句中表达式的值不一致,则以函数返回类型为准,即。对数值型数据,可以⾃动进行类型转换。

    c
    double max() // 函数的返回值为double类型 
    {
        int a = 10;
        return a;// 返回值a为int类型,它会转为double类型再返回
    }

    ⚠️:如果函数返回的类型和return语句中表达式的值不一致,而它又⽆法⾃动进行类型转换,程序则会报错。

  • return语句的另一个作用为中断return所在的执行函数,类似于break中断循环、switch语句一样。

    c
    int max(){ 
        return 1;// 执行到,函数已经被中断,所以下⾯的return 2⽆法被执行到 
        return 2;// 没有执行 
    }
  • 如果函数带返回值,return后⾯必须跟着一个值,如果函数没有返回值,函数名字的前⾯必须写一个void关键字,这时候,我们写代码时也可以通过return中断函数(也可以不用),只是这时,return后⾯不带内容( 分号“;”除外)。

    c
    // 最好要有void关键字 
    void max(){ 
        return; // 中断函数,这个可有可⽆ 
    }

函数的声明

如果使用用户⾃⼰定义的函数,而该函数与调用它的函数(即主调函数)不在同一文件中,或者,则必须在调用此函数之前对被调用的函数作声明。

所谓函数声明,就是在函数尚在未定义的情况下,事先将该函数的有关信息通知编译系统,相当于告诉编译器,函数在后⾯定义,以便使编译能正常进行。

⚠️ :一个函数只能被定义一次,但可以声明多次。

c
#include <stdio.h>
#include <stdlib.h>

int maxx(int x, int y);// 函数的声明,分号不能省略
// int maxx(int, int); // 另一种⽅式

int main(void) {
    int a = 10, b = 25, num_max = 0;
    num_max = maxx(a, b);// 函数的调用
    printf("num_maxx = %d\n", num_max);

    return 0;
}

// 函数的定义
int maxx(int x, int y) { return x > y ? x : y; }

程序输出:

shell
num_maxx = 25

函数定义和声明的区别

  • 定义是指对函数功能的确⽴,包括指定函数名、函数类型、形参及其类型、函数体等,它是一个完整的、独⽴的函数单位。
  • 声明的作用则是把函数的名字、函数类型以及形参的个数、类型和顺序(注意,不包括函数体)通知编译系统,以便在对包含函数调用的语句进行编译时,据此对其进行对照检查(例如函数名是否正确,实参与形参的类型和个数是否一致)。

函数的调用

这和main()函数不一样,main()为编译器设定好⾃动调用的主函数,⽆需人为调用,我们都是在main()函数里调用别的函数,

当调用函数时,需要关⼼

  • 头文件:包含指定的头文件
  • 函数名字:函数名字必须和头文件声明的名字一样
  • 功能:需要知道此函数能⼲嘛后才调用
  • 参数:参数类型要匹配
  • 返回值:根据需要接收返回值

函数执行流程

c
#include <stdio.h>
#include <stdlib.h>

void print_test() { printf("this is for test\n"); }

int main(void) {
    print_test();// print_test函数的调用

    return 0;
}

程序输出:

shell
this is for test
  1. 进⼊main()函数

  2. 调用print_test()函数:

    • 它会在main()函数的前寻找有没有一个名字叫“print_test”的函数定义;
    • 如果找到,接着检查函数的参数,这里调用函数时没有传参,函数定义也没有形 参,参数类型匹配;
    • 开始执行print_test()函数,这时候,main()函数里⾯的执行会阻塞( 停 )在print_test()这 一行代码,等待print_test()函数的执行。
  3. print_test()函数执行完( 这里打印一句话 ),main()才会继续往下执行,执行到return 0, 程 序执行完毕。

函数的形参和实参

  • 形参出现在函数定义中,在整个函数体内都可以使用,离开该函数则不能使用。
  • 实参出现在主调函数中,进⼊被调函数后,实参也不能使用。
  • 实参变量对形参变量的数据传递是“值传递”,即单向传递,
  • 在调用函数时,编译系统临时给形参分配存储单元。调用结束后,形参单元被释放。
  • 实参单元与形参单元是不同的单元。调用结束后,形参单元被释放,函数调用结束返回主调函数后则不能再使用该形参变量。实参单元仍保留并维持原值。因此,

无参函数调用

如果是调用⽆参函数,则不能加上“实参”,但括号不能省略。

c
// 函数的定义 
void test(){}

int main(){
    // 函数的调用 
    test(); // right, 圆括号()不能省略 
    test(250); // error, 函数定义时没有参数

return 0; 
}

有参函数调用

  • 如果实参表列包含多个实参,则各参数间用逗号隔开。

    c
    // 函数的定义 
    void test(int a, int b){}
    
    int main(){
        int p = 10, q = 20;
        test(p, q); // 函数的调用
    
        return 0;
    }
  • 实参与形参的个数应相等,类型应匹配(相同或赋值兼容)。实参与形参按顺序对应,一对一地传递数据。

  • 实参可以是常量、变量或表达式, 所以,这里的变量是在圆括号( )外⾯定义好、赋好值的变量。

    c
    // 函数的定义 
    void test(int a, int b){}
    
    int main(){ 
        // 函数的调用
        int p = 10, q = 20;
        test(p, q); // right 
        test(11, 30 - 10); // right
        
        test(int a, int b); // error, 不应该在圆括号里定义变量
    
        return 0;
    }

函数返回值

  • 如果函数定义没有返回值,函数调用时不能写void关键字,调用函数时也不能接收函数的返回值。

    c
    // 函数的定义 
    void test(){}
    
    int main(){
        // 函数的调用 
        test(); // right 
        void test(); // error, void关键字只能出现在定义,不可能出现在调用的地⽅ 
        int a = test(); // error, 函数定义根本就没有返回值
    
        return 0;
    }
  • 如果函数定义有返回值,这个返回值我们根据用户需要可用可不用,但是,假如我们需要使用这个函数返回值,

    c
    // 函数的定义, 返回值为int类型 
    int test(){}
    
    int main(){
      // 函数的调用 
        int a = test(); // right, a为int类型 
        int b; 
        b = test(); // right, 和上⾯等级
    
      char *p = test(); // 虽然调用成功没有意义, p为char *, 函数返回值为 int, 类型不匹配
    
      // error, 必须定义一个匹配类型的变量来接收返回值 
        // int只是类型,没有定义变量 
        int = test();
    
      // error, 必须定义一个匹配类型的变量来接收返回值 
        // int只是类型,没有定义变量 
        int test();
    
    return 0;
    }

产生随机数

c
#include <time.h> 
time_t time(time_t *t);

功能:获取当前系统时间

参数:常设置为NULL

返回值:当前系统时间, time_t 相当于long类型,单位为

c
#include <stdlib.h> 
void srand(unsigned int seed);

功能:用来设置rand()产生随机数时的随机种⼦

参数:如果每次seed相等,rand()产生随机数相等

返回值:⽆

c
#include <stdlib.h> 
int rand(void);

功能:返回一个随机数值

参数:⽆

返回值:随机数

c
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main() {
    time_t tm = time(NULL);  //得到系统时间
    srand((unsigned int) tm);//随机种⼦只需要设置一次即可

    int r = rand();
    printf("r = %d\n", r);

    return 0;
}

程序输出:

shell
r = 687136184

函数的作用

函数的使用可以省去重复代码的编写,降低代码重复率

c
#include <stdio.h>
#include <stdlib.h>

// 求两数的最⼤值
int maxx(int a, int b) {
    if (a > b) {
        return a;
    } else {
        return b;
    }
}

int main() {
    // 操作1 ……
    // ……
    int a1 = 10, b1 = 20, c1 = 0;
    c1 = maxx(a1, b1);// 调用max()
    printf("c1 = %d\n", c1);

    // 操作2 ……
    // ……
    int a2 = 11, b2 = 21;
    c1 = maxx(a2, b2);// 调用max()
    printf("c1 = %d\n", c1);

    // ……

    return 0;
}

程序输出:

shell
c1 = 20
c1 = 21

函数可以让程序更加模块化,从而有利于程序的阅读,修改和完善

假如我们编写一个实现以下功能的程序:读⼊一行数字;对数字进行排序;找到它们的平均值;打印出一个柱状图。如果我们把这些操作直接写在main()里,这样可能会给用户感觉代码会有点凌乱。但,假如我们使用函数,这样可以让程序更加清晰、模块化:

c
#include <stdio.h>
#include <stdlib.h>

// 求两数的最⼤值
int maxx(int a, int b) {
    if (a > b) {
        return a;
    } else {
        return b;
    }
}

int main() {
    float list[50];

    // 这里只是举例,函数还没有实现
    readlist(list, 50);
    sort(list, 50);
    average(list, 50);
    bargraph(list, 50);

    return 0;
}

这里我们可以这么理解, 默认情况下,公司就是一个⼤部门( 只有一个部门的情况下 ),相当于C程序的main()函数。如果公司比较⼩( 程序比较⼩ ),因为任务少而简单,一个部门即可( main()函数 )胜任。但是,如果这个公司很⼤( 大型应用程序 ),任务多而杂,如果只是一个部门管理( 相当于没有部门,没有分工 ),我们可想而知,公司管理、运营起来会有多混乱,不是说这样不可以运营,只是这样不完美而已,如果根据公司要求分成一个个部门( 根据功能封装一个一个函数 ),招聘由行政部门负责,研发由技术部门负责等,这样就可以分工明确,结构清晰,⽅便管理,各部门之间还可以相互协调。

main函数与exit函数

在main函数中调用exit和return结果是一样的,但在⼦函数中调用return只是代表⼦函数终⽌了,在⼦函数中调用exit,那么程序终⽌。

c
#include <stdio.h>
#include <stdlib.h>

void fun() {
    printf("fun\n");
    //return;
    exit(0);
}

int main(void) {
    fun();
    while (1);

    return 0;
}

程序输出:

shell
fun

关于主函数,我们需要知道

  1. 主函数是程序的入口,任何C程序的运行都是从主函数开始的。C语言的这种设计在编程领域引领潮流,被许多后来的编程语言采用,例如C++、Java、C#以及Go等。
  2. int 是 main 函数的返回值类型,表示该函数执行完毕后将返回一个整数值给操作系统。
  3. 我们约定,main函数的返回值是0时表示程序正常终止,非0则表示程序意外终止。

函数类型

通过什么来区分两个不同的函数?

一个函数在编译时被分配一个⼊⼜地址,这个地址就称为函数的指针,函数名代表函数的⼊ ⼜地址。

函数三要素: 名称、参数、返回值。C语⾔中的函数有⾃⼰特定的类型。

C语⾔中通过typedef为函数类型重命名:

c
typedef int f(int, int); // f 为函数类型 
typedef void p(int);// p 为函数类型

这一点和数组一样,因此我们可以用一个指针变量来存放这个⼊⼜地址,然后通过该指针变 量调用函数。

注意:通过函数类型定义的变量是不能够直接执行,因为没有函数体。只能通过类型定义一 个函数指针指向某一个具体函数,才能调用。

c
typedef int(p)(int, int);

void my_func(int a,int b){ printf("%d %d\n",a,b); }

void test(){ 
    p p1; 
    //p1(10,20); //错误,不能直接调用,只描述了函数类型,但是并没有定义函数体,没 有函数体⽆法调用 
    p* p2 = my_func; p2(10,20); //正确,指向有函数体的函数⼊⼜地址 
}

函数指针

函数指针(指向函数的指针)

  • 函数指针定义⽅式(先定义函数类型,根据类型定义指针变量);
  • 先定义函数指针类型,根据类型定义指针变量;
  • 直接定义函数指针变量;
c
int my_func(int a, int b) {
    printf("ret:%d\n", a + b);
    return 0;
}

//1. 先定义函数类型,通过类型定义指针
void test01() {
    typedef int(FUNC_TYPE)(int, int);

    FUNC_TYPE *f = my_func;

    //如何调用?
    (*f)(10, 20);
    f(10, 20);
}

//2. 定义函数指针类型
void test02() {
    typedef int (*FUNC_POINTER)(int, int);
    FUNC_POINTER f = my_func;//如何调用?
    (*f)(10, 20);
    f(10, 20);
}

//3. 直接定义函数指针变量
void test03() {
    int (*f)(int, int) = my_func;//如何调用?
    (*f)(10, 20);
    f(10, 20);
}

函数指针数组

函数指针数组,每个元素都是函数指针。

c
void func01(int a) { printf("func01:%d\n", a); }
void func02(int a) { printf("func02:%d\n", a); }
void func03(int a) { printf("func03:%d\n", a); }

void test() {
#if 0

//定义函数指针 
void(*func_array[])(int) = { func01, func02, func03 };

#else

    void (*func_array[3])(int);
    func_array[0] = func01;
    func_array[1] = func02;
    func_array[2] = func03;

#endif

    for (int i = 0; i < 3; i++) {
        func_array[i](10 + i);
        (*func_array[i])(10 + i);
    }
}

回调函数

函数指针做函数参数(回调函数),函数参数除了是普通变量,还可以是函数指针变量。在 C 语言中把函数指针作为参数传递是十分普遍的,这样的函数我们称之为回调(callback)函数。

c
//形参为普通变量 
void fun( int x ){} 

//形参为函数指针变量 
void fun( int(*p)(int a) ){}

函数指针变量常见的用途之一是把指针作为参数传递到其他函数,指向函数的指针也可以作为参数,以实现函数地址的传递。

  • void (*func)(int, int)
  • &func1 或者 func1
  • (*func)(实参列表) 或者 func(实参列表)
代码示例01
c
#include <stdio.h>

int plus(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }

int main() {
    // 函数指针变量
    int (*func)(int, int);
    
    // 把函数地址赋值给函数指针变量,函数地址表示方式为 &plus,通过函数指针调用函数
    func = &plus;
    printf("%d\n", (*func)(10, 20));
    
    // 把函数地址赋值给函数指针变量,函数地址表示方式为 sub,通过函数指针调用函数
    func = sub;
    printf("%d\n", func(10, 20));
    
    return 0;
}

程序输出:

shell
30
-10
代码示例02
c
#include <stdio.h>

int plus(int a, int b) { return a + b; }

int sub(int a, int b) { return a - b; }

//函数指针 做函数的参数 --- 回调函数
void Calculator(int (*myCalculate)(int, int), int a, int b) {
    int ret = myCalculate(a, b);
    printf("ret = %d\n", ret);
}

int main(int argc, char const *argv[])
{
    Calculator(plus, 10, 20);
    Calculator(sub, 10, 20);

    return 0;
}

程序输出:

shell
ret = 30
ret = -10

注意:函数指针和指针函数的区别

  • 函数指针是指向函数的指针;
  • 指针函数是返回类型为指针的函数;

C 函数库中一些功能强大的函数都是以函数指针作为参数,比如 qsort 函数。qsort 声明在 <stdlib.h> 头文件中,它是可以给任意数组排序的通用函数。数组的元素也可以是任意类型,甚至可以是结构体或者指针,因此,我们必须告诉 qsort 函数如何比较数组元素的大小。传入一个比较函数可以提供这些信息。

qsort 函数的原型如下:

c
void qsort(void* base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));
  • base 指向数组中要排序的第一个元素 (一般是数组的第一个元素)。
  • nmemb 是要排序元素的数量 (可以小于或等于数组中元素的个数)。
  • size 表示数组元素的大小。
  • compar 是比较函数,如果第一个参数比第二个参数小则返回负数,相等则返回零,第一个参数大于第二个参数则返回正数。

qsort 函数可以对任意类型的数组进行排序。默认情况下,qsort 函数使用 strcmp 函数来比较字符串,并且是从小到大排序。

compar 是比较函数,一般也会被称为钩子函数。qsort 通过参数伸出一个钩子,通过钩子函数来比较数组元素。

qsort 示例

从键盘录入 5 个学生的信息,然后对学生进行排序。排序规则如下:先按总分从高到低进行排序,如果总分一样,依次按语文、数学、英语的分数从高到低进行排序;如果各科成绩都一样,则按名字的字典顺序从小到大排序。

学生结构体定义如下:

c
typedef struct {
    int id;
    char name[25];
    int chinese;
    int math;
    int english;
} Student;
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 学生结构体定义
typedef struct {
    int id;
    char name[25];
    int chinese;
    int math;
    int english;
} Student;

int compare(const void *p1, const void *p2);


int main(void) {
    Student s[5];
    for (int i = 0; i < 5; ++i)
    {
        scanf("%d%s%d%d%d", &s[i].id, s[i].name, &s[i].chinese, &s[i].math, &s[i].english);
    }

    qsort(s, 5, sizeof(Student), compare);

    puts("----------------------------");

    for (int i = 0; i < 5; ++i)
    {
        printf("%d %s %d %d %d\n", s[i].id, s[i].name, s[i].chinese, s[i].math, s[i].english);
    }

    return 0;
}

int compare(const void *p1, const void *p2) {
    const Student *s1 = p1;
    const Student *s2 = p2;

    int total1 = s1->chinese + s1->math + s1->english;
    int total2 = s2->chinese + s2->math + s2->english;

    if(total1 != total2) {
        // qosrt默认从小到大排序,且返回<0 则p1小 越靠前
        // 但是排名顺序应该为分数越高名次越靠前,所以分数大的应该靠前
        return total2 - total1;
    }

    if(s1->chinese != s2->chinese) {
        return s2->chinese - s1->chinese;
    }

    if(s1->math != s2->math) {
        return s2->math - s1->math;
    }

    if(s1->english != s2->english) {
        return s2->english - s1->english;
    }

    return strcmp(s1->name, s2->name);
}

程序运行情况:

shell
1 liuyifei 98 95 95
2 tangyutong 95 95 98
3 xixi 100 100 100
4 wangyuyan 100 100 100
5 wuqing 95 98 95
----------------------------
4 wangyuyan 100 100 100
3 xixi 100 100 100
1 liuyifei 98 95 95
5 wuqing 95 98 95
2 tangyutong 95 95 98

递归函数

基本概念

C通过运行时堆栈来支持递归函数的实现。递归函数就是直接或间接调用⾃⾝的函数。

普通函数调用

c
void funB(int b) {
    printf("b = %d\n", b);
}

void funA(int a) {
    funB(a - 1);
    printf("a = %d\n", a);
}

int main(void) {
    funA(2);
    printf("main\n");
    return 0;
}

函数的调用流程如下:

递归函数调用

c
void fun(int a) {
    if (a == 1) {
        printf("a = %d\n", a);
        return;//中断函数很重要
    }

    fun(a - 1);
    printf("a = %d\n", a);
}

int main(void) {
    fun(2);
    printf("main\n");

    return 0;
}

函数的调用流程如下:

作业:递归实现给出一个数8793,依次打印千位数字8、百位数字7、⼗位数字9、个位数字3。

c
void recursion(int val){
    if (val == 0){
        return;
    }
    int ret = val / 10; 
    recursion(ret); 
    printf("%d ",val % 10);
}

可以参考下⾯一个普通函数实现改为递归函数实现的案例

c
#include <stdio.h>

double power(double n, int p);// ANSI函数原型

int main(void) {
    double x, xpow;
    int exp;

    printf("Enter a number and the positive integer power");
    printf(" to which\nthe number will be raised. Enter q");
    printf(" to quit.\n");

    while (scanf("%lf%d", &x, &exp) == 2) {
        xpow = power(x, exp);// 函数调用
        printf("%.3g to the power %d is %.5g\n", x, exp, xpow);
        printf("Enter next pair of numbers or q to quit.\n");
    }
    printf("Hope you enjoyed this power trip -- bye!\n");

    return 0;
}

double power(double n, int p) {
    // 函数定义
    if (p == 0) {
        if (n > -0.000001 && n < 0.000001) { printf("0的0次幂未定义,因此把该值处理为1。\n"); }
        return 1;
    } else if (n > -0.000001 && n < 0.000001) {
        return 0;
    } else if (p < 0) {
        double pow = 1;
        int i;

        for (i = p; i < 0; i++)
            pow /= n;
        return pow;
    }

    double pow = 1;
    int i;

    for (i = 1; i <= p; i++)
        pow *= n;
    return pow;// 返回pow的值
}
#include <stdio.h>

double power(double n, int p);// ANSI函数原型
double power1(double n, int p, double pow);

int main(void) {
    double x, xpow;
    int exp;

    printf("Enter a number and the positive integer power");
    printf(" to which\nthe number will be raised. Enter q");
    printf(" to quit.\n");
    while (scanf("%lf%d", &x, &exp) == 2) {
        xpow = power(x, exp);// 函数调用
        printf("%.3g to the power %d is %.5g\n", x, exp, xpow);
        printf("Enter next pair of numbers or q to quit.\n");
    }
    printf("Hope you enjoyed this power trip -- bye!\n");

    return 0;
}

double power(double n, int p) {
    // 函数定义
    double pow = 1;
    if (p == 0) {
        if (n > -0.000001 && n < 0.000001) { printf("0的0次幂未定义,因此把该值处理为1。\n"); }
        return 1;
    } else if (n > -0.000001 && n < 0.000001) {
        return 0;
    }
    pow = power1(n, p, pow);
    return pow;
}

double power1(double n, int p, double pow) {
    if (p == 0) { return pow; }
    if (p > 0) {
        pow *= n;
        p--;
        return power1(n, p, pow);
    } else {
        pow /= n;
        p++;
        return power1(n, p, pow);
    }
}

在上⾯改写的例⼦当中,如果power1函数中调用递归的时间没有使用return程序就⽆法得到预期的结果;

又如下⾯一个实际使用递归函数然后改写成另一个需求的例⼦(在该程序中,如果r的值是0,to_binary()函数就显⽰字符'0'; 如果r的值是1,to_binary()函数则显⽰字符'1'。条件表达式r == 0 ? '0' : '1'用于把数值转换成字符。);第二部分为了让程序中的to_binary()函数更通用,编写了一个to_base_n()函数接受两个参数,且第二个参数在2~10范围内,然后以第2个参数中指定的进制打印第1个参数的数值。例如,to_base_n(129,8)显⽰的结果为201,也就是129的⼋进制数。在一个完整的程序中测试该函数。

c
#include <stdio.h>

void to_binary(unsigned long n);

int main(void) {
    unsigned long number;
    printf("Enter an integer (q to quit):\n");
    while (scanf("%lu", &number) == 1) {
        printf("Binary equivalent: ");
        to_binary(number);
        putchar('\n');
        printf("Enter an integer (q to quit):\n");
    }
    printf("Done.\n");

    return 0;
}

/* 递归函数 */
void to_binary(unsigned long n) {
    int r;
    r = n % 2;

    if (n >= 2) to_binary(n / 2);
    putchar(r == 0 ? '0' : '1');
    return;
}
#include <stdio.h>

void to_binary(unsigned long n);
void to_base_n(unsigned long n, int x);

int main(void) {
    unsigned long number;
    printf("Enter an integer (q to quit):\n");

    while (scanf("%lu", &number) == 1) {
        printf("Binary equivalent: ");
        to_binary(number);
        putchar('\n');
        to_base_n(number, 8);
        putchar('\n');
        printf("Enter an integer (q to quit):\n");
    }
    printf("Done.\n");

    return 0;
}

/* 递归函数 */
void to_binary(unsigned long n) {
    int r;
    r = n % 2;
    if (n >= 2) to_binary(n / 2);
    putchar(r == 0 ? '0' : '1');
    return;
}

void to_base_n(unsigned long n, int x) {
    if (x < 2 || x > 10) { return; }
    int r;
    r = n % x;

    if (n >= x) to_base_n(n / x, x);
    putchar('0' + r);
    return;
}

递归实现字符串反转

c
#include <stdio.h>
#include <string.h>

int reverse1(char *str) {
    if (str == NULL) { return -1; }

    // 函数递归调用结束条件
    if (*str == '\0') { return 0; }

    reverse1(str + 1);
    printf("%c", *str);
    return 0;
}

char buf[1024] = {0};//全局变量

int reverse2(char *str) {
    if (str == NULL) { return -1; }

    // 函数递归调用结束条件
    if (*str == '\0') { return 0; }

    reverse2(str + 1);
    strncat(buf, str, 1);
    return 0;
}

int reverse3(char *str, char *dst) {
    if (str == NULL || dst == NULL) { return -1; }

    // 函数递归调用结束条件
    if (*str == '\0') { return 0; }

    reverse3(str + 1);
    strncat(dst, str, 1);

    return 0;
}

多文件(分文件)编程

分文件编程

  • 把函数声明放在头文件xxx.h中,在主函数中包含相应头文件
  • 在头文件对应的xxx.c中实现xxx.h声明的函数

防止头文件重复包含

当一个项目比较⼤时,往往都是分文件,这时候有可能不⼩⼼把同一个头文件 include 多次,或者头文件嵌套包含。

a.h` 中包含 `b.h` : `#include "b.h" 
b.h` 中包含 `a.h`: `#include "a.h"

main.c 中使用其中头文件:

c
#include "a.h"

int main(){
    return 0;
}

编译上⾯的例⼦,会出现如下错误:

为了避免同一个文件被include多次,C/C++中有两种⽅式,一种是 #ifndef ⽅式,一种是 #pragma once ⽅式。

#ifndef ⽅式

c
#ifndef __SOMEFILE_H__ 
#define __SOMEFILE_H__ 

// 声明语句 

#endif

#pragma once ⽅式

c
#pragma once 

// 声明语句