结构体与联合体
约 3639 字大约 12 分钟
2025-06-25
结构体
结构体
在C++中,结构体(struct
) 是一种用户自定义的数据类型,用于将不同类型的数据组合成一个整体,以表示具有多个属性的复杂对象(如学生、点、矩形等)。在信奥赛中,结构体是处理多属性数据的核心工具,广泛应用于存储坐标、记录信息、定义复杂数据模型等场景。
一、结构体的定义与基本用法
结构体的定义与基本用法
结构体通过struct
关键字定义,可包含多个不同类型的成员变量(属性)。
1. 定义语法
struct 结构体名 {
数据类型 成员变量1;
数据类型 成员变量2;
// ... 更多成员变量
}; // 注意末尾的分号
2. 示例:定义“学生”结构体
// 定义一个表示学生的结构体
struct Student {
string name; // 姓名(string类型)
int age; // 年龄(int类型)
double score; // 成绩(double类型)
}; // 分号不可省略
二、结构体变量的创建与初始化
结构体变量的创建与初始化
定义结构体后,需创建结构体变量才能使用,初始化方式灵活。
1. 声明与初始化
#include <iostream>
#include <string>
using namespace std;
struct Student {
string name;
int age;
double score;
};
int main() {
// 方式1:先声明后赋值
Student s1;
s1.name = "Alice";
s1.age = 15;
s1.score = 92.5;
// 方式2:声明时初始化(按成员顺序)
Student s2 = {"Bob", 16, 88.0};
// 方式3:指定成员初始化(C++11及以上)
Student s3 = {.age = 15, .name = "Charlie", .score = 95.5};
return 0;
}
2. 访问成员变量
通过点运算符(.
) 访问结构体变量的成员:
cout << "姓名:" << s1.name << endl; // 输出Alice
cout << "年龄:" << s1.age << endl; // 输出15
cout << "成绩:" << s1.score << endl; // 输出92.5
三、结构体数组
结构体数组
结构体数组用于存储多个同类型的结构体对象(如多个学生、多个点),是信奥赛中批量处理复杂数据的常用方式。
1. 定义与初始化
// 定义学生数组(存储3个学生)
Student class1[3] = {
{"Alice", 15, 92.5},
{"Bob", 16, 88.0},
{"Charlie", 15, 95.5}
};
2. 访问数组元素的成员
通过“数组下标+点运算符”访问:
// 遍历学生数组
for (int i = 0; i < 3; i++) {
cout << "第" << i+1 << "名学生:"
<< class1[i].name << ","
<< class1[i].age << "岁,"
<< class1[i].score << "分" << endl;
}
四、结构体与函数
结构体与函数
结构体可作为函数的参数、返回值,实现复杂数据的传递和处理。
1. 结构体作为函数参数(传值)
// 函数:输出学生信息(传值,不修改原始数据)
void printStudent(Student s) {
cout << "姓名:" << s.name << ",年龄:" << s.age << ",成绩:" << s.score << endl;
}
// 调用
printStudent(s1); // 传递结构体变量s1
2. 结构体作为函数参数(传引用,高效且可修改)
// 函数:提高学生成绩(传引用,直接修改原始数据)
void improveScore(Student& s, double add) {
s.score += add; // 修改成员变量
}
// 调用
improveScore(s1, 5.5); // s1.score变为98.0
3. 结构体作为函数返回值
// 函数:创建并返回一个学生对象
Student createStudent(string name, int age, double score) {
Student s;
s.name = name;
s.age = age;
s.score = score;
return s;
}
// 调用
Student s4 = createStudent("David", 17, 85.0);
五、结构体的进阶用法
结构体的进阶用法
1. 结构体嵌套
结构体成员可以是另一个结构体(表示更复杂的对象):
// 定义点结构体
struct Point {
int x; // x坐标
int y; // y坐标
};
// 定义矩形结构体(嵌套Point)
struct Rectangle {
Point topLeft; // 左上角点
Point bottomRight; // 右下角点
string color; // 颜色
};
// 使用嵌套结构体
Rectangle rect;
rect.topLeft.x = 10;
rect.topLeft.y = 20;
rect.bottomRight.x = 30;
rect.bottomRight.y = 40;
rect.color = "red";
2. 结构体中的函数(成员函数)
C++结构体可以包含函数(成员函数),用于处理结构体自身的数据:
struct Student {
string name;
int age;
double score;
// 成员函数:判断是否及格
bool isPassed() {
return score >= 60; // 直接访问成员变量
}
// 成员函数:输出信息
void print() {
cout << name << " (" << age << "岁):" << score << "分" << endl;
}
};
// 使用成员函数
Student s = {"Alice", 15, 92.5};
if (s.isPassed()) {
s.print(); // 输出Alice (15岁):92.5分
}
六、信奥赛核心应用
信奥赛核心应用
1. 存储坐标与几何计算
struct Point {
int x, y;
// 计算两点距离的平方(避免浮点数)
int distance2(Point other) {
int dx = x - other.x;
int dy = y - other.y;
return dx*dx + dy*dy;
}
};
// 比较两点距离原点的远近
bool isFarther(Point a, Point b) {
return a.distance2({0,0}) > b.distance2({0,0});
}
2. 排序结构体数组
通过自定义排序规则(需结合sort
函数和比较函数):
#include <algorithm> // 含sort函数
// 比较函数:按成绩从高到低排序学生
bool compareScore(Student a, Student b) {
return a.score > b.score; // 降序
}
// 使用
Student class1[3] = {{"Alice",15,92.5}, {"Bob",16,88.0}, {"Charlie",15,95.5}};
sort(class1, class1 + 3, compareScore); // 按成绩排序
3. 记录复杂数据(如事件、任务)
struct Event {
string name; // 事件名称
int time; // 时间(分钟)
bool isUrgent; // 是否紧急
};
// 筛选紧急事件
void printUrgentEvents(Event events[], int n) {
for (int i = 0; i < n; i++) {
if (events[i].isUrgent) {
cout << events[i].name << "(时间:" << events[i].time << ")" << endl;
}
}
}
七、避坑指南
避坑指南
结构体定义后的分号:
结构体定义末尾必须加;
,否则会编译错误:c++struct Point { int x, y; } // 错误:缺少分号 struct Point { int x, y; }; // 正确
未初始化的成员变量:
结构体变量的成员若未初始化,值为随机垃圾值(尤其全局结构体数组会默认初始化):c++Student s; // cout << s.score; // 错误:未初始化,值不确定
结构体作为参数的效率:
传值方式会复制整个结构体(大结构体效率低),建议用传引用(const
引用用于只读):c++// 高效且安全的只读访问 void printStudent(const Student& s) { // const确保不修改 cout << s.name << endl; }
结构体数组的初始化:
部分初始化时,未指定的成员会默认初始化(数值为0,字符串为空):c++Student class2[2] = {{"Alice", 15}}; // 第二个学生:name空,age=0,score=0.0
八、实战:结构体数组排序与统计
结构体数组排序与统计
题目:定义学生结构体,输入5名学生的姓名和成绩,按成绩降序排序后输出,并统计及格人数。
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
struct Student {
string name;
double score;
};
// 比较函数:按成绩降序
bool compare(const Student& a, const Student& b) {
return a.score > b.score;
}
int main() {
const int N = 5;
Student students[N];
// 输入数据
cout << "输入" << N << "名学生的姓名和成绩:" << endl;
for (int i = 0; i < N; i++) {
cin >> students[i].name >> students[i].score;
}
// 排序
sort(students, students + N, compare);
// 输出排序结果
cout << "按成绩降序排列:" << endl;
for (int i = 0; i < N; i++) {
cout << students[i].name << " " << students[i].score << endl;
}
// 统计及格人数
int passCount = 0;
for (int i = 0; i < N; i++) {
if (students[i].score >= 60) {
passCount++;
}
}
cout << "及格人数:" << passCount << endl;
return 0;
}
重要
结构体是信奥赛中组织复杂数据的核心工具,通过将多个相关属性封装为一个整体,能显著提高代码的可读性和逻辑性。掌握结构体的定义、初始化、成员访问,以及结构体数组和函数的结合使用,是解决几何问题、数据统计、排序等场景的基础。在实际解题中,合理设计结构体成员和相关函数,能高效处理多维度数据。
联合体
联合体(union
)
在C++中,联合体(union
) 是一种特殊的数据类型,它允许在同一块内存空间中存储不同类型的数据,但同一时间只能有一个成员有效。与结构体(struct
)不同,联合体的所有成员共享同一段内存,这使得它在内存受限场景或需要类型转换时非常有用。
一、联合体的定义与基本特性
联合体的定义与基本特性
1. 定义语法
union 联合体名 {
数据类型 成员1;
数据类型 成员2;
// ... 更多成员
};
2. 核心特性
- 内存共享:所有成员共用同一块内存空间,联合体的大小等于其最大成员的大小。
- 互斥访问:同一时间只能使用一个成员,修改一个成员会覆盖其他成员的值。
示例:
#include <iostream>
using namespace std;
// 定义一个联合体,存储int、float或char
union Data {
int i;
float f;
char c;
};
int main() {
Data data;
cout << "联合体大小:" << sizeof(data) << endl; // 输出4(float和int通常占4字节)
// 使用int成员
data.i = 65;
cout << "int值:" << data.i << endl; // 输出65
cout << "char值(ASCII码65对应'A'):" << data.c << endl; // 输出'A'(共享内存的低字节)
// 修改float成员(会覆盖int的值)
data.f = 3.14f;
cout << "float值:" << data.f << endl; // 输出3.14
cout << "int值(已被覆盖):" << data.i << endl; // 输出随机整数(内存被float占用)
return 0;
}
二、联合体与结构体的区别
特性 | 联合体(union ) | 结构体(struct ) |
---|---|---|
内存分配 | 所有成员共享同一块内存,大小为最大成员的大小 | 成员按顺序分配内存,总大小为各成员大小之和(可能有对齐) |
成员访问 | 同一时间只能有效访问一个成员,修改会覆盖其他成员 | 所有成员可同时访问,修改一个不影响其他成员 |
用途 | 节省内存、类型转换、存储互斥数据 | 组合不同类型数据,表示一个完整对象(如学生、点) |
三、联合体的应用
联合体的应用
1. 节省内存(存储互斥数据)
当数据项互斥(即不会同时使用)时,用联合体可大幅减少内存占用:
// 表示一个"值",可能是整数或字符串(但不会同时是两者)
union Value {
int num;
char str[20]; // 假设字符串最长19字符(+结束符)
};
// 若用结构体,大小为4+20=24字节;用联合体,大小为20字节(取最大成员)
2. 类型转换(访问内存的不同方式)
通过联合体可查看同一内存区域的不同类型表示(如将整数的字节拆分):
union IntBytes {
int i;
char bytes[4]; // 存储int的4个字节(适合小端/大端判断)
};
void printBytes(int x) {
IntBytes b;
b.i = x;
cout << "整数" << x << "的字节表示:";
for (int j = 0; j < 4; j++) {
cout << (int)(unsigned char)b.bytes[j] << " ";
}
cout << endl;
}
// 调用:printBytes(0x12345678);
// 在小端系统输出:120 86 52 18(对应0x78 0x56 0x34 0x12)
3. 与结构体结合(标记联合体)
通过结构体标记联合体当前使用的成员,避免访问错误:
// 标记联合体:用type表示当前有效成员
struct TaggedData {
enum Type { INT, FLOAT, CHAR } type; // 枚举类型,标记当前成员
union {
int i;
float f;
char c;
} data; // 联合体成员
};
// 使用:
TaggedData d;
d.type = TaggedData::INT; // 标记为int类型
d.data.i = 100;
// 访问时先检查类型
if (d.type == TaggedData::INT) {
cout << d.data.i << endl; // 安全访问
}
四、信奥赛中的应用
信奥赛中的应用
联合体在信奥赛中虽不如结构体常用,但在特定场景下可简化代码:
- 内存敏感的嵌入式场景:如限制内存使用的题目,存储互斥数据时减少内存占用。
- 位运算与内存解析:如解析二进制文件格式(将字节流转换为不同类型数据)。
- 类型转换技巧:如快速获取浮点数的二进制表示(用于特定算法优化)。
五、避坑指南
避坑指南
成员互斥性:
绝对不能同时访问联合体的多个成员,修改一个成员后,其他成员的值会变得不确定(可能是垃圾值):c++union U { int a; float b; }; U u; u.a = 10; u.b = 3.14; // cout << u.a; // 错误:a已被b覆盖,值无意义
字符串等复杂类型:
联合体中避免使用string
等非POD(Plain Old Data)类型(C++标准不允许,部分编译器可能支持但不安全),应使用字符数组:c++union Bad { string s; // 错误:string包含构造/析构函数,不能放在联合体中 int i; }; union Good { char s[20]; // 正确:字符数组是POD类型 int i; };
初始化与赋值:
联合体只能初始化第一个成员,或通过显式指定初始化:c++union U { int a; float b; }; U u1 = {10}; // 正确:初始化第一个成员a U u2 = {.b = 3.14f}; // 正确:C++11后支持指定成员初始化
六、实战:判断系统字节序
判断系统字节序
字节序是数据在内存中的存储顺序(小端:低字节存低地址;大端:高字节存低地址),可用联合体判断:
#include <iostream>
using namespace std;
union EndianTest {
int i;
char c;
};
int main() {
EndianTest et;
et.i = 1; // 二进制:00000001 00000000 00000000 00000000(假设32位int)
// 若c为1,说明低地址存储低字节(小端);否则为大端
if (et.c == 1) {
cout << "当前系统是小端字节序" << endl;
} else {
cout << "当前系统是大端字节序" << endl;
}
return 0;
}
说明:多数PC(如x86架构)是小端字节序,部分嵌入式设备是大端字节序。
重要
联合体是C++中一种特殊的数据共享机制,核心价值在于内存复用。虽然在信奥赛中应用场景不如结构体广泛,但理解其原理和用法,能在处理互斥数据、类型转换等场景中写出更高效的代码。使用时需严格遵守“同一时间只访问一个成员”的规则,避免逻辑错误。