20260227 151824 Cpp 入门第八课

20260227_151824_CPP_入门第八课.md

第八章:自定义数据类型——结构体和联合体

你好!欢迎来到第八章!在前面的章节,我们学习了基本数据类型(int、double、char)和数组(一组相同类型的数据)。但生活中很多事物是由不同类型的数据组成的,比如一个学生有姓名(字符串)、年龄(整数)、身高(小数)。如果能把它们组合成一个整体,就方便多了!这就是结构体。另外还有一种特殊的类型叫联合体,可以节省内存。这一章我们就来学习如何自定义这些数据类型。


8.1 结构体程序范例

先来看一个例子:定义一个学生结构体,包含姓名、年龄、成绩,然后创建学生变量并输出信息。

#include <iostream>
#include <string>
using namespace std;

// 定义结构体类型 Student
struct Student {
    string name;   // 姓名
    int age;       // 年龄
    double score;  // 成绩
};

int main() {
    // 创建结构体变量并初始化
    Student stu1 = {"小明", 12, 98.5};
    Student stu2;

    // 给stu2的成员赋值
    stu2.name = "小红";
    stu2.age = 11;
    stu2.score = 95.0;

    // 输出学生信息
    cout << "学生1:" << stu1.name << ",年龄" << stu1.age << ",成绩" << stu1.score << endl;
    cout << "学生2:" << stu2.name << ",年龄" << stu2.age << ",成绩" << stu2.score << endl;

    return 0;
}

运行结果

学生1:小明,年龄12,成绩98.5
学生2:小红,年龄11,成绩95

8.2 结构体的用法

8.2.1 结构体的概念

结构体是一种可以包含多个不同数据类型的成员的数据类型。它就像一张表格,每一列都有不同的类型。你可以把结构体看作是自己创造的一种新类型,然后像使用int一样用它定义变量。

8.2.2 定义结构体类型

格式:

struct 结构体名 {
    数据类型 成员1;
    数据类型 成员2;
    ...
};   // 注意最后有分号

例如:

struct Point {
    int x;
    int y;
};

struct Book {
    string title;
    string author;
    double price;
};

8.2.3 创建结构体变量

有几种方式:

// 方式1:先定义类型,再定义变量
struct Point p1;   // C++中可以省略struct关键字,直接写 Point p1;

// 方式2:定义类型的同时定义变量
struct Point {
    int x;
    int y;
} p2, p3;   // 定义了p2和p3两个变量

// 方式3:直接定义匿名结构体变量
struct {
    int x;
    int y;
} p4;   // 但这种无法再用这个类型定义其他变量

8.2.4 初始化结构体变量

可以在定义时用大括号初始化:

Point p1 = {10, 20};           // x=10, y=20
Point p2 = {0};                // x=0, y=0(未指定的成员自动初始化为0)

如果使用C++11,也可以这样:

Point p3 {30, 40};   // 等号可选

8.2.5 访问结构体成员

使用点号 . 访问成员变量:

p1.x = 100;
p1.y = 200;
cout << "(" << p1.x << ", " << p1.y << ")" << endl;

8.2.6 结构体数组

可以把结构体放进数组,就像普通类型一样:

Student class3[3] = {
    {"小明", 12, 98.5},
    {"小红", 11, 95.0},
    {"小刚", 12, 88.0}
};

// 遍历输出
for (int i = 0; i < 3; i++) {
    cout << class3[i].name << " " << class3[i].age << " " << class3[i].score << endl;
}

8.2.7 结构体作为函数参数

结构体可以像普通变量一样传递给函数:

#include <iostream>
#include <string>
using namespace std;

struct Student {
    string name;
    int age;
    double score;
};

// 输出学生信息(传值,会复制一份)
void printStudent(Student stu) {
    cout << stu.name << " " << stu.age << " " << stu.score << endl;
}

// 修改学生年龄(传引用,可以直接修改原变量)
void setAge(Student &stu, int newAge) {
    stu.age = newAge;
}

int main() {
    Student s = {"小明", 12, 98.5};
    printStudent(s);
    setAge(s, 13);
    printStudent(s);   // 年龄变为13
    return 0;
}

8.2.8 结构体作为返回值

函数可以返回结构体:

Student createStudent(string name, int age, double score) {
    Student s;
    s.name = name;
    s.age = age;
    s.score = score;
    return s;
}

int main() {
    Student s = createStudent("小红", 11, 95.0);
    printStudent(s);
    return 0;
}

8.2.9 结构体嵌套

结构体的成员可以是另一个结构体:

struct Date {
    int year;
    int month;
    int day;
};

struct Student {
    string name;
    int age;
    double score;
    Date birthday;   // 嵌套结构体
};

int main() {
    Student s = {"小明", 12, 98.5, {2012, 5, 20}};
    cout << "出生日期:" << s.birthday.year << "年" << s.birthday.month << "月" << s.birthday.day << "日" << endl;
    return 0;
}

8.2.10 阶段性编程练习(结构体基础)

  1. 练习1:定义一个结构体 Rectangle,包含长和宽(整数)。写一个函数计算面积并返回。
  2. 练习2:定义一个结构体 Time,包含时、分、秒。写一个函数输入一个时间,输出它过了多少秒(从0:0:0开始)。
  3. 练习3:定义结构体 Student,包含姓名、学号、3门课成绩。输入5个学生,计算每个学生的平均分,并按平均分从高到低排序输出。
  4. 练习4:用结构体嵌套表示一个学生的家庭地址(省、市、街道),并输入输出。

8.3 联合体

8.3.1 联合体程序范例

联合体(union)和结构体类似,但它的所有成员共用同一块内存,也就是说,同时只能存储一个成员的值。它主要用于节省内存或处理不同类型的数据。

#include <iostream>
using namespace std;

union Data {
    int i;
    double d;
    char c;
};

int main() {
    Data data;
    data.i = 10;          // 现在存储整数
    cout << "整数:" << data.i << endl;

    data.d = 3.14;        // 现在存储浮点数,覆盖了之前的整数
    cout << "浮点数:" << data.d << endl;

    data.c = 'A';         // 现在存储字符
    cout << "字符:" << data.c << endl;

    // 注意:此时 data.i 的值已经无效,因为内存被覆盖
    cout << "整数(已无效):" << data.i << endl;   // 可能输出乱码

    return 0;
}

8.3.2 联合体的用法

  • 联合体的定义和结构体类似,只是关键字是 union
  • 所有成员共享同一块内存,所以联合体的大小等于最大成员的大小。
  • 在同一时刻只能使用一个成员,否则会导致数据混乱。
  • 常用于需要多种类型但只存一种的场景,比如协议解析、节省内存等。
定义和使用
union Value {
    int i;
    float f;
    char str[20];
};

int main() {
    Value v;
    v.i = 100;
    cout << v.i << endl;
    v.f = 3.14;   // 覆盖
    cout << v.f << endl;
    // 注意:不能再访问 v.i,因为值已被覆盖
    return 0;
}

8.3.3 联合体和结构体的区别

  • 结构体:每个成员都有自己的内存空间,可以同时存储所有成员的值。
  • 联合体:所有成员共用同一块内存,只能存储一个成员的值。

8.3.4 阶段性编程练习(联合体)

  1. 练习1:定义一个联合体,包含 int、double、char,分别输入并输出,观察内存覆盖现象。
  2. 练习2:用联合体实现一个可以存储整数或浮点数的变量,并编写一个函数,根据类型打印(需要额外用一个变量标记当前类型,这通常和联合体一起使用,构成“带标记的联合体”)。

8.4 枚举类型(补充知识)

虽然大纲没有明确要求,但枚举类型也是自定义数据类型的一种,常和结构体一起使用,这里简单介绍一下。

枚举用于定义一组命名的整数常量,使代码更易读。

#include <iostream>
using namespace std;

enum Weekday { MON, TUE, WED, THU, FRI, SAT, SUN };
// 默认 MON=0, TUE=1, ...

int main() {
    Weekday today = WED;
    if (today == WED) {
        cout << "今天是星期三" << endl;
    }

    // 枚举值可以转成整数
    cout << "MON = " << MON << endl;   // 输出0

    return 0;
}

可以指定枚举的值:

enum Color { RED = 1, GREEN = 2, BLUE = 4 };

C++11引入了强类型枚举(enum class),但初学者了解基本枚举即可。


8.5 编程实例讲解

实例1:学生成绩管理系统(结构体数组)

题目:有N个学生,每个学生有姓名、学号、语文、数学、英语成绩。要求:
- 输入学生信息
- 计算每个学生的总分和平均分
- 输出所有学生信息(包括总分平均分)
- 按总分从高到低排序输出

#include <iostream>
#include <string>
#include <algorithm>   // 用sort需要,但我们自己实现排序函数
using namespace std;

struct Student {
    string name;
    int id;
    int chinese;
    int math;
    int english;
    int total;      // 总分,可以计算后存储
    double average; // 平均分
};

// 输入学生信息
void inputStudent(Student &s) {
    cout << "请输入姓名、学号、语文、数学、英语:";
    cin >> s.name >> s.id >> s.chinese >> s.math >> s.english;
    s.total = s.chinese + s.math + s.english;
    s.average = s.total / 3.0;
}

// 输出学生信息
void printStudent(const Student &s) {
    cout << s.name << "\t" << s.id << "\t" << s.chinese << "\t" << s.math << "\t" << s.english
         << "\t总分:" << s.total << "\t平均:" << s.average << endl;
}

// 按总分冒泡排序(降序)
void sortStudents(Student stu[], int n) {
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j < n - 1 - i; j++) {
            if (stu[j].total < stu[j + 1].total) {   // 降序
                Student temp = stu[j];
                stu[j] = stu[j + 1];
                stu[j + 1] = temp;
            }
        }
    }
}

int main() {
    const int N = 3;   // 假设3个学生
    Student students[N];

    // 输入
    for (int i = 0; i < N; i++) {
        cout << "请输入第" << i+1 << "个学生信息:" << endl;
        inputStudent(students[i]);
    }

    // 排序
    sortStudents(students, N);

    // 输出
    cout << "\n排序后的学生信息(按总分降序):" << endl;
    cout << "姓名\t学号\t语文\t数学\t英语\t总分\t平均" << endl;
    for (int i = 0; i < N; i++) {
        printStudent(students[i]);
    }

    return 0;
}

实例2:点与矩形(结构体嵌套)

定义一个点结构体,一个矩形结构体(由左上点和右下点确定)。写函数判断一个点是否在矩形内(包括边界)。

#include <iostream>
using namespace std;

struct Point {
    int x;
    int y;
};

struct Rectangle {
    Point topLeft;     // 左上角
    Point bottomRight; // 右下角
};

// 判断点p是否在矩形r内
bool isPointInRect(const Point &p, const Rectangle &r) {
    return (p.x >= r.topLeft.x && p.x <= r.bottomRight.x &&
            p.y <= r.topLeft.y && p.y >= r.bottomRight.y);   // 注意y轴方向:左上角y大,右下角y小
}

int main() {
    Rectangle rect = { {0, 10}, {10, 0} };   // 左上(0,10),右下(10,0)
    Point p;
    cout << "输入点的坐标(x y):";
    cin >> p.x >> p.y;

    if (isPointInRect(p, rect)) {
        cout << "点在矩形内" << endl;
    } else {
        cout << "点不在矩形内" << endl;
    }

    return 0;
}

实例3:带标记的联合体(简单模拟)

有时我们需要一个变量能存储不同类型的数据,并记住当前是什么类型。可以用结构体包含一个枚举类型和一个联合体。

#include <iostream>
#include <string>
using namespace std;

enum DataType { TYPE_INT, TYPE_DOUBLE, TYPE_CHAR };

struct Variant {
    DataType type;
    union {
        int i;
        double d;
        char c;
    } data;
};

void printVariant(const Variant &v) {
    switch (v.type) {
        case TYPE_INT:
            cout << "整数:" << v.data.i << endl;
            break;
        case TYPE_DOUBLE:
            cout << "浮点数:" << v.data.d << endl;
            break;
        case TYPE_CHAR:
            cout << "字符:" << v.data.c << endl;
            break;
    }
}

int main() {
    Variant v1, v2, v3;
    v1.type = TYPE_INT;
    v1.data.i = 100;

    v2.type = TYPE_DOUBLE;
    v2.data.d = 3.14159;

    v3.type = TYPE_CHAR;
    v3.data.c = 'A';

    printVariant(v1);
    printVariant(v2);
    printVariant(v3);

    return 0;
}

8.6 第8章编程作业

恭喜你学完了结构体和联合体!现在来挑战几个综合题目。

作业1:图书管理系统

定义一个结构体 Book,包含书名、作者、出版社、价格、库存数量。实现以下功能:
- 输入一批图书信息(假设最多100本)
- 按书名查找图书,输出信息
- 按价格区间查找(如输入最低价和最高价)
- 统计库存总量和总价值

作业2:日期计算器

定义结构体 Date(年、月、日)。写函数:
- 判断某年是否是闰年
- 计算两个日期之间相差多少天(假设在同一年,或不同年)
- 输入一个日期,输出它是该年的第几天

作业3:学生成绩统计(文件版扩展)

用结构体数组存储学生信息,包括姓名、学号、多门课成绩。要求:
- 从文件读取(暂时可以先从键盘输入)
- 计算每门课的平均分、最高分、最低分
- 按总成绩排名
- 输出不及格学生名单

作业4:联合体应用——解析IP地址

IP地址通常用点分十进制表示,但也可以看作一个32位整数。用联合体实现:可以分别以整数形式和4个字节形式访问同一个IP。写程序输入一个整数形式的IP,输出点分十进制;或者反过来。(提示:可以用 unsigned intunsigned char 数组共用内存)

作业5:简单通讯录

定义结构体 Contact,包含姓名、电话、邮箱。实现一个简单的通讯录管理系统,支持添加、删除、修改、查找、显示所有联系人。


好了,第八章的内容就到这里!你已经学会了如何用结构体组织不同类型的数据,用联合体节省内存,还了解了枚举类型。这些自定义类型让程序能更好地描述现实世界中的事物。加油!🚀