【C语言】自定义类型:结构体


前言

结构体是C语言实现数据封装的核心工具,广泛应用于复杂对象描述(如学生信息、链表节点)与底层开发(如协议解析)。本文将从语法基础到性能优化,提炼核心知识点与实战技巧,帮你高效掌握这一高频考点与开发利器。


一、结构体的声明及初始化

1.1 声明与变量定义

结构体声明的核心格式:

struct 标签 { 成员列表 } [变量列表];(分号不可省略)

c 复制代码
// 标准声明(推荐,可复用)
struct Stu {
    char name[20];  // 名字(数组避免指针悬空)
    int age;        // 年龄
    char id[20];    // 学号
};

// 变量创建:全局/局部均可
struct Stu s1;        // 局部变量
struct Stu s2[10];    // 结构体数组(批量存储)

1.2 初始化

  • 顺序初始化 :按成员顺序赋值,简洁直接:

    c 复制代码
    struct Stu s = {"张三", 20, "20230818001"};
  • 指定成员初始化 :跳过无关成员,灵活适配于部分赋值场景:

    c 复制代码
    struct Stu s = {.age = 18, .name = "李四"};  // 未赋值成员默认初始化为0/空

1.3 关键点

  • 匿名结构体 :仅能使用一次,多次声明会被视为不同类型(慎用):

    c 复制代码
    struct { int a; char b; } x;  // x是唯一变量,无法再定义同类型变量
  • 自引用正确写法 :必须用指针(避免无限递归内存占用):

    c 复制代码
    // 错误:struct Node未定义完成,直接用类型会报错
    struct Node { int data; struct Node next; };
    // 正确:指针仅占4/8字节,无需知道结构体完整大小
    struct Node { int data; struct Node* next; };
  • typedef重命名 :结合标签使用,避免匿名结构体的局限:

    c 复制代码
    typedef struct Node {
        int data;
        struct Node* next;
    } Node;  // 可直接用Node代替struct Node

二、结构体内存对齐

内存对齐是"空间换时间"的优化策略,计算结构体大小需遵循以下规则:

2.1 对齐规则 (VS默认对齐数8,GCC无默认对齐数,按成员最大大小对齐)

  1. 第一个成员从偏移量0开始。
  2. 其他成员对齐到「对齐数」的整数倍地址(对齐数=min(编译器默认对齐数, 成员大小))。
  3. 结构体总大小为「所有成员对齐数最大值」的整数倍。
  4. 嵌套结构体时,嵌套部分对齐到自身最大对齐数的整数倍。

2.2 实战计算(VS环境)

结构体定义 成员偏移与大小 总大小 优化点
struct S1 { char c1; int i; char c2; } c1(0,1)→i(4,4)→c2(8,1) 12 小成员分散,浪费空间
struct S2 { char c1; char c2; int i; } c1(0,1)→c2(1,1)→i(4,4) 8 小成员集中,节省4字节
struct S3 { double d; char c; int i; } d(0,8)→c(8,1)→i(12,4) 16 double占8字节,对齐到8
struct S4 { char c1; struct S3 s3; double d; } c1(0,1)→s3(8,16)→d(24,8) 32 嵌套结构体对齐到自身最大对齐数(8)

2.3 对齐优化技巧

  • 成员排序原则:小成员集中存放(如S2 vs S1)。

  • 修改默认对齐数 :用#pragma pack(n)调整(n=1时取消对齐)。

    c 复制代码
    #pragma pack(1)  // 强制按1字节对齐
    struct S { char c1; int i; char c2; };  // 总大小=1+4+1=6
    #pragma pack()   // 还原默认

三、结构体传参

3.1 传参方式对比

传参方式 实现代码 性能开销 适用场景
传值调用 void print(struct Stu s) 拷贝整个结构体(栈空间+时间) 结构体极小(<16字节)
传址调用 void print(struct Stu* ps) 仅传指针(4/8字节) 绝大多数场景(推荐)

3.2 代码示例

c 复制代码
// 高效写法:传址+const保证只读
void printStu(const struct Stu* ps) {
    printf("姓名:%s,年龄:%d\n", ps->name, ps->age);  // 用->访问成员
}

int main() {
    struct Stu s = {"张三", 20, "20230818001"};
    printStu(&s);  // 传递地址
    return 0;
}

四、位段的使用与实战

位段是结构体的"空间压缩版",通过指定成员占用的二进制位数,大幅减少内存占用(适用于硬件寄存器、网络协议等场景)。

4.1 位段声明与大小计算

c 复制代码
// 位段声明:成员为整数类型,后接:位数
struct A {
    int a:2;   // 占2bit(范围:-2~1或0~3,取决于有符号)
    int b:5;   // 占5bit
    int c:10;  // 占10bit
    int d:30;  // 占30bit
};  // 总大小:2+5+10+30=47bit → 占2个int(8字节)

4.2 核心限制与注意事项

  • 成员类型仅限int/unsigned int/char

  • 位数不能超过类型总位数(如char不能超过8bit)。

  • 无独立地址,不能用&取地址(需通过中间变量赋值)。

    c 复制代码
    struct A sa;
    // 错误:&sa.a非法
    // scanf("%d", &sa.a);
    // 正确
    int tmp = 0;
    scanf("%d", &tmp);
    sa.a = tmp;

4.3 实际应用:IP头位段定义

c 复制代码
// IPv4头部的位段实现(简化版)
struct IpHeader {
    unsigned int version:4;    // 版本(4bit)
    unsigned int ihl:4;        // 首部长度(4bit)
    unsigned int tos:8;        // 服务类型(8bit)
    unsigned int total_len:16; // 总长度(16bit)
    // ... 其他字段
};  // 仅用4字节存储多个字段,比普通结构体节省空间

结构体作为C语言中连接基础数据类型与复杂数据结构的桥梁,从简单的数据封装到底层的内存优化,每一处细节都体现着"效率"与"灵活"的平衡。无论是应对面试中的内存计算考题,还是在项目中设计高效的协议解析、数据存储模块,掌握这些知识点都能让你事半功倍。如果在实际开发中遇到结构体的疑难问题(比如特殊场景的内存对齐优化、位段的跨平台适配),不妨回头再梳理这些规则,看似琐碎的细节,恰恰是写出高性能、高可读性代码的关键。

至此,我们已梳理完"数据在内存中的存储"的全部内容了。最后我们在文末来进行一个投个票,告诉我你对哪部分内容最感兴趣、收获最大,也欢迎在评论区聊聊你的学习感受。

以上就是本期博客的全部内容了,感谢各位的阅读以及关注。如有内容存在疏漏或不足之处,恳请各位技术大佬不吝赐教、多多指正。

相关推荐
合作小小程序员小小店1 小时前
桌面开发,点餐管理系统开发,基于C#,winform,sql server数据库
开发语言·数据库·sql·microsoft·c#
笃行客从不躺平1 小时前
线程池监控是什么
java·开发语言
星轨初途1 小时前
C++的输入输出(上)(算法竞赛类)
开发语言·c++·经验分享·笔记·算法
再睡一夏就好2 小时前
string.h头文件中strcpy、memset等常见函数的使用介绍与模拟实现
c语言·c++·笔记·string·内存函数·strcpy
dangdang___go2 小时前
动态内存管理||malloc和free.realloc和calloc
c语言·开发语言·算法·动态内存管理
YA3332 小时前
mcp-grafana mcp 使用stdio报错
java·开发语言
周杰伦_Jay2 小时前
【Go 语言主流 Web】 框架详细解析
开发语言·后端·微服务·架构·golang
PfCoder2 小时前
WinForm真入门(20)——StatusStrip控件解析
开发语言·windows·c#·winform·statusstrip
灵犀坠2 小时前
前端面试八股复习心得
开发语言·前端·javascript