第八章:自定义数据类型——结构体和联合体¶
你好!欢迎来到第八章!在前面的章节,我们学习了基本数据类型(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:定义一个结构体
Rectangle,包含长和宽(整数)。写一个函数计算面积并返回。 - 练习2:定义一个结构体
Time,包含时、分、秒。写一个函数输入一个时间,输出它过了多少秒(从0:0:0开始)。 - 练习3:定义结构体
Student,包含姓名、学号、3门课成绩。输入5个学生,计算每个学生的平均分,并按平均分从高到低排序输出。 - 练习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:定义一个联合体,包含 int、double、char,分别输入并输出,观察内存覆盖现象。
- 练习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 int 和 unsigned char 数组共用内存)
作业5:简单通讯录¶
定义结构体 Contact,包含姓名、电话、邮箱。实现一个简单的通讯录管理系统,支持添加、删除、修改、查找、显示所有联系人。
好了,第八章的内容就到这里!你已经学会了如何用结构体组织不同类型的数据,用联合体节省内存,还了解了枚举类型。这些自定义类型让程序能更好地描述现实世界中的事物。加油!🚀