函数入门

前言

在 C 语言的编程中,其实用到很多的函数,而每一个函数就可以理解为一个独立的模块,因此 C 语言也称为模块化编程。

我们封装函数应该尽可能左到:低耦合,高内聚

function_Started_1

对于函数的使用者来说,应该尽可能简单地去使用该函数接口,使用者只管往函数中输入需要的数据,通过该函数获得一个结果即可

function_Started_2

函数的定义

  1. 函数头:函数对外公开的接口信息。 比如:void *calloc(size_t nmemb, size_t size);

    • 函数的返回值 , 该函数运行结束后会返回什么东西给你 , 比如: void *
    • 函数名, 命名规则跟变量一致。应该尽量顾名思义。比如: calloc
    • 参数列表,告诉用户该函数需要输入的数据以及类型,有可能有多个也可能没有 , 比如(size_t nmemb, size_t size)
  2. 语法:

    1
    2
    3
    4
    5
    6
    返回值类型 函数名 ( 参数1 , 参数2 ,参数3 , ... , 参数N )
    {
    // 函数体

    return 返回值 ;
    }
  3. 示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    #include <stdio.h>

    // 函数声明
    int add ( int , int ); // 函数声明中可以把形参的名字省略

    int main(int argc, char const *argv[])
    {
    int x = 100 ;
    int y = 250 ;
    int tmp = 0 ;

    // 调用add函数, 把 x 和 y 的值 传递过去,
    // x , y 是实参, 作为形参 a , b 初始值
    // a = x , b = y ;
    // 使用 tmp 来接受 add函数的返回值

    tmp = add(x,y);
    printf("x + y = %d \n" , tmp );
    return 0;
    }

    // 设计一个函数 ,接收 两个整型参数, 并返回 它们的和
    int add ( int a , int b ) // a , b 属于函数add 的局部变量
    {
    int tmp = a + b ;
    return tmp ;
    }
  4. 练习:

    • 使用函数来实现求 3个整型中最大的并返回。
    • 使用函数来实现交换两个变量的值(指针)。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    #include <stdio.h>
    #include <stdbool.h>

    // 使用函数来实现求 3个整型中最大的并返回。
    static  int max( int a , int b , int c )
    {
    int max = a > b ? a : b ;
    max = max > c ? max : c ;
    return max ;
    // a>b?(a>c?c:a):(b>c?b:c)
    }

    //使用函数来实现交换两个变量的值(指针)
    static bool swap( int * a , int * b )
    {
    if ( a == NULL || b == NULL )
    {
    printf("参数有误, 大意了!!\n");
    return false ;
    }
    printf("a:%d b:%d\n" , *a , *b );

    *a^=*b;
    *b^=*a;
    *a^=*b;
    printf("a:%d b:%d\n" , *a , *b );
    return true ;
    }

    int main(int argc, char const *argv[])
    {
    int x = 129 ;
    int y = 245;
    int z = 350;
    int num = max( x , y , z ) ; // x , y , z 都是实参
    printf("max:%d\n" , num );
    if( swap( &x , &y) )
    {
    printf("x:%d y:%d\n" , x , y );
    }
    else
    {
    printf("交换失败 !!\n");
    }
    return 0;
    }
  5. 总结:

    • 当函数的返回值类型为 void 的时候, 表示该函数不会有返回的值
    • 当参数列表中为 void 的时候,表示该函数不需要参数
    • 当返回值为具体的类型的时候 , 函数的结尾就应该返回一个对应类型的数据
    • return 只能够携带一个数据进行返回
    • 当返回值类型为 void 的时候,可以不写 return , 如果写的话一般就直接写 return ;

形参与实参

  1. 概念

    函数调用的时候传递的值,称为实参 max(123.034 , 'C' , "87") ;
    函数定义当中出现的参数列表,称为形参 max(float a , char b , char * c)

  2. 实参与形参的关系

    • 实参与形参应该是一一对应的。(顺序+类型)
    • 形参的值是由实参进行初始化。
    • 形参与实参是处于两个完全不相关的栈空间中。彼此是独立的

函数调用的过程

函数调用的时候,进程会进行上下文切换,当指向完被调用的函数后,将会切换会被调用的语句,继续往下执行。

function_Started_3

局部变量与栈内存

  1. 局部变量简介

    被函数体的一对大括号所包含的变量,称为局部变量(在函数体内部定义)

  2. 局部变量的特点

    • 属于函数内部的变量, 所存储的位置是该函数所拥有的栈空间
    • 局部变量不会被其它函数所访问,因此不同的函数内部可以拥有完全一样的两个变量名字, 但是从内存来看它们是完全独立的。
    • 当函数退出的时候, 局部变量所占用的内存,将会被系统回收,因此局部变量也称为临时变量
    • 形参当中的变量,虽然没有在大括号的范围内, 但是也是属于该函数的局部变量。
  3. 栈内存的特点

    • 当有一个函数被调用的时候,栈内存将会增长一段, 用来存放该函数的局部变量。
    • 当函数退出时,他所占用的栈内存将会被释放回收。
    • 系统分配栈空间遵循从上往下增长的原则。
    • 栈空间的内存相对来说比较少, 不建议用来存放大量的数据。

function_Started_4

特殊函数

静态函数

  1. 背景:普通的函数都是跨文件可见的,也就是比如a.c里面有个函数swap(), 在b.c中也可以调用该函数。

  2. 静态函数:只能够在当前定义的文件中使用,称为静态函数

  3. 语法:

    1
    2
    3
    4
    5
    static int func (int , int b )
    {
    // 函数体

    }
  4. 注意:

    • 静态函数用来缩小可见范围,减少与其它文件中同名函数冲突的问题
    • 静态函数一般会定义在头文件中, 然后被需要使用该函数的源文件包含即可。

递归函数

  1. 递归函数的概念:如果一个函数的内部,包含了对自己的调用则称为递归函数。

  2. 递归的问题:阶乘、幂运算、字符反转

  3. 要点:

    • 只有可以被递归的问题,才可以使用递归函数来实现。
    • 递归函数必须有一个可以直接退出的条件,否则会进入无线递归,最后导致栈溢出。
    • 递归函数包含了两个过程: 逐渐递进 , 逐步回归。
  4. 示例:
    要求使用递归来输出 n 个自然数
    思路: 先输出n-1个自然数, 最后才输出N

    function_special_1
    function_special_2

  5. 总结:

    • 递归的时候函数的栈空间,会随之从上往下不断增长,栈空间会越来越少,直到耗尽或者开始返回。
    • 在一层一层递进的时候,问题的规模会随之缩小,当达到某一个条件的时候,则开始一层一层回归。

函数指针

  1. 概念:指向一个函数的指针,称为函数指针;

  2. 语法:

    1
    int (*p) (int , float ) ;
  3. 示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    int Printf( int a , float f )
    {
    printf("a:%d , f:%f\n" , a , f );
    return 0 ;
    }

    int main(int argc, char const *argv[])
    {
    // 定义一个函数指针, 名字为 p 。它指向的函数 有一个整型返回值
    // 需要 一个整型参数 以及 浮点参数
    int (*p) (int , float ) ;

    p = Printf ; // 函数名其实也是这个函数的入口地址
    Printf(10 , 3.14);
    p(56 ,9.8888);

    return 0;
    }

指针函数

  1. 概念:一个返回指针的函数,就称为指针函数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    int * func(int a , int b)
    {
    int kk ;

    /*
    ..
    ...
    */

    return &kk ;
    }

回调函数

  1. 概念:函数实现不方便调用该函数,而由接口提供方来调用该函数,就称为回到函数。

  2. 示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    #include <stdio.h>
    #include <signal.h>

    void func(int num )
    {
    printf("当前收到 3 号信号 , 军师让我蹲下 !!\n");
    }

    int test( int num , void (*p)(int) )
    {
    for (size_t i = 0; i < num ; i++)
    {
    printf("num:%d\n" , num‐‐ );
    if (num == 50 )
    {
    p(1);
    }
    }

    return 0 ;
    }

    int main(int argc, char const *argv[])
    {
    void (*p)(int); // 定义一个函数指针
    p = func ; // 让指针p 指向函数 func
    test( 100 , p );
    return 0;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    #include <stdio.h>
    #include <signal.h>3

    void func(int num )
    {
    printf("当前收到 3 号信号 , 军师让我蹲下 !!\n");
    }

    int main(int argc, char const *argv[])
    {
    void (*p)(int); // 定义一个函数指针
    p = func ; // 让指针p 指向函数 func

    // 设置进程捕获信号 ,如果信号值 为 3的时候 , 会自行调用 p 所指向的函数 (代码/指令)
    signal( 3 , p );
    while(1);
    return 0;
    }
  3. 总结:

    • signal 函数是一个用于捕获信号的函数, 当他捕获到指定信号的时候则会执行用户所提供的函数。
    • 由于 signal 函数是内核提供的函数,修改内核的代码不现实,因此它提供的接口是一个函数指针,只要某个条件满足则会自动执行我们所给的函数。
    • 使得不同软件模块的开发者的工作进度可以独立出来,不受时空的限制,需要的时候通过约定好的接口(或者标准)相互契合在一起。

内联函数