Files
EmbeddedSoftwareLearn1/01-C语言基础与进阶/Readme.md
T
2025-06-16 21:54:22 +08:00

771 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 🔵 第一层:C/C++ 语言基础与进阶(必修)
### ✅ 变量 / 数据类型 / 关键字 / 常量
#### 📌 变量(Variable)
- 用于在程序中存储数据的具名内存区域。
- 声明格式:`类型 变量名 [= 初始值];`
```c
int count = 10;
float temperature;
char c = 'A';
```
- 局部变量:函数内声明,仅在函数内部可用。
- 全局变量:函数外声明,整个文件或项目中可见(根据作用域)。
#### 📌 数据类型(Data Types)
- **整型**:`int`, `short`, `long`, `long long`, `unsigned`
- **浮点型**:`float`, `double`
- **字符型**:`char`
- **派生类型**:指针、数组、结构体等
```c
unsigned int u = 100;
long long big_number = 12345678900LL;
```
#### 📌 关键字(Keywords)
常用 C 关键字解释如下:
| 关键字 | 说明 |
|------------|------------------------------|
| `const` | 定义只读变量 |
| `volatile` | 防止编译器优化,常用于寄存器 |
| `static` | 变量作用域或函数仅在本文件可见 |
| `extern` | 声明外部变量/函数 |
| `typedef` | 为数据类型取别名 |
```c
static int counter = 0; // 内部链接
extern int g_value; // 声明外部变量
volatile uint32_t *reg = (uint32_t *)0x40021000; // 用于寄存器访问
```
#### 📌 常量(Constant)
- **字面常量**:如 `10`, `3.14`, `'a'`, `"abc"`
- **符号常量**:用 `#define` 或 `const` 定义
```c
#define PI 3.14159
const int MAX_SIZE = 100;
```
---
### ✅ 栈 和 堆(内存管理)
#### 栈 (stack):自动分配内存,函数退出即释放。
1. 核心特性
- 自动分配与释放:由编译器自动管理,函数调用时分配栈帧,函数返回时自动释放。
- 后进先出(LIFO):类似一摞盘子,最后放入的最先取出。
- 高速访问:栈内存访问效率高(通常通过寄存器直接操作)。
- 空间有限:栈空间通常较小(如 Linux 默认 8MB),过大的局部变量可能导致栈溢出。
2. 存储内容
- 局部变量:函数内部定义的变量。
- 返回地址:函数执行完毕后返回的位置。
- 函数参数:调用函数时传递的参数。
- 寄存器值:保存调用前的寄存器状态,以便恢复。
3. 工作原理
- 栈指针(ESP):指向当前栈顶的内存地址。
- 栈帧(Stack Frame):每个函数调用在栈上分配的独立空间,包含局部变量和参数。
- 示例代码:
```c
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 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
```
### ✅ 函数指针 / 函数指针数组
- **函数指针声明**:`int (*fp)(int)` 表示指向返回 `int` 的函数的指针
- **函数指针数组**:用于策略模式或注册多个处理函数
```c
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int (*ops[2])(int, int) = { add, sub };
printf("%d\n", ops[0](5, 3)); // 输出 8
```
### ✅ 表达式、语句、运算符
- 表达式:`a + b`, `x++`, `p[i]`
- 运算符:`+`, `-`, `*`, `/`, `%`, `&&`, `||`, `!`, `==`, `!=`
- 语句:控制流程结构 `if`, `for`, `while`, `switch`
### ✅ 数组 / 字符串
#### 数组(Array)
- 数组是一组相同类型数据的有序集合,在内存中连续存储。
- 一维数组定义:`类型 数组名[大小];`
```c
int arr[5] = {1, 2, 3, 4, 5};
char str[10] = "Hello";
```
- 数组索引从 0 开始,访问方式如:arr[2] 表示第三个元素。
- 多维数组:int matrix[3][4]; 表示 3 行 4 列矩阵。
#### 字符串(String)
- 字符串是以空字符 '\0' 结尾的字符数组。
- 定义方式:
```c
char str1[] = "Hello"; // 自动添加 '\0'
char str2[6] = {'H','e','l','l','o','\0'}; // 手动指定
```
- 常用字符串函数
```
// 需包含头文件 <string.h>
strlen(str); // 计算长度(不含 '\0')
strcpy(dest, str); // 拷贝
strcmp(a, b); // 比较字符串
strcat(a, b); // 将 b 拼接到 a 后面
```
```c
#include <string.h>
char msg[20];
strcpy(msg, "Hi"); // msg: "Hi"
strcat(msg, " there"); // msg: "Hi there"
```
### ✅ 结构体 / 共用体 / 枚举 / 位域
#### 结构体
**定义:**
结构体是将多个不同类型的数据组合在一起的复合数据类型,用于表示实体的多个属性。
**使用场景:**
- 表示传感器、外设状态、网络包头等复杂数据
- 多变量统一传参,提升代码组织性
语法:
```c
struct StructName {
int id;
float value;
char name[20];
};
```
示例:
```c
struct SensorData {
int id;
float temperature;
char location[20];
};
struct SensorData s1 = {1, 36.5, "room_1"};
printf("Sensor: %d, Temp: %.1f\n", s1.id, s1.temperature);
```
#### 共用体
**定义:**
共用体中的所有成员共享同一块内存,任何时刻只能使用其中一个成员。
**使用场景:**
- 节省内存:如嵌入式协议帧解析
- 多种数据格式的重解释
语法:
```c
union UnionName {
int i;
float f;
char c;
};
```
示例:
```c
union Data {
int i;
float f;
};
union Data d;
d.i = 10;
printf("i = %d\n", d.i);
d.f = 3.14;
printf("f = %.2f\n", d.f); // 修改 f 会破坏 i
```
注意事项:
- 占用内存大小为最大成员的大小
- 修改一个成员后,其他成员的值不可预测
#### 枚举(Enumeration)
**定义:**
枚举是一种用户自定义的数据类型,用于定义一组命名的整数常量。
**使用场景:**
- 定义状态机状态
- 表示设备运行模式、错误码
语法:
```c
enum Color { RED, GREEN, BLUE };
enum Color color = GREEN;
```
示例:
```c
enum State {
STATE_IDLE,
STATE_ACTIVE,
STATE_ERROR = 100, // 可手动赋值
STATE_SLEEP
};
enum State current = STATE_ACTIVE;
printf("State: %d\n", current); // 输出 1
```
特性:
- 默认从 0 开始递增
- 可强制设定起始值
#### 位域(Bit Field)
**定义:**
位域用于结构体中,定义每个字段占用的比特位数,实现更细粒度的内存控制。
**使用场景:**
- 配置寄存器映射
- 网络协议比特位字段解析
- 内存空间紧张场景
语法:
```c
struct Flags {
unsigned int ready : 1;
unsigned int error : 1;
unsigned int mode : 2;
};
```
示例:
```c
struct Flags f;
f.ready = 1;
f.error = 0;
f.mode = 3; // 占2位,最大为11(二进制)即3
printf("ready = %d, mode = %d\n", f.ready, f.mode);
```
注意事项:
- 位域不能取地址(&f.ready 不合法)
- 字段数值不能超过位数范围(2^n - 1)
- 与具体编译器实现密切相关(跨平台需小心)
### ✅ 位操作
- 嵌入式开发中用于设置寄存器位、控制硬件
```c
#define LED_PIN (1 << 2)
PORT |= LED_PIN; // 置位
PORT &= ~LED_PIN; // 清零
PORT ^= LED_PIN; // 翻转
```
### ✅ 关键语义 & 修饰符
#### `const`(只读限定符)
```c
const int a = 10;
void print(const char* msg); // msg 不可修改
```
#### `volatile`(防止优化)
```c
volatile int *reg = (int *)0x40021000; // 硬件寄存器访问
```
#### `static`(静态变量/内部链接)
```c
static int count = 0; // 静态变量,函数调用间保留值
```
#### `extern`(外部变量声明)
```c
extern int global_var;
```
#### `register`(提示变量存放寄存器)
```c
register int speed;
```
#### `auto`(默认局部变量)
```c
auto int a = 10; // 一般可省略 auto
```
### ✅ 内存存储类型与生命周期
| 存储类型 | 生命周期 | 作用域 | 关键字 |
|------------|-----------------|-------------------|--------------|
| 栈(stack) | 函数调用期间 | 局部变量 | auto |
| 静态区 | 程序全程 | 局部/全局 | static |
| 堆(heap) | 手动管理 | 全局 | malloc/free |
| 寄存器 | 函数调用期间 | 局部 | register |
### ✅ 编译与调试基础
#### C 编译四阶段(以 GCC 为例)
```bash
gcc -E main.c -o main.i # 预处理
gcc -S main.c -o main.s # 编译为汇编
gcc -c main.c -o main.o # 汇编为目标文件
gcc main.o -o main # 链接生成可执行文件
```
#### Makefile 示例
```makefile
CC = gcc
TARGET = app
OBJS = main.o utils.o
$(TARGET): $(OBJS)
$(CC) -o $@ $^
%.o: %.c
$(CC) -c $< -o $@
clean:
rm -f *.o $(TARGET)
```
#### GCC 编译参数
| 参数 | 含义 |
|------|------|
| `-Wall` | 开启所有警告 |
| `-g` | 含调试信息 |
| `-O2` | 优化等级 |
| `-I` | 头文件路径 |
| `-L`/`-l` | 库路径和链接 |
| `-D` | 宏定义 |
#### GDB 基础调试
```bash
gdb ./main
(gdb) break main
(gdb) run
(gdb) next / step
(gdb) print var
(gdb) continue
```
#### 内联汇编
```c
int result;
__asm__ __volatile__ (
"movl $5, %%eax;"
"movl $3, %%ebx;"
"addl %%ebx, %%eax;"
"movl %%eax, %0;"
: "=r"(result)
:
: "%eax", "%ebx"
);
printf("result = %d\n", result); // 输出 8
```
---
### 冒泡排序(Bubble Sort)
#### 原理:
相邻元素两两比较,把最大的“冒”到最后。
#### 时间复杂度:
* 最坏/平均:O(n²)
* 最好:O(n)(加优化判断)
#### 适用场景:
数据量小、逻辑简单、嵌入式环境友好
#### 示例代码:
```c
void bubble_sort(int arr[], int n) {
for (int i = 0; i < n - 1; ++i) {
int swapped = 0;
for (int j = 0; j < n - i - 1; ++j) {
if (arr[j] > arr[j+1]) {
int t = arr[j]; arr[j] = arr[j+1]; arr[j+1] = t;
swapped = 1;
}
}
if (!swapped) break; // 优化:已排序
}
}
```
---
### 选择排序(Selection Sort)
#### 原理:
每轮从未排序区间中选择最小值放到前面。
#### 时间复杂度:
* 所有情况:O(n²)
#### 适用场景:
嵌入式设备中内存访问代价高,交换少
#### 示例代码:
```c
void selection_sort(int arr[], int n) {
for (int i = 0; i < n - 1; ++i) {
int min = i;
for (int j = i + 1; j < n; ++j) {
if (arr[j] < arr[min])
min = j;
}
if (min != i) {
int t = arr[i]; arr[i] = arr[min]; arr[min] = t;
}
}
}
```
---
### 插入排序(Insertion Sort)
#### 原理:
每次将一个元素插入到已排序部分的合适位置。
#### 时间复杂度:
* 最坏/平均:O(n²)
* 最好:O(n)
#### 适用场景:
数据量小、数据基本有序时表现好
#### 示例代码:
```c
void insertion_sort(int arr[], int n) {
for (int i = 1; i < n; ++i) {
int key = arr[i], j = i - 1;
while (j >= 0 && arr[j] > key) {
arr[j+1] = arr[j];
j--;
}
arr[j+1] = key;
}
}
```
---
### 快速排序(Quick Sort)
#### 原理:
分治法。选定基准,左边小于它,右边大于它。
#### 时间复杂度:
* 最坏:O(n²)
* 平均:O(n log n)
#### 适用场景:
高性能需求、数据量较大(慎用递归栈)
#### 示例代码:
```c
int partition(int arr[], int low, int high) {
int pivot = arr[high], i = low - 1;
for (int j = low; j < high; ++j) {
if (arr[j] < pivot) {
++i;
int t = arr[i]; arr[i] = arr[j]; arr[j] = t;
}
}
int t = arr[i+1]; arr[i+1] = arr[high]; arr[high] = t;
return i + 1;
}
void quick_sort(int arr[], int low, int high) {
if (low < high) {
int p = partition(arr, low, high);
quick_sort(arr, low, p - 1);
quick_sort(arr, p + 1, high);
}
}
```
---
### 归并排序(Merge Sort)
#### 原理:
分治法。将数组分成两半排序后合并。
#### 时间复杂度:
* 所有情况:O(n log n)
#### 适用场景:
追求稳定排序、高精度处理、实时传感器数据等(需额外内存)
#### 示例代码:
```c
void merge(int arr[], int l, int m, int r) {
int n1 = m-l+1, n2 = r-m;
int L[n1], R[n2];
for (int i = 0; i < n1; ++i) L[i] = arr[l+i];
for (int j = 0; j < n2; ++j) R[j] = arr[m+1+j];
int i = 0, j = 0, k = l;
while (i < n1 && j < n2)
arr[k++] = (L[i] <= R[j]) ? L[i++] : R[j++];
while (i < n1) arr[k++] = L[i++];
while (j < n2) arr[k++] = R[j++];
}
void merge_sort(int arr[], int l, int r) {
if (l < r) {
int m = (l + r) / 2;
merge_sort(arr, l, m);
merge_sort(arr, m+1, r);
merge(arr, l, m, r);
}
}
```
---
### 堆排序(Heap Sort)
#### 原理:
利用堆结构(大根堆),反复取出最大元素构造有序序列。
#### 时间复杂度:
* 所有情况:O(n log n)
#### 适用场景:
嵌入式中对最值处理(最大温度等)、优先级调度
#### 示例代码:
```c
void heapify(int arr[], int n, int i) {
int largest = i, l = 2*i + 1, r = 2*i + 2;
if (l < n && arr[l] > arr[largest]) largest = l;
if (r < n && arr[r] > arr[largest]) largest = r;
if (largest != i) {
int t = arr[i]; arr[i] = arr[largest]; arr[largest] = t;
heapify(arr, n, largest);
}
}
void heap_sort(int arr[], int n) {
for (int i = n/2 - 1; i >= 0; i--) heapify(arr, n, i);
for (int i = n-1; i > 0; i--) {
int t = arr[0]; arr[0] = arr[i]; arr[i] = t;
heapify(arr, i, 0);
}
}
```
---
## 📌 总结对比表
| 排序算法 | 时间复杂度(平均) | 空间复杂度 | 稳定性 | 嵌入式适用性 |
| ---- | ---------- | -------- | --- | -------- |
| 冒泡排序 | O(n²) | O(1) | ✅ | ✅(小数据) |
| 选择排序 | O(n²) | O(1) | ❌ | ✅ |
| 插入排序 | O(n²) | O(1) | ✅ | ✅(近似有序) |
| 快速排序 | O(n log n) | O(log n) | ❌ | ⚠️(递归栈) |
| 归并排序 | O(n log n) | O(n) | ✅ | ⚠️(额外内存) |
| 堆排序 | O(n log n) | O(1) | ❌ | ✅ |