C learning_10 (函数的嵌套调用和链式访问、函数的声明和定义、函数递归)

简介: C learning_10 (函数的嵌套调用和链式访问、函数的声明和定义、函数递归)

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


嵌套调用


       函数嵌套调用指的是在一个函数中调用另外一个函数,而被调用函数中又调用了另外一个函数,以此类推,形成了多个函数互相嵌套的调用关系。在程序设计中,函数嵌套调用可以提高代码的灵活性和可维护性,使程序结构更加清晰,并可以使代码重复利用更多。

#include <stdio.h>
void new_line()
{
    printf("hello world!\n");
}
void three_line()
{
    int i = 0;
    for (i = 0; i < 3; i++)
    {
        new_line();
    }
}
int main()
{
    three_line();
    return 0;
}


   这段代码的功能是输出三行"hello world!"。具体解释如下:


1. 定义了一个名为new_line()的函数,该函数的作用是输出一行"hello world!",并在行末添加换行符"\n"。

2. 定义了一个名为three_line()的函数,该函数的作用是调用三次new_line()函数,即输出三行"hello world!"。

3. 在main函数中,调用了three_line()函数,即实现了输出三行"hello world!"的功能。

4. 整个程序的返回值为0,表示程序正常结束。


在这个程序中,函数嵌套调用的例子是three_line()函数中调用了new_line()函数。通过函数的嵌套调用,使得代码具有了更好的组织结构,使得程序更加易于阅读和维护。


函数可以嵌套调用,可以嵌套定义吗?


#include<stdio.h>
void test()
{
  printf("hello world!\n");
  void test2()
  {
    printf("hello today!\n");
  }
}
int main()
{
  test();
}


 这段代码中,定义了两个函数test()和test2(),其中test2()函数是定义在test()函数中的,称为嵌套函数。但是,在C语言中是不支持嵌套函数的,因此这段代码编译时将会出现错误。


总结:函数可以嵌套调用,但是不能嵌套定义。


链式访问


       链式访问就是把一个函数的返回值作为另外一个函数的参数。

#include<stdio.h>
#include<string.h>
int main()
{
  //strlen - 求字符串的长度(不包括'\0')
  printf("%d\n",strlen("abcdef"));
  return 0;
}


本程序中将strlen函数的返回值作为参数传递给printf函数,实现函数的链式访问。


注意:标准库函数`strlen`求字符串的长度,它不包括字符串结尾的`\0`。


接下来我们来看一个代码


#include<stdio.h>

int main()

{

   printf("%d", printf("%d", printf("%d", 43)));

   return 0;

}


解释此题,我们就需要理解printf函数的返回值是什么?


printf函数是返回的是写入字符的个数。


#include<stdio.h>

int main()

{

   int ret = printf("%d ", 100);


   printf("%d",ret);//3

   return 0;

}


现在我们来解释一下上面的题目


程序输出会输出:4321


       程序的主体是一个`main()`函数,它调用了三个嵌套的`printf()`函数,并使用了格式化字符串进行输出。


1.最里层的`printf()`函数输出数字43,并返回了一个值,这个值是格式化输出到屏幕上的字符数量,即返回值2,表示输出了两个字符,即'4'和'3'。

2.外层的`printf()`函数的参数是最里层`printf()`函数的返回值,即2。因此外层`printf()`函数打印出来的是数字2,并返回一个值,这个值是格式化输出到屏幕上的字符数量,即返回值1,表示输出了一个字符,即'2'。

3.最外层的`printf()`函数的参数是中间层`printf()`函数的返回值,即1。因此最外层`printf()`函数只打印出一个数字1,并返回一个值,但没有printf函数接收该返回值。


最后,程序返回0,表示正常结束。


当我们在每个%d的后面加上空格,输出又是什么样的呢?


#include<stdio.h>

int main()

{

   printf("%d ", printf("%d ", printf("%d ", 43)));//注意%d的后面有空格

   return 0;

}


程序输出会输出:43 3 2


       程序的主体是一个`main()`函数,它调用了三个嵌套的`printf()`函数,并使用了格式化字符串进行输出。


1.最里层的`printf()`函数输出数字43,并在后面加上了一个空格,然后返回了一个值,这个值是格式化输出到屏幕上的字符数量,即返回值3,表示输出了三个字符,即'4'、'3'和'空格'。

2.外层的`printf()`函数的参数是最里层`printf()`函数的返回值,即3。因此外层`printf()`函数打印出来的是数字3,并在后面加上了一个空格,然后返回一个值,这个值是格式化输出到屏幕上的字符数量,即返回值2,表示输出了两个字符,即'2'和'空格'。

3.最外层的`printf()`函数的参数是中间层`printf()`函数的返回值,即2。因此最外层`printf()`函数只打印出一个数字2,并在后面加上了一个空格,然后返回一个值,但没有printf函数接收该返回值。


最后,程序返回0,表示正常结束。


函数的声明和定义


声明


       在C语言中,函数声明是指在函数使用之前,提前告诉编译器该函数的名字、参数类型和返回值类型等信息,以便编译器在编译时能够正确地编译函数的使用。函数的声明一般要放在头文件中的。


       函数声明通常包括以下几个部分:


1. 返回类型:函数的返回值类型,可以是`void`,表示无返回值,也可以是任何基本数据类型,如`int`、`float`、`double`等。

2. 函数名:函数的名称,用于在程序中调用函数。

3. 参数列表:函数所接受的参数的数据类型和名称,参数的数量可以为0个或多个。


一个典型的函数声明的形式如下所示:

       返回类型 函数名(参数列表);


例如,下面是一个求和函数的声明:

       int sum(int a, int b);


该函数的返回类型是`int`,函数名是`sum`,参数列表包含两个`int`类型的参数`a`和`b`。


       因此,如果要在程序中调用该函数,需要提供两个`int`类型的参数,并将该函数的返回值赋值给一个`int`类型的变量。


定义


       在C语言中,函数定义是指实现一个函数的具体功能,也就是编写函数体中的代码。


一个函数的定义包括以下几个部分:


1. 函数头:与函数声明相似,函数头包括返回类型、函数名和参数列表。在函数定义中,参数列表中的参数名称可以与函数声明中的不同,但是参数类型必须相同。

2. 函数体:函数体是函数实现的具体代码,它包括在函数声明中定义的参数和局部变量,以及执行的语句和返回值。


一个典型的函数定义的形式如下所示:

       返回类型 函数名(参数列表)

       {  

               // 函数体    

               return 返回值;

       }

例如,下面是一个求和函数的定义:

       int sum(int a, int b)

       {    

               int result = a + b;    

               return result;

       }

       该函数的返回类型是`int`,函数名是`sum`,参数列表包含两个`int`类型的参数`a`和`b`。函数体中首先定义一个整型变量`result`,然后将`a`和`b`相加的结果赋值给`result`,最后使用`return`关键字返回`result`的值。如果要在程序中调用该函数,需要提供两个`int`类型的参数,并将该函数的返回值赋值给一个`int`类型的变量。


总结:函数必须先声明后使用。


函数递归


       递归是一种通过调用自身的方式来解决问题的方法。在计算机科学中,递归是一种常用的算法,可以简化问题的解决方式。在递归的过程中,问题被不断地分解成更小的问题,并通过递归函数的返回值来实现问题的求解。

       递归函数通常由两部分组成:基本情况和递归情况。基本情况是递归结束的条件,当满足了基本情况时,递归函数不再调用自身,而是返回一个固定的值。递归情况是递归本身,即递归函数调用自身,并将问题分解成更小的子问题。

       递归函数的调用过程是一个栈的过程,每次递归函数调用时,会把当前函数的执行状态保存在栈中,随着每次递归函数调用结束,栈中保存的函数执行状态也会被依次弹出,直到最后一个递归函数调用结束并返回结果。

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


递归的两个必要条件


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

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


接下来来看一段代码来帮我们理解递归算法

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
void print(unsigned int n)
{
  if (n > 9)
  {
    print(n / 10);
  }
  printf("%d ", n % 10);
}
int main()
{
  unsigned int num = 0;
  scanf("%d", num);
  print(num);
  return 0;
}


1. 在`main`函数中,我们定义了一个`unsigned int`类型的变量`num`,并使用`scanf`函数从控制台输入一个整数"1234",将其赋值给`num`。

2. 然后,我们调用了`print`函数,并将`num`作为参数传递给它。

3. 在`print`函数中,我们首先进行条件判断:如果`n>9`,说明这个数字至少有两位,需要进行下一次递归调用。

4. 当`n>9`成立时,我们调用`print`函数并将`n/10`作为参数传递给它,这个参数的值为`1234/10=123`。

5. 在新的`print`函数中,我们同样进行条件判断,发现`123>9`,需要递归调用函数。

6. 我们继续调用`print`函数,并将`123/10`作为参数传递给它,这个参数的值为`12`。

7. 在新的`print`函数中,我们同样进行条件判断,发现`12>9`,需要递归调用函数。

8. 我们继续调用`print`函数,并将`12/10`作为参数传递给它,这个参数的值为`1`。

9. 在新的`print`函数中,我们同样进行条件判断,发现`1<=9`,直接进行下一步操作。

10. 因为`n<=9`成立,所以我们将`n%10`的值打印出来,即`1%10=1`。

11. 因为上一次递归调用的打印值为1,所以当前递归中`n=12`,继续打印`n%10`的值,即`12%10=2`。

12. 因为上一次递归调用的打印值为2,所以当前递归中`n=123`,继续打印`n%10`的值,即`123%10=3`。

13. 因为上一次递归调用的打印值为3,所以当前递归中`n=1234`,继续打印`n%10`的值,即`1234%10=4`。

14. 因为已经完成了最后一次递归调用,程序返回到`main`函数中,并通过`return 0`语句结束程序的运行。


       总结:该递归算法通过将输入的数字不断除以10并向下取整,然后取余数,实现了数字的打印。具体来说,每次递归调用都会将数字的个位数打印出来,然后返回上一次递归调用,并依次打印其余位数的值,最终实现了数字输出,下面的图配合我们理解。


接下来我们再看几个代码


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


先来看不创建临时变量的求字符串长度的写法

#include<stdio.h>
int my_strlen(char* s)
{
  int count = 0;
  while (*s != '\0')
  {
    count++;
    s++;
  }
  return count;
}
int main()
{
  char arr[] = "abc";
  //[a b c \0]
  /*
    库函数求字符串的长度
    int len = strlen(arr);
    printf("%d\n", len);
  */
  printf("%d\n", my_strlen(arr));
  return 0;
}

 首先,在主函数中定义了一个字符数组arr,并初始化为"abc"。注意,字符串最后要以'\0'作为结尾,表示字符串的结束,因此"abc"实际上是由字母a、b、c和一个'\0'组成的。


       主函数中调用自定义函数my_strlen()。这个函数的参数也是一个字符指针,指向要计算长度的字符串。函数内部定义了一个计数器count,并初始化为0。然后使用while循环遍历字符串,每遇到一个字符就将计数器加`这样,调用my_strlen(arr)将会返回字符串"abc"的长度,即3。

再来按照题目的要求来写本题的代码。

#include<stdio.h>
//递归
/*
  my_strlen("abc")
  1+my_strlen("bc")
  1+1+my_strlen("c")
  1+1+1+my_strlen("")
  1+1+1+0
  3
*/
int my_strlen(char* s)
{
  if (*s == '\0')
    return 0;
  else
    return 1 + my_strlen(s+1);
}
int main()
{
  char arr[] = "abc";
  printf("%d\n", my_strlen(arr));
  return 0;
}


int my_strlen(char* s)

{  // 定义一个名为my_strlen的函数,它接受一个指向字符数组的指针s。  

       if (*s == '\0')  // 如果指针s指向的当前字符是'\0',也就是字符串的结尾,则返回0,表示字符串的长度为0。        

               return 0;    

       else  // 如果指针s指向的不是'\0',则执行下面的代码。        

               return 1 + my_strlen(s+1);  // 返回1(表示当前字符的长度)加上递归调用my_strlen()函数,并传入下一个字符的指针s+1。

}


1. 当我们第一次调用my_strlen函数时,传入的是字符串的首地址,即s指向字符串的第一个字符'a'。

2. 函数内部首先检查s指向的当前字符是否为'\0',即字符串的结尾。由于当前字符是'a',不是'\0',因此进入else分支。

3. 在else分支中,函数返回1 + my_strlen(s+1)。我们传入s+1,也就是指向字符串的下一个字符'b'的指针,然后递归调用my_strlen函数。

4. 在第二次调用my_strlen函数时,传入的是指向'b'的指针s。因为'b'不是字符串的结尾,函数再次进入else分支,并返回1 + my_strlen(s+1)。我们再次传入s+1,指向字符串的下一个字符'c',递归调用my_strlen函数。

5. 在第三次调用my_strlen函数时,传入的是指向'c'的指针s。同样因为'c'不是字符串的结尾,函数再次进入else分支,并返回1 + my_strlen(s+1)。我们再次传入s+1,指向'\0',递归调用my_strlen函数。

6. 在第四次调用my_strlen函数时,传入的是指向'\0'的指针s。因为'\0'是字符串的结尾,函数进入if分支,并返回0。

7. 然后上一层的my_strlen函数收到返回值0,并将其加上1,即1 + 0 = 1,表示字符串长度为1。这个返回值被再次返回给更上一层的函数,即第二次调用my_strlen函数的函数。

8. 第二次调用my_strlen函数收到返回值1,并将其加上1,即1 + 1 = 2,表示字符串长度为2。这个返回值被再次返回给更上一层的函数,即第一次调用my_strlen函数的函数。

9. 第一次调用my_strlen函数收到返回值2,并将其加上1,即1 + 2 = 3,表示字符串长度为3。

最后,3被返回给主函数,输出在屏幕上。这样,使用递归函数的方式,我们就计算出了字符串"abc"的长度,为3。


      2.求n的阶乘。(不考虑溢出)


先来看看我们之前的写法

#include<stdio.h>
int Fac(int n)
{
  int i = 0;
  int result = 1;
  for (i = 1; i < n + 1; i++)
  {
    result *= i;
  }
  return result;
}
int main()
{
  int n = 0;
  scanf("%d", &n);
  int ret = Fac(n);
  printf("%d\n", ret);
  return 0;
}


  首先,程序包含一个头文件<stdio.h>,这个头文件是C语言标准库中的一个头文件,提供了一系列输入输出相关的函数,如printf()和scanf()等。


       接下来是定义函数Fac(),该函数的参数为一个整数n,返回一个整数。函数内部定义了两个变量i和result,分别用于循环计数和最终结果的累乘。其中,循环结构采用了for循环,循环条件为i小于n加1,即从1到n逐个进行累乘操作。每次循环都将result乘以i的值,最终得到的result即为n的阶乘。最后通过return语句将结果返回。、


       在主函数main()中,首先定义了一个整数n,用于接收用户的输入。通过scanf()函数将用户输入的整数赋值给变量n。接下来调用Fac()函数,将n作为参数传入,得到计算结果。最后通过printf()函数将结果输出,并返回0表示程序正常结束。


       总的来说,这个程序比较简单,通过使用(迭代)循环和函数来实现计算n的阶乘的功能。


再来按照题目的要求来写本题的代码。

#include<stdio.h>
int Fac(int n)
{
  if (n <= 1)
    return 1;
  else
    return n * Fac(n - 1);
}
int main()
{
  int n = 0;
  scanf("%d", &n);
  int ret = Fac(n);
  printf("%d\n", ret);
  return 0;
}


这是一个计算阶乘的程序,和之前的程序不同的是,这个程序采用了递归的方式来实现。        


       首先,程序包含一个头文件<stdio.h>,这个头文件是C语言标准库中的一个头文件,提供了一系列输入输出相关的函数,如printf()和scanf()等。        


       接下来是定义函数Fac(),该函数的参数为一个整数n,返回一个整数。函数内部使用了递归的方式来计算阶乘。当n小于等于1时,直接返回1,否则返回n乘以Fac(n-1)的结果,即将问题转化为计算(n-1)的阶乘,并乘以n。这就是递归的过程。


       在主函数main()中,首先定义了一个整数n,用于接收用户的输入。通过scanf()函数将用户输入的整数赋值给变量n。接下来调用Fac()函数,将n作为参数传入,得到计算结果。最后通过printf()函数将结果输出,并返回0表示程序正常结束。


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


斐波那契数列(Fibonacci sequence)是指:后面每一项数都是前面两项数的和。

       即:1, 1, 2, 3, 5, 8, 13, 21, 34, ...

       斐波那契数列可以通过递归的方式来定义,即:F(1) = 1 ; F(2) = 1 ; F(n) = F(n-1) + F(n-2) (n >= 2)其中,F(1)和F(2)是初始值,之后的每一项数都是前面两项数的和。


      程序首先包含了头文件<stdio.h>,这个头文件是C语言标准库中的一个头文件,提供了一系列输入输出相关的函数,如printf()和scanf()等。


       接下来是定义函数Fib(),该函数的参数为一个整数n,返回一个整数。函数内部使用了递归的方式来计算斐波那契数列。当n小于等于2时,直接返回1,否则返回Fib(n-1)加上Fib(n-2)的结果,即将问题转化为计算(n-1)和(n-2)的斐波那契数列之和。这就是递归的过程。


       在主函数main()中,首先定义了一个整数n,用于接收用户的输入。通过scanf()函数将用户输入的整数赋值给变量n。接下来调用Fib()函数,将n作为参数传入,得到计算结果。最后通过printf()函数将结果输出,并返回0表示程序正常结束。


       这个例子比较简单,但通过递归的方式来计算斐波那契数列并不是最优的方式,因为它存在重复计算的问题,所以在实际应用中更多地使用循环的方式来计算斐波那契数列。


循环方式:

#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);
  printf("%d\n", Fib(n));
  return 0;
}


   程序首先包含了头文件<stdio.h>,这个头文件是C语言标准库中的一个头文件,提供了一系列输入输出相关的函数,如printf()和scanf()等。


       接下来是定义函数Fib(),该函数的参数为一个整数n,返回一个整数。函数内部使用了循环的方式来计算斐波那契数列。首先定义了三个整数a、b和c,将a、b、c初始化为1,表示斐波那契数列的初始值。然后通过while循环,当n大于等于3时,就计算c等于a加上b的值,然后将a和b的值分别赋值为b和c,即将问题转化为计算下一个斐波那契数列的值。当n小于3时,循环结束,并将最后一个斐波那契数列的值c返回。


       在主函数main()中,首先定义了一个整数n,用于接收用户的输入。通过scanf()函数将用户输入的整数赋值给变量n。接下来调用Fib()函数,将n作为参数传入,得到计算结果。最后通过printf()函数将结果输出,并返回0表示程序正常结束。


       使用循环的方式来计算斐波那契数列,相比于递归的方式,不会导致栈溢出的问题,代码执行效率也更高,所以在实际应用中更多地使用循环的方式来计算斐波那契数列。


总结:


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

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

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


相关文章
|
7月前
嵌套调用和链式访问
嵌套调用和链式访问
49 0
|
2月前
|
存储 编译器 C语言
C语言函数的定义与函数的声明的区别
C语言中,函数的定义包含函数的实现,即具体执行的代码块;而函数的声明仅描述函数的名称、返回类型和参数列表,用于告知编译器函数的存在,但不包含实现细节。声明通常放在头文件中,定义则在源文件中。
|
7月前
|
Python
函数嵌套调用
函数嵌套调用
70 1
|
2月前
|
Java 程序员 C++
【Python】链式、嵌套调用、递归、函数栈帧、参数默认值和关键字参数
【Python】链式、嵌套调用、递归、函数栈帧、参数默认值和关键字参数
33 0
【Python】链式、嵌套调用、递归、函数栈帧、参数默认值和关键字参数
Python函数:函数的定义和调用
本文详细介绍了Python函数的定义和调用方法,包括基本函数定义、参数传递、返回值、文档字符串、作用域、嵌套函数和闭包。通过一个综合详细的学生成绩管理系统的例子,我们展示了如何在实际编程中应用这些函数概念。希望本文对您理解和应用Python函数有所帮助。
|
5月前
|
语音技术
语音识别----函数基础定义联系案例,函数的参数,函数的参数练习案例,函数的返回值定义语法,函数返回值之None,函数的说明文档,函数的嵌套调用,变量在函数中的作用域,内部变量变全局变量用global
语音识别----函数基础定义联系案例,函数的参数,函数的参数练习案例,函数的返回值定义语法,函数返回值之None,函数的说明文档,函数的嵌套调用,变量在函数中的作用域,内部变量变全局变量用global
|
7月前
|
C语言
C语言函数的嵌套调用详解
C语言函数的嵌套调用详解
228 1
|
7月前
|
存储 Java
Java数组与带参数方法:定义、调用及实践
Java数组与带参数方法:定义、调用及实践
80 1
|
7月前
|
存储 Python 容器
python函数的返回值、嵌套方式以及函数中的变量(一)
python函数的返回值、嵌套方式以及函数中的变量(一)
258 2
|
7月前
|
Python
python函数的返回值、嵌套方式以及函数中的变量(二)
python函数的返回值、嵌套方式以及函数中的变量(二)
247 1
下一篇
DataWorks