递归中用指针传参本质是地址传递,所有层级操作同一内存,可安全累计状态或构建路径;需确保指针生命周期覆盖全程,避免悬空、泄漏,并注意回溯还原与空指针检查。

递归中用指针传参,本质是传地址而非值
直接传指针(比如 int*)进递归函数,和传普通变量完全不同:你改的是同一块内存里的值,所有递归层级看到的都是实时变化后的状态。这比用全局变量或返回值拼接更可控,也比传引用在某些场景下更明确(比如需要区分“未初始化”和“零值”时)。
常见错误是把指针本身当成值来递归传递——比如写成 func(&x) 然后在函数里又取地址 &val,结果得到的是栈上临时变量的地址,一退出就悬空。
- 确保指针指向的内存生命周期覆盖整个递归过程(通常是栈上变量、堆分配内存,或静态存储)
- 不要在递归内部对指针做
new或malloc后不统一管理,容易泄漏 - 如果只是累加计数、收集路径、标记访问等,优先用指针;若需多返回值或复杂结构,考虑结构体指针或引用
遍历二叉树时用指针累计节点数
这是最典型的应用:不用全局变量,也不靠返回值层层回传,而是让每个调用都往同一个计数器里加 1。
void countNodes(TreeNode* root, int* count) {
if (!root) return;
(*count)++; // 直接修改
countNodes(root->left, count);
countNodes(root->right, count);
}
<p>// 调用方式:
int total = 0;
countNodes(root, &total); // &total 是 int* 类型
注意:count 是 int*,不是 int**;除非你要在递归中重新分配它(比如动态扩容数组),否则不需要二级指针。
立即学习“C++免费学习笔记(深入)”;
- 如果误写成
count++,那只是移动指针位置,不是累加数值 - 传入
nullptr指针会导致解引用崩溃,调用前务必检查 - 这个模式天然支持“提前终止”,比如加个
bool* shouldStop控制递归深度
用指针构建递归路径(vector 的地址传递)
DFS 路径搜索常需边递归边记录当前路径,用 std::vector<int>* 就很自然:
void dfs(TreeNode* root, vector<int>* path, vector<vector<int>>* result) {
if (!root) return;
path->push_back(root->val);
if (!root->left && !root->right) {
result->push_back(*path); // 保存一份拷贝
}
dfs(root->left, path, result);
dfs(root->right, path, result);
path->pop_back(); // 回溯:必须撤回,否则污染上层
}
这里 path 和 result 都是指针,避免了 vector 复制开销,也保证了所有递归帧操作的是同一份容器。
-
pop_back()不可省略,否则路径会不断累积,变成“所有节点按遍历顺序排成一长串” - 如果用
vector<int>&引用也能做到,但指针在接口上更清晰地表达了“可能为空”或“可选”的语义 - 多线程环境下不能共用同一指针,得各自传独立实例
为什么不用智能指针?什么时候该用 raw pointer?
在纯递归状态传递中,std::unique_ptr 或 std::shared_ptr 几乎没优势:它们解决的是所有权和生命周期管理问题,而递归中的状态指针通常指向调用者栈上的变量(如 &total)或明确由调用方 new 出、并在递归外 delete 的内存——所有权边界非常清楚。
用智能指针反而引入不必要的引用计数开销(shared_ptr)或转移语义干扰(unique_ptr 不能拷贝,无法直接传进多层递归)。
- 只有当你需要在递归中动态创建并移交状态对象(比如生成一棵新树),且不确定谁负责释放时,才考虑
unique_ptr<State> - 裸指针在这里不是“过时”,而是精准匹配需求:只传递访问权,不涉及所有权变更
- 编译器不会帮你检查空指针解引用,所以每次用
*ptr前最好加if (ptr),尤其接口开放给他人调用时
递归中用指针传状态的关键,在于始终清醒:你操作的不是副本,而是那个变量本身;而最容易被忽略的,是回溯时忘记还原指针所指内容的状态。


















