我遇到了一些我以前没见过的东西。我有以下递归函数,只有在i
是静态
void printNthFromLast(Node* head, int n) {
static int i = 0;
if(head == nullptr)
return;
printNthFromLast(head->next, n);
if(++i == n)
cout << head->data;
}
我假设此上下文中的static意味着在对同一函数的多次递归调用中共享 printNthFromLast
?
真正令人困惑的部分是,每次递归函数调用自身时,它都不会将其重新初始化为0。这就像是跳过整行代码static int i = 0;
?
有人可以向我解释一下吗?
答案 0 :(得分:2)
您观察到的行为:
真正令人困惑的部分是,每次递归函数调用自身时,它都不会将其重新初始化为0。这就像整行代码static int i = 0;被跳过?
正是你要求静态的。本地静态变量初始化第一次其定义(即,行 static int i = 0; )已达到。
在您的情况下,这意味着只有在程序的整个运行时期间对此方法的第一次调用才会将其设置为零。没有概念对同一个函数的多次递归调用,所以如果方法是由它自己调用的(你所指的是多次递归调用),或者你是在开始一个整体,那就不会有任何区别了。客户端代码中其他地方的新堆栈递归。
要返回您的说明,它只适用于我静态(对于n!= 1),因为如果您删除了静态关键字,那么我会每次输入方法时初始化为零(每次调用方法时,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);
}
}
答案 1 :(得分:2)
当你声明一个static
局部变量时,编译器只初始化它一次(控制流第一次通过变量声明);此后,变量在对函数的所有调用中保持其值,该函数在程序的生命周期内声明它,就像全局变量一样。
当编译器看到类似这样的内容时:
void foo() {
static int i = 0;
cout << i++;
}
它产生的代码相当于:
bool foo_i_initialized = false; // global
int foo_i; // global
void foo() {
if (!foo_i_initialized) {
foo_i = 0;
foo_i_initialized = true;
}
cout << foo_i++;
}
上面的例子有点人为,因为foo_i
是一个用常量初始化的原语,因此它可以在全局范围内静态初始化(不需要布尔标志),但这个机制也是处理更复杂的场景。
答案 2 :(得分:1)
初始化仅在第一次调用函数时执行。后续调用将使用已经初始化的值。
答案 3 :(得分:1)
所以,如上所述,static int i = 0;
是函数的本地。它是在流量控制第一次通过其定义时初始化的,并且在此之后被跳过。有两个特例:
现在,关于你的代码:不要。本地静态是伪装的全局变量,您的代码等同于:
int i = 0;
void printNthFromLast(Node* head, int n) {
if(head == nullptr)
return;
printNthFromLast(head->next, n);
if(++i == n)
cout << head->data;
}
注意:不仅调试起来比较困难,而且它也不是线程安全的。
相反,您应该为此用法提供一个局部变量:
static void printNthFromLastImpl(Node const* const head, int& i, int const n) {
if(head == nullptr)
return;
printNthFromLastImpl(head->next, i, n);
if(++i == n)
cout << head->data;
}
// Each call to this function instantiates a new `i` object.
void printNthFromLast(Node const* const head, int const n) {
int i = 0;
printNthFromLastImpl(head, i, n);
}
编辑:正如Ad N所述,由于列表未被修改,因此应该通过const
指针传递。
遵循Ad N最新编辑(TCO版本),我意识到迭代实现应该有效(注意,可能有一些错误)。
void printNthFromLast(Node const* const head, int const n) {
if (n == 0) { return; }
// Set "probe" to point to the nth item.
Node const* probe = head;
for (int i = 0; i != n; ++i) {
if (probe == nullptr) { return; }
probe = probe->next;
}
Node const* current = head;
// Move current and probe at the same rhythm;
// when probe reaches the end, current is the nth from the end.
while (probe) {
current = current->next;
probe = probe->next;
}
std::cout << current->data;
}
答案 4 :(得分:0)
你自己已经很好地描述了它。静态变量只初始化一次,第一次通过函数,然后变量在函数调用之间共享。
答案 5 :(得分:0)
声明为static的变量只初始化一次。因此,即使再次调用该函数,此上下文中的变量i的值也会保留在上一次调用中。
下次程序到达初始化变量的静态初始化语句时,它会跳过该语句。
由于静态变量存储在BSS段而不是堆栈中,因此先前函数调用中变量的先前状态不会改变。
答案 6 :(得分:0)
不仅在多个递归调用之间共享,在所有调用之间共享。
实际上只有一个变量i
的实例,它在函数的所有调用中共享。