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

C语言进阶—函数(static,递归,回调,指针,内联,变参,结构体尺寸)

目录

一 static函数

1. static变量

1.静态局部变量

2.静态全局变量

2. static函数

二 递归函数

三 指针函数&函数指针

1. 指针函数

2. 函数指针

四 回调函数

五 内联函数

1. 核心特性表

2. 优缺点分析表

3. 用场景建议

3.1 推荐使用场景

3.2 应避免场景

六 变参函数

1. 核心特性表

2. 优缺点对比表

七 结构体尺寸

1. 内存对齐规则表

2. 结构体布局优化表


一 static函数

功能分析
类型特点使用场景优点缺点内存特点
局部变量生命周期延长至程序结束,作用域保持局部计数器、状态保持、缓存重用保持状态,避免全局污染线程不安全存储在静态存储区
全局变量限制作用域仅在当前文件文件内共享数据避免命名冲突增加内存占用存储在静态存储区
函数限制函数作用域仅在当前文件模块化开发、隐藏实现细节提高封装性无法跨文件调用代码段存储
底层内存分析
内存区域存储内容生命周期访问特点
自动变量函数执行期间自动分配/释放
动态分配内存直到free()手动管理
静态区static/全局变量程序整个生命周期编译时分配
代码段函数/static函数程序整个生命周期只读

1. static变量

1.静态局部变量

1. 初始化在程序启动时执行一次
2. 存储在静态存储区(.data段)
3. 作用域仍为函数内局部
4. 线程不安全(多线程需加锁)

void func() {static int count = 0; 
}

2.静态全局变量

其他文件无法通过extern访问

// file1.c
static int secret = 42; // 其他文件无法通过extern访问// file2.c
extern int secret; // 链接错误:undefined reference

2. static函数

关键字static

  1.修饰变量为静态变量,修饰函数为静态函数

    static 函数:只能本文件使用,其他文件不可访问,static要加在返回类型前

    普通函数:普通函数默认都是可以跨文件可见的,也就是比如A.c 中有一个swap()函数,那么在b.c也可以访问得到

  2.static可以限定变量或者函数为静态存储,static限定的变量或函数不会和同意程序中的其他文件名相冲突

    static 变量,一开始就会有内存,不会随函数的出入栈,分配空间

优点:

  1.静态函数会被自动分配在一个一直使用的存储区,知道程序结束内存消失,避免调用函数时压栈出栈  即全局

  2.其文件可以定义相同的函数,不会冲突

  3.静态函数不能被其他文件调用,对本代码可用

#include <stdio.h>static void function1()
{printf("niho");
}int main()
{function1();return 0;
}

二 递归函数

递归函数: 即函数调用自己

递归解决的问题:

  1.阶乘

     n!=(n-1)!*n=(n-2)!*(n-1)*n.......asm

     0 的阶乘就是0

  2.幂运算

     同底数的幂的乘法

  3.字符反转

     123-321

递归的使用:

  1.必须要有跳出条件,否则无限循环

#include <stdio.h>
#include <stdlib.h>int factorial(int n);
int main(int argc, char const *argv[])
{printf("输入一个不小于0的数值");int b = 0;scanf("%d", &b);int num = factorial(b);printf("%d的递归结构为:num=%d", b, num);return 0;
}
int factorial(int n)
{if (n < 0){printf("n不能小于0");exit(EXIT_FAILURE);}else if (n == 0){return 1;}else{return factorial(n - 1) * n;}
}

三 指针函数&函数指针

概念对比表

特性指针函数(返回指针的函数)函数指针(指向函数的指针)
定义方式int* func(int a)int (*func_ptr)(int)
本质函数的返回类型是指针变量类型为函数指针
内存存储函数代码存储在代码段指针变量存储在栈/静态区,指向代码段地址
主要用途返回动态内存、数组首地址、静态变量地址实现回调函数、策略模式、动态函数调用
优点1. 灵活返回数据结构
2. 支持动态内存管理
1. 提高代码灵活性
2. 实现多态行为
缺点1. 可能返回无效指针
2. 需手动管理内存
1. 语法复杂
2. 类型安全需自行保证
典型应用场景1. 工厂模式创建对象
2. 返回数组首地址
1. 事件处理器
2. 排序算法比较函数

内存结构分析

元素存储区域生命周期访问方式
指针函数代码段程序运行期间函数调用
函数指针变量栈/静态区取决于作用域指针解引用
动态分配内存直到free()通过指针访

使用建议

  1. 指针函数

    • 优先返回动态分配内存(需配套提供释放函数)

    • 避免返回局部变量地址

    • 可返回静态变量地址(但需注意线程安全)

  2. 函数指针

    • 使用typedef定义易读的类型别名

    • 始终检查指针是否为NULL后再调用

    • 适用于插件架构、策略模式等场景

1. 指针函数

指针函数 :一个返回指针的函数,就被称为指针函数

定义全局变量,多个函数需要用到某一个变量的指针,就不能返回局部变量的指针,栈空间用完是销毁的

#include <stdio.h>
int i=9527;//指针函数
int* get_point(int a); int main(int argc, char const *argv[])
{   printf("main  &i=%p\n",&i);//调用int *p=get_point(666);printf("  p=%p\n",p);return 0;
}//指针函数
int* get_point(int a){printf("get_point.....&a=%p,&i=%p\n",&a,&i);//全局变量的指针return &i;
}

2. 函数指针

函数指针:函数指针是一个变量,他存储了一个函数的地址(函数也是要放到内存内,也有地址)。

  指向一个函数的指针,称为函数指针

声明函数指针语言:

  int (*p)(int n);

  指向函数的返回值和参数列表必须一致

#include <stdio.h>int my_function(int, double);
int you_function(int, double);int my_function(int i, double n)
{printf("my_function:%d,and%lf\n", i, n);
}
int you_function(int i, double n)
{printf("you_function:%d,and%lf\n", i, n);
}
int main(int argc, char const *argv[])
{// 1.普通调用函数my_function(1, 1.1);// 2.调用指针函数调用you的值,与函数地址int (*you)(int, double);you = you_function;int you1 = you(2, 2.2);printf("指针调用的值为%d\n", you);printf("指针调用函数地址为%p\n", you);printf("指针调用函数地址为%p\n", you_function); // 此处的函数名和数组名一样,可以当作内存地址。// 3.指针函数的返回值类类型,必须与参数列表一致return 0;
}

四 回调函数

核心概念表

特性描述底层原理
定义方式通过函数指针传递函数作为参数函数指针存储代码段地址
内存存储函数代码存储在代码段,指针变量存储在栈/静态区通过指针间接寻址执行
调用时机由被调用函数在特定条件/事件触发时执行通过函数指针跳转执行
参数传递支持普通参数和函数指针参数遵循C函数调用约定(参数压栈)
生命周期回调函数需在调用时保持有效需避免使用已释放的栈内存指针

特性对比表

维度普通函数调用回调函数调用
调用控制权直接由调用者控制由被调用方在特定时机触发
耦合度高(直接依赖具体实现)低(仅依赖接口)
灵活性固定实现运行时动态绑定
典型应用顺序执行逻辑事件处理、算法策略、异步通知
内存开销无额外开销需存储函数指针(4/8字节)
调试难度简单(线性执行)复杂(执行路径不可见)
#include <stdio.h>
#include <string.h>
/*
回调函数:会点函数是一种特殊函数,他作为参数传递给另一个函数,并在被调用函数执行完毕后被调用回调:旨被传入到另一个函数异步编程:指代码执行时不会阻塞整个程序运行的方式;事件驱动:指程序的执行是由外部时间触发而不是顺序执行的方式;c语言实现回调函数的方法:回调函数可以通过指针来实现*/// 回调函数,接受数值,做出回应。
void callback_function(int num);// 发送数值,请求回应。
void weixin_function(char *str, void (*p)(int));// 回调函数,接受数值,做出回应
void callback_function(int num)
{if (num > 10){printf("回应收到,做的不错\n");}else{printf("回应收到,做的很坏了\n");}
}
// 发送数值,请求回应,然后被main执行。void (*p)(int)即为函数指针
void weixin_function(char *str, void (*p)(int))
{int length = strlen(str);printf("数值已经发送,请求回应中......\n");// 事件驱动:指程序的执行是由外部事件触发而不是顺序执行的方式;// 并在被调用函数执行完毕后被调用p(length);
}
int main(int argc, char const *argv[])
{weixin_function("aaaaaaaaaa", callback_function);return 0;
}

五 内联函数

1. 核心特性表

特性内联函数(inline)普通函数
定义方式inline 返回类型 函数名(参数)返回类型 函数名(参数)
编译行为代码直接嵌入调用处生成独立函数体
执行效率高(无调用开销)低(有调用/返回开销)
代码体积可能增大(多位置展开)较小(单一副本)
调试难度较难(无明确调用栈)容易(完整调用栈)
适用场景简单频繁调用的小函数复杂或递归函数
内存特点代码段多位置复制代码段单一存储
跨文件可见性需在头文件定义通过声明可见

2. 优缺点分析表

优点缺点
1. 消除函数调用开销1. 增加代码体积(代码膨胀)
2. 避免栈操作(push/pop)2. 调试困难(无调用信息)
3. 支持编译器优化3. 不适合复杂函数
4. 减少指令缓存未命中4. 可能被编译器忽略

3. 用场景建议

3.1 推荐使用场景
  1. 频繁调用的小函数(如数学运算)

  2. 硬件寄存器操作(如嵌入式开发)

  3. 关键性能路径代码(如游戏引擎)

  4. 替代宏函数(类型安全的宏)

3.2 应避免场景
  1. 递归函数

  2. 包含循环的大函数

  3. 虚函数/多态函数

  4. 导出库函数

#include <stdio.h>/*
内联函数函数频繁被调用,不断出入栈,会导致栈空间不断被消耗,慢语法:关键字inline必须和函数定义放在一起才能使用成为内联函数,仅将inline放在函数声名为何不所有的函数都使用内联?内联是以代码“膨胀”(赋值代码内容),仅仅省略了函数调用的开销从而提高代码执行效率如果执行函数体内代码的时间相比于函数调用的开下较大,那么效率收货会很少不适用于内联1.如果函数体内的代码较长,使用内联将导致内存消耗代价较高2.如果函数体内出现循环,那么执行函数体内的代码时间比函数调用的开销大
一个好的编译器会将根据函数的定义体,自动地取消不值得的内联(这进一步说了inline不应该出现在函数的声明中);*/// 内联函数在声名的时候,必须要加入static关键字
static inline void in_func();
static inline void in_func()
{printf("内联函数被调用,每一次调用,都要拷贝函数体的内容,消耗空间\n");
}int main(int argc, char const *argv[])
{in_func();// 内联函数调用,拷贝函数体,上面调用和下面的效果等同printf("内联函数被调用,每一次调用,都要拷贝函数体的内容,消耗空间\n");return 0;
}

六 变参函数

1. 核心特性表

特性变参函数(Variable Arguments)普通函数
定义方式使用省略号...声明参数列表固定参数列表
参数数量调用时参数数量可变参数数量固定
参数类型支持不同类型参数混合参数类型固定
内存访问通过指针遍历栈内存编译器自动处理参数访问
类型安全无(需手动校验类型)有(编译器检查)
适用场景格式化输出、日志系统、数学运算常规函数调用
内存特点参数连续存储在调用栈参数按调用约定存储
实现复杂度高(需手动处理参数)低(编译器自动处理)
地址高地址
+-----------------+
| 返回地址         |
| 参数n           | <-- va_arg(args, type)访问位置
| ...             |
| 参数2           |
| 参数1           |
| count参数       | <-- va_start后的args初始指向位置
+-----------------+
低地址

2. 优缺点对比表

优点缺点
1. 参数数量灵活1. 无类型安全检查
2. 支持混合类型参数2. 需要手动管理参数
3. 实现通用接口3. 调试困难(无参数信息)
4. 兼容C标准库函数4. 参数读取顺序敏感
#include <stdio.h>
#include <stdarg.h> //可变参数/*
可变参数:指的是参数的个数以及类型可以根据实际应用有所变化的c语言参数的入栈的顺序是从右往左进行的。
*/// 求传入的参数之和
double sum(int a, ...);
double sum(int a, ...)
{// 声名一个va_list list;// 2.读取参数列表,并给参数列表赋值a是 last最后一个读取到的va_start(list, a);// 3.取出参数double sum = 0;sum = sum + va_arg(list, int);sum = sum + va_arg(list, int);for (int i = 0; i < a - 2; i++){sum = sum + va_arg(list, double);}// 销毁释放listva_end(list);return sum;
}
int main(int argc, char const *argv[])
{printf("ddd\n");printf("%d\n", 4444);printf("%d,%lf\n", 4444, 2.22);/*参数列表的进入顺序是从右往左void fun(int a,)*/// 传入两个参数printf("%lf\n", sum(3, 2, 2, 2.2, 2.2, 2.2));return 0;
}

七 结构体尺寸

1. 内存对齐规则表

数据类型典型尺寸(字节)对齐要求(字节)常见平台
char11所有
short22x86, ARM
int4432/64位系统
float44多数平台
double88x86-64, ARM64
指针4/84/832位/64位系统
结构体不定最大成员对齐值依赖成员类型

2. 结构体布局优化表

策略优点缺点
降序排列成员(大→小)最小化填充空间可能影响代码可读性
使用位域精确控制位级存储增加访问复杂度
#pragma pack(1)完全紧凑布局降低访问性能
人工填充明确控制内存布局需要手动计算
联合体嵌套共享内存空间节省尺寸需要严格管理使用状态
结构体的大小收到存储变量地址对齐规则和编译器优化策略影响:
 1.偏移量从0开始
 2.整体结构体按照最大成员的对齐要求来对齐(通常是基本类型成员)
 3.每个成员按照其自身类型的对齐规则对齐(通常是其类型大小)
 4.结构体的整体大小必须是最大对齐数的整数倍(对齐规则)
 5.编译器为了对齐,可能在成员之间插入填充字节(padiing偏移、补齐)
 6.结构体末尾可能也会补(padiing偏移、补齐)
#include <stdio.h>
#include <stddef.h> //查看偏移量/*
结构体的大小收到存储变量地址对齐规则和编译器优化策略影响:1.偏移量从0开始2.整体结构体按照最大成员的对齐要求来对齐(通常是基本类型成员)3.每个成员按照其自身类型的对齐规则对齐(通常是其类型大小)4.结构体的整体大小必须是最大对齐数的整数倍(对齐规则)5.编译器为了对齐,可能在成员之间插入填充字节(padiing偏移、补齐)6.结构体末尾可能也会补(padiing偏移、补齐)*/
// 1.无嵌套的结构体
struct student
{int id;      // 4字节long age;    // 8字节int ranking; // 4字节char gender; // 1字节
};// 2.含有数组的结构体大小
struct student2
{// 如果下一个成员是数组,则不填充额外的字节int id;          // 4字节char name[5];    // 5字节char address[5]; // 5字节long age;        // 8字节int ranking;     // 4字节char gender;     // 1字节
};// 3.嵌套结构体的大小
struct student3
{int id; // 4字节struct{long a; // 8字节char c; // 1字节} my;int age;     // 4字节int ranking; // 4字节char gender; // 1字节
};int main(int argc, char const *argv[])
{printf("struct student 结构体大小:%lu 字节\n", sizeof(struct student));            // 24printf("struct student  id的偏移量=%lu\n", offsetof(struct student, id));           // 0printf("struct student  age的偏移量=%lu\n", offsetof(struct student, age));         // 8printf("struct student  ranking的偏移量=%lu\n", offsetof(struct student, ranking)); // 16printf("struct student  gender的偏移量=%lu\n", offsetof(struct student, gender));   // 20printf("struct student2 结构体大小:%lu 字节\n", sizeof(struct student2));            // 32printf("struct student2 id的偏移量=%lu\n", offsetof(struct student2, id));            // 0printf("struct student2 name的偏移量=%lu\n", offsetof(struct student2, name));        // 4printf("struct student2  address的偏移量=%lu\n", offsetof(struct student2, address)); // 9printf("struct student2  age的偏移量=%lu\n", offsetof(struct student2, age));         // 16printf("struct student2  ranking的偏移量=%lu\n", offsetof(struct student2, ranking)); // 24printf("struct student2  gender的偏移量=%lu\n", offsetof(struct student2, gender));   // 28printf("struct student3 结构体大小:%lu 字节\n", sizeof(struct student3));            // 40printf("struct student3 id的偏移量=%lu\n", offsetof(struct student3, id));            // 0printf("struct student3  my的偏移量=%lu\n", offsetof(struct student3, my));           // 8printf("struct student3  age的偏移量=%lu\n", offsetof(struct student3, age));         // 24printf("struct student3  ranking的偏移量=%lu\n", offsetof(struct student3, ranking)); // 28printf("struct student3  gender的偏移量=%lu\n", offsetof(struct student3, gender));   // 32return 0;
}

 2. 总结

即,想要求结构体的尺寸,首先要知道其最大字节的type,依据这个,来算出整个结构体的字节大小,以64位处理器来算,不满8的字节,系统自己填充为8,然后继续下个类型,数组除外,数组按照顺序进行排列。

相关文章:

  • DID在元宇宙的应用爆发:数字身份资产化与跨平台迁移——解析Decentraland等项目的虚拟身份全链路实现
  • 25G 80km双纤BIDI光模块:远距传输的创新标杆
  • 蓝桥杯-通电(最小生成树java)
  • 盛最多水的容器
  • UE5 MetaHuman眼睛变黑
  • 软件设计师-软考知识复习(3)
  • 【强化学习】什么是强化学习?2025
  • 解决 Exception in thread “main“ java.lang.NoClassDefFoundError
  • 【java】程序设计基础 八股文版
  • 深入理解 Web 架构:从基础到实践
  • 0506--01-DA
  • tinyrenderer笔记(Phong光照模型)
  • QML ProgressBar控件详解
  • C++高性能内存池
  • 逻辑越权--登录和支付数据篡改
  • DeepSeek智能时空数据分析(七):4326和3857两种坐标系有什么区别?各自用途是什么?
  • 【Python面向对象编程】类与对象的深度探索指南
  • USB学习【2】通讯的基础-反向不归零编码
  • Linux 更改内存交换 swap 为 zram 压缩,减小磁盘写入
  • OrcaFex11.5
  • 60岁济南石化设计院党总支书记、应急管理专家李有臣病逝
  • 巴国家安全委员会授权军方自主决定对印反击措施
  • 丁薛祥在学习《习近平经济文选》第一卷专题研讨班上强调:深入学习贯彻习近平经济思想,加强党中央对经济工作的集中统一领导
  • “穿越看洪武”,明太祖及其皇后像台北故宫博物院南园展出
  • 侯麦:从莫扎特到贝多芬
  • “95后”楼威任浙江师范大学教授,研究方向为医学人工智能