指针声明必须初始化为nullptr,未初始化指针值随机;动态分配后需检查,释放后置nullptr防悬空;new/delete与malloc/free不可混用;数组须配对使用new[]/delete[];函数参数按是否可空选引用或指针;shared_ptr循环引用需weak_ptr破环;指针生命周期不得短于使用范围。

指针变量声明时必须初始化或明确赋值
不初始化的指针(尤其是局部指针)内容是随机内存地址,delete 或解引用会直接崩溃。很多新手以为 int* p; 是“空指针”,其实它只是未定义值,不是 nullptr。
- 声明即初始化:用
nullptr(C++11 起推荐),不要用NULL或0—— 后两者在函数重载场景可能引发歧义 - 动态分配后立刻检查:比如
int* p = new int(42); if (!p) return;,虽然现代系统极少失败,但嵌入式或资源受限环境仍需防御 - 避免“悬空指针”:释放后立即置为
nullptr,比如delete p; p = nullptr;,否则后续误用if (p)判断会失效(因为悬空指针非空但非法)
new / delete 和 malloc / free 不能混用
new 不仅分配内存,还会调用构造函数;delete 会调用析构函数。而 malloc / free 是纯 C 内存操作,不触发任何对象生命周期管理。混用会导致对象状态损坏、资源泄漏或未定义行为。
- 类对象必须用
new/delete:比如std::string* s = new std::string("hello"); delete s;—— 用free(s)不会调用析构函数,内部缓冲区不会释放 - 原始内存块可用
malloc,但别对它调用delete;反之亦然。编译器通常不报错,运行时崩得无声无息 - 数组要配对:用
new[]就必须用delete[],否则只调用第一个元素的析构函数,其余被跳过
指针和引用在函数参数中怎么选
传参选指针还是引用,关键看“是否允许传入空值”和“是否需要重新绑定”。引用本质是别名,天生非空且不可重绑;指针更灵活,但也更易出错。
- 如果函数逻辑上必须有有效对象(比如修改一个
std::vector),优先用std::vector&—— 编译器强制你传实参,不用写if (!v)检查 - 如果参数可选(比如日志回调可为空),必须用指针:
void process(int* opt_data),调用方可以传nullptr - 别为了“看起来安全”把所有参数都改成引用:比如
void swap(int& a, int& b)没问题,但void load_config(Config& cfg)如果内部可能做cfg = Config{};,就隐含了“修改原对象”的契约,调用方未必意识到
智能指针不是万能的,循环引用得手动破
std::shared_ptr 用引用计数管理生命周期,但两个对象互相持有对方的 shared_ptr,计数永远不归零,内存就漏了。这不是 bug,是设计使然。
立即学习“C++免费学习笔记(深入)”;
- 典型场景:树节点父子互持、观察者模式中主体与监听器双向引用
- 破环靠
std::weak_ptr:子节点存父节点用weak_ptr,访问前用lock()转成临时shared_ptr,既安全又不增加计数 - 别滥用
weak_ptr:它带来额外开销(控制块多一次原子操作),且每次访问都要判断是否已释放 —— 只在确认有循环风险时才引入
实际写代码时,最容易被忽略的是:指针的生命周期必须比它的使用范围长。哪怕语法全对,只要 int* p = &x; 中的 x 是函数局部变量,返回 p 就是野指针 —— 这种错误不报编译警告,调试器里看到的值还常常“碰巧”是对的。









