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

本文主要是介绍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 中的宏实现接受可变数量参数的函数。

这篇关于14-特殊函数——静态函数、递归函数、函数指针、回调函数、内联函数、变参函数的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1050259

相关文章

hdu1171(母函数或多重背包)

题意:把物品分成两份,使得价值最接近 可以用背包,或者是母函数来解,母函数(1 + x^v+x^2v+.....+x^num*v)(1 + x^v+x^2v+.....+x^num*v)(1 + x^v+x^2v+.....+x^num*v) 其中指数为价值,每一项的数目为(该物品数+1)个 代码如下: #include<iostream>#include<algorithm>

业务中14个需要进行A/B测试的时刻[信息图]

在本指南中,我们将全面了解有关 A/B测试 的所有内容。 我们将介绍不同类型的A/B测试,如何有效地规划和启动测试,如何评估测试是否成功,您应该关注哪些指标,多年来我们发现的常见错误等等。 什么是A/B测试? A/B测试(有时称为“分割测试”)是一种实验类型,其中您创建两种或多种内容变体——如登录页面、电子邮件或广告——并将它们显示给不同的受众群体,以查看哪一种效果最好。 本质上,A/B测

BUUCTF(34)特殊的 BASE64

使用pycharm时,如果想把代码撤销到之前的状态可以用 Ctrl+z 如果不小心撤销多了,可以用 Ctrl+Shift+Z 还原, 别傻傻的重新敲了 BUUCTF在线评测 (buuoj.cn) 查看字符串,想到base64的变表 这里用的c++的标准程序库中的string,头文件是#include<string> 这是base64的加密函数 std::string

C++操作符重载实例(独立函数)

C++操作符重载实例,我们把坐标值CVector的加法进行重载,计算c3=c1+c2时,也就是计算x3=x1+x2,y3=y1+y2,今天我们以独立函数的方式重载操作符+(加号),以下是C++代码: c1802.cpp源代码: D:\YcjWork\CppTour>vim c1802.cpp #include <iostream>using namespace std;/*** 以独立函数

函数式编程思想

我们经常会用到各种各样的编程思想,例如面向过程、面向对象。不过笔者在该博客简单介绍一下函数式编程思想. 如果对函数式编程思想进行概括,就是f(x) = na(x) , y=uf(x)…至于其他的编程思想,可能是y=a(x)+b(x)+c(x)…,也有可能是y=f(x)=f(x)/a + f(x)/b+f(x)/c… 面向过程的指令式编程 面向过程,简单理解就是y=a(x)+b(x)+c(x)

Thymeleaf:生成静态文件及异常处理java.lang.NoClassDefFoundError: ognl/PropertyAccessor

我们需要引入包: <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>org.springframework</groupId><artifactId>sp

利用matlab bar函数绘制较为复杂的柱状图,并在图中进行适当标注

示例代码和结果如下:小疑问:如何自动选择合适的坐标位置对柱状图的数值大小进行标注?😂 clear; close all;x = 1:3;aa=[28.6321521955954 26.2453660695847 21.69102348512086.93747104431360 6.25442246899816 3.342835958564245.51365061796319 4.87

OpenCV结构分析与形状描述符(11)椭圆拟合函数fitEllipse()的使用

操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C++11 算法描述 围绕一组2D点拟合一个椭圆。 该函数计算出一个椭圆,该椭圆在最小二乘意义上最好地拟合一组2D点。它返回一个内切椭圆的旋转矩形。使用了由[90]描述的第一个算法。开发者应该注意,由于数据点靠近包含的 Mat 元素的边界,返回的椭圆/旋转矩形数据

Unity3D 运动之Move函数和translate

CharacterController.Move 移动 function Move (motion : Vector3) : CollisionFlags Description描述 A more complex move function taking absolute movement deltas. 一个更加复杂的运动函数,每次都绝对运动。 Attempts to

✨机器学习笔记(二)—— 线性回归、代价函数、梯度下降

1️⃣线性回归(linear regression) f w , b ( x ) = w x + b f_{w,b}(x) = wx + b fw,b​(x)=wx+b 🎈A linear regression model predicting house prices: 如图是机器学习通过监督学习运用线性回归模型来预测房价的例子,当房屋大小为1250 f e e t 2 feet^