函数—C(下)

简介: 函数—C(下)

前言

在函数(1)中已经详细介绍了什么是库函数和自定义函数,以及函数参数的两种类型(实参和形参)的区别和用法。接下来将接着介绍函数的其他用法,让读者全面理解C语言函数用法以及背后得细节。

1. 函数的嵌套调用和链式访问

函数和函数之间可以根据实际需求进行组合,即相互调用。

1.1 嵌套调用

在C语言中,函数调用简单来说就是函数内部调用其他函数,以实现嵌套

 #include <stdio.h>

void new_line()
{
  printf("hehe\n");
}

void three_line()
{
  int i = 0;
  for (i = 0; i < 3; i++)
  {
    new_line(); //调用函数
  }
}

int main()
{
  three_line(); //调用three_line()函数
  return 0;
}

Tips:

函数可以嵌套调用,但不能嵌套定义。

1.2 链式访问

在C语言中,把一个函数的返回值作为另一个函数的参数叫链式访问。

#include <stdio.h>
int main()
{     //函数printf()的返回值作为了printf()的参数
  printf("%d\n", printf("%d\n", printf("%d\n", 43)));
  //注:printf()函数的返回值是打印在屏幕上字符的个数
  return 0;
}

2. 函数的声明和定义

2.1 函数声明

  1. 函数声明告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但具体是不存在的,函数声明决定不了。
  2. 函数的声明一般在函数使用之前,要满足先声明后使用。
  3. 函数的声明一般要放在头文件中。

2.2 函数定义

函数的定义是指函数的具体实现,交代函数的功能实现。

3. 函数递归

3.1 什么是递归

程序调用自身的编程技巧称为递归。

递归作为一种算法在程序设计语言中广泛应用。一个过程或函数在其定义或说明中直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。递归只需要少量的程序就可描述出解题过程所需要的多次重复计算,大大减少了程序的代码量。

递归的主要思想在于:把大事化小

//main()函数不断嵌套调用自身
//但由于没有相关限制,该程序会一直执行,死循环,最后栈溢出,程序结束
int main()
{
  printf("hehe\n");
  main();
  return 0;
}

3.2 递归的两个必要条件

前面一个程序我们已经见识到了如果递归没有相关条件限制,程序可能会出现异常。所以递归应满足以下两个必要条件:

  1. 存在限制条件,当满足这个限制条件时,递归便不再继续。
  2. 每次递归调用后越来越接近这个限制条件。

3.3 递归和迭代

3.3.1 练习1

求n的阶乘

函数实现功能参考代码:

int factorial(int n)
{
  if (n <= 1)
    return 1;
  else
    return n + factorial(n - 1);
  return 0;
}

3.3.2

求第n个斐波那契数(不考虑溢出)

函数实现功能参考代码:

int fib(int n)
{
  if (n <= 2)
    return 1;
  else
    return fib(n - 1) + fib(n - 2);
}

虽然上面两个函数可以实现需求,但我们会发现一些问题:

  • 使用fib这个函数的时候如果我们计算比较大的数(如第50个斐波那契数)时特别耗时间。
  • 使用factorial函数求第10000的阶乘时(不考虑结果正确性),程序会崩溃。

为什么呢?

  • 我们发现fib函数在调用过程中许多计算一直在重复。

以第40个斐波那契数,fib(3)被重复计算为例:

#include<stdio.h>
int count = 0;
int fib(int n)
{
  if (n == 3)
    count++;
  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);
  printf("count=%d\n", count);
  return 0;
}

运行结果:

查看输出结果,我们发现第40个斐波那契数,fib(3)被重复计算39088169次,这是一个很大的数!

Tips:

  • 在调试函数时,日如果你的参数比较大,那就会报错:stack overflow(栈溢出),这样的信息。
  • 系统分配给程序的栈空间是有限的,但是如果出现死循环或者死递归时,这样可能导致一直开辟栈空间,最终产生栈空间耗尽的情况,我们称这种现象为栈溢出。

那要怎么解决问题呢?

  1. 将递归写成非递归
  2. 使用static对象替代nonstatic局部变量。在递归函数设计中,可以使用static对象代替nonstatic局部对象(即栈对象)。这不仅可以减少每次递归调用和返回时产生和释放的nonstatic对象的开销,而且static对象还可以保存递归调用的中间状态,并且可为各个调用层所访问。

比如下面代码采用了非递归的方式来实现:

//求n的阶乘
int factorial(int n)
{
  int result = 1;
  while (n > 1)
  {
    result *= n;
    n -= 1;
  }
  return result;
}
//求第n个斐波那契数
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;
}

总结:

1.许多问题是以递归的方式来进行解释的,这只是应为它比非递归的形式更清晰。

2. 但这些问题的迭代实现往往比递归实现的效率要高,虽然代码的可读性稍差。

3. 当一个问题相当复杂时,难以用迭代实现时,此时递归实现的简洁性便可以补偿它运行时所带来的开销。

结尾

本片文章到此就圆满结束了。如果对你有帮助,记得点赞加关注哦!感谢您的支持!!

函数—C(上)


相关文章
|
9月前
|
程序员 C语言
函数(1)
函数(1)
|
9月前
|
编译器 C语言
C 中的函数
C 中的函数
|
自然语言处理 C++
C/C++ 中的 atol()、atoll() 和 atof() 函数
1.atol(): 此函数将作为参数传递给函数调用的 C 类型字符串转换为长整数。它解析 C 字符串 str 并将其内容解释为整数,该整数作为 long int 类型的值返回。该函数会丢弃字符串开头的空白字符,直到找到非空白字符。如果 C 字符串 str 中的非空白字符序列不是有效的整数,或者如果因为 str 为空或仅包含空白字符而不存在这样的序列,则不执行任何转换并返回零。
144 0
|
JavaScript 前端开发 API
h函数为什么叫h?
h函数为什么叫h?
201 0
|
SQL
last函数
last函数
107 0
|
C语言
可变长参数函数
可变长参数函数
100 0
|
JavaScript
什么是函数?
什么是函数?
C/C++ 字符串转数字函数
C/C++ 字符串转数字函数
334 0
十、详解函数柯里【下】
柯里化是函数的一个高级应用,想要理解它并不简单。因此我一直在思考应该如何更加表达才能让大家理解起来更加容易。 通过上一个章节的学习我们知道,接收函数作为参数的函数,都可以叫做高阶函数。我们常常利用高阶函数来封装一些公共的逻辑。 这一章我们要学习的柯里化,其实就是高阶函数的一种特殊用法。
126 0