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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void callback(int result)//定义回调函数
{
printf("Result: %d\n", result);
}

void do_something(int (*fptr)(int, int), int x, int y, void (*callback)(int))
{
int result = fptr(x, y);
callback(result);
}

int add(int x, int y)
{
return x + y;
}

int main()
{
do_something(add, 3, 4, callback);
return 0;
}

上述程序在主函数中,我们调用 do_something 函数,传递 add 函数的地址、两个整数和回调函数的地址。do_something 函数调用 add 函数,并将结果传递给回调函数。回调函数的好处在于,它可以将程序的控制权交给另一个函数,使程序更加灵活和可扩展。

2.3 函数指针作为成员

函数指针数组是指一个数组,其中的每个元素都是一个指向函数的指针。

1
int (*fptr_arr[3])(int, int);

这个函数指针数组可以用来存储指向三个不同的函数的地址,这些函数都接受两个 int 类型参数并返回 int 类型结果。

另一个做法是将函数指针放入结构体中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct my_struct {
int (*fptr)(int, int);
};

int add(int x, int y)
{
return x + y;
}

int main()
{
struct my_struct s;
s.fptr = add;
int result = s.fptr(3, 4);
printf("Result: %d\n", result);
return 0;
}

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
2
3
4
5
6
7
8
9
//例1
#define PI 3.14159
double radius = 10.0;
double area = PI * radius * radius;

//例2
#define MAX(x,y) ((x) > (y) ? (x) : (y))
int a = 10, b = 20;
int max = MAX(a, b);

宏定义并非真正意义上的函数调用,而是将代码中的某些部分替换为指定的文本内容。因此,在使用宏定义时,需要特别注意其替换内容是否合法,以及是否会产生意料之外的副作用。

4.数据结构

这里只给出简约版提供复习用。

4.1 链表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
typedef struct Node {
int data; // 节点数据
struct Node* next; // 指向下一个节点的指针
} Node;

typedef struct LinkedList {
Node* head; // 头结点指针
Node* tail; // 尾结点指针
int length; // 链表长度
} LinkedList;

// 初始化链表
void initList(LinkedList* list) {
list->head = NULL;
list->tail = NULL;
list->length = 0;
}

4.2 栈

1
2
3
4
5
6
7
8
9
10
11
12
13
// 栈结构体定义
typedef struct {
int *data; // 存储栈元素的数组
int top; // 栈顶指针
int size; // 栈的大小
} Stack;

// 栈的初始化函数
void init(Stack *s, int size) {
s->data = (int *) malloc(size * sizeof(int));
s->top = -1;
s->size = size;
}

这个栈可以实现动态分配内存。

4.3 队

1
2
3
4
5
6
7
8
9
10
11
#define MAX_QUEUE_SIZE 100

typedef struct {
int data[MAX_QUEUE_SIZE];
int front, rear;
} Queue;

void init(Queue *q) {
q->front = 0;
q->rear = -1;
}

4.4 树

1
2
3
4
5
6
// 定义二叉树节点结构体
typedef struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
} TreeNode;