C语言结构体与联合体详解

目录

结构体(Structure)和联合体(Union)是C语言中两种重要的自定义数据类型,它们允许程序员将不同类型的数据组合在一起,形成更复杂的数据结构。虽然它们在语法上相似,但在内存布局和使用场景上有本质区别。

结构体(Structure)

基本概念

结构体是一种用户自定义的数据类型,用于将多个不同类型的数据项组合在一起。每个数据项称为结构体的成员(Member),它们在内存中依次排列。

结构体定义与声明

c 复制代码
// 结构体定义
struct Person {
    char name[50];    // 姓名
    int age;          // 年龄
    float height;     // 身高
};  // 注意分号不能省略

// 声明结构体变量
struct Person p1;  // 声明一个Person类型的变量

// 定义并初始化
struct Person p2 = {
    "张三",
    25,
    175.5
};

// 访问结构体成员
printf("姓名: %s\n", p2.name);
printf("年龄: %d\n", p2.age);

结构体嵌套

结构体可以包含其他结构体作为成员:

c 复制代码
// 日期结构体
struct Date {
    int year;
    int month;
    int day;
};

// 包含Date结构体的Person结构体
struct Person {
    char name[50];
    int age;
    struct Date birthday;  // 嵌套结构体
};

// 初始化嵌套结构体
struct Person p = {
    "李四",
    30,
    {1990, 5, 15}  // 初始化birthday成员
};

// 访问嵌套结构体成员
printf("出生日期: %d-%d-%d\n", 
       p.birthday.year, 
       p.birthday.month, 
       p.birthday.day);

结构体指针

通过结构体指针可以间接访问结构体成员:

c 复制代码
struct Person {
    char name[50];
    int age;
};

int main() {
    struct Person p = {"王五", 28};
    struct Person *ptr = &p;  // 指向结构体的指针
    
    // 通过指针访问成员的两种方式
    printf("姓名: %s\n", (*ptr).name);  // 使用解引用操作符
    printf("年龄: %d\n", ptr->age);     // 使用箭头操作符
    
    return 0;
}

结构体数组

结构体数组是一组具有相同结构体类型的变量:

c 复制代码
struct Point {
    int x;
    int y;
};

int main() {
    // 结构体数组定义与初始化
    struct Point points[3] = {
        {1, 2},
        {3, 4},
        {5, 6}
    };
    
    // 访问结构体数组元素
    for (int i = 0; i < 3; i++) {
        printf("点 %d: (%d, %d)\n", i+1, points[i].x, points[i].y);
    }
    
    return 0;
}

结构体的内存布局

结构体的成员在内存中依次排列,但可能存在内存对齐(Alignment):

c 复制代码
struct Example {
    char c;     // 1字节
    int i;      // 4字节
    char d;     // 1字节
};

// 在32位系统上,sizeof(struct Example)通常为12字节,而非6字节
// 原因是内存对齐:char后填充3字节,使int从4字节边界开始

结构体作为函数参数

结构体可以作为函数参数传递,有两种方式:

c 复制代码
struct Rectangle {
    int width;
    int height;
};

// 1. 值传递(复制整个结构体)
int areaByValue(struct Rectangle r) {
    return r.width * r.height;
}

// 2. 指针传递(传递结构体地址)
int areaByPointer(struct Rectangle *r) {
    return r->width * r->height;
}

int main() {
    struct Rectangle rect = {10, 5};
    
    printf("面积(值传递): %d\n", areaByValue(rect));
    printf("面积(指针传递): %d\n", areaByPointer(&rect));
    
    return 0;
}

联合体(Union)

基本概念

联合体是一种特殊的数据类型,允许在相同的内存位置存储不同类型的数据。联合体的所有成员共享同一块内存空间,因此联合体的大小等于其最大成员的大小。

联合体定义与声明

c 复制代码
// 联合体定义
union Data {
    int i;          // 4字节
    float f;        // 4字节
    char str[20];   // 20字节
};  // 注意分号不能省略

// 声明联合体变量
union Data data;  // 声明一个Data类型的变量

// 联合体大小
printf("联合体大小: %lu 字节\n", sizeof(data));  // 输出20(最大成员大小)

联合体成员访问

联合体在同一时间只能存储一个成员的值:

c 复制代码
union Data {
    int i;
    float f;
    char str[20];
};

int main() {
    union Data data;
    
    // 存储整数
    data.i = 10;
    printf("data.i: %d\n", data.i);
    
    // 存储浮点数(覆盖之前的整数)
    data.f = 22.5;
    printf("data.f: %f\n", data.f);
    
    // 存储字符串(覆盖之前的浮点数)
    strcpy(data.str, "Hello");
    printf("data.str: %s\n", data.str);
    
    return 0;
}

联合体的内存布局

联合体的所有成员共享同一块内存空间:

c 复制代码
union Mixed {
    int i;         // 4字节
    char c[4];     // 4字节
};

int main() {
    union Mixed m;
    m.i = 0x12345678;
    
    // 在小端序系统上,内存布局为:
    // m.c[0]: 0x78
    // m.c[1]: 0x56
    // m.c[2]: 0x34
    // m.c[3]: 0x12
    
    printf("m.c[0]: 0x%x\n", m.c[0]);  // 输出0x78(小端序)
    
    return 0;
}

联合体嵌套

联合体可以嵌套在结构体中,反之亦然:

c 复制代码
// 嵌套联合体的结构体
struct Record {
    int id;
    char type;  // 'I'表示整数,'F'表示浮点数
    
    union {
        int iValue;
        float fValue;
    } value;  // 匿名联合体
};

int main() {
    struct Record r1 = {1, 'I', {.iValue = 100}};
    struct Record r2 = {2, 'F', {.fValue = 3.14f}};
    
    if (r1.type == 'I') {
        printf("r1的整数值: %d\n", r1.value.iValue);
    }
    
    if (r2.type == 'F') {
        printf("r2的浮点值: %f\n", r2.value.fValue);
    }
    
    return 0;
}

联合体的应用场景

  1. 节省内存:当一个变量可能有多种类型,但同一时间只使用一种类型时
  2. 类型转换:在不同数据类型之间进行位模式转换
  3. 访问数据的不同表示:例如访问整数的各个字节
  4. 变体数据结构:实现可以存储不同类型数据的通用容器

结构体与联合体的对比

特性 结构体(Structure) 联合体(Union)
内存布局 成员依次排列,可能有内存对齐 所有成员共享同一块内存空间
大小计算 所有成员大小之和(考虑对齐) 最大成员的大小
成员访问 所有成员可同时访问 同一时间只能访问一个成员
数据存储 每个成员存储独立的数据 所有成员共享相同的数据存储空间
内存占用 通常较大 通常较小
适用场景 组合不同类型的数据 节省内存,处理变体数据

实际应用示例

结构体应用示例:学生信息管理

c 复制代码
#include <stdio.h>
#include <string.h>

// 学生结构体定义
struct Student {
    char name[50];
    int id;
    float scores[3];  // 三门课程的成绩
};

// 计算平均分
float calculateAverage(struct Student *s) {
    float sum = 0;
    for (int i = 0; i < 3; i++) {
        sum += s->scores[i];
    }
    return sum / 3;
}

int main() {
    // 初始化学生数组
    struct Student students[2] = {
        {"张三", 101, {85.5, 90.0, 78.5}},
        {"李四", 102, {92.0, 87.5, 95.0}}
    };
    
    // 显示学生信息
    for (int i = 0; i < 2; i++) {
        printf("学生: %s (ID: %d)\n", students[i].name, students[i].id);
        printf("成绩: %.1f, %.1f, %.1f\n", 
               students[i].scores[0], 
               students[i].scores[1], 
               students[i].scores[2]);
        printf("平均分: %.2f\n\n", calculateAverage(&students[i]));
    }
    
    return 0;
}

联合体应用示例:变体数据类型

c 复制代码
#include <stdio.h>

// 变体数据类型
union Variant {
    int i;
    float f;
    char str[20];
};

// 数据类型枚举
enum Type {INT, FLOAT, STRING};

// 数据结构
struct Data {
    enum Type type;
    union Variant value;
};

// 打印变体数据
void printData(struct Data d) {
    switch (d.type) {
        case INT:
            printf("整数值: %d\n", d.value.i);
            break;
        case FLOAT:
            printf("浮点值: %f\n", d.value.f);
            break;
        case STRING:
            printf("字符串值: %s\n", d.value.str);
            break;
    }
}

int main() {
    // 创建不同类型的数据
    struct Data d1 = {INT, {.i = 42}};
    struct Data d2 = {FLOAT, {.f = 3.14f}};
    struct Data d3 = {STRING, {.str = "Hello"}};
    
    // 打印数据
    printData(d1);
    printData(d2);
    printData(d3);
    
    return 0;
}

注意事项

结构体注意事项

  1. 内存对齐:结构体大小可能大于成员大小之和,编译器会插入填充字节以满足对齐要求
  2. 结构体复制:结构体赋值会复制整个结构体,对于大型结构体可能影响性能
  3. 结构体指针传递:对于大型结构体,使用指针传递可提高性能
  4. 结构体嵌套:避免过度嵌套导致代码复杂度过高

联合体注意事项

  1. 成员覆盖:写入一个成员会覆盖其他成员的值
  2. 类型安全:必须跟踪当前存储的成员类型,否则访问错误类型会导致未定义行为
  3. 对齐要求:联合体的对齐方式由其最大成员决定
  4. 应用限制:联合体主要用于节省内存,不适合需要同时存储多个值的场景

总结

结构体和联合体是C语言中两种重要的自定义数据类型,它们各自有独特的内存布局和应用场景:

  • 结构体用于组合不同类型的数据,每个成员有独立的内存空间
  • 联合体用于节省内存,所有成员共享同一块内存空间
  • 结构体适合需要同时存储多个相关数据的场景
  • 联合体适合处理变体数据或需要在不同类型间共享位模式的场景

理解结构体和联合体的区别和联系,能够帮助程序员设计更高效、更灵活的数据结构,满足不同的编程需求。

相关推荐
Scott9999HH43 分钟前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
码智社2 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海2 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖3 小时前
JDK 26 新特性详解
java·开发语言
马优晨3 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区5 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大6 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai6 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
এ慕ོ冬℘゜6 小时前
前端基础:什么是时间戳?JS获取时间戳三种方法与实战用途
开发语言·前端·javascript
执明wa6 小时前
LayoutInflater详解: XML是如何变成View的?
android·xml·开发语言·android studio