
C函数高级
栈:存储局部变量、参数、函数返回地址等。 堆:也称动态内存分配,由程序员用malloc等函数向系统申请任意指定大小的内存,并由程序员自己调用free等函数来释放内存。 数据段:包括静态存储区和常量区。 代码段(正文段):存储程序的函数代码。 文件作用域:变量从它定义的位置开始直到这个程序文件的末尾都有效。 函数作用域:变量仅在一个函数中都有效,是一个局部变量。 代码块作用域:变量位于一对花括号{}中(函数体或语句块),从它定义的位置开始到右}括号之间有效。 函数原型作用域:变量出现在函数原型中,这个函数原型只是一个声明而不是定义(没有函数体),那么变量从声明的位置开始到末尾之间有效。
静态存储区(属于数据段)上分配:针对全局变量和static变量,程序在编译时编译器就已对其分配好内存地址,这些变量的值在程序运行期间一直存在,直到程序运行结束才会被释放。 栈上分配:针对局部变量(包括参数),在每次调用函数时,系统会动态为局部变量在栈中分配内存空间,在函数调用后这些局部变量占有的内存空间会被自动释放。 堆上分配:或称动态内存分配,由程序员主动使用malloc,free等函数向系统申请指定大小内存或释放,生命周期由程序员决定。 静态存储方式:程序在编译时分配的固定存储空间(静态存储区)的方式,程序执行完毕就释放。全局变量和静态变量属于静态存储方式,存放在静态存储区中。 动态存储方式:程序运行期间根据需要进行动态的分配存储空间(动态存储区)的方式。 包括函数的形参、自动变量(带auto或者不带auto的局部变量)、函数调用时的现场保护和返回地址等。
External:存储类型为extern,作用域可以在当前文件和其他文件使用。 Internal:存储类型为static,仅限于当前文件或函数使用。 None:存储类型为auto,仅限于函数使用。 static可以修改链接属性,存储类型,作用域和生命周期。 内部函数:一个函数只能被本文件中的其他函数调用。 内部函数的声明:static 类型说明符 函数名(型参列表); 内部函数也称为静态函数,仅限于本文件内调用。 外部函数:函数声明时在函数前面使用extern修饰称为外部函数,省略extern默认就是外部函数。 在一个文件内声明外部变量:在外部变量定义之前若函数想使用该变量,则应在使用之前用extern对该变量作外部变量的声明,那么就可以从使用extern声明处起使用该外部变量。 在多个文件的程序中声明外部变量:一个c程序可以由多个源文件组成,在一个文件中通过extern可以使用另外一個文件中已经定义的外部变量。 若c程序中的两个源文件都要用到同一个外部变量,那就不能分别在两个文件中各自定义这个外部变量,否则在程序链接时会出现重复定义的错误。正确做法:在一个文件中用extern作为外部变量声明,这样此文件就可以使用该外部变量。 存在限制条件,当符合限制条件递归不再继续。 每次递归调用后越来越接近该限制条件。 定义一个回调函数(如get_grade函数)。 提供函数调用的另一方(如process函数)在初始化的时候,将回调函数的函数指针(如get_grade)传递给调用者(如process(score, get_grade))。 当特定的事件或条件发生时,调用者使用函数指针调用所指向的回调函数对事件进行处理。 通过使用switch语句可以获得类似的效果,但是使用函数指针数组可以有更大的灵活性,因为数组元素可以在程序运行时发生改变。 可应用在命令菜单的选择处理。
发布日期:2021-05-14 23:42:23
浏览次数:25
分类:精选文章
本文共 7216 字,大约阅读时间需要 24 分钟。
C语言高级函数指南
一、函数返回
函数返回值
函数的返回值是指函数被调用后,执行函数体中的程序段所取得的并返回给主调函数的值。
return语法
return 表达式;
或者 return (表达式);return语句的实现机制
执行return语句时,先计算return后面的表达式的值,再将值返回给主调函数。
函数中允许有多个return语句,但每次调用只能有一个return语句被执行,因此只能返回一个函数值。实际上,函数的右花括号具有return功能。 返回值的数据类型应该与函数原型中返回值的数据类型匹配,若两者不一致,则以函数原型中的返回值类型为准,自动进行类型转换,但可能会出现精度损失。 当遇到return语句时,函数执行将终止,程序控制流将立即返回调用函数。 不返回函数值的函数,可以明确定义为“空类型”,类型说明符为“void”。若要终止函数执行可用“return;”语句。二、Linux内存管理机制
冯诺依曼体系结构
五个设备:控制器、运算器、存储器、输入设备、输出设备。
三条总线:控制总线、地址总线、数据总线。Linux内存
地址从高到低的四种虚拟内存:
内存的管理
进程隔离:保护独立的进程,防止互相干涉数据和存储空间。
进程中使用的地址是虚拟地址,分配有4个G。 段页式内存管理:进程在虚拟内存中分为代码段、数据段、堆栈段。 进程在段中由许多固定大小的块组成,这些块称为页。 虚拟地址由段号、页号、页中偏移量构成。 虚拟地址和内存中物理地址的动态映射。 按需调页,消除了进程全部载入内存中。称为LPU。三、变量的存储类型和生命周期
作用域的分类
变量的存储类型
存储类型 | 变量 | 说明 |
---|---|---|
auto | 自动变量 | 局部变量在缺省存储类型的情况下归为自动变量,默认不会初始化,会产生随机值。 |
static | 静态变量 | 在程序执行时存在,并且只要整个程序在运行,就可以继续访问该变量,默认会初始化为0且仅初始化一次。可以是全局也可以是局部变量。 |
extern | 外部变量 | 作用域是整个程序,包含该程序的各个文件。生存期非常长,它在该程序运行结束后,才释放内存,默认会初始化并仅初始化一次。 |
register | 寄存器变量 | 一般变量存储在内存中,而寄存器变量存放在CPU的寄存器中。只能是局部变量,不能是全局变量并且不能加static修饰,默认不会初始化。会占用CPU,所以不推荐使用。 |
变量的内存分配方式
变量的存储方式
变量的作用域和生命周期
变量 | 作用域 | 生命周期 |
---|---|---|
自动变量 | 一个函数内有效 | 一个函数,函数调用结束后被释放 |
寄存器变量 | 同自动变量 | 同自动变量 |
静态局部变量 | 一个函数内有效 | 整个程序的生命周期,程序运行结束才会被释放 |
静态全局变量 | 一个源文件 | 同静态局部变量 |
全局变量(外部变量) | 一个源文件范围,也可以通过extern声明扩展到程序中的多个源文件 | 同静态局部变量 |
链接属性
内部函数和外部函数
四、extern
extern的使用
extern使用注意点
- 外部变量值的修改对所有使用该变量的文件都有影响。
- 编译器遇到extern时,先从本文件中找到外部变量的定义,若找到就在本文件中扩展作用域。若没有找到就在链接时从其他文件中找到外部变量定义,若找到就将作用域扩展到本文件,若找不到按出错处理。
五、函数栈
主调函数和被调函数
- 在函数调用时,被调函数名后面必须有括号。
- 一个函数只能返回一个值。
函数栈的调度流程
- 函数的调用过程实际上是对栈空间的操作过程,调用函数是使用对栈空间来保存信息,过程如下:
- 建立被调用函数的栈空间
- 保护调用函数的运行状态和返回地址
- 传递函数的实参给形参
- 执行被调用函数的函数体内语句
- 将控制权或返回值转交给调用函数
- 函数调用完毕,释放被调用函数的栈空间。 不同函数的栈区互相独立,函数间通过参数传递、返回值等方式进行数据传递。
六、递归调用
递归调用概念
- 一个函数在它的函数体内调用它自身称为递归调用,这种函数称为递归函数。
- C语言中支持运行时通过堆栈支持递归函数的实现。
- 在递归调用中,主调函数又是被调函数。执行递归函数将反复调用其自身,每调一次就进入新的一层。调用太深会造成溢出。
- 尾部递归最好采用迭代方式以提高效率。
递归所需特性
递归案例
//将数值转换成字符void bin2asc(int value) { int temp; temp = value / 10; if (temp != 0) bin2asc(temp); //递归调用 putchar(value % 10 + '0');}int main(void) { bin2asc(4267); //返回"4268" printf("\n"); return 0;}
递归计算斐波那契数列
long fibonacci(int n) { if (n < 2) return 1; return fibonacci(n - 1) + fibonacci(n - 2);}long factorial(int n) { if (n <= 0) return 1; else return n * factorial(n - 1);}
七、函数指针与指针函数
指针函数
- 指针函数是指带指针的函数,其本质是一个函数。
- 指针函数返回类型是某一类型的指针。
- 指针函数的定义:返回类型标识符 *函数名(形参列表){函数体}
指针函数案例
int sum = 0; //也可以作为全局变量int *fun(int n) { static int sum = 0; //局部静态变量,将sum存储在数据段,所以在函数清空后还得以保存 int *p = ∑ int i; for (i = 1; i <= n; i++) sum += i; return p; //返回的指针指向的是局部变量,函数释放后局部变量会清空,不推荐使用}int main() { int *p_sum = fun(100); //指针赋值操作 sleep(1); //函数调用完毕,这1秒内fun()函数栈空间被清空,使得其中的值都变随机值。但是若使用static变量则避免了随机值。 printf("sum:%d\n", *p_sum); return 0;}
函数指针
- C语言在编译时,每一个函数都有一个入口地址,该入口地址就是函数指针。
- 通过函数指针可以调用它所指向的函数。
- 函数指针也称函数指针变量。
- 函数指针的定义和初始化: 返回类型说明符 (*函数指针变量名)(参数列表); 函数指针初始化:函数指针变量 = 函数名;
函数指针调用方法
int max(int x, int y); //函数声明int (*p)(int, int); //函数指针p = max; //函数名即为函数入口地址result = p(a, b); //函数指针调用所指函数//或者result = (*p)(a, b);
回调函数
- 回调函数就是一个通过函数指针调用的函数。
- 把函数指针作为参数传递给另一个函数,当这个指针被用来调用其他所指向的函数时,我们就说这是回调函数。
- 回调函数不是由该函数的实现方(如main函数)直接调用,而是在特定的时间或条件发生时由另外的一方(如下process函数)调用的,用于对该事件或条件进行响应。
回调函数实现流程
回调函数优点
- 实现了代码的通用性。可以方便地调用各种算法。
回调函数案例
/*文件:callback.h*/char process(int score, char (*p)(int));/*文件:callback.c*/#include "callback.h"char process(int score, char (*p)(int)) { char result = p(score); return result;}/*文件:callback_test.c*/void get_grade(int score) {}int main(void) { int score; printf(); scanf(); printf("result:%c\n", process(score, get_grade));}
回调函数排序算法
/*文件:callback_sort.h*/extern void data_sort(int data[], int n, void (*p)(int*, int), void (*out)(int*, int));/*文件:callback_sort.c*/void data_sort(int data[], int n, void (*p)(int*, int), void (*out)(int*, int)) { p(data, n); out(data, n);}/*文件:callback_sort_test.c*/#include#include "callback_sort.h"void bubble_sort(int *p, int n) { int i, j; for (i = 0; i < n - 1; i++) { for (j = n - 1; j > i; j--) { if (*p + j < *p + j - 1) { int temp; temp = *p + j; *p + j = *p + j - 1; *p + j - 1 = temp; } } }}void selection_sort(int *p, int n) { int i, j; for (i = 0; i < n - 1; i++) { // 选择最小的一个数到排好序的数列前面 }}
八、可变参数列表
可变参数列表的概念
- 参数个数可变的函数,如scanf()其函数原型: int scanf(const char* format, ...); 除了参数format固定外,后面跟的参数个数和类型是可变的(用三个点"..."做参数占位符)。
- “…”称为可变参数列表,可以用来接受个数和类型不确定的实参。
可变参数列表的实现
通过三个宏(va_start, va_arg, va_end)和一个类型(va_list)实现,它们都定义在头文件stdarg.h中。
可变参数列表的使用
宏va_start的原型:void va_start(va_list ap, paramN)
参数:va_list:存储参数的类型;ap:可变参数列表地址;paramN:确定的参数。 功能:初始化可变参数列表。宏va_arg的原型:type va_arg(va_list ap, type)
功能:返回下一个参数的值。宏va_end的原型:type va_end(va_list ap, type)
功能:关闭初始化列表(将可变参数列表清空)。使用方式:
- 用va_start初始化可变参数列表,用va_arg逐个获取参数的值,最后用va_end将可变参数列表清空。
可变参数列表的实现方式
#include#include float average(int n_values, ...) { va_list vary_arg; int count; float sum = 0; va_start(vary_arg, n_values); for (count = 0; count < n_values; count++) { float value = va_arg(vary_arg, float); sum += value; } va_end(vary_arg); return sum / n_values;}
使用可变参数列表实现平均值计算
#include#include float average2(int n_values, ...) { int *p = &n_values; float sum = 0.0f; int i; for (i = 1; i <= n_values; i++) { float value = *p++; sum += value; } return sum / n_values;}
九、函数指针数组
函数指针数组概念
- 数组元素是函数指针的数组称为函数指针数组,也称为__转移表__。
函数指针数组的定义和初始化
- 返回类型说明符(*函数指针数组名[])(参数列表)={函数指针/函数名1,……,函数指针/函数名n}
函数指针数组的使用
- 函数指针数组名;
- 或者(*函数指针数组名[下标])(实参列表);
函数指针数组的应用场景
函数指针数组的示例
int (*p[])(int); //这是一个函数指针数组int (*p)[4]; //这是一个数组指针,或称行指针int (*p)(int); //这是一个函数指针int *p(int); //这是一个指针函数声明
发表评论
最新留言
不错!
[***.144.177.141]2025年04月09日 19时01分46秒
关于作者

喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!
推荐文章
pwn题shellcode收集
2019-03-12
python中的序列化
2019-03-12
django中使用celery执行异步任务实现
2019-03-12
centos7 安装 mongodb3.6.3
2019-03-12
java有道翻译
2019-03-12
lora技术在无线抄表行业应用
2019-03-12
msfvenom的使用&免杀&外网渗透
2019-03-12
HTTP/2 协议详解
2019-03-12
grafana改用https登录
2019-03-12
使用MySQLTuner-perl对MySQL进行优化
2019-03-12
2018年3月最新的Ubuntu 16.04.4漏洞提权代码
2019-03-12
异或交换两个数的值
2019-03-12
使用python绘出常见函数
2019-03-12
Golang AES加密
2019-03-12
Puppet的一些奇技淫巧
2019-03-12
foreman源NO_PUBKEY 6F8600B9563278F6
2019-03-12
亚马逊aws文档语法错误
2019-03-12
什么是5G?居然有人用漫画把它讲得如此接地气!
2019-03-12
Spring cloud --分布式配置中心组件Spring Cloud Config
2019-03-12
UE4接入Android第三方库2——通过JIN与GameActivity通信
2019-03-12