找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

407

积分

0

好友

47

主题
发表于 2025-12-27 09:51:53 | 查看: 35| 回复: 0

C语言提供了丰富的基本数据类型,但在处理复杂对象时往往力不从心。例如,描述一个学生需要组合姓名(字符串)、年龄(整型)、成绩(浮点型)等多种属性。为此,C语言赋予了程序员自定义类型的能力,主要包括结构体枚举联合。掌握这些工具,能帮助你更好地组织数据,编写出更高效、更易维护的代码。

一、结构体

1. 结构体概念

结构体本质上是一个值的集合,这些值称为成员变量。每个成员可以是不同类型的变量。你可以把它想象成一个“容器”,能将不同类型的数据打包成一个逻辑整体。

1.1 结构体的声明

基本语法:

struct tag
{
    member-list;  // 成员列表
}variable-list;  // 变量列表(可选)

示例:描述学生

struct Stu
{
    char name[20];  // 名字
    int age;        // 年龄
    char sex[5];    // 性别
    char id[20];    // 学号
};

使用typedef简化(推荐):

typedef struct Stu
{
    char name[20];
    int age;
    char sex[5];
    char id[20];
}Stu; // Stu 是 struct Stu 的别名

int main()
{
    Stu s1; // 直接使用别名定义变量
    return 0;
}

C语言结构体详解:内存对齐、联合枚举与位段实用指南 - 图片 - 1

1.2 结构成员的类型

结构体的成员可以是标量、数组、指针,甚至是其他结构体。

1.3 特殊的声明:匿名结构体

匿名结构体在声明时未提供标签,只能使用一次。

struct // 匿名结构体
{
    int a;
    char b;
}x;

// struct {int a; char b;} y;
// p = &x; // 错误:编译器认为这是两个不同的类型

C语言结构体详解:内存对齐、联合枚举与位段实用指南 - 图片 - 2

1.4 结构的自引用

结构体内部包含一个指向自身类型的指针,常用于实现链表、树等数据结构

错误方式:

struct Node
{
    int data;
    struct Node next; // 错误!会导致无限递归
};

正确方式:

struct Node
{
    int data;
    struct Node* next; // 使用指针
};

C语言结构体详解:内存对齐、联合枚举与位段实用指南 - 图片 - 3

1.5 结构体变量的定义和初始化

struct Point
{
    int x;
    int y;
}p1 = {10, 15}; // 声明时定义并初始化

struct Point p2 = {20, 30}; // 单独定义并初始化

// C99支持指定初始化器
struct Stu s = {.age = 20, .name = "lisi"};

C语言结构体详解:内存对齐、联合枚举与位段实用指南 - 图片 - 4

2. 结构体成员访问

2.1 结构体变量访问成员

使用点操作符.访问。

struct Stu s;
strcpy(s.name, "zhangsan");
s.age = 20;
printf("name = %s, age = %d\n", s.name, s.age);

2.2 结构体指针访问成员

使用箭头操作符->访问。

void print(struct Stu* ps)
{
    printf("name = %s\n", ps->name); // 推荐方式
    printf("name = %s\n", (*ps).name); // 等价方式
}

C语言结构体详解:内存对齐、联合枚举与位段实用指南 - 图片 - 5

3. 结构体传参

结构体传参应优先传递地址,以避免复制大块数据带来的性能开销。

struct S { int data[1000]; int num; };

// 低效:传值,复制整个结构体
void print1(struct S s) { printf("%d\n", s.num); }

// 高效:传址,只传递指针
void print2(struct S* ps) { printf("%d\n", ps->num); }

int main()
{
    struct S s = {{0}, 1000};
    print2(&s); // 推荐
    return 0;
}

C语言结构体详解:内存对齐、联合枚举与位段实用指南 - 图片 - 6

4. 结构体内存对齐

计算结构体大小是常见考点,必须掌握对齐规则。

4.1 对齐规则

  1. 第一个成员在偏移量为0的地址处。
  2. 其他成员要对齐到对齐数(成员大小与编译器默认对齐数较小者)的整数倍地址。
  3. 结构体总大小为最大对齐数的整数倍。
  4. 嵌套的结构体对齐到自己成员的最大对齐数的整数倍处。

4.2 对齐规则示例

struct S1
{
    char c1; // 对齐数=1, 地址0
    int i;   // 对齐数=4, 地址4-7 (1-3填充)
    char c2; // 对齐数=1, 地址8
}; // 总大小=12 (对齐到最大对齐数4的倍数)

struct S2
{
    char c1; // 地址0
    char c2; // 地址1
    int i;   // 地址4-7 (2-3填充)
}; // 总大小=8

C语言结构体详解:内存对齐、联合枚举与位段实用指南 - 图片 - 7
优化成员顺序可以节省空间。

4.3 offsetof宏

用于计算结构体成员相对于起始位置的偏移量。

#include <stddef.h>
struct S1 { char c1; int i; char c2; };
printf("i的偏移量:%zu\n", offsetof(struct S1, i)); // 输出 4

4.4 为什么存在内存对齐?

主要是性能原因。访问未对齐的内存,处理器可能需要两次内存访问,而对齐的内存仅需一次。这是一种以空间换时间的策略,也涉及不同硬件平台的可移植性。

4.5 如何节省空间?

让占用空间小的成员尽量集中在一起。 对比S1S2,仅调整了char成员的顺序,大小就从12字节变为8字节。

4.6 修改默认对齐数

使用#pragma预处理指令。

#pragma pack(1) // 设置对齐数为1
struct S { char c; double d; }; // 大小变为9
#pragma pack() // 恢复默认

C语言结构体详解:内存对齐、联合枚举与位段实用指南 - 图片 - 8

二、位段

1. 位段基本概念

位段的声明类似结构体,但成员必须是整型家族,且成员名后需指明所占的比特位数。设计初衷是节省空间。

struct A
{
    int _a:2; // 占2个bit
    int _b:5; // 占5个bit
    int _c:10;// 占10个bit
    int _d:30;// 占30个bit
};
printf("%zu\n", sizeof(struct A)); // 输出 8 (2个int)

2. 位段的内存分配

位段的空间按需以intchar为单位开辟,其内存分配细节(如分配方向、剩余位利用)由编译器决定,因此不跨平台

struct S
{
    char a:3;
    char b:4;
    char c:5;
    char d:4;
};
// 在VS下,可能占用3个字节

3. 位段的跨平台问题

  1. int位段被视为有符号还是无符号不确定。
  2. 位段最大位数由机器字长决定(16/32/64位)。
  3. 内存分配方向(从左向右或从右向左)标准未定义。
  4. 空间不足时是舍弃剩余位还是利用不确定。

4. 位段的应用

适用于对空间要求苛刻且无需跨平台的场景,如网络协议头部定义、硬件寄存器映射。

// 简化的IP头部结构
struct IPHeader {
    unsigned int version:4;
    unsigned int header_len:4;
    unsigned int total_len:16;
    // ...
};

C语言结构体详解:内存对齐、联合枚举与位段实用指南 - 图片 - 9

三、枚举

枚举将可能的取值一一列举,提高代码可读性。

1. 枚举类型的定义

enum Color // 颜色
{
    RED,    // 默认为0
    GREEN,  // 默认为1
    BLUE    // 默认为2
};

enum Day { Mon=1, Tues, Wed, Thur=10, Fri }; // 可自定义值

2. 枚举的优点

相比#define

  1. 增加可读性和可维护性RED1更有意义。
  2. 有类型检查,更严谨。
  3. 防止命名污染
  4. 便于调试
  5. 一次定义多个常量,方便。

3. 枚举的使用

enum Color clr = GREEN; // 推荐使用枚举常量赋值
// clr = 5; // C语言中合法,但不推荐

C语言结构体详解:内存对齐、联合枚举与位段实用指南 - 图片 - 10

四、联合(共用体)

1. 联合类型的定义

联合的成员共享同一块内存空间,因此联合大小至少是最大成员的大小。

union Un
{
    char c;
    int i;
};
printf("%zu\n", sizeof(union Un)); // 输出 4
printf("%p\n%p\n", &(un.i), &(un.c)); // 两个地址相同

2. 联合的特点

所有成员从同一地址开始,修改一个成员会影响其他成员。

2.1 利用联合判断大小端

此特性常用于网络编程中判断主机字节序。

int check_endian()
{
    union Un { char c; int i; } u;
    u.i = 1; // 十六进制 0x00000001
    return u.c == 1; // 若低地址存1则为小端
}

C语言结构体详解:内存对齐、联合枚举与位段实用指南 - 图片 - 11

3. 联合大小的计算

  1. 大小至少是最大成员的大小。
  2. 当最大成员大小不是最大对齐数的整数倍时,要向上对齐。
    union Un1 { char c[5]; int i; }; // 大小=8 (5对齐到4的倍数?不,最大对齐数是4,但总大小需是4的倍数,且能容纳c[5],所以是8)
    union Un2 { short c[7]; int i; }; // 大小=16 (14对齐到4的倍数)

总结

自定义类型是C语言构建复杂数据模型的基石。

  • 结构体:组织异类数据。注意内存对齐以优化空间和性能,传参时传地址。
  • 位段:极致节省空间,但牺牲了可移植性,慎用。
  • 枚举:提高代码可读性,替代魔数。
  • 联合:多种解释共享同一内存,可用于类型转换、节省空间或判断字节序。

掌握这些类型的特性和适用场景,能够让你在系统编程、嵌入式开发等领域更加得心应手。理解内存布局是关键,多实践、多思考才能真正融会贯通。




上一篇:C语言指针全面解析:从内存管理到函数指针与qsort应用
下一篇:Visual Studio调试:C语言数组越界实例与Windows环境技巧详解
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-11 17:54 , Processed in 0.279879 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表