第八章 指针与内存:最危险也最强大的部分

← 返回目录

本章目标:指针概念、new/delete、引用与指针的区别、 智能指针(unique_ptr / shared_ptr)、RAII。 现代 C++ 的核心思想:**永远别手写 new/delete**。

8.1 指针:存"地址"的变量

int x = 10;
int* p = &x;      // & 取 x 的地址,p 指向 x
*p = 20;          // * 解引用:通过 p 修改 x
std::println("{}", x);   // 20

形象理解:变量是"装了 10 的盒子",&x 是盒子的门牌号, p 是一个写着门牌号的小纸条,*p 是"按照纸条找到盒子"。

空指针:不指向任何东西

int* p = nullptr;   // 现代写法(C++11 起,替代 NULL)
if (p != nullptr) { /* 安全使用 */ }
// 解引用空指针 = 直接崩溃(segfault)

8.2 为什么需要指针?(先有概念再动手)

1) 函数间共享大对象(但引用已经能搞定,见 5.3) 2) 动态分配:运行时长出来的对象(需要 new,见 8.3) 3) 多态:指向父类的指针实际操作子类对象(第九章) 4) 与 C 库/系统接口对接(旧 API 全是裸指针)

现代 C++ 的结论:业务代码里 90% 的指针应该用引用或 智能指针替代。裸指针(*)只出现在"观察别人数据"的场合。

8.3 new / delete:手动分配与释放(要懂但别用)

int* p = new int(42);     // 堆上分配一个 int,值 42
delete p;                 // 释放!忘了 = 内存泄漏
p = nullptr;              // 顺手置空,防止悬空

int* arr = new int[10];   // 堆上分配数组
delete[] arr;             // 数组用 delete[],别搞混

三大经典 bug(这就是手写 new 的下场):

  1. 忘 delete → 内存泄漏(程序内存越吃越多)
  2. delete 后再用 → 悬空指针 → 崩溃或数据被篡改
  3. 异常发生时来不及 delete → 泄漏

C++ 官方 1994 年就想消灭这些,于是发明了……

8.4 智能指针:new/delete 的终结者(重点!)

#include <memory>

// unique_ptr:独占所有权(最常用)
std::unique_ptr<int> u = std::make_unique<int>(42);
// 用 *u 访问。u 离开作用域时,自动 delete!

// shared_ptr:共享所有权(引用计数)
std::shared_ptr<int> s1 = std::make_shared<int>(10);
auto s2 = s1;               // 两个智能指针共享同一个 int
// 最后一个 shared_ptr 销毁时才真正 delete

// weak_ptr:旁观者,不增加引用计数(打破循环引用)
std::weak_ptr<int> w = s1;
if (auto sp = w.lock()) {   // 安全地"借用"一下
    std::println("{}", *sp);
}

人话总结:

为什么不直接传裸指针给 shared_ptr?因为 new 泄漏在 "构造失败/异常"时会漏,make_shared 一次搞定还更高效。

8.5 RAII:C++ 最强思想(一劳永逸)

RAII = Resource Acquisition Is Initialization (资源获取即初始化)。翻译成人话:

"把资源(内存、文件、锁、连接)的获取放在对象构造时, 释放放在对象析构时。对象死了,资源自动还回去。"

{
    std::ofstream f("a.txt");   // 构造 = 打开文件
    f << "hello";               // 使用
}   // 离开作用域,f 析构 = 自动关闭文件(即使有异常)

智能指针就是 RAII 的代表。你写的类也应该遵循 RAII: 构造函数拿资源,析构函数还资源。这样:

这是 C++ 区别于 C 的最伟大之处。记住这句话: **new 对应 delete,但 RAII 让你永远不用记得配对。**

8.6 引用 vs 指针:一张表说清

引用 &指针 *
可空不能(必须绑定)可以 nullptr
可换绑不能(终身绑定)可以改指向
解引用自动要 *
用途参数、返回值可选对象、容器节点

铁律:能用引用就用引用;必须"没有东西"(可空)才用指针。 现代代码里裸指针只用来"指向但不拥有"(叫非拥有指针)。

8.7 悬空引用/指针:最阴险的 bug

int* p;
{
    int local = 5;
    p = &local;
}   // local 已销毁
// 现在 *p 是"悬空"的——地址还在,但东西没了。
// 可能打印 5,可能乱码,可能崩溃——都是"未定义行为"。

预防:

  1. 用智能指针,别存裸指针
  2. 别返回局部变量的地址/引用
  3. 存了指针/引用就要想清楚"它指向的东西谁负责活着"

8.8 其他裸指针知识点(了解)

int* const p:p 本身不能改(常指针)

本章小结(背下来)

练习题


  1. 用 unique_ptr 创建一个 vector,push 几个数再遍历。

(这题不会也要背答案:unique_ptr<vector<int>>)

  1. 用 shared_ptr 在两个变量间共享一个 string,验证引用计数

(use_count() 可以看)。

  1. 写出"手写 new/delete 的三种死法"(泄漏、悬空、异常)。
  2. 写一个 RAII 类 File:构造打开文件,析构关闭,验证自动关闭。
  3. 试着解引用 nullptr,观察崩溃(学 gdb 之前先感受一下)。