14-特殊函数——静态函数、递归函数、函数指针、回调函数、内联函数、变参函数

14-特殊函数——静态函数、递归函数、函数指针、回调函数、内联函数、变参函数

一、静态函数

背景:在C语言中,普通函数默认是跨文件可见的。这意味着,如果在a.c文件中定义了一个函数swap(),则在b.c中也可以调用这个函数。这种特性在大型项目中可能会导致函数命名冲突。

静态函数:静态函数只能在其定义所在的文件中使用,限制了函数的作用范围,避免了命名冲突

1.1 语法

定义静态函数的语法是在函数的返回类型前加上static关键字:

static int func(int a, int b) {
    // 函数体
}

注意:

  • 静态函数的作用范围仅限于定义它的文件。
  • 静态函数可以防止不同文件中同名函数的冲突。
  • 静态函数不应被定义在头文件中,因为头文件可能会被多个源文件包含,这违背了静态函数的设计初衷。

二、递归函数

概念:递归函数是指在其内部包含对自身调用的函数。

递归问题示例

  1. 阶乘计算
  2. 幂运算
  3. 字符串反转

要点

  • 递进与回归:递归函数包含两个过程,逐步递进(问题规模逐渐缩小)和逐步回归(达到基准条件后开始返回)。
  • 基准条件:递归函数必须包含一个明确的基准条件以避免无限递归,防止栈溢出。

2.1 示例:输出n个自然数

思路:先输出n-1个自然数,最后输出n

#include <stdio.h>

void printNaturalNumbers(int n) {
    if (n > 0) {
        printNaturalNumbers(n - 1);  // 递进
        printf("%d ", n);  // 回归
    }
}

int main() {
    int n = 5;
    printf("Natural numbers up to %d:\n", n);
    printNaturalNumbers(n);
    return 0;
}

2.2 内存变化

递归调用时,每次调用都会在栈上分配新的栈帧。栈帧包括函数的局部变量和返回地址等。当递归深度过大时,会导致栈空间耗尽,可能引起栈溢出。
在这里插入图片描述


总结:

  • 递归栈增长:递归调用时,栈空间不断增长,直到满足基准条件或栈空间耗尽。
  • 递进和回归:问题逐步递进,达到基准条件后开始逐步回归。
  • 基准条件重要性:基准条件确保递归能正常终止,避免无限递归导致的栈溢出。

三、函数指针

函数指针是指向函数的指针,指针可以调用它指向的函数。函数指针的定义和使用如下:

#include <stdio.h>

int Printf(int a, float f) {
    printf("a: %d, f: %f\n", a, f);
    return 0;
}

int main(int argc, char const *argv[]) {
    // 定义一个函数指针, 名字为 p ,它指向的函数有一个整型返回值,需要一个整型参数以及一个浮点参数
    int (*p)(int, float);
    p = Printf;  // 函数名其实也是这个函数的入口地址

    // 直接调用函数
    Printf(10, 3.14);

    // 使用函数指针调用函数
    p(56, 9.8888);

    return 0;
}

四、指针函数

指针函数是一个返回指针的函数。例如:

int* func(int a, int b) {
    int* kk = (int*)malloc(sizeof(int));
    *kk = a + b;
    return kk;
}

注意:在实际使用中要避免返回局部变量的地址,应返回动态分配的内存或全局变量的地址。

五、回调函数

回调函数是一种通过函数指针实现的,函数的实现方不直接调用该函数,而是由接口提供方来调用该函数。例如:

案例一

#include <stdio.h>

void func(int num) {
    printf("当前收到信号,军师让我蹲下 !!\n");
}

void test(int num, void (*p)(int)) {
    for (size_t i = 0; i < num; i++) {
        printf("num: %d\n", num--);
        if (num == 50) {
            p(1);
        }
    }
}

int main(int argc, char const *argv[]) {
    void (*p)(int); // 定义一个函数指针
    p = func; // 让指针 p 指向函数 func 

    test(100, p);

    return 0;
}

案例二

#include <stdio.h>
#include <signal.h>

void func(int num )
{  printf("当前收到  3 号信号 , 军师让我蹲下 !!\n");

}

    int main(int argc, char const *argv[])
{
    void (*p)(int); // 定义一个函数指针
    p = func ; // 让指针p 指向函数  func 

    // 设置进程捕获信号 ,如果信号值 为 3的时候 , 会自行调用 p 所指向的函数 (代码/指令)
    signal( 3 , p );

    while(1);     
    

    return 0;
}

signal( 3 , function);是一个用于捕获信号的函数,当他捕获到指定信号的时候则会执行用户所提供的函数。
由于signal函数是内核提供的函数,修改内核的代码不现实, 因此它提供的接口是一个函数指针,只要某个条件满足则会自动执行我们所给的函数。
使得不同软件模块的开发者的工作进度可以独立出来,不受时空的限制,需要的时候通过约定好的接口(或者标准)相互契合在一起。

六、内联函数

内联函数通过将函数调用替换为函数体来提高运行效率,避免函数调用的开销。语法上在函数前加上 inline 关键字。

语法:与普通函数区别不大, 只是在前面增加了函数的修饰 inline

inline int max_value(int x, int y) {
    return (x > y) ? x : y;
}

不适用内联函数的情况下,有可能某一个函数被多次重复调用则会浪费一定的时间在调用的过程中(现场保护+恢复)
在这里插入图片描述

如果使用内联函数就相当与把需要调用的函数的内容(指令)拷贝到需要调用的位置,可以节省函数调用的过程中浪费的时间
在这里插入图片描述

内联函数在提高运行效率的过程中,消耗了更多的内存空间。

七、变参函数

变参函数允许接受可变数量和类型的参数,通过 stdarg.h 头文件中的宏来实现。这些宏包括 va_listva_startva_argva_end

7.1 示例:实现一个简单的变参函数

下面是一个示例,实现一个类似 printf 的变参函数,用于输出格式化字符串。

7.1.1 分析 printf 函数

printf 函数可以接受可变数量的参数。比如 printf("%d%c%lf", 100, 'x', 3.14);。这里的各个参数的入栈顺序是从右往左进行的。
在这里插入图片描述

7.2 实现步骤

  1. 定义变参函数:定义一个接受变参的函数 my_printf
  2. 使用 stdarg.h 中的宏:利用 va_listva_startva_argva_end 来处理变长参数。
  3. 处理格式化字符串:解析格式化字符串,根据不同的格式符号输出相应类型的参数。
7.2.1 代码示例
#include <stdio.h>
#include <stdarg.h>

// 定义变参函数
void my_printf(const char* format, ...) {
    va_list args; // 定义一个 va_list 类型的变量,用于访问变长参数
    va_start(args, format); // 初始化 args,使其指向第一个可变参数

    while (*format) { // 遍历格式化字符串
        if (*format == '%' && *(format + 1)) { // 如果遇到 '%' 符号,并且下一个字符不是 '\0'
            format++;
            switch (*format) { // 判断格式符号
                case 'd': {
                    int i = va_arg(args, int); // 获取 int 类型的参数
                    printf("%d", i);
                    break;
                }
                case 'c': {
                    char c = (char)va_arg(args, int); // 获取 char 类型的参数,注意 char 类型通过 int 获取
                    printf("%c", c);
                    break;
                }
                case 'f': {
                    double d = va_arg(args, double); // 获取 double 类型的参数
                    printf("%f", d);
                    break;
                }
                case 's': {
                    char* s = va_arg(args, char*); // 获取字符串类型的参数
                    printf("%s", s);
                    break;
                }
                default:
                    printf("Unknown format specifier: %%%c\n", *format); // 处理未知格式符号
            }
        } else {
            putchar(*format); // 输出普通字符
        }
        format++;
    }

    va_end(args); // 清理工作
}

int main() {
    // 测试变参函数
    my_printf("Hello %s, your score is %d and your average is %f\n", "John", 85, 87.5);
    my_printf("Character: %c\n", 'A');
    return 0;
}

代码说明

  1. 定义 my_printf 函数

    • 使用 va_list 定义一个变长参数列表 args
    • 使用 va_start(args, format) 初始化 args,并将其指向第一个变长参数。
    • 遍历格式化字符串 format,遇到格式符号(如 %d%c)时,使用 va_arg 获取相应类型的参数并输出。
    • 使用 va_end(args) 结束变长参数处理。
  2. 调用 my_printf 函数

    • my_printf("Hello %s, your score is %d and your average is %f\n", "John", 85, 87.5); 输出 Hello John, your score is 85 and your average is 87.500000
    • my_printf("Character: %c\n", 'A'); 输出 Character: A

总结

  1. 函数指针:定义和使用指向函数的指针。
  2. 指针函数:返回指针的函数。
  3. 回调函数:通过函数指针实现的,由接口提供方调用的函数。
  4. 内联函数:通过 inline 关键字避免函数调用的开销,提高效率。
  5. 变参函数:通过 stdarg.h 中的宏实现接受可变数量参数的函数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

写的什么石山代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值