递归 解剖

调用函数时会发生什么情况?

每个函数(包括main)的状态是由函数中所有自动变量的内容,函数参数的值,表明在调用函数的何处重新开始的返回地址决定的。包含所有这些信息的数据区称为活动记录(activation record)或者栈结构(stack frame)。他是在运行时栈(run-time stack)上分配空间的。只有函数正在执行,他的活动记录就一直存在这个记录是函数的私有信息池,它存储了程序正确执行并正确返回到调用它的函数所需的所有信息。活动记录的寿命一般很短,因为他们在函数开始执行时得到动态分配的空间,在函数退出时就释放其空间,只有main()的活动记录的寿命比其他活动记录常。

活动记录通常包含以下信息:

1.函数所有参数的值,如果传送的是数组或变量按引用传递,则活动记录包含的是该数组第一个单元的地址或者该变量的地址,所有其 他数据元素的副本。

2.可以存储在其他地方的局部变量(自动变量),活动记录只包含他们的描述法和指向存放他们位置的指针。

3.回到调用程序的返回地址,即在调用完成之后紧接着此函数调用指令之后的指令地址

4一个指向调用程序的活动记录的动态链接指针

5.非void类型的函数返回值,活动记录空间的大小随调用不同而不同,返回值在调用程序的活动记录最上方。

外加一篇文章:

一个由C/C++编译的程序占用的内存分为以下几个部分:

1、栈区(stack-由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

2、堆区(heap-一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。

3、全局区(静态区)(static-,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后有系统释放

4文字常量区-常量字符串就是放在这里的。程序结束后由系统释放

5、程序代码区-存放函数体的二进制代码。

文章结束

上面提到,如果函数由主函数main()或者 其他函数调用,它的活动记录就在运行时栈中建立,运行时栈总是反映函数的当前状态,假如,main函数调用f1,f1调用f2,f2调用f3,如果f3正在运行,运行时栈中的状态如下,根据栈的特性,如果函数f3的活动记录从栈中弹出,并将栈指针移到紧挨着函数f3的返回值的下方,然后f2继续执行,并能自由访问它重新执行所需的私有信息池,另一方面,如果f3调用f4,运行时栈的空间将增大,因为f4的活动记录在该栈上建立,而函数f3将暂停执行

递归 解剖

无论何时调用函数都会创建一个活动记录,因此系统可以正确地递归问题递归就是调用的函数名称正好和调用者相同,因此,递归调用不是表面上的函数自身调用,而是一个函数的实例调用同一个函数的另一个实例,这些调用在内部表示为不同的活动记录,并有系统区分。

尾部递归

特点是:在每个函数实现的末尾只使用一个递归调用,也就是说,当进行调用时,函数中没有其他剩余的语句要执行,递归调用不仅是最后一条语句,而且在这之前也没用其他直接或间接的递归调用,例如tail()定义如下:

void tail(int i)
{
    if(i>0)
    {
        cout<<i<<ends;
        tail(i-1);
    }
    cout<<endl;
}

这就是一个尾部递归的例子,下面的不是尾部递归:

void nonTail(int i)
{
    if(i>0)
    {
        nonTail(i-1);
        cout<<i<<ends;
        nonTail(i-1);
    }
}

nonTail就不是尾部递归,思考nonTail(3)输出什么?1 2 131 2 1.尾部递归只是一个变形的循环,很容易用循环来代替,在这个例子中,可以写成:

void iterativeEquivalentOfTail (int i)
{
     for(; i>0;i--)
         cout<<i<<ends;
}

非尾部递归

可以用递归实现的一个问题是将输入行以相反的顺序打印出来:

void reverse()
{
    char ch;
    cin.get(ch);
    if(ch!='n')
    {
        reverse();
        cout.put(ch);
    }
}

图显示了reverse第一次递归调用它自身之前运行时栈的内容。

递归 解剖

采用非递归形式调用如下:

void iterativeReverse()
{
    char stack[80];
    int top=0;
    cin.get(stack[top]);
    while(stack[top]!='n')
       cin.get(stack[++top]);
    for(top-=1;top>=0;cout.put(stack[top--]));
}

注意数组使用变量名stack,是为了将系统隐含的完成的工作展示出来,这个栈取代了运行时栈的功能,这里使用它很有必要,因为一个简单的循环是不够的,如在尾部递归中那样

无论采用哪种方式,将非尾部递归形式转换为迭代形式都需要对栈进行显示处理,而且,由递归转为非递归,程序清晰度降低。

函数所做的最后一件事情是一个函数调用(递归的或者非递归的),这被称为 尾部调用(tail-call)使用尾部调用的递归称为尾部递归。让我们来看一些函数调用示例,以了解尾部调用的含义到底是什么:

int test1()
{
    int a = 3;
    test1(); /* recursive, but not a tail call.  We continue */
             /* processing in the function after it returns. */
    a = a + 4;
    return a;
}
int test2()
{
    int q = 4;
    q = q + 5;
    return q + test1(); /* test1() is not in tail position.
                         * There is still more work to be
                         * done after test1() returns (like
                         * adding q to the result
                         */
}
int test3()
{
    int b = 5;
     b = b + 2;
     return test1();  /* This is a tail-call.  The return value
                      * of test1() is used as the return value
                      * for this function.
                      */
}
int test4()
{
    test3(); /* not in tail position */
    test3(); /* not in tail position */
    return test3(); /* in tail position */
}

可见,要使调用成为真正的尾部调用,在尾部调用函数返回之前,对其结果 不能执行任何其他操作

注意,由于在函数中不再做任何事情,那个函数的实际的栈结构也就不需要了。惟一的问题是,很多程序设计语言和编译器不知道 如何除去没有用的栈结构。如果我们能找到一个除去这些不需要的栈结构的方法,那么我们的尾部递归函数就可以在固定大小的栈中运行。
•Is the factorial method a tail recursive method?int fact(int x){if (x==0)return 1;elsereturn xfact(x-1);}*••When returning back from a recursive call,there is still one pending operation,multiplication.••Therefore, factorial is a non-tail recursive method.转成尾部递归:

factorial1(n, accumulator) {
    if (n == 0) return accumulator;
    return factorial1(n - 1, n * accumulator);
  }
factorial(n) {
    return factorial1(n, 1);
  }

深入:

精通递归程序设计http://www.ibm.com/developerworks/cn/linux/l-recurs.html


原文链接: https://www.cnblogs.com/youxin/archive/2012/07/22/2603815.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/56330

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月9日 上午7:15
下一篇 2023年2月9日 上午7:17

相关推荐