C PrimerPlus 第九章 第五天学习

第九章 函数

本章介绍以下内容:
关键字:return
运算符:*(一元)、&(一元)
函数及其定义方式
如何使用参数和返回值
如何把指针变量用作函数参数
函数类型
ANSI C原型
递归

如何组织程序?

C的设计思想是,把函数用作构件块。我们已经用过C 标准库的函数,如printf()、scanf()、getchar()、putchar()和 strlen()。现在要进 一步学习如何创建自己的函数。

什么是函数?

***函数(function)是完成特定任务的独立程序代码 单元。语法规则定义了函数的结构和使用方式。***虽然C中的函数和其他语言 中的函数、子程序、过程作用相同,但是细节上略有不同。一些函数执行某 些动作,如printf()把数据打印到屏幕上;一些函数找出一个值供程序使用, 如strlen()把指定字符串的长度返回给程序。一般而言,函数可以同时具备以 上两种功能

为什么要使用函数?

首先,使用函数可以省去编写重复代码的苦差。如 果程序要多次完成某项任务,那么只需编写一个合适的函数,就可以在需要 时使用这个函数,或者在不同的程序中使用该函数,就像许多程序中使用 putchar()一样。其次,即使程序只完成某项任务一次,也值得使用函数。因 为函数让程序更加模块化,从而提高了程序代码的可读性,更方便后期修 改、完善。

当然,还要编写4个函数readlist()、sort()、average()和bargraph()的实现 细节。***描述性的函数名能清楚地表达函数的用途和组织结构。***然后,单独设 计和测试每个函数,直到函数都能正常完成任务。如果这些函数够通用,还 可以用于其他程序。

许多程序员喜欢***把函数看作是根据传入信息(输入)及其生成的值或响 应的动作(输出)来定义的“黑盒”。***如果不是自己编写函数,根本不用关心 黑盒的内部行为。例如,***使用printf()时,只需知道给该函数传入格式字符串 或一些参数以及 printf()生成的输出,无需了解 printf()的内部代码。以这种 方式看待函数***有助于把注意力集中在程序的整体设计,而不是函数的实现细 节上。因此,在动手编写代码之前,仔细考虑一下函数应该完成什么任务, 以及函数和程序整体的关系

如何了解函数?

首先要知道如何正确地定义函数、如何调用函数和如何 建立函数间的通信。

我们的第1个目标是创建一个在一行打印40个星号的函数,并在一个打印表头的程序中使用该函数。

程序清单9.1 lethead1.c程序 
/* lethead1.c */ 
#include <stdio.h> 
#define NAME "GIGATHINK, INC." 
#define ADDRESS "101 Megabuck Plaza" 
#define PLACE "Megapolis, CA 94904" 
#define WIDTH 40 void starbar(void); /* 函数原型 */ 
int main(void) 
{
    starbar();
    printf("%s\n", NAME); 
    printf("%s\n", ADDRESS); 
    printf("%s\n", PLACE); 
    starbar(); /* 使用函数 */ 
    return 0; 
    } 
void starbar(void) /* 定义函数 */ 
{
    int count; 
    for (count = 1; 
    count <= WIDTH; count++) 
    putchar('*'); 
    putchar('\n'); 
 }该程序的输出如下: 
****************************************
GIGATHINK, INC. 
101 Megabuck Plaza 
Megapolis, CA 94904 
****************************************

9.1.2 分析程序

注意:
程序在3处使用了starbar标识符:函数原型(function prototype)告诉编 译器函数starbar()的类型;函数调用(function call)表明在此处执行函数; 函数定义(function definition)明确地指定了函数要做什么。

函数和变量一样,有多种类型。任何程序在使用函数之前都要

声明该函 数的类型

void starbar(void);
圆括号表明starbar是一个函数名
第1个void是函数类型,void类型表明 函数没有返回值
第2个void(在圆括号中)表明该函数不带参数
分号表 明这是在声明函数,不是定义函数
声明了程序将使用一个 名为starbar()、没有返回值、没有参数的函数,并告诉编译器在别处查找该 函数的定义

(***了解即可:***对于不识别ANSI C风格原型的编译器,只需声明函数的类 型,如下所示: void starbar(); 注意,一些老版本的编译器甚至连void都识别不了。如果使用这种编译 器,就要把没有返回值的函数声明为int类型。当然,最好还是换一个新的编 译器。)

函数的签名

函数原型指明了函数的返回值类型和函数接受的参数类型。 这些信息称为该函数的签名
程序把 starbar()原型置于 main()的前面。当然,也可以放在 main()里面 的声明变量处。放在哪个位置都可以。(这应该就是局部变量和全局变量的区别)

在main()中,执行到下面的语句时调用了starbar()函数: starbar(); 这是调用void类型函数的一种形式。当计算机执行到starbar();语句时, 会找到该函数的定义并执行其中的内容。执行完starbar()中的代码后,计算 机返回主调函数(calling function)继续执行下一行(本例中,主调函数是 main()),见图9.1(更确切地说,编译器把C程序翻译成执行以上操作的机 器语言代码)。

程序中strarbar()和main()的定义形式相同。首先函数头包括函数类型、 函数名和圆括号,接着是左花括号、变量声明、函数表达式语句,最后以右 花括号结束(见图9.2)。

注意,函数头中的starbar()后面没有分号,告诉编 译器这是定义starbar(),而不是调用函数或声明函数原型。

程序把 starbar()和 main()放在一个文件中。当然,也可以把它们分别放 在两个文件中。把函数都放在一个文件中的单文件形式比较容易编译,而使 用多个文件方便在不同的程序中使用同一个函数。如果把函数放在一个单独 的文件中,要把#define 和#include 指令也放入该文件。我们稍后会讨论使用 多个文件的情况。现在,先把所有的函数都放在一个文件中。main()的右花 括号告诉编译器该函数结束的位置,后面的starbar()函数头告诉编译器 starbar()是一个函数。

在这里插入图片描述
在这里插入图片描述

starbar()函数中的变量count是局部变量(local variable),意思是该变 量只属于starbar()函数。可以在程序中的其他地方(包括main()中)使用 count,这不会引起名称冲突,它们是同名的不同变量。

如果把starbar()看作是一个黑盒,那么它的行为是打印一行星号。不用 给该函数提供任何输入,因为调用它不需要其他信息。而且,它没有返回 值,所以也不给 main()提供(或返回)任何信息。简而言之,starbar()不需 要与主调函数通信。 接下来介绍一个函数间需要通信的例子。

9.1.3 函数参数

在程序清单9.1的输出中,如*果文字能居中,信头会更加美观。可以通
过在打印文字之前打印一定数量的空格来实现,这和打印一定数量的星号 (starbar()函数)类似***,只不过现在要打印的是一定数量的空格。虽然这是 两个任务,但是任务非常相似,与其分别为它们编写一个函数,不如写一个 更通用的函数,可以在两种情况下使用。我们设计一个新的函数 show_n_char()(显示一个字符n次)。唯一要改变的是使用内置的值来显示 字符和重复的次数,show_n_char()将使用函数参数来传递这些值。 我们来具体分析。假设可用的空间是40个字符宽。调用show_n_char(’
’, 40)应该正好打印一行40个星号,就像starbar()之前做的那样。第2行 GIGATHINK, INT.的空格怎么处理?GIGATHINK, INT.是15个字符宽,所以 第1个版本中,文字后面有25个空格。为了让文字居中,文字的左侧应该有 12个空格,右侧有13个空格。因此,可以调用show_n_char(’
’, 12)。 show_n_char()与starbar()很相似,但是show_n_char()带有参数。从功能 上看,前者不会添加换行符,而后者会,因为show_n_char()要把空格和文本 打印成一行。程序清单9.2是修改后的版本。为强调参数的工作原理,程序 使用了不同的参数形式

程序清单9.2 lethead2.c程序 
/* lethead2.c */
 #include <stdio.h>
 #include <string.h> /* 为strlen()提供原型 */ 
 #define NAME "GIGATHINK, INC."
 #define ADDRESS "101 Megabuck Plaza" 
 #define PLACE "Megapolis, CA 94904" 
 #define WIDTH 40 #define SPACE ' ' 570
void show_n_char(char ch, int num); 
int main(void) 
{
     int spaces; 
     show_n_char('*', WIDTH); /* 用符号常量作为参数 */ 
     putchar('\n'); show_n_char(SPACE, 12); /* 用符号常量作为参数 */ 
     printf("%s\n", NAME); 
     spaces = (WIDTH - strlen(ADDRESS)) / 2; /* 计算要跳过多少个空格*/ 
     show_n_char(SPACE, spaces); /* 用一个变量作为参数*/ 
     printf("%s\n", ADDRESS); 
     show_n_char(SPACE, (WIDTH - strlen(PLACE)) / 2); 
     printf("%s\n", PLACE); /* 用一个表达式作为参数 */ 
     show_n_char('*', WIDTH); putchar('\n'); 
     return 0; }/* show_n_char()函数的定义 */
void show_n_char(char ch, int num)
 {
     int count; 
     for (count = 1; count <= num; count++) 
     putchar(ch); 
}

9.1.4 定义带形式参数的函数

void show_n_char(char ch, int num)
该行告知编译器show_n_char()使用两个参数ch和num,ch是char类型, num是int类型。这两个变量被称为***形式参数***
和定义在函数中变量一样,形式 572

参数也是局部变量,属该函数私有。这意味着在其他函数中使用同名变量不 会引起名称冲突。每次调用函数,就会给这些变量赋值。

注意,ANSI C要求在每个变量前都声明其类型。也就是说,不能像普 通变量声明那样使用同一类型的变量列表
void dibs(int x, y, z) /
无效的函数头 /
void dubs(int x, int y, int z) /
有效的函数头 /
*

在这里插入图片描述
下面这张图意思是说:声明时括号内:要么什么都不写,要么只写变量类型,要么都写,现在的用法是都要写,程序看起来会更加清晰易懂
在这里插入图片描述

9.1.6 调用带实际参数的函数

在函数调用中,实际参数(actual argument,简称实参)提供了ch和num 的值。

形式参数是被调函数(called function)中的变量,实际参数是主调函数(calling function)赋给被调函数的具体值。

实际参数可以是常量、变量,或甚至是更复杂的表达式。无论 实际参数是何种形式都要被求值,然后该值被拷贝给被调函数相应的形式参 数。

eg : show_n_char(SPACE, (WIDTH - strlen(PLACE)) / 2); 实际参数是具体的值,该值要被赋 给作为形式参数的变量
因为被调函数使用的值是从主调函数 中拷贝而来,所以无论被调函数对拷贝数据进行什么操作,都不会影响主调 函数中的原始数据

注意 实际参数和形式参数 实际参数是出现在函数调用圆括号中的表达式。形式参数是函数定义的 函数头中声明的变量。调用函数时,创建了声明为形式参数的变量并初始化 为实际参数的求值结果。

在这里插入图片描述

黑盒里发生了什么对主调函数是不可见的

9.1.7 黑盒视角

从黑盒的视角看 show_n_char(),待显示的字符和显示的次数是输入。 执行后的结果是打印指定数量的字符。输入以参数的形式被传递给函数。这 些信息清楚地表明了如何在 main()中使用该函数。而且,这也可以作为编写 该函数的设计说明。 黑盒方法的核心部分是:ch、num和count都是show_n_char()私有的局部 变量。如果在main()中使用同名变量,那么它们相互独立,互不影响。也就 是说,如果main()有一个count变量,那么改变它的值不会改变show_n_char() 中的count,反之亦然。黑盒里发生了什么对主调函数是不可见的。

9.1.8 使用return从函数中返回值

前面介绍了如何把信息从主调函数传递给被调函数。反过来,函数的返回值可以把信息从被调函数传回主调函数。

创建一个简单的main(),用于检查imin()是否正 常工作。

这种被设计用于测试函数的程序有时被称为驱动程序(driver)

该驱动程序调用一个函数。如果函数成功通过了测试,就可以安装在一个更 重要的程序中使用。

/*找出两个整数中较小的一个*/
#include "stdio.h"

int imin(int n,int m);
int main(void)
{
    int evil1,evil2;
    printf("Please Enter a pai of integer ( 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("Please Enter a pai of integer ( q to quit ): \n");
    }
    return 0;
}
int imin( int n,int m )
{
    int min;
    if( m > n)
        min = n ;
    else
        min = m;
    return min;
}

关键字return后面的表达式的值就是函数的返回值

在该例中,该函数 返回的值就是变量min的值。因为min是int类型的变量,所以imin()函数的类 型也是int。
变量min属于imin()函数私有,但是return语句把min的值传回了主调函数。

变量min属于imin()函数私有,但是return语句把min的值传回了主调函 数。下面这条语句的作用是把min的值赋给
lesser:
lesser = imin(n,m);
是否能像写成下面这样:
imin(n,m);
lesser = min;

不能

因为主调函数甚至不知道min的存在。记住,imin()中的变量是 imin()的局部变量。函数调用imin(evil1, evil2)只是把两个变量的值拷贝了一 份

返回值不仅可以赋给变量,也可以被用作表达式的一部分。
返回值不仅可以赋给变量,也可以被用作表达式的一部分。
例如,可以 这样:

answer = 2 * imin(z, zstar) + 25; 
printf("%d\n", imin(-32 + answer, LIMIT));

返回值不一定是变量的值,也可以是任意表达式的值。
在这里插入图片描述

如果函数返回值的类型与函数声明的类型不匹配会怎样?

在这里插入图片描述
在这里插入图片描述

许多C程序员都认为只在函数末尾使用一次return语句比较好,因为这样 做更方便浏览程序的人理解函数的控制流。但是,在函数中使用多个return 语句也没有错。无论如何,对用户而言,这3个版本的函数用起来都一样, 因为所有的输入和输出都完全相同,不同的是函数内部的实现细节。

在这里插入图片描述
另外,还可以这样使用return:

return; 这条语句会导致终止函数,

并把控制返回给主调函数。因为 return 后面 没有任何表达式,所以没有返回值,只有在void函数中才会用到这种形式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值