Update Readme.md

This commit is contained in:
Natsume1710
2025-06-16 21:54:22 +08:00
committed by GitHub
parent e1810c0197
commit d82cd4ecb0
+171 -19
View File
@@ -49,31 +49,183 @@ const int MAX_SIZE = 100;
---
### ✅ 栈 和 堆(内存管理)
- **栈 (stack)**:自动分配内存,函数退出即释放。
- 示例:局部变量、函数参数等
- **堆 (heap)**:使用 `malloc` / `free` 手动分配和释放
#### 栈 (stack):自动分配内存,函数退出即释放。
1. 核心特性
- 自动分配与释放:由编译器自动管理,函数调用时分配栈帧,函数返回时自动释放。
- 后进先出(LIFO):类似一摞盘子,最后放入的最先取出。
- 高速访问:栈内存访问效率高(通常通过寄存器直接操作)。
- 空间有限:栈空间通常较小(如 Linux 默认 8MB),过大的局部变量可能导致栈溢出。
2. 存储内容
- 局部变量:函数内部定义的变量。
- 返回地址:函数执行完毕后返回的位置。
- 函数参数:调用函数时传递的参数。
- 寄存器值:保存调用前的寄存器状态,以便恢复。
3. 工作原理
- 栈指针(ESP):指向当前栈顶的内存地址。
- 栈帧(Stack Frame):每个函数调用在栈上分配的独立空间,包含局部变量和参数。
- 示例代码:
```c
// 栈内存示例
void func() {
int a = 10; // a 在栈中
}
// 堆内存示例
int *p = (int *)malloc(sizeof(int) * 10);
if (p) {
p[0] = 100;
free(p);
void func(int a, int b) {
int sum = a + b; // sum存储在栈上
// ...
} // 函数返回时,sum和参数a、b自动释放
```
4. 优缺点
- 优点:无需手动管理内存,速度快,不会内存泄漏。
- 缺点:生命周期固定(函数结束即释放),空间有限。
#### 堆 (heap):使用 `malloc` / `free` 手动分配和释放
1. 核心特性
- 手动分配与释放:使用malloc/calloc/realloc分配,free释放。
- 动态生命周期:内存块的生命周期由程序员控制,可跨函数使用。
- 碎片化问题:频繁分配和释放可能导致内存碎片,降低空间利用率。
- 慢速访问:需通过指针间接访问,效率低于栈。
2. 存储内容
- 动态分配的对象:如malloc返回的内存块。
- 大型数据结构:如数组、链表、树等需要动态调整大小的结构。
- 跨函数数据:需要在函数调用结束后继续存在的数据。
3. 工作原理
- 内存管理器:操作系统提供的堆管理器负责分配和回收内存。
- 空闲链表:堆管理器维护空闲内存块列表,分配时查找合适大小的块。
- 示例代码:
```c
void createArray() {
int* arr = (int*)malloc(10 * sizeof(int)); // 从堆分配内存
if (arr != NULL) {
arr[0] = 100; // 使用堆内存
// ...
}
free(arr); // 手动释放内存
}
```
4. 常见函数
- malloc(size_t size):分配指定字节的内存,不初始化。
- calloc(size_t num, size_t size):分配内存并初始化为 0。
- realloc(void* ptr, size_t new_size):调整已分配内存的大小。
- free(void* ptr):释放内存,必须与malloc配对使用。
**栈 vs 堆的对比**
| 特性 | 栈(Stack) | 堆(Heap) |
|--------------|--------------------------------------|---------------------------------------------|
| 分配方式 | 自动(由编译器管理) | 手动(如 `malloc`/`free` 或 `new`/`delete`)|
| 生命周期 | 函数调用期间自动创建和销毁 | 程序员控制,需手动释放 |
| 内存空间 | 连续、有限(通常几 MB) | 不连续、较大(受限于物理内存) |
| 访问速度 | 快(通过寄存器快速访问) | 慢(通过指针间接访问) |
| 内存碎片 | 不存在(先进后出结构) | 可能产生(频繁分配和释放) |
| 使用场景 | 局部变量、函数调用栈帧 | 动态数据结构、跨函数共享数据 |
### ✅ 指针 / 指针数组 / 二级指针
- **指针**:保存变量的地址
- **指针数组**:数组中每个元素是一个指针
- **二级指针**:指向指针的指针
#### 指针:保存变量的地址
1. 核心概念
- 指针是变量:存储的是内存地址,而非实际数据。
- 类型关联:指针类型必须与所指向变量的类型匹配(如int*指向int)。
- 间接访问:通过解引用操作符*访问或修改指向的内存。
2. 声明与初始化
```c
int val = 42;
int *p = &val;
int **pp = &p;
printf("%d\n", **pp); // 输出 42
int num = 10; // 普通变量
int* ptr = # // 指针ptr指向num的地址
// 等价于两步:
int* ptr; // 声明指针
ptr = # // 初始化指针
```
3. 解引用操作
```c
printf("%d", *ptr); // 输出:10(访问指针指向的值)
*ptr = 20; // 修改指针指向的内存,num变为20
```
4. 指针运算
```c
int arr[3] = {1, 2, 3};
int* p = arr; // p指向arr[0]
printf("%d", *p); // 输出:1
p++; // 指针后移,指向arr[1]
printf("%d", *p); // 输出:2
```
#### 指针数组:数组中每个元素是一个指针
1. 核心概念
- 数组本质:连续存储多个指针变量。
- 用途:常用于处理多个字符串或动态分配的内存块。
2. 声明与初始化
```c
// 存储3个int指针的数组
int a = 10, b = 20, c = 30;
int* ptrArray[3] = {&a, &b, &c};
// 存储字符串常量的指针数组(每个元素是char*)
char* fruits[3] = {
"Apple",
"Banana",
"Cherry"
};
```
3. 访问与应用
```c
// 访问指针数组中的值
printf("%d", *ptrArray[0]); // 输出:10(a的值)
// 遍历字符串数组
for (int i = 0; i < 3; i++) {
printf("%s\n", fruits[i]); // 输出每个字符串
}
```
4. 典型场景
- 字符串处理:避免字符串拷贝,直接操作地址。
- 动态内存管理:存储多个动态分配的内存块(如malloc返回的指针)。
#### 二级指针:指向指针的指针
1. 核心概念
- 双重间接性:二级指针存储的是一级指针的地址。
- 用途:常用于函数参数传递指针的地址,以便修改原始指针。
2. 声明与初始化
```c
int num = 100;
int* ptr = &num; // 一级指针指向num
int** ptr2 = &ptr; // 二级指针指向ptr
// 等价于:
int** ptr2;
ptr2 = &ptr;
```
3. 解引用操作
```c
printf("%d", **ptr2); // 输出:100(两次解引用)
**ptr2 = 200; // 修改num的值为200
```
4. 典型应用场景
(1)函数中修改指针
```c
void allocateMemory(int** p) {
*p = (int*)malloc(sizeof(int)); // 修改传入的指针
**p = 100; // 修改指针指向的值
}
// 调用
int* ptr = NULL;
allocateMemory(&ptr); // 传递指针的地址
printf("%d", *ptr); // 输出:100
```
(2)二维数组动态分配
```c
// 创建3x3的二维数组
int** matrix = (int**)malloc(3 * sizeof(int*));
for (int i = 0; i < 3; i++) {
matrix[i] = (int*)malloc(3 * sizeof(int));
}
// 访问元素
matrix[0][1] = 5; // 等价于*(*(matrix+0)+1) = 5
```
### ✅ 函数指针 / 函数指针数组