函数(三)

简介: 函数(三)

一.函数的声明与定义

//函数的声明
int Add(int x, int y);
int main()
{
  int num1 = 0;
  int num2 = 0;
  scanf("%d %d", &num1, &num2);
  //计算
  //函数的调用(传值调用)
  //2
  int ret = Add(num1, num2);
  printf("%d\n", ret);
  return 0;
}
//函数的定义
int Add(int x, int y)
{
  return x + y;
}

正常情况下,如果函数是在主函数main后面定义,那么就需要在前面加上声明。这样才可以使用。

如果不想声明,那就在主函数main前面进行定义。

在未来工程中,代码是比较多的,所以函数都是会放在.h文件中声明,在.c文件中实现。这就是模块化编程。

所以我们先添加2个文件。

把函数放置好后,不要忘记在主函数那里调用add.h文件

二.函数递归

递归的主要思考方式:大事化小

int main()
{
  printf("hehe\n");
  main();
  return 0;
}

这是一个最简单的递归,但最后会因为栈溢出而崩溃。其原因就在于没有结束条件,只能无止境的运行。

递归的两个必要条件:

1.存在限制条件,当满足这个限制条件的时候,递归便不再继续。

2.每次递归调用之后越来越接近这个限制条件。

1.案例一.接收一个整型值,按照顺序打印它的每一位。

例如:输入:1234 输出:1 2 3 4

int main()
{
  //1234
  //1234%10=4
  //1234/10=123
  //123%10=3
  return 0;
}

目前的基本思路就是先把每个数都单独拎出来,采用模10,除10可以解决,不过又该怎么让这些单独的数字按照顺序排列呢?

采用拆分的方式:

int main()
{
  //1234
  //1234%10=4
  //1234/10=123
  //123%10=3
  int num = 0;
  scanf("%d", &num);
  Print(num);
  //Print(1234)
  //Print(123)+    4
  //Print(12)+    3
  //Print(1)+    2
  return 0;
}

只要在每一次打印出单独数字之前再继续调用函数,让其先进入函数内部而无法打印进行递进,最后回归打印全部。

void Prinit(int n)
{
  if (n > 9)
  {
    Print(n / 10);
  }
  printf("%d ", n % 10);
}

而这一步就是递归的精华。

每一次都要等调用函数完后才可以打印,当n=12时,因为要等函数Print(1)结束,当(1)结束时打印1再返回到函数(12)中,代表函数(1)结束所以打印2,以此类推函数(12)结束,函数(123)打印3,最终结果就是1 2 3 4.

红色线条代表递进,通过函数层层递进,而其中会有条件限制函数,并逐渐接近条件。最后再通过蓝色线条层层回归。

下面会引申关于函数栈帧的概念(后续会具体介绍)

2.函数栈帧

函数之所以能实现调用,能实现递归都是因为,函数在调用的时候会维护一个函数栈帧(内存上的一块区域)函数调用开始,函数栈帧创建,函数调用结束后,栈帧销毁。

我们可以看到在每一次的开辟空间中都会存放其中的局部变量。

当把1打印出来的时候,黄色空间就会销毁回收,以此类推。而在销毁之前存放的变量会被打印出来,完成价值。

3.案例二.编写函数不允许创建临时变量,求字符串的长度。

int main()
{
  char arr[] = { "abc" };
  size_t len = strlen(arr);
  printf("%zd\n", len);
  return 0;
}

这是正常的代码,strlen返回的是size_t类型所以需要size_t变量接受,打印也需要用zd。

不过这样就有了临时变量了。所以我们需要的就是模拟实现strlen的功能并返回长度。

接下来演绎用函数求长度。

size_t my_strlen(char* str)
{
  size_t count = 0;
  while (*str != '\0')
  {
    str++;
    count++;
  }
  return count;
}
int main()
{
  char arr[] = { "abc" };
  size_t len = my_strlen(arr);
  printf("%zd", len);
  return 0;
}

基本思路就是利用\0字符串结束标志来确定字符串长度,不过需要注意的是str++是指往数组下一位进行判断,但还是创建了临时变量。

这里我们可以利用案例一的办法——拆分:只要不是结束标志\0那就+1并继续判断str后一位的字符是否是\n。不过注意一点,str+1不要用str++,因为str++是先用再加,这里就不能达到下一位的目的了。

size_t my_strlen(char* str)
{
  if (*str != '\0')
  {
    return 1 + my_strlen(str + 1);
  }
  else
  {
    return 0;
  }
}
int main()
{
  char arr[] = { "abc" };
  size_t len = my_strlen(arr);
  printf("%zd", len);
  return 0;
}

分析如上:红线从上往下递进,蓝线从下往上递归。

4.案例三.求n的阶层

怎么说呢,其实这在数学中是有归纳规律的。就像等差,等比一样,利用递归和迭代写的话,记住规律就好了。

这就是递归套用的函数。

int Fac(int k)
{
  if (k <= 1)
  {
    return 1;
  }
  else
  {
    return k * Fac(k - 1);
  }
}
int main()
{
  int k = 0;
  scanf("%d", &k);
  int r = Fac(k);
  printf("%d\n", r);
  return 0;
}

很简单就写出来了。不过如果数字太大,那么就会一直递归下去,很久才往回返,因此会算得很慢。

5.案例四.求第n个斐波那契数

这个是跟阶层同理,可以通过数学原理解答,也有自己的递归规律。

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

不过还是老问题,有太多大量重复计算,会影响效率。

例如输入50,会发现牵连的数字很多是重复计算的。

这时我们可以尝试不用递归来算,从斐波那契中可以看出,第一,二个数是不用算的,直到第3个数字才开始,可以采用相互赋值逻辑计算。

int Fib(int n)
{
  int a = 1;
  int b = 1;
    int c = 1;
  while (n >= 3)
  {
    c = a + b;
    a = b;
    b = c;
    n--;
  }
  return c;
}

而这个n>-3条件只是确定在第几个数中而已,因为后续的数都需要前面的数作计算。

在没有用递归之后,反而系统很快就算出来了。

后续还有汉诺塔问题和青蛙跳台阶问题,敬请期待。

相关文章
|
6月前
|
C语言
函数
函数
29 1
|
6月前
|
存储 编译器 C++
|
5月前
|
算法 Java 开发者
解密CollectGarbage函数
解密CollectGarbage函数
|
程序员 编译器 C语言
从零带你认识函数(一)
从零带你认识函数
85 1
|
6月前
|
程序员 C语言 Python
函数—C(上)
函数—C(上)
52 0
|
6月前
|
开发工具 Windows
GetMessage()函数使用时的注意
GetMessage()函数使用时的注意
79 0
|
11月前
|
存储 程序员 C语言
函数(1)
函数(1)
73 0
|
11月前
|
编译器 C语言
对函数的剖析一
对函数的剖析一
38 0
|
存储 编译器 C语言
C语言知识点之 函数
C语言知识点之 函数
55 0