C语言初学者笔记【结构体】

文章目录

  • 一、结构体的使用
    • [1. 结构体声明](#1. 结构体声明)
    • [2. 变量创建与初始化](#2. 变量创建与初始化)
    • [3. 特殊声明与陷阱](#3. 特殊声明与陷阱)
  • 二、内存对齐
    • [1. 规则:](#1. 规则:)
    • [2. 示例分析:](#2. 示例分析:)
    • [3. 修改默认对齐数:](#3. 修改默认对齐数:)
  • 三、结构体传参
  • 四、结构体实现位段
    • [1. 定义](#1. 定义)
    • [2. 内存分配](#2. 内存分配)
    • [3. 应用场景](#3. 应用场景)
    • [4. 跨平台问题:](#4. 跨平台问题:)
    • [5. 注意事项:](#5. 注意事项:)
  • 关键总结

一、结构体的使用

1. 结构体声明

c 复制代码
struct Stu {        // struct 关键字 + 标签(tag)
    char name[20];  // 成员变量
    int age;        // 成员类型可不同
    char sex[5];
    char id[20];
};                  // 分号不可省略

2. 变量创建与初始化

c 复制代码
// 顺序初始化
struct Stu s1 = {"张三", 20, "男", "20230818001"};

// 指定成员初始化(C99+)
struct Stu s2 = {.age=18, .name="lisi", .id="20230818002", .sex="女"};

3. 特殊声明与陷阱

· 匿名结构体(只能使用一次):

c 复制代码
struct { int a; char b; } x;  // 无标签
struct { int a; char b; } *p;
p = &x;  // 错误!编译器认为两者类型不同

· 自引用正确方式:

c 复制代码
struct Node {         // 错误:struct Node next;(无限递归)
    int data;
    struct Node* next; // 正确:使用指针
};

· typedef 陷阱:

c 复制代码
typedef struct {      // 错误:内部提前使用Node
    int data;
    Node* next;      // ❌ 未定义
} Node;

// 正确写法
typedef struct Node {
    int data;
    struct Node* next;
} Node;

二、内存对齐

1. 规则:

· 首成员偏移量 = 0

· 其他成员对齐数 = min(编译器默认对齐数, 成员大小)

· VS默认对齐数 = 8,Linux gcc无默认值(对齐数=成员大小)

· 结构体总大小 = 最大对齐数的整数倍

· 嵌套结构体:嵌套的结构体成员对齐到自身内部最大对齐数的整数倍处,结构体的整体大小是所有元素中最大对齐数的整数倍

2. 示例分析:

c 复制代码
struct S1 { char c1; int i; char c2; }; // 大小=12 (1+3填充+4+1+3填充)
struct S2 { char c1; char c2; int i; }; // 大小=8  (1+1+2填充+4)

节省空间技巧:将小成员集中放置(对比S1 vs S2)

3. 修改默认对齐数:

c 复制代码
#pragma pack(1)      // 对齐数设为1(无填充)
struct S { char c1; int i; char c2; }; // 大小=6
#pragma pack()       // 恢复默认对齐数

三、结构体传参

· 传地址优于传结构体:

c 复制代码
void print(struct S* ps) {  // ✅ 推荐:传递指针(4/8字节)
    printf("%d\n", ps->num);
}
void print(struct S s) {    // ❌ 避免:大结构体拷贝开销大
    printf("%d\n", s.num);
}

原因:传值导致拷贝开销,降低性能。


四、结构体实现位段

1. 定义

:二进制位(比特位)

c 复制代码
struct A {             // 位段声明
    int _a:2;         // 占2比特
    int _b:5;         // 占5比特
    int _c:10;        // 占10比特
    int _d:30;        // 占30比特(不能超过自身大小,即<=32)
};                    // 总大小:8字节(2个int)

· 成员主要为整形

2. 内存分配

· 空间按需以4字节(int)或1字节(char)开辟
· 给定空间后,成员在字节内的分配顺序由编译器决定(从左向右/从右向左)
· 当剩余空间不够存储一个成员时,剩余空间是浪费与否也取决于编译器

3. 应用场景

. 如_a中只需要存储0、1、2、3数字,则_a只需要两个比特位的空间,避免空间的浪费

· 网络协议头(如IP数据报):
| 4位版本号 | 4位首部长度 | 8位服务类型 | 16位总长度 | ...
用位段精简存储(如4位版本号仅需半字节)


4. 跨平台问题:

· int位段符号不确定(signed/unsigned)

· 最大位数依赖机器(16位机最大16,32位机最大32)

· 剩余位处理方式不确定(舍弃或利用)

5. 注意事项:

c 复制代码
struct A sa;
scanf("%d", &sa._b);  // ❌ 错误:位段成员无独立地址
// 正确做法:
int b;
scanf("%d", &b);
sa._b = b;

关键总结

主题 要点

结构体声明 分号不可省;避免匿名结构体自引用

内存对齐 掌握4条规则;通过成员排序节省空间;#pragma pack修改对齐数

传参方式 首选传地址(避免拷贝开销)

位段 节省空间但不可移植;成员无地址;慎用于跨平台程序

应用场景 协议封装、硬件寄存器映射等空间敏感场景

注:位段内存分配示例(假设小端存储):

c 复制代码
struct S { char a:3; char b:4; char c:5; char d:4; };
struct S s = {0};
s.a = 10; // 二进制: 010 (存储低3位)
s.b = 12; // 二进制: 1100 (存储后续4位)
// 内存布局: | 1100 010 | ???? ??? | ... |