云计算百科
云计算领域专业知识百科平台

C语言进阶第四弹——函数(持续更新ing)

Part 5 函数

概念:实现具体功能的一个模块,尽可能的功能单一。

作用:

  • 避免重复代码,提高复用性
  • 模块化:把一个复杂的问题,拆分成一个个小的功能模块。
  • 要求:低耦合,高内聚。(对外部依赖越少越好,工作内容越单一集中越好)

    函数的定义

    语法:

    返回类型  函数名称(数据类型 参数名1,数据类型 参数名2,…)  // ()是参数列表

    {

            return 表达式;

    }  //函数体

    返回值:

    • 类型 int,long,double,float,char,void(表示不返回任何数据)
    • 情况1 表示函数调用是否成功的状态, 0(表示调用成功) 1(调用失败)
    • 情况2 计算的结果。
    • 不能返回数组类型。

    函数名:

    • 标识符
    • 字母,数字,下划线。不能以数字开头
    • 见名知意

    参数:

    • 作用:会与其他模块进行交互。
    • 如果没有参数,参数列表空着;如果有多个参数,用逗号隔开。
    • 参数必须要有数字类型。

    示例:

    在C语言源文件(xxx.c)中,存储实现类的信息

    int cmptwonum(int a, int b) // 形式参数
    {
    if(a>b)
    {
    printf("num1 > num2"\\n);
    }
    else
    {
    printf("num1 <= num2\\n");
    }
    return 0;
    }

    函数的调用

    定义函数 ——> 声明 ——> 调用函数

    函数的声明:

    声明:告诉编译器,有这个函数。

    语法:

    int cmptwonum(int a, int b);  // 在C语言头文件(xxx.h—)中,主要存放声明类信息

    int show();

    函数的调用:

    调用:使用函数,()是一个运算符。

    语法:

    函数名(参数); 

    注意事项:

    • 参数类型匹配,个数要相同。可以是常量、变量、表达式
    • 在调用函数时,会发生跳转,从函数体开始执行,当执行完后,会发回到调用的地方
    • 函数调用完成后,因为是表达式,所以有类型和值。类型和值由return返回。

    示例:

    int main()
    {
    int a =10;
    int b =20;
    cmptwonum(a,b); //实参
    int a1 =30;
    int a2 =50;
    cmptwonum(a1,a2);
    int a3 = 20;
    int a4 =70;
    cmptwonum(a3,a4);
    return 0;
    }

    标识符的作用域和生命周期

    1. 作用域

    概念:能够使用变量的范围,即标识符能被使用的范围,被称为变量的作用域。

    局部变量
    • 在函数内部定义的变量,和函数的参数,都是局部变量。
    • 被包含在{}中。
    • 局部变量的作用域在离该变量定义最近的大括号里。
    • 在一个局部范围内,有定义一个大括号。在大括号内部,可以有同名标识符。那么,内层变量会遮挡外层变量(外层还存在),内层只能访问到内层变量。
    全局变量
    • 变量可以在整个工程中都可以被访问到。

    2. 生命周期

    概念:从变量的创建开始,到变量对应内存空间的回收,这之间的时间就是生命周期。

    局部变量生命周期
    • 从定义变量开始,到函数结束。
    • 从定义处开始,到最近的右大括号结束。
    全局变量生命周期
    • 全局变量的生命周期从 ./a.out 开始运行就创建,直到 ./a.out消亡,变量才会被回收。
    • 全局变量若没有给初值,那么他的初值是0。

    存储类型

    存储类型 数据类型 标识符;

    auto int a;

    register int b;

    extern int c;

    static intd;

    int e;

    auto

    自动类型,就是普通的局部变量。(栈区)

    auto int a == int a;         auto 可省略

    register 

    寄存器,装变量

    register int b;  //告诉编译器,建议把 b 这个变量存储在 cpu 的寄存器中。

    extern  

    extern int fun(int a, int b); //函数的声明,出现在头文件中。

    extern(外部),和全局变量搭配。用于多文件编程时使用。

    假如在1.c 中,定义了一个全局变量。需要在 2.c 使用1.c 中定义全局变量。就要先使用extern 声明后,才可以使用。

    static 

      可执行程序的内存分布图

    • code           自己写代码的地方(只读)。
    • 数据段        装全局变量。在 a.out 开始运行时,就已经创建;a.out 结束,变量才会释放。也叫全局生命周期。
      .bss            未初始化数据段,即没有给初值的全局和静态变量。
      .data           给了初值的全局和静态变量。
    • 堆空间        由程序员手动申请或释放。malloc / free 空间相对较大,可占总内存的80%~90%
    • 映射/共享    C库相关函数的实现(只读),共享库里实现。
    • 栈                有局部变量、函数参数、返回地址。自动创建和回收。大小:8M。栈空间的变量需要初始化,不初始化的话是随机数。
    • static 用法1

      static int d;

      • 使用static修饰变量的话,那么这个变量会存储在静态区(数据区)。
      • 从形式上看,类似全局变量类型,但会受到作用域的限制。
      • 如果没有初始化,默认初值为0。
      static 用法2

      限制作用域,被限制的对象是全局变量或函数。

      //1.c

      int a = 10;
      // 如果希望这个全局变量只能在本模块(1.c)中被使用
      static int a = 10;

      //希望fun函数只能在本模块(1.c)中被使用
      static int fun(); //如果这个函数被声明为static,就不能是extern

      int fun()
      {

      }

       调用补充:实参的个数和形参的个数要相同

      int fun(int num)  // num 形参(形式参数)

      {

              printf("%d\\n", num);

              ………

              return 0; // 返回主调的地方(fun函数当时被调用的地方)
      }
      int main()

      {
              int a = 20;
              fun(a);  // main 调用fun,main 函数称为主调函数,fun 是被调函数

                          // main 把a变量传递给了 fun 函数,a 被称为实参(实际传递的参数)

                          // 当函数调用完毕后,0

           

              int ret = fun(a);

              printf("%d\\n", ret);  // 先对fun(a)求值,printf("%d\\n", 0);

      }

    • 形参和实参的变量名可以不一致。
    • 参数类型匹配,如果类型不一致,会把实参类型向形参类型转换。
    • 形参是实参的复制品。
      形参和实参不是同一个变量,只是把a变量(实参)的内容,复制一份给num变量(形参)。
    • 函数的传参

      1. 值传递

      int fun(int num) // 0
      {
      int result = num++; // result = 0; num =1

      // num++ 对于这个表达式而言, 变量的值和表达式的值,不一样。表达式的值是0(先用后加)。变量的值是1,会产生临时变量,把num自加前的值保存并作为表达式的值,变量直接+1行。

      printf("result %d\\n",result); // 0
      return result; // 0 return 1,2,3,4;
      }
      int fun2(int a,int b,int c)
      {

      }
      int main()
      {
      int i = 0 ;
      fun(i); // 0 ;
      fun2(1,2,(3,3,4,5,6)); // 1,2 传递多个参数 (3,3,4,5,6),逗号表达式,值是最后一个
      printf("%d\\n",i );// 0 num是i的一个复制品,所有在fun中,num(形参)无论如何改变,都无法改变实参值。
      return 0;
      }

      • 实参将值传递给形参,形参是个副本。
      • 形参的值变化无法改变实参的值。

      2. 地址传递

      在函数调用过程中,如果传递的是数组类型,那么这种传递方式就是地址传递。也就是形参获得了实参对应内存空间的地址,所以可以通过形参修改实参的内容。

      • 在被调函数中,是可以通过修改形参,来修改主调中的实参。
      • 在传递数组过程中,如果使用值传递,那么被调函数中也需要创建一样大的数组。有的时候数组可能会很大。在被调函数结束的时候,这段空间就需要回收。C语言中,数组也是值传递的话,效率会很低。
      • 这个就是数组传递的时候,是地址传递,而不是值传递的原因。
      整型数组传参

      void show_array(int a[],int nums) // int [] 地址传递
      {
      int i = 0 ;
      //int len = sizeof(a); // 由于是地址传递 ,sizeof(a),== 8byte ,
      //printf("sizeof(a) %lu sizeof(a[0]) %lu\\n",sizeof(a),sizeof(a[0]));
      for(i=0;i<nums;i++)
      {
      printf("%d: %d\\n",i,a[i]);
      }
      }
      void input_array(int a[],int nums) // a -> int []
      {
      int i = 0;
      for(i=0;i<nums;i++)
      {
      printf("input num:");
      scanf("%d",&a[i]); // a[0]–> int &a[i]
      }
      return ;
      }
      int main()
      {
      int a[3]={1,2,3}; // int []

      printf("before:");
      show_array(a,3);

      input_array(a,3);

      printf("\\n change after:")

      show_array(a,3);
      }

      字符串类型传参
    • 形参依然可以修改实参
    • 在被调函数定义过程中,只需要传递数组名,不需要长度。因为每个字符串都有结束标志 \\0
    • void show_str(char str[])
      {
      int i = 0 ;
      for(i=0;'\\0'!=str[i];i++)
      {
      printf("%c",str[i]);
      }
      }
      int main()
      {
      char str[100]={0};
      printf("input str:");
      int len = sizeof(str)/sizeof(str[0]);
      my_gets(str,100); // char []
      show_str(str);
      //printf("str:%s\\n",);

      return 0;
      }

      赞(0)
      未经允许不得转载:网硕互联帮助中心 » C语言进阶第四弹——函数(持续更新ing)
      分享到: 更多 (0)

      评论 抢沙发

      评论前必须登录!