结构体介绍


结构体是⼀些值的集合,这些值称为 成员变量。结构的每个成员可以是不同类型的变量。结构体(struct)是一种用户自定义的数据类型,它允许你将多个不同类型的数据组合成一个单独的类型。结构体允许你将相关的数据元素组合成一个单一的数据结构,从而方便数据的处理和管理。

一,结构体变量的创建和初始化

1,1结构体的创建与声明

创建一个结构体的关键字为:struct
演示:

#include <stdio.h>
// 定义一个名为Student的结构体
struct Student 
{
    char name[50];    // 学生的名字
    int age;          // 学生的年龄
    char ID[20];      // 学生的学号
};

这样我们就定义了一个结构体数据类型,结构体的成员变量具体是什么数据类型需要我们根据创建的结构体所描述的对象来定义。例如这里创建了一个结构体用来存储关于学生的身份信息,所以成员变量包括了名字。年龄,学号。
定义完数据类型后,我们就可以创建结构体变量,与其他数据类型创建变量的方式没有什么非常大的差异。
结构体变量的创建方式有以下几种:

struct Student 
{
    char name[50];    // 学生的名字
    int age;          // 学生的年龄
    char ID[20];      // 学生的学号
}b1,b2;//一,直接在结构体后创建变量,为全局变量

  struct Student b3;//全局变量
  
int main()
{
  struct Student b4;//局部变量
    struct Student arr[5];//创建一个结构体数组
}

1,2结构体的初始化

我们在创建其它数据类型的变量时,通常会进行初始化,同样的,在创建结构体变量的时候,我们也可以对结构体变量进行初始化。
结构体的初始化有不同的方法:

#include <stdio.h>
struct Student
{
    char name[50];    // 学生的名字
    int age;          // 学生的年龄
    char ID[20];      // 学生的学号
};
int main()
{
    struct Student s1 = { "zhangsan",18,"2024666478" };
    //一,按照结构体成员的顺序初始化

    struct Student s2 = { .age = 18, .name = "lisi", .ID = "20230818002" };
    //二,按照指定的顺序初始化
    //"."用于访问结构体的成员

    //打印
    printf("%s %d %s\n", s1.name, s1.age, s1.ID);
    printf("%s %d %s\n", s2.name, s2.age, s2.ID);
    return 0;
}

打印结果:

在这里插入图片描述

1,3结构体特殊声明

在声明结构的时候,可以不完全的声明。
例如:

//匿名结构体类型
struct
{
int a;
char b;
float c;
}x={18,'a',3.14};//匿名结构体初始化
int main()
{
 printf("%d %c %f\n",x.a,x.b,x.c);//打印
 return 0;
}

匿名结构体智能够使用一次,因为没有标签,所以第二次使用时编译器无法识别。
在这里插入图片描述
匿名结构体使用时还有一个重要的注意事项,我们先来看一段代码:


struct
{
int a;
char b;
float c;
}x;


struct
{
int a;
char b;
float c;
}* px;

上面我们创建了一个匿名结构体变量和一个匿名结构体指针,在这个条件下,代码:

px = &x;

不合法,因为编译器会把上⾯的两个声明当成完全不同的两个类型,所以是⾮法的。
在这里插入图片描述

警告:
匿名的结构体类型,如果没有对结构体类型重命名的话,基本上只能使⽤⼀次。

二,结构体的大小

2,1内存对齐

在了解了结构体后,我们再来讨论一个问题,结构体在内存中所占用的空间大小是如何计算的。
我们先来看一段计算结构体大小的代码:

#include <stdio.h>
struct Stu
{
	int a;
	char b;
	int c;
}s1;
int main()
{
	printf("%d \n",sizeof(s1));
	return 0;
}

我们知道,整型占4个字节,char占一个字节,那么这个结构体的大小是9个字节吗?
答案是占12个字节:
在这里插入图片描述
至于12个字节是怎么计算的,就要关于结构体内存对齐的知识了。
我们首先要知道结构体的对齐规则
1.结构体的第⼀个成员对⻬到和结构体变量起始位置偏移量为0的地址处
2.其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。
对⻬数=编译器默认的⼀个对⻬数与该成员变量⼤⼩的较⼩值。
3.结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的整数倍。

下面我就使用VS来具体介绍一下(VS 中默认的值为 8)
首先,系统再给结构体分配空间时,每个结构体变量的空间是一整块连续的内存:

struct Stu
{
	char b;//一个字节
	int a;//四个字节
	char c;//一个字节
}s1;

首先,第一个成员对⻬到和结构体变量起始位置偏移量为0的地址处,然后,我们计算每个成员变量的对齐地址,char类型大小为一个字节(1<8),所以对齐地址为1的整数倍,int类型占四个字节(4<8),所以对齐地址为4的整数倍,所以,我们就可以将该结构体在内存中变量所占的地址使用图的形式表示出来:
在这里插入图片描述
×表示结构体中浪费的空间,又因为结构体总⼤⼩为最⼤对⻬数的整数倍,在该结构体中,最大对其数为4,所以总大小为4的整数倍,所以总大小应该为12个字节。我们可以看到,这样安排成员变量浪费了许多空间,所以我们在创建结构体时应该规划好成员的顺序。

如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍。

三,用结构体实现位段

在C语言中,位段(也称为位域或位字段)是一种数据结构,它允许程序员在结构体中定义具有特定位数的成员。这可以用于节省内存,特别是在处理硬件寄存器或需要紧凑存储的数据结构时。位段在struct中定义,并指定每个成员所占用的位数。
下面是一个简单的位段示例:

#include <stdio.h>
struct packed_data 
{
    unsigned int a:3;    // a占用3位
    unsigned int b:5;    // b占用5位
    unsigned int c:8;    // c占用8位
};
int main() {
    struct packed_data data;
    data.a = 7;       // 3位可以表示的最大值是7 (2^3 - 1)
    data.b = 31;      // 5位可以表示的最大值是31 (2^5 - 1)
    data.c = 255;     // 8位可以表示的最大值是255 (2^8 - 1)

    printf("a: %u, b: %u, c: %u\n", data.a, data.b, data.c);
    return 0;
}

在上面的示例中,我们定义了一个名为packed_data的结构体,其中包含三个位段成员:a、b和c。它们分别占用3位、5位和8位。注意,位段的长度不能超过其基础类型的长度。例如,对于unsigned int,其长度通常是32位(这取决于具体的编译器和平台),所以你不能定义一个超过32位的位段。
虽然位段在某些情况下可以节省内存,但它们也有一些限制和注意事项:
跨平台问题:位段在不同的系统和编译器上的行为可能会有所不同。因此,在使用位段进行跨平台编程时,需要格外小心。
对齐和填充:编译器可能会在结构体成员之间插入填充字节,以确保正确的对齐。这可能会影响位段的布局和大小。
访问效率:访问位段可能比访问普通的结构体成员要慢一些,因为编译器可能需要执行额外的位操作来读取或写入位段的值。
因此,尽管位段在某些特定场景下很有用,但在一般的应用程序中,通常建议使用完整的数据类型(如int、float等)来定义结构体的成员。

### CAN_HandleTypeDef 结构体详解 CAN_HandleTypeDef 是 CAN 总线通信中用于管理 CAN 外设的一个结构体,它在 HAL(Hardware Abstraction Layer)库中被广泛使用。该结构体的设计体现了面向对象编程的思想,通过封装和抽象,使得 CAN 模块的配置和操作更加直观和灵活 [^1]。 #### 1. 结构体定义 CAN_HandleTypeDef 结构体通常定义如下: ```c typedef struct { CAN_TypeDef *Instance; /*!< CAN register base address */ CAN_InitTypeDef Init; /*!< CAN required parameters */ CAN_TxHeaderTypeDef TxHeader; /*!< CAN transmit message header */ CAN_RxHeaderTypeDef RxHeader; /*!< CAN receive message header */ HAL_LockTypeDef Lock; /*!< Locking object for CAN peripheral */ __IO HAL_CAN_StateTypeDef State; /*!< CAN communication state */ __IO uint32_t ErrorCode; /*!< CAN Error code */ } CAN_HandleTypeDef; ``` - **Instance**:指向 CAN 寄存器基地址的指针,用于访问底层硬件寄存器。 - **Init**:包含 CAN 初始化参数,如波特率、工作模式等配置信息。 - **TxHeader**:用于存储 CAN 发送帧的头部信息,包括标识符、帧类型(标准帧或扩展帧)、数据长度等。 - **RxHeader**:用于存储接收到的 CAN 帧头部信息。 - **Lock**:用于保护 CAN 外设的锁机制,确保在多线程或中断环境下操作的安全性。 - **State**:表示 CAN 模块的当前状态(如就绪、忙、错误等)。 - **ErrorCode**:记录最近发生的错误代码,用于调试和错误处理。 - **Lock** 和 **State** 字段体现了结构体在并发访问中的安全性和状态管理能力 [^1]。 #### 2. 结构体用途 CAN_HandleTypeDef 的主要用途是为 CAN 模块提供统一的接口,简化用户对 CAN 外设的操作。通过该结构体,用户可以: - **初始化 CAN 控制器**:通过 `Init` 字段配置 CAN 的通信参数,如波特率、工作模式(正常模式、回环模式等)。 - **发送和接收数据**:利用 `TxHeader` 和 `RxHeader` 字段管理发送和接收的数据帧格式。 - **状态管理**:通过 `State` 和 `ErrorCode` 字段监控 CAN 模块的状态和错误情况。 - **并发控制**:通过 `Lock` 字段实现对 CAN 外设的互斥访问,防止多个任务或中断同时操作导致的数据冲突。 例如,在 CAN 初始化过程中,开发者可以通过设置 `Init` 字段来配置 CAN 控制器的工作模式和通信速率 [^2]。而在发送数据时,`TxHeader` 字段用于指定发送帧的标识符、帧类型和数据长度,从而确保数据帧的格式符合 CAN 协议的要求 [^4]。 #### 3. 示例代码 以下是一个使用 CAN_HandleTypeDef 结构体的简单初始化和发送示例: ```c CAN_HandleTypeDef hcan; void CAN_Init(void) { hcan.Instance = CAN1; hcan.Init.Prescaler = 16; hcan.Init.Mode = CAN_MODE_NORMAL; hcan.Init.SyncJumpWidth = CAN_SJW_1TQ; hcan.Init.TimeSeg1 = CAN_BS1_13TQ; hcan.Init.TimeSeg2 = CAN_BS2_2TQ; hcan.Init.TimeTriggeredMode = DISABLE; hcan.Init.AutoBusOff = ENABLE; hcan.Init.AutoWakeUp = ENABLE; hcan.Init.AutoRetransmission = ENABLE; hcan.Init.ReceiveFifoLocked = DISABLE; hcan.Init.TransmitFifoPriority = DISABLE; if (HAL_CAN_Init(&hcan) != HAL_OK) { // 初始化失败处理 } } void CAN_SendMessage(uint32_t id, uint8_t *data, uint8_t len) { CAN_TxHeaderTypeDef txHeader; uint32_t txMailbox; txHeader.StdId = id; txHeader.ExtId = 0x00; txHeader.IDE = CAN_ID_STD; txHeader.RTR = CAN_RTR_DATA; txHeader.DLC = len; txHeader.TransmitGlobalTime = DISABLE; if (HAL_CAN_AddTxMessage(&hcan, &txHeader, data, &txMailbox) != HAL_OK) { // 发送失败处理 } } ``` 在上述代码中,`hcan` 是一个 CAN_HandleTypeDef 类型的变量,用于管理 CAN1 外设的配置和操作。通过调用 `HAL_CAN_Init` 函数完成 CAN 控制器的初始化,而 `HAL_CAN_AddTxMessage` 函数则用于发送数据帧 [^2]。 --- ###
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值