庖丁解牛函数知识---C语言《2》

简介: 庖丁解牛函数知识---C语言《2》

前言:


 在第一篇的基础上,我们接着学习函数相关的知识。虽然都是基础知识,但干货满满,读来没有新的知识收获,也有复习的奇效。


1.嵌套调用函数

 在C语言中,调用函数是非常常见的。我们需要使用函数来解决问题,自然就需要把函数给调出来使用。那么什么是嵌套调用呢?我们看下面的代码:

#include <stdio.h>
void put_word(int n)
{
    if (11 == n)
        printf("I miss you!\n");
    if (13 == n)
        printf("I want to say\n");
}
void put_line()
{
    int a = 0;
    int i = 0;
    printf("Hello long time no see!\n");
    for (i = 0; i < 2; i++)
    {
        if (i != 1)
        {
            printf("你可以输入两次\n");
        }
        printf("请输入你要听的字符数(13,11)>:");
        scanf("%d", &a);
        put_word(a);
    }
}
int main()
{
    put_line();
    return 0;
}

 这段代码的逻辑是:先在main函数里调用put_line函数;进入到put_line函数里面再调用了put_word函数;这里是多层调用,A调用B,B调用C来完成"我想你"这段有趣的输出。这种就是函数的嵌套调用。


 函数的嵌套调用是必要的,比如在生成汽车的时候,原材料从哪里来,各种原材料加工厂是原材料变成相应的部件,最后组装在一起变成汽车。在计算机中,每个函数就是一个部件,最后有机组合(嵌套调用)在一起,完成程序任务。


 补充:



 相信大家看完这段代码后,印象会更深刻一点~


2.链式访问

 链式访问是将函数的返回值作为一个函数的参数使用。

#include <stdio.h>
#include <string.h>
int main()
{
    int len = strlen("abcdef");
    printf("%d\n", len);
    printf("%d\n", strlen("abcdef"));
    return 0;
}


 printf函数参数为strlen("abcdef")利用的就是函数的链式访问,strlen返回字符串的长度,这个长度作为printf函数的参数被打印出来。



 它的效果和使用整型len接收strlen返回的长度,并打印len是一样的。


 我们接下来看这样一段代码,感受函数的链式调用:

#include <stdio.h>
int main()
{
    printf("%d", printf("%d", printf("%d", 43)));
    return 0;
}

 这段代码将打印什么呢?首先最外层的printf要打印一个整型值,它要看里层printf函数的返回值是多少; 里层函数要打印一个整型值,它要看最里层printf函数的返回值是多少;这个时候,从最外层的printf函数打印,层层往里像一条链一样串起来。


 现在已经分析到最里层的printf函数了,它打印了43这个整型,但我们不知道printf函数打印完后,返回什么东西,这时候我们得上网站查一查:cplusplus.com


 具体的使用方法可以快速阅读一下上一篇函数知识讲解如何学习库函数:链接



 得知printf函数的返回值是成功读取到字符的个数;那么最里层printf函数成功读取到43这两个字符,返回值是2;上面的代码就变成了printf("%d", printf("%d", 2));这样子了。接着就是里层打印2,成功读取的字符个数是1,返回值为1,变成printf("%d", 1);。


 最终在屏幕上显示的就是:



3.函数的声明与定义

 函数的使用必须遵循先声明后使用的原则。

#include <stdio.h>
int Add(int, int);//声明Add函数
int main()
{
    int a = 10;
    int b = 20;
    printf("%d\n", Add(a, b));
    return 0;
}
int Add(int x, int y)//定义函数
{
    return x + y;
}

 声明函数的时候,需要指出它的返回类型、函数名、函数的参数类型,由于声明是一条语句,不要漏了最后的分号。  


 补充:函数声明参数的时候,可以只写类型,而不用写名字。我们不必写int Add(int x, int y);。


 由于程序的执行流程是从上往下执行的,程序入口是main函数,所以将定义放在函数使用之前,就可以不用声明。


#include <stdio.h>
int Add(int x, int y)//定义函数
{
    return x + y;
}
int main()
{
    int a = 10;
    int b = 20;
    printf("%d\n", Add(a, b));//已经知道Add是什么样的函数了
    return 0;
}


 这是一种特殊的声明,定义声明。主旨思想就是:编译器需要知道你使用的函数是什么,要么定义在我使用之前,要么你告诉我这个函数是什么。


4.*递归

 函数自己调用自己叫做递归;递归往往能将一个大量重复的计算用简洁少量的代码写出来,递归的本质是大事化小。我们先来看一个代码:

#include <stdio.h>
//打印12345的每一位数
void print(int n)
{
    if(n > 9)
    {
        print(n/10);//打印1234的每一位数
    }
    printf("%d ", n%10);//打印5
}
//打印1234的每一位数
//void printf(int n)
//{
//   if(n > 9)
//    {
//        print(n/10);//打印123的每一位数
//    }
//    printf("%d ", n%10);//打印4
//}
//...
//...
//打印1的每一位数
//void printf(int n)
//{
//      if(n > 9)
//      {
//          print(n/10);
//      }
//      printf("%d ", n%10);//1
//}
int main()
{
    int n = 0;
    scanf("%d", &n);//12345
    //将n的每一位打印出来
    print(n);
    return 0;
}


 想求数字12345的每一位,可以使用取模取商交替使用的方法求。12345%10 = 5;得到数字5。12345/10得到数字1234;再将1234%10 = 4;得到数字4。1234/10得到数字123........。  


 打印12345的每一位数,我们定义一个print函数用来求之。利用大事化小的本质,可以这样思考:使用print函数打印1234的每一位+打印数字5,再将(打印数字1234的每一位)分解成,打印123每一位+打印数字4.......。


 这个比较难理解,再过一会下面有图解,保证清晰易懂~。


 从这个思考里面我们可以得出:每次打印最后一位是需要打印数字每一位的最后一位,也就是说当我需要打印数字12345的每一位的时候,我需要打印5,然后在打印数字1234的每一位的时候,我需要打印4。以此类推,直到当我在打印数字1的时候,我把最后一位1打印出来,已经没有数字了,程序停止。



 注意的要点是:递归需要有限制,没有限制条件或限制条件不恰当都会导致栈溢出。


 在调用函数的时候,函数需要在栈上开辟空间,这个过程叫做压栈,如果递归一直持续下去没有完结,栈上的空间都被占满了,没有栈空间程序就自动挂掉,并给警告说栈溢出。


 所以使用递归的时候,有两个注意点:


进行递归是有限制条件的,不是可以任何情况都可以递归下去。

每进行一次递归调用,都会使递归条件越来越接近结束条件。

 在求数字的每一位的时候,进行递归的条件是大于9,也就是要有两位数才进行递归,如果只有一位数,那直接取余打印出来就OK了。每次递归n的值都会少一位,所以这个递归不会出现很大的问题。最重要的一点来了:用画图法来解释刚刚这道代码题,保证能理解透彻。



 一层一层往下递推下去,限制条件限制继续往下递归的时候,开始往回退,一层一层处理,结束。


5.递归与非递归

 一般能用递归解决的问题,都可以使用循环解决,因为递归实际上也相当于是在重复做一件事,只不过递归不像循环一样一点都不变,但仍可以使用循环代替。是使用循环还是递归呢?我们需要结合具体情况。


 求斐波那契数列的第n的数:

#include <stdio.h>
//递归实现
int Fib(int n)
{
    if(n <= 2)
        return 1;
    else
        return Fib(n-2)+Fib(n-1);
}
int main()
{
    int n = 0;
    scanf("%d", &n);
    int ret = Fib(n);
    printf("%d\n", ret);
}


 斐波那契数列的规律是:1 1 2 3 5 8 13 21 34 55.......、从第三各个数开始,每个数都是前两个数的和。递归的实现思路是:如果求的是第一,第二个斐波那契数列,就返回1;如果不是就返回求n-1和n-2的斐波那契数列值。这样一直递归求下去直到n<=2时到达限制条件,不再递归下去了,开始回溯。


 假如我们求的是第五十个斐波那契数列,那么由代码可知,我们会求第48斐波那契数列值和第49斐波那契数列值;48再求46和47的,49在求47和48的;这样每次递归下去,求的数值都翻一倍,重复计算很多,效率非常低下。我们可以用一个计数器,看看求40的斐波那契额数列值时,Fib(3)被重复计算了多少次!



 被重复计算了3千9百多万次!!! 这时候用递归来求是不可以取的。我们看循环求解斐波那契额数列。

#include <stdio.h>
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;
}
int main()
{
    int n = 0;
    scanf("%d", &n);
    int ret = Fib(n);
    printf("%d\n", ret);
    return 0;
}


  循环求解斐波那契数列就是,1 1 2 3 5 8.....、a代表Fib(n-2)、b代表Fib(n-1),每次循环都把Fib(n-2)和Fib(n-1)的和赋给cFib(n),然后整体将a、b整体向右移。当n=3时,这个循环操作一次,n=4时,循环操作两次,也就是两次将a、b的和赋值给c,符合要求斐波那契额数列的求法。


 这个算法可以很快的算出n = 50时的斐波那契数列值。这时我们会选择用循环解决这个问题。


 总结:什么时候使用递归呢?如果递归的实现比较简单,而且不需要耗费太多时间,我们就可以选择递归。即使递归写起来比较方便,但耗时太久了,我们反而会选择,代码篇幅较长的循环来解决。


 文已至此,函数的内容就讲完啦~,感谢观看,咱们下篇见。


结语:希望读者读完有所收获!在学C的路上,祝福我们能越来越C!✔


 读者对本文不理解的地方,或是发现文章在内容上有误等,请在下方评论区留言告诉博主哟~,也可以对博主提出一些文章改进的建议,感激不尽!最后的最后!


 ❤求点赞,求关注,你的点赞是我更新的动力,一起努力进步吧。

相关文章
|
8天前
|
存储 Serverless C语言
【C语言基础考研向】11 gets函数与puts函数及str系列字符串操作函数
本文介绍了C语言中的`gets`和`puts`函数,`gets`用于从标准输入读取字符串直至换行符,并自动添加字符串结束标志`\0`。`puts`则用于向标准输出打印字符串并自动换行。此外,文章还详细讲解了`str`系列字符串操作函数,包括统计字符串长度的`strlen`、复制字符串的`strcpy`、比较字符串的`strcmp`以及拼接字符串的`strcat`。通过示例代码展示了这些函数的具体应用及注意事项。
|
11天前
|
存储 C语言
C语言程序设计核心详解 第十章:位运算和c语言文件操作详解_文件操作函数
本文详细介绍了C语言中的位运算和文件操作。位运算包括按位与、或、异或、取反、左移和右移等六种运算符及其复合赋值运算符,每种运算符的功能和应用场景都有具体说明。文件操作部分则涵盖了文件的概念、分类、文件类型指针、文件的打开与关闭、读写操作及当前读写位置的调整等内容,提供了丰富的示例帮助理解。通过对本文的学习,读者可以全面掌握C语言中的位运算和文件处理技术。
|
11天前
|
存储 C语言
C语言程序设计核心详解 第七章 函数和预编译命令
本章介绍C语言中的函数定义与使用,以及预编译命令。主要内容包括函数的定义格式、调用方式和示例分析。C程序结构分为`main()`单框架或多子函数框架。函数不能嵌套定义但可互相调用。变量具有类型、作用范围和存储类别三种属性,其中作用范围分为局部和全局。预编译命令包括文件包含和宏定义,宏定义分为无参和带参两种形式。此外,还介绍了变量的存储类别及其特点。通过实例详细解析了函数调用过程及宏定义的应用。
|
16天前
|
Linux C语言
C语言 多进程编程(三)信号处理方式和自定义处理函数
本文详细介绍了Linux系统中进程间通信的关键机制——信号。首先解释了信号作为一种异步通知机制的特点及其主要来源,接着列举了常见的信号类型及其定义。文章进一步探讨了信号的处理流程和Linux中处理信号的方式,包括忽略信号、捕捉信号以及执行默认操作。此外,通过具体示例演示了如何创建子进程并通过信号进行控制。最后,讲解了如何通过`signal`函数自定义信号处理函数,并提供了完整的示例代码,展示了父子进程之间通过信号进行通信的过程。
|
16天前
|
C语言
C语言 字符串操作函数
本文档详细介绍了多个常用的字符串操作函数,包括 `strlen`、`strcpy`、`strncpy`、`strcat`、`strncat`、`strcmp`、`strncpy`、`sprintf`、`itoa`、`strchr`、`strspn`、`strcspn`、`strstr` 和 `strtok`。每个函数均提供了语法说明、参数解释、返回值描述及示例代码。此外,还给出了部分函数的自实现版本,帮助读者深入理解其工作原理。通过这些函数,可以轻松地进行字符串长度计算、复制、连接、比较等操作。
|
17天前
|
SQL 关系型数据库 C语言
PostgreSQL SQL扩展 ---- C语言函数(三)
可以用C(或者与C兼容,比如C++)语言编写用户自定义函数(User-defined functions)。这些函数被编译到动态可加载目标文件(也称为共享库)中并被守护进程加载到服务中。“C语言函数”与“内部函数”的区别就在于动态加载这个特性,二者的实际编码约定本质上是相同的(因此,标准的内部函数库为用户自定义C语言函数提供了丰富的示例代码)
|
28天前
|
机器学习/深度学习 编译器 Serverless
C语言中函数
C语言中函数
19 0
|
4月前
|
存储 C语言
C 语言函数完全指南:创建、调用、参数传递、返回值解析
函数是一段代码块,只有在被调用时才会运行。 您可以将数据(称为参数)传递给函数。 函数用于执行某些操作,它们对于重用代码很重要:定义一次代码,并多次使用。
137 3
|
3月前
|
存储 C语言
C语言的函数返回值和指针
C|函数返回值(区分各类值)和指针(区分各类存储空间)的细节
|
4月前
|
存储 C语言
C语言中向函数传递值和从函数返回值的技术解析
C语言中向函数传递值和从函数返回值的技术解析
46 0