一、说明
这篇笔记基于《21天学通 C++(第8版)》第八章“指针和引用”的核心内容整理而成,但题目、解析和拓展说明均为重新组织后的训练材料。
使用方式很简单:
- 先独立做题
- 每题后面立即对照答案
- 顺手看解析
- 再把“知识点拓展”当成复盘
整套题都做完,基本就能把这一章最关键的坑和概念吃透。
二、指针基础题(1-20)
题 1
题目: 指针变量中直接保存的内容是什么?
A. 变量的类型信息
B. 变量的值
C. 某个对象所在的内存地址
D. 编译器自动生成的标记
正确答案: C
解析: 指针本质上是一个保存地址的变量,它不是直接保存目标对象的值,而是保存“这个对象在哪里”。
知识点拓展: 学指针时一定先分清 ptr 和 *ptr。前者通常是地址,后者才是地址处的数据。
题 2
题目: 下面哪一项最能描述 &age 的含义?
A. age 的值加 1
B. age 的地址
C. age 的引用类型
D. age 的长度
正确答案: B
解析: & 在这里是取地址运算符,表示变量 age 存放在内存中的位置。
知识点拓展: 同一个符号 & 在不同上下文里角色不同。声明引用时它表示“引用”,表达式里它表示“取地址”。
题 3
题目: 设 int age = 30; int* ptr = &age;,则 *ptr 的值是:
A. age 的地址
B. 30
C. int*
D. 不确定
正确答案: B
解析: ptr 保存的是 age 的地址,*ptr 通过解引用访问这个地址中的整数值,因此结果是 30。
知识点拓展: 解引用的前提是指针有效。无效指针一旦解引用,程序可能崩溃。
题 4
题目: 下面哪种写法最适合作为现代 C++ 中空指针的初始化方式?
A. int* ptr = 0;
B. int* ptr = NULL;
C. int* ptr = nullptr;
D. int* ptr;
正确答案: C
解析: 在现代 C++ 中,nullptr 是专门为空指针引入的关键字,语义和类型都更清晰。
知识点拓展: 书里常见 NULL,但实际现代代码里优先用 nullptr。
题 5
题目: 下面哪项说法正确?
A. 指针不是变量
B. 指针不占内存
C. 指针也是变量,只是保存的是地址
D. 指针只能指向整数
正确答案: C
解析: 指针和普通变量一样需要声明、占内存、可赋值,只不过它存储的是地址。
知识点拓展: 正因为指针本身也是变量,所以它也有自己的地址。
题 6
题目: 若要声明一个指向 double 的指针,下面哪项正确?
A. double ptr*;
B. *double ptr;
C. double* ptr;
D. pointer double ptr;
正确答案: C
解析: double* ptr; 表示 ptr 是一个指向 double 的指针。
知识点拓展: 很多人会把 * 看成类型的一部分,实际上更安全的理解是:ptr 是指针。
题 7
题目: 对未初始化指针最大的风险是什么?
A. 会导致编译变慢
B. 会导致指针不能参与比较
C. 垃圾值会被当成地址使用
D. 会自动变成 nullptr
正确答案: C
解析: 未初始化指针包含随机值,而这些随机值一旦被解释为地址并解引用,就可能发生非法访问。
知识点拓展: 指针未初始化,比普通变量未初始化更危险,因为它可能让程序访问根本不该访问的内存。
题 8
题目: 设 int age = 30; int* ptr = &age;,下面哪一项会修改 age 的值?
A. ptr = nullptr;
B. *ptr = 40;
C. &ptr = &age;
D. ptr = &ptr;
正确答案: B
解析: *ptr = 40; 表示把 ptr 指向位置上的整数值改为 40,也就是改了 age。
知识点拓展: 指针赋值改的是“指向哪里”,解引用赋值改的是“地址里的内容”。
题 9
题目: 指针重新赋值后,改变的是:
A. 原变量的类型
B. 原变量所在内存的大小
C. 指针当前保存的地址
D. 编译器对变量的解释方式
正确答案: C
解析: 指针重新赋值意味着它改为保存另一个地址,从而改为指向另一个对象。
知识点拓展: 这也是指针和引用的重要区别之一:指针可改指向,引用通常不可改绑。
题 10
题目: void* 指针最典型的特点是:
A. 不能保存地址
B. 可以指向任意类型对象的地址
C. 只能指向 void 类型
D. 会自动解引用
正确答案: B
解析: void* 常被称为“无类型指针”,它可以保存任意对象的地址,但使用前通常需要转换。
知识点拓展: 在 C++ 里,void* 没有类型信息,因此不能像具体类型指针那样直接解引用。
题 11
题目: 在表达式环境里,*ptr 的含义是:
A. 取 ptr 的地址
B. 声明 ptr 是指针
C. 访问 ptr 指向位置上的对象
D. 释放 ptr
正确答案: C
解析: * 在表达式里是解引用运算符,用来访问指针指向的对象。
知识点拓展: 同样一个 *,在声明中和在表达式中的含义完全不同,这是初学者高频混淆点。
题 12
题目: 设 int age = 30; int* ptr = &age;,则 ptr 和 &age 的关系是:
A. 一定不同
B. 类型不同但值含义相同
C. ptr 等于 age
D. ptr 等于 *age
正确答案: B
解析: ptr 中保存的是 age 的地址,而 &age 表达式求出的也是 age 的地址,所以地址值一致。
知识点拓展: ptr 是变量,&age 是表达式。两者语义位置不同,但地址值可以相同。
题 13
题目: 如果一个指针已经指向合法对象,以下哪个操作通常是安全的?
A. 对空指针解引用
B. 对野指针解引用
C. 通过解引用读取目标值
D. 对未初始化指针做 delete
正确答案: C
解析: 指针已经指向合法对象时,读写该对象才有意义;其余几项都具有明显风险。
知识点拓展: “安全”这件事和语法往往不是一回事,很多危险代码也能通过编译。
题 14
题目: 下面哪一项体现了“指针保存地址而不是值”?
A. int x = 10;
B. int* p = &x;
C. int& r = x;
D. const int y = 10;
正确答案: B
解析: p = &x 是把 x 的地址交给指针,而不是把 x 的数值 10 直接拷贝进 p。
知识点拓展: 如果把数值 10 强行塞给指针,语义上就是把“10 当成地址”,这通常完全错误。
题 15
题目: 指针变量与普通变量相比,最大的特殊性在于:
A. 不能初始化
B. 不能被赋值
C. 它保存的值会被解释为地址
D. 它必须是全局变量
正确答案: C
解析: 指针之所以特殊,不是因为它的语法形式,而是因为其内容会被解释为地址。
知识点拓展: 地址是数值,但不是“普通业务值”;一旦程序把某个数当作地址使用,就进入了内存访问语义。
题 16
题目: 在 64 位环境下,sizeof(int*) 通常更可能是:
A. 1
B. 2
C. 4
D. 8
正确答案: D
解析: 64 位环境的地址宽度通常是 8 字节,因此指针大小通常为 8。
知识点拓展: 指针大小跟平台相关,不要把某个平台上的结果写死进认知里。
题 17
题目: sizeof(char*) 与 sizeof(double*) 在同一平台上通常:
A. 前者更大
B. 后者更大
C. 一样大
D. 一个固定、一个不固定
正确答案: C
解析: 无论指针指向哪种类型,指针变量自己存的都是地址,所以大小通常一样。
知识点拓展: 真正有差异的是 sizeof(char) 和 sizeof(double),而不是它们的指针大小。
题 18
题目: 下面哪项最准确?
A. sizeof(ptr) 取决于 ptr 指向对象的值
B. sizeof(ptr) 取决于平台和地址宽度
C. sizeof(ptr) 恒等于 sizeof(*ptr)
D. sizeof(ptr) 只有运行时才能确定
正确答案: B
解析: 指针本身大小取决于平台地址宽度,而不取决于它当前指向对象的内容。
知识点拓展: sizeof 对于普通对象和指针对象往往反映的是两套完全不同的含义。
题 19
题目: 若 int age = 30; int* ptr = &age;,则下面哪个表达式结果最可能是地址?
A. age
B. *ptr
C. ptr
D. *&age
正确答案: C
解析: ptr 本身通常表示地址,age 和 *ptr 则是整数值。
知识点拓展: *&age 会先取地址再解引用回来,最终仍然是 age 本身,不是地址。
题 20
题目: 指针在 C++ 中之所以强大,是因为它让程序能够:
A. 只在源代码级操作变量
B. 完全忽略内存模型
C. 直接控制和访问内存位置
D. 自动进行垃圾回收
正确答案: C
解析: 指针把程序从“只看变量名”推进到“直接处理地址和内存位置”的层面。
知识点拓展: 强大和危险往往是同一枚硬币的两面。越靠近底层,越需要纪律。
三、动态内存与资源管理题(21-40)
题 21
题目: new int 的直接返回值是什么?
A. 一个整数
B. 一个 int 对象
C. 指向新分配整数空间的指针
D. 一个数组长度
正确答案: C
解析: new int 会在自由存储区申请一块能放 int 的空间,并返回其地址。
知识点拓展: new 返回地址,所以通常要用指针接收。
题 22
题目: 下面哪组配对是正确的?
A. new 对应 delete[]
B. new[] 对应 delete
C. new 对应 delete
D. malloc 对应 delete
正确答案: C
解析: 单对象分配 new 必须配对单对象释放 delete。
知识点拓展: 数组分配和释放必须是 new[] / delete[] 配套,不可混用。
题 23
题目: int* nums = new int[10]; 用完后正确的释放方式是:
A. delete nums;
B. delete[] nums;
C. free(nums);
D. 不需要释放
正确答案: B
解析: 动态数组必须用 delete[] 释放,否则行为未定义。
知识点拓展: 这类错误往往不会立刻暴露,但它属于内存管理错误,后果可能很隐蔽。
题 24
题目: 动态内存分配的核心价值是:
A. 让所有对象都变成全局对象
B. 让数组长度在运行时可决定
C. 让代码不需要编译
D. 自动避免内存泄漏
正确答案: B
解析: 动态内存让资源大小可以根据运行时需求决定,而不是在编译期写死。
知识点拓展: 这也是 vector 等容器背后的思想来源之一。
题 25
题目: 哪种情况最符合“内存泄漏”的定义?
A. 使用了静态数组
B. 同一块内存被释放一次
C. 分配的内存失去引用却未释放
D. 指针指向栈变量
正确答案: C
解析: 一旦分配出的内存无法再被程序找到并释放,就形成了泄漏。
知识点拓展: 泄漏的本质不是“占了内存”,而是“资源所有权丢失且不可回收”。
题 26
题目: 下面哪段代码最可能造成内存泄漏?
A. int x = 10;
B. int* p = new int; delete p;
C. int* p = new int[5]; p = new int[10];
D. int* p = nullptr;
正确答案: C
解析: 第二次赋值前没有释放第一块内存,旧地址丢失,因此泄漏。
知识点拓展: 覆盖指针变量是典型泄漏源,尤其在复杂控制流里很容易发生。
题 27
题目: 下面哪一项是对 delete 的正确理解?
A. 它可以释放任意地址
B. 它只能释放由匹配的 new 分配出的资源
C. 它会自动把所有同地址指针都置空
D. 它会把内存永久删除
正确答案: B
解析: delete 只能用于释放由 new 获得的对应资源,不能拿来释放普通变量地址。
知识点拓展: “看起来像地址”不等于“可以 delete”,关键在于这块资源的来源。
题 28
题目: 为什么 delete &age; 是错误的?
A. 因为 age 必须是 double
B. 因为 &age 不是指针
C. 因为 age 不是通过 new 创建的
D. 因为 delete 只能在类里用
正确答案: C
解析: age 是普通自动对象,不是自由存储区分配来的资源,不能交给 delete。
知识点拓展: 栈对象和动态对象的生命周期管理方式完全不同。
题 29
题目: 下面哪种做法最可能导致悬空指针?
A. int* p = nullptr;
B. int* p = new int(5); delete p;
C. int x = 3; int* p = &x;
D. const int* p = nullptr;
正确答案: B
解析: 删除之后,指针变量 p 仍然存在,但它原先指向的资源已失效,这就是悬空。
知识点拓展: 很多人误以为 delete 会让指针自动变空,实际上不会。
题 30
题目: delete p; p = nullptr; 这两步连着写的主要意义是:
A. 提高编译速度
B. 防止后续误用悬空指针
C. 让内存变大
D. 让 delete 生效两次
正确答案: B
解析: 释放后手动置空,能减少后续误解引用旧地址的风险。
知识点拓展: 这不是万能安全措施,但在裸指针时代是一种常见自保手段。
题 31
题目: 对同一块动态内存重复调用 delete 会怎样?
A. 一定安全
B. 一定什么都不发生
C. 属于未定义行为
D. 会自动转成 delete[]
正确答案: C
解析: 同一资源只能释放一次,重复释放属于严重内存错误。
知识点拓展: 重复释放和使用已释放资源,都是典型“生命周期违规”。
题 32
题目: new 失败时,默认行为更接近于:
A. 返回随机地址
B. 返回 0 但不提示
C. 抛出异常
D. 自动重试直到成功
正确答案: C
解析: 默认 new 分配失败时会抛出 std::bad_alloc。
知识点拓展: 这是现代 C++ 资源分配错误处理的重要入口,和异常机制结合很紧密。
题 33
题目: 如果不想依赖异常处理,而想在分配失败时得到空指针,应使用:
A. new[]
B. new(nothrow)
C. delete(nothrow)
D. nullptr new
正确答案: B
解析: new(nothrow) 在分配失败时返回空指针,而不是抛异常。
知识点拓展: 两种思路都可用,关键是团队要在资源分配失败的策略上保持一致。
题 34
题目: 使用 new(nothrow) 后,最合理的下一步是:
A. 直接解引用
B. 直接 delete
C. 检查返回指针是否为空
D. 立即递增指针
正确答案: C
解析: 既然使用了 nothrow,就意味着分配失败时可能返回空,因此必须检查。
知识点拓展: “检查失败路径”是系统编程里必须形成的条件反射。
题 35
题目: 下列关于动态内存的说法哪项正确?
A. 用完后系统总会立即自动替你释放
B. 手动分配的资源应由程序显式管理
C. new 只适用于类对象
D. 静态数组和动态数组没有区别
正确答案: B
解析: 原始 new/delete 模式下,资源释放责任在程序员。
知识点拓展: 现代 C++ 倾向于通过 RAII 把这种责任交给对象本身而不是手工记忆。
题 36
题目: 为什么说原始 new/delete 容易出错?
A. 因为它们语法太短
B. 因为资源生命周期由程序员手工维护
C. 因为它们只能用于整数
D. 因为它们不能和函数一起使用
正确答案: B
解析: 一旦生命周期靠人手工追踪,就容易出现忘记释放、重复释放、释放过早等问题。
知识点拓展: 智能指针和容器类的价值,本质上就在于减少这种人工管理负担。
题 37
题目: 下列哪一项最能体现“资源所有权”概念?
A. 一个资源应该知道由谁负责释放
B. 指针一定比引用快
C. 所有地址都能复制
D. 所有变量都能 delete
正确答案: A
解析: 内存管理的关键不只是“能访问”,而是“谁负责最终释放”。
知识点拓展: unique_ptr 就是把“唯一所有者”写进类型系统。
题 38
题目: 下面哪个现代 C++ 工具通常可以替代手写动态数组?
A. std::vector
B. std::cin
C. std::endl
D. typedef
正确答案: A
解析: std::vector 能自动管理动态数组容量与释放,是更现代、更安全的做法。
知识点拓展: 真正的工程经验不是“会写裸指针”,而是“知道什么时候不该手写裸指针”。
题 39
题目: 动态分配和释放一块内存后,下面哪项表述更合理?
A. 指针自动失效且自动置空
B. 指针变量还在,但不应再被解引用
C. 可以继续用旧指针安全写入
D. 可以立刻再次 delete
正确答案: B
解析: 资源释放后,指针变量依旧存在,但指向的对象生命周期已经结束。
知识点拓展: 这正是悬空指针的来源:变量还在,目标已死。
题 40
题目: 如果程序运行时间越长,占用内存越多,而功能并未显著增加,首先该怀疑:
A. 指针大小变化
B. 内存泄漏
C. 编译器失效
D. 引用语法错误
正确答案: B
解析: 这是内存泄漏最典型的外部表现之一。
知识点拓展: 长时间运行服务里,泄漏问题远比小脚本更致命。
四、指针运算与数组题(41-60)
题 41
题目: 若 int* p 指向某个整数,则执行 ++p 后,p 通常会:
A. 前进 1 位二进制
B. 前进 1 个字节
C. 前进一个 int 的跨度
D. 变成空指针
正确答案: C
解析: 指针递增按“元素类型大小”移动,不是固定按 1 字节。
知识点拓展: 指针运算的单位不是字节,而是“当前所指类型的元素个数”。
题 42
题目: 若 char* p 执行 ++p,与 int* p 执行 ++p 的主要区别在于:
A. 前者不能递增
B. 后者不能递增
C. 前进的字节数通常不同
D. 语法完全不同
正确答案: C
解析: char 通常占 1 字节,而 int 往往占 4 字节或其他平台相关大小,因此前进步长不同。
知识点拓展: 这是为什么 char* 常被用来做字节级处理。
题 43
题目: 对于数组 int arr[5];,在很多表达式中,arr 会退化为:
A. int
B. int*,指向首元素
C. int&
D. void*
正确答案: B
解析: 数组名在多数表达式里会退化为指向首元素的指针。
知识点拓展: 这就是为什么 arr 常常和 &arr[0] 看起来效果相同。
题 44
题目: arr[2] 与下面哪个表达式等价?
A. *arr + 2
B. *(arr + 2)
C. &arr + 2
D. **arr
正确答案: B
解析: 数组下标本质上就是基址加偏移再解引用。
知识点拓展: 这也是“数组和指针关系紧密”的根本原因。
题 45
题目: 若 int* p = arr;,则 p[3] 与下面哪个表达式等价?
A. *(p + 3)
B. *p + 3
C. &p[3]
D. **(p + 3)
正确答案: A
解析: 指针同样支持下标语法,p[i] 就是 *(p + i)。
知识点拓展: 因为下标语法本质上就是一种指针偏移访问。
题 46
题目: 为什么 myNumbers 可以赋给 int* p,但通常不能再写 myNumbers = p?
A. 因为数组名不是左值,不能随意改绑定
B. 因为数组名没有类型
C. 因为 p 不是地址
D. 因为数组必须是 const
正确答案: A
解析: 数组名虽然常退化成指针使用,但数组本身不是一个可修改的普通指针变量。
知识点拓展: 这说明“数组像指针”不等于“数组就是普通指针”。
题 47
题目: 对动态数组 int* nums = new int[5];,访问第 3 个元素的表达式可以是:
A. nums(2)
B. *(nums + 2)
C. nums + 2
D. &nums[2] = 3
正确答案: B
解析: 第 3 个元素下标是 2,因此可通过偏移两个元素再解引用得到。
知识点拓展: nums[2] 与 *(nums + 2) 等价。
题 48
题目: 设 int arr[5] = {1,2,3,4,5};,则 *arr 的值是:
A. arr 的地址
B. 1
C. 5
D. sizeof(arr)
正确答案: B
解析: arr 退化为首元素地址,*arr 就是首元素的值,即 1。
知识点拓展: 这和 *p 访问指针指向的第一个对象是同一逻辑。
题 49
题目: 在数组遍历中,如果你不断执行 p++,用完之后为什么往往要恢复到初始地址再释放?
A. 因为 delete[] 需要最初 new[] 返回的地址
B. 因为编译器会忘记数组类型
C. 因为释放前必须先排序
D. 因为数组长度会改变
正确答案: A
解析: 如果你把指针移动走了,释放时必须交回原始首地址。
知识点拓展: 这也是很多人更喜欢用额外迭代指针或下标,而不是直接移动拥有者指针的原因。
题 50
题目: 指针加偏移量的本质是:
A. 地址按字节强行增加
B. 指向同类型下一个或第 n 个元素
C. 改变目标对象类型
D. 创建一个新的数组
正确答案: B
解析: 指针运算的语义是“按元素跨度移动”,而不是简单算术。
知识点拓展: 这也是为什么指针类型信息很重要,编译器需要靠它决定步长。
题 51
题目: 下面哪项最准确地描述了数组名?
A. 总是普通变量
B. 总是引用
C. 在多数表达式里表现得像指向首元素的指针
D. 一定等于数组长度
正确答案: C
解析: 数组名的经典行为就是“衰减”为指向首元素的地址。
知识点拓展: 但在 sizeof(arr) 这样的上下文里,数组不会简单退化。
题 52
题目: 对 int arr[5];,下面哪项通常成立?
A. arr == &arr[0]
B. arr == arr[0]
C. arr == *arr
D. arr == sizeof(arr)
正确答案: A
解析: 数组名在比较场景下通常退化为首元素地址,因此和 &arr[0] 一致。
知识点拓展: 这只是“值相同”的效果,不代表两种写法语义上完全等价。
题 53
题目: 为什么说数组和指针“相似但不相同”?
A. 因为数组不能存数据
B. 因为数组没有类型
C. 因为数组名常像指针,但数组本身不是可重绑的普通指针变量
D. 因为指针不能访问元素
正确答案: C
解析: 它们在访问语法上很接近,但对象模型不同。
知识点拓展: 这一点不搞清楚,后面学函数参数和类成员时很容易错。
题 54
题目: int* arrs[100]; 的含义是:
A. 一个包含 100 个 int 的数组
B. 一个指向 100 个 int 的指针
C. 一个包含 100 个 int* 元素的数组
D. 一个二维整型数组
正确答案: C
解析: 这是“指针数组”,数组里的每个元素都是 int*。
知识点拓展: 它与 int arr[100]; 是完全不同的声明。
题 55
题目: int arr[100]; 最准确的描述是:
A. 一个指针数组
B. 一个包含 100 个整数的数组
C. 一个指向 100 个整数的单指针变量
D. 一个引用对象
正确答案: B
解析: 它是静态整型数组,而不是“100 个指针”。
知识点拓展: int* arr[100] 和 int arr[100] 是经典容易看错的一组。
题 56
题目: 对指针做 --p 的语义通常是:
A. 回到前一个同类型元素
B. 删除指针
C. 让地址按位清零
D. 让对象值减 1
正确答案: A
解析: 递减指针表示向前移动一个同类型元素跨度。
知识点拓展: 指针加减都体现“类型化地址”的思想。
题 57
题目: 如果 int* p 已经偏移到数组中间,p[0] 访问的是:
A. 原数组首元素
B. 当前 p 指向的元素
C. 数组最后一个元素
D. 未定义固定地址
正确答案: B
解析: 下标是相对于当前指针位置而言的,不一定相对于原数组首地址。
知识点拓展: 所以在“滑动窗口”式算法里,移动指针后再用 p[i] 很常见。
题 58
题目: 指针算术最适合使用在哪类连续存储结构上?
A. 动态数组或静态数组
B. 任意树形结构
C. 所有哈希表
D. 所有类对象
正确答案: A
解析: 因为连续存储结构天然支持“按固定跨度走到下一个元素”。
知识点拓展: 这也是数组访问高效的底层原因。
题 59
题目: 为什么对 int* 指针加 1 不是“地址数值 +1”的直觉用法?
A. 因为 C++ 不支持加法
B. 因为编译器要保证它指向下一个 int 的起始位置
C. 因为地址不能运算
D. 因为 int* 其实是引用
正确答案: B
解析: 如果只加 1 字节,指针可能落在一个整数的中间,这没有意义。
知识点拓展: 编译器之所以能做对,是因为指针类型里包含了元素类型信息。
题 60
题目: 关于数组与指针,下列说法更合理的是:
A. 任何时候都完全等价
B. 完全无关
C. 在访问语法上联系紧密,但对象模型不同
D. 只有在 C 语言中才相似
正确答案: C
解析: 这句话最完整,既承认它们很像,也保留了关键差异。
知识点拓展: 理解“相似但不等价”,是学好指针这一章的一个门槛。
五、const 指针与函数参数题(61-80)
题 61
题目: int* const p = &x; 的含义是:
A. p 不能改指向,但能通过它改 x
B. p 能改指向,但不能改 x
C. p 和 x 都不能改
D. p 是常量引用
正确答案: A
解析: const 修饰的是指针本身,因此地址固定,但数据可变。
知识点拓展: 判断时可以从右向左读,很多人会更容易理清 const 绑定谁。
题 62
题目: const int* p = &x; 的含义是:
A. 指针固定,数据可变
B. 指针可变,数据经由该指针不可变
C. 指针和数据都可变
D. 指针和数据都不可变
正确答案: B
解析: 这里 const 修饰的是被指向的数据,所以不能通过 p 修改它。
知识点拓展: 这不代表原对象永远不能被别的非 const 路径修改。
题 63
题目: const int* const p = &x; 的含义是:
A. 指针和所指数据都不可通过它修改
B. 只有指针不可改
C. 只有数据可改
D. 等价于引用
正确答案: A
解析: 这是最严格的一种 const 指针形式,地址和数据都锁死。
知识点拓展: 在函数参数里,如果只是只读观察,一般会偏好这种“高 const 程度”的表达。
题 64
题目: 在函数参数中,若一个参数只读且必须有效,通常更推荐:
A. T*
B. T&
C. const T&
D. T&&
正确答案: C
解析: const T& 既避免拷贝,又表达“只读”语义,非常常见。
知识点拓展: 这就是现代 C++ 函数接口设计的核心套路之一。
题 65
题目: 在函数里使用指针参数前,书里特别强调要做的一件事是:
A. 必须先做类型转换
B. 必须先做递增
C. 检查指针有效性
D. 先 delete
正确答案: C
解析: 指针参数可能为空或无效,因此使用前应先判断。
知识点拓展: 引用参数通常不需要这一步,因为引用语义上默认必须绑定到有效对象。
题 66
题目: 如果函数需要通过参数返回结果,下面哪种接口更常见?
A. void f(const int& in, int& out)
B. void f(int in, int out)
C. void f(const int* in, const int* out)
D. void f(int&& in, int&& out)
正确答案: A
解析: 一个只读输入参数配 const int&,一个输出参数配非常量引用,是非常典型的设计。
知识点拓展: 这比双指针接口更简洁,前提是你能保证对象一定存在。
题 67
题目: 若函数只是读取半径计算面积,不应该修改半径,那么参数更合适的是:
A. double* radius
B. double& radius
C. const double& radius
D. double radius*
正确答案: C
解析: const double& 清楚表达“只读输入”。
知识点拓展: 对基础类型来说,传值和 const& 在性能上差异不大,但语义表达仍然重要。
题 68
题目: 下面哪项是“不能通过这个指针修改数据”的声明?
A. int* p
B. int* const p
C. const int* p
D. int& p
正确答案: C
解析: const int* p 限制了经由 p 修改其所指对象。
知识点拓展: const 修饰层次不一样,语义就完全不同。
题 69
题目: 为什么说函数参数要“尽可能提高 const 程度”?
A. 因为 const 更短
B. 因为 const 让函数一定更快
C. 因为它能减少误修改,提升接口自说明性
D. 因为 const 能代替异常
正确答案: C
解析: const 不只是编译器约束,还是接口文档的一部分。
知识点拓展: 在多人协作中,越清楚的约束越能减少二次误改。
题 70
题目: 下面哪个说法是正确的?
A. 可以把 const int* 随意赋给 int*
B. const 限定可以随意丢掉
C. 不能把“指向 const 的指针”直接赋给“指向非 const 的指针”
D. 任何指针都能自动降级为非 const
正确答案: C
解析: 如果允许这种赋值,就能绕过 const 约束去修改本该只读的数据。
知识点拓展: 类型系统在这里是在保护程序,而不是故意为难你。
题 71
题目: 对于“必须非空而且只读”的参数,相比指针版本,引用版本的优势之一是:
A. 可以表达空值
B. 语法更长
C. 不需要空指针检查
D. 必须手动释放
正确答案: C
解析: 引用语义上应当绑定到有效对象,因此接口层面更强。
知识点拓展: 这也是为什么很多现代接口更偏好引用而不是原始指针。
题 72
题目: 下面哪种写法最适合表达“参数必须存在,而且函数会修改它”?
A. const T&
B. T&
C. const T*
D. const T* const
正确答案: B
解析: 非 const 引用表达“对象一定存在,而且允许被修改”。
知识点拓展: 这类参数常见于交换、填充、输出结果等函数。
题 73
题目: 为什么书里在指针参数计算面积的函数中要检查 ptrPi && ptrRadius && ptrArea?
A. 为了提高打印速度
B. 为了避免对空指针解引用
C. 为了让指针更大
D. 为了让 cout 生效
正确答案: B
解析: 如果指针为空就直接解引用,会发生非法访问。
知识点拓展: 指针接口相比引用接口的一大额外负担,就是你必须考虑空值路径。
题 74
题目: 在“输入参数只读、输出参数可写”的函数设计中,下面哪组更合理?
A. const T& in, T& out
B. T& in, const T& out
C. T in, const T out
D. const T* in, const T* out
正确答案: A
解析: 输入是只读引用,输出是可写引用,这最符合语义。
知识点拓展: 参数的 const 设计,应该和“谁是输入、谁是输出”一一对应。
题 75
题目: 下列关于指针参数的描述哪项更合理?
A. 只要能传地址就一定优于引用
B. 指针适合表达“可空”或“可能不存在”
C. 指针一定更快
D. 指针一定更安全
正确答案: B
解析: 指针最常见的额外语义价值之一,就是能表达“没有对象”。
知识点拓展: 也正因为它能为空,所以它比引用多了一层使用风险。
题 76
题目: double* const ptrArea 更强调:
A. 输出地址不能改,但地址中的结果可写
B. 结果永远不能写
C. 该参数一定为空
D. 该参数是数组
正确答案: A
解析: 这是固定指针、可写数据的典型表达。
知识点拓展: 很多 C 风格接口会这样写输出参数。
题 77
题目: 如果函数内部不应该修改输入对象,最差的接口设计通常是:
A. const T&
B. T&
C. const T*
D. const T* const
正确答案: B
解析: 非 const 引用会给函数提供修改能力,和“只读输入”语义冲突。
知识点拓展: 接口设计里,能力越少越安全,除非你确实需要那项能力。
题 78
题目: 为什么说 const 是接口设计工具而不仅仅是语法?
A. 因为它改变 CPU 架构
B. 因为它会自动生成文档
C. 因为它明确告诉调用者和维护者“哪些数据不能改”
D. 因为它让所有函数都内联
正确答案: C
解析: const 让类型直接承载了“可否修改”的契约。
知识点拓展: 好接口的特征之一,就是从函数签名就能看出大部分行为边界。
题 79
题目: 如果一个接口中参数允许为空,那么更自然的形式常常是:
A. T&
B. const T&
C. T* 或 const T*
D. T&&
正确答案: C
解析: 引用通常表达“对象一定存在”,而指针可表达“没有对象”。
知识点拓展: 这也是很多旧接口大量使用指针的原因之一。
题 80
题目: 关于 const 指针和 const 引用,下面哪句总结更准确?
A. 它们只是为了写着好看
B. 它们让编译器和人都更清楚数据是否可改
C. 它们会自动管理内存
D. 它们只在模板中有用
正确答案: B
解析: const 同时是编译约束和可读性约束。
知识点拓展: 如果你总能自然地区分输入参数、输出参数、只读参数,这一节就算真正掌握了。
六、引用与综合应用题(81-100)
题 81
题目: 引用最准确的定义是:
A. 一个能存放多个地址的数组
B. 一个对象的别名
C. 一个自动释放的指针
D. 一个不可修改的变量
正确答案: B
解析: 引用不是新对象,而是已有对象的另一个名字。
知识点拓展: 这也是为什么引用看起来更像“语义绑定”,而不是“地址容器”。
题 82
题目: 声明引用时,下面哪项是必须的?
A. 必须使用 new
B. 必须初始化
C. 必须是全局变量
D. 必须是 const
正确答案: B
解析: 引用一旦声明就必须绑定到某个对象,不能像指针那样先空着。
知识点拓展: 这也是引用通常比裸指针更稳的原因之一。
题 83
题目: int& ref = original; 之后,ref 和 original 的关系是:
A. 两个独立整数
B. 一个指针一个变量
C. 同一对象的两个名字
D. 一个数组一个元素
正确答案: C
解析: 引用只是别名,不会新建一份独立副本。
知识点拓展: 观察它们地址相同,是理解“别名”最直观的办法。
题 84
题目: 为什么引用适合作为函数参数来避免拷贝?
A. 因为引用不需要类型
B. 因为引用能直接绑定到调用者对象
C. 因为引用能自动释放内存
D. 因为引用一定更小
正确答案: B
解析: 引用让函数直接操作原对象,而不是先复制一个新对象。
知识点拓展: 对大对象来说,这能明显减少构造和拷贝开销。
题 85
题目: void GetSquare(int& number) 中 number 被声明为引用的主要作用是:
A. 让函数改的是调用者对象本身
B. 让函数一定返回 int
C. 让函数不能修改值
D. 让函数变成模板
正确答案: A
解析: 这样函数内部对 number 的修改会直接反映到调用者对象上。
知识点拓展: 这类函数本质上是“就地修改”接口。
题 86
题目: 如果希望函数读取输入但不能修改输入,应优先考虑:
A. T&
B. const T&
C. T*
D. T
正确答案: B
解析: const T& 兼顾避免拷贝和禁止修改,是最典型的只读参数写法。
知识点拓展: 对复杂对象尤其适合;对小基础类型则更多体现语义价值。
题 87
题目: 下面哪项对引用与指针的比较更合理?
A. 引用一定比指针快很多
B. 指针一定比引用安全
C. 引用更适合表达必然存在的对象
D. 两者在所有设计场景都完全等价
正确答案: C
解析: 这是两者最有价值的语义差异之一。
知识点拓展: 好的 C++ 设计往往先问“语义上是不是允许为空”,再决定用引用还是指针。
题 88
题目: 对于 const int& ref = value;,下面哪项正确?
A. 可以通过 ref 改 value
B. ref 是空引用
C. 不能通过 ref 修改 value
D. ref 是一个指针数组
正确答案: C
解析: const 引用是只读别名,不能通过它修改原对象。
知识点拓展: const 引用在参数设计中出现频率极高,要形成直觉。
题 89
题目: 下面哪种情况更倾向于用引用而不是指针?
A. 参数可能不存在
B. 需要表达空值
C. 参数必须有效且函数语义上直接操作该对象
D. 需要频繁改指向
正确答案: C
解析: 当对象必须存在时,引用表达更自然。
知识点拓展: “必须有效”这四个字,往往就是引用上场的信号。
题 90
题目: 为什么说引用在很多接口里比指针“更简单”?
A. 因为它不用考虑空值分支
B. 因为它没有类型
C. 因为它一定能管理动态内存
D. 因为它能自动做异常处理
正确答案: A
解析: 引用省掉了“为空怎么办”的接口分支。
知识点拓展: 少一种状态,就少一类 bug。
题 91
题目: 如果一个函数既需要输入,又需要通过参数回传结果,下面哪组最合理?
A. const T& in, T& out
B. T in, T out
C. const T* in, const T* out
D. T&& in, const T& out
正确答案: A
解析: 只读输入配 const 引用,可写输出配非常量引用,这是经典组合。
知识点拓展: 如果参数可能缺失,再考虑换成指针输出。
题 92
题目: 下列哪项最能体现“引用不能像指针那样重指向”?
A. 引用可以为 nullptr
B. 引用声明后通常绑定关系固定
C. 引用必须在堆上
D. 引用不能作为参数
正确答案: B
解析: 引用一旦绑定到对象,通常就代表这层别名关系固定下来。
知识点拓展: 所以引用更适合表达稳定语义,而不是游走式的地址操作。
题 93
题目: 对于大对象参数,如果只读取,为什么常用 const T&?
A. 为了增大对象体积
B. 为了避免不必要拷贝
C. 为了强制动态分配
D. 为了自动内联
正确答案: B
解析: 大对象按值传递可能带来很高复制成本,const 引用可以避免这一点。
知识点拓展: 这条规则在容器、字符串、大型结构体中尤其重要。
题 94
题目: 下列哪项更接近现代 C++ 的实践态度?
A. 只要学会裸指针,什么都不用学
B. 应彻底回避理解指针
C. 必须理解裸指针,但日常代码尽量减少手写 new/delete
D. 引用已经完全淘汰指针
正确答案: C
解析: 理解底层机制是必须的,但工程上更强调安全抽象。
知识点拓展: 这就是“会原理”和“会写工程代码”的区别。
题 95
题目: 下面哪一项是更符合现代风格的动态数组写法?
A. int* nums = new int[n];
B. std::vector<int> nums(n);
C. int nums[] = n;
D. delete[] n;
正确答案: B
解析: vector 自动管理容量和释放,能显著减少裸内存错误。
知识点拓展: 第八章学的是原理,但真正写业务代码时,容器通常是第一选择。
题 96
题目: 下列哪种场景最适合继续使用原始指针?
A. 所有普通业务对象传参
B. 需要表达“可能为空”或与底层 API 交互
C. 所有字符串操作
D. 所有动态数组管理
正确答案: B
解析: 原始指针在底层接口、可空语义、资源边界交互中仍有价值。
知识点拓展: 关键不是“禁用原始指针”,而是“不要滥用它来承担所有工作”。
题 97
题目: 如果某个参数不应该为空,而你仍然把它设计成裸指针,最大的额外成本是:
A. 代码行数减少
B. 调用方更难用
C. 你和调用方都得额外处理空值可能性
D. 编译器自动报错
正确答案: C
解析: 这会让接口状态空间变大,本来不需要的空值分支也被引入了。
知识点拓展: 很多糟糕接口的问题,不是“功能不够”,而是“允许了不该允许的状态”。
题 98
题目: 对于“谁拥有资源、何时释放资源”这一类问题,第八章真正训练的是:
A. 输出格式技巧
B. 生命周期意识
C. 变量命名规范
D. 预处理器语法
正确答案: B
解析: 指针和动态内存最核心的训练目标,就是建立生命周期和所有权意识。
知识点拓展: 后续的构造函数、析构函数、智能指针、移动语义,其实都围绕这件事展开。
题 99
题目: 学完这一章后,面对一个函数接口,最值得先问的问题不包括:
A. 这个参数会不会为空
B. 这个对象由谁释放
C. 这里是否需要修改对象
D. 这个变量名是不是足够长
正确答案: D
解析: 命名当然重要,但这一章更核心的是空值、所有权、生命周期和可变性。
知识点拓展: 真正的底层 bug 很少源于名字,更多源于资源边界和状态边界。
题 100
题目: 如果你做完整套题后只能记住一句话,下面哪句最值得记住?
A. 指针只是语法花样
B. 引用一定能代替所有指针
C. 地址、对象和值不是同一回事;生命周期管理比语法更重要
D. new 比 vector 更现代
正确答案: C
解析: 这句话把第八章真正的核心压缩出来了。指针和引用只是入口,背后真正训练的是内存模型与生命周期意识。
知识点拓展: 只会写 * 和 & 不算学会指针。能判断“谁拥有资源、何时失效、该用值/引用/指针哪一种语义”才算入门。
七、刷题建议
如果你准备把这一章真正学扎实,可以这样复习:
- 先做 1-20,确保
&、*、地址和值不再混。 - 再做 21-40,重点盯住
new/delete、泄漏、悬空、重复释放。 - 再做 41-60,把数组和指针关系彻底理顺。
- 再做 61-80,把
const的修饰层次吃透。 - 最后做 81-100,把“接口设计”和“现代 C++ 实践”连起来。
如果你能把整套题做到:
- 看见一段指针代码时不发怵
- 能判断是否有生命周期问题
- 能说出为什么该用
const T& - 知道为什么现代代码尽量少写裸
new/delete
那这一章就真的掌握住了。