当前位置: 首页 > news >正文

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

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

文章目录

  • 14-特殊函数——静态函数、递归函数、函数指针、回调函数、内联函数、变参函数
    • 一、静态函数
      • 1.1 语法
    • 二、递归函数
      • 2.1 示例:输出n个自然数
      • 2.2 内存变化
    • 三、函数指针
    • 四、指针函数
    • 五、回调函数
    • 六、内联函数
    • 七、变参函数
      • 7.1 示例:实现一个简单的变参函数
        • 7.1.1 分析 `printf` 函数
      • 7.2 实现步骤
        • 7.2.1 代码示例

一、静态函数

背景:在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 中的宏实现接受可变数量参数的函数。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 神经网络 torch.nn---Convolution Layers
  • 深入理解交叉熵损失 CrossEntropyLoss - CrossEntropyLoss
  • 深入ES6:解锁 JavaScript 类与继承的高级玩法
  • KUKA机器人中断编程详细教程1—了解中断
  • MySQL使用
  • CSS学习|css三种导入方式、基本选择器、层次选择器、结构伪类选择器、属性选择器、字体样式、文本样式
  • 数字取证技术(Digital Forensics Technology)实验课II
  • 通过在idea上搭建虚拟hadoop环境使用MapReduce做词频去重
  • 【C#线程设计】3:threadpool
  • 模板显式、隐式实例化和(偏)特化、具体化的详细分析
  • IAR仿真调试
  • Ubuntu部署开源网关Apache APISIX
  • Facebook革新:数字社交的下一个阶段
  • 【内存管理】内存管理概述
  • UnityXR Interaction Toolkit 如何使用XRHand手部识别
  • 深入了解以太坊
  • 【翻译】babel对TC39装饰器草案的实现
  • JS基础之数据类型、对象、原型、原型链、继承
  • Linux学习笔记6-使用fdisk进行磁盘管理
  • Python学习笔记 字符串拼接
  • quasar-framework cnodejs社区
  • React-Native - 收藏集 - 掘金
  • Redis的resp协议
  • 第13期 DApp 榜单 :来,吃我这波安利
  • 机器学习学习笔记一
  • 经典排序算法及其 Java 实现
  • 实习面试笔记
  • 算法之不定期更新(一)(2018-04-12)
  • 我有几个粽子,和一个故事
  • HanLP分词命名实体提取详解
  • ​ssh-keyscan命令--Linux命令应用大词典729个命令解读
  • !$boo在php中什么意思,php前戏
  • # 消息中间件 RocketMQ 高级功能和源码分析(七)
  • #单片机(TB6600驱动42步进电机)
  • #我与Java虚拟机的故事#连载08:书读百遍其义自见
  • (aiohttp-asyncio-FFmpeg-Docker-SRS)实现异步摄像头转码服务器
  • (poj1.3.2)1791(构造法模拟)
  • (PWM呼吸灯)合泰开发板HT66F2390-----点灯大师
  • (笔试题)合法字符串
  • (顶刊)一个基于分类代理模型的超多目标优化算法
  • (二十四)Flask之flask-session组件
  • (附源码)springboot车辆管理系统 毕业设计 031034
  • (一)、软硬件全开源智能手表,与手机互联,标配多表盘,功能丰富(ZSWatch-Zephyr)
  • (轉貼) 資訊相關科系畢業的學生,未來會是什麼樣子?(Misc)
  • ../depcomp: line 571: exec: g++: not found
  • ./indexer: error while loading shared libraries: libmysqlclient.so.18: cannot open shared object fil
  • .dat文件写入byte类型数组_用Python从Abaqus导出txt、dat数据
  • .gitignore文件—git忽略文件
  • .NET CLR Hosting 简介
  • .net core 源码_ASP.NET Core之Identity源码学习
  • .Net Framework 4.x 程序到底运行在哪个 CLR 版本之上
  • .net web项目 调用webService
  • .NET 设计模式—简单工厂(Simple Factory Pattern)
  • .NET 中什么样的类是可使用 await 异步等待的?
  • .Net6 Api Swagger配置