我对return
和递归函数有疑问。
这再次基于我目前正在处理的二叉树。代码是
void Tree::display()
{
if( !root_ )
return;
display_r(root_);
}
void Tree::display_r(Tree *node)
{
if( 0 == node )
return;
display_r(node->left_);
std::cout << node->value_ << std::endl;
display_r(node->right_);
}
这是有效的代码。编译并运行,从最小到最大打印数字。但是,这不是原来的。
上面的代码首先用
编写return display_r(node->left_);
std::cout << node->value_ << std::endl;
return display_r(node->right_);
哪个不起作用。它只是返回而不打印任何东西。这是有道理的,返回不允许代码向下移动。
这给我带来了一个有趣的问题。在编写树时,我经常想知道它是否是在递归函数中使用return
的好地方。显然,只要return
是在代码块中执行的最后一个命令就可以使用。我认为在display()
函数中使用
void Tree::display()
{
if( !root_ )
return;
return display_r(root_);
}
所以我的问题是:我什么时候可以确定我可以使用return
,何时不应该使用它?是否有灰色区域由我决定什么是最好的,是否有安全网?如同,“如果有疑问,不要在递归函数中使用返回值?”
谢谢!
答案 0 :(得分:12)
我建议更仔细地研究return关键字,并进一步练习递归。
return display_r(node->left_);
// this following code would not be executed in your example,
// you've already returned out of the function!
std::cout << node->value_ << std::endl;
return display_r(node->right_);
这里必须返回:
if( 0 == node )
return;
...因为这是递归算法的基本情况(也就是一般解决方案)。当您遇到孩子的空值时,您会停止,否则继续。请注意,此代码是if语句的一部分。它仅在某些情况下执行(恰好是您希望过早退出函数并停止递归的情况)。
在您的特定情况下,您也可以在不使用返回的情况下编写此内容,并且非常容易:
void Tree::display_r(Tree *node)
{
if (node) // equivalent to if (node != 0)
{
display_r(node->left_);
std::cout << node->value_ << std::endl;
display_r(node->right_);
}
}
顺便说一句,没有任何意义可以冒犯,看起来好像你是在借用例子而不太了解它们是如何运作的。尝试自己思考并使用代码并尝试理解它。如果需要,请在每条指令旁边添加注释,以便以您可以理解的方式表明它的作用。
也尝试学习调试器;我不能强调这一点。许多大学生在没有被教导如何使用调试器的情况下完成整个本科学位,这真是一种耻辱。它应该是最早教授的东西之一!使用调试器跟踪代码将真正帮助您查看您编写的代码的行为。如果您没有被教导如何使用它,我建议您自己学习如何使用它。它将向您展示机器如何逐步完成您编写的每一行代码。
答案 1 :(得分:4)
您只是使用您的返回,作为停止执行该功能的方法。为什么不这样做?
void Tree::display_r(Tree *node)
{
if(node)
{
display_r(node->left_);
std::cout << node->value_ << std::endl;
display_r(node->right_);
}
}
然后,如果没有节点,则不执行任何操作。
您的所有功能都属于void
类型,这意味着它们不会返回任何内容。基本上你应该只使用return来停止这个功能。
这就是为什么你的回报都没有按你认为的那样工作的原因,因为按定义的void函数会返回void,换句话说,它们不能返回任何东西。
您可以返回整数,指针或其他任何内容,但必须声明它,例如
int function() {
return 3;
}
此外,当您使用return调用函数时,通常会将变量设置为等于返回的变量。从上面:
x = function();
<强>相切强>
实际上,您可以在void函数中使用return语句来执行void函数。这通常不是一个好主意。这是一个简单的例子:
void yeah()
{
cout << "yeah\n";
}
void test()
{
return yeah();
}
一个简单的无限递归循环:
void test()
{
// !!!Caution!!! This will produce an infinite loop!
return test();
}
只要你在返回之后写的东西什么都没有(就像一个void函数)这个就行了,return cout<<"yeah\n"
就不会编译任何一个函数,因为你调用的cout的成员函数不是无效功能。
答案 2 :(得分:3)
return display_r(node-&gt; left_);
std :: cout&lt;&lt; node-&gt; value_&lt;&lt;的std :: ENDL;
return display_r(node-&gt; right_);
此代码不起作用,因为在第一次调用(递归)左子节点上的display_r后,您从函数返回,因此从不显示打印,并且右子节点的display_r也都没有。基本上会发生的是,在所有左节点上都会对该函数进行回调调用,直到没有左子节点的节点为止,然后所有返回的调用都返回而不会打印这些值。
现在,为了回答你的问题,在递归函数中使用return不仅很好,它(通常)是必不可少的算法的“停止机制”。但是,您不希望从display_r返回值,因为它不返回任何内容(void)。 此外,使用“return XXX”令人困惑和错误。关于void函数。这意味着他们会返回某种类型的价值。因为在我们的情况下,display_r只返回一个void,然后这不会产生编译错误,但通常,使用“return”是正确的形式。 (当没有任何函数或值后调用)返回空格时。 在你的情况下,递归函数的停止机制是当你“显示”一个空节点时,所以第一个返回是唯一的一个nessesary。
答案 3 :(得分:2)
return
退出当前函数,因此有两个return
语句不受程序控制块的影响,如下所示:
return x; return y;
永远只会导致x返回。
C / C ++本身并不真正支持协同例程/ yield返回(这可能是你在有两个return语句时所期望的)
答案 4 :(得分:1)
我总是觉得在编写递归函数时考虑数学归纳是有帮助的:你需要一个基础案例和一个归纳案例来实现归纳。递归并没有太大的不同:你需要一个方法/例程/函数来解决基本案例的问题,你需要一个可以处理基本案例的方法/例程/函数。
换句话说,你需要能够终止递归函数的东西,并且你需要能够使当前输入处于终止状态的东西。
一旦考虑到这一点,就会更容易想到返回语句的位置:
当你的所有函数都返回'void'时,在这种情况下,它们返回的内容并不重要。如果将“return”语句放在自己的行上,您可能会更容易查看代码。 (您的代码几乎给人的印象是'return'被用作'goto'或'call'语句。)
当你的函数返回数据(而不是执行副作用,比如打印)时,你可能会更清楚返回语句必须去哪里:它们必须在你计算出案例所需的数据之后你在做什么,无论是基础案例还是归纳案例。
您可能会发现使用树结构学习“广度优先搜索”和“深度优先搜索”以及“预订”,“按顺序”和“后订购”树遍历会很有帮助。一开始可能听起来令人生畏,但迟早它会全部点击,递归功能对你来说会容易得多。 :)
答案 5 :(得分:1)
你也可以考虑在语言和/或环境中练习递归,当你犯这样的错误时会给你警告。例如,Eclipse和Java将在第一个返回语句之下的任何内容上给出一个简洁的“无法访问代码”错误。
至于递归函数,你只需要记住它给它三个部分:
当然,在某些情况下可以省略该动作,并且可以互换第二和第三步骤。一个简单的计数器将在伪代码中创建:
[代码] int counter(int count){ if(count == 0)return; //停止条件 其他计数器(count--); // Action(count -1)和递归调用(counter())。 } [/代码]
我确信这三个步骤的官方名称更多。