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 容器内部与部分题目(链表模拟)中会用到;*与&的含义辨析是初赛笔试高频选择题。
八、课内练习建议
- 用结构体存三名学生的姓名与三门成绩,按平均分排序输出。
- 用两个指针依次输出数组前半部分和后半部分的"对折和"。
- 写一个函数
swap(int &a, int &b)实现交换,并比较值传递的差异。 - 用
new/delete动态申请一个结构体数组并读取数据。