轮询/阻塞/DMA
轮询模式是阻塞模式,程序等待所有的数据发送/接收完成才会接着向下执行;
而中断与DMA模式是非阻塞模式,它们将任务交给外设后就会接着向下执行,并不会等待数据的发送/接收完成,整个通信流程都交给外设与中断进行控制,通信完成后,再通过中断来通知CPU
什么是extern
在 C 语言中,extern 是一个关键字,主要用于声明一个变量或函数在其他文件(或同一文件的其他位置)中已定义,从而允许在当前文件中使用它。
1. 跨文件访问全局变量
// file1.c
int global_var = 42; // 定义全局变量(分配内存)
// file2.c
extern int global_var; // 声明变量(不分配内存)
void func() {
printf("%d", global_var); // 正确访问
}
2. 跨文件调用函数
函数默认是 extern 的(可省略),但显式声明可以增强可读性:
// file1.c
void func() { /* ... */ } // 定义函数
// file2.c
extern void func(); // 声明函数(可省略 extern)
3. 使用场景
头文件中声明全局变量
在头文件中用 extern 声明变量,源文件中定义变量:
// globals.h
extern int global_var; // 声明
// globals.c
#include "globals.h"
int global_var = 42; // 定义
// main.c
#include "globals.h"
int main() {
printf("%d", global_var); // 正确访问
}
避免重复定义
如果多个文件都包含同一个头文件,且头文件中直接定义变量(如 int var;),会导致链接错误(重复定义)。通过 extern 声明可避免此问题。
4. 声明vs定义
extern 是声明(告诉编译器“变量/函数存在,但别处定义”),不是定义(不分配内存)。
未初始化的 extern 变量是声明,初始化的 extern 变量是定义:
extern int a; // 声明
extern int b = 10; // 定义(等价于 int b = 10;)
11:34时提到:i2c.c调用main.c中定义的aht20State变量,需要将此变量在main.h中进行extern声明
原因:i2c.c中#include了i2c.h,i2c.h中#include了main.h,而main.h中声明了aht20State,层层套娃就相当于在i2c.c中已经声明了aht20State。所以i2c.c中的TxCpltCallback/RxCpltCallback函数能够正确调用aht20State变量。
static的作用
在 C 语言中,static 关键字的作用取决于它修饰的对象类型(变量或函数),主要用途如下:
1. 修饰局部变量(在函数内部)
作用:将局部变量的生命周期延长至整个程序运行期间(但作用域仍限制在函数内部)。
特点:
变量只初始化一次,函数多次调用时保留上一次的值。
默认初始化为 0(普通局部变量默认是垃圾值)。
void counter() {
static int count = 0; // 仅初始化一次
count++;
printf("Count: %d\n", count);
}
int main() {
counter(); // 输出 "Count: 1"
counter(); // 输出 "Count: 2"
return 0;
}
2. 修饰全局变量(在函数外部)
作用:将全局变量的作用域限制在当前文件(默认全局变量可被其他文件通过 extern 访问)。
特点:
避免与其他文件中的同名全局变量冲突。
实现“文件级封装”,隐藏变量。
// file1.c
static int hidden_var = 42; // 仅当前文件可见
// file2.c
extern int hidden_var; // 编译错误!无法访问其他文件的 static 全局变量
3.修饰函数
作用:将函数的作用域限制在当前文件(默认函数是 extern 的,可被其他文件调用)。
特点:
实现“文件级封装”,隐藏函数实现。
避免与其他文件中的同名函数冲突
// file1.c
static void private_func() { // 仅当前文件可见
// ...
}
// file2.c
extern void private_func(); // 编译错误!无法访问其他文件的 static 函数
修饰对象 | 作用 |
局部变量 | 延长生命周期(函数多次调用保留值),作用域仍为函数内。 |
全局变量 | 限制作用域到当前文件,避免命名冲突。 |
函数 | 限制作用域到当前文件,隐藏实现细节。 |
作用域的类型
1. 全局作用域(Global Scope)
- 定义:在函数外定义的变量或函数。
- 特点:
- 从定义位置开始,到文件末尾均可访问。
- 默认可被其他文件通过
extern
访问。 - 生命周期为整个程序运行期间。
int global_var = 10; // 全局变量,全局作用域
void global_func() { // 全局函数,全局作用域
// ...
}
2. 局部作用域(Local Scope / Function Scope)
- 定义:在函数内部定义的变量或函数参数。
- 特点:
- 仅在函数内部可见,函数外无法访问。
- 生命周期为函数调用期间(函数结束时销毁)。
void func() {
int local_var = 20; // 局部变量,局部作用域
}
3. 块作用域(Block Scope)
- 定义:在代码块(如
{}
包裹的循环、条件语句等)内部定义的变量。 - 特点:
- 仅在代码块内可见。
- C 语言中需使用
{}
明确声明块(C99 标准支持块内变量)。
void example() {
if (1) {
int block_var = 30; // 块作用域变量,仅在 if 块内可见
}
// 此处无法访问 block_var
}
4. 文件作用域(File Scope)
- 定义:用
static
修饰的全局变量或函数。 - 特点:
- 仅在当前文件内可见,其他文件无法通过
extern
访问。 - 生命周期为整个程序运行期间。
- 仅在当前文件内可见,其他文件无法通过
static int file_scope_var = 40; // 文件作用域变量
static void file_scope_func() { // 文件作用域函数
// ...
}
作用域的核心规则
1.就近原则(Shadowing):
内层作用域的变量会覆盖外层同名变量。
int x = 10; // 全局变量
void func() {
int x = 20; // 局部变量覆盖全局变量
printf("%d", x); // 输出 20
}
2.访问权限:
外层作用域无法访问内层作用域的变量。
内层作用域可以访问外层作用域的变量(如全局变量)。
3.生命周期与作用域分离:
作用域决定可见性(哪里能访问)。
生命周期决定存在时间(何时创建/销毁)。
例如:static 局部变量的作用域是函数内,但生命周期是程序全程。
函数参数传递方式
1. 值传递(C语言默认方式)
特点:将实参的值复制给形参,函数内修改形参不影响实参。
void swap(int a, int b) {
int temp = a;
a = b;
b = temp;
}
int main() {
int x = 3, y = 5;
swap(x, y); // 实参 x 和 y 的值被复制给形参 a 和 b
// x 仍为 3,y 仍为 5(未被交换)
}
2. 引用传递(通过指针模拟)
特点:传递实参的地址,函数内通过指针直接操作实参的内存。
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main() {
int x = 3, y = 5;
swap(&x, &y); // 传递 x 和 y 的地址
// x 变为 5,y 变为 3
}