2026/1/8 8:55:58
网站建设
项目流程
网站查外链,郑州网络公司联系方式,网站新闻页面无法翻页,做网站怎么实现在线支付自定义类型
目录
自定义类型 1. 结构体#xff08;struct#xff09;#xff1a;复杂对象的 “组合框架”
1.1 结构体声明与变量定义
1.2 结构体初始化#xff08;顺序 / 指定成员#xff09;
1.3 特殊声明#xff1a;匿名结构体#xff08;仅用一次#xff09;
…自定义类型目录自定义类型1. 结构体struct复杂对象的 “组合框架”1.1 结构体声明与变量定义1.2 结构体初始化顺序 / 指定成员1.3 特殊声明匿名结构体仅用一次1.4 结构体自引用链表节点核心1.5 结构体重命名typedef 用法与易错点1.6 结构体内存对齐底层规则 计算实战1.6.1 对齐核心规则VS 环境默认对齐数 8gcc 无默认对齐数1.6.2 对齐数定义1.6.3 4 个计算实战逐行拆解练习 1成员顺序影响大小练习 2优化成员顺序节省空间练习 3包含 double 类型练习 4嵌套结构体1.6.4 为什么需要内存对齐1.6.5 修改默认对齐数#pragma pack1.6.6 计算成员偏移量offsetof 宏1.7 结构体传参传值 vs 传地址1.8 结构体位段内存优化神器1.8.1 位段的声明规则1.8.2 位段的内存分配细节VS 环境1.8.3 位段的跨平台问题⚠️ 重点1.8.4 位段的实际应用IP 协议头1.8.5 位段的注意事项不能用 操作符2. 联合体union数据的 “共享空间”2.1 联合体声明与核心特点关键特性修改一个成员会覆盖其他成员2.2 联合体大小计算对齐规则练习 1成员为数组 int练习 2成员为短数组 int2.3 联合体实战应用应用 1判断机器大小端比结构体更简洁应用 2存储互斥数据节省内存3. 枚举enum有限值的 “一一列举”3.1 枚举声明与赋值规则3.2 枚举的优势对比 #define3.3 枚举实战菜单程序4. 三大自定义类型对比适用场景 区别核心区别总结 最后总结✨引言在 C 语言中内置类型char、int、float等只能描述简单数据但现实中的数据往往是复杂的比如 “一本书”“一个人”“一份礼品单”。这时就需要自定义类型来组合不同类型的数据实现复杂对象的描述。本文将从 “基础用法→底层原理→实战应用” 三层详细拆解结构体、联合体、枚举三大自定义类型。1. 结构体struct复杂对象的 “组合框架”结构体就像一个 “收纳盒”可以把不同类型的数据成员变量装在一起描述一个复杂对象。比如用结构体描述 “一本书”需要包含书名、作者、价格、书号等不同类型的信息。1.1 结构体声明与变量定义核心语法struct 结构体名 { 成员变量列表; } 变量名;变量定义分三种全局变量、局部变量、结构体数组。#define _CRT_SECURE_NO_WARNINGS 1 #include stdio.h // 声明一个结构体描述“书”的框架 struct Book { char name[20]; // 书名字符串 char author[20]; // 作者字符串 float price; // 价格浮点型 char id[13]; // 书号字符串如ISBN } b4, b5, b6; // 声明时直接定义变量全局变量整个文件可用 struct Book b2; // 全局变量声明后单独定义 int main() { struct Book b1; // 局部变量仅main函数内可用 struct Book book_arr[5]; // 结构体数组存储5本书的信息 return 0; } 注意结构体名是 “类型名”就像int一样必须结合struct使用除非用typedef重命名。1.2 结构体初始化顺序 / 指定成员初始化分两种方式顺序初始化按成员顺序赋值和指定成员初始化不按顺序用.成员名指定后者更灵活。int main() { // 1. 顺序初始化必须按结构体成员声明顺序赋值 struct Book b1 { 鹏哥C语言, 鹏哥, 18.8f, PG10001 }; // 2. 指定成员初始化可跳过成员、打乱顺序C99支持 struct Book b2 { .id PG10002, // 直接指定成员名赋值 .author 鹏哥, .name C语言程序设计, .price 8.8f }; // 打印验证 printf(书名%s作者%s价格%.1f\n, b2.name, b2.author, b2.price); return 0; }✅ 输出书名C语言程序设计作者鹏哥价格8.81.3 特殊声明匿名结构体仅用一次如果结构体只需要使用一次可以省略结构体名匿名但无法重复定义该类型的变量。// 匿名结构体无类型名仅定义变量s全局 struct { char c; // 字符 int i; // 整数 double d; // 双精度浮点 } s { x, 100, 3.14 }; int main() { printf(%c %d %.2lf\n, s.c, s.i, s.d); // 输出x 100 3.14 return 0; } // ❌ 易错点两个匿名结构体即使成员完全相同也是不同类型 struct { char c; int i; double d; } s1; struct { char c; int i; double d; } *ps; int main() { ps s1; // 编译报错类型不兼容编译器认为是两个不同结构体 return 0; }1.4 结构体自引用链表节点核心结构体自引用是指结构体成员中包含 “指向自身类型的指针”这是实现链表、树等数据结构的核心。❌ 错误写法直接包含自身类型成员结构体大小无限无法计算struct Node { int data; // 数据域 struct Node n; // 错误结构体包含自身大小无穷大 };✅ 正确写法包含自身类型的指针指针大小固定如 4/8 字节struct Node { int data; // 数据域存储实际数据 struct Node* next;// 指针域指向同类型下一个节点 }; int main() { struct Node n1; // 节点1 struct Node n2; // 节点2 n1.next n2; // 链表串联n1指向n2 return 0; } 比喻每个节点就像 “一节火车车厢”data是车厢里的货物next是连接下一节车厢的挂钩。1.5 结构体重命名typedef 用法与易错点用typedef可以给结构体重命名简化书写无需每次写struct但要注意重命名的顺序。❌ 错误写法重命名前使用新类型名typedef struct Node { int data; Node* next; // 错误此时Node还未完成重命名编译器不识别 } Node; // 重命名struct Node为Node✅ 正确写法重命名前用原始类型名// 方式1先声明结构体再重命名 struct Node { int data; struct Node* next; // 正确用原始类型名struct Node }; typedef struct Node Node; // 重命名后可直接用Node定义变量 // 方式2声明重命名合并推荐 typedef struct Node { int data; struct Node* next; // 仍需用struct Node因为Node还在定义中 } Node; int main() { Node n; // 简化书写等价于struct Node n return 0; }1.6 结构体内存对齐底层规则 计算实战结构体的成员在内存中不是连续排列的而是遵循内存对齐规则—— 这是为了平衡 “平台兼容性” 和 “访问效率”牺牲少量空间换取更快的读取速度。1.6.1 对齐核心规则VS 环境默认对齐数 8gcc 无默认对齐数第一个成员对齐到结构体起始地址偏移量 0后续成员对齐到「自身大小」和「默认对齐数」的较小值称为 “实际对齐数”的整数倍地址结构体总大小是「所有成员实际对齐数的最大值」的整数倍嵌套结构体嵌套的结构体成员对齐到其内部最大对齐数的整数倍总大小仍遵循规则 3。1.6.2 对齐数定义实际对齐数 min(成员变量大小, 编译器默认对齐数)例int大小 4VS 默认对齐数 8 → 实际对齐数 4。1.6.3 4 个计算实战逐行拆解练习 1成员顺序影响大小struct S1 { char c1; // 大小1实际对齐数1 → 偏移量0 int i; // 大小4实际对齐数4 → 偏移量401不够4补3字节 char c2; // 大小1实际对齐数1 → 偏移量8448 }; // 最大对齐数4总大小需是4的整数倍 → 819→补3→12 int main() { printf(%zd\n, sizeof(struct S1)); // 输出12 }练习 2优化成员顺序节省空间struct S2 { char c1; // 偏移量0 char c2; // 偏移量11是1的整数倍 int i; // 偏移量4112不够4补2字节 }; // 最大对齐数4总大小448无需补位 int main() { printf(%zd\n, sizeof(struct S2)); // 输出8 } 优化技巧将占用空间小的成员集中在一起减少补位浪费。练习 3包含 double 类型struct S3 { double d; // 大小8实际对齐数8 → 偏移量0 char c; // 大小1实际对齐数1 → 偏移量8 int i; // 大小4实际对齐数4 → 偏移量12819不够12补3字节 }; // 最大对齐数8总大小1241616是8的整数倍 int main() { printf(%zd\n, sizeof(struct S3)); // 输出16 }练习 4嵌套结构体struct S3 { double d; char c; int i; }; // 大小16内部最大对齐数8 struct S4 { char c1; // 偏移量0 struct S3 s3; // 对齐到8的整数倍 → 偏移量801不够8补7字节 double d; // 大小8实际对齐数8 → 偏移量81624 }; // 最大对齐数8总大小2483232是8的整数倍 int main() { printf(%zd\n, sizeof(struct S4)); // 输出32 }1.6.4 为什么需要内存对齐平台兼容性某些硬件只能访问特定地址的数据如只能访问 4 的整数倍地址不对齐会导致程序崩溃访问效率CPU 读取内存时按 “块” 读取如每次读 8 字节对齐后数据只需读一次未对齐可能需要读两次。1.6.5 修改默认对齐数#pragma pack用#pragma pack(n)可以修改默认对齐数n 必须是 2 的幂如 1、2、4、8#pragma pack()恢复默认。#pragma pack(1) // 设置默认对齐数1取消对齐紧凑排列 struct S { char c1; // 偏移0 int i; // 偏移11是1的整数倍 char c2; // 偏移5 }; // 总大小1416无需补位 #pragma pack() // 恢复默认对齐数 int main() { printf(%zd\n, sizeof(struct S)); // 输出6 }1.6.6 计算成员偏移量offsetof 宏offsetof(type, member)是 C 语言内置宏用于计算结构体成员相对于起始地址的偏移量头文件stddef.h。#include stddef.h struct S1 { char c1; int i; char c2; }; int main() { printf(c1偏移量%zd\n, offsetof(struct S1, c1)); // 0 printf(i偏移量%zd\n, offsetof(struct S1, i)); // 4 printf(c2偏移量%zd\n, offsetof(struct S1, c2)); // 8 return 0; }1.7 结构体传参传值 vs 传地址结构体传参有两种方式传值拷贝整个结构体和传地址传递指针推荐用传地址。struct S { int arr[1000]; // 大数组结构体体积大 int n; double d; }; // 方式1传值拷贝整个结构体浪费空间和时间 void print1(struct S tmp) { for (int i 0; i 5; i) { printf(%d , tmp.arr[i]); // 访问拷贝后的成员 } } // 方式2传地址仅传递4/8字节指针高效 // const修饰防止意外修改原结构体 void print2(const struct S* ps) { for (int i 0; i 5; i) { printf(%d , ps-arr[i]); // 指针访问成员用- } printf(%d %.2lf\n, ps-n, ps-d); } int main() { struct S s { {1,2,3,4,5}, 100, 3.14 }; print1(s); // 传值拷贝s的所有数据100048字节 print2(s); // 传地址仅拷贝s的地址4/8字节 return 0; } 原理函数传参时参数会压栈结构体过大会导致压栈开销大、性能下降传地址可避免此问题。1.8 结构体位段内存优化神器位段是结构体的 “精简版”通过指定成员占用的二进制位数bit来节省内存适用于不需要完整字节的场景如网络协议、硬件配置。1.8.1 位段的声明规则成员必须是int、unsigned int、signed intC99 支持char等整数类型成员名后加:和数字表示占用的 bit 数。// 位段声明每个成员指定占用的bit数 struct A { int _a : 2; // 占用2bit取值范围0~300、01、10、11 int _b : 5; // 占用5bit取值范围0~31 int _c : 10; // 占用10bit取值范围0~1023 int _d : 30; // 占用30bit取值范围0~2^30-1 }; struct B { // 普通结构体对比用 int _a; int _b; int _c; int _d; }; int main() { printf(位段大小%zd\n, sizeof(struct A)); // 8字节25103047bit≈6字节→对齐到8字节 printf(普通结构体大小%zd\n, sizeof(struct B)); // 16字节4×4 return 0; }✅ 输出位段大小8普通结构体大小16→ 节省 50% 内存1.8.2 位段的内存分配细节VS 环境空间按 “4 字节int” 或 “1 字节char” 开辟优先满足当前成员同一开辟的空间内成员从右向左使用VS 规则其他编译器可能从左向右剩余空间不足时直接浪费开辟新空间。struct S { char a : 3; // 占用1字节的3bit char b : 4; // 占用同一字节的4bit剩余1bit char c : 5; // 剩余1bit不够浪费开辟新字节占用5bit char d : 4; // 开辟新字节占用4bit }; int main() { printf(%zd\n, sizeof(struct S)); // 3字节111 return 0; } 内存布局VS 环境第 1 字节b(4bit) a(3bit)→ 剩余 1bit 浪费第 2 字节c(5bit)→ 剩余 3bit 浪费第 3 字节d(4bit)→ 剩余 4bit 浪费。1.8.3 位段的跨平台问题⚠️ 重点位段的内存分配规则未完全标准化导致跨平台兼容性差int位段的符号性不确定有的编译器视为有符号有的视为无符号成员存储方向左→右 / 右→左不确定剩余空间是否复用不确定最大 bit 数限制16 位编译器最大 16bit32 位最大 32bit。✅ 结论位段适合单平台场景如嵌入式硬件配置跨平台需谨慎使用。1.8.4 位段的实际应用IP 协议头位段最经典的应用是网络协议头如 IP 数据报通过精准分配 bit 数节省网络传输带宽// IP协议头简化模型用位段描述 struct IPHeader { unsigned int version : 4; // 版本号4bit unsigned int header_len : 4; // 首部长度4bit unsigned int service_type : 8; // 服务类型8bit unsigned int total_len : 16; // 总长度16bit unsigned int id : 16; // 标识符16bit // ... 其他字段 };1.8.5 位段的注意事项不能用 操作符位段成员可能不占用完整字节因此没有独立地址不能用取地址也不能直接用scanf输入struct A { int _a : 2; int _b : 5; }; int main() { struct A sa; // scanf(%d, sa._b); // ❌ 错误不能取位段成员地址 int b 0; scanf(%d, b); sa._b b; // ✅ 正确先存到普通变量再赋值 return 0; }2. 联合体union数据的 “共享空间”联合体又称 “共用体”的核心特点是所有成员共用同一块内存空间就像 “多人共享一个房间”同一时间只能有一个成员使用空间节省内存。2.1 联合体声明与核心特点关键字union声明语法与结构体类似但成员共用空间。// 联合体声明 union Un { char c; // 1字节 int i; // 4字节 }; int main() { union Un u; // 特点1所有成员地址相同共用空间 printf(u的地址%p\n, u); printf(u.c的地址%p\n, u.c); printf(u.i的地址%p\n, u.i); // 特点2大小至少是最大成员的大小4字节 printf(联合体大小%zd\n, sizeof(u)); // 输出4 return 0; }✅ 输出三个地址完全相同大小为 4 字节 → 验证 “共用空间”。关键特性修改一个成员会覆盖其他成员int main() { union Un u; u.i 0x11223344; // 小端环境下内存44 33 22 11 printf(u.i 0x%x\n, u.i); // 0x11223344 u.c 0x55; // 修改char成员覆盖低地址1字节 → 内存55 33 22 11 printf(u.i 0x%x\n, u.i); // 0x11223355被覆盖 return 0; } 原理u.c占用低地址 1 字节修改后直接覆盖u.i的低字节数据。2.2 联合体大小计算对齐规则联合体大小遵循两个规则至少是最大成员的大小保证能容纳最大成员必须是最大对齐数的整数倍对齐规则与结构体一致。练习 1成员为数组 intunion Un1 { char c[5]; // 大小5字节实际对齐数1 int i; // 大小4字节实际对齐数4 }; // 最大成员大小5最大对齐数4 → 5不是4的整数倍→补3→8 int main() { printf(%zd\n, sizeof(union Un1)); // 输出8 }练习 2成员为短数组 intunion Un2 { short c[7]; // 大小14字节实际对齐数2 int i; // 大小4字节实际对齐数4 }; // 最大成员大小14最大对齐数4 → 14不是4的整数倍→补2→16 int main() { printf(%zd\n, sizeof(union Un2)); // 输出16 }2.3 联合体实战应用应用 1判断机器大小端比结构体更简洁利用 “成员共用空间”通过低地址字节的值判断大小端int check_sys() { union Un { char c; int i; } u; u.i 1; // 内存小端→01 00 00 00大端→00 00 00 01 return u.c; // 小端返回1大端返回0 } int main() { printf(%s\n, check_sys() ? 小端 : 大端); // 输出小端x86环境 return 0; }应用 2存储互斥数据节省内存当多个数据 “不会同时使用” 时用联合体存储可大幅节省空间。例如 “礼品兑换单”// 礼品兑换单图书、杯子、衬衫互斥一次只能兑换一种 struct GiftList { int stock; // 库存量公共属性 double price; // 价格公共属性 int type; // 类型1图书2杯子3衬衫公共属性 // 互斥属性用联合体存储 union { struct { char title[20]; // 书名 char author[20]; // 作者 int pages; // 页数 } book; struct { char design[30]; // 设计方案 } mug; struct { char design[30]; // 设计方案 int colors; // 可选颜色数 int sizes; // 可选尺寸数 } shirt; } item; // 联合体成员 }; int main() { struct GiftList gl; gl.type 1; // 兑换图书 strcpy(gl.item.book.title, C语言编程); printf(书名%s\n, gl.item.book.title); // 输出C语言编程 return 0; } 优势如果用结构体存储所有属性大小会更大用联合体仅存储当前需要的属性节省内存。3. 枚举enum有限值的 “一一列举”枚举用于描述 “有限个可选值” 的场景如星期、月份、菜单选项关键字enum本质是 “有名字的整数常量”。3.1 枚举声明与赋值规则枚举成员默认从0开始递增也可手动指定值未指定的成员继承前一个值 1。// 枚举1默认赋值0,1,2 enum Color { RED, // 0 GREEN, // 1 BLUE // 2 }; // 枚举2手动指定起始值5,6,7 enum Color2 { RED2 5, GREEN2, // 6 BLUE2 // 7 }; // 枚举3跳跃赋值0,5,6 enum Color3 { RED3, // 0 GREEN3 5, BLUE3 // 6 }; int main() { printf(RED%d, GREEN%d, BLUE%d\n, RED, GREEN, BLUE); // 0 1 2 printf(RED2%d, GREEN2%d, BLUE2%d\n, RED2, GREEN2, BLUE2); //5 6 7 return 0; } 注意枚举成员是常量不能修改如RED 10编译报错。3.2 枚举的优势对比 #define虽然#define也能定义常量但枚举有明显优势特性枚举enum#define 宏类型安全有明确类型如 enum Color无类型仅文本替换调试支持调试时可见成员名预处理阶段被替换不可见批量定义一次定义多个相关常量需逐个定义繁琐作用域遵循作用域规则如局部枚举仅局部可用全局有效易冲突// 用枚举定义菜单选项推荐 enum Option { EXIT 0, // 0退出 ADD, // 1加法 SUB, // 2减法 MUL, // 3乘法 DIV // 4除法 };3.3 枚举实战菜单程序枚举让菜单选项更具可读性和维护性避免硬编码数字#include stdio.h // 枚举菜单选项 enum Option { EXIT 0, ADD, SUB, MUL, DIV }; // 加法函数 int Add(int a, int b) { return a b; } // 减法函数 int Sub(int a, int b) { return a - b; } // 乘法函数 int Mul(int a, int b) { return a * b; } // 除法函数处理除数为0 int Div(int a, int b) { if (b 0) { printf(除数不能为0\n); return 0; } return a / b; } // 菜单打印 void menu() { printf(********************************\n); printf(******** 1.ADD 2.SUB ********\n); printf(******** 3.MUL 4.DIV ********\n); printf(******** 0.EXIT ********\n); printf(********************************\n); } int main() { int input 0; int a 0, b 0, ret 0; do { menu(); printf(请选择); scanf(%d, input); switch (input) { case ADD: printf(请输入两个数); scanf(%d%d, a, b); ret Add(a, b); printf(结果%d\n, ret); break; case SUB: printf(请输入两个数); scanf(%d%d, a, b); ret Sub(a, b); printf(结果%d\n, ret); break; case MUL: printf(请输入两个数); scanf(%d%d, a, b); ret Mul(a, b); printf(结果%d\n, ret); break; case DIV: printf(请输入两个数); scanf(%d%d, a, b); ret Div(a, b); printf(结果%d\n, ret); break; case EXIT: printf(退出程序\n); break; default: printf(选择错误请重新输入\n); break; } } while (input ! EXIT); return 0; }✅ 运行效果菜单选项与枚举成员一一对应代码可读性强后续修改选项顺序无需改逻辑。4. 三大自定义类型对比适用场景 区别类型核心特点内存布局适用场景结构体成员独立类型可不同成员按对齐规则排列描述复杂对象如书、人、礼品单联合体成员共用空间类型可不同所有成员起始地址相同存储互斥数据、判断大小端枚举成员是有名字的整数常量仅存储常量值无实例描述有限可选值如菜单、状态码核心区别总结内存占用结构体成员大小之和 补位 联合体最大成员大小 补位枚举不占实例内存数据关系结构体成员 “同时存在”联合体成员 “互斥存在”枚举成员 “相互独立”用途侧重结构体→组合复杂数据联合体→节省内存枚举→规范常量。 最后总结自定义类型是 C 语言实现复杂逻辑的核心工具结构体是 “组合器”适合描述有多个属性的复杂对象需掌握内存对齐规则和传参技巧联合体是 “共享器”适合存储互斥数据利用共用空间节省内存可用于大小端判断枚举是 “规范器”适合描述有限可选值比#define更安全、更易维护。掌握这三大自定义类型能让你的代码更简洁、更高效、更具可读性无论是日常开发还是面试如内存对齐计算、大小端判断、链表实现都是高频考点如果这篇博客帮你打通了自定义类型的 “任督二脉”欢迎点赞收藏