C语言函数的递归调用
简单的求和函数
- 定义一个函数求1到n的和,马上就想到用循环累加,这里用for循环,名称为sumf。
- 参数和返回值都采用无符号整型,防止累加的和超出范围,出现意外情况!!!
- 将无符号整型自定义为uint,其实就是一种重命名,加强代码的可读性。
代码如下:
/* filename: sum.c */
#include <stdio.h>
/* compile : gcc sum.c -o sum
run : ./sum */
/**/
typedef unsigned int uint;
/**/
uint
sumf (uint n)
{
uint tmp = 0;
for (uint i = 1; i <= n; i++)
tmp = tmp + i;
return tmp;
}
/**/
void
test_sum (void)
{
printf ("Sum 1 - 100: %u\n", sumf(100));
printf ("Sum 1 - 10000: %u\n", sumf(10000));
}
/**/
int
main (int argc, char *argv[])
{
test_sum ();
return 0;
}
/* --(:-O-:)-- */
编译运行,结果在意料之中:
songvm@ubuntu:~/works/xdn$ gcc sum.c -o sum
songvm@ubuntu:~/works/xdn$ ./sum
Sum 1 - 100: 5050
Sum 1 - 10000: 50005000
将参数10000改为100000,编译运行,结果如下:
songvm@ubuntu:~/works/xdn$ gcc sum.c -o sum
songvm@ubuntu:~/works/xdn$ ./sum
Sum 1 - 100: 5050
Sum 1 - 100000: 705082704
明显是结果超出表示范围了,解决办不将无符号整型改为无符号长整型!
- 自定义无符号长整型ulong
- 将printf输出中的转意符改为%lu
代码如下:
/* filename: sum.c */
#include <stdio.h>
/* compile : gcc sum.c -o sum
run : ./sum */
/**/
typedef unsigned long ulong;
/**/
ulong
sumf (ulong n)
{
ulong tmp = 0;
for (ulong i = 1; i <= n; i++)
tmp = tmp + i;
return tmp;
}
/**/
void
test_sum (void)
{
printf ("Sum 1 - 100: %lu\n", sumf(100));
printf ("Sum 1 - 100000: %lu\n", sumf(100000));
}
/**/
int
main (int argc, char *argv[])
{
test_sum ();
return 0;
}
/* --(:-O-:)-- */
编译运行,结果如预期:
songvm@ubuntu:~/works/xdn$ gcc sum.c -o sum
songvm@ubuntu:~/works/xdn$ ./sum
Sum 1 - 100: 5050
Sum 1 - 100000: 5000050000
用递归的方法写一个求和函数
- 参数还可以更大,这里就不做极限测试了,但要考虑到超出取值范围这种情况的出现!!!
- 不用循环也可以实现求和,就是用递归的方法!!!
- 说白了,递归就是函数调用自己本身,sum中再次调用sum,执行下去就会无限循环,所以一定要设置一个条件让函数跳出执行,返回一个值,就是当n等于1时返回1;
- 当再次调用sum时,参数n都减1,直到n等于1返回,这样就达到了循环的效果。
代码如下:
/* filename: sum.c */
#include <stdio.h>
/* compile : gcc sum.c -o sum
run : ./sum */
/**/
typedef unsigned long ulong;
/**/
ulong
sumf (ulong n)
{
ulong tmp = 0;
for (ulong i = 1; i <= n; i++)
tmp = tmp + i;
return tmp;
}
/**/
ulong
sum (ulong n)
{
if (n == 1) return 1;
return n + sum (n - 1);
}
/**/
void
test_sum (void)
{
printf ("Use for method \n");
printf ("Sum 1 - 100: %lu\n", sumf(100));
printf ("Sum 1 - 100000: %lu\n", sumf(100000));
printf ("--------------------\n");
printf ("Use recursion method \n");
printf ("Sum 1 - 100: %lu\n", sum(100));
printf ("Sum 1 - 100000: %lu\n", sum(100000));
printf ("--------------------\n");
}
/**/
int
main (int argc, char *argv[])
{
test_sum ();
return 0;
}
/* --(:-O-:)-- */
编译运行,结果如下:
songvm@ubuntu:~/works/xdn$ gcc sum.c -o sum
songvm@ubuntu:~/works/xdn$ ./sum
Use for method
Sum 1 - 100: 5050
Sum 1 - 100000: 5000050000
--------------------
Use recursion method
Sum 1 - 100: 5050
Sum 1 - 100000: 5000050000
--------------------
结果一样,方法不同
- 理解递归对编写函数代码来说非常重要,尤其是在数据结构和算法方面!!!
- 上面的sum函数使用了简单的递归调用,可以看出参数n越大,返回的表达式就越长,占用的程序运行资源就越多,对于递归次数较少的的应用来说是可行的。
- 如何改变表达式越来越长的这种情况呢,就是采用尾递归调用,使表达式的长度不变!!!
- 定义函数sumt,除原有参数n外,再加一个参数s,来保存累加结果,这个累加结果初始值定为1,当n等于1时直接返回s,否则每次调用s都要加n来继续累加,n则减1继续调用。
代码如下:
/* filename: sum.c */
#include <stdio.h>
/* compile : gcc sum.c -o sum
run : ./sum */
/**/
typedef unsigned long ulong;
/**/
ulong
sumf (ulong n)
{
ulong tmp = 0;
for (ulong i = 1; i <= n; i++)
tmp = tmp + i;
return tmp;
}
/**/
ulong
sum (ulong n)
{
if (n == 1) return 1;
return n + sum (n - 1);
}
/**/
ulong
sumt (ulong n, ulong s)
{
if (n == 1) return s;
return sumt (n - 1, s + n);
}
/**/
void
test_sum (void)
{
printf ("Use for method \n");
printf ("Sum 1 - 100: %lu\n", sumf(100));
printf ("Sum 1 - 100000: %lu\n", sumf(100000));
printf ("--------------------\n");
printf ("Use recursion method \n");
printf ("Sum 1 - 100: %lu\n", sum(100));
printf ("Sum 1 - 100000: %lu\n", sum(100000));
printf ("--------------------\n");
printf ("Use tail recursion method \n");
printf ("Sum 1 - 100: %lu\n", sumt(100, 1));
printf ("Sum 1 - 100000: %lu\n", sumt(100000, 1));
printf ("--------------------\n");
}
/**/
int
main (int argc, char *argv[])
{
test_sum ();
return 0;
}
/* --(:-O-:)-- */
编译运行,结果如预期:
songvm@ubuntu:~/works/xdn$ gcc sum.c -o sum
songvm@ubuntu:~/works/xdn$ ./sum
Use for method
Sum 1 - 100: 5050
Sum 1 - 100000: 5000050000
--------------------
Use recursion method
Sum 1 - 100: 5050
Sum 1 - 100000: 5000050000
--------------------
Use tail recursion method
Sum 1 - 100: 5050
Sum 1 - 100000: 5000050000
--------------------
菲波那契数列
- 数列中从第三项开始,每一项都是前两项之和。
- 即F(n) = F(n-1) + F(n-2),其中n >= 2
- 此处以1和1作为初始值: 1 1 2 3 5 8 13 21 …
- 定义递归函数fib和上面的sum雷同,一个参数n,直接返回fib(n-1)与fib(n-2)的和。
- 定义尾递归函数fibt,在fib的基础上加三个参数,rsa对应n-1的值,rsb对应n-2的值,n不变,idx每次加1,rsb赋值给rsa,rsb赋值rsa与rsb的和(即fib(n-1)+fib(n-2)),当idx等于n时,返回rsb的值。
代码如下:
/* filename: fib.c */
#include <stdio.h>
/* compile : gcc fib.c -o fib
run : ./fib */
/**/
typedef unsigned int uint;
typedef unsigned long ulong;
/* recursion call */
ulong
fib (ulong n)
{
if (n == 1 || n == 2) return 1;
return fib (n - 1) + fib (n - 2);
}
/* tail recursion call */
ulong
fibt (ulong n, ulong rsa, ulong rsb, ulong idx)
{
if (n == 1 || n == 2) return 1;
if (n == idx) return rsb;
return fibt (n, rsb, rsa + rsb, idx + 1);
}
/**/
void
test_fib (void)
{
for (int i = 1; i < 20; i++)
printf ("%lu ", fib (i));
printf ("\n--------------------\n");
for (int i = 1; i < 20; i++)
printf ("%lu ", fibt (i, 1, 1, 2));
printf ("\n--------------------\n");
}
/**/
int
main (int argc, char *argv[])
{
test_fib ();
return 0;
}
/* --(:-O-:)-- */
编译运行,菲波那契数列前20项结果如下:
songvm@ubuntu:~/works/xdn$ gcc fib.c -o fib
songvm@ubuntu:~/works/xdn$ ./fib
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181
--------------------
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181
--------------------
能看出尾递归还是有效率的
- 因为fib函数当n大于2时每次递归调用时都调用2次,而fibt函数每次递归调用只调用一次!!!
- 可以加个计数变量看一下都调用了多少次!!!
- 在函数外加一个静态变量count,初始值赋为0,在函数的第一行令count自增,这样在函数调用结束后输出count,查看函数调用次数。
- 再次计数时别忘了重置count为0!!!
代码如下:
/* filename: fib.c */
#include <stdio.h>
/* compile : gcc fib.c -o fib
run : ./fib */
/**/
typedef unsigned int uint;
typedef unsigned long ulong;
/* define counter */
static int count = 0;
/* recursion call */
ulong
fib (ulong n)
{
count++;
if (n == 1 || n == 2) return 1;
return fib (n - 1) + fib (n - 2);
}
/* tail recursion call */
ulong
fibt (ulong n, ulong rsa, ulong rsb, ulong idx)
{
count++;
if (n == 1 || n == 2) return 1;
if (n == idx) return rsb;
return fibt (n, rsb, rsa + rsb, idx + 1);
}
/**/
void
test_fib (void)
{
for (int i = 1; i < 20; i++)
printf ("%lu ", fib (i));
printf ("\nCount: %d\n--------------------\n", count);
count = 0;
for (int i = 1; i < 20; i++)
printf ("%lu ", fibt (i, 1, 1, 2));
printf ("\nCount: %d\n--------------------\n", count);
}
/**/
int
main (int argc, char *argv[])
{
test_fib ();
return 0;
}
/* --(:-O-:)-- */
编译运行,结果如下:
songvm@ubuntu:~/works/xdn$ gcc fib.c -o fib
songvm@ubuntu:~/works/xdn$ ./fib
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181
Count: 21871
--------------------
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181
Count: 172
--------------------
仅求前20项,fib函数就调用了21871次,而fibt只调用了172次,尾递归效率之高,出乎意料之外!!!
字符串输出函数
常用printf来输出字符串,putchar只能输出单个字符,可以考虑设计函数来递归调用通过putchar输出字符串(当然循环也可以)。
- 字符串以’\0’结尾,设计一个函数out_charactor递归调用,参数为字符指针,当遇到’\0’时输出换行符并退出,否则输出当前字符。
- 做为参数的字符指针每次自增,指向下一个字符,继续调用out_charactor。
- 写一个测试函数,定义两个字符串,一个是简单的hello world!,另一个是全部为0的字符数组。
- 向数组循环填入26个大写字母,填充10次。
- 最后分别输出这两个字符串!!!
代码如下:
/* filename: outchar.c */
#include <stdio.h>
/* recursion call */
void
out_charactor (char *pc)
{
if (*pc == '\0')
{
putchar ('\n');
return;
}
else
{
putchar (*pc);
pc++;
out_charactor (pc);
}
}
/**/
void
test_out_charactor (void)
{
char *str = "Hello world!";
char buf[261] = { 0 };
for (int i = 0; i < 10; i++)
for (int j = 0; j < 26; j++)
buf[i*26 + j] = 'A' + j;
out_charactor (str);
out_charactor (buf);
}
/**/
int
main (int argc, char *argv[])
{
test_out_charactor ();
return 0;
}
/* --(:-)-- */
编译运行,结果如下:
songvm@ubuntu:~/works/xdn$ gcc -g outchar.c -o outchar
songvm@ubuntu:~/works/xdn$ ./outchar
Hello world!
ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ
怎么样,会玩递归了吧?下一步研究一下求质数的方法!