8 结构体与排序(一)
正在加载视频播放器…
GESP C++ 四级:结构体与排序精讲
核心目标:掌握结构体的定义、初始化、成员访问、内存布局、嵌套与指针操作;掌握结构体作为函数参数的传递方式;系统掌握冒泡、选择、插入三种基础排序对结构体的排序方法,以及使用
sort配合cmp实现多关键字排序。
结构体把不同类型的数据“打包”成一个整体;排序结构体时,比较规则由cmp函数或运算符重载决定,核心是多关键字排序——先比第一关键字,相同再比第二关键字,以此类推。
一、考纲定位
根据CCF官方考纲,四级知识内容明确包含:
- 结构体的定义与使用:定义结构体、结构体数组/指针、嵌套结构体
- 结构体作为函数参数:值传递 vs 引用传递
- 排序概念和稳定性
- 经典排序:冒泡排序、插入排序、选择排序等
sort函数的使用- 简单算法复杂度的估算
考核目标要求:“掌握C++指针类型、二维及多维数组、结构体的基本使用”,“掌握排序算法的基本原理和实现,能对结构体等复合数据类型进行排序”。
四级明确不考:结构体模板、类与对象、动态内存分配。
二、结构体基础
2.1 为什么需要结构体
当需要描述一个“复杂对象”时,单一变量不够用。例如一个学生有姓名、学号、身高、体重、成绩等多个属性,这些属性类型不同但属于同一个对象。结构体就是把这些不同类型的数据打包成一个整体的工具。
2.2 结构体的定义
struct 结构体名 {
类型1 成员名1;
类型2 成员名2;
// ...
}; // 注意:末尾分号不能少!
标准示例:
struct Student {
string name; // 姓名
int id; // 学号
double score; // 成绩
};
关键规则:
struct关键字在前,结构体名在后。- 成员变量声明要符合C++语法,如
char name[20];而不是char[20] name;。 - 末尾的分号
;绝对不能省略。 - 结构体定义通常放在所有函数之前、
using namespace std;之后。
真题示例(2024.09 GESP四级) :
在 C++ 中,( )正确定义一个名为 student 的结构体,其中包含一个 name 字符数组和一个 age 整数?
A.struct student { char name[20]; int age; };
B.struct student { char name[20]; int age; }
C.struct student { string name; int age; };
D.struct student { char[20] name; int age; };
答案:A
解析:B 缺少末尾分号;C 虽然语法正确但题目明确要求字符数组;D 的数组声明语法错误。
2.3 结构体变量的定义与初始化
// 方式一:定义结构体时同时定义变量
struct Point {
int x, y;
} p1, p2;
// 方式二:先定义结构体,再定义变量(推荐)
struct Point {
int x, y;
};
Point p1, p2;
// 方式三:聚合初始化(C++11 推荐)
Point p = {1, 2};
// 方式四:逐个赋值
Point p;
p.x = 1;
p.y = 2;
真题示例(2026.03 GESP四级) :
下列关于结构体初始化的写法,正确的是?
A.Point p = (1,2);
B.Point p = {1,2};
C.Point p = new Point(1,2);
D.Point p = <1,2>;
答案:B
解析:C++ 中结构体使用花括号聚合初始化。
2.4 成员访问
struct Student {
string name;
int age;
double score;
};
Student s;
s.name = "张三"; // 使用 . 访问成员
s.age = 15;
s.score = 95.5;
cout << s.name << " " << s.age << " " << s.score << endl;
要点:普通结构体变量用 . 访问成员;结构体指针用 -> 访问成员。
2.5 结构体数组
结构体数组是最常用的形式,尤其配合排序时。
struct Student {
string name;
int score;
};
Student stu[100]; // 最多100个学生
// 读入
for (int i = 0; i < n; i++) {
cin >> stu[i].name >> stu[i].score;
}
// 输出
for (int i = 0; i < n; i++) {
cout << stu[i].name << " " << stu[i].score << endl;
}
2.6 结构体指针
Student s = {"张三", 15};
Student *p = &s;
cout << p->name << endl; // 使用 -> 访问成员
cout << (*p).age << endl; // 等价写法
要点:p->name 等价于 (*p).name,-> 更简洁,更常用。
2.7 结构体嵌套
结构体中的成员可以是另一个结构体。
struct Date {
int year, month, day;
};
struct Person {
string name;
Date birthday; // 嵌套结构体
};
Person p;
p.name = "张三";
p.birthday.year = 2010;
p.birthday.month = 5;
p.birthday.day = 15;
注意:嵌套的结构体必须先定义,否则会报错。
三、结构体的内存布局与地址特性(高频选择题考点)
3.1 首成员地址等于结构体变量地址
在内存中,结构体变量的起始地址就是其第一个成员的起始地址。
struct Pass {
int no;
char name[20];
int level;
};
Pass XiaoYang;
cout << &XiaoYang << endl; // 结构体变量地址
cout << &(XiaoYang.no) << endl; // 第一个成员的地址
// 两个地址完全相同!
真题示例(2024.03 GESP四级) :
上述代码执行后,
&XiaoYang和&(XiaoYang.no)的大小关系是?
A. X > Y B. X == Y C. X < Y D. 不确定
答案:B
3.2 内存对齐原则
虽然四级不常直接考查对齐计算,但了解对齐原则有助于理解结构体大小:
- 每个成员的偏移量必须是其类型大小的整数倍。
- 结构体总大小必须是最大成员大小的整数倍。
struct Example {
char a; // 1字节
int b; // 4字节,偏移量需为4的倍数
short c; // 2字节
};
// 在32位系统中,这个结构体大小是12字节,不是1+4+2=7字节
四、结构体作为函数参数
4.1 值传递 vs 引用传递
| 传递方式 | 语法 | 函数内修改是否影响原结构体 | 效率 |
|---|---|---|---|
| 值传递 | void f(Student s) |
❌ 不影响(拷贝整个结构体) | 低(拷贝开销大) |
| 引用传递 | void f(Student &s) |
✅ 影响 | 高 |
| const引用传递 | void f(const Student &s) |
❌ 不影响(只读) |
推荐:传递结构体时优先使用 const 引用,既能避免拷贝,又能防止修改。
// 值传递:拷贝整个结构体
void printStudent1(Student s) {
cout << s.name << " " << s.score << endl;
}
// const引用传递:不拷贝,不修改(推荐)
void printStudent2(const Student &s) {
cout << s.name << " " << s.score << endl;
}
真题示例(2024.03 GESP四级) :
以下函数调用中,
fun(&x)和fun(x)分别对应哪种参数传递方式?void fun1(Point p) { } // 值传递 void fun2(Point *p) { } // 指针传递 void fun3(Point &p) { } // 引用传递答案:
fun(&x)是指针传递,fun(x)可能是值传递或引用传递(取决于函数声明)。
4.2 结构体数组作为函数参数
void sortStudents(Student stu[], int n) {
// 数组退化为指针,函数内修改会影响原数组
}
五、结构体与三种基础排序
5.1 冒泡排序(结构体版)
以学生成绩排序为例,按成绩从高到低排列:
struct Student {
string name;
int score;
};
Student stu[100];
// 按成绩从高到低冒泡排序
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - 1 - i; j++) {
if (stu[j].score < stu[j + 1].score) { // 降序
swap(stu[j], stu[j + 1]); // 直接交换整个结构体
}
}
}
要点:swap(stu[j], stu[j+1]) 可以直接交换两个结构体变量,C++ 会自动交换所有成员。
5.2 选择排序(结构体版)
for (int i = 0; i < n - 1; i++) {
int maxIdx = i;
for (int j = i + 1; j < n; j++) {
if (stu[j].score > stu[maxIdx].score) {
maxIdx = j;
}
}
if (maxIdx != i) {
swap(stu[i], stu[maxIdx]);
}
}
5.3 插入排序(结构体版)
for (int i = 1; i < n; i++) {
Student key = stu[i]; // 保存整个结构体
int j = i - 1;
while (j >= 0 && stu[j].score < key.score) {
stu[j + 1] = stu[j]; // 整体后移
j--;
}
stu[j + 1] = key;
}
要点:结构体可以直接赋值(=),会逐个成员拷贝。
六、多关键字排序:cmp 函数详解
这是四级结构体排序的核心考点,也是编程题中出现频率最高的题型。
6.1 什么是多关键字排序
现实中的排序往往有多个优先级。例如:
- 先按总分从高到低排。
- 总分相同,再按语文成绩从高到低排。
- 语文也相同,再按学号从小到大排。
这就是多关键字排序。
6.2 sort 函数的基本用法
#include <algorithm>
sort(起始地址, 结束地址, 比较函数);
- 对数组
arr的前 n 个元素排序:sort(arr, arr + n, cmp); - 对结构体数组
stu的前 n 个元素排序:sort(stu, stu + n, cmp); - 第三个参数
cmp可省略,默认升序。
6.3 cmp 函数的写法
核心规则:
cmp(a, b)返回true表示a应该排在b前面。- 返回
false表示a不应该排在b前面(即b排在a前面或两者相等)。 cmp里面千万不要把>和<写反!
单关键字排序:
// 按成绩从高到低
bool cmp(const Student &a, const Student &b) {
return a.score > b.score;
}
双关键字排序:
// 先按身高从高到低,身高相同按体重从重到轻
bool cmp(const Student &a, const Student &b) {
if (a.height != b.height) return a.height > b.height;
return a.weight > b.weight;
}
三关键字排序:
bool cmp(const Box &a, const Box &b) {
if (a.total != b.total) return a.total < b.total; // 总价升序
if (a.max_val != b.max_val) return a.max_val < b.max_val; // 最大值升序
if (a.min_val != b.min_val) return a.min_val < b.min_val; // 最小值升序
return a.id < b.id; // 编号升序
}
记忆口诀:
先比第一关键字,不同就返回;
相同再比第二关键字,不同就返回;
依次往下,最后按编号。
七、真题精讲
真题1:身高体重指数(2026年6月)
题目描述:每个小朋友有编号、身高、体重,按 BMI(体重/身高²)从高到低排序,输出编号。
解题思路:用结构体封装编号、体重、BMI,用 sort 配合 cmp 排序。
struct Student {
int id;
double bmi;
};
bool cmp(const Student &a, const Student &b) {
return a.bmi > b.bmi; // BMI 从高到低
}
int main() {
int n;
cin >> n;
Student s[1005];
for (int i = 1; i <= n; i++) {
double h, w;
cin >> h >> w;
s[i].id = i;
s[i].bmi = w / (h * h);
}
sort(s + 1, s + n + 1, cmp);
for (int i = 1; i <= n; i++) {
cout << s[i].id << " ";
}
return 0;
}
关键点:cmp 返回 a.bmi > b.bmi 表示 BMI 大的排在前面,实现降序。
真题2:排序(2025年6月)
题目描述:按照“身高更高者优先,身高相同则体重更重者优先”的规则,统计队伍中有多少对同学站错了位置。
解题思路:双重循环检查每一对同学,统计逆序对数量。
struct Stu {
int h, w;
};
Stu p[1005];
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) cin >> p[i].h >> p[i].w;
int cnt = 0;
for (int i = 1; i < n; i++) {
for (int j = i + 1; j <= n; j++) {
// 如果第 j 位同学应该排在第 i 位前面,说明站错了
if (p[j].h > p[i].h || (p[j].h == p[i].h && p[j].w > p[i].w)) {
cnt++;
}
}
}
cout << cnt << endl;
return 0;
}
核心考点:结构体存储 + 多条件判断,本质是统计逆序对数量。
真题3:礼盒排序(2026年3月)
题目描述:对礼盒进行排序,规则依次为:总价升序 → 最贵商品价格升序 → 最便宜商品价格升序 → 编号升序。
解题思路:结构体封装四大属性,cmp 函数逐级比较。
struct Box {
int id;
int total;
int max_val;
int min_val;
};
bool cmp(const Box &a, const Box &b) {
if (a.total != b.total) return a.total < b.total;
if (a.max_val != b.max_val) return a.max_val < b.max_val;
if (a.min_val != b.min_val) return a.min_val < b.min_val;
return a.id < b.id;
}
核心考点:标准的多关键字排序,cmp 函数逐级比较,最后用编号保证唯一性。
真题4:优先购买(2025年12月)
题目描述:按商品优先级排序,优先级规则为:价格低的优先 → 保质期长的优先 → 编号小的优先。
struct Goods {
int id, price, shelf_life;
};
bool cmp(const Goods &x, const Goods &y) {
if (x.price != y.price) return x.price < y.price; // 价格升序
if (x.shelf_life != y.shelf_life) return x.shelf_life > y.shelf_life; // 保质期降序
return x.id < y.id; // 编号升序
}
核心考点:混合升降序的多关键字排序,注意每个关键字的排序方向可能不同。
八、易错点汇总
8.1 结构体定义与初始化
- 末尾漏写分号:
struct Student { ... }后面必须加;。 - 数组成员声明语法错误:写
char name[20];而不是char[20] name;。 - 嵌套结构体未先定义:嵌套的结构体必须在外层结构体之前定义。
- 初始化方式错误:用
{1, 2}而不是(1, 2)。
8.2 内存与地址
- 首成员地址等于结构体地址:这是标准规定,不要误以为有偏移。
- 内存对齐导致大小不等于成员之和:了解即可,四级不常考计算。
8.3 函数传参
- 值传递拷贝开销大:传递结构体建议用
const 引用。 - const引用不可修改:
void f(const Student &s)中不能修改s的成员。
8.4 排序与cmp
cmp中比较方向写反:a.score > b.score是降序,a.score < b.score是升序。写反结果完全相反。- 多关键字排序遗漏相等情况:第一关键字相同时必须继续比较第二关键字,不能直接返回。
sort的区间是左闭右开:sort(stu, stu + n, cmp)排序的是stu[0]到stu[n-1]。- 结构体数组下标从0还是从1开始要统一:用
sort(stu + 1, stu + n + 1, cmp)时下标从1开始。
8.5 综合
- 结构体可以直接赋值和交换:
swap(stu[i], stu[j])会自动交换所有成员。 - 结构体数组作为函数参数退化为指针:函数内修改会影响原数组。
- 输出格式严格按题目要求:空格、换行、编号顺序都不能错。
九、阅读程序写结果
题目1
struct Point { int x, y; };
Point p = {3, 5};
cout << p.x + p.y << endl;
输出:8
题目2
struct Student { string name; int score; };
Student s[3] = {{"A", 90}, {"B", 85}, {"C", 95}};
bool cmp(const Student &a, const Student &b) {
return a.score > b.score;
}
sort(s, s + 3, cmp);
cout << s[0].name << endl;
输出:C(95分最高)
题目3
struct Stu { int h, w; };
Stu a = {180, 70}, b = {180, 75};
cout << (a.h == b.h) << " " << (a.w < b.w) << endl;
输出:1 1
题目4
struct Box { int total, maxv; };
Box a = {10, 5}, b = {10, 3};
bool cmp(const Box &x, const Box &y) {
if (x.total != y.total) return x.total < y.total;
return x.maxv < y.maxv;
}
cout << cmp(a, b) << endl;
输出:0(a.total == b.total,继续比较 maxv;a.maxv = 5 > b.maxv = 3,所以 a 不该排在 b 前面,返回 false,即 0)
十、速查表
| 需求 | 写法 | 关键注意 |
|---|---|---|
| 定义结构体 | struct 名 { 成员列表 }; |
末尾分号不能少 |
| 定义变量 | Student s; |
先定义结构体再定义变量 |
| 聚合初始化 | Point p = {1, 2}; |
花括号 |
| 访问成员 | s.name |
普通变量用 . |
| 指针访问 | p->name |
等价于 (*p).name |
| 结构体数组 | Student stu[100]; |
最常用形式 |
| 结构体嵌套 | Date birthday; |
嵌套结构体需先定义 |
| 值传递 | void f(Student s) |
拷贝,不修改原结构体 |
| 引用传递 | void f(Student &s) |
不拷贝,可修改 |
| const引用 | void f(const Student &s) |
不拷贝,不可修改(推荐) |
| 冒泡排序结构体 | if (stu[j].score < stu[j+1].score) swap(...) |
直接交换结构体 |
| 选择排序结构体 | 记录 maxIdx,最后 swap |
每轮最多交换一次 |
| 插入排序结构体 | Student key = stu[i]; |
结构体可直接赋值 |
sort + cmp |
sort(stu, stu+n, cmp); |
需 <algorithm> |
| 单关键字降序 | return a.score > b.score; |
> 降序,< 升序 |
| 多关键字排序 | if (a.x != b.x) return ...; return a.y < b.y; |
逐级比较 |
| 首成员地址 | &s == &s.first_member |
地址相同 |
十一、总结
结构体与排序是GESP四级编程题的核心组合。
- 结构体把不同类型的数据打包成一个整体,末尾分号不能少。
- 首成员地址等于结构体变量地址,这是标准规定。
- 传递结构体优先用
const 引用,避免拷贝又防止修改。 - 结构体数组可以直接用
swap交换,也可以用sort排序。 cmp(a, b)返回true表示a排在b前面,方向不能写反。- 多关键字排序的核心是“逐级比较”:第一关键字不同就返回,相同再比第二关键字。
>是降序,<是升序,多关键字中每个关键字的升降序可以不同。sort的区间是左闭右开,sort(stu, stu+n)排序下标 0 到 n-1。- 冒泡、选择、插入排序都可以直接用于结构体数组,比较规则写在
if条件里。 - 多练“身高体重指数”“礼盒排序”“优先购买”三道真题,熟练掌握多关键字
cmp函数的写法。
复习建议:先把结构体的定义、初始化、成员访问、嵌套写熟;再掌握结构体数组和 const 引用 传参;然后重点突破多关键字排序的 cmp 函数写法,从单关键字到双关键字再到三关键字逐步练习。考试中先确定结构体需要封装哪些属性,再确定排序的优先级顺序,最后逐级写出 cmp 函数。