当前位置: 首页 > news >正文

C语言的结构体

在C语言中,普通变量只能存单个数据(一个数字或者一个字符),但是结构体可以把一组相关的、不同类型的数据捆成一个整体。
即:结构体可以把一个数据元素的各个不同的数据项聚合为一个整体,是一种自定义数据类型。核心作用是把多个不同类型的变量打包在一起,用来描述一个事物的多个属性。
本文主要介绍结构体的定义、引用,最后并通过一个实例来测试实际场景中的应用

一、声明格式
struct 结构体名{
成员列表;
};
成员列表包括:成员类型 成员名
不同属性成员类型可以相同,但是成员名必须要不同
例如定义学生这个结构体:

structStudent{intid;charname[20];//不能指针,因为可能野指针intage;floatscore;};structStudentstu1;

定义完结构体类型只是声明构造了一种新的类型(struct 结构体名),还没有创建实际变量,未存储实际的数据,不能在{}进行赋值、运算等
实际使用还需需要进行声明(创建具体的变量,可真正存数据的对象),如上面的struct Student stu1

二、引用
c语言结构体有两种成员运算符:点运算符(域运算符)和箭头运算符(分量运算符)
点运算符:用于结构体普通变量访问成员:格式:变量.成员名
箭头运算符:用于结构体指针访问成员,格式:变量->成员名《==》等价于(*指针).成员名
.的优先级高于*,(*p).成员的括号不能省略
1、域运算符
对结构体变量的运算必须通过成员域运算符“.”来访问结构体变量的成员,方式如下
结构体变量名.成员名

intmain(){// 2. 定义结构体变量structStudentstu1;// 3. 给成员赋值stu1.id=888;strcpy(stu1.name,"王五");//字符数组定义的时候,可以name[10]="wangwu";这里不可以使用。使用strcpy或scanf("%s",stu1.name)stu1.age=18;stu1.score=99.5;// 4. 访问成员并打印printf("学号:%d\n",stu1.id);printf("姓名:%s\n",stu1.name);printf("年龄:%d\n",stu1.age);printf("成绩:%.1fd\n",stu1.score);return0;}

编译运行后的结果如下:

通过scanf实现字符串的输入:

#include<stdio.h>#include<windows.h>#include<math.h>structtest_person{/* data */charname[20];intage;intclass;charaddress[20];};intmain(){SetConsoleOutputCP(65001);SetConsoleCP(65001);{/* data */structtest_personperson1;scanf("%s %d %d %s",person1.name,&person1.age,&person1.class,person1.address);printf("姓名:%s\n",person1.name);printf("年龄:%d\n",person1.age);printf("年级:%d\n",person1.class);printf("地址:%s\n",person1.address);};return0;}

运行结果:

2、分量运算符(箭头运算符)->

#include<stdio.h>#include<windows.h>#include<math.h>structtest_person{/* data */charname[20];intage;intclass;charaddress[20];};intmain(){SetConsoleOutputCP(65001);SetConsoleCP(65001);{/* data */structtest_personperson1;structtest_person*p=&person1;//struct test_person *p=malloc(sizeof(struct test_person));p->age=22;p->class=2026;scanf("%s %s",person1.name,person1.address);printf("姓名:%s\n",p->name);printf("年龄:%d\n",p->age);printf("年级:%d\n",p->class);printf("地址:%s\n",p->address);};return0;}

运行结果:

3结构体嵌套的引用

structtest_birthday{intyear;intmouth;intday;};structtest_person{/* data */charname[20];intage;intclass;structtest_birthdaytime;charaddress[20];};intmain(){SetConsoleOutputCP(65001);SetConsoleCP(65001);{/* data */structtest_personperson1;person1.time.year=2008;person1.time.mouth=8;person1.time.day=8;person1.age=18;printf("年龄:%d\n",person1.age);printf("出生年:%d\n",person1.time.year);printf("出生月份:%d\n",person1.time.mouth);};return0;}

运行结果:

核心判断规则:每一步看当前是什么:
如果是结构体变量(实体):后面跟.
如果是结构体指针:后面跟->

情况写法
外层变量,内层结构体变量obj.in.a
外层变量,内层结构体指针obj.pin->a
外层指针,内层结构体变量p->in.a
外层指针,内层结构体指针p->pin->a

struct 结构体可以放在main函数,作用域只限于main函数,也可以放到函数外面,此时作用域是全局变量。
三、结构体的特点和内存分布
1、特点
允许存在多种类型:可以把int、char、float、数组甚至其他结构体打包在一起;
自定义性强:可以根据需求创建任意类型
整体管理:一个结构体变量能代表一个完整事物,不用维护一堆零散变量。
2、内存分布
计算结构体的大小,需要考虑到内存单元的分布,结构体在内存中按照单元存储的,每个单元多大,是跟结构体中最大的基本类型有关。
存储方式:
把第一个成员变量的存储地址看成0
把第二个成员变量的存储地址就是它所占的字节数的倍数
。。。。
整个结构体的大小是最大的基本类型的倍数。
(1)示例一

structtest_struct{chara;shortb;shortc;intd;};structtest_person{charname[20];intage;intclass;structtest_structtime;charaddress[20];};intmain(){SetConsoleOutputCP(65001);SetConsoleCP(65001);{printf("test_struct的长度是%d\n",sizeof(structtest_struct));printf("test_person的长度是%d\n",sizeof(structtest_person));};return0;}

我们按照上面的存储规则进行计算存储大小:
test_struct的大小如下存储:
char a:从0开始存储,占用1字节
short b:short类型是2字节,需要从2的倍数开始存储,故1#需要填充,b占用2#,3#两个字节
short c:同b,正好可以从4#开始
int d:int占用4字节,需要从4的倍数开始存储,故6#7#需要填充,d占用8-11字节
综上一共12字节。
test_person含有嵌套结构体:
char name[20]:占用0-19#
int age:int需要从4的倍数开始,正好从20#-23
int class;同age,24-27#
struct test_struct time;(内部abcd继续按照上面的原则存储):a:占28#,b:填充29#,占用30-31,c:占用32-33;d:int类型需要从36-39(34、35填充)
char address[20];占用40-59
综上占用60字节

通过程序运行一下,可得到如下结果:

(2)示例二

#include<stdio.h>#include<windows.h>#include<math.h>structtest_struct{chara;intb;shortc;};structtest_person{chard;structtest_structf;intg;};intmain(){SetConsoleOutputCP(65001);SetConsoleCP(65001);{printf("test_struct的长度是%d\n",sizeof(structtest_struct));printf("test_person的长度是%d\n",sizeof(structtest_person));};return0;}

运行结果:

分析过程:


test_struct是一个结构体,是4字节对齐,它的第一个成员变量应该放到最大成员变量所占字节数的倍数地址上。

这种数据对齐方式(不限结构体这样存储,其他也是),可以提升访问性能(访问对应的变量可以尽可能少的存取周期),但是造成了空间的浪费。
两个相同结构体,成员相同,但是排列顺序不同,其占空间也是不一样的
四、实际场景练习
1、需求说明
使用结构体描述硬件通信数据的485通信
485通信的核心固定格式是:

帧头(固定)从机地址功能码数据长度数据段校验和帧尾(固定)
1字节1字节1字节1字节n字节1字节1字节

2、实现逻辑
先定义帧结构体、实现求校验和函数,主机声明485结构体,模拟主机发送数据,从机接收数据。

#include<stdio.h>#include<stdint.h>// 1. 定义485通信数据帧---结构体类型typedefstruct{uint8_thead;//帧头uint8_taddr;//设备地址uint8_tfunc;//功能码uint8_tdata_len;//数据长度uint8_tdata[10];//数据区:最多存10个字节uint8_tcheck_h;//校验和uint8_tcheck_l;//校验和uint8_ttail_h;//帧尾uint8_ttail_l;//帧尾}RS485_Frame;intmain(){// ===================== 模拟主机:打包485指令()=====================RS485_Frame tx_frame;// 定义发送帧变量// 填充指令(给从机发:地址1,读数据,数据段2字节)tx_frame.head=0x3A;// 帧头固定tx_frame.addr=0x11;// 目标从机地址=0x11tx_frame.func=0x05;// 功能码=0x05(读数据)tx_frame.data_len=0x01;// 数据段长度=0字节tx_frame.data[0]=0x10;// 数据1tx_frame.check_h=0x27;tx_frame.check_l=0x00;tx_frame.tail_h=0x0D;// 帧尾固定tx_frame.tail_l=0x0A;// 帧尾固定// 主机发送:直接把结构体转成字节流,通过485总线发出去printf("主机发送485帧(十六进制):");uint8_t*tx_buf=(uint8_t*)&tx_frame;// 结构体转字节指针for(inti=0;i<sizeof(RS485_Frame);i++){printf("0x%02X ",tx_buf[i]);}printf("\n");// ===================== 模拟从机:接收485指令(硬件从机用结构体解析)=====================RS485_Frame rx_frame;// 定义接收帧变量// 假设从机通过485接收到数据,直接存入结构体rx_frame=tx_frame;// 从机解析指令:直接访问结构体成员printf("\n从机解析结果:\n");printf("帧头:0x%02X\n",rx_frame.head);printf("从机地址:%d\n",rx_frame.addr);printf("功能码:0x%02X(读数据)\n",rx_frame.func);printf("数据长度:%d字节\n",rx_frame.data_len);printf("数据段:0x%02X 0x%02X\n",rx_frame.data[0],rx_frame.data[1]);printf("校验和:0x%02X(数据有效)\n",rx_frame.check_h);printf("帧尾:0x%02X\n",rx_frame.tail_h);return0;}

运行结果如下:

通过实践可以看出:
485通信贴合硬件数据格式,发送时打包一个结构体,接收时直接解析成员,不用拼接字节也不用算偏移。
硬件通信最怕字节顺序错,结构体固定成员顺序,可杜绝bug;
扩展性好,后续增加数据或状态位,直接在结构体里添加成员就可以。
五、注意事项
1、不同编译器以及编译选项的属性,系统为它分配的存储空间会有所不同
在存储该结构时会按照不同的内存对齐规则进行相关处理。
32位的操作系统,是按照最大不超过4字节对齐的;
64位的操作系统,按最大的基本类型对齐
程序员可以自己指定对齐方式:(在程序的最上面增加#pragma pack(n),n就是最大对齐方式)

#include<stdio.h>#include<windows.h>#include<math.h>#pragmapack(2);structtest_struct{chara;intb;shortc;};#pragmapack()structtest_person{chard;structtest_structf;intg;};intmain(){SetConsoleOutputCP(65001);SetConsoleCP(65001);{printf("test_struct的长度是%d\n",sizeof(structtest_struct));printf("test_person的长度是%d\n",sizeof(structtest_person));};return0;}

运行结果:

分析过程:

http://www.jsqmd.com/news/1380812/

相关文章:

  • 2026溯源品牌成长:仁汇集团17年深耕,造就搜好房行业**地位 - 滚动商讯
  • KMP算法核心原理:next数组构建与字符串高效匹配详解
  • Path of Building Community:打造《流放之路》终极离线构建规划器
  • KunPeng 之 BoostKit
  • 解决Windows/Linux混合刻录文件丢失:ISO 9660、UDF与Rock Ridge详解
  • UE5高斯泼溅插件实战:3DGS技术原理与工程化应用全解析
  • SSM框架房屋租赁系统开发与数据库优化实践
  • Ubuntu 22.04.1 LTS 桌面版安装与配置全指南:从零到开发环境搭建
  • XUnity AutoTranslator:Unity游戏实时翻译框架的深度解析与实战指南
  • Android 15 16 wifi热点选项缺少Dual Band
  • JMeter永久汉化:修改启动脚本实现一劳永逸的中文界面配置
  • UI学习: UI的初步了解
  • 【ORC】如何选择合适的压缩算法以平衡 CPU 和存储成本?ZSTD 的压缩级别如何调整?
  • 大语言模型生命周期解析:预训练、微调与推理的核心原理与实践
  • Retrofit - Call 接口与 Observable 接口
  • 从能用进阶到会玩:pytest自动化测试框架深度解析与实战
  • 2026年8月智能仓储厂家:自动化智能货架仓储生产企业深度盘点
  • 网上预约公立医院慢病防癌筛查体检,要看哪些服务能力?卫蓝鲸鱼帮你分析 - 滚动商讯
  • 【硬核计算】只靠阳光和水,一辆氢能车到底能不能日常代步?——光伏制氢 + 纯氢内燃机短途闭环的能量账与技术剖析
  • 眼视光设备采购指南:从设备选型到服务闭环,AI数字视光技术应用与投资回报分析
  • ai相关: linux安装codex
  • 免费开源AI软件.桌面单机版,可移动的AI知识库,察元 AI桌面版:Windows装包被防病毒拦了 看安装日志和签名链的实战
  • GEO 培训哪家靠谱:【沐晞甄选】以诚为本 - 18002239949
  • 2026合规为王!杭州滴滴**直营二手网约车全资质车辆清单解读 - 滚动商讯
  • Claude服务中断与能力分层:AI模型架构变革与开发者应对策略
  • Token Router 实战:基于预算的智能 API 路由与成本管控方案
  • 函数调用参数不匹配错误全解析:从C语言到命令行
  • 叠叠高:3D 方块精准叠放,越叠越快越上头
  • HS2-HF_Patch汉化补丁:一站式解决Honey Select 2本地化与增强需求
  • Pythonic COMSOL多物理场仿真:基于JPype架构的高性能自动化接口设计