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

第20讲:自定义类型:结构体

📚 第20讲:自定义类型:结构体 🧱

用“积木思维”构建复杂数据,告别单一变量的局限!
从学生档案到网络协议,结构体无处不在!


📂 目录

  1. 结构体类型的声明 —— 搭建数据的“房屋蓝图” 🏗️
  2. 结构体变量的创建和初始化 —— 盖房入住 🏡
  3. 结构成员访问操作符 —— 开门进屋 🔑
  4. 结构体内存对齐 —— 房间的“风水布局” 🧭
  5. 结构体传参 —— 搬家还是寄快递? 📦
  6. 结构体实现位段 —— 比特级的“空间魔术”

📖 正文开始

前面我们学了 intchar 这些“单身汉”变量,但现实世界的数据往往是“一家人”!
比如一个学生,有姓名、年龄、性别、学号……
这时候,结构体(struct) 就派上用场了——它能把多个不同类型的数据“打包”成一个整体!


结构体类型的声明 —— 搭建数据的“房屋蓝图” 🏗️

🌟 生活比喻:房屋设计图

想象你要建一栋房子,得先画设计图

  • 客厅多大?
  • 卧室几间?
  • 厨房在哪?

结构体就像这设计图,它定义了“这个数据类型长什么样”。


🔹 基本语法

struct 标签名 {成员列表;
} 变量列表;
✅ 示例:学生档案设计图
struct Stu {char name[20];   // 名字int age;         // 年龄char sex[5];     // 性别char id[20];     // 学号
}; // 分号不能丢!

struct Stu 就是“学生档案”的蓝图。


🔧 特殊声明方式

1️⃣ 匿名结构体 —— “一次性设计图”
struct {int a;char b;float c;
} x; // 直接创建变量 xstruct {int a;char b;float c;
} *p; // p 是指针

❗ 问题:p = &x; 合法吗?

不合法!
虽然长得一样,但编译器认为这是两个完全不同的类型
匿名结构体就像“无名图纸”,用一次就扔,不能再用第二次。


2️⃣ 结构体自引用 —— “套娃式设计”

想定义一个链表节点:

struct Node {int data;struct Node next; // ❌ 错!无限套娃!
};

❌ 错!next 是一个完整的 struct Node,大小无限大!

✅ 正确做法:用指针自引用

struct Node {int data;struct Node* next; // ✅ 指针大小固定(4或8字节)
};

✅ 指针只是“地址”,不是整个结构体,不会导致无限大。


3️⃣ typedef 与自引用的陷阱
typedef struct {int data;Node* next; // ❌ 错!Node 还没定义完!
} Node;

❌ 编译错误!Node 是在 } 之后才诞生的,不能在内部提前使用。

✅ 正确写法:

typedef struct Node {int data;struct Node* next; // 先用 struct Node
} Node; // 再用 Node 重命名

结构体变量的创建和初始化 —— 盖房入住 🏡

有了蓝图,就可以“盖房”了!

#include <stdio.h>struct Stu {char name[20];int age;char sex[5];char id[20];
};int main() {// 方法1:按顺序初始化struct Stu s = { "张三", 20, "男", "20230818001" };// 方法2:指定成员初始化(C99)struct Stu s2 = { .age = 18, .name = "李四", .id = "20230818002", .sex = "女" };printf("name: %s\n", s.name);printf("age : %d\n", s2.age);return 0;
}

✅ 两种方式都能成功“入住”!


结构成员访问操作符 —— 开门进屋 🔑

🔹 两种“开门”方式

操作符用法场景
.结构体变量.成员名直接访问变量
->结构体指针->成员名通过指针访问
✅ 示例
struct Stu s = { "张三", 20, "男", "001" };
struct Stu* ps = &s;// 方法1:用 .
printf("%s\n", s.name);// 方法2:用 ->
printf("%s\n", ps->name); // 等价于 (*ps).name

结构体内存对齐 —— 房间的“风水布局” 🧭

🌟 生活比喻:超市货架摆放

超市货架不会乱摆商品:

  • 大件(冰箱)→ 占整块区域
  • 小件(牙膏)→ 可以塞缝里

计算机内存也一样:对齐 = 高效访问


🔹 对齐规则(VS 默认对齐数=8)

  1. 第一个成员:从偏移量 0 开始。
  2. 其他成员:对齐到 min(默认对齐数, 成员大小) 的整数倍地址。
  3. 总大小:是最大对齐数的整数倍。
  4. 嵌套结构体:先对齐到其内部最大对齐数,再参与整体对齐。

🔍 经典练习:算“房子面积”

✅ 练习1
struct S1 {char c1; // 1字节 → 放在0int i;   // 4字节 → 对齐到4的倍数 → 放在4~7(前面3字节浪费)char c2; // 1字节 → 放在8
}; // 总大小:9 → 但必须是4的倍数 → 实际12字节
sizeof(struct S1) = 12
✅ 练习2
struct S2 {char c1; // 0char c2; // 1int i;   // 4 → 对齐到4的倍数 → 放在4~7
}; // 总大小:8 → 是4的倍数 → 实际8字节
sizeof(struct S2) = 8
✅ 练习3
struct S3 {double d; // 8 → 放在0~7char c;   // 1 → 对齐到1的倍数 → 放在8int i;    // 4 → 对齐到4的倍数 → 放在12~15
}; // 总大小:16 → 是8的倍数 → 实际16字节
sizeof(struct S3) = 16

🔧 修改默认对齐数:#pragma pack

#include <stdio.h>#pragma pack(1) // 设置对齐数为1
struct S {char c1; // 0int i;   // 1(紧挨着)char c2; // 5
}; // 总大小:6
#pragma pack() // 恢复默认int main() {printf("%d\n", sizeof(struct S)); // 输出:6return 0;
}

✅ 用空间换时间,或用时间换空间,灵活调整!


结构体传参 —— 搬家还是寄快递? 📦

🌟 生活比喻:搬家 vs 寄钥匙

假设你要告诉朋友你家的情况:

  • 搬整个房子过去 → 慢、费力(传结构体)
  • 只给一把钥匙 → 快、省力(传指针)

✅ 代码对比

struct Big {int data[1000];int num;
};void print1(struct Big b) {      // 传整个结构体 → 搬家printf("%d\n", b.num);
}void print2(struct Big* pb) {    // 传指针 → 寄钥匙printf("%d\n", pb->num);
}int main() {struct Big b = {{1,2,3}, 1000};print1(b);  // ❌ 大量数据压栈,慢!print2(&b); // ✅ 推荐!只传4/8字节地址return 0;
}

结论:结构体传参,首选传地址!


结构体实现位段 —— 比特级的“空间魔术” ✨

🌟 生活比喻:身份证号码的“压缩存储”

身份证号 11010519900307231X
但很多信息其实只需要几位:

  • 省份:前2位 → 最多100种
  • 出生年:4位 → 0000~9999

位段就是把“大容器”切成“小格子”,精确存储!


🔹 什么是位段?

struct A {int _a : 2;  // 只用2个比特存_a(范围:0~3)int _b : 5;  // 用5个比特存_b(范围:0~31)int _c : 10; // 用10个比特存_cint _d : 30; // 用30个比特存_d
};

sizeof(struct A) = 8(64位系统,int 4字节,但需要跨int存储)


🔧 位段的内存分配

  • 按需以 int(4字节)或 char(1字节)开辟空间。
  • 位段是不跨平台的!不同编译器行为可能不同。
✅ 示例:char 位段
struct S {char a : 3; // 用3位char b : 4; // 用4位char c : 5; // 需要5位,但前面只剩1位 → 开新字节char d : 4; // 紧接着
};

内存布局复杂,但能节省空间


⚠️ 位段的跨平台问题

  1. int 位段是有符号还是无符号?不确定!
  2. 最大位数?16位机最多16位,32位机最多32位。
  3. 从左还是右分配?标准未定义!
  4. 剩余位不够时,是浪费还是继续用?不确定!

❗ 所以:注重可移植性的程序,慎用位段!


🚫 位段使用注意事项

不能对位段成员取地址!

struct A {int _a : 2;int _b : 5;
};int main() {struct A sa = {0};// scanf("%d", &sa._b); // ❌ 错!_b 没有地址!int tmp;scanf("%d", &tmp); // ✅ 先读到普通变量sa._b = tmp;       // ✅ 再赋值return 0;
}

✅ 位段成员是“比特缝里的数据”,没有独立地址!


🎯 总结:结构体“三板斧”

技能要点
声明struct 标签 { 成员 };,注意自引用用指针
对齐空间换时间,VS默认对齐数=8,可用 #pragma pack 调整
传参大结构体 → 传指针!小结构体 → 可传值

✅ 位段:节省空间的利器,但有跨平台风险,慎用!


🎯 恭喜你!
你已经掌握了“数据建筑师”的核心技能——结构体!
下一站:联合体与枚举,继续拓展C语言的“工具箱”!🔧🚀

http://www.dtcms.com/a/495541.html

相关文章:

  • 《FastAPI零基础入门与进阶实战》第21篇:告别 /path/ vs /path:静默斜杠修正中间件
  • Sherpa 语音识别工具链安装指南(Linux CPU 版)
  • 布林带中轨斜率的计算方法并判断趋势强度
  • 【小白笔记】torch.Tensor 类的实例
  • 俄语网站开发登录信产部网站
  • 学院门户网站建设自己在线制作logo免费生成器
  • 操作系统——进程管理
  • 在docker运行ros及其可视化
  • Python使用 pandas操作Excel文件并新增列数据
  • 宝塔面板点击ssl证书报错:出错了,面板运行时发生错误!ModuleNotFoundError: No module named ‘OpenSSL‘
  • Django与Tornado框架深度对比:从MVCMTV到高并发架构设计
  • 湖南畅想网站建设大连品牌网站建设公司
  • S4和ECC或者不通CLIENT,不通HANA服务器互相取数
  • Linux中控制台初始化console_init函数的实现
  • pycharm 默认终端设置 cmd
  • JavaScript 加密工具 sojson.v5 全解析:原理、应用与实践
  • 【Python库包】ESMF 库包介绍及安装
  • HarmonyOS ArkUI框架自定义弹窗选型与开发实战
  • 智能体开发(2)智能数据处理Agent
  • Visual Studio在一个解决方案管理多项目属性
  • 网站图片防盗连怎么做韶关营销网站开发
  • 10.17 设置组件导航和页面路由
  • 福田做商城网站建设找哪家公司比较安全简约好看的网站模板免费下载
  • 【GD32F527_EVAL】USB 驱动移植 和 USB CDC Device 接入PC实验
  • 网站开发网站定制查看网站源代码建站可以
  • stm32_QT6怎么打包
  • c 做网站流程如何做做网站
  • 深度剖析大模型Function Calling:从原理到优化策略
  • SQL入门:表关联-从基础到优化实战
  • YOLOv3 技术总结