C函数与递归

简介: C函数与递归

函数的特性及定义

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

  • 开始执行时,函数可以被输入一些值
  • 执行过程中,函数可以做一些事情
  • 执行完成后,函数可以返回一些值
函数的写法公式:
函数返回值类型 函数名(函数输入参数值)
{
    做点什么事情
    return 函数返回值;
}

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

注意每个输入参数必须指明其变量类型,不能省略变量类型。
int add(int a, int b)   //正确
int add(int a, b)       //错误

函数的调用

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

#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类型的结果

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

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

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

函数返回

#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,就会停止函数的执行,返回主调函数。

函数声明

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

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

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

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);

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

#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被称作实际参数,简称实参。它们将确定形式参数的值具体是什么。

参数的自动转换

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

#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 ,小数部分被丢失。
若无法转换,那么编译失败。

返回值的自动转换

#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,所以编译通
过。

形参与实参相互独立

#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;
}
交换失败
a=1 b=2
a=1 b=2

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

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

#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;
}
从结果中可以看出,这两个变量虽然变量名相同,但是却是两个互相独立的变量。
a in main 0
a in func 100
a in main 0

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

函数递归调用

#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时,就让递推结束。

#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时,从下级函数开始回归

递推与回归

#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;
}
输出结果
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)
写出如下代码。
#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)。
#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)中,两个函数均回归了结果,即可计算和,再回归上级。

目录
相关文章
C4.
|
6月前
|
C语言
C语言函数的递归调用
C语言函数的递归调用
C4.
107 0
函数递归(详细解读)(下)
函数递归(详细解读)(下)
|
算法
函数递归(详细解读)(上)
函数递归(详细解读)(上)
|
2月前
利用递归函数调用
利用递归函数调用。
34 9
|
6月前
|
C++
C++程序中的函数递归调用
C++程序中的函数递归调用
67 1
|
1月前
函数的递归
函数的递归
14 0
|
3月前
|
搜索推荐 开发者 Python
递归调用
递归调用
|
5月前
|
C语言
C语言函数递归详解:理解递归的原理与应用
C语言函数递归详解:理解递归的原理与应用
102 0
|
6月前
|
算法 C语言
C语言函数递归调用详解与实战应用
C语言函数递归调用详解与实战应用
61 0
|
6月前
|
C语言
函数递归.
这篇内容介绍了递归的概念,指出在C语言中递归是函数自我调用。它通过一个简单的死递归示例展示了未设置停止条件会导致栈溢出。接着,文章阐述了递归的两个必要条件:存在限制条件以终止递归,以及每次递归调用都更接近这个限制条件。随后,文章通过计算阶乘和顺序打印整数位的例子展示了递归的应用,并对比了递归和迭代的效率,强调在存在冗余计算时,迭代通常比递归更高效。
32 0