C语言基础-八、结构体和共同(用)体

目录

1、结构体定义与使用

[1.1 结构体的基本概念](#1.1 结构体的基本概念)

[1.2 结构体定义格式](#1.2 结构体定义格式)

[1.3 结构体变量的使用](#1.3 结构体变量的使用)

[1.4 typedef 起别名](#1.4 typedef 起别名)

[1.5 结构体定义位置](#1.5 结构体定义位置)

2、结构体数组

[2.1 结构体数组定义与初始化](#2.1 结构体数组定义与初始化)

[2.2 结构体数组练习:投票系统](#2.2 结构体数组练习:投票系统)

3、结构体作为函数参数

[3.1 值传递 vs 地址传递](#3.1 值传递 vs 地址传递)

[3.2 结构体参数传递对比](#3.2 结构体参数传递对比)

[3.3 函数返回结构体](#3.3 函数返回结构体)

4、结构体嵌套

[4.1 嵌套结构体定义](#4.1 嵌套结构体定义)

[4.2 多层嵌套](#4.2 多层嵌套)

5、结构体与指针

[5.1 结构体指针访问](#5.1 结构体指针访问)

[5.2 箭头运算符 vs 点运算符](#5.2 箭头运算符 vs 点运算符)

6、结构体内存对齐

[6.1 内存对齐规则](#6.1 内存对齐规则)

[6.2 内存对齐示例](#6.2 内存对齐示例)

[6.3 内存布局可视化](#6.3 内存布局可视化)

[6.4 优化建议](#6.4 优化建议)

[6.5 强制对齐(pragma)](#6.5 强制对齐(pragma))

7、共用体(联合体)

[7.1 共用体定义与使用](#7.1 共用体定义与使用)

[7.2 共用体内存特点](#7.2 共用体内存特点)

[7.3 共用体应用场景](#7.3 共用体应用场景)

场景1:节省内存

场景2:类型转换

场景3:变体数据类型

8、结构体与共用体对比

9、综合练习

[9.1 学生管理系统](#9.1 学生管理系统)

[9.2 配置文件解析器](#9.2 配置文件解析器)

10、常见错误与避坑指南

11、最佳实践总结

结构体 :将多个不同类型的数据组合成一个整体的自定义数据类型

共用体 :多个变量共享同一块内存空间的特殊数据类型

核心价值:组织复杂数据、提高代码可读性、实现数据抽象


1、结构体定义与使用

1.1 结构体的基本概念

复制代码
结构体 = 一批相关数据的集合
每个数据称为"成员"或"字段"
适用于描述具有多个属性的事物

应用场景举例

  • 学生信息(姓名、年龄、成绩)
  • 员工信息(工号、部门、工资)
  • 商品信息(编号、名称、价格、库存)
  • 坐标点(x, y, z)

1.2 结构体定义格式

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

// 方式1:标准定义
struct Student {
    char name[100];
    int age;
    float score;
};

// 方式2:定义同时创建变量
struct Teacher {
    char name[50];
    int age;
} teacher1, teacher2;

// 方式3:匿名结构体(只能使用一次)
struct {
    int x;
    int y;
} point = {10, 20};

int main() {
    // 使用结构体定义变量
    struct Student stu1;
    struct Student stu2;
    
    // 方式4:定义时初始化
    struct Student stu3 = {"张三", 20, 95.5};
    
    return 0;
}

1.3 结构体变量的使用

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

struct GirlFriend {
    char name[100];
    int age;
    char gender;
    double height;
};

int main() {
    // 定义结构体变量
    struct GirlFriend gf1;
    
    // 成员赋值(使用.运算符)
    strcpy(gf1.name, "小诗诗");
    gf1.age = 23;
    gf1.gender = 'F';
    gf1.height = 1.63;
    
    // 输出打印
    printf("=== 女朋友信息 ===\n");
    printf("姓名: %s\n", gf1.name);
    printf("年龄: %d\n", gf1.age);
    printf("性别: %c\n", gf1.gender);
    printf("身高: %.2f米\n", gf1.height);
    
    // 结构体整体赋值
    struct GirlFriend gf2 = gf1;  // 拷贝所有成员
    printf("\n拷贝后 gf2 姓名: %s\n", gf2.name);
    
    return 0;
}

1.4 typedef 起别名

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

// 方式1:给结构体起别名
typedef struct {
    char name[100];
    int attack;
    int defense;
    int blood;
} Ultraman;

// 方式2:同时保留原名和别名
typedef struct Student {
    char name[50];
    int id;
    float score;
} Stu;

int main() {
    // 使用别名定义变量(更简洁)
    Ultraman taro = {"泰罗", 100, 90, 500};
    Ultraman leo = {"雷欧", 90, 80, 450};
    Ultraman edo = {"艾迪", 120, 70, 600};
    
    // 放入数组
    Ultraman arr[] = {taro, leo, edo};
    int len = sizeof(arr) / sizeof(arr[0]);
    
    printf("=== 奥特曼信息 ===\n");
    for (int i = 0; i < len; i++) {
        printf("%-10s 攻击:%3d 防御:%3d 血量:%4d\n", 
               arr[i].name, arr[i].attack, arr[i].defense, arr[i].blood);
    }
    
    // 使用Stu别名
    Stu s1 = {"张三", 1001, 88.5};
    printf("\n学生: %s, 学号: %d, 成绩: %.1f\n", s1.name, s1.id, s1.score);
    
    return 0;
}

1.5 结构体定义位置

位置 作用域 生命周期 推荐使用
函数外(全局) 所有函数可用 程序全程 ✅ 推荐
函数内(局部) 仅本函数可用 函数执行期间 ❌ 不推荐
复制代码
#include <stdio.h>

// ✅ 全局定义(推荐)
struct GlobalStruct {
    int value;
};

void func1() {
    struct GlobalStruct gs;  // 可以使用
}

void func2() {
    // ❌ 局部定义(不推荐)
    struct LocalStruct {
        int value;
    };
    struct LocalStruct ls;  // 仅func2可用
}

int main() {
    struct GlobalStruct gs;  // ✅ 可以使用
    // struct LocalStruct ls;  // ❌ 无法使用
    return 0;
}

2、结构体数组

2.1 结构体数组定义与初始化

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

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

int main() {
    // 方式1:先定义后赋值
    struct Student stu1 = {"张三", 20, 85.5};
    struct Student stu2 = {"李四", 21, 90.0};
    struct Student stu3 = {"王五", 22, 78.5};
    
    // 方式2:直接初始化数组
    struct Student stuArr[3] = {
        {"张三", 20, 85.5},
        {"李四", 21, 90.0},
        {"王五", 22, 78.5}
    };
    
    // 方式3:部分初始化
    struct Student stuArr2[5] = {
        {"张三", 20, 85.5},
        {"李四", 21, 90.0}
        // 剩余元素自动初始化为0/"\0"
    };
    
    // 遍历数组
    printf("=== 学生信息表 ===\n");
    printf("%-15s %-5s %-8s\n", "姓名", "年龄", "成绩");
    printf("----------------------------\n");
    for (int i = 0; i < 3; i++) {
        printf("%-15s %-5d %-8.1f\n", 
               stuArr[i].name, stuArr[i].age, stuArr[i].score);
    }
    
    return 0;
}

2.2 结构体数组练习:投票系统

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

struct Spot {
    char name[20];
    int count;
};

int main() {
    // 1. 定义4个景点
    struct Spot arr[4] = {
        {"故宫", 0},
        {"长城", 0},
        {"天坛", 0},
        {"颐和园", 0}
    };
    int spotCount = 4;
    int voterCount = 80;
    
    // 2. 设置随机数种子
    srand(time(NULL));
    
    // 3. 模拟80名同学投票
    printf("=== 模拟投票 ===\n");
    for (int i = 0; i < voterCount; i++) {
        int choose = rand() % spotCount;  // 0-3
        arr[choose].count++;
    }
    
    // 4. 显示投票结果
    printf("\n=== 投票结果 ===\n");
    printf("%-15s %-10s\n", "景点", "票数");
    printf("----------------------------\n");
    for (int i = 0; i < spotCount; i++) {
        printf("%-15s %-10d\n", arr[i].name, arr[i].count);
    }
    
    // 5. 找出最高票
    int maxCount = arr[0].count;
    for (int i = 1; i < spotCount; i++) {
        if (arr[i].count > maxCount) {
            maxCount = arr[i].count;
        }
    }
    
    // 6. 显示获胜者(可能有多个)
    printf("\n=== 最受欢迎景点 ===\n");
    for (int i = 0; i < spotCount; i++) {
        if (arr[i].count == maxCount) {
            printf("🏆 %s (%d票)\n", arr[i].name, arr[i].count);
        }
    }
    
    return 0;
}

3、结构体作为函数参数

3.1 值传递 vs 地址传递

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

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

// 方式1:值传递(拷贝整个结构体)
void modifyByValue(Student stu) {
    strcpy(stu.name, "李四");
    stu.age = 25;
    printf("函数内: %s, %d\n", stu.name, stu.age);
}

// 方式2:地址传递(推荐,效率高)
void modifyByPointer(Student *stu) {
    strcpy(stu->name, "王五");
    stu->age = 30;
    printf("函数内: %s, %d\n", stu->name, stu->age);
}

// 方式3:const指针(只读,不修改)
void printStudent(const Student *stu) {
    printf("只读访问: %s, %d\n", stu->name, stu->age);
    // stu->age = 100;  // ❌ 编译错误
}

int main() {
    Student s1 = {"张三", 20};
    
    printf("原始数据: %s, %d\n\n", s1.name, s1.age);
    
    // 值传递:不影响原数据
    printf("【值传递】\n");
    modifyByValue(s1);
    printf("调用后: %s, %d\n\n", s1.name, s1.age);  // 不变
    
    // 地址传递:影响原数据
    printf("【地址传递】\n");
    modifyByPointer(&s1);
    printf("调用后: %s, %d\n\n", s1.name, s1.age);  // 已变
    
    // const指针:只读访问
    printf("【const指针】\n");
    printStudent(&s1);
    
    return 0;
}

3.2 结构体参数传递对比

传递方式 语法 效率 是否修改原值 适用场景
值传递 func(Student s) 低(拷贝) ❌ 否 小结构体、只读
指针传递 func(Student *s) 高(地址) ✅ 是 大结构体、需修改
const指针 func(const Student *s) 高(地址) ❌ 否 大结构体、只读

3.3 函数返回结构体

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

typedef struct {
    int id;
    char name[50];
    float score;
} Student;

// 返回结构体(C99支持)
Student createStudent(int id, const char *name, float score) {
    Student s;
    s.id = id;
    strcpy(s.name, name);
    s.score = score;
    return s;  // 返回整个结构体
}

// 返回结构体指针(注意生命周期)
Student* createStudentPtr(int id, const char *name, float score) {
    // ❌ 错误:返回局部变量地址
    // Student s;
    // return &s;
    
    // ✅ 正确:动态分配
    Student *s = (Student*)malloc(sizeof(Student));
    s->id = id;
    strcpy(s->name, name);
    s->score = score;
    return s;
}

// 返回多个值(通过指针参数)
void getTopStudent(Student students[], int len, Student *top) {
    *top = students[0];
    for (int i = 1; i < len; i++) {
        if (students[i].score > top->score) {
            *top = students[i];
        }
    }
}

int main() {
    // 返回结构体
    Student s1 = createStudent(1001, "张三", 95.5);
    printf("学生: %s, 成绩: %.1f\n", s1.name, s1.score);
    
    // 返回结构体指针
    Student *s2 = createStudentPtr(1002, "李四", 88.0);
    printf("学生: %s, 成绩: %.1f\n", s2->name, s2->score);
    free(s2);  // 记得释放
    
    // 返回多个值
    Student arr[] = {
        {1, "A", 85.0},
        {2, "B", 92.0},
        {3, "C", 78.0}
    };
    Student top;
    getTopStudent(arr, 3, &top);
    printf("最高分: %s (%.1f)\n", top.name, top.score);
    
    return 0;
}

4、结构体嵌套

4.1 嵌套结构体定义

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

// 子结构体:联系方式
struct Contact {
    char phone[20];
    char email[50];
    char address[100];
};

// 主结构体:学生信息
struct Student {
    char name[50];
    int age;
    char gender;
    float score;
    struct Contact contact;  // 嵌套结构体
};

int main() {
    // 方式1:逐个赋值
    struct Student stu1;
    strcpy(stu1.name, "张三");
    stu1.age = 20;
    stu1.gender = 'M';
    stu1.score = 88.5;
    strcpy(stu1.contact.phone, "13812345678");
    strcpy(stu1.contact.email, "zhangsan@qq.com");
    strcpy(stu1.contact.address, "北京市朝阳区");
    
    // 方式2:初始化列表
    struct Student stu2 = {
        "李四",
        21,
        'F',
        92.0,
        {"13987654321", "lisi@qq.com", "上海市浦东新区"}
    };
    
    // 访问嵌套成员
    printf("=== 学生信息 ===\n");
    printf("姓名: %s\n", stu2.name);
    printf("年龄: %d\n", stu2.age);
    printf("电话: %s\n", stu2.contact.phone);
    printf("邮箱: %s\n", stu2.contact.email);
    printf("地址: %s\n", stu2.contact.address);
    
    return 0;
}

4.2 多层嵌套

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

// 最内层:日期
struct Date {
    int year;
    int month;
    int day;
};

// 中间层:地址
struct Address {
    char province[50];
    char city[50];
    char street[100];
};

// 最外层:员工
struct Employee {
    int id;
    char name[50];
    float salary;
    struct Date hireDate;
    struct Address addr;
};

int main() {
    struct Employee emp = {
        1001,
        "王五",
        15000.0,
        {2020, 6, 15},
        {"广东省", "深圳市", "南山区科技园"}
    };
    
    printf("=== 员工信息 ===\n");
    printf("工号: %d\n", emp.id);
    printf("姓名: %s\n", emp.name);
    printf("薪资: %.2f\n", emp.salary);
    printf("入职日期: %d-%02d-%02d\n", 
           emp.hireDate.year, emp.hireDate.month, emp.hireDate.day);
    printf("地址: %s%s%s\n", 
           emp.addr.province, emp.addr.city, emp.addr.street);
    
    return 0;
}

5、结构体与指针

5.1 结构体指针访问

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

typedef struct {
    int id;
    char name[50];
    float score;
} Student;

int main() {
    Student stu = {1001, "张三", 88.5};
    
    // 结构体指针
    Student *pStu = &stu;
    
    // 访问方式对比
    printf("=== 访问方式对比 ===\n");
    printf("stu.id = %d\n", stu.id);           // 直接访问
    printf("(*pStu).id = %d\n", (*pStu).id);   // 解引用后访问
    printf("pStu->id = %d\n", pStu->id);       // 箭头运算符(推荐)
    
    printf("\nstu.name = %s\n", stu.name);
    printf("pStu->name = %s\n", pStu->id);
    
    // 修改成员
    pStu->score = 95.0;
    printf("\n修改后成绩: %.1f\n", stu.score);
    
    // 指针数组
    Student stu2 = {1002, "李四", 90.0};
    Student stu3 = {1003, "王五", 85.0};
    
    Student *arr[] = {&stu, &stu2, &stu3};
    int len = sizeof(arr) / sizeof(arr[0]);
    
    printf("\n=== 指针数组遍历 ===\n");
    for (int i = 0; i < len; i++) {
        printf("%s: %.1f分\n", arr[i]->name, arr[i]->score);
    }
    
    return 0;
}

5.2 箭头运算符 vs 点运算符

运算符 使用场景 示例
. 结构体变量 stu.name
-> 结构体指针 pStu->name
复制代码
// 等价关系
(*pStu).name  ==  pStu->name

6、结构体内存对齐

6.1 内存对齐规则

复制代码
规则1:首地址规则
  结构体首地址能被最大对齐系数整除

规则2:成员对齐规则
  每个成员地址必须是其自身大小(或对齐系数)的整数倍

规则3:总大小规则
  结构体总大小必须是最大成员大小的整数倍(向上取整)

6.2 内存对齐示例

复制代码
#include <stdio.h>

// 示例1:未优化(24字节)
struct Layout1 {
    double a;    // 8字节,偏移0-7
    char b;      // 1字节,偏移8
                 // 填充3字节,偏移9-11
    int c;       // 4字节,偏移12-15
    char d;      // 1字节,偏移16
                 // 填充7字节,偏移17-23
};               // 总大小:24字节

// 示例2:优化后(16字节)
struct Layout2 {
    char b;      // 1字节,偏移0
    char d;      // 1字节,偏移1
                 // 填充2字节,偏移2-3
    int c;       // 4字节,偏移4-7
    double a;    // 8字节,偏移8-15
};               // 总大小:16字节

// 示例3:最优排列(13字节→16字节对齐)
struct Layout3 {
    char b;      // 1字节
    char d;      // 1字节
    int c;       // 4字节
    double a;    // 8字节
};               // 总大小:16字节(8的倍数)

int main() {
    printf("Layout1大小: %zu 字节\n", sizeof(struct Layout1));  // 24
    printf("Layout2大小: %zu 字节\n", sizeof(struct Layout2));  // 16
    printf("Layout3大小: %zu 字节\n", sizeof(struct Layout3));  // 16
    
    printf("\n节省空间: %zu 字节 (%.1f%%)\n", 
           sizeof(struct Layout1) - sizeof(struct Layout2),
           (sizeof(struct Layout1) - sizeof(struct Layout2)) * 100.0 / sizeof(struct Layout1));
    
    return 0;
}

6.3 内存布局可视化

复制代码
struct Layout1 (24字节):
┌────────┬────────┬────────┬────────┐
│  double a (8)   │ 偏移 0-7  │
├────────┼────────┼────────┼────────┤
│ char b │  填充   │  填充   │  填充   │ 偏移 8-11
├────────┴────────┴────────┴────────┤
│    int c (4)    │ 偏移 12-15 │
├────────┬────────┬────────┬────────┤
│ char d │  填充 (7字节)     │ 偏移 16-23
└────────┴────────┴────────┴────────┘

struct Layout2 (16字节):
┌────────┬────────┬────────┬────────┐
│ char b │ char d │  填充   │  填充   │ 偏移 0-3
├────────┴────────┴────────┴────────┤
│    int c (4)    │ 偏移 4-7  │
├────────┬────────┬────────┬────────┤
│   double a (8)       │ 偏移 8-15  │
└────────┴────────┴────────┴────────┘

节省:8字节 (33.3%)

6.4 优化建议

复制代码
// ✅ 推荐:按大小排序(小到大或大到小)
struct Optimized {
    char a;      // 1
    char b;      // 1
    short c;     // 2
    int d;       // 4
    double e;    // 8
};               // 16字节

// ❌ 不推荐:大小交错
struct Unoptimized {
    double e;    // 8
    char a;      // 1
    int d;       // 4
    char b;      // 1
    short c;     // 2
};               // 24字节

6.5 强制对齐(pragma)

复制代码
#include <stdio.h>

// 默认对齐
struct Default {
    char a;
    int b;
    char c;
};

// 1字节对齐(紧凑,但可能降低性能)
#pragma pack(push, 1)
struct Packed {
    char a;
    int b;
    char c;
};
#pragma pack(pop)

int main() {
    printf("默认对齐: %zu 字节\n", sizeof(struct Default));  // 12
    printf("1字节对齐: %zu 字节\n", sizeof(struct Packed));  // 6
    
    return 0;
}

⚠️ 注意:强制对齐可能降低CPU访问效率,仅在内存紧张时使用


7、共用体(联合体)

7.1 共用体定义与使用

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

// 定义共用体
union Data {
    int i;
    float f;
    char str[20];
};

int main() {
    union Data data;
    
    // 每次只能使用一个成员
    data.i = 10;
    printf("整数: %d\n", data.i);
    
    data.f = 20.5;
    printf("浮点: %.2f\n", data.f);
    // printf("%d\n", data.i);  // ❌ 值已改变
    
    strcpy(data.str, "Hello");
    printf("字符串: %s\n", data.str);
    
    return 0;
}

7.2 共用体内存特点

复制代码
#include <stdio.h>

union Money {
    int moneyi;          // 4字节
    double moneyd;       // 8字节
    char moneystr[100];  // 100字节
};

int main() {
    union Money money;
    
    // 所有成员共享同一地址
    printf("moneyi地址: %p\n", (void*)&money.moneyi);
    printf("moneyd地址: %p\n", (void*)&money.moneyd);
    printf("moneystr地址: %p\n", (void*)&money.moneystr);
    
    // 大小 = 最大成员(考虑对齐)
    printf("moneyi大小: %zu\n", sizeof(money.moneyi));   // 4
    printf("moneyd大小: %zu\n", sizeof(money.moneyd));   // 8
    printf("moneystr大小: %zu\n", sizeof(money.moneystr)); // 100
    printf("union大小: %zu\n", sizeof(money));           // 100或104(对齐)
    
    // 数据互斥演示
    money.moneyi = 99;
    printf("存入整数后: %d\n", money.moneyi);
    
    money.moneyd = 1.23;
    printf("存入浮点后: %.2f\n", money.moneyd);
    printf("读取整数: %d (错误!数据已覆盖)\n", money.moneyi);
    
    return 0;
}

7.3 共用体应用场景

场景1:节省内存
复制代码
#include <stdio.h>

// 使用结构体(浪费内存)
struct ProductStruct {
    int id;
    char name[50];
    int intPrice;      // 整数价格
    double floatPrice; // 浮点价格
    // 但每次只用一种价格
};  // 约64字节

// 使用共用体(节省内存)
struct ProductUnion {
    int id;
    char name[50];
    union {
        int intPrice;
        double floatPrice;
    } price;
    int priceType;  // 0=整数,1=浮点
};  // 约60字节

// 大量数据时节省明显
场景2:类型转换
复制代码
#include <stdio.h>

union TypeConverter {
    float f;
    unsigned int i;
};

int main() {
    union TypeConverter converter;
    
    converter.f = 3.14f;
    printf("浮点数: %f\n", converter.f);
    printf("二进制表示: 0x%X\n", converter.i);
    
    // 修改二进制表示
    converter.i = 0x40490FDB;
    printf("新浮点数: %f\n", converter.f);
    
    return 0;
}
场景3:变体数据类型
复制代码
#include <stdio.h>
#include <string.h>

typedef enum {
    TYPE_INT,
    TYPE_FLOAT,
    TYPE_STRING
} DataType;

typedef struct {
    DataType type;
    union {
        int i;
        float f;
        char str[50];
    } data;
} Variant;

void printVariant(Variant v) {
    switch (v.type) {
        case TYPE_INT:
            printf("整数: %d\n", v.data.i);
            break;
        case TYPE_FLOAT:
            printf("浮点: %.2f\n", v.data.f);
            break;
        case TYPE_STRING:
            printf("字符串: %s\n", v.data.str);
            break;
    }
}

int main() {
    Variant v1 = {TYPE_INT, {.i = 100}};
    Variant v2 = {TYPE_FLOAT, {.f = 3.14}};
    Variant v3 = {TYPE_STRING, {.str = "Hello"}};
    
    printVariant(v1);
    printVariant(v2);
    printVariant(v3);
    
    return 0;
}

8、结构体与共用体对比

特性 结构体 (struct) 共用体 (union)
用途 描述事物的多个属性 同一数据的多种表示
内存 各成员独立存储 所有成员共享内存
大小 所有成员之和(+对齐) 最大成员大小(+对齐)
赋值 可同时给所有成员赋值 每次只能给一个成员赋值
访问 所有成员可同时访问 最后赋值的成员有效
地址 各成员地址不同 所有成员地址相同
应用 记录、配置、实体 类型转换、节省内存

9、综合练习

9.1 学生管理系统

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

#define MAX_STUDENTS 100

typedef struct {
    int id;
    char name[50];
    int age;
    char gender;
    float scores[3];  // 语数英
    float total;
    float average;
} Student;

// 计算总分和平均分
void calculateStats(Student *s) {
    s->total = 0;
    for (int i = 0; i < 3; i++) {
        s->total += s->scores[i];
    }
    s->average = s->total / 3;
}

// 添加学生
int addStudent(Student students[], int count) {
    if (count >= MAX_STUDENTS) {
        printf("学生数量已达上限!\n");
        return count;
    }
    
    Student *s = &students[count];
    printf("请输入学号: ");
    scanf("%d", &s->id);
    printf("请输入姓名: ");
    scanf("%s", s->name);
    printf("请输入年龄: ");
    scanf("%d", &s->age);
    printf("请输入性别 (M/F): ");
    scanf(" %c", &s->gender);
    printf("请输入语文成绩: ");
    scanf("%f", &s->scores[0]);
    printf("请输入数学成绩: ");
    scanf("%f", &s->scores[1]);
    printf("请输入英语成绩: ");
    scanf("%f", &s->scores[2]);
    
    calculateStats(s);
    return count + 1;
}

// 显示所有学生
void displayStudents(Student students[], int count) {
    if (count == 0) {
        printf("暂无学生记录\n");
        return;
    }
    
    printf("\n%-8s %-15s %-5s %-5s %-8s %-8s %-8s %-8s %-8s\n",
           "学号", "姓名", "年龄", "性别", "语文", "数学", "英语", "总分", "平均分");
    printf("--------------------------------------------------------------------------------\n");
    
    for (int i = 0; i < count; i++) {
        printf("%-8d %-15s %-5d %-5c %-8.1f %-8.1f %-8.1f %-8.1f %-8.1f\n",
               students[i].id, students[i].name, students[i].age, students[i].gender,
               students[i].scores[0], students[i].scores[1], students[i].scores[2],
               students[i].total, students[i].average);
    }
}

// 查找学生
int findStudent(Student students[], int count, int id) {
    for (int i = 0; i < count; i++) {
        if (students[i].id == id) {
            return i;
        }
    }
    return -1;
}

// 排序(按平均分降序)
void sortByAverage(Student students[], int count) {
    for (int i = 0; i < count - 1; i++) {
        for (int j = 0; j < count - 1 - i; j++) {
            if (students[j].average < students[j + 1].average) {
                Student temp = students[j];
                students[j] = students[j + 1];
                students[j + 1] = temp;
            }
        }
    }
}

int main() {
    Student students[MAX_STUDENTS];
    int count = 0;
    int choice;
    
    while (1) {
        printf("\n=== 学生管理系统 ===\n");
        printf("1. 添加学生\n");
        printf("2. 显示所有学生\n");
        printf("3. 查找学生\n");
        printf("4. 按平均分排序\n");
        printf("5. 退出\n");
        printf("请选择: ");
        scanf("%d", &choice);
        
        switch (choice) {
            case 1:
                count = addStudent(students, count);
                break;
            case 2:
                displayStudents(students, count);
                break;
            case 3: {
                int id;
                printf("请输入学号: ");
                scanf("%d", &id);
                int index = findStudent(students, count, id);
                if (index >= 0) {
                    printf("找到学生: %s\n", students[index].name);
                } else {
                    printf("未找到该学生\n");
                }
                break;
            }
            case 4:
                sortByAverage(students, count);
                printf("排序完成!\n");
                displayStudents(students, count);
                break;
            case 5:
                printf("再见!\n");
                return 0;
            default:
                printf("无效选择\n");
        }
    }
}

9.2 配置文件解析器

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

typedef enum {
    CONFIG_INT,
    CONFIG_FLOAT,
    CONFIG_STRING,
    CONFIG_BOOL
} ConfigType;

typedef struct {
    char key[50];
    ConfigType type;
    union {
        int i;
        float f;
        char str[100];
        int b;
    } value;
} ConfigItem;

void printConfig(ConfigItem config) {
    printf("%s = ", config.key);
    switch (config.type) {
        case CONFIG_INT:
            printf("%d (整数)\n", config.value.i);
            break;
        case CONFIG_FLOAT:
            printf("%.2f (浮点)\n", config.value.f);
            break;
        case CONFIG_STRING:
            printf("%s (字符串)\n", config.value.str);
            break;
        case CONFIG_BOOL:
            printf("%s (布尔)\n", config.value.b ? "true" : "false");
            break;
    }
}

int main() {
    ConfigItem configs[] = {
        {"port", CONFIG_INT, {.i = 8080}},
        {"version", CONFIG_FLOAT, {.f = 1.5}},
        {"name", CONFIG_STRING, {"MyApp"}},
        {"debug", CONFIG_BOOL, {.b = 1}}
    };
    
    int count = sizeof(configs) / sizeof(configs[0]);
    
    printf("=== 配置信息 ===\n");
    for (int i = 0; i < count; i++) {
        printConfig(configs[i]);
    }
    
    return 0;
}

10、常见错误与避坑指南

错误类型 错误示例 正确写法
忘记分号 struct Student { int a } struct Student { int a; };
修改常量字符串 char *s="hi"; strcpy(s,"x"); char s[]="hi"; strcpy(s,"x");
结构体比较 if(s1==s2) memcmp或逐成员比较
返回局部地址 return &localStruct; 使用malloc或返回值
未初始化 直接使用结构体 struct S s = {0};
嵌套赋值错误 s.contact = "123" strcpy(s.contact.phone, "123")
指针未检查 p->member if(p!=NULL) p->member
内存对齐忽视 随意排列成员 按大小排序成员
共用体误用 同时访问多个成员 只访问最后赋值的成员
typedef位置 函数内typedef 全局定义

11、最佳实践总结

复制代码
✅ 应该做的:
1. 结构体定义放在全局(头文件中)
2. 使用typedef简化类型名
3. 大结构体传指针(const表示只读)
4. 成员按大小排序优化内存
5. 初始化结构体为{0}
6. 使用->访问指针结构体成员
7. 共用体配合类型标记使用

❌ 不应该做的:
1. 不要在函数内定义结构体类型
2. 不要直接比较结构体(用==)
3. 不要返回局部结构体地址
4. 不要忽视内存对齐影响
5. 不要同时访问共用体多个成员
6. 不要忘记释放动态分配的结构体

相关推荐
plus4s1 小时前
2月21日(91-93题)
c++·算法
陈天伟教授2 小时前
人工智能应用- 材料微观:03. 微观结构:纳米金
人工智能·神经网络·算法·机器学习·推荐算法
拳里剑气2 小时前
C++ 11
开发语言·c++·学习方法
孞㐑¥2 小时前
算法—穷举,爆搜,深搜,回溯,剪枝
开发语言·c++·经验分享·笔记·算法
Highcharts.js2 小时前
如何根据派生数据创建钟形曲线图表?highcharts正态分布曲线使用指南:从创建到设置一文搞定
开发语言·javascript·开发文档·正态分布·highcharts·图表类型·钟形图
宇木灵2 小时前
C语言基础-九、动态内存分配
c语言·开发语言·学习·算法
追随者永远是胜利者2 小时前
(LeetCode-Hot100)301. 删除无效的括号
java·算法·leetcode·职场和发展·go
楼田莉子2 小时前
Linux网络学习:网络的基础概念
linux·运维·服务器·网络·c++·学习
追随者永远是胜利者2 小时前
(LeetCode-Hot100)239. 滑动窗口最大值
java·算法·leetcode·职场和发展·go