用 Keil5 理解 C 语言结构体(一):定义、存储与初始化

发布于:2025-07-28 ⋅ 阅读:(17) ⋅ 点赞:(0)

前言

在之前的笔记中,我们学习了数组(包括多维数组和指针数组)在存储同类型数据时的用法。但实际开发中,常需要记录由不同类型数据组成的 “整体信息”—— 比如学生的姓名(字符串)、年龄(整数)、性别(字符)。这时,用多个数组分别存储会显得零散且难管理,而结构体正是为解决这类 “异构数据整合” 问题而生的。这篇笔记将通过具体例子,讲讲结构体如何整合不同类型数据,以及它的定义、使用和初始化方法。

结构体:异构数据的整合工具

📌 从数组的局限看结构体的优势

若要记录 3 个学生的姓名、年龄、性别,用数组需要定义多个不同类型的数组,例如:

#include<stdio.h>
#include<string.h>
int mymain(void)
{
	char names[3][20];  //存储姓名(字符串数组)
	int ages[3];        //存储年龄(整数数组)
	char sex[3];        //存储性别(字符数组)
	
	// 记录第一个学生信息
	strcpy(names[0], "zhangsan");
	ages[0] = 18;
	sex[0] = 'M';
	return 0;
}

这种方式的缺点很明显:每类信息需要一个独立数组,新增信息(如成绩)就得再定义数组,且数据之间的关联性被割裂(很难直观看出 “names [0]、ages [0]、sex [0] 属于同一个学生”)。
而结构体可以将这些不同类型的信息 “打包” 成一个整体,让数据管理更清晰。用结构体实现同样功能:

补充strcpy的作用:复制整个字符串(含 '\0'

#include<stdio.h>
#include<string.h>

// 定义结构体类型,描述学生的组成信息
struct Student{
	char name[20];  //姓名(字符串)
	int age;        //年龄(整数)
	char sex;       //性别(字符)
	int score;      //成绩(整数,新增信息更方便)
};

int mymain(void)
{
	struct Student a;  //声明一个Student类型的结构体变量a
	
	// 为结构体成员赋值
	strcpy(a.name, "zhangsan");  //字符串赋值需用strcpy
	a.age = 18;
	a.sex = 'M';
	a.score = 60;
	return 0;
}

对比可见,结构体通过struct Student将学生的各类信息整合在一起,新增信息只需在结构体中添加成员,且通过 “结构体变量。成员”(如a.age)能直观访问,数据关联性更强。

🔗 结构体与数组的结合:批量管理同类对象

若要记录多个学生的信息,可将结构体与数组结合,定义 “结构体数组”—— 数组的每个元素都是一个结构体变量。例如:

#include<stdio.h>
#include<string.h>

struct Student{
	char name[20];
	int age;
	char sex;
	int score;
};

int mymain(void)
{
	struct Student students[3];  //包含3个Student结构体的数组
	
	// 为第一个学生赋值
	strcpy(students[0].name, "zhangsan");
	students[0].age = 18;  
	students[0].sex = 'M';
	students[0].score = 60;
	return 0;
}
🔗 结构体数组:批量管理同类对象的优势

对比前面 “用struct Student a定义单个结构体变量” 的方式,这里使用结构体数组(如struct Student students[3])的好处很明显:通过[0]、[1]、[2]的索引编号,就能分别表示 3 个学生,无需再定义b、c等多个变量。这种方式既保留了结构体 “整合信息” 的特性,又借助数组的 “连续存储” 优势,让批量管理同类对象(如多个学生、多个传感器)变得简洁高效。

📌 结构体变量与数组的类比理解

我们可以通过已学知识类比理解结构体:

  • char a是单个字符变量,char a[]是字符数组(多个字符的集合);
  • 同理,struct Student a是单个结构体变量(一个学生的完整信息),struct Student a[]是结构体数组(多个学生的信息集合)。
    这种类比能帮我们快速上手:结构体变量像 “单个复合数据单元”,结构体数组则是 “多个复合数据单元的连续集合”,和我们对 “变量” 与 “数组” 的基本认知逻辑一致。
📏 结构体的存储空间与内存对齐

既然结构体是 “定义的类型”,那么它和普通变量一样,会占据一定的存储空间。接下来我们以struct Student为例,详细看看它的内存分布:
结构体Student的成员及各自占用的字节数如下:

  • name[20]:20 字节(字符串,含结束符);
  • age:4 字节(int 类型);
  • sex:1 字节(char 类型);
  • score:4 字节(int 类型)。
    请添加图片描述
    从图中能看到,结构体成员在内存中按定义顺序依次存储,但有个细节:sex只占 1 字节,其后却留出了 3 字节的空闲空间,并没有紧接着存储score。这并非浪费,而是编译器遵循 “四字节对齐原则” 的结果。
    对于 32 位单片机来说,它读取数据时习惯访问 “4 的整数倍地址”(如 0x00、0x04、0x08)。如果score紧跟在sex后(即从 “20+4+1=25 字节” 处开始),其地址不是 4 的倍数,单片机需要分两次读取(先读 25-28 字节,再取其中有效部分),效率会降低。而留出 3 字节空闲后,score从 28 字节(4 的 7 倍)开始存储,单片机一次就能读完整,大大提升访问效率。
✅ 验证结构体的存储与访问

为了验证上述特性,我们可以通过代码打印结构体成员的信息和地址:

#include<stdio.h>
#include<string.h>

struct Student{
	char name[20];
	int age;
	char sex;
	int score;
};

int mymain(void)
{
	struct Student a;
	strcpy(a.name, "zhangsan");
	a.age = 18;
	a.sex = 'M';
	a.score = 60;
	
	// 打印成员值,验证信息是否正确存储
	printf("a's name : %s\n\r", a.name);
	printf("a's age : %d\n\r", a.age);
	printf("a's sex : %c\n\r", a.sex);
	printf("a's score : %d\n\r", a.score);
	
	// 打印成员地址,验证内存对齐
	printf("a's name address : 0x%x\n\r", a.name);    // name首地址(结构体起始地址)
	printf("a's age address : 0x%x\n\r", &a.age);     // age地址(紧跟name,20字节处,符合4字节对齐)
	printf("a's sex address : 0x%x\n\r", &a.sex);     // sex地址(紧跟age,24字节处)
	printf("a's score address : 0x%x\n\r", &a.score); // score地址(28字节处,验证4字节对齐)
	return 0;
}

请添加图片描述

我一开始误用printf("a's sex : %s\n\r", a.sex);时出现了错误,原因是a.sex = 'M'赋值的是单个字符,需用%c格式打印;若用%s,则需赋值字符串(如a.sex = "M",此时"M"包含结束符'\0',符合字符串格式)。

📝 结构体的定义与初始化方法

最后我们再梳理结构体的定义格式,加深记忆:

struct 结构体名{
	数据类型 成员1;  
	数据类型 成员2;
	...
	数据类型 成员n;
};

针对事先定义好的struct Student(包含name、age、sex),以下是三种常用的初始化方法:

方式一:逐个成员初始化

适用于需要分步赋值或只初始化部分成员的场景:

struct Student student1;
strcpy(student1.name, "bb");
student1.age = 18;
student1.sex = 'M';
方式二:声明时按顺序初始化

适用于成员顺序明确,且需一次性初始化所有成员的场景:

struct Student student2 = {"mm", 18, 'F'};
方式三:指定成员初始化器(适用于 C99 及以上标准)

可灵活指定初始化的成员,无需严格按顺序,更清晰:

struct Student student3 = {
	.name, "jj";
	.age = 18;
	.sex = 'F';
};

结尾

这篇笔记中,我们通过对比数组的局限,认识了结构体在整合异构数据时的优势:它能将不同类型的信息打包成一个整体,结合数组还能高效管理多个同类对象。同时,我们也了解了结构体的内存对齐特性(为提高访问效率)和三种初始化方法。
结构体在嵌入式开发中应用广泛 —— 比如描述传感器的 “型号(字符串)+ 采样值(浮点数)+ 状态(整数)”,或外设的 “地址(整数)+ 配置参数(结构体嵌套)” 等。掌握结构体,能让我们更自然地映射现实中的 “复杂对象”。
Hello_Embed 会继续带你探索结构体的进阶用法,比如结构体嵌套和结构体指针。下篇笔记见。


网站公告

今日签到

点亮在社区的每一天
去签到