05 结构体与指针 / 引用

GESP 4 级CSP-J / NOIP 进阶

级别标注:结构体与指针 GESP 4 级(考级要求)/ 引用等竞赛用法 CSP-J、NOIP(竞赛进阶)。

一、结构体(struct):自定义数据类型

把多个相关的数据(和函数)打包成一个整体,便于表示"一个物品/一个人"等复合对象。

C++ 实例
#include <iostream>
#include <string>
using namespace std;

struct Student {          // 结构体:把多个相关数据打包成一个新类型
    string name;          // 成员:姓名
    int id;               // 成员:学号
    double score;         // 成员:分数
    // 结构体内可以定义成员函数
    void print() {        // 打印该学生的全部信息
        cout << name << " " << id << " " << score << endl;
    }
};

int main() {
    Student a;            // 声明一个 Student 变量
    a.name = "Alice";     // 用 . 访问成员并赋值
    a.id = 1001;
    a.score = 95.5;
    a.print();            // 调用成员函数

    // 初始化列表(C++11/17 支持):按成员声明顺序给初值
    Student b{"Bob", 1002, 88.0};
    b.print();
    return 0;
}

结构体数组与排序

C++ 实例
#include <iostream>
#include <algorithm>
#include <string>
using namespace std;

struct Stu {
    string name;          // 姓名
    int score;            // 分数
};

// 自定义比较规则(sort 的第三个参数)
bool cmp(const Stu &x, const Stu &y) {
    if (x.score != y.score) return x.score > y.score;  // 分数从高到低
    return x.name < y.name;                            // 同分按姓名升序
}

int main() {
    int n; cin >> n;
    Stu s[105];                                // 结构体数组:一组学生
    for (int i = 0; i < n; i++) cin >> s[i].name >> s[i].score;
    sort(s, s + n, cmp);                       // 按 cmp 规则排序
    for (int i = 0; i < n; i++) cout << s[i].name << " " << s[i].score << endl;
    return 0;
}
形象理解结构体像把零散物品装进同一个文件袋:学生的姓名、学号、成绩原本是三个互不相干的变量,用 struct Student 打包后就是一个整体,可以整体赋值、整体放进数组、整体排序。
考级视角GESP 4 级常考结构体数组排序:必须自己写 cmp 比较函数传给 sort。规则:先比主关键字,相等再比次关键字;升降序通过 > / < 控制,别写反。

二、指针基础

指针 = 保存"变量内存地址"的变量。用 & 取地址,用 * 解引用。

C++ 实例
#include <iostream>
using namespace std;
int main() {
    int a = 10;
    int *p = &a;       // p 存 a 的地址(& 取地址)
    cout << "a = " << a << endl;
    cout << "a 的地址 = " << p << endl;   // 地址值(十六进制)
    *p = 20;           // * 解引用:通过地址直接修改 a
    cout << "a = " << a << endl;   // 20
    return 0;
}

指针核心概念:

写法 含义
int *p 声明一个指向 int 的指针
&a 取变量 a 的地址
*p 解引用,访问 p 指向的值
nullptr 空指针(C++17 推荐,取代旧 NULL)

指针与数组的关系:arr 就是首元素地址

C++ 实例
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr;              // 数组名即首地址:等价 int *p = &arr[0];
cout << *p << endl;        // 10(当前指向 arr[0])
p++;                       // 指针后移一个 int(不是 1 字节)
cout << *p << endl;        // 20(arr[1])
cout << *(p + 2) << endl;  // 40(arr[3])
// 重要等价:数组下标本质是指针运算,arr[i] == *(arr+i)
形象理解指针像快递单上的地址:变量是房子,&a 是房子的地址,指针 p 是一张写着地址的单子。拿着单子(*p)就能找到房子并进去改东西;单子本身是空的(nullptr)时千万别去敲门,会崩溃。
易错点数组名 arr 本身就是首元素地址,arr[i] 等价于 *(arr + i)——初赛笔试爱考这个等价关系。但注意:sizeof(arr) 在数组作用域内返回整个数组字节数,不是指针大小,两个概念别混。

三、动态内存分配(new / delete)

C++ 实例
int *p = new int;         // new 申请一块 int 堆内存,返回地址
*p = 42;                  // 往堆空间写值
cout << *p << endl;
delete p;                 // 释放单块内存(防内存泄漏)

int *q = new int[100];    // 申请数组(100 个 int 的连续空间)
q[0] = 7;                 // 像普通数组一样用下标访问
cout << q[0] << endl;
delete[] q;               // 释放数组必须用 delete[](配对使用)

竞赛中数组大小编译期可知时,直接开全局数组即可,不必用 new;大数据量的动态需求可借助 STL(第 7 章)。

形象理解new 像临时租仓库:运行时按需申请空间(new int[100] 租一排),用完要 delete 退租。竞赛里数组大小编译期可知时直接开全局数组即可,不用折腾“租仓库”。
易错点new[] 必须配 delete[],new 配 delete,配对错会未定义行为。忘 delete 造成内存泄漏,在长期运行的评测服务上可能致命,竞赛题中一般可忽略。

四、引用(Reference)

引用 = 变量的"别名",必须初始化,一旦绑定不可改绑。 常用于函数参数,避免拷贝、实现"改原值"。

C++ 实例
#include <iostream>
using namespace std;

void addOne(int &x) { x += 1; }      // 引用传参:改的是原变量本身

struct Big { int a[1000]; };         // 演示大对象:拷贝代价高
void readOnly(const Big &b) {        // const &:只读引用,避免拷贝
    cout << b.a[0] << endl;
}

int main() {
    int n = 5;
    int &r = n;          // r 是 n 的别名(同一块内存)
    r = 100;             // 通过别名修改 n
    cout << n << endl;   // 100
    addOne(n);           // 引用参数使 n 自增
    cout << n << endl;   // 101
    return 0;
}

指针 vs 引用对比:

对比项 指针 引用
是否可为空 可(nullptr) 必须初始化绑定对象
能否改绑 可 不可
语法 *p 解引用 直接用别名
传递 传地址(拷贝指针) 传别名(不拷贝)
形象理解引用像给同一个人起的绰号:int &r = n; 之后 r 和 n 就是同一个变量的两个名字,喊哪个都是同一个人。绰号必须“出生就绑定”(初始化),不能中途换人。
考级视角竞赛高性能编码标配 const T &:既能避免拷贝大对象,又能防止函数内误改。读程序题常问“const 引用能改吗”——不能,编译器会报错。

五、结构体 + 指针:动态链表雏形(第 9 章详述)

C++ 实例
struct Node {             // 链表结点:数据 + 指向下一个结点的指针
    int data;             // 数据域
    Node *next;           // 指针域(指向下一个 Node)
};
// 链表的插入/删除在第 9 章展开
形象理解链表节点像寻宝游戏的线索卡:每张卡(Node)写着宝藏数据(data),还写着下一张卡的位置(next 指针)。顺着 next 一张张找下去,就遍历完整个链表。
考级视角GESP 4-5 级笔试常考链表概念题:插入/删除只需改指针、不需要移动元素,这是链表相对数组的最大优势;但按下标随机访问慢(要顺着找)。

六、易错点 / 常见坑点

  • ⚠️ 指针未初始化就使用 → 野指针崩溃;一律初始化为 nullptr。
  • ⚠️ new 了没 delete(内存泄漏,竞赛一般可忽略,长期服务要小心);new[] 必须 delete[] 配对。
  • ⚠️ 解引用空指针(nullptr)会段错误崩溃。
  • ⚠️ 函数返回局部变量地址是未定义行为,别这么干。
  • ⚠️ 结构体数组 sort 必须提供比较函数 cmp,不然默认无法比较(struct 无内置 <)。
  • ⚠️ 引用必须初始化:int &r; 是编译错误。
  • ⚠️ arr 与 *arr:二维数组的指针层级容易绕晕,初学可先用下标书写。

七、考级要求 vs 竞赛进阶

  • 考级要求(GESP 4 级):struct 定义、成员访问、结构体数组排序、指针的概念与基本应用(值传递/引用传递/指针传递)是典型考点。
  • 竞赛进阶(CSP-J/NOIP):引用参数(const T &)是高性能编码标配;指针/链表在 STL 容器内部与部分题目(链表模拟)中会用到;* 与 & 的含义辨析是初赛笔试高频选择题。

八、课内练习建议

  1. 用结构体存三名学生的姓名与三门成绩,按平均分排序输出。
  2. 用两个指针依次输出数组前半部分和后半部分的"对折和"。
  3. 写一个函数 swap(int &a, int &b) 实现交换,并比较值传递的差异。
  4. 用 new/delete 动态申请一个结构体数组并读取数据。
本章目录
一、结构体(struct):自定义数据类型 二、指针基础 三、动态内存分配(new / delete) 四、引用(Reference) 五、结构体 + 指针:动态链表雏形(第 9 章详述) 六、易错点 / 常见坑点 七、考级要求 vs 竞赛进阶 八、课内练习建议