


一、第1题:指针修改变量
题目:
int count = 8;
int *p = &count;
*p += 4;
cout << count << " " << *p;
选项:
A. 12 14
B. 14 14
C. 12 12
D. 12 8
答案:C
1. 先认识 p 是什么
这一句:
int *p = &count;
可以拆成两部分:
&count
表示:
取 count 的地址
而:
int *p
表示:
p是一个"指向 int 的指针"。
所以:
p = &count;
可以理解成:
p 手里拿着 count 的房间地址。
可以画成:
count
┌───────┐
│ 8 │
└───────┘
↑
│
│ p保存的地址
│
p
2. *p 又是什么意思?
这里特别容易混淆。
p
表示:
地址
*p
表示:
这个地址对应的变量
因为:
p = &count;
所以:
*p
就是:
count
因此:
*p += 4;
其实就相当于:
count += 4;
所以:
count:8 → 12
3. 最后输出
cout << count << " " << *p;
此时:
count = 12
*p = 12
所以输出:
12 12
答案:
C
⭐ 小学生记忆口诀
*p 看地址,p 找房子。
也就是:
p → 地址
*p → 地址里面的东西
&p → p自己的地址
二、第2题:const int *p 到底能不能修改?
题目:
int a = 10;
int b = 20;
const int *p = &a;
题目问哪个说法正确。
选项:
A. 可以通过 p 修改 a 的值
B. 可以令 p = &b
C. p 的指向和所指向的值都不能修改
D. p 必须始终指向 a
答案:B
1. 先看这个奇怪的 const
const int *p = &a;
这里的 const 修饰的是:
int
也就是说:
不能通过 p 修改它所指向的 int。
因此:
*p = 20;
是不允许的。
因为 *p 被看成一个:
const int
2. 但是 p 自己能不能改变?
可以!
例如:
p = &b;
完全可以。
原来:
p
↓
a
10
执行:
p = &b;
之后:
p
↓
b
20
所以:
p 可以换目标,但是不能通过 p 修改目标。
因此 B 正确。
3. 三种非常容易混淆的写法
这道题非常值得掌握。
第一种
const int *p;
或者:
int const *p;
意思:
指向的值不能通过 p 修改,p 自己可以改变指向。
例如:
*p = 100; // ❌
p = &b; // ✅
第二种
int * const p = &a;
意思:
p 是一个不能移动的指针。
但是:
p 指向的值可以修改。
所以:
*p = 100; // ✅
p = &b; // ❌
第三种
const int * const p = &a;
两个都不能:
*p = 100; // ❌
p = &b; // ❌
⭐ 超级口诀
看到 const,先问:
const 管的是谁?
const int *p
↑
管数据
int * const p
↑
管指针
const int * const p
↑ ↑
管数据 管指针
这也是 C++ 指针题的经典考点。
三、第3题:二维数组 + 数组指针
题目:
int goods[3][4] = {
{2, 4, 6, 8},
{10, 12, 14, 16},
{18, 20, 22, 24}
};
int (*p)[4] = goods;
int x = *(*(p + 1) + 2);
选项:
A. 12
B. 14
C. 20
D. 22
答案:B
这一题是前面 7 道里面最需要理解的一道。
1. 先看二维数组
把它画出来:
第0列 第1列 第2列 第3列
第0行 2 4 6 8
第1行 10 12 14 16
第2行 18 20 22 24
2. p 是什么?
int (*p)[4] = goods;
注意括号:
int (*p)[4]
表示:
p 是一个指针,指向"包含4个int的数组"。
也就是:
p
↓
第0行
[2][4][6][8]
因此:
p + 1
不是往后走一个 int。
而是:
跳到下一整行。
所以:
p
↓
第0行
p + 1
↓
第1行
3. *(p + 1)
p + 1:
指向第1行
再 *:
*(p + 1)
得到:
第1行这个数组
也就是:
10 12 14 16
4. 再看 *(p + 1) + 2
第1行:
10 12 14 16
↑
+2:
10 12 14 16
↑
指向:
14
最后:
*(*(p + 1) + 2)
把它解引用:
14
所以:
x = 14;
答案:
B
⭐ 二维数组指针万能拆法
遇到:
*(*(p + i) + j)
直接翻译:
第 i 行,第 j 列。
所以:
*(*(p + 1) + 2)
就是:
goods[1][2]
答案自然是:
14
四、第4题:二维数组作为函数参数
题目:
void addOne(________, int r) {
for (int j = 0; j < 5; j++) {
arr[r][j]++;
}
}
题目要求:
一个 3行5列 的二维数组,第
r行所有元素加 1。
选项:
A.
int arr[][]
B.
int **arr
C.
int arr[][5]
D.
int arr[5][]
答案:
C
1. 为什么必须写 [5]?
二维数组:
int arr[3][5];
可以理解成:
有 3 行,每行有 5 个整数。
也就是:
第0行:5个
第1行:5个
第2行:5个
计算机在内存中是连续存储的。
因此,当你想通过:
arr[r][j]
找到元素时,计算机必须知道:
一行到底有多少个元素?
这里就是:
5个
所以函数参数至少要告诉计算机:
[5]
因此:
int arr[][5]
正确。
2. 为什么第一维可以省略?
函数:
void addOne(int arr[][5], int r)
第一维:
[]
可以不写。
因为函数真正需要知道的是:
每一行有多少列。
也就是:
[5]
3. 为什么 int **arr 不对?
这是一个非常重要的坑。
很多初学者会认为:
int arr[3][5]
就是:
int **arr
其实不是。
二维数组:
int arr[3][5];
和:
int **p;
在类型上完全不同。
简单理解:
二维数组:
┌───────────────┐
│ int int int...│
├───────────────┤
│ int int int...│
├───────────────┤
│ int int int...│
└───────────────┘
int**:
指针 → 指针 → int
所以不能把它们混为一谈。
五、第5题:全局变量、局部变量与引用
题目:
int score = 60;
void update(int &score) {
score += 5;
}
int main() {
int score = 80;
update(score);
cout << score << " " << ::score;
return 0;
}
选项:
A. 85 60
B. 80 65
C. 85 65
D. 80 60
答案:
A
1. 这里有两个 score
最开始:
int score = 60;
这是:
全局变量
可以理解为:
整个程序都能找到的 score
score = 60
然后:
int main() {
int score = 80;
这里又定义了一个:
局部变量 score
所以现在有:
全局 score = 60
main里面的 score = 80
2. update(score) 修改谁?
update(score);
这里传进去的是:
main
里的:
score = 80
而函数参数:
void update(int &score)
这里的 & 是:
引用
所以函数里的 score 和 main 里的 score 是同一个变量。
执行:
score += 5;
于是:
80 → 85
3. 最后的 ::score 是什么?
这里:
cout << score << " " << ::score;
第一个:
score
因为在 main 中,所以找到:
局部 score = 85
而:
::score
前面的:
::
叫:
作用域解析运算符
这里表示:
去全局作用域寻找 score。
所以:
score = 85
::score = 60
输出:
85 60
答案:
A
⭐ 这一题一定记住
score
找当前作用域。
::score
找全局作用域。
可以把 :: 想象成:
"去最外面的房间找!"
六、第6题:结构体传值 vs 引用
题目:
struct Device {
int id;
int state;
};
void reset(Device d) {
d.state = 0;
}
void start(Device &d) {
d.state += 1;
}
int main() {
Device d{7, 2};
reset(d);
start(d);
cout << d.id << " " << d.state;
return 0;
}
选项:
A. 7 0
B. 7 1
C. 7 2
D. 7 3
答案:
D
1. 一开始
Device d{7, 2};
所以:
d.id = 7
d.state = 2
2. 调用 reset(d)
函数:
void reset(Device d)
注意:
这里没有
&!
所以是:
值传递
也就是说,函数得到的是一个副本。
可以想象成:
原来的 d
id = 7
state = 2
↓ 复制
reset里面的 d
id = 7
state = 2
然后:
d.state = 0;
修改的是:
reset里面的副本
原来的:
state = 2
完全没有改变。
3. 接下来 start(d)
函数:
void start(Device &d)
这里有:
&
所以是:
引用传递
函数里面的 d 就是 main 里面的那个 d。
执行:
d.state += 1;
原来的:
state = 2
变成:
state = 3
4. 最后
cout << d.id << " " << d.state;
得到:
7 3
答案:
D
⭐ 第6题是四级非常重要的考点
一定要分清:
值传递
void f(Device d)
相当于:
复印一份再拿进去改。
函数里面改了:
原来的不变。
引用传递
void f(Device &d)
相当于:
直接拿原件进去改。
函数里面改了:
原来的也跟着变。
可以用这个表记忆:
| 写法 | 实际操作 | 能否修改原变量 |
|---|---|---|
void f(int x) |
复制一份 | ❌ |
void f(int &x) |
使用原变量 | ✅ |
void f(int *x) |
通过地址访问 | ✅ |
七、第7题:结构体数组 + 指针
题目:
struct Book {
string name;
int pages;
};
int main() {
Book books[2] = {
{"C++", 120},
{"Math", 150}
};
Book *p = books + 1;
p->pages += 10;
cout << books[1].name << " " << books[1].pages;
return 0;
}
答案:
C
即:
Math 160
1. 先把结构体数组画出来
Book books[2]
里面有两个 Book:
books[0]
┌──────────────┐
│ C++ │
│ 120 │
└──────────────┘
books[1]
┌──────────────┐
│ Math │
│ 150 │
└──────────────┘
2. p = books + 1
这里:
Book *p = books + 1;
数组名:
books
通常可以看成:
&books[0]
所以:
books + 1
就是:
&books[1]
因此:
p
↓
books[1]
3. p->pages 是什么意思?
这是结构体指针非常重要的语法。
p->pages
等价于:
(*p).pages
也就是:
找到 p 指向的结构体,再访问它的
pages。
现在:
p → books[1]
所以:
p->pages
就是:
books[1].pages
4. 执行加 10
原来:
books[1].pages = 150
执行:
p->pages += 10;
变成:
150 + 10 = 160
而:
books[1].name
仍然是:
Math
所以:
cout << books[1].name << " " << books[1].pages;
输出:
Math 160
答案:
C
⭐ . 和 -> 一定要分清
这是结构体指针题的核心。
如果手里是:
Book b;
直接使用:
b.pages
因为:
b是结构体对象。
如果手里是:
Book *p;
使用:
p->pages
因为:
p是结构体指针。
口诀
对象用点
.,指针用箭头->。
并且:
p->pages
等价于:
(*p).pages
注意这里的括号不能省:
*p.pages
含义就不一样了。
八、把1~7题串成一张"知识地图"
这 7 道题看起来分别在考不同东西,其实它们是一条非常清晰的知识链:
C++变量
│
┌─────────┴─────────┐
↓ ↓
地址 引用
│ │
↓ ↓
指针 int &x
│
┌─────┴─────┐
↓ ↓
普通变量 数组
│ │
↓ ↓
*p / &x 二维数组
│
↓
int (*p)[5]
│
↓
函数参数
│
↓
int arr[][5]
结构体
│
├──────────────┐
↓ ↓
对象 指针
│ │
↓ ↓
b.id p->id
九、1~7题考点总结
| 题号 | 核心考点 | 正确答案 | 一句话记忆 |
|---|---|---|---|
| 1 | 指针修改变量 | C | *p就是p指向的变量 |
| 2 | const指针 |
B | const int *p:不能改值,可以换指向 |
| 3 | 二维数组指针 | B | *(*(p+i)+j)就是第 i 行第 j 列 |
| 4 | 二维数组函数参数 | C | 二维数组参数必须知道"每行多少列" |
| 5 | 引用、全局/局部作用域 | A | ::x找全局变量 |
| 6 | 结构体值传递/引用传递 | D | 有 & 改原件,没有 & 改副本 |
| 7 | 结构体指针 | C | 对象用.,指针用-> |
十、给小学生的"考场版"口诀
如果考试的时候遇到这类题,不要马上算,先问自己几个问题:
① 看到 *p
问:
p指向谁?
例如:
int *p = &a;
那么:
*p == a
② 看到 const int *p
问:
const管的是谁?
答案:
管
*p,不能通过 p 修改数据。
③ 看到二维数组
遇到:
*(*(p+i)+j)
直接翻译:
第 i 行第 j 列。
④ 看到二维数组函数参数
例如:
void f(int a[][5])
记:
第一维可以省,第二维不能乱省。
⑤ 看到函数参数 &
例如:
void f(int &x)
记:
带
&,改的是原来的。
⑥ 看到 ::
记:
往外找,全局作用域。
⑦ 看到结构体
记住:
对象.成员
而:
指针->成员
也就是:
"人用点,地址用箭头。"
最后给大家一个特别重要的思维方法
要掌握下面这 7 个"翻译器":
&p → p的地址
*p → p指向的变量
const int *p
→ 不能通过p修改数据
*(*(p+i)+j)
→ 第i行第j列
int a[][5]
→ 每行5个元素
int &x
→ x是原变量的别名
::x
→ 全局作用域的x
p->x
→ (*p).x
如果这 7 个翻译器真正理解了,那么这类 GESP 四级选择题就不是"背答案",而是在做"C++代码翻译题"。







