【C语言】自定义类型:结构体
文章目录一、结构体的基本概念1.1结构体的声明1.2 结构体变量的创建1.2.1 在结构体声明同时创建1.2.2 在main函数中创建1.2.3 匿名结构体1.3 结构体变量的初始化1.3.1 按照默认顺序1.3.2 指定成员初始化1.4 结构体成员访问1.5 结构体的自引用二、结构体内存对齐2.1 对齐规则2.2 示例分析2.3 内存对齐的原因2.4 修改默认对齐数三、结构体传参四、结构体实现位段4.1 位段的声明4.2 位段的内存分配4.3 位段的跨平台问题4.4 位段的应用场景4.5 位段使用注意事项一、结构体的基本概念C语言已经提供了内置类型如char,short,float,double等,这些内置类型无法描述一个复杂的对象。因此C语言提供了结构体这种自定义的数据类型。它允许我们将不同类型的数据组合在一起形成一个新的自定义数据类型。这一特性极大地增强了 C 语言处理复杂数据的能力。1.1结构体的声明结构体使用关键字struct来定义例如想要描述一个学生structstudent{charname[20];// 名字intage;//年龄doubleheight;//身高doubleweight;//体重charid[16]//学号};//分号不可以少结构体是一些值的集合这些值称为成员变量。结构体中的每个成员可以是不同的数据类型如标量数组指针甚至是其他结构体。1.2 结构体变量的创建1.2.1 在结构体声明同时创建structstudent{charname[20];intage;doubleheight;doubleweight;charid[16]}s1,s2;结构体声明时创建变量是可选选项。此时创建的s1,s2是全局变量。1.2.2 在main函数中创建#includestdio.hstructstudent{charname[20];intage;doubleheight;doubleweight;charid[16]};intmain(){structstudents3;structstudents4;return0;}此时创建的变量是局部变量。1.2.3 匿名结构体在声明结构体时可以省略标签tag形成匿名结构体// 匿名结构体类型struct{inta;charb;floatc;}x;// 直接创建变量xstruct{inta;charb;floatc;}a[20],*p;// 创建数组a和指针p匿名结构体的特点如果没有对其重命名基本上只能使用一次。需要注意的是编译器会把两个成员列表相同的匿名结构体视为不同的类型因此下面的赋值是非法的px;// 错误编译器认为 p和 x的类型不同1.3 结构体变量的初始化1.3.1 按照默认顺序structstudents3{daisy,20,170,50,2410};如果结构体成员变量中包含结构体#includestdio.hstructS{charc;intn;};structA{structSs;int*p;chararr[10];};intmain(){structAa{{1,1},NULL,hhhh};}1.3.2 指定成员初始化structstudents3{.age30,.weight60,.nameRare,.height180,.id2410};1.4 结构体成员访问使用操作符.可以直接访问结构体中的成员例如打印结构体成员#includestdio.hstructstudent{charname[20];// 名字intage;//年龄doubleheight;//身高doubleweight;//体重charid[16];//学号};intmain(){structstudents1{daisy,20,170,50,2410};printf(%s %d %f %f %s\n,s1.name,s1.age,s1.height,s1.weight,s1.id);}使用操作符-可以间接访问成员一般用于访问指针变量指向的内容#includestdio.hstructPoint{intx;inty;};intmain(){structPointp{3,4};structPoint*ptrp;ptr-x10;ptr-y20;printf(x %d y %d\n,ptr-x,ptr-y);return0;}1.5 结构体的自引用结构体可以包含指向自身类型的指针这在数据结构中非常常见如链表、树等。错误的自引用方式structNode{intdata;structNodenext;// 错误会导致结构体大小无限大};正确的自引用方式structNode{intdata;//数据域structNode*next;// 指针域//正确使用指针};使用typedef对结构体重命名时需要注意自引用的正确写法错误写法typedefstruct{intdata;Node*next;// 错误此时Node尚未定义}Node;正确写法typedefstructNode{intdata;structNode*next;// 正确使用struct Node}Node;二、结构体内存对齐计算结构体的大小是C语言中的一个重要考点涉及到内存对齐规则。2.1 对齐规则结构体的第一个成员对齐到与结构体变量起始位置偏移量为0的地址处。其他成员变量要对齐到某个对齐数的整数倍的地址处。对齐数编译器默认的一个对齐数 与 该成员变量大小的较小值.VS中默认对齐数为8Linux中gcc没有默认对齐数对齐数就是成员自身的大小结构体总大小为最大对齐数结构体中所有成员对齐数的最大值的整数倍。如果嵌套了结构体嵌套的结构体成员对齐到自己的最大对齐数的整数倍处结构体的整体大小是所有最大对齐数含嵌套结构体的的整数倍。2.2 示例分析练习1structS1{charc1;// 大小1对齐数1inti;// 大小4对齐数4VS环境charc2;// 大小1对齐数1};printf(%d\n,sizeof(structS1));// 输出结果12分析c1从偏移0开始占用1字节0i需要对齐到4的倍数从偏移4开始占用4字节4-7c2从偏移8开始占用1字节8总大小需为最大对齐数4的倍数因此总大小为12练习2structS2{charc1;// 1字节charc2;// 1字节inti;// 4字节};printf(%d\n,sizeof(structS2));// 输出结果8分析c1在0c2在1共占用2字节i对齐到4的倍数从4开始占用4字节4-7总大小8是最大对齐数4的倍数练习3structS3{doubled;// 8字节charc;// 1字节inti;// 4字节};printf(%d\n,sizeof(structS3));// 输出结果16分析d在0-78字节c在81字节i需要对齐到4的倍数从12开始12-15总大小16是最大对齐数8的倍数练习4嵌套结构体structS4{charc1;// 1字节structS3s3;// 16字节最大对齐数8doubled;// 8字节};printf(%d\n,sizeof(structS4));// 输出结果32分析c1在01字节s3需要对齐到自身最大对齐数8的倍数从8开始占用16字节8-23d对齐到8的倍数从24开始占用8字节24-31总大小32是最大对齐数8的倍数2.3 内存对齐的原因平台原因移植性某些硬件平台只能访问特定地址的数据否则会抛出异常。性能原因对齐的内存访问只需一次操作未对齐的可能需要两次。例如访问未对齐的8字节数据可能需要读取两个8字节块并拼接。内存对齐是空间换时间的策略。设计结构体时应将占用空间小的成员集中在一起以减少内存浪费如S2比S1更节省空间。2.4 修改默认对齐数可以使用#pragma pack预处理指令修改默认对齐数#includestdio.h#pragmapack(1)// 设置默认对齐数为1structS{charc1;// 1字节inti;// 4字节charc2;// 1字节};#pragmapack()// 取消设置还原默认intmain(){printf(%d\n,sizeof(structS));// 输出6141return0;}当对齐方式不合适时可通过此指令优化内存使用。三、结构体传参结构体传参有两种方式传值和传地址。structS{intdata[1000];intnum;};structSs{{1,2,3,4},1000};// 传值voidprint1(structSs){printf(%d\n,s.num);}// 传地址voidprint2(structS*ps){printf(%d\n,ps-num);}intmain(){print1(s);// 传结构体print2(s);// 传地址return0;}推荐使用传地址方式原因是函数传参时参数需要压栈结构体过大时传值会导致大量的时间和空间开销传地址仅传递4或8字节指针大小效率更高若需要修改结构体内容传地址是必要的四、结构体实现位段位段bit-field是结构体的一种特殊形式用于节省内存空间尤其适合存储只需要少量bit位的数据。4.1 位段的声明位段的声明与结构体类似但有两个区别位段成员通常是int、unsigned int、signed int或char类型成员名后有一个冒号和数字表示占用的bit位数structA{int_a:2;// 占用2个bit位int_b:5;// 占用5个bit位int_c:10;// 占用10个bit位int_d:30;// 占用30个bit位};printf(%d\n,sizeof(structA));// 输出8分析前三个成员共占用251017个bit位加上_d的30位总共47位位段以4字节int或1字节char为单位开辟空间因此需要8字节64位4.2 位段的内存分配存在矛盾位段空间按4字节int或1字节char的方式开辟位段成员在内存中的分配方向左到右或右到左未定义当剩余空间不足时是否利用剩余空间未定义示例structS{chara:3;// 3位charb:4;// 4位charc:5;// 5位chard:4;// 4位};structSs{0};s.a10;// 二进制1010只取低3位010s.b12;// 二进制1100s.c3;// 二进制11s.d4;// 二进制100在VS环境中的内存分配每次分配1字节分配方向为从右到左第一个字节a占3位010b占4位1100共7位剩余1位未使用第二个字节c占5位00011第三个字节c剩余0位d占4位01004.3 位段的跨平台问题位段存在以下跨平台问题需谨慎使用int位段的符号性不确定有符号/无符号位段最大位数不确定16位机器最大1632位机器最大32成员分配方向左到右/右到左未定义剩余空间处理方式不确定4.4 位段的应用场景位段适合存储不需要完整字节的数据例如网络协议中的数据报格式使用位段可以精确控制每个字段的位数显著减少数据报大小提高网络传输效率。4.5 位段使用注意事项位段成员没有地址因此不能使用操作符也不能直接用scanf输入structA{int_a:2;int_b:5;};intmain(){structAsa{0};// scanf(%d, sa._b); // 错误不能取位段成员地址// 正确方式intb0;scanf(%d,b);sa._bb;return0;}