您观察到的行为:
真正令人困惑的部分是它不会在每次递归函数调用自身时将其重新初始化为 0。这就像整行代码 static int i = 0; 被跳过?
正是您对静态的要求。局部静态变量在第一次达到其定义(即行static int i = 0;)时被初始化。
在您的情况下,这意味着它只会在程序的整个运行时第一次调用此方法时设置为零。没有对同一个函数进行多次递归调用的概念,因此如果该方法是由自身调用(您所指的多次递归调用)或者如果您在其他地方开始一个全新的递归堆栈,则没有区别你的客户代码。
可能的解决方案
回到您的描述,它只适用于 i 是静态的(对于 n!=1),因为如果您删除了static关键字,那么每次输入该方法时 i 将被初始化为零( i 的不同本地实例对于方法的每次调用)。因此在你的情况下
if(++i == n)
++i将始终评估为 1,与递归深度无关。
如果您希望每次在客户端代码中调用您的方法时都重新初始化静态 i (即开始一个新的递归堆栈),您可以这样编写:
void printNthFromLast(Node* head, int n, bool reinit=true)
{
static int i = 0;
if(reinit)
{
i=0;
}
if(head == nullptr)
return;
printNthFromLast(head->next, n, false);
if(++i == n)
cout << head->data;
}
带有尾调用优化的线程安全
更简洁的解决方案是将 i 作为函数的可变参数,因此您的函数将是线程安全的。并且通过更好的操作顺序,您无需保存先前的调用帧,从而享受大多数最新编译器提供的尾调用优化。
编辑:正如 Matthieu 所指出的,我最初的实现打印了第 N 个元素而不是最后一个元素的第 N 个。在启用 TCO 的同时进行修复不太优雅,但可以按如下方式完成:
/// n==1 means printing the last element here
static void printNthFromLastImpl(const Node* currentNode, const Node* offsetNode, const int n, int currentDepth)
{
// n == 0 will never print in the original example by op, so forbid it
assert(n);
if(++currentDepth > n)
{
offsetNode = offsetNode->next;
}
if(currentNode->next)
{
currentNode = currentNode->next;
}
else
{
if(currentDepth >= n)
{
cout << offsetNode->data;
}
return;
}
return printNthFromLastImpl(currentNode, offsetNode, n, currentDepth);
}
/// \brief : code to call from client
static void printNthFromLast(const Node* head, const int n)
{
if (head)
{
printNthFromLastImpl(head, head, n, 0);
}
}