变量和地址

变量名对应一块存储空间,存储空间有地址。取地址运算符 & 可以得到变量地址,解引用运算符 * 可以读取或修改这个地址中的值。

int value = 42;
int* pointer = &value;

std::cout << value << '\n';
std::cout << *pointer << '\n';

*pointer = 43;
// value == 43

pointer 保存地址,*pointer 才是地址对应的对象。指针类型决定了读取多少字节,以及指针运算如何移动。

指针和引用

函数参数的写法需要表达调用条件。引用绑定到一个已经存在的对象,调用时不需要额外判断。指针可以是空值,适合表示对象可能不存在的情况。

void swap_values(int& left, int& right) {
  const int old = left;
  left = right;
  right = old;
}

int first = 3;
int second = 5;
swap_values(first, second);

如果函数接收指针,需要在解引用前检查 nullptr。接口中是否允许空指针,应当在函数名、参数类型或文档中保持一致。

动态对象的所有权

使用 new 创建对象后,程序必须在合适的位置调用 delete。手动管理容易出现忘记释放、重复释放和异常路径泄漏。现代 C++ 优先使用智能指针表达所有权。

#include <memory>

auto lesson = std::make_unique<Lesson>();
lesson->set_title("Pointers and memory");

// lesson 离开作用域后自动销毁对象

std::unique_ptr 表示单一所有者,不能复制,只能移动。确实需要多个所有者时再考虑 std::shared_ptr,并确认共享关系不会形成循环引用。

悬空指针

对象销毁后,原来的地址仍然可能保存在指针中,这个指针称为悬空指针。继续使用它属于未定义行为。

int* dangling = nullptr;
{
  int local = 10;
  dangling = &local;
}

// local 已经销毁,不能再使用 dangling

返回局部变量地址、保存临时对象引用、删除对象后继续使用原指针,都会产生同类问题。将对象的生命周期限制在使用范围内,并让所有权类型负责销毁,可以减少这类错误。

练习

写一个函数,接收两个整数的引用并交换它们。再写一个版本,接收两个指针并处理空指针。分别传入有效地址和 nullptr,记录输出结果。

完成后使用 GDB 设置断点,观察两个参数的地址和值。再把动态创建的对象改写为 std::unique_ptr,比较程序退出前后的析构调用。

下一篇,std::vector 的常用操作 返回文章列表