• C Primer Plus(6) 中文版 第9章 函数 9.1 复习函数


    C的设计思想是,把函数用作构件块。
    9.1 复习函数
    函数(function)是完成特定任务的独立程序代码单元。语法规则定义了函数的结构和使用方式。虽然C中的函数和其他语言中的函数、
    子程序、过程作用相同,但是细节上略有不同。一些函数执行某些动作,如printf();一些函数找出一个值提供程序使用,如strlen();
    一般而言,函数可以同时具备以上两种功能。
    使用函数的原因如下:
    首先,使用函数可以省去编写重复代码的苦差。
    其次,即使程序只完成某项任务一次,也值得使用函数。因为函数让程序更加模块化,从而提高了程序代码的可读性,更方便后期
    修改、完善。
    假设要编写一个程序完成以下任务:
    *读入一系列数字;
    *分类这些数字;
    *找出这些数字的平均值;
    *打印一份柱状图。
    使用下面的程序:
    #include
    #define SIZE 50
    int main( void ){
        float list[SIZE];
        
        readlist( list, SIZE );
        sort( list, SIZE );
        average( list, SIZE );
        bargraph( list, SIZE );
        return 0;

    当然,还要编写4个函数readlist()、sort()、average()和bargraph()的实现细节。描述性的函数名能清楚第表达函数的用途和组织
    结构。然后,单独设计和测试每个函数,直到函数都能正常完成任务。如果这些函数够通用,还可以用于其他程序。
    许多程序员喜欢把函数看作是根据传入信息(输入)及其生成的值或响应的工作(输出)来定义的“黑盒”。如果不是自己编写函数,
    根本不用关心黑盒的内部行为。以这种方式看待函数有助于把注意力集中在程序的整体设计,而不是函数的实现细节上。因此,在动手
    编写代码之前,仔细考虑一下函数应该完成什么任务,以及函数和程序整体的关系。
    如何了解函数?首先要知道如何正确地定义函数、如何调用函数和如何建立函数间的通信。
    9.1.1 创建并使用简单函数
    /* lethead1.c */
    #include
    #define NAME "GIGATHINK, INC."
    #define ADDRESS "101 Megabuck Plaza"
    #define PLACE "Megapolis, CA 94904"
    #define WIDTH 40

    void starbar(void);  /* prototype the function */

    int main(void)
    {
        starbar();
        printf("%s\n", NAME);
        printf("%s\n", ADDRESS);
        printf("%s\n", PLACE);
        starbar();       /* use the function       */
        
        return 0;
    }

    void starbar(void)   /* define the function    */
    {
        int count;
        
        for (count = 1; count <= WIDTH; count++)
            putchar('*');
        putchar('\n');
    }

    /* 输出:

    */

    9.1.2 分析程序
    *函数原型(function prototype)告诉编译器函数的类型;
    函数调用(function call)表明此处执行函数;
    函数定义(function definition)明确地指定了函数要做什么。
    *函数和变量一样,有多种类型。任何程序在使用函数之前都要声明该函数的类型。
    ANSI风格的函数原型:
    void starbar( void );
    分号表明这是在声明函数,不是定义函数。并告诉编译器在别处查找该函数的定义。对于不识别ANSI C风格原型的编译器,只需声明函数的类型,如下所示:
    void starbar();
    注意,一些老版本的编译器甚至对void都识别不了。如果使用这种编译器,就要把没有返回值的函数声明为int类型。当时,最好还是换一个新的编译器。
    *一般而言,函数原型指明了函数的返回值类型和函数接受的参数类型。这些信息称为该函数的签名(signature)。
    *程序把starbar()原型置于main()的前面。当然,也可以放在main()里面的声明变量处。放在那个位置都可以。
    *在main()中,执行了下面的语句时调用了starbar()函数:
    starbar();
    这是调用void类型函数的一种形式。当计算机执行到starbar();语句时,会找到该函数的定义并执行其中的内容。执行完starbar()中的代码后,计算机返回主调函数(calling function)继续执行下一行。见图9.1(更确切地说,编译器把C程序翻译成执行以上操作的机器语言代码)。 
    main()
    {
        starbar() -->putchar() 每个函数都能调用其他函数
        printf()
        printf() ---依次执行每个函数
        printf()
        starbar()-->putchar()    

        图9.1 lethead1.c的程序流
    *函数定义的函数头包括函数原型、函数名和圆括号,接着是左花括号、变量声明、函数表达式语句,最后以右花括号结束。注意,函数定义函数头中的后面没有分号,告诉编译器这是定义,而不是调用函数或声明函数原型。
    *程序把starbar()和main()放在一个头文件中。当然,也可以把它们放在两个文件中。把函数都放在一个文件中的单文件形式比较容易编译,而使用多个文件方便在不同的程序中使用同一个函数。如果把函数放在一个单独的文件中,要把#define和#include指令也放入该文件。main()的右花括号告诉编译器该函数的结束位置。
    *starbar()函数中的变量count是局部变量(local variable),意思是该变量只属于starbar()函数。可以在程序中的其他地方使用count,这不会引起名称冲突,它们是同名的不同变量。
    starbar的返回类型为void,没有返回值给主调函数,参数为void,也不需要主调函数给其传递值。简而言之,starbar()不需要与主调函数通信。
    9.1.3 函数参数
    /* lethead2.c */
    #include
    #include            /* for strlen() */
    #define NAME "GIGATHINK, INC."
    #define ADDRESS "101 Megabuck Plaza"
    #define PLACE "Megapolis, CA 94904"
    #define WIDTH 40
    #define SPACE ' '

    void show_n_char(char ch, int num);

    int main(void)
    {
        int spaces;
        
        show_n_char('*', WIDTH);   /* using constants as arguments */
        putchar('\n');
        show_n_char(SPACE, 12);    /* using constants as arguments */
        printf("%s\n", NAME);
        spaces = (WIDTH - strlen(ADDRESS)) / 2;
        /* Let the program calculate    */
        /* how many spaces to skip      */
        show_n_char(SPACE, spaces);/* use a variable as argument   */
        printf("%s\n", ADDRESS);
        show_n_char(SPACE, (WIDTH - strlen(PLACE)) / 2);
        /* an expression as argument    */
        printf("%s\n", PLACE);
        show_n_char('*', WIDTH);
        putchar('\n');
        
        return 0;
    }

    /* show_n_char() definition */
    void show_n_char(char ch, int num)
    {
        int count;
        
        for (count = 1; count <= num; count++)
            putchar(ch);

    /* 输出:

    */ 

    9.1.4 定义带形式参数的函数
    ANSI C风格的函数头开始:
    void show_n_char( char ch, int num )
    两个变量被称为形式参数(formal argument,但是最近的标准推荐使用formal parameter),简称形参。和定义在函数中变量一样,形式参数也是局部变量,属该函数私有。这意味着在其他函数中使用同名变量不会引起名称冲突。每次调用函数,就会给这些变量赋值。
    注意,ANSI C要求在每个变量前都声明其类型。也就是,不能像普通变量声明那样使用同一类型的变量列表。
    ANSI C也接受ANSI C之前的形式,但是将其视为废弃不用的形式:
    void show_n_char( ch, num )
    char ch;
    int num;
    这里,圆括号只有参数名列表,而参数的类型在后面声明。注意,普通的局部变量在左括号之后声明,而上面的变量在函数左花括号之前声明。如果变量是同一类型,这种形式可以用逗号分隔变量名列表,如下所示:
    void dibs( x, y, z )
    int x, y, z; /*有效*/
    当前的标准正逐渐淘汰ANSI之前的形式。但是自己编写程序时应使用现在的标准形式(C99和C11标准继续警告这些过时的用法即将被淘汰)。
    show_n_char()接受来自main()的值,但是它没有返回值。
    9.1.5 声明带形式参数函数的原型
    ANSI C形式声明函数原型:
    void show_n_char( char ch, int num );
    当函数接受参数是,函数原型使用逗号分隔的列表指明参数的数量和类型。根据个人喜欢,你也可以省略变量名:
    void show_n_char( char, int );
    在原型中使用变量名并没有实际创建变量,char仅代表了一个char类型的变量,以此类推。
    ANSI C也接受过去的声明函数形式,即圆括号内没有参数列表:
    void show_n_char();
    这种形式最终会从标准中剔除。即使没有被剔除,现在函数原型的设计也更有优势。了解这种形式是为了以后读得懂以前写的代码。
    9.1.6 调用带实际参数的函数
    在函数调用中,实际参数(actual argument,简称实参)提供了ch和num的值。
    简而言之,形式参数是被调函数(called function)中的变量,实际参数是主调函数(calling function)赋给被调用函数的具体值。实际参数可以是常量、变量、或甚至是更复杂的表达式。无论实际参数是何种形式都要被求值,然后该值被拷贝给被调用函数相应的形式参数。
    被调函数不知道也不关心传入的数值是来自常量、变量还是一般表达式。再次强调,实际参数是具体的值,该值要被赋给作为形式参数的变量。因为被调函数使用的值是从主调函数中拷贝而来,所以无论被调用函数对拷贝数据进行什么操作,都不会影响主调函数中的原始数据。
    注意 实际参数和形式参数
    实际参数是出现在函数调用圆括号中的表达式。形式参数是函数定义的函数头中声明的变量。调用函数时,创建了声明为形式参数的变量并初始化为实际参数的求值结果。
    9.1.7 黑盒视角
    从黑盒的视角看show_n_char(),待显示的字符和显式的次数是输入。执行后的结果是打印指定数量的字符。输入以参数的形式被传递给函数。这些信息清楚地表明了如何在main()中使用该函数。而且,这也可以作为编写该函数的设计说明。
    黑盒方法的核心部分是:ch、num和count都是show_n_char()私有的局部变量。如果在main()中使用同名变量,那么它们相互独立,互不影响。反之亦然。黑盒里发生了什么对主调函数是不可见的。
    9.1.8 使用return从函数中返回值
    函数的返回值可以把信息从被调函数传回主调函数。
    这种被设计用于测试函数的程序有时被称为驱动程序(driver),该驱动程序调用了一个函数。如果函数成功通过了测试,就可以安装在一个更重要的程序中使用。
    /* lesser.c -- finds the lesser of two evils */
    #include
    int imin(int, int);

    int main(void)
    {
        int evil1, evil2;
        
        printf("Enter a pair of integers (q to quit):\n");
        while (scanf("%d %d", &evil1, &evil2) == 2)
        {
            printf("The lesser of %d and %d is %d.\n",
                   evil1, evil2, imin(evil1,evil2));
            printf("Enter a pair of integers (q to quit):\n");
        }
        printf("Bye.\n");
        
        return 0;
    }

    int imin(int n,int m)
    {
        int min;
        
        if (n < m)
            min = n;
        else
            min = m;
        
        return min;
    }

    关键字return后面的表达式的值就是函数的返回值。
    变量min属于imin()函数私有,但是return语句把min的值传回了主调函数。
    因此不能写成下面这样:
    lesser = min;
    因为主调函数甚至不知道min的存在。记住,imin()中的变量是imin()的局部变量。
    返回值不仅可以赋给变量,也可以被用作表达式的一部分。
    返回值不一定是变量的值,也可以是任意表达式的值。
    虽然不要求用圆括号把返回值括起来,但是如果想让程序条理更清楚或统一风格,可以把返回值放在圆括号内。
    如果函数返回值的类型与函数声明的类型不匹配,实际得到的返回值相当于把函数中指定的返回值赋给与函数类型相同的变量所得到的值。
    int what_if( int n ){
        double z = 100.0 / (double)n;
        return z; 

    相当于把z的值赋给int类型的变量,然后返回int类型变量的值。
    使用return语句的另一个作用是,终止程序并把控制返回给主调函数的下一条语句。因此,可以这样编写imin():
    /*返回最小值的函数,第3个版本*/
    int imin(int n, int m ){
        if( n < m )
            return n;
        else
            return m;
    }
    许多程序员都认为只在函数末尾使用一次return语句比较好,因为这样做更方便浏览程序的人理解函数的控制流。但是,在函数中使用多个return语句也没有错。
    /*返回最小值的函数,第3个版本*/
    int imin(int n, int m ){
        if( n < m )
            return n;
        else
            return m;
        printf( "..." ); 
    }
    return语句导致printf()语句永远不会被执行。
    另外,还可以这样使用return:
    return;
    这条语句会导致终止函数,并把控制返回给主调函数。因为return后面没有任何表达式,所以没有返回值,只有void函数中才会用到这种形式。
    9.1.9 函数类型
    声明函数时必须声明函数的类型。带返回值的函数类型应该与其返回值类型相同,而没有返回值的函数应声明为void类型。如果没有声明函数的类型,旧版本的C编译器会假定函数的类型是int。这一惯例源于C的早期,那时的函数绝大多数都是int类型。然后,C99标准不再支持int类型函数这种假定设置。
    类型声明是函数定义的一部分。要记住,函数类型指的是返回类型,而不是函数参数的类型。
    要正确地使用函数,程序在第1次使用函数之前必须知道函数的类型。方法之一是,把完整的函数定义放在第1次调用函数的前面。然而,这种方法增加了程序的阅读难度。而且,要使用的函数可能在C库或其他文件中。因此,通常的做法是提前声明函数,把函数的信息告知编译器。
    函数的前置声明放在主调函数外面。当然,也可以放在主调函数里面。
    注意在这两种情况中,函数原型都声明在使用函数之前。
    ANSI C标准库中,函数被分成多个系列,每一系列都有各自的头文件。这些头文件除了其他内容,还包含了本系列所有函数的声明。
    stdio.h包含标准I/O库函数的声明;
    math.h包含各种数学函数的声明。
    函数声明告知编译器函数的类型,而函数定义则提供实际的代码。 

  • 相关阅读:
    Jenkins 相关内容
    ElasticSearch如何在前后台启动
    【MyBatis源码分析】六、MyBatis Plugins(拦截器)
    CSS 渐变锯齿消失术
    FPGA_状态机工作原理
    Unity3D 引擎学习2022资料整理(二)
    L72.linux命令每日一练 -- 第十章 Linux网络管理命令 -- mail和nslookup
    Stable Diffusion生成图片
    UNIAPP实战项目笔记29 购物车设置自定义导航栏
    进销存软件排行榜前十名!
  • 原文地址:https://blog.csdn.net/weixin_40186813/article/details/126227534