目录
[1.1 嵌套调用:](#1.1 嵌套调用:)
[1.2 函数的链式访问:](#1.2 函数的链式访问:)
[2.1 函数的声明:](#2.1 函数的声明:)
[2.2 函数的定义:](#2.2 函数的定义:)
[2.3 函数的声明和定义例子:](#2.3 函数的声明和定义例子:)
[2.4 模块化编程思想:](#2.4 模块化编程思想:)
[3.1 什么是递归?](#3.1 什么是递归?)
[3.2 递归的两个必要条件](#3.2 递归的两个必要条件)
[3.3 递归练习:](#3.3 递归练习:)
[3.3.1 接受一个整型值(无符号),按照顺序打印它的每一位。](#3.3.1 接受一个整型值(无符号),按照顺序打印它的每一位。)
[4.1 计算n的阶乘(不考虑溢出):](#4.1 计算n的阶乘(不考虑溢出):)
[4.2 有些情况递归不一定好用](#4.2 有些情况递归不一定好用)
1.函数的嵌套调用和链式访问
函数和函数之间可以根据实际的需求进行组合的,也就是互相调用的。
1.1 嵌套调用:
cs
#include <stdio.h>
void new_line()
{
printf("hehe\n");//最后在这里打印
}
void three_line()
{
int i = 0;
for(i=0; i<3; i++)
{
new_line();//这里调用new_line()函数
}
}
int main()
{
three_line();//这里调用three_line()函数
return 0;
}
这个就是函数的嵌套调用。注意:是嵌套调用,不是嵌套定义,每个函数的地位是相等的。
1.2 函数的链式访问:
把一个函数的返回值作为另外一个函数的参数
cs
#include <stdio.h>
#include <string.h>
int main()
{
char arr[20] = "hello";
int ret = strlen(strcat(arr,"bit"));//这里介绍一下strlen函数
printf("%d\n", ret);//结果为1,strcat函数的作用是将两个字符串连接,变成了char att[20]="hellobit",所以strlen计算结果为8
return 0;
}
cs
#include <stdio.h>
int main()
{
printf("%d", printf("%d", printf("%d", 43)));//printf函数的返回值是打印在屏幕上字符的个数
//打印1,返回值为1 //打印2,返回值为1 //打印43,返回值为2
//最终打印为4321
return 0;
}
补充函数知识:函数不写返回值的时候,默认返回类型int
2.函数的声明和定义
2.1 函数的声明:
-
告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,函数 声明决定不了。
-
函数的声明一般出现在函数的使用之前。要满足先声明后使用。
-
函数的声明一般要放在头文件中的。
2.2 函数的定义:
函数的定义是指函数的具体实现,交待函数的功能实现。
2.3 函数的声明和定义例子:
cs
#include <stdio.h>
//编译器扫描代码是从上到下
//当在一个工程里面时,自定义函数在主函数的下面时,要在主函数的全面声明自定义函数
int Add(int x,int y);//函数的声明
int main()
{
int a = 0;
int b = 0;
scanf("%d %d",&a,&b);
//加法
int sum = Add(a,b);
printf("%d\n",sum);
return 0;
}
int Add(int x,int y)//函数的定义
{
return x + y;
}
2.4 模块化编程思想:
模块化编程,必须要一个主函数、自定义函数的.c文件和自定义函数的.h文件。
.h文件:
放置函数的声明
cs
#ifndef __TEST_H__
#define __TEST_H__
//函数的声明
int Add(int x, int y);
#endif //__TEST_H__
.c文件:
放置函数的实现
cs
#include "test.h"
//函数Add的实现
int Add(int x, int y)
{
return x+y;
}
这里的模块化编程思想,后续我们会详细讲解并应用
3.函数的递归:
3.1 什么是递归?
递:函数内部自己给自己传递参数 归:当函数自我传参完后,回归主函数
程序调用自身的编程技巧称为递归( recursion)。
递归做为一种算法在程序设计语言中广泛应用。
一个过程或函数在其定义或说明中有直接或间接 调用自身的 一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解, 递归策略 只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。
递归的主要思考方式在于:把大事化小
3.2 递归的两个必要条件
存在限制条件,当满足这个限制条件的时候,递归便不再继续。
每次递归调用之后越来越接近这个限制条件。
3.3 递归练习:
3.3.1 接受一个整型值(无符号),按照顺序打印它的每一位。
例如:输入:1234,输出 1 2 3 4
cs
#include <stdio.h>
void print(int n)
{
if(n>9)
{
print(n/10);
}
printf("%d ", n%10);
}
int main()
{
int num = 1234;
print(num);
return 0;
}
接下来我们对于这题进行画图讲解:







3.3.2编写一个函数不允许创建临时变量,求字符串的长度
这题的意思就是求字符串的长度(strlen),模拟实现strlen
非递归方法:
cs
#include <stdio.h>
//int my_strlen(char str[])//参数部分写出数组的形式
int my_strlen(char* str)//参数部分写出指针的形式
{
int count = 0;//计数,临时变量
while (*str != '\0')
{
count++;
str++;//找下一个字符
}
return count;
}
int main()
{
char arr[] = "abc";//[a b c \0]
int len = my_strlen(arr);
printf("%d\n", len);
return 0;
}
*/
递归方法:
cs
#include <stdio.h>
//my_strlen("abc");
//1+my_strlen("bc");
//my_strlen("c");
//my_strlen(" ");
//1+1+1+0
int my_strlen(char* str)
{
if (*str != '\0')
{
return 1 + my_strlen(str+1);
}
else
{
return 0;
}
}
int main()
{
char arr [] = "abc";
int len = my_strlen(arr);
printf("%d\n", len);
return 0;
}
接下来我们对于这题进行画图讲解:



4.函数的递归和迭代
4.1 计算n的阶乘(不考虑溢出):
递归方法:
cs
#include <stdio.h>
int fac(int n)
{
if (n <= 1)
{
return 1;
}
else
{
return n * fac(n - 1);
}
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = fac(n);
printf("ret = %d\n", ret);
return 0;
}
迭代方法:
cs
#include <stdio.h>
int fac(int n)
{
int i = 0;
int ret = 1;
for (i = 1;i <= n;i++)
{
ret *= i;
}
return ret;
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = fac(n);
printf("ret = %d\n", ret);
return 0;
}
4.2 有些情况递归不一定好用
例如:求第n个斐波那契数(不考虑溢出)

递归方法(递归计算会重复计算很多次,效率低):
cs
#include <stdio.h>
int Fib(int n)
{
if (n <= 2)
{
return 1;
}
else
{
return Fib(n - 1) + Fib(n - 2);
}
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = Fib(n);
printf("%d\n", ret);
return 0;
}
迭代方法(效率高):
cs
#include <stdio.h>
int Fib(int n)
{
int a = 1;
int b = 1;
int c = 0;
while (n >= 3)
{
c = a + b;
a = b;
b = c;
n--;
}
return c;
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = Fib(n);
printf("%d\n", ret);
return 0;
}

5.对于迭代和递归的使用一些提示
许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更为清晰。
但是这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些。
当一个问题相当复杂,难以用迭代实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开销。
6.对于解决一些递归栈溢出的问题:
将递归改写成非递归。
使用static对象替代 nonstatic 局部对象。在递归函数设计中,可以使用 static 对象替代nonstatic 局部对象(即栈对象),这不仅可以减少每次递归调用和返回时产生和释放 nonstatic 对象的开销,而且 static 对象还可以保存递归调用的中间状态,并且可为各个调用层所访问。
补:之前对栈解释过了,下一章会对函数的栈帧的创建和销毁进行讲解。
7.总结
递归三步走:
1.定义函数
2.设置终止条件
3.找到等价关系式