目录
引言
结构体(struct
)在 C/C++ 及嵌入式开发中广泛应用。访问结构体成员时,使用点运算符(.
)或箭头运算符(->
)是基础操作。本文分为两大部分:
- 深入理解 C/C++ 结构体:讲解
.
与->
的原理与用法,包括调试与最佳实践。 - 在 STM32 单片机中应用:结合外设寄存器、HAL 库及动态句柄场景,分享嵌入式开发中的实战示例与优化技巧。
一、深入理解 C/C++ 结构体:.
与 ->
的区别与用法
1. .
(点运算符)详解
作用:用于直接访问结构体变量的成员。
示例代码:
#include <stdio.h>
#include <string.h>
struct Student {
char name[50];
int age;
float score;
};
int main() {
struct Student stu1; // 定义结构体变量
strcpy(stu1.name, "Alice"); // 使用 . 访问成员
stu1.age = 20;
stu1.score = 95.5;
printf("Name: %s, Age: %d, Score: %.1f\n", stu1.name, stu1.age, stu1.score);
return 0;
}
关键点:
stu1
是一个结构体变量,使用.
访问其成员。
2. ->
(箭头运算符)详解
作用:用于访问结构体指针所指向的对象的成员。
示例代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Student {
char name[50];
int age;
float score;
};
int main() {
struct Student *stu_ptr = malloc(sizeof(struct Student)); // 动态分配内存
strcpy(stu_ptr->name, "Bob"); // 使用 -> 访问指针指向的成员
stu_ptr->age = 22;
stu_ptr->score = 88.5;
printf("Name: %s, Age: %d, Score: %.1f\n", stu_ptr->name, stu_ptr->age, stu_ptr->score);
free(stu_ptr); // 释放内存
return 0;
}
关键点:
stu_ptr
是一个结构体指针,使用->
访问其指向的结构体成员。->
等同于(*ptr).member
的简写。
3. .
与 ->
的等价与转换
struct Student *ps = &stu1;
// 等价示例:
ps->age = 25; // 使用 ->
(*ps).age = 25; // 解引用后用 .
链式访问时,->
更简洁:
node->next->data;
4. 常见错误与调试技巧
场景 | 错误示例 | 正确写法 | 调试思路 |
---|---|---|---|
对指针使用 . |
stu_ptr.name = 20; |
stu_ptr->name = 20; |
检查变量类型 |
对实例使用 -> |
stu1->age = 20; |
stu1.age = 20; |
确认对象是否指针 |
空指针访问 | Node *n = NULL; n->x; |
— | 添加空指针检查 |
调试技巧:
- 使用 GDB:
p stu1
vsp *stu_ptr
- 加断言:
assert(ptr); ptr->field;
- 使用 Valgrind 检测内存问题
5. C++ 特性与运算符重载
- 智能指针:
std::unique_ptr<T>
、std::shared_ptr<T>
支持->
。 - 运算符重载:自定义
operator->()
实现代理模式。
struct Wrapper {
Target* ptr;
Target* operator->() { return ptr; }
};
6. 实战案例:链表与智能指针
6.1 传统 C 链表
struct Node { int data; struct Node *next; };
6.2 C++ 智能指针实现
#include <memory>
#include <iostream>
struct Node { int data; std::unique_ptr<Node> next; };
auto head = std::make_unique<Node>();
head->data = 1;
head->next = std::make_unique<Node>();
head->next->data = 2;
自动释放,无需手动
delete
7. 最佳实践与性能考量
- 优先使用智能指针与容器
- 注意结构体对齐与缓存友好
- 简化多级解引用为
p->next->x
二、在 STM32 单片机中,结构体的 .
与 ->
使用详解
在 STM32 单片机开发中,结构体被广泛用于外设寄存器映射、HAL/LL 库配置及自定义数据管理。以下在不修改示例的基础上,保留原内容并优化说明。
1. 外设寄存器访问:->
的绝对主场
// STM32F4 GPIO 寄存器定义
typedef struct {
__IO uint32_t MODER;
__IO uint32_t OTYPER;
__IO uint32_t OSPEEDR;
__IO uint32_t PUPDR;
__IO uint32_t IDR;
__IO uint32_t ODR;
__IO uint32_t BSRR;
__IO uint32_t LCKR;
__IO uint32_t AFR[2];
} GPIO_TypeDef;
#define GPIOA ((GPIO_TypeDef *)0x40020000U)
// 设置 PA0 为推挽输出,高速,无上下拉
GPIOA->MODER &= ~(3U << (0 * 2));
GPIOA->MODER |= (1U << (0 * 2));
GPIOA->OTYPER &= ~(1U << 0);
GPIOA->OSPEEDR |= (2U << (0 * 2));
GPIOA->PUPDR &= ~(3U << (0 * 2));
// 切换 PA0 电平
GPIOA->ODR ^= (1U << 0);
关键点:
GPIOA
是指针宏,需用->
- 可加
__DSB()
/__ISB()
保证时序
2. 库函数配置:.
与 ->
的混合使用
UART_HandleTypeDef huart2;
huart2.Instance = USART2;
huart2.Init.BaudRate = 115200;
huart2.Init.WordLength = UART_WORDLENGTH_8B;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.Parity = UART_PARITY_NONE;
HAL_UART_Init(&huart2);
- 配置结构体变量用
.
- HAL 内部以
->
操作寄存器
3. 动态分配与回调:->
的灵活应用
DMA_HandleTypeDef *hdma = malloc(sizeof(DMA_HandleTypeDef));
hdma->Instance = DMA1_Channel1;
hdma->Init.Direction = DMA_MEMORY_TO_PERIPH;
HAL_DMA_Init(hdma);
void USART2_IRQHandler(void) {
HAL_UART_IRQHandler(&huart2);
}
- 建议
memset
清零并检查 NULL - 回调使用句柄指针,结合 HAL 生命周期管理
4. 常见错误与避坑指南
场景 | 错误示例 |
---|---|
变量误用 -> |
GPIO_InitStruct->Pin = ...; |
指针未初始化访问 | hi2c->Instance = I2C1; |
5. 实战技巧与性能优化
- 位带别名提升 GPIO 操作速度
- DMA 缓存一致性:使用
SCB_CleanDCache()
- 多句柄存储于数组或链表
三、综合总结
通用 C/C++ 场景:
.
用于访问结构体实例成员,简洁直观->
用于解引用结构体指针并访问其成员,语法糖简化书写
STM32 嵌入式场景:
.
用于配置 HAL/LL 库的局部结构体变量(InitStruct 等)->
用于外设寄存器映射(宏定义指针)、动态句柄和回调中访问成员
C++ 扩展:
- 智能指针(
std::unique_ptr
/std::shared_ptr
)原生支持->
,自动管理生命周期 - 可自定义
operator->
实现代理或链式访问
- 智能指针(
性能与可靠性优化:
- 位掩码和位带操作加速单比特寄存器访问
- DMA 传输需进行 Cache 管理(如
SCB_CleanDCache()
、SCB_InvalidateDCache()
) - 关注结构体对齐与数据局部性,提高缓存友好性