Lesson 8:指针

从对象地址、指针类型、解引用和动态生命周期建立间接访问模型。

学习目标

  • 能解释对象、地址、指针值、指针类型和解引用之间的关系
  • 能实现空指针检查、通过指针修改对象,并比较指针与引用的接口语义
  • 能分析动态对象的分配、使用、释放与失效路径,发现泄漏、悬空和重复释放

来源、版次与标准边界

“Lesson 8:指针”以 SB Creative 出版社书页核定高桥麻奈著、第 5 版、2017 年 6 月 14 日发行、ISBN 978-4-7973-9259-3;出版社页面标 596 页。CiNii Books 书目与 16 课目录记录正文 xxiii+571 页,日本国会图书馆书目记录 571 页。页数口径存在前置页差异,因此本课程不拿页码充当目录证据。

“Lesson 8:指针”的中文说明、示例、交互、练习和答案均为独立教学重写。公开资料只确认 16 个 Lesson 标题;本页列出的细分概念是课程教学映射,不冒充原书逐级小节。技术规则参考 ISO C++ 标准入口复核,但 2017 年教材的 Visual Studio 2017 语境不会被静默升级为 C++23;现代写法只能明确标为迁移说明。

围绕“一次解引用需要哪些对象存在性、类型、生命周期和所有权前提?”,本页要求保留“对象创建点、地址、所有者、借用范围、释放点、空值状态和检测器诊断。”。若故障“delete 后保留原地址并再次解引用”无法在同一输入下制造首个分岔,应拒绝当前解释,而不是追加随机样例。

为什么“地址”不是对象本身

变量是具有类型和生命周期的对象,存储位于某个内存位置;地址用于标识该位置,指针对象则保存地址值。把三者混在一起会产生典型误解:修改指针值是在改变它指向哪里,解引用后赋值才是在修改目标对象。

int score = 80;
int* pointer = &score;
 
std::cout << score << '\n';     // 80
std::cout << pointer << '\n';   // 一个地址值
std::cout << *pointer << '\n';  // 80

int* 表示“指向 int 的指针”,不是“任何地址”。指针自身也有地址和生命周期,但初学时先固定两层:pointer 保存 score 的地址,*pointer 访问 score。

取地址与解引用构成往返

一元 &object 取得对象地址,一元 *pointer 根据指针类型访问所指对象。星号在声明 int* p 中表示类型,在表达式 *p 中表示解引用;上下文不同,责任不同。

int value = 10;
int* p = &value;
*p = 25;
 
// value 现在也是 25,因为 p 指向同一个对象。

这里没有复制第二个 int;value*p 是同一对象的两条访问路径。先预测并打印修改前后的 valuep*p,可以建立“地址不变、目标值改变”的证据。

指针类型保护解释规则

double* 解引用时按 double 规则访问目标,int*int 规则。编译器通常拒绝把 double* 隐式赋给 int*,因为地址即使位数相同,目标大小、对齐和表示都不兼容。

const int* 表示不能通过该指针修改目标;int* const 表示指针自身不能改指向。它们分别限制“目标写入”和“地址值重新赋值”,不能只看 const 离星号近不近死记。

int first = 1;
int second = 2;
const int* readOnly = &first;
int* const fixedTarget = &first;
 
readOnly = &second;   // 可以改指向
*fixedTarget = 3;     // 可以改目标

空指针表示当前没有目标

nullptr 是现代 C++ 的空指针字面量,表示指针不指向有效对象。空是一种可检查状态,不是可以解引用的特殊对象。

int* selected = nullptr;
if (selected != nullptr) {
    std::cout << *selected << '\n';
}

解引用空指针会产生未定义行为。更棘手的是非空也不保证有效:对象生命周期结束后,旧地址仍可能保留在指针里,形成悬空指针。因此检查 p != nullptr 只能证明“不是空”,不能证明目标仍存活。

指针与引用表达不同接口契约

引用在初始化时绑定对象,日常使用像对象别名,通常不表达“没有目标”;指针可以为 nullptr、可以重新指向,并需要显式解引用。两者都能提供间接访问,但可选性和重绑定语义不同。

void increment(int& value) {
    ++value;
}
 
bool tryIncrement(int* value) {
    if (value == nullptr) return false;
    ++*value;
    return true;
}

第一个接口要求调用者必须提供对象,第二个接口把“可能没有对象”写进类型与返回契约。若空没有业务意义,引用通常更清楚;若需要遍历、可选目标、重绑定或与 C API 交互,指针更合适。

动态对象的生命周期由程序控制

new T 创建动态存储期对象并返回指针,delete p 结束对应单对象生命周期并释放存储。每次成功分配必须有明确唯一的释放责任;释放后所有指向该对象的指针都失效。

int* number = new int{42};
std::cout << *number << '\n';
delete number;
number = nullptr;

忘记 delete 会泄漏;delete 后继续解引用会悬空;同一对象 delete 两次会破坏分配器状态。把本地指针设为 nullptr 可阻止这一条路径再次使用,但不能自动清除其他别名。

现代 C++ 更常用自动对象、容器和智能指针管理动态资源,本课仍要理解原始 new/delete,因为只有看清所有权与生命周期,才能明白这些库设施替你保证了什么。

动态数组需要匹配释放形式

new T[n]delete[] 配对,单对象 new Tdelete 配对。混用释放形式是未定义行为。若长度在运行时确定,优先使用 std::vector<T>;它记录长度并在离开作用域时自动释放。

int* samples = new int[3]{10, 20, 30};
std::cout << samples[1] << '\n';
delete[] samples;
samples = nullptr;

指针本身不记录数组长度。拿到 int* 的函数无法仅从地址知道有多少元素,因此接口还要传长度,或直接接收能携带大小的容器/视图。

正式节点与章专属证据

  • 地址:在“Lesson 8:指针”中核对输入、状态变化、失败模式和可复现证据;第 1 个节点必须能回到“被解引用地址指向仍在生命周期内且类型匹配的对象,并且释放责任唯一。”。
  • 指针:在“Lesson 8:指针”中核对输入、状态变化、失败模式和可复现证据;第 2 个节点必须能回到“被解引用地址指向仍在生命周期内且类型匹配的对象,并且释放责任唯一。”。
  • 解引用:在“Lesson 8:指针”中核对输入、状态变化、失败模式和可复现证据;第 3 个节点必须能回到“被解引用地址指向仍在生命周期内且类型匹配的对象,并且释放责任唯一。”。
  • 引用:在“Lesson 8:指针”中核对输入、状态变化、失败模式和可复现证据;第 4 个节点必须能回到“被解引用地址指向仍在生命周期内且类型匹配的对象,并且释放责任唯一。”。
  • 动态内存:在“Lesson 8:指针”中核对输入、状态变化、失败模式和可复现证据;第 5 个节点必须能回到“被解引用地址指向仍在生命周期内且类型匹配的对象,并且释放责任唯一。”。

先用输入合同检查本页正式节点,再在相同初值下逐步比较正常和失败轨迹,最后只启用“delete 后保留原地址并再次解引用”完成反例与复位。三个交互都必须能独立重置,且重置后再次满足“被解引用地址指向仍在生命周期内且类型匹配的对象,并且释放责任唯一。”。

输入与状态合同

Lesson 8:指针

一次解引用需要哪些对象存在性、类型、生命周期和所有权前提?

必须先声明

为地址声明输入类型、有效范围、对象生命周期和失败策略。

可复核证据

保存Lesson 8:指针的原始输入、初值与第一条可检查诊断。

正式节点:地址、指针、解引用、引用、动态内存

编译与运行轨迹

同一输入下比较正常与失败路径

  1. 01创建对象并取得地址
  2. 02检查指针非空与类型
  3. 03在对象生命周期内解引用
  4. 04释放后立即清除所有权状态

不变量:被解引用地址指向仍在生命周期内且类型匹配的对象,并且释放责任唯一。

故障定位与复位

一次只破坏一个前提

基线可继续:被解引用地址指向仍在生命周期内且类型匹配的对象,并且释放责任唯一。

小结

  • 地址标识对象位置,指针对象保存地址,解引用才按指针类型访问目标
  • 取地址与解引用形成间接访问,同一对象可有多个别名,修改会通过所有有效路径可见
  • nullptr 表示没有目标,但非空不等于有效;有效性还依赖目标生命周期、边界和类型
  • 引用通常表达必有目标且不重绑定,指针表达可空、可重绑定及显式间接访问
  • 动态对象需要唯一释放责任,new/delete 与 new[]/delete[] 必须配对;现代代码优先使用自动资源管理

练习

  1. 问题 1:画出身份。 int x=3; int* p=&x; *p=5; 后 x、p、*p 分别是什么?
  1. 问题 2:选择接口。 必须提供的可写计数器与可选目标,分别用引用还是指针?
  1. 问题 3:审计释放。 两个指针保存同一动态对象地址,delete 其中一个后另一个为何危险?

名词解释

名词解释

本章出现的专业名词,用大白话再讲一遍。

地址
标识对象内存位置的值。
指针
保存兼容对象或函数地址的对象。
解引用
通过有效指针访问其所指对象的操作。
空指针
不指向有效对象的指针状态,以 nullptr 表示。
引用
初始化时绑定已有对象并作为别名使用的间接访问形式。
动态生命周期
由分配与所有权策略决定结束时机,而非自动随当前块结束的对象生命周期。

资料与写作方式声明

本章以高桥麻奈 2017 年 C++ 入门教材第 5 版权威目录界定学习范围,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

原作版权归作者与出版社所有;本站原创教学结构与表述仅供学习交流。

讨论

评论区加载中…