前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C函数与递归

C函数与递归

作者头像
WuShF
发布2023-03-08 09:47:45
4380
发布2023-03-08 09:47:45
举报
文章被收录于专栏:笔记分享

函数的特性及定义

在编程语言中,可以把函数看做一个盒子,这个盒子有如下几个特性:

  • 开始执行时,函数可以被输入一些值
  • 执行过程中,函数可以做一些事情
  • 执行完成后,函数可以返回一些值

函数的写法公式:

代码语言:javascript
复制
函数返回值类型 函数名(函数输入参数值)
{
    做点什么事情
    return 函数返回值;
}

被花括号包括的被称为函数体,注意函数体一定要被花括号包括且不可省略。 花括号上面的函数名、函数参数及返回值被称作函数头

注意每个输入参数必须指明其变量类型,不能省略变量类型。

代码语言:javascript
复制
int add(int a, int b)   //正确
int add(int a, b)       //错误

函数的调用

函数需要被另一个函数调用才能执行。

代码语言:javascript
复制
#include <stdio.h>
int add(int a, int b)
{
	return a + b;
}
int main()
{
	int result;
	result = add(2, 3); // 函数调用
	printf("%d", result);
	return 0;
}

main被称作主调函数,add被称作被调函数。 在add函数头中,标明了函数的返回值类型为int,说明这个函数被调用后将返回一个int类型的结果

为什么要将代码封装成函数?

如果程序需要多次完成某项任务,那么你有两个选择:

  • 将同样的代码复制多份。
  • 将代码封装为一个函数,在需要的地方调用这个函数。

函数返回

代码语言:javascript
复制
#include <stdio.h>
void showStarts()
{
	printf("    *\n");
	printf("   * *\n");
	printf("  * * *\n");
	printf(" * * * *\n");
	printf("* * * * *\n");
}
int main()
{
	showStarts();
	return 0;
}

showStarts 函数将会打印一个星星组成的三角形。这个函数没有输入参数,也不需要返回值。 所以,在函数定义时,参数的括号留空。返回值类型为 void ,表示空类型,即没有返回值。 可以在函数参数的括号中填上void,明确表示函数不需要参数。 可以用return将函数返回主调函数,并带回一个返回值。对于没有返回值的函数,可以省略return。函数运行完花括号内的语句后,就自动结束。 若函数需要返回值,则必须使用return带回一个返回值才能正常通过编译。 return可以出现在函数的任意位置。一旦程序执行到return,就会停止函数的执行,返回主调函数。

函数声明

在一个源文件中,如果函数调用前没有函数定义。那么可以使用函数声明通知编译器,有这个函数存在。 函数声明的写法非常简单:函数头 + 分号 函数声明也被称作函数原型

代码语言:javascript
复制
#include <stdio.h>
#include <math.h>
// 函数调用前加上了函数声明,告诉编译器有这个函数存在
double areaOfTriangle(double a, double b, double c);
int isTriangle(double a, double b, double c);
int main()
{
	double a, b, c;
	scanf("%lf %lf %lf", &a, &b, &c);
	if (isTriangle(a, b, c) == 0)
	{
		printf("Not a triangle\n");
		return 0;
	}
	double s;
	s = areaOfTriangle(a, b, c);
	printf("area of triangle is %f", s);
	return 0;
}
// 函数定义放到了函数调用后
double areaOfTriangle(double a, double b, double c)
{
	double p, s;
	p = (a + b + c) / 2;
	s = sqrt(p * (p - a) * (p - b) * (p - c));
	return s;
}
int isTriangle(double a, double b, double c)
{
	if (a + b > c && a + c > b && b + c > a)
	{
		return 1;
	}
	return 0;
}

编译器读到函数声明后,便可知晓 areaOfTriangle 与 isTriangle 的参数与返回值。 在其后的函数调用中,可以根据函数声明的形式,检查参数类型和个数是否传递正确。返回值是否被正常接收。 虽然编译器暂时不知道函数里面是如何定义的,但是这对于检查函数调用是否正确已经足够了。

另外,函数声明时可以省略变量名。从这里可以看出,参数变量名对于函数声明来说并不重要,即使函数声明使用与函数定义不一样的参数变量名,也是可以的。

代码语言:javascript
复制
double areaOfTriangle(double a, double b, double c);
int isTriangle(double a, double b, double c);
// 省略参数变量名
double areaOfTriangle(double, double, double);
int isTriangle(double, double, double);
// 乱写参数变量名
double areaOfTriangle(double xsie, double sgrb, double xvdc);
int isTriangle(double aooj, double bngb, double vfhfc);

参数与返回值的类型自动转换

代码语言:javascript
复制
#include <stdio.h>
int add(int a, int b)
{
	return a + b;
}
int main()
{
	int result;
	result = add(2, 3); // 函数调用
	printf("%d", result);
	return 0;
}

add函数参数列表中的a,b被称作形式参数,简称形参。它指代函数参数的类型,以及参数进入add后,需要经历的处理步骤没有确定值。 而在函数调用中add(2, 3)2,3被称作实际参数,简称实参。它们将确定形式参数的值具体是什么。

参数的自动转换

形式参数与实际参数的类型允许不一致的情况。

代码语言:javascript
复制
#include <stdio.h>
int add(int a, int b)
{
	return a + b;
}
int main()
{
	int result;
	result = add(2.2, 3.3); // 函数调用
	printf("%d", result);
	return 0;
}

编译出现了两个警告,告诉我们double到int的转换会丢失数据,并且最后的结果为5。 将实际参数 2.2,3.3 传递给形式参数 int a, int b 时,编译器会尝试将实参转换为形参的类型。 若可以转换,那么将编译通过。若转换过程中可能出现数据丢失,将以警告的形式告诉程序员。 2.2,3.3 被转换为了整型 2,3 ,小数部分被丢失。 若无法转换,那么编译失败。

返回值的自动转换

代码语言:javascript
复制
#include <stdio.h>
double add(int a, int b)
{
	return a + b;
}
int main()
{
	double result;
	result = add(2, 3); // 函数调用
	printf("%f", result);
	return 0;
}

现在我们把返回值改为了double类型。但是,参数仍然保持int类型。 a与b相加的结果为int类型,返回时,会尝试将int转换为double。int可以被转换为double,所以编译通 过。

形参与实参相互独立

代码语言:javascript
复制
#include <stdio.h>
void swap(int a, int b)
{
	int temp = a;
	a = b;
	b = temp;
}
int main()
{
	int a, b;
	int temp;
	a = 1;
	b = 2;
	printf("a=%d b=%d\n", a, b);
	// 交换a,b变量
	swap(a, b);
	printf("a=%d b=%d\n", a, b);
	return 0;
}

交换失败

代码语言:javascript
复制
a=1 b=2
a=1 b=2

虽然主函数中的变量a,b与函数中的形式参数a,b变量名相同。但是,它们却是相互独立的变量。 调用 swap 函数并传参时,是将主函数中变量a,b的值,传递给形式参数a,b。

不同函数内的变量相互独立

代码语言:javascript
复制
#include <stdio.h>
void func()
{
	int a;
	a = 100;
	printf("a in func %d\n", a);
}
int main()
{
	int a = 0;
	printf("a in main %d\n", a);
	func();
	printf("a in main %d\n", a);
	return 0;
}

从结果中可以看出,这两个变量虽然变量名相同,但是却是两个互相独立的变量。

代码语言:javascript
复制
a in main 0
a in func 100
a in main 0

若去掉 func 函数中的变量声明,那么编译器将无法识别a标识符。 函数内声明的变量为局部变量,不同函数内的局部变量相互独立。 如果你想让一个局部变量的值在另一个函数中使用,可以把它当做一个参数,传递其值到另一个函数中。

函数递归调用

代码语言:javascript
复制
#include <stdio.h>
void func(int n)
{
    printf("%d\n", n);
    func(n + 1);
}
int main()
{
    func(0);
    return 0;
}

编译可以通过,运行依次打印出了0,1,2,3,4,5… 在C语言中,在一个函数内部是可以再次调用自己的。这种调用被称之为函数递归。 由于函数func首尾相接,它将造成程序陷入死循环。就像一条蛇,咬住了自己的尾巴,整个蛇构成了一个环形。 如果程序陷入了循环,请使用Ctrl + C组合键结束程序 如果不打断程序执行,那么过不了多久,程序将出现栈溢出异常,导致程序异常结束。

如何正确地进行递归?

递归函数的两个要素:

  • 递推规则
  • 递推结束条件

现在我们给程序加上递推结束条件,当n为5时,就让递推结束。

代码语言:javascript
复制
#include <stdio.h>
void func(int n)
{
    if (n == 5)
        return;
    printf("%d\n", n);
    func(n + 1);
}
int main()
{
    func(0);
    return 0;
}

流程在n小于5之前,一直递推至下级函数。当n为5时,从下级函数开始回归

递推与回归

代码语言:javascript
复制
#include <stdio.h>
void func(int n)
{
    if (n == 5)
        return;
    printf("after %d\n", n);
    func(n + 1);
    printf("before %d\n", n);
}
int main()
{
    func(0);
    return 0;
}

输出结果

代码语言:javascript
复制
after 0
after 1
after 2
after 3
after 4
before 4
before 3
before 2
before 1
before 0

标号为1,2,3,4,5的printf在递推过程中,被依次执行。而标号为6,7,8,9,10的printf,必须等到回归过程,才会被执行到。由于回归过程与递推过程是逆向的,所以,输出的n值是逆序的。 对于此func函数,放在递归调用前的语句将在递推过程中执行。而放在递归调用后的语句将在回归过程中执行。

使用递归计算阶乘

规律如下:

  • 当n为1或0时,n的阶乘为1。
  • 当n大于1时,n的阶乘为n * (n - 1)!

那么假设有这么一个函数f(n),这个函数传入一个整数n,返回n的阶乘n!。

  • 当n为1或0时,f(n)返回1。
  • 当n大于1时,f(n) = n * f(n - 1)

写出如下代码。

代码语言:javascript
复制
#include <stdio.h>
int f(int n)
{
    if (n == 0 || n == 1)
    {
        return 1;
    }
    return n * f(n - 1);
}
int main()
{
    int result = f(4);
    printf("%d\n", result);
    return 0;
}

递推流程中,可以确定当前的n分别为4,3,2,1。 接着进入了递归调用f(n - 1),直到n为1时,开始回归。 回归到n为2时,计算2 * 1。 回归到n为3时,计算3 _ (2 _ 1)。 回归到n为4时,计算4 _ (3 _ 2 * 1)。

使用递归计算斐波那契数列

斐波那契数列,第1、2项分别为1,之后每一项为前两项相加之和 那么假设有这么一个函数f(n),这个函数传入一个整数n,返回斐波那契数列的第n项。

  • 当n为1或2时,f(n)返回1。
  • 当n大于2时,f(n) = f(n - 1) + f(n - 2)。
代码语言:javascript
复制
#include <stdio.h>
int f(int n)
{
    if (n == 1 || n == 2)
    {
        return 1;
    }
    return f(n - 1) + f(n - 2);
}
int main()
{
    int result = f(4);
    printf("%d\n", result);
    return 0;
}

上图中,红线为递推过程,蓝线为回归过程。 当表达式f(n-1)+f(n-2)中,两个函数均回归了结果,即可计算和,再回归上级。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-03-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 函数的特性及定义
  • 函数的调用
  • 为什么要将代码封装成函数?
  • 函数返回
  • 函数声明
  • 参数与返回值的类型自动转换
    • 参数的自动转换
      • 返回值的自动转换
      • 形参与实参相互独立
      • 不同函数内的变量相互独立
      • 函数递归调用
      • 如何正确地进行递归?
      • 递推与回归
        • 使用递归计算阶乘
          • 使用递归计算斐波那契数列
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档