函数(三)

简介: 函数(三)

一.函数的声明与定义

//函数的声明
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条件只是确定在第几个数中而已,因为后续的数都需要前面的数作计算。

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

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

相关文章
|
移动开发 Ubuntu 网络协议
Linux(11)Debain 调试EC25-EUX总记录
Linux(11)Debain 调试EC25-EUX总记录
575 0
|
C++
移除元素(C++)
移除元素(C++)
84 0
|
12月前
|
Java Spring
【Spring】方法注解@Bean,配置类扫描路径
@Bean方法注解,如何在同一个类下面定义多个Bean对象,配置扫描路径
529 73
可控细节的长文档摘要,探索开源LLM工具与实践
本文通过将文档分为几部分来解决这个问题,然后分段生成摘要。在对大语言模型进行多次查询后,可以重建完整的摘要。通过控制文本块的数量及其大小,我们最终可以控制输出中的细节级别。
开发指南029-el-table-column对齐属性
按开发文档和正常理解,el-table-column具有属性align,可以填left,center,right控制内容的对齐方式
|
安全 数据库
【Debian】配置aide入侵检测服务
基于debian系统。aide主要功能检测系统文件,当系统文件发生变化,如/etc/passwd文件出现差异,那么aide将会认为系统遭受入侵被增添用户
2459 0
|
关系型数据库 MySQL Linux
百度搜索:蓝易云【Cnetos7编译安装MySQL教程。】
请注意,上述步骤提供了基本的编译安装MySQL的指南,具体的过程可能会因您选择的MySQL版本和特定需求而有所不同。如果遇到任何问题,建议参考MySQL官方文档或社区资源进行进一步的研究和咨询。
176 0
|
Prometheus Kubernetes 监控
在k8S中,DaemonSet类型的资源特性有哪些?
在k8S中,DaemonSet类型的资源特性有哪些?
|
存储 缓存 网络协议
说说 HTTP1.0/1.1/2.0 的区别?
HTTP协议从1.0到1.1再到2.0不断发展,提升了性能和效率。HTTP1.0每次请求需新建TCP连接,而1.1默认保持长连接,允许多个请求复用一个连接,减少延迟。HTTP2.0引入多路复用,允许同时发送多个请求和响应,避免队头堵塞;采用二进制分帧,提高解析效率;并使用首部压缩减少数据传输;此外,服务器推送功能允许预加载资源,优化加载速度。
503 0
|
SQL 关系型数据库 MySQL
Presto【基础 01】简介+架构+数据源+数据模型+特点(一篇即可入门支持到PB字节的分布式SQL查询引擎Presto)
Presto【基础 01】简介+架构+数据源+数据模型+特点(一篇即可入门支持到PB字节的分布式SQL查询引擎Presto)
1291 0