C语言之聚合数据

发布时间:2026/8/20 13:58:53
C语言之聚合数据 一、结构体的定义与赋值使用1.基本结构体最简单的结构体定义形式是直接定义结构体类型并声明变量。#include stdio.h struct Point{ int x; int y; }; struct Point p1; int main(){ p1.x1; p1.y0; peintf(p1.x%d,p1.y%d\n,p1.x,p1.y); return 0; }2.同时定义和声明可以在定义结构体时同时声明变量减少代码量。#include stdio.h struct Rectangle { int width; int height; } rect1,rect2;//定义了两个Rectangle类型的变量rect1和rect2 int main(){ rect1.width10; printf(%d\n,rect1.width); return 0; }3.结构体类型别名使用typedef可以为结构体类型定义别名从而简化后续的变量声明。可以用typedef struct定义的Student类型直接代替struct Student。#include stdio.h #include string.h typedef struct { int id; char name[50]; } Student; Student student1;//使用别名Student来声明变量 int main(){ student1.id1; strcpy(student1.name,John); printf(学号%d姓名%s\n,student1.id,student1.name); return 0; }typedef用法typedef long long ll; ll a;//long long类型4.嵌套结构体结构体可以包含其他结构体作为其成员这种形式称为嵌套结构体。#include stdio.h #include string.h struct Date { int day; int month; int year; }; struct Event { char title[100]; struct Date eventDate;// Date作为Event的成员 }; int main(){ struct Event e1; strcpy(e1.title,Party); e1.eventDate.day1; e1.eventDate.month10; e1.eventDate.year2027; printf(在%d年%d月%d日举办一场%s.\n,e1.eventDate.year,e1.eventDate.month,e1.eventDate.day,e1.title); return 0; }5.结构体数组可以定义结构体类型的数组以存储多个相同类型的结构体。#include stdio.h #include string.h struct Employee { int id; char name[50]; }; struct Employee employees[10];//定义一个包含10个Employee的数组 int main(){ for(int i0;isizeof(employees)/sizeof(employees[0]);i){ employees[i].idi1; strcpy(employees[i].name,employee); char idstr[10]; sprintf(idstr,%d,i1); strcat(employees[i].name,idstr); printf(工号%d姓名%s.\n,employees[i].id,employees[i].name); } return 0; }6.结构体指针可以使用指针来指向结构体常用于动态内存分配和传递结构体给函数。注意用指针访问结构体变量时不能用“.”需要用箭头“-”的形式。#include stdio.h struct Node { int data; struct Node* next; }; struct Node* head;//定义一个野指针未分配空间 int main(){ //用malloc为head指针分配一个struct Node大小的空间并强制转化为struct Node*类型 head(struct Node*)malloc(sizeof(struct Node));//指向Node结构体的指针 //head为指针需要用-的形式为其赋值。 head-data10; printf(%d\n,head-data); return 0; }7.匿名结构体可以定义没有名称的结构体并在定义变量时直接使用。#include stdio.h #include string.h struct { int x; int y; } point {10, 20};//定义并初始化一个匿名结构体变量 int main(){ printf(x%d,y%d.\n,point.x,point.y); return 0; }总结结构体是C语言中非常强大和灵活的功能能够有效地组织和管理复杂数据。通过上述几种定义形式开发者可以根据具体需求选择合适的方式来使用结构体。二、联合体一联合体的声明1.联合体的语法声明语法union union_name{ data_type1 member1; data_type2 member2; ... };示例声明union Data { int i; float f; char str[20]; };2.联合体的内存布局内存共享联合体中所有成员共享同一内存联合体的大小取决于其最大成员的大小。内存对齐与大小计算union Data data; printf(Size of union: %ld\n, sizeof(data));//输出联合体的大小二联合体的使用#include stdio.h union Data { int i; float f; char str[20]; }; int main() { union Data data; data.i 10; printf(Integer: %d\n, data.i); data.f 220.5; printf(Float: %f\n, data.f); // 因为共享内存字符串值会覆盖之前的float值 sprintf(data.str, Hello); printf(String: %s\n, data.str); return 0; }三联合体的应用1.节省内存联合体的主要用途之一是节省内存。在需要存储不同类型的数据但同一时刻只需要存储其中一种时使用联合体可以极大减少内存的浪费。例如一个设备驱动程序可能需要存储不同类型的传感器数据如温度、湿度或压力但在同一时刻只需要存储其中一个值。使用联合体可以节省内存空间。union SensorData{ int temperature; float humidity; double pressure; };在这个例子中SensorData联合体根据具体传感器类型来存储对应的数据同时只占用最大的数据类型double所需的内存空间。2.数据类型转换联合体常用于类型转换。由于联合体中的所有成员共享相同的内存修改其中一个成员会影响其他成员。通过这种特性可以方便地将同一块内存以不同类型的视角进行访问这在底层编程中非常有用。例如将一个float类型的数据转换为其二进制表示#include stdio.h union { float f; unsigned int i; } floatToInt; int main() { floatToInt.f 3.14; printf(The binary representation of 3.14 is: %x\n, floatToInt.i); return 0; }在这个例子中联合体的float和unsignedint成员共用内存因此可以通过unsignedint成员查看float的二进制表示。注意这种使用方式需要对系统的内存布局如大小端序有清晰的了解。3.协议解析在网络编程或通信协议中经常会需要从接收到的字节流中提取多种数据类型。在处理这些字节流时联合体可以帮助程序员方便地将字节数组解释为特定的数据类型。例如在处理网络协议中的数据包时可以使用联合体将字节数据转换为整数、字符等具体类型#include stdio.h union Packet { unsigned char bytes[4]; int number; }; int main() { union Packet p; p.bytes[0] 0x01; p.bytes[1] 0x02; p.bytes[2] 0x03; p.bytes[3] 0x04; printf(Integer representation: %d\n, p.number); return 0; }在这个例子中通过访问bytes数组可以从字节流中读取数据并将其解释为整数类型。这样的应用在底层网络协议、文件格式解析等场景中非常常见。4.变长数据处理当需要处理变长数据时例如某个消息可能包含不同长度的字段联合体可以帮助动态选择处理的数据类型。例如某个消息体可能根据不同的命令号有不同的结构联合体可以根据命令号选择不同的数据格式。struct Message { int command; union { int intValue; float floatValue; char str[20]; } data; };在这个例子中不同的command值可能对应int、float或字符串类型的数据通过联合体可以灵活处理不同格式的数据而无需为所有类型的数据预留空间。5.联合体和枚举的结合使用联合体常与枚举enum一起使用以区分联合体中当前存储的具体数据类型。通过枚举可以确定当前应该解释联合体中的哪个成员。例如#include stdio.h enum DataType{ INT, FLOAT, STR}; union Data{ int i; float f; char str[20]; }; struct TaggedData{ enum DataType type; union Data data; }; int main(){ struct TaggedData td; td.typeFLOAT; td.data.f3.14; if(td.typeFLOAT){ printf(Float value:%f\n,td.data.f); } return 0; }在这个例子中枚举类型DataType用来标记TaggedData结构体中当前存储的数据类型并根据标记正确访问联合体中的成员。这种模式在处理需要存储多种类型的数据时非常有用。6.硬件寄存器访问在嵌入式编程中访问硬件寄存器时通常需要同时以不同的位段形式访问寄存器中的不同字段。联合体可以帮助方便地将寄存器的二进制位段拆解为可访问的结构。union Register { unsigned int reg; struct { unsigned int flag1 : 1; unsigned int flag2 : 1; unsigned int mode : 2; unsigned int data : 12; } bits; };在这个例子中Register联合体允许既可以通过reg访问整个寄存器值也可以通过bits访问寄存器的具体位段。这在嵌入式系统和硬件驱动程序中非常常见。三、位域一定义与使用位域与联合体的区别联合体是所有类型共用一个空间位域是在同一个整数类型的内存空间内按位bit划分出多个成员。%d为有符号输出所以会将最高位当作符号位9-1001(原码)-1111(补码)--7可以按照无符号数对data进行定义unsignedchar data : 4;#include stdio.h struct Status { unsigned int flag1 : 1; unsigned int flag2 : 1; unsigned int mode : 2; //char data : 4; unsigned char data : 4; }; int main() { struct Status s; //flag1,flag2都只有1bit所以二者的值只能是0/1最大为1 s.flag1 1; //修改值 s.flag2 0; //flag3有2bit所以值可以是0/1/2/3最大为3 s.mode 3; //data有4bit所以最大值为15 s.data 9; //%u无符号输出 printf(Flag1: %d, Flag2: %d, Mode: %d, Data: %u\n, s.flag1, s.flag2, s.mode, s.data); return 0; }二位域的限制与注意事项1.位域不能被取地址位域成员不能使用指针指向它们。2.位域只适用于整型类型int、unsigned int等位域的位数不能超过宿主类型的大小。例如定义了一个char型sizeof(char)1B8bit所以最大只能是8位下面定义为9位则超出了宿主类型char的大小//错误最大为8 unsigned char data : 9;3.在不同编译器下位域的内存分配和对齐方式可能有所不同。因此位域在跨平台编程中需要谨慎使用。字节序问题大端序vs小端序在不同的平台上位域的高位和低位的存储顺序可能会不同。例如在小端序little-endian平台上低位字节存储在低地址而在大端序big-endian平台上高位字节存储在低地址。这可能会影响位域的跨平台表现。跨平台编程建议尽量避免依赖位域的内部实现细节如位域成员的排列顺序和内存布局。如果必须使用位域建议明确地处理不同平台的差异或通过封装位操作函数来提高可移植性。三位域的宿主数据类型位域的宿主数据类型通常是整型类型包括int、unsignedint、signedint甚至一些编译器支持使用short、long等类型。然而不同编译器对宿主类型的处理方式可能会有所不同存在以下特殊情况1.有符号和无符号类型的区别使用unsigned int定义位域时值只允许是非负整数。使用signed int或int定义位域时位域允许存储负数但需要考虑符号位的影响。位域的最高位将作为符号位表示数值的正负。示例struct BitField { unsignedinta : 3; // 无符号位域3位 //最高位为符号位 signedintb : 4; // 有符号位域4位 };2.宿主类型的大小限制位域的总位数不能超过宿主类型的位数。例如对于unsigned int在大多数平台上是32位因此位域定义的总位数不能超过32位。如果宿主类型为short通常是16位位域的总位数不能超过16位。示例struct BitField { unsigned short a : 5; // 正确位域大小不超过16位 unsigned short b : 11; // 正确两个位域总和不超过16位 };位数超过限制如果试图定义位域的位数大于宿主类型例如unsignedint的32位会引发错误因为位域不能存储超过宿主类型容量的数据。大多数编译器会给出编译错误或警告表明位域的位数超出了宿主类型的大小。错误示例struct BitField { unsigned int a : 33; // 错误超过32位的int位域 };自动分配的情况在某些情况下如果位域的定义使得一个宿主类型的空间无法完全容纳这些位编译器可能会自动将位域分配到下一个可用的宿主类型。这种行为通常是由编译器处理的它会确保各位域成员能够正确存储在结构体的内存中。示例struct BitField { unsigned int a : 16; // 16位 unsigned int b : 20; // 超出剩余的16位因此会分配到下一个32位整数中 };四位域与非位域成员的混合使用在结构体中位域成员和非位域成员可以混合使用但需要注意它们的存储方式和对齐。可以使用sizeof()进行检验其所占字节数。sizeof(struct MixedField);混合定义位域与非位域成员在结构体中混合使用时非位域成员会按正常对齐方式存储而位域成员会根据宿主类型的规则进行存储。示例下面位域占了3个字节3个int。struct MixedField { unsigned int flag : 1; // 位域成员占1位 int id; // 非位域成员占用完整的4字节假设int为4字节 unsigned int mode : 3; // 位域成员占3位 };对齐与填充非位域成员通常会在位域之前或之后引入额外的填充以确保对齐。这可能导致结构体的整体内存布局变得复杂并增加额外的内存开销。因此混合使用位域和非位域成员时应注意对齐带来的潜在浪费。例如如果调换一下位置则位域占了2个字节2个int位域成员flag与mode在同一个int中。struct MixedField { unsigned int flag : 1; // 位域成员占1位 unsigned int mode : 3; // 位域成员占3位 int id; // 非位域成员占用完整的4字节假设int为4字节 };