C语言的结构体与联合体

发布于:2025-08-06 ⋅ 阅读:(21) ⋅ 点赞:(0)

C语言的结构体与联合体

结构体(struct)和联合体(union)是C语言中用于组合不同数据类型的数据结构。它们允许程序员将多个相关的数据项组合在一起,以便更好地组织和管理复杂的数据。枚举类型(enum)则用于定义一组具名的整型常量,增强代码的可读性和可维护性。掌握结构体、联合体和枚举类型的定义与使用,是编写高效、可扩展C程序的重要技能。


1 结构体的定义与使用

结构体是将不同类型的变量组合在一起的用户定义的数据类型。它用于表示一个实体的多个属性,便于管理和传递复杂的数据。

结构体的定义

语法

struct 结构体名 {
    数据类型 成员名1;
    数据类型 成员名2;
    // ...
};
  • struct:关键字,用于定义结构体。
  • 结构体名:结构体的名称,用于引用该结构体类型。
  • 成员:结构体内部的变量,每个成员可以是不同的数据类型。

示例

#include <stdio.h>

// 定义一个表示学生信息的结构体
struct Student {
    char name[50];
    int age;
    float gpa;
};
结构体的声明与初始化

定义结构体类型后,可以声明结构体变量,并对其进行初始化。

示例

#include <stdio.h>
#include <string.h>

// 定义结构体
struct Student {
    char name[50];
    int age;
    float gpa;
};

int main() {
    // 声明并初始化结构体变量
    struct Student student1;
    
    strcpy(student1.name, "张三");
    student1.age = 20;
    student1.gpa = 3.8;

    // 输出结构体成员
    printf("姓名: %s\n", student1.name);
    printf("年龄: %d\n", student1.age);
    printf("GPA: %.2f\n", student1.gpa);

    return 0;
}

输出

姓名: 张三
年龄: 20
GPA: 3.80

详细解释

  • 使用strcpy函数复制字符串到结构体的name成员。
  • 通过点运算符(.)访问和修改结构体成员。
结构体的初始化方式
  1. 逐个成员赋值

    struct Student student2;
    strcpy(student2.name, "李四");
    student2.age = 22;
    student2.gpa = 3.5;
    
  2. 使用初始化列表

    struct Student student3 = {"王五", 21, 3.9};
    
  3. 部分初始化

    未初始化的成员将被自动初始化为零。

    struct Student student4 = {"赵六", 0, 0.0};
    
结构体的嵌套

结构体可以包含其他结构体作为成员,实现更复杂的数据结构。

示例

#include <stdio.h>
#include <string.h>

// 定义地址结构体
struct Address {
    char city[50];
    char country[50];
};

// 定义学生结构体,包含地址结构体
struct Student {
    char name[50];
    int age;
    float gpa;
    struct Address addr;
};

int main() {
    struct Student student;

    strcpy(student.name, "孙七");
    student.age = 23;
    student.gpa = 3.7;
    strcpy(student.addr.city, "北京");
    strcpy(student.addr.country, "中国");

    // 输出结构体成员
    printf("姓名: %s\n", student.name);
    printf("年龄: %d\n", student.age);
    printf("GPA: %.2f\n", student.gpa);
    printf("城市: %s\n", student.addr.city);
    printf("国家: %s\n", student.addr.country);

    return 0;
}

输出

姓名: 孙七
年龄: 23
GPA: 3.70
城市: 北京
国家: 中国

详细解释

  • 结构体Student中嵌套了结构体Address,通过student.addr.city访问嵌套结构体的成员。
结构体类型定义与typedef

使用typedef可以简化结构体类型的定义,使得声明结构体变量时无需重复使用struct关键字。

示例

#include <stdio.h>
#include <string.h>

// 使用typedef定义结构体类型
typedef struct {
    char name[50];
    int age;
    float gpa;
} Student;

int main() {
    // 直接使用Student类型声明变量
    Student student1;

    strcpy(student1.name, "周八");
    student1.age = 24;
    student1.gpa = 3.6;

    printf("姓名: %s\n", student1.name);
    printf("年龄: %d\n", student1.age);
    printf("GPA: %.2f\n", student1.gpa);

    return 0;
}

输出

姓名: 周八
年龄: 24
GPA: 3.60

详细解释

  • 使用typedef将匿名结构体类型命名为Student,简化后续的变量声明。
注意事项
  • 成员访问:使用点运算符(.)访问结构体成员;如果通过指针访问结构体成员,使用箭头运算符(->)。

    struct Student *ptr = &student1;
    printf("姓名: %s\n", ptr->name);
    
  • 内存对齐:结构体成员的排列可能会导致内存对齐问题,影响内存使用效率。可以使用#pragma pack指令控制内存对齐,但需谨慎使用。


2 结构体数组与指针

结构体数组和结构体指针是管理多个结构体数据的常用方式,适用于存储和操作大量相关的数据项。

结构体数组

结构体数组是由相同类型的结构体元素组成的数组,用于存储多个结构体实例。

示例

#include <stdio.h>
#include <string.h>

typedef struct {
    char name[50];
    int age;
    float gpa;
} Student;

int main() {
    // 声明结构体数组,包含3个学生
    Student students[3] = {
        {"李雷", 21, 3.5},
        {"韩梅梅", 22, 3.8},
        {"小明", 20, 3.2}
    };

    // 遍历结构体数组并输出成员
    for(int i = 0; i < 3; i++) {
        printf("学生%d:\n", i + 1);
        printf("  姓名: %s\n", students[i].name);
        printf("  年龄: %d\n", students[i].age);
        printf("  GPA: %.2f\n\n", students[i].gpa);
    }

    return 0;
}

输出

学生1:
  姓名: 李雷
  年龄: 21
  GPA: 3.50

学生2:
  姓名: 韩梅梅
  年龄: 22
  GPA: 3.80

学生3:
  姓名: 小明
  年龄: 20
  GPA: 3.20

详细解释

  • 使用结构体数组students存储多个Student结构体实例。
  • 通过数组索引访问每个结构体元素,并使用点运算符访问其成员。
结构体指针

结构体指针是指向结构体变量的指针,通过指针可以访问和修改结构体的成员。

示例

#include <stdio.h>
#include <string.h>

typedef struct {
    char name[50];
    int age;
    float gpa;
} Student;

int main() {
    Student student = {"张华", 23, 3.9};
    Student *ptr = &student; // 指针指向结构体变量

    // 通过指针访问成员
    printf("姓名: %s\n", ptr->name);
    printf("年龄: %d\n", ptr->age);
    printf("GPA: %.2f\n", ptr->gpa);

    // 修改成员
    ptr->age = 24;
    ptr->gpa = 4.0;
    printf("\n修改后:\n");
    printf("姓名: %s\n", ptr->name);
    printf("年龄: %d\n", ptr->age);
    printf("GPA: %.2f\n", ptr->gpa);

    return 0;
}

输出

姓名: 张华
年龄: 23
GPA: 3.90

修改后:
姓名: 张华
年龄: 24
GPA: 4.00

详细解释

  • 使用箭头运算符(->)通过指针访问和修改结构体成员。
  • 修改指针指向的结构体成员,实际修改了原结构体变量的值。
结构体数组与指针的结合使用

结构体数组与指针结合使用,可以高效地遍历和操作结构体数组。

示例

#include <stdio.h>
#include <string.h>

typedef struct {
    char name[50];
    int age;
    float gpa;
} Student;

int main() {
    // 声明结构体数组
    Student students[3] = {
        {"刘强", 25, 3.6},
        {"陈静", 22, 3.7},
        {"王磊", 24, 3.8}
    };

    // 声明结构体指针并指向数组的第一个元素
    Student *ptr = students;

    // 使用指针遍历结构体数组
    for(int i = 0; i < 3; i++) {
        printf("学生%d:\n", i + 1);
        printf("  姓名: %s\n", (ptr + i)->name);
        printf("  年龄: %d\n", (ptr + i)->age);
        printf("  GPA: %.2f\n\n", (ptr + i)->gpa);
    }

    return 0;
}

输出

学生1:
  姓名: 刘强
  年龄: 25
  GPA: 3.60

学生2:
  姓名: 陈静
  年龄: 22
  GPA: 3.70

学生3:
  姓名: 王磊
  年龄: 24
  GPA: 3.80

详细解释

  • 通过指针ptr和指针运算访问结构体数组的各个元素。
  • 使用(ptr + i)->member的方式访问每个结构体的成员。
注意事项
  • 数组越界:确保指针操作不超过结构体数组的边界,避免未定义行为。
  • 指针有效性:指针必须指向有效的结构体变量或数组元素,避免悬挂指针或野指针。

3 联合体的定义与应用

联合体(union)是与结构体类似的数据结构,但与结构体不同,联合体的所有成员共用同一块内存空间。这意味着在任意时刻,联合体只能存储一个成员的值。联合体适用于需要在同一内存位置存储不同类型数据的场景,如节省内存空间或实现数据类型转换。

联合体的定义

语法

union 联合体名 {
    数据类型 成员名1;
    数据类型 成员名2;
    // ...
};
  • union:关键字,用于定义联合体。
  • 联合体名:联合体的名称。
  • 成员:联合体内部的变量,共用同一内存空间。

示例

#include <stdio.h>

// 定义一个联合体,用于存储不同类型的数据
union Data {
    int intValue;
    float floatValue;
    char charValue;
};

int main() {
    union Data data;

    // 存储整数
    data.intValue = 100;
    printf("整数值: %d\n", data.intValue);

    // 存储浮点数,覆盖之前的整数值
    data.floatValue = 98.6;
    printf("浮点数值: %.1f\n", data.floatValue);

    // 存储字符,覆盖之前的浮点数值
    data.charValue = 'A';
    printf("字符值: %c\n", data.charValue);

    return 0;
}

输出

整数值: 100
浮点数值: 98.6
字符值: A

详细解释

  • 联合体Data的所有成员共用同一块内存空间。
  • 每次赋值给一个成员,会覆盖前一个成员的值。
  • 联合体的大小等于其最大成员的大小。
联合体的应用场景
  1. 内存节省:当一个数据结构的不同成员不会同时使用时,使用联合体可以节省内存空间。
  2. 数据类型转换:通过联合体可以实现不同数据类型之间的转换,如将浮点数的二进制表示解释为整数。
  3. 协议解析:在网络协议解析中,不同的数据字段可能需要不同的表示方式,使用联合体可以方便地处理。
联合体与结构体的区别
特性 结构体(struct 联合体(union
内存分配 每个成员都有独立的内存空间 所有成员共享同一块内存空间
大小 所有成员大小之和(考虑内存对齐) 最大成员的大小
成员访问 可以同时访问所有成员 只能正确访问最后一次赋值的成员
用途 表示具有多个属性的实体 表示不同类型的可选数据,节省内存空间
示例:使用联合体进行数据类型转换
#include <stdio.h>

// 定义一个联合体,用于数据类型转换
union Converter {
    float f;
    unsigned int i;
};

int main() {
    union Converter conv;

    conv.f = 3.14f;
    printf("浮点数: %.2f\n", conv.f);
    printf("对应的二进制表示: 0x%X\n", conv.i);

    // 通过整数成员访问浮点数的二进制表示
    conv.i = 0x4048F5C3;
    printf("整数: %u\n", conv.i);
    printf("对应的浮点数: %.2f\n", conv.f);

    return 0;
}

输出(具体二进制表示可能因系统不同):

浮点数: 3.14
对应的二进制表示: 0x4048F5C3
整数: 1078523331
对应的浮点数: 3.14

详细解释

  • 联合体Converter允许通过成员fi访问同一块内存。
  • 将浮点数赋值给f,然后通过i查看其二进制表示。
  • 通过直接赋值给i,再通过f解释该二进制表示为浮点数。
注意事项
  • 覆盖问题:赋值给联合体的一个成员会覆盖之前赋值的成员,导致其他成员的值不再有效。
  • 数据一致性:确保在使用联合体时,只访问最近赋值的成员,以避免未定义行为。
  • 内存对齐:联合体的内存对齐与结构体类似,需注意内存对齐对性能和正确性的影响。

4 枚举类型

枚举类型(enum)是用户定义的一种数据类型,用于表示一组具名的整型常量。枚举类型提高了代码的可读性和可维护性,使得程序员可以使用有意义的名称代替具体的数值。

枚举类型的定义

语法

enum 枚举名 {
    常量名1,
    常量名2,
    // ...
    常量名N
};
  • enum:关键字,用于定义枚举类型。
  • 枚举名:枚举类型的名称。
  • 常量名:枚举成员的名称,默认从0开始依次递增,可以手动指定值。

示例

#include <stdio.h>

// 定义一个表示星期的枚举类型
enum Weekday {
    SUNDAY,    // 0
    MONDAY,    // 1
    TUESDAY,   // 2
    WEDNESDAY, // 3
    THURSDAY,  // 4
    FRIDAY,    // 5
    SATURDAY   // 6
};

int main() {
    enum Weekday today;

    today = WEDNESDAY;
    printf("今天是星期%d\n", today); // 输出: 今天是星期3

    return 0;
}

输出

今天是星期3

详细解释

  • 枚举成员SUNDAYSATURDAY分别被赋予从06的整数值。
  • 通过枚举类型Weekday声明变量today,并赋值为WEDNESDAY
枚举成员的自定义值

可以为枚举成员手动指定整数值,后续成员值会自动递增。

示例

#include <stdio.h>

// 定义一个带有自定义值的枚举类型
enum ErrorCode {
    SUCCESS = 0,
    ERROR_NOT_FOUND = 404,
    ERROR_SERVER = 500
};

int main() {
    enum ErrorCode code;

    code = ERROR_NOT_FOUND;
    printf("错误代码: %d\n", code); // 输出: 错误代码: 404

    code = ERROR_SERVER;
    printf("错误代码: %d\n", code); // 输出: 错误代码: 500

    return 0;
}

输出

错误代码: 404
错误代码: 500

详细解释

  • 枚举成员SUCCESS被赋值为0ERROR_NOT_FOUND404ERROR_SERVER500
  • 可以根据需要为枚举成员指定具体的值,便于与外部系统或协议接口匹配。
枚举类型的使用

枚举类型可以用于控制流程、状态表示等场景,增强代码的可读性。

示例

#include <stdio.h>

// 定义一个表示交通信号灯的枚举类型
typedef enum {
    RED,
    YELLOW,
    GREEN
} TrafficLight;

int main() {
    TrafficLight light = RED;

    switch(light) {
        case RED:
            printf("停止!\n");
            break;
        case YELLOW:
            printf("准备!\n");
            break;
        case GREEN:
            printf("前进!\n");
            break;
        default:
            printf("未知信号灯状态。\n");
    }

    return 0;
}

输出

停止!

详细解释

  • 使用typedef简化枚举类型的声明,可以直接使用TrafficLight作为类型名。
  • 通过switch语句根据枚举成员执行不同的操作,提高代码的可读性和维护性。
枚举类型与整数的关系

枚举类型在C语言中本质上是整数类型,可以与整数进行互换,但需注意类型安全。

示例

#include <stdio.h>

// 定义枚举类型
enum Color {
    RED = 1,
    GREEN,
    BLUE
};

int main() {
    enum Color c = GREEN;
    int num = BLUE;

    printf("枚举成员 GREEN 的值: %d\n", c); // 输出: 2
    printf("整数 3 对应的枚举成员: %d\n", num); // 输出: 3

    // 比较枚举成员与整数
    if(c == 2) {
        printf("c 等于 2\n"); // 输出此行
    }

    return 0;
}

输出

枚举成员 GREEN 的值: 2
整数 3 对应的枚举成员: 3
c 等于 2

详细解释

  • 枚举成员GREEN被自动赋值为2BLUE3
  • 可以将枚举成员赋值给整数变量,反之亦然,但需确保值在枚举成员的定义范围内,以避免逻辑错误。
注意事项
  • 枚举类型的范围:枚举成员的值必须在整数类型的范围内。
  • 避免重复值:不同的枚举成员可以拥有相同的值,但应谨慎使用,以免引发混淆。
  • 类型安全:C语言中的枚举类型不是强类型,枚举变量可以与整数直接互换,但在大型项目中应注意类型安全,以防止错误。

总结

结构体、联合体和枚举类型是C语言中用于组织和管理复杂数据的强大工具。通过合理使用这些数据结构,程序员可以编写出更具结构性、可读性和可维护性的代码。本章详细介绍了结构体的定义与使用、结构体数组与指针、联合体的定义与应用以及枚举类型的定义与使用。以下是本章的关键点:

  • 结构体
    • 将不同类型的变量组合在一起,表示一个实体的多个属性。
    • 支持嵌套结构体,增强数据结构的表达能力。
    • 使用typedef简化结构体类型的声明。
  • 结构体数组与指针
    • 结构体数组用于存储多个结构体实例,适用于批量管理数据。
    • 结构体指针允许通过指针访问和修改结构体成员,提高操作灵活性。
    • 结构体数组与指针结合使用,可实现高效的数据遍历和操作。
  • 联合体
    • 所有成员共享同一块内存空间,适用于节省内存或实现数据类型转换。
    • 注意成员赋值覆盖问题,确保只访问最后赋值的成员。
    • 联合体与结构体相比,适用于不同类型数据互斥使用的场景。
  • 枚举类型
    • 定义一组具名的整型常量,增强代码可读性。
    • 可以为枚举成员指定具体的整数值,便于与外部系统接口。
    • 通过枚举类型实现状态表示、控制流程等功能,提高代码的逻辑清晰度。

网站公告

今日签到

点亮在社区的每一天
去签到