C语言复习-进阶篇
摘要
复习一下C语言的进阶应用。
基础篇:C语言复习-基础篇
1.内存管理
在C语言中,程序内存可以分为代码段、数据段、堆区(stack)和栈区(heap)。
1.1 代码段
代码段也称为文本段,是存储程序代码的区域,通常是只读的,且不允许在程序运行时进行修改。在程序执行时,代码段中的指令被复制到处理器中执行。
1.2 数据段
数据段是存储静态变量和全局变量的区域,包括已初始化和未初始化的变量。已初始化的变量在编译时就被赋初值,而未初始化的变量在程序运行时会被初始化为0或空指针。数据段也是只读的,不允许在程序运行时进行修改。
1.3 堆区
堆区是程序运行时动态分配内存的区域,通过malloc、realloc等函数来申请和释放内存。堆区的大小是在程序运行时动态变化的,因此需要开发者自行管理内存的分配和释放,这种相信程序员的方法带来方便的同时也会带来风险。
堆区是由操作系统进行管理的,当申请内存时,操作系统会在堆区中分配一块连续的空间,并返回一个指向该空间的指针。释放内存时,操作系统将该空间标记为可用,并将其返回给堆区。
1.3.1 malloc函数
malloc函数用来动态地分配指定字节数的内存空间,如果分配成功,则返回一个指向分配内存的指针,否则返回空指针。malloc函数的原型如下:
1 | void *malloc(size_t size); |
其中,size_t是一个无符号整数类型,表示要分配的内存字节数。如果调用成功,则返回一个指向分配内存的指针;否则返回空指针。void类型不能定义变量但是可以定义指针,这是一个指针函数。
1.3.2 realloc函数
realloc函数用来重新分配内存空间的大小,可以用来扩大或缩小原有的内存空间。如果成功,则返回指向新分配内存的指针,否则返回空指针。realloc函数的原型如下:
1 | void *realloc(void *ptr, size_t size); |
其中,ptr是一个指向原有内存的指针,size是新的内存大小。如果调用成功,则返回一个指向新分配内存的指针;否则返回空指针。
1.3.3 free函数
free函数用来释放已经分配的内存空间。free函数的原型如下:
1 | void free(void *ptr); |
其中,ptr是指向要释放的内存空间的指针。使用free函数时需要注意,必须传递给它一个之前由malloc或realloc函数返回的指针,否则会出现内存泄漏的问题。
1.4 栈区
栈区是用于存储局部变量、函数参数和函数返回地址等信息的区域,是由编译器自动管理的。每当一个函数被调用时,该函数的参数和局部变量就会被存储在栈区中。栈区是一个先进后出的结构,当一个函数返回时,该函数的所有局部变量和参数就会被弹出栈区。栈区的大小通常是固定的,但可以通过编译器选项进行调整。
需要注意的是,堆区和栈区的内存都属于虚拟内存范畴,具体的内存分配和管理是由操作系统进行管理的。在使用堆区和栈区时,需要注意内存的使用情况和分配方式,以避免内存泄漏和野指针等问题。
2.函数指针
函数指针是指可以存储函数地址的变量,通过函数指针,可以动态地调用不同的函数,实现程序的灵活性和可扩展性。
2.1 普通函数指针
函数指针的定义:
1 | int (*fptr)(int, int); |
这个函数指针可以用来存储指向接受两个 int 类型参数并返回 int 类型结果的函数的地址。
2.2 回调函数
回调函数指被传递给另一个函数,并由另一个函数在需要时调用的函数。回调函数通常用于事件处理、错误处理和异步通信等任务中。
1 | void callback(int result)//定义回调函数 |
上述程序在主函数中,我们调用 do_something 函数,传递 add 函数的地址、两个整数和回调函数的地址。do_something 函数调用 add 函数,并将结果传递给回调函数。回调函数的好处在于,它可以将程序的控制权交给另一个函数,使程序更加灵活和可扩展。
2.3 函数指针作为成员
函数指针数组是指一个数组,其中的每个元素都是一个指向函数的指针。
1 | int (*fptr_arr[3])(int, int); |
这个函数指针数组可以用来存储指向三个不同的函数的地址,这些函数都接受两个 int 类型参数并返回 int 类型结果。
另一个做法是将函数指针放入结构体中。
1 | struct my_struct { |
my_struct 结构体包含一个指向 int 类型函数的指针 fptr。在主函数中,我们将 add 函数的地址存储到结构体的 fptr 成员中,然后使用结构体中的 fptr 成员调用 add 函数。
另外函数指针还可以应用于动态加载库和多态性,这部分内容可能更偏向于C++这里先不展开。
3.多文件程序
一个工程通常将程序代码分布在多个文件中,以便于代码的组织、维护和共享。C语言中通常包括的是头文件(.h 文件)和源文件(.c 文件)。
头文件定义了函数的原型和常量等信息,供其他文件引用。通常包含 #ifndef、#define 和 #endif 三个预处理指令来防止头文件被重复引用。
源文件包含函数的实现代码,多个源文件可以通过头文件来共享函数的定义,从而实现模块化的程序设计。下面给出头文件中通常包含的一些内容。
3.1 标准库函数
下面列举了一些C标准库中常用的函数:
- stdio.h:输入输出函数,包括文件操作函数、格式化输入输出函数、字符输入输出函数等。
- string.h:字符串操作函数,包括字符串复制函数、字符串连接函数、字符串比较函数等。
- math.h:数学函数,包括三角函数、指数函数、对数函数、幂函数等。
- time.h:日期和时间函数,包括获取当前时间函数、时间格式化函数、计时函数等。
- stdlib.h:常用的函数,包括动态内存分配函数、字符串转换函数、系统调用函数等。
- ctype.h:字符处理函数,包括字符分类函数、字符转换函数等。
- assert.h:调试宏,用于在程序中检查错误和异常情况。
- errno.h:错误码定义,用于表示函数执行过程中的错误信息。
3.2 宏定义
宏定义是C语言中常用的一种预处理指令,用于在编译时将某些代码段替换为指定的文本内容。标准语法为#define 宏名 替换文本
其中,宏名是用户自定义的宏名称,替换文本是指在编译时需要替换的文本内容。当程序中出现该宏名称时,编译器会将其替换为指定的文本内容,从而达到代码重用的效果。
1 | //例1 |
宏定义并非真正意义上的函数调用,而是将代码中的某些部分替换为指定的文本内容。因此,在使用宏定义时,需要特别注意其替换内容是否合法,以及是否会产生意料之外的副作用。
4.数据结构
这里只给出简约版提供复习用。
4.1 链表
1 | typedef struct Node { |
4.2 栈
1 | // 栈结构体定义 |
这个栈可以实现动态分配内存。
4.3 队
1 |
|
4.4 树
1 | // 定义二叉树节点结构体 |