C语言基础03——函数。字符反向排序、逆序输出单词

目录

函数是什么?

库函数

自定义函数

函数调用

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

函数的声明和定义

函数递归

函数题


函数是什么?

​ 函数是可以完成某个特定功能的并且可以重复利用的代码片段。一般一个函数就是一个功能单元,假设在以后的开发中,某个功能是可以独立抽取出来的,建议定义为函数,这样以后要是需要实现这个功能,那么直接调用这个函数即可,不再需要重复编写。

库函数

  • 什么是库函数?

    如printf()打印函数、strcpy()字符串拷贝函数、pow()次方运算函数,像这些完成基础性功能的函数,我们每个人在开发的过程中都可能用的到,为了支持可移植性和提高程序的效率,所以C语言的基础库中提供了一系列类似的库函数,方便程序员进行软件开发。

  • C语言常用的库函数

    - IO函数
    - 字符串操作函数
    - 字符操作函数
    - 内存操作函数
    - 时间/日期函数
    - 数学函数
    - 其他函数
    
  • 如何学习库函数?

    • 使用库函数必须:包含其对应的头文件
    参照文档
    - http://www.cplusplus.com/reference/
    
    查询工具使用:
    - MSDN(Microsoft Developer Network)
    - www.cplusplus.com
    - http://en.cppreference.com(英文版)
    - http://zh.cppreference.com(中文版)
    
    • strcpy()函数

      /*
       * 函数原型:char *strcpy(char *dest,const char *src)  
       * 作用:将src指向的C字符串复制到dest指向的数组中,包括终止的空字符(并在该点停止)。
       * dest——用于复制内容的目标数组;           src——要复制的字符串
       * 需要注意:如果目标数组的dest不够大,而src中的字符串长度又太长,可能会造成缓冲溢出的情况
       * 返回值:返回一个指向最终的目标字符串dest的指针
       */
      #include <stdio.h>
      #include <string.h>
      
      int main()
      {
          char arr1[20] = {0};
          char arr2[] = "hello world!";
          strcpy(arr1,arr2);
          printf("%s",arr1);//打印arr1这个字符串
          return 0;
      }
      
    • memset()函数

      /*
       * 函数原型:void * memset(void * ptr,int value,size_t num);
       * 作用:将ptr指向的内存块的前num字节设置为指定值。
       *  - ptr:指向要填充的内存块的指针
       *  - value:要设置的值。该值作为int传递,但该函数使用该值的无符号字符转换填充内存块。
       *  - num:被设置为value值的字节数。size_t是无符号整数类型。
       */
      #include <string.h>
      #include <stdio.h>
      
      int main()
      {
          char arr[] ="hello world";
          //将arr数组的前5个字节替换为x
          memset(arr,'x',5);
          printf("%s",arr); //xxxxx world
      }
      

自定义函数

  • 函数怎么定义?函数的语法机制

    函数返回类型 函数名(形式参数列表)
    {
    	函数体;
    }
    
    - 函数的返回值类型可以是任何类型,只要是C语言中的类型就可以。
    
    - 什么是返回值?
      返回值一般指一个函数执行之后的结果。结果通常是一个数据,所以被称为“值”,也叫“返回值”。函数就是为了完成某个特定的功能,函数结束之后大部分情况下都是有一个结果的,体现结果的一般都是数据,数据要有类型,这就叫返回值类型。
      函数执行结束之后,返回值实际上是给调用者了。谁调用就返回给谁。
      
    - 当一个函数执行结束,不需要任何返回值时,返回类型不能空着,要写上:void。void就表示该函数执行结束不返回任何结果。
    
    - 如果返回值类型不是void,那么在函数执行结束时必须用“return 值;”这样的语句来反成值的返回,否则编译报错。
    
    - 只要有return关键字的语句执行,当前函数必然结束。
    
    - 如果返回值类型是void,那么在方法中不能有“return 值;”这样的语句。
      但是可以有“return;”语句,这个语句的作用是:终止当前方法
      
    - void之外,剩下的函数都必须有“return 值;”这样的语句
    
    - 如果定义函数时,没有写返回类型,则返回类型默认是int。但是不建议不写。
    
    
    函数名:
    - 函数名应该见名知意。
    - 函数名在标识符命名规范中,要求首字母小写,后面的每个单词首字母大写。只要是合法的标识符就可以。
      
    形式参数列表:
    - 形式参数只有在函数被调用的过程中才会实例化(分配内存空间),所以被叫做形式参数
    - 其中每一个参数都是“局部变量”,函数调用完之后自动销毁释放内存。
    - 函数的形参个数是:0~N个
    - 函数形参有多个的时候,用英文状态下的逗号“,”隔开。
    - 参数的数据类型起决定性作用,参数对应的变量名是随意的。
    
    实际参数:
    - 调用函数时传递给函数的参数,叫做实参。实参可以是:变量、常量、表达式、函数等。
    - 无论何种类型,在进行函数调用时,都必须有确定的值,以便传递给形参。
    
    函数体:
    - 由C语句构成,以;结尾。
    - 函数体中编写的是业务逻辑代码,完成某个特定的功能。
    - 在函数体中的代码是遵循自上而下的顺序逐行执行。
    - 在函数体中处理业务逻辑代码的时候,数据来源就是函数的参数。
    
    函数定义之后如何调用?
    - 函数必须调用才能执行。
    - 调用语法:函数名(实际参数);
      实际参数要与函数定义时的参数个数一致,并且类型也要一一对应。
        
    C语言不允许函数的嵌套定义
    - 不能在一个函数中去定义另一个函数
    - 我们可以在一个函数中放零一个函数的声明,但是不可以定义另一个函数
    
  • 程序返回的值有什么作用?

    /*
     * int main()函数是主函数
     * - 因为main()并不传递参数,所以可以将其写为int main(void)吗?
     *   编译不会有问题。
     * - main()函数结束时,return 0回返回值到调用main()函数的地方,也就是返回给操作系统
     *   表示这个程序运行之后返回了一个什么样的值,这些值也是有意义的。
     *   传统上,一个程序如果正常结束,就返回0。返回非0的值,则表示出现了错误
     * - 如在windows中,我们可以在批处理文件中,调用我们的这个程序。
     *   if errorlevel 1  如果程序返回1,做什么事情。
     * - 在Unix Bash下,当运行完程序之后,可以使用指令:echo $来查看程序运行返回的值。
     *   如果在Csh下,使用指令echo $status查看。
     */
    
  • 当定义的函数没有参数时,应如何写

    //当函数没有参数时,该如何定义?是void f(void),还是void f()
    /*
     * 两者的区别:
     * - void f(void) 函数中,括号中的viod表示这个函数没有参数。
     * - 而void f()函数中,在传统C标准下,()里什么都不写,表示函数的参数未知,而不是表示没有参数。
     * 所以,我们如果函数没有参数,我们就要选择:void f(void)这种写法。
     */
    int SUM()
    {
        return 10;
    }
    
    int main()
    {
        int a = 3;
        int b = 10;
        //当我们定义函数时,没有写类型,但是调用函数时,我们传入了值,编译并没有报错。还可以执行函数
        printf("%d",SUM(a,b));//10
        //当我们定义函数时,写为(void)后,调用该函数时传值,就会出现语法错误。
        //所以如果没有参数,我们就将定义为:int SUM(void){return 10}
        return 0;
    }
    
  • 调用函数时的逗号与逗号运算符如何区分?

    void f(int a,int b)
    {
        printf("%d %d\n",a,b);
    }
    
    int main()
    {
        int a = 5;
        int b = 10;
        //当调用函数时,如果()里没有其他的括号,那么此时的逗号就是分割函数参数的:
        f(a,b); //5 10
        //如果括号中还有其他括号,此时括号中的括号中的逗号就是逗号表达式。
        f((a,1),(b,250));   //1 250
        return 0;
    }
    
  • 这是什么?

    //定义一个i变量、一个j变量、返回值是int,有两个int参数的sum()函数
    //虽然这样定义可以,但是不推荐这样定义函数。
    int i,j,sum(int a,int b);
    
    //return 是一个语句,后面如果要跟东西,可以是一个值/表达式。
    //i被()括起来,没有什么意义。虽然有()是把i看为一个整体。
    //但是会让人误解其是一个函数,所以不推荐这样写。
    return (i);
    
  • 定义函数,比较两个整数,返回其较大值

    #include <stdio.h>
    int get_max(int x, int y)
    {
        if(x>y)
        {
            return x;
        }
        return y;
    }
    int main()
    {
        int a = 10;
        int b = 20;
        //函数的调用:函数名(参数)
        int max = get_max(a,b);
        printf("%d\n",max);
        return 0;
    }
    
  • 定义函数,交换两个变量的值。

    #include <stdio.h>
    //交换两个变量的值。函数返回类型为void,表示这个函数不返回任何值。
    void Swap(int x,int y)//传值调用
    {
        int z = 0;
        z = x;
        x = y;
        y = z;
    }
    int main()
    {
        int a = 10;
        int b = 20;
        printf("交换前:a=%d,b=%d\n",a,b);
        Swap(a,b);
        printf("交换后:a=%d,b=%d\n",a,b);
    }
    /*
     * 当前函数输出:交换前:a=10,b=20  交换后:a=10,b=20
     * 思考:为什么调用方法之后,变量没有交换?
     * - 调试之后发现:我们只是将a与b的值传入了Swap()函数中。然后Swap()函数又为其x、y开辟了内存空间。
     *   实际完成的是其局部变量x与y的交换,并不是我们传入的a和b变量的交换。
     * - 实参传递给形参,传递的只是值。而函数内改变了形参,形参无法传递到实参。所以无法交换
     */
    

    改正

    #include <stdio.h>
    //我们实际要改变的是a、b的内存空间中存储的值。可以通过传入指针变量,通过指针变量修改其值。
    //所以定义时,参数要定义为指针变量类型,也就是加*。
    void Swap(int* px,int* py)//传址调用
    {
        int z = 0;
        z = *px;
        *px = *py;
        *py = z;
    }
    int main()
    {
        int a = 10;
        int b = 20;
        printf("交换前:a=%d,b=%d\n",a,b);
        //因为需要传入指针变量,也就是要传入变量地址,使用&
        Swap(&a,&b);
        printf("交换后:a=%d,b=%d\n",a,b);
        //此时输出:交换前:a=10,b=20  交换后:a=20,b=10  交换成功!
    }
    /*
     * 我们发现在比较两个数谁大的时候,传入值就可以了。但是交换变量的时候就需要传递地址。
     * 思考:函数的参数什么时候要定义为指针变量类型?什么时候需要传递变量的地址呢?
     * - 在我们求较大值的时候,我们只是需要知道谁大谁小,并不会改变变量的值,所以不需要传变量地址。
     * - 而在我们做变量交换的时候,我们需要改变传入的变量的值,而在外部想要引用到该局部变量。只能通过地址的方式。
     *   此时,参数就需要定义为指针变量,通过其内存地址,才能改变其值。
     */
    

函数调用

如果只是要使用传入变量的值,不修改传入变量,则选择传值调用方式。

如果需要改变传入的实参,要修改外部变量。则定义时需要定义类型为指针变量,调用时需要传址调用。

  • 传值调用

    如果是传值调用,则函数被调用后,为形参开辟内存空间,存储实参传递的值。

    形参与实参具有不同的内存地址,对形参的修改不会影响实参。

    /*
     * 传值调用:形参与实参并没有底层联系。形参只是拷贝了实参的值。改变形参的值,不会影响到形参。
     * 函数执行时,再次为形参开辟内存空间,存储实参传递的值。
     * 只需要传值调用的函数,正常定义就好。
     */
    #include <stdio.h>
    void Swap(int x,int y)
    {
        int z = 0;
        z = x;
        x = y;
        y = z;
    }
    int main()
    {
        int a = 10;
        int b = 20;
        printf("交换前:a=%d,b=%d\n",a,b);
        //传值调用。只是将变量的值传递进函数。
        Swap(a,b);
        printf("交换后:a=%d,b=%d\n",a,b);
    }
    
  • 传址调用

    传址调用:函数的参数需要被定义为:指针变量。调用时需要传递内存地址:&变量名。

    如果是传址调用,则函数被调用后,传递进的是实参的内存地址。我们就可以在函数内对传入的外部的变量,进行修改。这个修改,修改的是传入的实际参数。

    本来实参不在函数内部,是不可以操作的,但是通过传址调用,得到了实参的内存地址,我们就可以通过内存地址进行操作了。

    /*
     * 如果是需要改变实参的值,则函数参数需要定义为指针变量。这样调用时通过传址调用,就可以在函数内就可以操作外部传入实参了。
     * 传址调用:传入函数外创建的局部变量的内存地址,在函数内部直接操作函数外部的变量。
     */
    #include <stdio.h>
    void Swap(int* px,int* py)
    {
        int z = 0;
        z = *px;
        *px = *py;
        *py = z;
    }
    int main()
    {
        int a = 10;
        int b = 20;
        //传址调用:传入变量的内存地址
        Swap(&a,&b);
    }
    
  • 练习

    • 100~200之间的素数,用函数判断这个数是不是素数

      #include <stdio.h>
      
      int is_prime(int n)
      {
          int j;
          for (j = 2; j <n-1 ; j++) {
              if(n%j == 0){
                  return 0;
              }
          }
          return 1;
      }
      int main()
      {
          //100~200之间的素数
          int i,count;
          count = 0;
          printf("100~200之间的素数:");
          for(i=100; i<=200 ;i++)
          {
              if(is_prime(i) == 1){
                  printf("%d ",i);
                  count++;
              }
          }
          printf("\n一共有:%d个",count);
          return 0;
      }
      
    • 使用函数判断闰年

      #include <stdio.h>
      int is_leap_year(int y)
      {
          if( y%4==0 && y%100!=0 ||y%400==0)
          {
              return 1;
          }
          return 0;
      }
      int main()
      {
          int y,count;
          count = 0;
          printf("1000~2000之间的闰年:");
          for( y =1000; y<=2000 ; y++)
          {
              if(is_leap_year(y))
              {
                  printf("%d ",y);
                  count++;
              }
          }
          printf("\n一共有:%d个",count);
          return 0;
      }
      
    • 写一个函数,实现有序数组的二分法查找

      #include <stdio.h>
      
      int binarySearch(int arr[],int end,int num)
      {
          //起始元素下标
          int begin = 0;
      
          //循环条件:起始元素下标 在 终止元素下标 的右边
          while (begin <= end){
              //中间元素下标
              int mid = (begin+end) / 2;
      
              if(arr[mid] == num)
              {
                  return mid;
              }
              else if(arr[mid] < num)
              {
                  begin = mid +1;
              }
              else
              {
                  end = mid -1;
              }
          }
          //如果循环没有结果,说明没有这个数字
          return -1;
      }
      
      int main()
      {
          int arr1[] ={15,23,46,79,88,110,236,333,450,555};
      
          //终止元素下标 :数组元素个数-1
          int end = sizeof(arr1)/sizeof(int) -1;
          /*
           * 我们传递了数组进去,那为什么不在函数内计算数组的元素个数呢?
           * - 数组arr传参,实际上传递的并不是数组本身,而是数组首元素的地址。是一个指针变量。
           *   如果在函数内部求,就成了int end = 4/4 = 1
           * - 所以我们要先计算好数组结束元素下标,再传递进去。
           */
          int index = binarySearch(arr1,end,333);
          index == -1 ? printf("数组中没有该数字") : printf("该数字下标是:%d",index);
          return 0;
      }
      
    • 写函数,实现每调用一次,就会将num的值加1。

      //因为是要修改变量,所以肯定是定义为指针变量,并传址调用
      
      #include <stdio.h>
      
      void Add(int *p)
      {
          (*p)++;
      }
      int main()
      {
          int num = 0;
          Add(&num);
          printf("%d\n",num);//1
      
          Add(&num);
          printf("%d\n",num);//2
      
          Add(&num);
          printf("%d\n",num);//3
      
          Add(&num);
          printf("%d\n",num);//4
          return 0;
      }
      

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

  • 嵌套调用

    不是只有在main()函数中才能调用方,任何一个函数中都可以调用其他函数。

    main()函数中调用doThat()函数,doThat()函数又调用doSome()函数,这就叫做嵌套调用

    #include <stdio.h>
    /*
     * main() begin
     * doThat() begin
     * doSome() begin
     * doSome() over
     * doThat() over
     * main() over
     */
    void doSome()
    {
        printf("doSome() begin\n");
        printf("doSome() over\n");
    }
    void doThat()
    {
        printf("doThat() begin\n");
        doSome();
        printf("doThat() over\n");
    
    }
    int main()
    {
        printf("main() begin\n");
        doThat();
        printf("main() over\n");
    
        return 0;
    }
    
  • 链式访问

    把一个函数的返回值作为另一个函数的参数。

    #include <string.h>
    #include <stdio.h>
    
    int main()
    {
        int len = strlen("abc");
        printf("%d\n",len);//3
    
        //链式访问
        printf("%d\n",strlen("abc"));//3
    
        char arr1[20] = {0};
        char arr2[] = "hello world";
    
        //链式访问
        printf("%s\n", strcpy(arr1,arr2));//hello world
    
        //printf返回值:打印在屏幕上的字符个数。
        //printf("%d",43)打印出了43,返回值是字符个数,也就是2
        //printf("%d",2)打印出2,返回值是字符个数,也就是1
        //printf("%d",1)打印出1。
        //合起来就是:4321
        printf("%d",printf("%d",printf("%d",43)));//4321
    
        return 0;
    }
    

函数的声明和定义

  • 函数的声明

    - 告诉编译器有一个函数,这个函数的参数、返回类型是什么。但具体是不是存在这个函数,不是由函数声明决定的。
      函数如果只是声明了,并没有去定义,然后在main()方法中调用。编译会报错:未定义的引用。
    
    - 函数的声明一般出现在函数的调用之前。要满足:先声明后使用。
      编译器是从上往下扫描的。如我们之前定义函数都是在mian()函数之前定义,在我们调用之前,编译器已经扫描过那个函数了,知道我们调用的哪个函数。
      如果我们定义到main函数之后,在main函数之中使用这个函数,编译器扫描到调用函数的那一行时,就会报错。所以我们要在调用之前声明这个函数。
      函数声明语法:返回类型 函数名(形参列表);
      
    - 函数的声明一般要放在头文件中。
    
    • 之前我们书写的方式:函数定义到main()方法前。

      函数在定义之后需要在使用之前声明。函数直接定义在使用之前,效果一样。

      #include <stdio.h>
      
      void printHehe()
      {
          printf("hehe");
      }
      int main()
      {
          printHehe();
          return 0;
      }
      
    • 实际上并不是所有函数都定义在main()函数之前,此时就需要提前声明。

      #include <stdio.h>
      
      //函数声明
      void printHehe();
      
      int main()
      {
          //void printHehe();  //也可以在这里声明
          printHehe();
          return 0;
      }
      
      //函数声明之后,也要具体实现,不然编译也会报错:undefined reference to 'printHehe'
      void printHehe()
      {
          printf("hehe");
      }
      
    • 函数声明一般要放在头文件中

      pr.h 函数声明

      #ifndef FIRST_PR_H
      #define FIRST_PR_H
      
      //函数声明
      void printHaha();
      
      #endif //FIRST_PR_H
      

      pr.c 函数定义

      #include <stdio.h>
      
      void printHaha()
      {
          printf("hahahaha");
      }
      

      test.c 函数调用

      /*
       * - 不管是标准头文件,还是自定义头文件,都只能包含变量和函数的声明,不能包含定义,否则在多次引入时会引起重复定义错误。
       * - 使用尖括号< >,编译器会到系统路径下查找头文件;
       *   而使用双引号" ",编译器首先在当前目录下查找头文件,如果没有找到,再到系统路径下查找。
       * - 一般使用尖括号来引入标准头文件,使用双引号来引入自定义头文件(自己编写的头文件),这样一眼就能看出头文件的区别。
       */
      //引入头文件
      #include "pr.h"
      
      int main()
      {
          printHaha(); //hahahaha
          return 0;
      }
      
  • 函数定义:函数的定义是指函数的具体实现,交代函数的功能实现。

  • 为什么要区分.h头文件与.c源文件呢?

    /*
    比如我们编写实现了某个功能,但是别人要拿去用。这个功能是我们花了很长时间做的,我们又不想把源码给他,不想给他源文件,此时应该怎么办呢?
    - 制作成静态库,然后将头文件跟这个库一起给他,因为头文件中只是我们声明的函数,而静态库文件中是乱码,具体实现代码就很好的隐藏了。
    - 它使用的时候引入头文件,并导入静态库。根据其头文件中的函数声明使用即可。
    */
    

函数递归

  • 函数递归

    - 什么是函数递归?
      函数自己调用自己,就叫函数递归。
      通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需要少量的程序就可以描述出解题过程中是所需要的多次重复计算,大大减少了程序的代码量。
      递归的主要思考方式:大事化小。
      
    - 使用递归的必要条件
      存在条件,当条件满足时,就不再递归。每次递归调用之后,应该越来越接近满足这个条件
      
    - 当递归时,程序没有结束条件,一定会发生:
      栈溢出错误:Stack Over flow Exception
    
    - 假设递归是有结束条件的,就一定不会发生栈内存溢出错误吗?
      假设这个结束条件是对的,是合法的,递归有的时候也会出现栈内存溢出错误。因为可能递归的太深,栈内存不够了。
      
    - 在实际开发中,不建议轻易的使用递归。能用for循环、while循环代替的,尽量使用循环来做,因为循环效率高,耗费的内存极少;递归耗费的内存比较大。但是在极少数的情况下,不使用递归,这个程序没有办法实现。
    
    - 在实际开发中,如果遇到了栈内存溢出错误,应如何解决?
      检查递归的结束条件对不对,如果结束条件不对,必须修改,直到正确为止。
    

    示例:递归调用main()函数,死循环,一直输出hehe

    #include <stdio.h>
    int main()
    {
        printf("hehe\n");
        main();
        return 0;
    }
    
  • 内存

    栈区存储:
    - 局部变量
    - 函数执行时,产生的局部变量。在函数被调用时,该函数需要的内存空间在栈中分配。
      函数调用时:压栈,分配空间    函数结束时:弹栈,释放空间。  
    栈空间特点:先进后出,后进先出原则。
    
    堆区存储:
    - 动态内存分配
    
    静态区存储:
    - 全局变量
    - 静态变量
    
  • 递归的应用

    • 接收一个无符号的整型值,按照顺序打印他的每一位

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

      /*
       * 输入:1234后,函数执行过程:
       * - main函数中调用print(1234)
       * - print(1234)执行,1234 > 9 ,继续调用print(1234/10),也就是print(123)
       * - print(123)执行,123 > 9 ,继续调用print(123/10),也就是print(12)
       * - print(12)执行,12 >9 ,继续调用print(12/10),也就是print(1)
       * - print(1)执行,1<9,函数不再递归,%d形式打印,表达式1%10 = 1  ,此时打印了1 。print(1)执行结束,弹栈释放内存。
       * - print(12)继续执行,%d形式打印,表达式 12%10 = 2,此时打印2 。print(12)执行结束,弹栈释放内存。
       * - print(123)继续执行,%d形式打印,表达式 123%10 = 3 ,此时打印了3 。 print(123)执行结束,弹栈释放内存。
       * - print(1234)继续执行,%d形式打印,表达式 1234%10 = 4 ,此时打印了4 。 print(1234)执行结束,弹栈释放内存。
       * - main()函数执行完毕,弹栈,程序结束。
       *
       * 输入其他数字同理。
       */
      #include <stdio.h>
      
      void print(unsigned int n)
      {
          //如果不是个位数,就/10
          if(n > 9)
          {
              //递归调用print()
              print(n / 10);
          }
          //直到是个位数之后打印出来
          printf("%d ",n % 10);
      }
      
      int main()
      {
          unsigned int num = 0;
          //%u,表示无符号的
          scanf("%u",&num);
          //调用print函数
          print(num); //print函数可以打印参数部分数字的每一位
      }
      
    • 编写函数,求字符串长度

      /*
       * 以下程序执行过程:
       * - main()函数调用,char数组创建,其中存储数据dog,printf("%d\n",my_strLen(arr));执行
       * - my_strLen(arr)执行,其中存储的是数组首元素的内存地址,其中存储的'd'!='\0',输出,并执行return 1+my_strLen(str+1);
       *   注意这里传进来的是str,也就是dog'\0',比较的时候用的指针变量,*str则表示其是首元素地址,也就是第一个字符。
       * - 1+my_strLen(str+1)执行,str+1表示’d‘的下一个内存地址。此时函数内的str就变成了og'\0',*str是'o'!='\0'
       *   输出并执行return 1+my_strLen(str+1);
       * - 1+my_strLen(str+1)执行,str+1表示’o‘的下一个内存地址。此时函数内的str就变成了g'\0',*str是'g'!='\0'
       *   输出并执行return 1+my_strLen(str+1);
       * - 1+my_strLen(str+1)执行,str+1表示’g‘的下一个内存地址。此时函数内的str就变成了'\0',*str是'\0'='\0'
       *   此时if中条件为0,不再进行函数递归。return0
       * - return 0+1之后,再次return 1+1,再次return 1+2,就返回3。
       */
      #include <stdio.h>
      
      //因为是计算数组中字符的个数,所以应该是返回一个数。因为是要读取数组本身,所以参数要传入指针变量。
      int my_strLen(char* str)
      {
          //如果传递进来的这个地址对应的不是'\0'字符串结尾字符,就递归调用
          //*str就表示我们传递进来的元素的内存地址,这里如果写成str,就
          if(*str != '\0')
          {
              //因为str是一个指针变量,其中存储的是数组首元素的内存地址
              //又因为数组中存储的元素内存地址都是连续的,所以str+1就表示下一个元素的内存地址。
              //这里不是*str,是因为str+1本身就是地址,我们传入之后,函数直接操作就可以。
              return 1+my_strLen(str+1);
          }
      
          //如果str中读取的是\n,也就是说读取到字符串末尾了,则返回0
          return 0;
      }
      
      int main()
      {
          char arr[] = "dog";
          printf("%d\n",my_strLen(arr));
      }
      
    • 求n的阶乘

      #include <stdio.h>
      int fac(int n )
      {
          if(n>1)
          {
              return n*fac(n-1);
          }
          return 1;
      }
      int main()
      {
          printf("%d", fac(6));
          return 0;
      }
      
    • 求第n个斐波那契数

      //斐波那契数:前两个数的和等于第三个数: 1 1 2 3 5 8 13 21 34 55......
      #include <stdio.h>
      //递归可以求解,但是效率太低了。
      int count = 0;
      
      int Fib(int n )
      {
          count++;//统计递归的执行次数
          if(n> 2)
          {
              return Fib(n-1)+ Fib(n-2);
          }
          return 1;
      }
      
      int main()
      {
          printf("%d\n",Fib(40));
          printf("递归总次数:%d",count);//递归总次数:204668309
      }
      

      使用循环的方式

      #include <stdio.h>
      
      int count = 0;
      
      int Fib(int n )
      {
      
          int a = 1;
          int b = 1;
          int c = 0;
          if(n ==1 || n ==2){
              count = 1;
              return 1;
          }
          while(n>2)
          {
              count++;
              c =a+b;  //把这里两个数加起来
              a = b;   //把b的值赋给a
              b = c;   //把c的值赋给b,这样下一次计算c,就是前面两个数的值相加
              n--;
          }
          return c;
      }
      
      int main()
      {
          printf("%d\n",Fib(40));
          printf("循环次数:%d",count);//循环次数:38
      }
      
    • 递归练习:汉诺谈、青蛙跳台阶。

函数题

  1. exec((v1,v2),(v3,v4),v5,v6)中有几个形参

    - exec((v1,v2),(v3,v4),v5,v6)中前两个是逗号表达式
    - 逗号表达式,只返回最后一个表达式的结果
    - 实际上就是exec(v2,v4,v5,v6)
    
  2. 打印乘法口诀

    #include <stdio.h>
    
    void mulTable(int num)
    {
        int i,j;
        for (i=1;i<=num;i++)
        {
            for (j=1;j<=i;j++)
            {
                printf("%d*%d=%d ",i,j,i*j);
            }
            printf("\n");
        }
    }
    
    int main()
    {
        int num;
        printf("请输入要打印的乘法口诀行数:");
        scanf("%d",&num);
        mulTable(num);
        return 0 ;
    }
    
  3. 对字符进行反向排序,并不是逆序打印

    • 使用循环的方式

      //编写一个函数reverse_string(char* string)  要求:不能使用库函数中的字符串函数。
      //递归实现,将字符数组反向排列,并不是逆序打印。如char arr[] ="abcdef"  调用之后 arr[] = "fedcba"
      //思路:a与f交换,b与e交换,c与d交换,借助另一个变量,实现begin与end的互换。
      
      #include <stdio.h>
      
      int myStrLen(char* str)
      {
          int count = 0;
          while(*str != '\0')
          {
              count++;
              str++;
          }
          return count;
      }
      
      void reverseString(char* str) {
          int left = 0;
          //编写函数,求字符串长度。长度-1就是数组最右侧的坐标
          int right = myStrLen(str) - 1;
          //当left<right时,进行交换
          while(left<right)
          {
              //左放到中间
              //通过解引用的方式*(str+left) 与str[left]相同,都是一个字符的内存地址
              char mid = str[left];
              //右放到左
              str[left] = str[right];
              //中间放到右
              str[right] = mid;
              //交换完之后,左向右移动,右向左移动
              left++;
              right--;
          }
      }
      int main()
      {
          char arr[] = "abcdef";
          reverseString(arr); //数组arr中存储的是arr首元素的内存地址
          printf("%s",arr);
          return 0;
      }
      
    • 使用递归

      //编写一个函数reverse_string(char* string)  要求:不能使用库函数中的字符串函数。
      //递归实现,将字符数组反向排列,并不是逆序打印。如char arr[] ="abcdef"  调用之后 arr[] = "fedcba"
      //思路:a与f交换,b与e交换,c与d交换,借助另一个变量,实现begin与end的互换。
      
      #include <stdio.h>
      
      int myStrLen(char* str)
      {
          int count = 0;
          while(*str != '\0')
          {
              count++;
              str++;
          }
          return count;
      }
      
      //递归思路:每次将头元素保存起来,尾元素放到头元素,然后将尾元素位置放'\0'。这样递归时,其中的字符串就有结束的位置,不会取到原来的字符串结尾。
      // 递归调用,中间的字符串也是取头尾。当不再递归时,再把保存了的头元素放入尾元素的位置。
      void reverseString(char* str) {
          //将左边的放到中间
          char mid = *str;
          //当前字符串长度
          int len = myStrLen(str);
          //把最后面的放到头部。最后面的地址就是:str+长度-1
          *str = *(str+len-1);
          //然后让最后面的等于结束字符
          *(str+len -1) = '\0';
          //只要有两个及以上的字符串,就递归该方法。当中间只有一个字符或中间没有字符时,不需要调换顺序。
          if(myStrLen(str+1) >=2)
          {
              //传入str+1,就表示下一个字符的内存地址
              reverseString(str+1);
          }
          //当调换完字符之后,再将我们保存好的头元素放入对应的尾位。
          *(str+len -1) = mid;
      }
      int main()
      {
          char arr[] = "abcdef";
          reverseString(arr); //数组arr中存储的是arr首元素的内存地址
          printf("%s",arr);
          return 0;
      }
      
  4. 计算一个数的每位之和(递归实现)

    //写一个递归函数DigitSum(),输入一个非负整数,返回组成它的数字之和。
    //如DigitSum(1729),1+7+2+9=19,应该返回19。
    #include <stdio.h>
    
    int DigitSum(int num){
        if(num >9)
        {
            return num%10 + DigitSum(num/10);
        }
        return num;
    }
    
    int main()
    {
        int num = 8848;
        printf("%d",DigitSum(num));
        return 0;
    }
    
  5. 编写一个函数实现n的k次方,使用递归实现。

    #include <stdio.h>
    
    double mul(int x,int y)
    {
        if(y == 0){
            return 1;
        }
        else if(y > 0 )
        {
            //如果是1次方,则x*mul(x,1),y=1返回1,x*1返回x。
            return x* mul(x,--y);
        }
        else
        {//如果是2的-3次方,则是 1/2的3次方,因为肯定是个小数,所以用1.0
            return 1.0/mul(x,-y);
        }
    
    }
    
    int main()
    {
        int m,n;
        printf("输入数字以及数字的次方:");
        scanf("%d %d",&m,&n);
        double num = mul(m,n);
        printf("%lf",num);
    
        return 0;
    }
    
  6. 逆序输出单词

    gets()函数

    /*
     * char * gets(char * str);
     * 功能:从标准输入读取字符并将它们作为C字符串存储到str中,直到达到换行符或文件结尾。
     *      如果读取到换行符,则不会将其复制到str中,而是在复制到str的字符之后自动附加一个字符串结尾符。
     * 参数:str —— 这是指向一个字符数组的指针,该数组中存储了C字符串。
     * 返回值:
     * - 成功,函数返回str;
     * - 如果到达文件结尾还未读取到任何字符,则返回NULL,并且str的内容保持不变
     * - 如果发生读取错误,则设置错误提示符(ferror),并返回空指针,但str指向的内容可能已经更改。
     */
    
    #include <stdio.h>
    #include <string.h>
    
    void reverse(char* left,char * right) {
        //当left<right时,进行交换
        while(left<right)
        {
            //交换
            char mid = *left;
            *left = *right;
            *right = mid;
            //交换完之后,左向右移动,右向左移动
            left++;
            right--;
        }
    }
    int main()
    {
        char arr[100] ={0};
        gets(arr);
        //求数组中字符串的长度
        int len = strlen(arr);
        //三步翻转法
        //1.逆序整个字符串//数组名表示首元素地址,+长度-1就是数组最后一个元素的地址
        reverse(arr,arr+len-1);
        //2.逆序每个单词
        char * start = arr;  //将首元素的地址赋给start
        //当*start不是'\0'时循环继续,如果start是'\0'了,'\0'就是0,循环就会结束
        while(*start)
        {
            //将起始坐标赋给结束坐标end
            char * end = start;
    
            //我们发现如果一个单词后面有多个空格,那么每次循环只会逆序一个空格,有点浪费
            //所以如果是进来的是空格,那么我们就让起始地址+1,然后跳出本次循环
            if(*start == ' ')
            {
                start += 1;
                continue;
            }
    
            //如果当前字符不是空字符并且也不是字符串结束字符,说明不是单词结尾,也不是字符串结尾
            while(*end != ' ' && *end!='\0')
            {
                end++;
            }
            //执行到这里,说明*end此时指向的是' '或'\0',end-1就是' '或'\0'前一个字符,也就是数组尾元素。
            //逆序单词
            reverse(start,end-1);
    
            //如果end是空字符' ',说明后面还有单词,则开始元素是end+1。
            if(*end == ' ')
            {
                start = end+1;
            }
            //如果end是字符串结束符,则start=end,此时判断条件就为'\0',也就是0,循环结束。
            else
            {
                start =end;
            }
        }
        printf("%s",arr);
        return 0;
    }
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值