【C语言】结构体
有时候需要将不同类型的数据组合为一个整体,以便于引用。例如,一名学生有学号、姓名、性别、年龄等属性,如果针对每个属性都单独定义一个变量,那么当有多名学生时变量就难以分清。结构体就是用来管理不同类型的数据组合。
结构体的声明与定义
结构体的声明一般形式为:
struct 结构体名{
成员1;
成员2;
...
}; //注意分号
例如:
struct student{
int num;
char name[20];
char gender;
int age;
};
结构体定义:
//struct 结构体名 变量名
struct student s;
结构体的初始化只能在定义时进行:
struct student s={100,"John",'M',20}; //定义并初始化
如果已经定义了结构体,就不能再初始化了,只能对它的每个成员单独赋值,如s.num=1;
结构体成员的访问
采用“结构体变量名.成员名”的形式来访问结构体成员。
#include <stdio.h>
//工作中往往把结构体声明放在头文件中
struct student{
int num;
char name[20];
char gender;
int age;
};
int main() {
struct student s={100,"John",'M',20}; //定义并初始化
struct student sarr[3]; //结构体数组
s.age = 21;
printf("%d %s %c %d\n",s.num,s.name,s.gender,s.age);
printf("---------------------\n");
for (int i = 0; i < 3; ++i) {
scanf("%d%s %c%d",&sarr[i].num,sarr[i].name,
&sarr[i].gender,&sarr[i].age);
}
for (int i = 0; i < 3; ++i) {
printf("%d %s %c %d\n",sarr[i].num,sarr[i].name,
sarr[i].gender,sarr[i].age);
}
return 0;
}
执行结果:
结构体指针
一个结构体变量的指针就是该变量所占据内存段的起始地址。
结构体指针的使用:
#include <stdio.h>
struct student{
int num;
char name[20];
char gender;
int age;
};
int main() {
struct student sarr[3]={1,"KK",'F',18,
2,"HH",'M',20,
3,"LL",'M',19};
struct student *p; //定义结构体指针
p=sarr;
//获取成员方法一,成员运算符优先级高于取值运算符,所以要加括号(*p).num
printf("%d %s %c %d\n",(*p).num,(*p).name,(*p).gender,(*p).age);
//获取成员方法二,更为常用的方法
printf("%d %s %c %d\n",p->num,p->name,p->gender,p->age);
printf("---------------\n");
p=p+1;
printf("%d %s %c %d\n",p->num,p->name,p->gender,p->age);
return 0;
}
执行结果:
typedef 的使用
typedef 声明新的类型名来代替已有的类型名。
#include <stdio.h>
typedef struct student{
int num;
char name[20];
char gender;
int age;
}stu,*pstu; //stu等价于struct student,pstu等价于struct student*
typedef int INTEGER;
int main() {
stu s={1,"KK",'M',20};
pstu p=&s;
INTEGER i=10;
printf("i=%d,p->num=%d\n",i,p->num);
return 0;
}
typedef 代替结构体能简化定义结构体编写;代替基础类型时能方便修改类型,而不需要到函数中逐个修改。
结构体对齐
结构体对齐是为了cpu高效存取内存上的数据。
先运行以下程序观察结构体的大小:
#include <stdio.h>
typedef struct A {
int a;
char b;
short c;
}A;
typedef struct B {
char a;
int b;
short c;
}B;
int main() {
A a;
B b;
printf("A size is %d\n",sizeof(a));
printf("B size is %d\n",sizeof(b));
return 0;
}
执行结果:
为什么这两个结构体所占字节大小不一致?这里按下不表,先来学习下结构体的对齐规则。
结构体对齐规则
结构体的对齐规则:
- 第一个成员的首地址为0
- 每个成员的首地址是自身大小的整数倍
- 结构体的总大小是其成员中所含最大类型的整数倍
结构体A对齐后占8个字节:
结构体B对齐后占12个字节:
指定默认对齐字节数
通过 #pragma pack()
宏命令可以指定默认对齐字节数,可选参数有 1/2/4/8/16,不带参或参数非以上值,将恢复默认值。
//要成对使用,防止对之后的结构体对齐产生影响
#pragma pack(2) //按照2字节对齐
typedef struct B {
char a;
int b;
short c;
}B;
#pragma pack() //取消指定对齐,恢复缺省对齐,
但是若结构体中的最大数据类型比宏声明的小,该声明无效,按照原来的规则进行。
结构体对齐的原因
结构体对齐准确来说是结构体内存对齐。
先来了解下对齐和非对齐的概率:当cpu需要连续取4个字节的数据时,若数据的起始地址能被4整除,则称其对齐访问,反之,则为非对齐访问。
若数据总线宽度为32位,即cpu一次可读取4字节数据,现代计算机是按字节编址,为什么规定cpu只能从地址为4的倍数开始读,而不能从任意位置开始连续读4个字节?
这需要一定的计算机组成原理知识,这里仅进行简要描述。下面给出简单的主存储器的基本组成框图。
数据总线宽度为32位,所以在按字节编址方式下,每次最多可以存取4个存储单元的内容。MAR给出要存取的地址,经过地址译码器译码后一根字选线有效,读取该行的4个存储单元内容。cpu从地址为4的倍数开始连续读4个字节只需一次访存。有些复杂指令集的cpu(如x86)虽然可以完成非对齐访问,但cpu也不是一次性读出四个字节,而是采取多次读取对齐的内存,然后进行数据拼接,从而实现对非对齐数据的访问。
例如,一个4字节的int型数据被在地址为4,5,6,7的存储单元中,只需一次访问即可读出;若放在地址为2,3,4,5的存储单元中,那么就需要两次访存才能读出。
结构体对齐带来了以下好处:
- 节省内存:特别是在嵌入式软件开发中。
- 硬件要求:许多硬件平台要求数据对齐到特定的内存边界。 如果不满足这些要求,可能会导致硬件异常。
- 性能优化:对齐的数据可以更快地被cpu访问,非对齐的数据可能需要多次内存访问才能完全读取或写入,这会降低性能。
本文来自博客园,作者:hzyuan,转载请注明原文链接:https://www.cnblogs.com/hzyuan/p/17963261
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 如何使用 Uni-app 实现视频聊天(源码,支持安卓、iOS)
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)