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 结构体定义与初始化

  1. 末尾漏写分号:struct Student { ... } 后面必须加 ;。
  2. 数组成员声明语法错误:写 char name[20]; 而不是 char[20] name;。
  3. 嵌套结构体未先定义:嵌套的结构体必须在外层结构体之前定义。
  4. 初始化方式错误:用 {1, 2} 而不是 (1, 2)。

8.2 内存与地址

  1. 首成员地址等于结构体地址:这是标准规定,不要误以为有偏移。
  2. 内存对齐导致大小不等于成员之和:了解即可,四级不常考计算。

8.3 函数传参

  1. 值传递拷贝开销大:传递结构体建议用 const 引用。
  2. const引用不可修改:void f(const Student &s) 中不能修改 s 的成员。

8.4 排序与cmp

  1. cmp 中比较方向写反:a.score > b.score 是降序,a.score < b.score 是升序。写反结果完全相反。
  2. 多关键字排序遗漏相等情况:第一关键字相同时必须继续比较第二关键字,不能直接返回。
  3. sort 的区间是左闭右开:sort(stu, stu + n, cmp) 排序的是 stu[0] 到 stu[n-1]。
  4. 结构体数组下标从0还是从1开始要统一:用 sort(stu + 1, stu + n + 1, cmp) 时下标从1开始。

8.5 综合

  1. 结构体可以直接赋值和交换:swap(stu[i], stu[j]) 会自动交换所有成员。
  2. 结构体数组作为函数参数退化为指针:函数内修改会影响原数组。
  3. 输出格式严格按题目要求:空格、换行、编号顺序都不能错。

九、阅读程序写结果

题目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四级编程题的核心组合。

  1. 结构体把不同类型的数据打包成一个整体,末尾分号不能少。
  2. 首成员地址等于结构体变量地址,这是标准规定。
  3. 传递结构体优先用 const 引用,避免拷贝又防止修改。
  4. 结构体数组可以直接用 swap 交换,也可以用 sort 排序。
  5. cmp(a, b) 返回 true 表示 a 排在 b 前面,方向不能写反。
  6. 多关键字排序的核心是“逐级比较”:第一关键字不同就返回,相同再比第二关键字。
  7. > 是降序,< 是升序,多关键字中每个关键字的升降序可以不同。
  8. sort 的区间是左闭右开,sort(stu, stu+n) 排序下标 0 到 n-1。
  9. 冒泡、选择、插入排序都可以直接用于结构体数组,比较规则写在 if 条件里。
  10. 多练“身高体重指数”“礼盒排序”“优先购买”三道真题,熟练掌握多关键字 cmp 函数的写法。

复习建议:先把结构体的定义、初始化、成员访问、嵌套写熟;再掌握结构体数组和 const 引用 传参;然后重点突破多关键字排序的 cmp 函数写法,从单关键字到双关键字再到三关键字逐步练习。考试中先确定结构体需要封装哪些属性,再确定排序的优先级顺序,最后逐级写出 cmp 函数。