
由于最近开始接触嵌入式开发,经常需要阅读和编写C代码,感觉自己对这方面还不够熟悉。因此结合嵌入式相关场景再系统地学习一遍C语言,以便扎实巩固自己的基础知识。
类别 | 工具/名称 | 作用 | 说明 |
|---|---|---|---|
编译器 | GCC / MinGW / Clang | 将 C 代码翻译成机器能执行的二进制程序 | GCC(GNU Compiler Collection):最常用跨平台开源编译器 - MinGW:Windows 上 GCC 的移植版本,包含编译器和必要工具 - Clang:LLVM 提供的 C/C++ 编译器,速度快、诊断信息好 |
调试器 | GDB / LLDB | 用于调试程序,检查变量、单步执行、追踪错误 | GDB(GNU Debugger):最常用 C/C++ 调试器 - LLDB:LLVM 提供的调试器 |
IDE / 编辑器 | VS Code / CLion / Dev-C++ / Code::Blocks | 提供代码编辑、编译、调试一体化界面 | 方便写代码、运行和调试 - 可以配置不同的编译器 |
辅助工具 | Make / CMake / Ninja | 管理项目编译流程,自动化生成可执行文件 | Make:最经典的构建工具 - CMake:跨平台生成 Makefile 或 VS 工程文件 - Ninja:快速构建系统 |
Vscode + MinGW 的方式来作为C语言的运行环境。...\mingw64\bingcc --version
g++ --version
gdb --version输入上面代码都能输出相关信息表示安装成功。
Code Runner主要分为三类:
int,char, float, double 等void类型 | 关键字 | 占用字节 (常见) | 位数 | 取值范围 | 示例 |
|---|---|---|---|---|---|
字符型 |
| 1 字节 | 8 位 | -128 ~ 127 (有符号)
0 ~ 255 (无符号 |
|
短整型 |
| 2 字节 | 16 位 | -32,768 ~ 32,767 0 ~ 65,535 (unsigned) |
|
整型 |
| 4 字节 (常见) | 32 位 | -2,147,483,648 ~ 2,147,483,647 0 ~ 4,294,967,295 (unsigned) |
|
长整型 |
| 4 字节 (32 位系统) 8 字节 (64 位系统) | 32/64 位 | 范围随系统而变 |
|
长长整型 |
| 8 字节 | 64 位 | -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807 |
|
单精度浮点 |
| 4 字节 | 32 位 | ~1.2E-38 ~ 3.4E+38 (约 6 位有效数字) |
|
双精度浮点 |
| 8 字节 | 64 位 | ~2.3E-308 ~ 1.7E+308 (约 15 位有效数字) |
|
长双精度 |
| 12/16 字节 (编译器不同) | ≥ 80 位 | 更高精度 |
|
空类型 |
| 无 | 无 | 无 |
|
语法:类型名 数组名元素个数; int numbers5;表示它在内存中连续分配 5 个 int 大小的空间;
数组名会自动转换为一个指向首元素的指针,但是注意:数组名不是指针变量,而是一个固定地址的符号常量;它本身不能被修改(例如不能写 numbers++)。
numbers → 等价于 &numbers[0]%[标志][宽度][.精度][长度修饰符]类型说明符
掌握代码过程中的到输入输出非常重要,C中常用的格式说明符如下:说明符 | 用途 | 示例 |
|---|---|---|
| 有符号十进制整数 |
|
| 无符号十进制整数 |
|
| 无符号八进制整数 |
|
| 无符号十六进制整数 |
|
| 十进制浮点数 |
|
| 科学计数法 |
|
| 自动选择格式 |
|
| 十六进制浮点数 |
|
| 字符 |
|
| 字符串 |
|
| 指针地址 |
|
| 输出字符计数 |
|
| 百分号字符 |
|
|
|
|
|
|
|
|
|
|
|
|
|
常见格式化操作:
宽度和对齐控制
#include <stdio.h>
int main() {
int n = 123;
float f = 3.14;
printf("|%5d|\n", n); // 宽度5,右对齐:| 123|
printf("|%-5d|\n", n); // 宽度5,左对齐:|123 |
printf("|%8.2f|\n", f); // 宽度8,2位小数:| 3.14|
printf("|%-8.2f|\n", f); // 宽度8,左对齐:|3.14 |
return 0;
}动态宽度和精度
#include <stdio.h>
int main() {
int width = 10;
int precision = 3;
float value = 3.14159;
// 使用 * 指定动态宽度和精度
printf("|%*.*f|\n", width, precision, value); // | 3.142|
// 也可以只动态指定一个
printf("|%*.2f|\n", width, value); // | 3.14|
printf("|%10.*f|\n", precision, value); // | 3.142|
return 0;
}注意事项:
int a =10;
int b = 4;
float c = a/b; // c = 2.00000当两个数都是int,先进行整数除法,再进行float。
scanf 时要注意:某些格式(尤其是%c、%s、%[ ])不会自动跳过空白字符,容易误读前一次输入留下的换行符,需要手动处理空白或加空格跳过。#include <stdio.h>
int main()
{
int a;
char b;
scanf("%d", &a);
scanf("%c", &b);
printf("%d -", a);
printf("%c -", b);
}
// 输入
2
// 输出
2 -
-#include <stdio.h>
int main()
{
char str[] = "ABCDEFG";
// 取出1:3的字符BCD
str[4] = '\0'; // 直接在索引4处截断
printf("结果: %s\n", str + 1); // 从索引1开始输出
return 0;
}
// BCD'0';同理, 整数值转换为对应数字字符:加上 0#include <stdio.h>
int main() {
char c = '5';
int digit = c - '0'; // '5' - '0' = 53 - 48 = 5
printf("字符 '%c' 转换为整数值: %d\n", c, digit);
// 输出:字符 '5' 转换为整数值: 5
return 0;
}#define BUFFER_SIZE 256 // 缓冲区大小
// 使用
uint8_t buffer[BUFFER_SIZE];#define MAX(a, b) ((a) > (b) ? (a) : (b))
int main() {
int x = MAX(3+1, 5*2);
// 预处理后:((3+1) > (5*2) ? (3+1) : (5*2))
return 0;
}()包裹,整个宏体用() 包裹#define func(x) x*x
int res = func(3+1);
// 替换后:3+1*3+1 = 3+3+1=7(错误,预期是16)
//正确写法:#define func(x) ((x)*(x))2、宏名和括号之间不能有空格
#define MAX (a,b) // 错误!变成普通宏,不是函数宏
#define MAX(a,b) // 正确#include <stdio.h>
// 调试等级
#define DEBUG_LEVEL_INFO 3
#define DEBUG_LEVEL_WARN 2
#define DEBUG_LEVEL_ERROR 1
#define CURRENT_DEBUG_LEVEL DEBUG_LEVEL_INFO
#define DEBUG_PRINT(level, fmt, ...) do { \
if (level <= CURRENT_DEBUG_LEVEL) { \
printf("[%s] " fmt "\r\n", __FUNCTION__, ##__VA_ARGS__); \
} \
} while(0)
// __FUNCTION__ C/C++编译器的预定义标识符,表示当前函数的名称
// __VA_ARGS__ 表示可变参数列表,## 是预处理连接符的特殊用法
// 使用
int main() {
int adc_value = 5;
int bus_id = 6;
DEBUG_PRINT(DEBUG_LEVEL_INFO, "ADC value: %d", adc_value);
DEBUG_PRINT(DEBUG_LEVEL_ERROR, "I2C timeout on bus %d", bus_id);
}关键字 | 作用/说明 |
|---|---|
| 自动变量,局部变量默认存储类型 |
| 跳出循环或 |
|
|
| 字符类型 |
| 常量修饰符 |
| 跳过本次循环,进入下一次循环 |
|
|
|
|
| 双精度浮点数类型 |
|
|
| 枚举类型 |
| 声明外部变量或函数 |
| 单精度浮点数类型 |
|
|
| 无条件跳转语句 |
| 条件语句 |
| 内联函数提示(C99 后) |
| 整型 |
| 长整型 |
| 寄存器变量提示 |
| 指针限定符(C99 后,用于优化) |
| 从函数返回值 |
| 短整型 |
| 有符号修饰符 |
| 求数据类型或变量占用字节数 |
| 静态存储类型 |
| 结构体 |
| 多分支选择语句 |
| 类型重定义 |
| 联合体 |
| 无符号修饰符 |
| 无类型/无返回值函数 |
| 告诉编译器变量可能随时被改变,禁止优化 |
|
|
#include <stdio.h>
int main()
{
const int a = 10;
const int *p = &a; // 指向常量的指针
int b = 20;
p = &b; // 指针可以改变 *p = 20
// *p = 20; // 指针指向的值不能改
}
int main()
{
int a = 10;
int * const p = &a;
*p = 20; //
// int b = 30;
// p = &b; // 指针不能改变
}与宏的对比:
#define PI 3.14:宏,不占内存,编译时替换
const double PI = 3.14: 是 类型安全的常量,可参与调试、类型检查
volatile:告诉编译器 不要对这个变量做优化,因为它的值可能在程序执行的任何时刻被 外部因素修改。主要有两个作用:
1.防止编译器将变量缓存到寄存器中。
2.强制每次使用变量时都从内存中读取最新值。有些情况下编译器为了优化性能,会做一些假设和优化,比如下面这段代码,如果编译器认为 x 在循环内没有被改变,它可能会优化成 死循环,永远读取缓存寄存器里的 x,而不会去内存检查。但是,x 的值可能被 中断、硬件或其他线程修改,这就会出问题。
int x = 0;
while (x == 0) {
// do something
}常见的使用方式如下:
#include <stdio.h>
#include <stdbool.h>
volatile int flag = 0;
void set_flag() {
flag = 1; // 外部函数修改 flag
}
int main() {
while(flag == 0) {
pass
}
printf("Flag is set!\n");
return 0;
}// 方法1:先定义类型,后定义变量
enum uart_baudrate {
BAUD_9600 = 9600,
BAUD_19200 = 19200,
BAUD_115200 = 115200
};
enum uart_baudrate uart_speed;
// 方法2:定义类型同时定义变量
enum i2c_speed {
I2C_STANDARD = 100000,
I2C_FAST = 400000,
I2C_HIGH = 3400000
} i2c_bus_speed;
// 方法3:匿名枚举(常用)
enum {
LED_OFF = 0,
LED_ON = 1
} led_state;typedef enum {
SYS_INIT,
SYS_RUN,
SYS_LOW_POWER,
SYS_FAULT
} SysState;
SysState system_state;
void state_machine(void)
{
switch(system_state) {
case SYS_INIT:
// 初始化硬件
break;
case SYS_RUN:
// 正常运行
break;
case SYS_LOW_POWER:
// 进入低功耗
break;
case SYS_FAULT:
// 故障处理
break;
}
}文本段 (.text):存放程序代码(只读)。
只读数据 (.rodata):字面量、const 常量等(通常只读)
已初始化数据段 (.data):全局/静态变量且被初始化的部分
未初始化数据段 (.bss):全局/静态变量未初始化或为 0 的部分
堆(heap):动态分配(malloc、calloc、realloc、free)得到的内存,通常向高地址增长或由 mmap 分配。
栈(stack):函数调用帧、局部变量、返回地址等
内存映射区(mmap)/共享库映射:动态库、映射文件、匿名映射等。核心特性:
优点:无需手动管理内存,速度快,不会内存泄漏。
缺点:生命周期固定(函数结束即释放),空间有限。
核心特性:
1、 malloc:适合已知需要多少字节、或者手动初始化的情况。
例如需要分配5个整数的数组时:
#include <stdio.h>
#include <stdlib.h>
int main(){
int *arr = malloc(5 * sizeof(int));
for (int i = 0; i<5; i++){
arr[i] = i * i;
}
for (int i = 0; i<5; i++){
printf("%d ", arr[i]);
}
free(arr);
}
// 0 1 4 9 16 对于这段代码有几个地方需要注意:
①这里的arr[i] 和 *(arr + i) 完全等价的。但是也有些不同的地方:
#include <stdio.h>
int main(){
int arr[5] = {1, 2, 3, 4, 5};
int *ptr = arr;
//大小不同
printf("sizeof(arr) = %zu bytes\n", sizeof(arr));
printf("sizeof(*ptr) = %zu bytes\n", sizeof(*ptr));
//地址不同
printf("&arr = %p\n", (void*)&arr);
printf("&ptr = %p\n", (void*)&ptr); // 这是ptr变量自己的地址
//赋值行为不同
int var = 10;
ptr = &var;
printf("*ptr = %d\n", *ptr); // ptr可以指向别处
// arr = &var; //错误,arr 不能指向别处
}
// sizeof(arr) = 20 bytes
// sizeof(*ptr) = 4 bytes
// &arr = 00000000005FFE60
// &ptr = 00000000005FFE58
// *ptr = 10②如果末尾不释放内存,运行也不会报错,为什么要手动释放?
#include <stdio.h>
#include <stdlib.h>
int main() {
int *p = malloc(sizeof(int) * 5); // 动态分配20字节内存
p = NULL; // 丢失了原来的指针
// 原来那块内存还在内存中,但我们再也访问不到了
// 没有free,也不会有错误提示
return 0;
}由于小程序运行时间短时没问题,但对于长时间运行的程序(例如服务器、嵌入式系统、驱动)不断循环分配内存的逻辑 会出现这种现象:内存占用越来越高 → 最终系统内存耗尽 → 程序崩溃或性能下降。💣
场景 | 示例 | 问题 |
|---|---|---|
忘记 |
| 占用未释放 |
指针丢失 |
| 无法再释放 |
提前返回 | 在函数中多处 | 逻辑遗漏 |
多次分配 |
| 原内存被覆盖 |
// ❌ 有泄漏
#include <stdlib.h>
void test() {
int *arr = malloc(10 * sizeof(int));
// 使用完后直接返回
return;
}
// ✅ 正确释放
#include <stdlib.h>
void test() {
int *arr = malloc(10 * sizeof(int));
// 使用arr...
free(arr); // 释放内存
arr = NULL; // 避免野指针
}NULL,就会变成一颗定时炸弹。③如果分配的是5个整数的数组,但是 访问了 10 个元素会出现什么情况?
#include <stdio.h>
#include <stdlib.h>
int main(){
int *arr = malloc(5 * sizeof(int));
for (int i = 0; i<10; i++){
arr[i] = i * i;
}
for (int i = 0; i<10; i++){
printf("%d ", arr[i]);
}
free(arr);
}
//0 1 4 9 16 25 36 49 64 81 arr[5]时,程序会往 arr之后的内存写数据,这块内存可能属于堆管理器(malloc系统)或别的变量。C语言不会阻止你这么干,但后果是 可能破坏了堆结构。🧨
那么当 free() 尝试释放时,就会“踩到雷” 💥,导致:程序死循环、崩溃、退出异常、甚至内存碎片混乱。函数名 | 功能 | 返回值 | 初始化 | 是否可调整大小 |
|---|---|---|---|---|
| 分配一块大小为 | 指向分配内存的指针,失败返回 | ❌ 不会初始化,内容不确定(是“脏数据”) | ❌ |
| 分配 | 指向分配内存的指针,失败返回 | ✅ 全部置 0 | ❌ |
| 改变一块已分配内存的大小 | 新的指针,失败返回 | ✅/❌ 保留原有数据,新增部分未初始化 | ✅ |
| 释放由以上函数分配的内存 | 无 |
2、 calloc:分配内存并自动初始化为 0。
ptr = (type *)calloc(number_of_elements, size_of_each_element); 分配 number_of_elements × size_of_each_element 字节,内存内容初始化为 0。
#include <stdio.h>
#include <stdlib.h>
int main(){
int *arr = calloc(5, sizeof(int));//分配5个int大小的内存,并初始化为0
for (int i = 0;i<5;i++){
printf("%d ", arr[i]);
}
free(arr);
return 0;
}
// 输出:0 0 0 0 03、 realloc:改变已经分配的内存大小。
ptr = (type *)realloc(ptr, new_size_in_bytes);
#include <stdio.h>
#include <stdlib.h>
int main(){
int *arr = malloc(3 * sizeof(int));
for (int i = 0; i<3; i++)
arr[i] = i * i; // 初始化为 0 1 4
// 重新分配内存为5个int
arr = realloc(arr, 5 * sizeof(int));
arr[3] = 10;
arr[4] = 20;
for (int i = 0; i < 5; i++)
printf("%d ", arr[i]); // 输出:0 1 4 10 20
free(arr);
return 0;
}类别 | 举例 | 功能概述 |
|---|---|---|
1️⃣ 普通指针 |
| 指向普通变量 |
2️⃣ 空指针(NULL指针) |
| 不指向任何有效地址 |
3️⃣ 野指针(Dangling Pointer) | 未初始化或已释放内存后继续使用的指针 | 危险,导致崩溃或未定义行为 |
4️⃣ void 指针(通用指针) |
| 可指向任意类型数据 |
5️⃣ 指向指针的指针 |
| 存放另一个指针的地址 |
6️⃣ 指向常量的指针 |
| 不能修改所指数据 |
7️⃣ 常量指针 |
| 指针本身不能改,内容可以改 |
8️⃣ 指向常量的常量指针 |
| 指针和内容都不能改 |
9️⃣ 数组指针 |
| 指向一个数组 |
🔟 函数指针 |
| 指向一个函数 |
1️⃣1️⃣ 结构体指针 |
| 指向结构体变量 |
1️⃣2️⃣ 指针数组 |
| 存放多个指针的数组 |
1️⃣3️⃣ 指向字符串常量的指针 |
| 指向常量字符串 |
1️⃣4️⃣ 指向文件的指针 |
| 用于文件操作( |
1️⃣5️⃣ 指向函数返回堆内存的指针 |
| 动态内存管理 |
#include <stdio.h>
#include <stdlib.h>
int main(){
int *p1 = NULL; // 最常用的方式
int *p2 = 0; // 直接赋值为0
int *p3 = (void *)0; // 显式类型转换
printf("p1 = %p\n", (void *)p1);
printf("p2 = %p\n", (void *)p2);
printf("p3 = %p\n", (void *)p3);
}
// p1 = 0000000000000000
// p2 = 0000000000000000
// p3 = 0000000000000000#include <stdio.h>
#include <stdlib.h>
int main()
{
// ❌ 危险:未初始化的指针(野指针)
// int *dangerous_ptr;
// ✅ 安全:初始化为空指针
int *safe_ptr = NULL;
// 检查后再使用
if (safe_ptr == NULL)
{
safe_ptr = (int *)malloc(sizeof(int));
*safe_ptr = 100;
}
free(safe_ptr);
safe_ptr = NULL; // 释放后重新置空
return 0;
}2️⃣动态内存分配失败检查
在前面内存管理 中提到了malloc、calloc、realloc这些内存分配函数,当需要检查是否分配成功时,可以使用空指针。
#include <stdio.h>
#include <stdlib.h>
int main()
{
int *ptr = (int*)malloc(100 * sizeof(int));
if (ptr == NULL)
{
printf("Memory allocation failed.\n");
return 1;
}
printf("Memory allocated successfully.\n");
// ...
free(ptr);
ptr = NULL;
}int *p = NULL;
// *p = 10; // 错误!// 返回了局部变量的地址,而栈是自动回收的,导致local这个局部变量的指针变成了野指针
int *create_dangling_pointer()
{
int local = 42;
return &local; // 返回后local的内存被回收,指针悬空
}正确做法:
int *create_valid_pointer() {
int *dynamic = (int*)malloc(sizeof(int));
*dynamic = 42;
return dynamic; // 调用者需要负责释放2️⃣多指针共享同一内存
void shared_memory_danger()
{
int *ptr1 = (int *)malloc(sizeof(int));
int *ptr2 = ptr1; // 两个指针指向同一内存
*ptr1 = 100;
free(ptr1); // 释放内存
ptr1 = NULL; // ptr1安全了, 但ptr2现在变成了野指针!
// printf("%d\n", *ptr2); // 未定义行为
}#include <stdio.h>
int main()
{
int int_value = 42;
float float_value = 3.14f;
char char_value = 'A';
char str[] = "Hello";
void *void_ptr; // 声明void指针
void_ptr = &int_value; // 指向整型
// printf("void_ptr = %d\n", *void_ptr); // 错误!不能直接解引用void指针
printf("void_ptr = %d\n", *(int *)void_ptr); // 正确:需要类型转换
void_ptr = &float_value; // 指向浮点型
void_ptr = &char_value; // 指向字符型
void_ptr = str; // 指向数组
return 0;
}#include <stdio.h>
int main()
{
int arr[3] = {10, 20, 30};
void *void_ptr = arr;
// void_ptr++; // 错误!void指针不能进行算术运算
// 正确:转换为具体类型后再运算
int *int_ptr = (int *)void_ptr;
int_ptr++;
printf("%d\n", *int_ptr); // 输出: 20
return 0;
}如memcpy函数的源码:

2️⃣ 通用数据传递(如线程、回调函数、自定义数据结构)
#include <pthread.h>
#include <stdio.h>
void *worker(void *arg) {
int *num = (int *)arg; // 将 void* 强转回原类型
printf("Thread received: %d\n", *num);
return NULL;
}
int main() {
pthread_t tid;
int value = 42;
pthread_create(&tid, NULL, worker, &value); // 传入任意类型指针
pthread_join(tid, NULL);
return 0;
}线程函数的参数 void *arg 可以是任意类型。在线程内部,再强制转换回正确的类型使用;
这使得线程函数可以处理任意类型的数据结构,而不必固定类型。
int main()
{
int value = 10; // 普通整型变量
int *ptr1 = &value; // 指针,指向整型变量
int **ptr2 = &ptr1; // 指向指针的指针,指向指针ptr1
printf("value值为:%d\n", value);
printf("value内存地址为:%p\n", ptr1);
printf("ptr1地址为:%p\n", ptr2);
printf("ptr2存储的值为%p", *ptr2);
}
// value值为:10
// value内存地址为:00000000005FFE74
// ptr1地址为:00000000005FFE68
// ptr2存储的值为00000000005FFE74#include <stdlib.h>
int main(){
int rows = 3, cols = 4;
// 创建二维动态数组
int **array = (int **)malloc(rows * sizeof(int *));
for(int i = 0; i < rows; i++){
array[i] = (int *)malloc(cols * sizeof(int));
}
// 使用数组
array[1][2] = 99; //
// 释放内存
for(int i=0; i < rows; i++){
free(array[i]);
}
free(array);
}[ array 指向 ]
+------+------+------+ <- 存放 3 个 int*(每个元素是一个指针,指向每行的首地址)
| p0 | p1 | p2 |
+------+------+------+
array -> [ p0 | p1 | p2 ]
| | |
v v v
[4 ints][4 ints][4 ints]2️⃣ 修改指针本身
#include <stdlib.h>
void changePointer(int **pp)
{
static int newValue = 100;
*pp = &newValue; // 修改指针指向的内容
}
int main()
{
int num = 50;
int *ptr = #
printf("修改前: %d\n", *ptr); // 50
changePointer(&ptr); // 传递指针的地址
printf("修改后: %d\n", *ptr); // 100
return 0;
}#include <stdio.h>
// 这个函数承诺不会修改传入的数组
void printArray(const int *arr, int size)
{
for (int i = 0; i < size; i++)
{
printf("%d ", arr[i]);
// arr[i] = 0; // ❌ 编译错误!安全保护
}
printf("\n");
}
int main()
{
int numbers[] = {1, 2, 3, 4, 5};
printArray(numbers, 5);
return 0;
}2️⃣ 硬件寄存器访问
// 只读的硬件寄存器
const volatile int *readonly_register = (const int*)0x1000;
int readHardwareValue() {
return *readonly_register; // 只能读取,不能写入
// *readonly_register = 1; // ❌ 硬件保护
}[类型] * const [指针名]// 例如,在嵌入式系统中,指向一个固定的、只读的硬件寄存器或ROM中的配置数据
const ConfigTable *const system_config = &default_config;
// 函数内部使用全局静态对象时,避免指针被误改
static int buffer[128];
int * const p = buffer; // p 永远指向这个 buffer2️⃣ 固定地址硬件寄存器映射
// 假设寄存器布局如下:
typedef struct {
volatile uint32_t MODER; // 模式寄存器
volatile uint32_t ODR; // 输出数据
volatile uint32_t BSRR; // 置位/复位
} GPIO_Regs;
// 指针不能修改 → 防止破坏寄存器映射
// 指向的内容(寄存器)可以读写 → 正常访问硬件
// 此时用 指向固定地址的指针常量 映射:
#define GPIOA_BASE 0x40021000UL
GPIO_Regs * const GPIOA = (GPIO_Regs *)GPIOA_BASE;
// 这样一来,GPIOA = xxx 会报错;但 GPIOA->MODER = ... 可正常修改寄存器内容const [类型] * const [指针名] 或 [类型] const * const [指针名]// 例如,系统全局配置表,既不能修改表内容,也不能让指针指向其他表
const ConfigTable * const system_config = &default_config;
// 下面的操作都是非法的:
// system_config = &backup_config; // 错误:指针本身是常量
// system_config->baudrate = 9600; // 错误:指向的内容是常量2️⃣ 保护静态常量池中的字符串或数据
嵌入式系统中,字符串字面量或只读数据通常存放在ROM或Flash中,不允许修改,且地址固定。
指向常量的常量指针指向常量的指针[类型] (*[指针名])[数组长度]1️⃣ 二维数组传参与遍历
// 处理固定列数的二维数组(矩阵)
void process_matrix(int rows, int (*matrix)[4]) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < 4; j++) {
matrix[i][j] *= 2; // 像普通二维数组一样使用
}
}
}
// 调用示例
int grid[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
process_matrix(3, grid); // 数组名自动退化为指向首行的指针;grid数组最终所有元素乘22️⃣ 跳过整个数组行(步长操作)
int main()
{
// 利用数组指针的步长特性,快速跳转到指定行
int scores[4][3] = {
{90, 85, 88},
{78, 92, 86},
{95, 89, 91},
{82, 88, 84}};
int (*row)[3] = scores; // 指向第0行
row++; // 跳转到第1行(步长 = 12字节,3个int)
printf("%d\n", (*row)[1]); // 输出第1行第2列:92
row += 2; // 跳转到第3行
printf("%d\n", (*row)[0]); // 输出第3行第1列:82
}注意区分:
int (*p)[5] → 数组指针,指向包含5个int的数组int *p[5] → 指针数组,包含5个int指针的数组[返回类型] (*[指针名])(参数类型列表)// 利用函数指针决定做什么运算
int calculate(int a, int b, int (*op)(int, int))
{
return op(a, b);
}
int add(int x, int y)
{
return x + y;
}
int sub(int x, int y)
{
return x - y;
}
int main()
{
printf("%d\n", calculate(10, 5, add)); // 函数名自动转换为函数指针,等价于&add
printf("%d\n", calculate(10, 5, sub));
return 0;
}
// 15
// 52️⃣ 中断向量表
// 中断向量表的本质:一个函数指针数组
void (*isr[3])(void); // 3个中断的处理函数
// 中断0的处理函数
void handler_for_irq0(void) {
printf("中断0发生了\n");
}
// 中断1的处理函数
void handler_for_irq1(void) {
printf("中断1发生了\n");
}
int main() {
// 注册:告诉系统,当中断0发生时,调用handler_for_irq0
isr[0] = handler_for_irq0;
isr[1] = handler_for_irq1;
// 模拟硬件:当中断0发生时
isr[0](); // 自动调用 handler_for_irq0
// 输出:中断0发生了
}int(*p)(int,int):函数指针, p 是一个指针,指向返回 int、参数是 (int, int) 的函数 ;int*p(int,int):普通函数声明,p 是一个函数,返回值类型:int*(整型指针);int(*p[3l)(int,int):函数指针数组,p 是一个数组,数组大小为3,每个元素:函数指针(指向返回 int、两个 int 参数的函数)[结构体类型] *[指针名]1️⃣ 访问硬件寄存器
// 定义GPIO寄存器的结构体(映射到真实硬件地址)
typedef struct {
volatile unsigned int MODER; // 模式寄存器
volatile unsigned int ODR; // 输出数据寄存器
volatile unsigned int IDR; // 输入数据寄存器
} GPIO_TypeDef;
// 定义指针指向真实的硬件地址(比如0x40020000)
#define GPIOA_BASE 0x40020000
GPIO_TypeDef *GPIOA = (GPIO_TypeDef *)GPIOA_BASE;
// 通过结构体指针操作寄存器
int main() {
GPIOA->MODER = 0x01; // 设置GPIO为输出模式
GPIOA->ODR = 0x01; // 输出高电平
unsigned int value = GPIOA->IDR; // 读取输入值
}const char *[指针名] 或 char const *[指针名]//如使用STM32单片机
#include "stm32f1xx_hal.h" // STM32的库
extern UART_HandleTypeDef huart2; // 串口2的句柄
// 指向字符串常量的指针(字符串存在ROM/Flash中,不可修改)
const char *welcome_msg = "System Ready\r\n";
const char *error_msg = "Error: Sensor failed\r\n";
// 串口发送函数
void uart_send_byte(uint8_t data) {
HAL_UART_Transmit(&huart2, &data, 1, 100); // 发送1个字节
}
// 发送字符串
void uart_send_string(const char *str) {
while (*str != '\0') {
uart_send_byte(*str); // 发送字符
str++;
}
}
const char *welcome_msg = "System Ready\r\n";
int main() {
uart_send_string(welcome_msg); // 输出:System Ready
uart_send_string(error_msg); // 输出:Error: Sensor failed
}FILE *[指针名]#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main() {
FILE* fp;
char buffer[100];
// 打开文件
fp = fopen("config.txt", "r");
if (fp != NULL) {
fgets(buffer, 100, fp);
printf("%s\n", buffer);
fclose(fp);
return 0;
}
}#include <stdio.h>
void write_log(const char *message) {
FILE *fp = fopen("log.txt", "a");
if (fp != NULL) {
fprintf(fp, "%s\n", message); // 写入内容
fclose(fp);
}
}
int main() {
write_log("系统启动");
write_log("温度:25°C");
write_log("系统关闭");
return 0;
}原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。