资讯动态

C语言的结构体

发布时间:2026/8/12 21:03:01 来源:尧图企业网站定制
在C语言中普通变量只能存单个数据一个数字或者一个字符但是结构体可以把一组相关的、不同类型的数据捆成一个整体。即结构体可以把一个数据元素的各个不同的数据项聚合为一个整体是一种自定义数据类型。核心作用是把多个不同类型的变量打包在一起用来描述一个事物的多个属性。本文主要介绍结构体的定义、引用最后并通过一个实例来测试实际场景中的应用一、声明格式struct 结构体名{成员列表;};成员列表包括成员类型 成员名不同属性成员类型可以相同但是成员名必须要不同例如定义学生这个结构体structStudent{intid;charname[20];//不能指针因为可能野指针intage;floatscore;};structStudentstu1;定义完结构体类型只是声明构造了一种新的类型struct 结构体名还没有创建实际变量未存储实际的数据不能在{}进行赋值、运算等实际使用还需需要进行声明创建具体的变量可真正存数据的对象如上面的struct Student stu1二、引用c语言结构体有两种成员运算符点运算符域运算符和箭头运算符分量运算符点运算符用于结构体普通变量访问成员格式变量.成员名箭头运算符用于结构体指针访问成员格式变量-成员名《》等价于*指针).成员名.的优先级高于*,(*p).成员的括号不能省略1、域运算符对结构体变量的运算必须通过成员域运算符“.”来访问结构体变量的成员方式如下结构体变量名.成员名intmain(){// 2. 定义结构体变量structStudentstu1;// 3. 给成员赋值stu1.id888;strcpy(stu1.name,王五);//字符数组定义的时候可以name[10]wangwu;这里不可以使用。使用strcpy或scanf(%s,stu1.name)stu1.age18;stu1.score99.5;// 4. 访问成员并打印printf(学号:%d\n,stu1.id);printf(姓名:%s\n,stu1.name);printf(年龄:%d\n,stu1.age);printf(成绩:%.1fd\n,stu1.score);return0;}编译运行后的结果如下通过scanf实现字符串的输入#includestdio.h#includewindows.h#includemath.hstructtest_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、分量运算符箭头运算符-#includestdio.h#includewindows.h#includemath.hstructtest_person{/* data */charname[20];intage;intclass;charaddress[20];};intmain(){SetConsoleOutputCP(65001);SetConsoleCP(65001);{/* data */structtest_personperson1;structtest_person*pperson1;//struct test_person *pmalloc(sizeof(struct test_person));p-age22;p-class2026;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.year2008;person1.time.mouth8;person1.time.day8;person1.age18;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-astruct 结构体可以放在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 bshort类型是2字节需要从2的倍数开始存储故1#需要填充b占用2#3#两个字节short c:同b正好可以从4#开始int dint占用4字节需要从4的倍数开始存储故6#7#需要填充d占用8-11字节综上一共12字节。test_person含有嵌套结构体char name[20]:占用0-19#int ageint需要从4的倍数开始正好从20#-23int class;同age24-27#struct test_struct time;内部abcd继续按照上面的原则存储a:占28#b填充29#占用30-31c占用32-33dint类型需要从36-3934、35填充char address[20];占用40-59综上占用60字节通过程序运行一下可得到如下结果2示例二#includestdio.h#includewindows.h#includemath.hstructtest_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结构体模拟主机发送数据从机接收数据。#includestdio.h#includestdint.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.head0x3A;// 帧头固定tx_frame.addr0x11;// 目标从机地址0x11tx_frame.func0x05;// 功能码0x05读数据tx_frame.data_len0x01;// 数据段长度0字节tx_frame.data[0]0x10;// 数据1tx_frame.check_h0x27;tx_frame.check_l0x00;tx_frame.tail_h0x0D;// 帧尾固定tx_frame.tail_l0x0A;// 帧尾固定// 主机发送直接把结构体转成字节流通过485总线发出去printf(主机发送485帧十六进制);uint8_t*tx_buf(uint8_t*)tx_frame;// 结构体转字节指针for(inti0;isizeof(RS485_Frame);i){printf(0x%02X ,tx_buf[i]);}printf(\n);// 模拟从机接收485指令硬件从机用结构体解析RS485_Frame rx_frame;// 定义接收帧变量// 假设从机通过485接收到数据直接存入结构体rx_frametx_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就是最大对齐方式#includestdio.h#includewindows.h#includemath.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;}运行结果分析过程

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价