自学C语言-9

**

第9章 函数

**
大型程序一般会被分为若干个程序模块,每个模块实现一个特定功能 。C语言中,由函数实现子程序,由子程序实现模块功能。本章致力于使读者了解函数的概念,掌握函数的定义及调用方式;了解内部函数和外部函数的作用范围,能区分局部变量和全局变量的不同;最后能将函数应用于程序中,将程序分成不同的功能模块。本章的知识架构及重难点如下:
在这里插入图片描述

9.1 函数概述

构成C程序的基本单元是函数,函数中包含着程序的可执行代码。每个C程序的入口和出口都位于main函数中,但并不需要把所有指令都放在main函数中。一般的做法是将程序划分成若干个模块,每个模块完成一部分功能,不同的程序模块可以由不同的人来完成,从而提高软件开发的效率。这就好比是盖楼房,一栋摩天大楼是不可能靠一个人完成的,而要靠多部门、多工种之间协力完成。通常是有一个总工程师,在他的指挥下,有部门运输建筑材料,有部门建造楼房主体,还有部门粉刷内外墙涂料。编写程序的道理与盖楼是一样的,主函数就像总工程师一样,控制着整体程序的推进和执行,其中定义的其他函数就好比参与盖楼的多个部门或工种,他们要通过某种调度(函数调用)才能完成特定的功能。
	主函数可以调用其他函数,其他函数间也可以相互调用。函数可以有参数和返回值,通过它们实现数据间的传递。在主函数中调用其他函数,这些函数执行完毕之后会返回main函数中。通常把这些被调用的函数称为下层函数。函数调用发生时,立即执行被调用的函数,而调用者则进入等待的状态,直到被调用函数执行完毕。
	【例9.1】编写3个函数:做饭,钓鱼,写诗,在本实例中,定义3个函数来完成做饭、钓鱼、写诗等待定的功能,然后在主函数中调用它们。为了简化函数的功能,这里只让其输出一条提示信息。读者可通过本实例对函数有一个直观的认识。
	![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/2101805495334648803ef774173c192d.png#pic_center)

在这里插入图片描述在分析本实例之前,我们先来了解一下什么是C程序源文件、库函数和用户自定义函数。
☑源文件:由一个或者多个函数组成。C语言以源程序为单位进行编译,而不是以函数为单位进行编译。
☑库函数:由C语言系统提供,用户无须定义,调用前也不必做类型说明,但需要在程序开始部分包含有该函数原型的头文件。例如,要使用能在控制台显示信息的printf函数,需在程序开始时包含stdio.h头文件;要使用字符串操作函数strlen、strcmp等时,需在程序开始时包含string.h头文件。
☑用户自定义函数:用户编写的用来实现特定功能的函数。例如,Cook、Fish和Poem函数都是自定义函数。
在本例程序中,首先包含了stdio.h头文件,然后声明了3个自定义函数,最后在主函数main中调用了这3个函数。在主函数main外,可以看到这3个函数的定义。
运行程序,显示效果如下图所示。
在这里插入图片描述

在这里插入图片描述编程训练:
训练1:盖楼房 编写3个函数,分别实现搬运建筑材料、建造楼房主体、粉刷内外墙功能,并在主函数中调用这3个函数。输出结果如下 :

执行搬运功能
执行建造功能
执行粉刷功能

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

训练2:写情书 定义一个函数,内定为一封情书,在主函数中调用该函数,将情书内容展示出来。运行结果如下:

人生最美好的是相遇
我一生最奢侈的事
就是途中与你相遇
然后相濡以沫,共闻花香,有生之年
只诉温暖不言歾,倾心相遇,安暖相陪

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

9.2 函数的定义和声明

C语言的库函数可以直接调用,如printf输出函数。而自定义函数则必须由用户进行定义,确定其要实现的功能,这样才能被其他函数调用。
**

9.2.1 函数的定义

**
一个函数应包括函数头和函数体。定义一个函数的语法格式如下:
在这里插入图片描述首先来看一段代码,然后通过分解,了解函数的构成。
在这里插入图片描述1.函数头
函数头是函数的入口,标志着一段函数代码的开始。函数头包括返回值类型、函数名和参数列表3个部分,如下图所示。
在这里插入图片描述☑返回值类型:函数返回值的类型,必须是C语言中的某个数据类型。这里函数的返回值将是一个int型值。
☑函数名:函数的标识符,在一个C程序中应保持唯一。因为是标识符,所以函数名要遵守标识符命名规则。这里,函数名是AddTwoNumber,可以推测出该函数的功能是两数相加求和。
☑参数列表:调用函数时,用于将主调函数中的实际参数复制到该列表对应的形式参数中。可以没有参数,也可以有多个参数。这里定义了两个int变量,表示要相加的两个数。

2.函数体

函数体位于函数头的下方位置,由一对大括号括起来,大括号决定了函数体的范围。函数要实现的特定功能,都是在函数体部分通过代码语句完成的,最后通过return语句返回实现的结果。
在上面的代码中,函数体内首先定义了一个int型变量,用来保存加法的计算结果,之后利用传递进来的参数进行加法操作,并将结果保存在result变量中,最后函数要将所得到的结果进行返回。通过这些语句的操作,实现了求解两数和的特定功能。
在定义函数时会出现以下几种特殊的情况。
☑无参函数:没有参数列表的函数。如例9.1中的Cook、Fish、Poem都是无参函数。
☑空函数:没有任何内容,也没有什么实际功能的函数。空函数的形式如下:

类型说明符	函数名()
{
}

实际开发中,有时某个函数还未编好,或者后续要拓展某个函数,这时就会先用一个空函数代替,先占个位置,待后续时机成熟再用编好的函数取代它。
注意,C语言中,函数的定义是互相平行、独立的。也就是说,函数体内不能再包含其他函数的定义。例如,下面的代码是错误的:

int main()
{
	void Display()												/*错误!不能在函数体内定义另一个函数*/
	{
			printf("I want to show the Nesting function");
	}
	return 0;
}

这里,主函数main中定义了一个Display函数,目的是输出一句提示。由于C语言不允许进行嵌套定义,因此编译时会出现如图9.3所示的错误提示。

在这里插入图片描述

9.2.2 函数的声明

在程序中编写函数时,要先对函数进行声明,再对函数进行定义。函数定义是为了让编译器知道函数的功能 ,而函数声明是为了让编译器预先知道有这么一个函数,以及函数的名称、参数、返回值类型等信息。
函数声明的一般形式如下:
返回值类型		函数名(参数列表);

要注意的是,函数声明语句的最后要用分号";"作为结尾。例如,声明一个函数的代码如下:

Int ShowNumber(int iNumber);

【例9.2】交换两个数值,通过本实例了解函数声明与函数定义的位置,及其在程序中的作用。

在这里插入图片描述(1)观察上面的程序,可以看到在main函数的开头先进行了exchange函数的声明,声明的作用是告知其函数将在后面进行定义。
(2)在main函数体中,首先定义两个整型变量a、b,之后输出一条提示消息,然后调用exchange函数。
(3)在main函数的定义之后可以看到exchange函数的定义,功能是实现两个数的数值互换。
运行程序,结果如下图所示。
在这里插入图片描述
注意:如果先定义函数,再调用函数,则不再需要进行函数声明,此时函数定义已经包含了函数声明的作用。

编程训练:
训练3:输出谚语 编写一个程序,输出“最大的挑战和突破在于用人,而用人最大的突破是信任人”。运行效果如下:
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/74ed798e2451404a96244f43bba46af4.png#pic_center
在这里插入图片描述

训练4:打印新年菜单 春节是中国最重要的节日,家家户户都会张灯结彩,丰盛的年夜饭更是必不可少的。定义一个函数,打印2021年新年菜单,再在主函数中调用函数。运行结果如下:
在这里插入图片描述

9.3 函数参数

多数情况下,主调函数和被调函数之间存在着数据传递关系,这种数据传递是通过函数参数来实现的。函数参数的作用是传递数据给函数使用,函数利用接收到的数据进行具体的操作处理。

9.3.1 形式参数与实际参数

函数的参数分为两种:形式参数和实际参数。同为参数,要仔细体会其中的区别。
1.形式参数
声明和定义函数时,函数名后面括号中的参数称为形式参数。这些参数只是定义了类型,在实际参数传入前并没有实际意义,因此叫作形式参数,简称形参。
2.实际参数
调用函数时,函数名后面括号中的参数称为实际参数。调用函数的过程就是真正使用这个函数的过程,此时调用者会传递一些要实际参与运算的参数给被调用函数,这些实际参与运算的参数就是实际参数,简称实参。
下面来看一段代码,加深对形参和实参的理解。

在这里插入图片描述上述代码定义了一个Minus函数,功能是两数相减。这里,iNumber1和iNumber2是形式参数,表示相减的两个整型数,没有具体值(等待实参传入)。主函数中,通过“iResult=Minus(9,4); "语句调用Minus函数,这里9和4这实际参数,调用函数后,将用4代替iNumber1,用9代替iNumber2,因此Minus函数实际执行的运算的是9-4。
注意,函数参数可以是常量、变量、数组、指针等,也可以是表达式。

9.3.2 数组作函数参数

1.数组元素作为函数参数

【例9.3】输出数组元素
定义一个数组并为其赋值,然后将数组元素作为函数实参进行传递。自定义函数体中,形参得到实参传递的数值后,将其显示输出。
在这里插入图片描述(1)首先进行函数声明,在主函数main中定义一个整型数组和一个整型变量i。
(2)使用for循环语句对数组中的元素依次赋值,在这里,变量i既是循环条件,也是引用数组元素的下标。
(3)通过for循环语句调用ShowMember函数,显示数据。
运行程序,显示效果如上图所示。
**

2.数组名作为函数参数

**
C语言中,数组名表示的是数组中第一个元素的地址 。因此,当数组名作为函数实参时,传递的是数组的地址。这点和数组元素做实参时是不一样的,注意体会其不同。
【9.4】数组名作为函数参数
在本实例中,使用数组名作为函数的实参和形参,实现数组的赋值和输出。
在这里插入图片描述(1)首先对Evaluate函数和Display函数进行声明,在声明语句中可以看到数组名作为形参。
(2)在主函数main中定义一个具有10个元素的整型数组iArray。
(3)调用Evaluate函数,数组名iArray作为函数实参,传递的是数组的地址。在Evaluate函数中,使用数组iArrayName作为形参,接受对应的地址空间,并对数组进行赋值操作。
(4)调用Display函数,将数组输出,可以看到在函数参数中使用的也是数组名称。运行程序,显示效果如上图所示。

3.可变长度数组作为函数参数

数组作为函数参数时,如果未指明数组大小,就属于长度可变的数组作为函数参数。
【例9.5】不使用库函数,实现字符串连接功能
在本实例中,不使用编译器提供的库函数,将函数的参数声明为长度可变数组。具体代码如下:
在这里插入图片描述运行程序,显示效果如上图所示。
编程训练
训练5:用户登录验证 编写登录函数,函数有两个形参:账号名和密码。如果账号名为“张三”,密码为“123”,则登录成功,否则登录失败。运行结果如下:

登陆成功

训练6:自定义运算函数 编写函数fun(int a[][N],int n),使数组左下半边角的元素值均乘以n。例如,n=3,a={{1,9,7},{2,3,8},{4,5,6}},运行程序后得到{{3,9,7},{6,9,8},{12,15,18}}。运行结果如下:

a[0][0]=1
a[0][1]=9
a[0][2]=7
a[1][0]=2
a[1][1]=3
a[1][2]=8
a[2][0]=4
a[2][1]=5
a[2][2]=6
左下半边角元素中的值乘以n的结果是:
3           9           7
6           9           8
12         15          18

9.4 函数的调用和返回

通过前面的学习,我们已经知道,使用函数的过程就是调用函数的过程。

9.4.1 函数的调用

函数的调用方式有3种,包括语句调用、表达式调用和函数参数调用。
1.语句调用
函数调用作为一个独立语句出现,就称为语句调用。这是最常用的函数调用方式,可以有返回值,也可以没有返回值。例如:

Display();																			/*通过语句调用Display函数*/

2.表达式调用
当函数调用出现在一个表达式中时,函数必须返回一个确定的值,作为表达式运算的一部分。
例如:

iResult=iNum3*AddTwoNum(3,5); 									/*函数调用出现在表达式中,返回值参与乘法运算*/

这条语句中,函数AddTwoNum的返回值将与iNum3执行乘法,得到的结果赋值给iResult变量。
【例9.6】实现欧姆定律功能
在本实例中,定义一个函数,其功能是利用欧姆定义(R=U/I)计算电阻值,并在表达式中调用该函数,使其返回值参与运算。
在这里插入图片描述(1)先对要使用的函数进行声明操作。
(2)在主函数main中,调用TwoNum函数来计算数值5和10的相除。
(3)定义函数TwoNum,定义变量用来存储计算的结果,利用表达式计算欧姆值,并且将运算结果赋值给变量 iTempResult。使用printf函数对所得到的结果进行输出显示。运行程序,显示效果如上图所示。

3.函数参数调用

函数调用还可以出现在函数参数中。此时,函数的返回值将作为参数使用。例如:
iResult=AddTwoNum(10,AddTwoNum(3,5));							/*函数调用出现在函数参数中*/

在这条语句中,AddTwoNum函数的功能还是进行两个数相加,然后将相加的结果作为函数的一个参数,继续进行计算。
【例9.7】判断体温是否正常
本实例编写getTemperature函数得到体温值,将其返回的体温数据交给judgeTemperature函数,作为参数使用。具体代码如下:
在这里插入图片描述

9.4.2 函数的返回

在函数体中常会看到这样一条return语句。

return 0;

这就是返回语句。return语句的作用有两个,下面进行详细介绍。
1.退出函数,返回主调程序中。
C程序中,有两种方法可以终止函数执行,返回到调用函数位置。
☑函数体中的所有语句都已执行完毕,遇到结束符号“}”后自动返回。
☑遇到return语句,随即返回。
2.返回一个值,供主调函数使用
用户调用函数时,通常是希望得到一个确定的返回值。该返回值是通过return语句实现的。返回值也需要约定类型,因此定义函数时,需要明确指定函数返回值的类型。例如:

int Max(int iNum1,int iNum2);								/*函数返回值类型为int*/
double Min(double dNum1,double dNum2);				/*函数返回值类型为double*/
char show(char cChar);											/*函数返回值类型为char*/

如果函数返回值的类型和return语句中表达式的值不一致,则以函数返回值的类型为准。数值型数据可以自动进行类型转换,即函数定义的返回值类型决定最终返回值的类型。
注意,return语句后面的括号可以省略,即return 0和return(0)是相同的。另外,函数也可以没有返回值。例如,返回值类型为void的函数就没有返回值。
【例9.8】返回体温值
在本实例中,编写函数getTemperature,返回一个体温值。具体代码如下:
在这里插入图片描述(1)首先为程序声明一个getTemperature函数,在主函数调用自定义getTemperature函数。
(2)getTemperature函数的定义中,定义了一个浮点型变量,用户通过提示信息输入一个温度值,最后将温度值返回。在这里可以看到,getTemperature函数中定义的温度值是float型,所以返回的也是float型变量。运行程序,显示效果如上图所示。

9.4.3 函数的嵌套调用

C语言虽然不允许进行函数嵌套定义,但却可以函数嵌套调用,即可在一个函数体内可以调用另外一个函数。例如,在main函数中调用Display函数,在Display函数中再调用ShowMessage函数,这种就叫作嵌套调用 。调用时,一般从最外层main函数开始,一层层的调用,直到深入最内层的函数。嵌套调用返回时,恰好相反,从最内层的函数开始,一层层返回,直到返回最外层的主调函数中。
这就好比某公司要完成业绩,CEO需要将运营指标交代给总经理,总经理将工作分拆给各部门经理(副经理),部门经理们再将工作分拆给下属职员,职员按照上级指示完成具体工作。之后,职员将完成结果汇报给部门经理,部门经理汇总后将结果汇报给总经理,总经理再汇总后将结果汇报给CEO。
【例9.9】CEO下达工作内容 在本实例中,利用嵌套函数模拟CEO分派工作过程。这里简化任务的完成过程,只输出一条语句表示逐级分派任务(即调用函数)的过程。
在这里插入图片描述(1)首先在程序中声明将要使用的4个函数,其中的CEO代表公司总裁,Manager代表总经理,AssistantManager代表副经理,Clerk代表职员。
(2)main函数下是4个函数的定义。先来看一下CEO函数,通过输出一条信息来表示这个函数的功能和作用,然后嵌套调用Manager函数。Manager和CEO函数运行的步骤是相似的,最后在其函数体内调用AssistantManager函数。在AssistantManager函数中调用Clerk函数。
(3)在主函数main中,调用了CEO函数,于是程序的整个流程按照步骤(2)进行,直到“return 0"语句返回,程序结束。
运行程序,显示效果如下图所示。
在这里插入图片描述
9.4.4 函数的递归调用
所谓递归调用,就是函数自己调用自己。从定义中可以看出,函数递归调用是函数嵌套调用的一种特殊形式。函数可以直接调用自己,也可以间接调用自己,如图9.12所示。所谓间接调用,就是在递归函数的下层函数中调用自己。
在这里插入图片描述递归之所以能实现,是因为函数的每个执行过程在栈中都有自己的形参和局部变量副本,这些副本和该函数的其他执行过程不发生关系。这种机制是当代大多数程序设计语言实现子程序结构的基础。
【例9.10】倒序输出名单 在本实例中,定义一个字符串数组,为其赋值一系列名称,通过递归函数,逆序显示名单。注意,本例之所以能逆序输出名单,是因为嵌套函数返回时是从最内层到最外层逐层返回。

在这里插入图片描述如下图所示为程序的调用流程,通过此图可以对递归调用过程有一个更直观的认识。
在这里插入图片描述 (1)首先声明递归函数DisplayNames,其参数声明为指针的指针(第10章中将介绍,这里不用深究)。
(2)定义一个全局字符串数组,并且为其进行赋值。其中,字符串“end"作为数组的结尾标志。
(3)在主函数main中调用递归函数DisplayNames。
(4)在DisplayNames的函数体中,通过if语句判断当前要输出的字符串是否是结束字符“end",如果是,则使用return语句返回。否则,执行下面的else语句,调用递归函数,在函数参数处可以看到传递的字符串数组元素发生改变,传递下一个数组元素。调用递归函数后,仍然要先判断传递进来的字符串是否是数组的结束标志。最后输出字符串数组的元素。
运行程序,显示效果如下图所示。
在这里插入图片描述编程训练:
训练7:判断某个数是否为回文数 编写函数,判断字符串是否为回文数(即顺读和倒读是一样的)。若是,函数返回1,否则返回0。运行结果如下:

请输入一个数:
12321
是回文数

训练8:某宝集福活动 每年春节,支付宝都会推出集福活动。自定义一个函数,将2020年支付宝推出的福打印出来,然后在主函数中调用编写的函数,运行结果如下:

五福为:
爱国福
富强福
和谐福
友善福
敬业福
附加福为:
全家福
沾气福

训练9:魔术师及代表作 自定义一个包含魔术师及其代表作的函数,在主函数中调用这个函数,输出每个魔术师的名字及作品,运行结果如下:

1、Jason Latimer:	"划时代的近景魔术师”,代表魔术《杯与球》《激光魔术》
2、大卫·科波菲尔:代表作《人体切割》《消失的自由女神》。
3、大卫·布莱恩:擅长跳脱术,曾在一座桥下不吃不喝坚持了44天。
4、Daryl:	近景魔术大师。出过很多教学光碟。
5、Michael.Ammar:也是近景魔术大师,出过很多教学。

训练10: 判断血压是否正常 正常的血压收缩压是90140mmHg,舒张压是6090mmHg。如要收缩压低于90mmHg或舒张压低于60mmHg,属于低血压。如果收缩压高于140mmHg或舒张压高于90mmHg,属于高血压。编写程序,判断舒张压是否正常,运行结果如下:

请输入舒张压数值:
88
当前舒张压是:88
血压正常

9.5 内部函数和外部函数

函数是C程序中的最小单位,可以把一个或多个函数保存为一个文件,这个文件就称为源文件。当一个源程序由多个源文件组成时,多个文件之间的函数可以相互调用。当然我们也可以通过将其设置为内部函数,禁止其被其他文件调用。因此,根据能不能被其他文件调用,C语言又把函数分为两类:一类是内部函数,另一类是外部函数。

9.5.1 内部函数

定义一个函数,如果该函数只能被所在的源文件使用,那么就称这样的函数为内部函数。内部函数又称为静态函数。使用内部函数,可以使函数只局限在函数所在的源文件中,如果在不同的源文件中有同名的内部函数,则这些同名函数间是互不干扰的。
定义内部函数时,要在函数返回值和函数名前面加上关键字static进行修饰。一般形式如下:

static	返回值类型	函数名(参数列表)

例如,定义一个功能为加法运算且返回值是int型的内部函数,代码如下:

static int Add(int iNum1, int iNum2)

在函数的返回值类型int前加上关键字static,可将原来的函数指定成内部函数。

技巧:
使用内部函数的好处是,不同开发者编写函数时,不必再担心函数是否会与其他源文件中的函数同名。因为内部函数只在所在源文件中有效,不同源文件中即使有相同的函数名,也没有关系。

【例9.11】显示 “Hello RiMing!"
在本实例中使用内部函数,通过一个函数对字符串进行赋值,再通过一个函数对字符串进行输出显示。
在这里插入图片描述在程序中,使用static关键字对函数进行修饰,使其只能在其源文件中进行调用。运行程序,显示效果如上图所示。

**

9.5.2 外部函数

**
外部函数是可以被其他源文件调用的函数。定义外部函数时,使用关键字extern进行修饰。同样,使用外部函数时,要先用extern声明所用的函数是外部函数。
例如,外部函数的函数头可以写成下面的形式:

extern int Add(int iNum1,int iNum2);

这样,Add函数就可以被其他源文件调用,进行加法运算。

注意
C语言中,定义函数时如果未指明是内部函数还是外部函数,系统将默认此函数为外部函数。也就是说,定义外部函数时可以省略关键字extern。本学习章节中多数实例使用的函数都为外部函数。

【例9.12】求三角形的内角度数
一个三角形的3个内角度数之比为2:3:3,求各内角的度数。使用外部函数编写程序求解。

在这里插入图片描述
在这里插入图片描述主函数main在源文件Cal.c中。首先声明一个函数,其中使用extern关键字说明函数为外部函数。然后在main函数体中调用这个函数。在call.c源文件中对GetAngle函数进行定义,通过对传入的参数执行返回操作,完成对变量的赋值功能 。
运行程序,显示效果如下图所示。
在这里插入图片描述编程训练
训练11:嵌套计算 利用嵌套函数计算s=1sq2!+2sq2!+3sq2!。运行结果如下:

计算的结果是:362905

训练12:输出名言 编写外部函数并调用,输出名言 “我们必须在别人改变之前改变自己”。

9.6 局部变量和全局变量

在讲解局部变量和全局变量的知识之前,先来了解一下作用域。作用域决定了程序中变量的作用范围:局部变量的作用域是局部的,如函数体内;全局变量的作用域是整个程序。下面具体看一下有关局部变量和全局变量的内容。

9.6.1 局部变量

在函数内部定义的变量是局部变量。我们前面学习的实例中绝大多数变量都只是局部变量,这些变量声明在函数内部,无法被其他函数所使用。函数的形式参数也属于局部变量,作用范围仅限于函数内部的所有语句块。
图9.17表示的是不同情况下局部变量的作用域范围。
在这里插入图片描述说明 在语句块内声明的变量仅在该语句块内部起作用,当然也包括嵌套在其中的子语句块。
【例9.13】模拟美团外卖商家的套餐
某米线店推出如下套餐:考神套餐13元,单身套餐9.9元,情侣套餐20元。
本实例在不同的位置定义一些变量,并为其赋值来表示变量的所在位置,最后输出显示其变量值,通过输出的信息来观察局部变量的作用范围。
在这里插入图片描述(1)程序中有3个作用域 范围,主函数main是其中最大的作用域范围。因为定义变量iNumber1在main函数中,所以iNumber1的作用范围是整个main函数体。
(2)iNumber2定义在第一个if语句块中,因此它的使用范围就是在第一个if语句块内。变量iNumber3在最内部的嵌套层,因此使用范围只在最里面的if语句块中。
局部变量的作用范围由包含该变量的一对大括号所限定,据此可以更好地观察出局部变量的作用域。运行程序,显示效果如上图所示。
C语言中,位于不同作用域的变量可以使用相同的标识符,也就是变量名可以相同。如果内层作用域中定义的变量和已经声明的某个外层作用域中的变量有相同名称,在内层中使用这个变量名,此时的变量名表示的是外层变量还是内层变量呢?答案是:内层作用域中的变量将屏蔽外层作用域中的同名变量,直到结束内层作用域为止。这就是局部变量的屏蔽作用。
【例9.14】局部变量的屏蔽作用
在本实例中,在不同的语句块内定义了3个相同名称的变量,通过输出的变量值来感受局部变量的屏蔽作用效果。
在这里插入图片描述(1)在主函数main中,定义了第一个整型变量iNumber1,将其赋值为1,然后使用printf函数进行输出。可以看到,此时iNumber1的值为1。
(2)使用if语句进行判断,这里使用if语句的目的在于划分出一段语句块。因为位于不同作用域的变量可以使用相同的标识符,所以在if语句块中也定义一个iNumber1变量,并将其赋值为2。再次使用printf函数输出变量iNumber1的操作,观察一下程序的运行结果,发现输出的值为2。说明值为2的iNumber1在此作用域中的值为1的iNumber1屏蔽掉了。
(3)在if语句中再次进行嵌套,其嵌套语句中定义第3个相同的标识符iNumber1变量。为了进行区分,将其赋值为3。调用printf函数输出变量iNumber1,从程序运行的结果可以看出显示结果为3。说明值为3的iNumber1将值为2与1的两个iNumber1都进行了屏蔽。
(4)在最深层嵌套的if语句结束之后,使用printf函数进行输出,发现此时显示的值为2。说明此时已经不在值为3的iNumber1作用域范围内,而在值为2的iNumber1作用域范围。
(5)当if语句结束之后,再次输出iNumber1值,此时显示结果为1,说明已离开了值为2的iNumber1的作用域范围,不再对值为1的iNumber1产生屏蔽作用。运行程序,显示效果如上图所示。

9.6.2 全局变量

函数中定义的变量都是局部变量。如果一个变量在所有函数外部声明,这个变量就是全局变量。顾名思义,全局变量可以在程序的任何位置进行访问。
注意
	全局变量不属于某个函数,而属于整个源文件。如果外部文件要访问它,则要用extern关键字进行引用修饰。

全局变量可增加函数间的数据联系。同一文件中的所有函数都能引用全局变量的值,因此如果在一个函数中改变了全局变量的值,就能影响到其他函数,相当于各个函数间有了一个直接传递通道。例如,有一家全国连锁商店,其商品价格是全国统一的。全国各地有很多这样的连锁商店,当进行价格调整时,应该确保每一家连锁商店的价格是相同的。全局变量就像其中所要设定的价格,而函数就像每一家连锁店,当全局变量进行修改时,函数中使用的该变量都将同步被更改。

【例9.15】模拟连锁超市价格调整
在本程序中,使用全局变量模拟连锁店的全国价格调整,使用函数表示连锁店,并在函数中输出一条消息,表示连锁店中的价格。

在这里插入图片描述(1)在程序中,定义了一个全局变量iGlobalPrice来表示全国连锁店的初始商品价格,为了可以形成对比,初始化值为100。还定义了4个函数,其中3个代表连锁店的价格,例如Store1Price代表1号连锁店;ChangePrice函数来改变全局变量的值,代表对所有连锁店进行调价。
(2)在主函数main中,首先将3家连锁店的初始价格进行显示,之后通过一条信息提示更改iGlobalPrice变量。全局变量被修改后,再次输出将所有连锁店的当前价格。
(3)通过程序的运行结果可以看出,全局变量成为函数间数据联系的渠道,修改一个全局变量后,所有函数中的该变量都会发生改变。
运行程序,显示效果如上图所示。
编程训练
训练13:选择桌面主题 小红经常变换电脑桌面,由于收藏的桌面风格太多,他编写了一个程序,定义style局部变量,利用这个变量将所有风格输出。假设本例中需要输出风景、蓝天白云、奇幻梦境3种风格的壁纸。
训练14:钱包里还剩多少钱 将钱包中的总金额设为全局变量,定义一个pay(int number)付款方法,每次付款之后,都会减少钱包中的总金额。付款3次之后,钱包中还剩多少钱?

9.7 函数应用

编译系统通常会提供一些标准库函数,以供用户快速调用。不同的编译系统,提供的库函数会略有差异。下面介绍ANSI C提供的部分常用的库函数。

9.7.1 数学函数

在程序中经常会进行一些数学相关的运算,这里首先介绍有关数学的常用函数。注意,使用数学函数时,要包括math.h头函数,即要在程序开始处添加#include<math.h>。
**1.	abs函数**
abs函数的功能是求解整数的绝对值。函数形式如下:
int abs(int i);

例如,求一个负数绝对值的代码如下:

int iAbsoluteNumber;								/*定义整型变量*/
int iNumber=-12;										/*定义整型变量,为其赋值-12*/
iAbsoluteNumber=abs(iNumber);				/*调用abs函数*/

2. labs函数
labs函数的功能是求解长整型数的绝对值。函数形式如下:

long labs(long n);

例如,求一个长整型绝对值的代码如下:

long IResult;															/*定义长整型变量*/
long INumber=-1234567890L;									/*定义长整型变量,为其赋值-1234567890*/
IResult=labs(INumber);											/*调用labs函数*/
  1. fabs函数
    fabs函数的功能是求解实型数的绝对值。函数形式如下:
double fabs(double x);

例如,求一个实型数绝对值的代码如下:

double fFloatResult;								/*定义实型变量*/
double fNumber=-1234.0;							/*定义实型变量,为其赋值-1234.0*/
fFloatResult=fabs(fNumber);					/*调用fabs函数*/

【例9.16】判断两人相差几岁
在本实例中,使用abs函数计算两人相差的岁数。具体代码如下:
在这里插入图片描述运行程序,显示效果如上图所示。

4.sin函数
sin函数的功能是求解正弦。函数形式如下:
double sin(double x);
例如,求一个角正弦值的代码如下:

double fResultSin;									/*定义实型变量*/
double fXsin = 0.5;								/*定义实型变量,为其赋值为0.5*/
fResultSin=sin(fXsin);							/*使用正弦函数*/
  1. cos函数
    cos函数的功能是求解余弦。函数形式如下:
double cos(double x);

例如,求一个角余弦值的代码如下:

double fResultCos;							/*定义实型变量*/
double fXcos=0.5;							/*定义实型变量,为其赋值为0.5*/
fResultCos=cos(fXcos);					/*调用余弦函数*/
  1. tan函数
    tan 函数的功能是求解正切。函数形式如下:
double tan(double x);

例如,求一个角正切值的代码如下:

double fResultTan;										/*定义实型变量*/
double fXtan=0.5;										/*定义实型变量,为其赋值为0.5*/
fResultTan = tan(fXtan);							/*调用正切函数*/

【例9.17】求梯形顶角及正切值 已知一个梯形是由一个正方形和两个等腰直角三角形组成的,求此梯形顶角的度数和它的正切值。
在这里插入图片描述运行程序,显示效果如上图所示。

9.7.2 字符函数

下面要介绍的是3个有关字符的函数,使用时要先包含头文件ctype.h。
1.isalpha函数
isalpha函数的功能是检测某个字符是否是字母,如果是字母表中的字母(大写或小写),则返回非0值,否则返回0。函数形式如下:

int isalpha(int ch);

例如,判断输入的字符是否为字母的代码如下:

char c;								/*定义字符变量*/
scanf("%c",&c);					/*输入字符*/
isalpha(c);							/*调用isalpha函数判断输入的字符*/

2.isdigit函数
isdigit函数的功能是检测某个字符是否是数字,如果是数字,则函数返回非0值,否则返回0。函数形式如下:

int isdigit(int ch);

例如,判断输入的字符是否为数字的代码如下:

char c;									/*定义字符变量*/
scanf("%c",&c);						/*输入字符*/
isdigit(c);								/*调用isdigit函数判断输入的字符*/

3.isalnum函数
isalnum函数的功能是检测某个字符是否是字母或数字,如果是字母表中的某个字母或某个数字,则函数返回非0值,否则返回0。函数形式如下:

int isalnum(int ch);

例如,判断输入的字符是否为数字或字母的代码如下:

char c;									/*定义字符变量*/
scanf("%c"&c);						/*输入字符*/
isalnum(c);								/*调用isalnum函数判断输入的字符*/

【例9.18】判断输入字符的类型
在本程序中,向控制台输入字符,利用if判断语句和字符函数判断输入的是哪一种类型的字符,然后根据不同的字符类型输出相应提示信息。
在这里插入图片描述(1)要使用字符函数,先要引入头文件ctype.h。
(2)在程序中定义了两个字符变量,cCharPut用来在程序中接收用户输入的字符,而cCharTemp的作用是接收用户按Enter键所产生的回车符。
(3)定义SwitchShow函数,实现在程序中判断字符的功能。SwitchShow函数体中,在3个if语句的判断条件中调用不同的字符函数,根据返回值判断传递的字符参数cChar是哪种情况,并给出不同的提示信息。
(4)在main函数中调用getchar函数,其作用是获取一个字符。因为用户每次输入完毕后都要按Enter键进行确定,为防止回车符被当做输入的字符,这里调用了getchar函数预先提取走回车符。
运行程序,显示效果如下图所示。
在这里插入图片描述说明 可以尝试将main函数中getchar函数所在行的代码注销掉,运行程序并观察结果,会发现第二个输入操作会被程序跳过。想一想,为什么。

9.7.3 字符串处理函数

	编写程序时,经常要对字符和字符串进行操作,如转换字符大小写、求字符串长度等,这些都可以使用字符串函数来解决。C语言标准函数库中提供了一系列字符串处理函数,合理、有效地使用这些字符串函数,可以大大提高编程效率。本节将介绍常用的字符串处理函数,使用时要先包含头文件string.h。
****1.strcpy函数****
	在字符串处理函数中,strcpy函数用于复制特定长度的字符串到另一个字符串中。其语法格式如下:
	strcpy(字符数组1,字符数组2
	功能:把字符数组2中的字符串复制到字符数组1中,字符串结束标志“\0"也一同复制。复制过去的内容将覆盖字符数组1中的对应内容。
说明
(1)字符数组1要有足够的长度,否则无法装下待复制的字符串。
(2)“字符数组1”必须写成数组名形式;而“字符数组2”可以是数组名,也可以是一个字符串常量,这时相当于把一个字符串赋予该字符数组。
(3)不能用赋值语句将一个字符串常量或字符数组直接赋给一个字符数组。

【例9.19】照葫芦画瓢
在main函数体中定义两个字符数组,通过键盘输入获取两个字符串并分别输出,调用strcpy函数将源字符数组中的字符串赋值给目标字符数组,最后输出目标字符数组。
在这里插入图片描述运行程序,字符串复制效果如上图所示。
2.strcat函数
字符串连接就是将一个字符串连接到另一个字符串的末尾,使其组合成一个新的字符串。strcat函数就具有字符串连接的功能,其语法格式如下:

strcat(字符数组1,字符数组2

功能:把字符数组2中的字符串连接到字符数组1的字符串后面,并删去字符数组1中原有的串结束标志“\0"。

说明
字符数组1应有足够的长度,以保证能装下连接后的字符串。

【例9.20】显示文件完整路径
编写程序,接受用户输入的目录和文件名,然后输出文件的完整路径。
在main函数体中定义两个字符数组,分别存储输入的目录和文件名,调用strcat函数将文件名字符串连接到目录字符串的后面,最后输出目录字符数组。
在这里插入图片描述运行程序,字符串连接效果如上图所示。

说明
	字符串复制实质上是用字符数组2中的字符串覆盖字符数组1中的字符串,而字符串连接则不存在覆盖的问题,只是单纯地将字符数组2中的字符串连接到字符数组1中字符串的后面。
**```
**3.strcmp函数****
字符串比较就是将一个字符串与另一个字符串从首字母开始,按照ASCII码的顺序逐个进行比较。strcmp函数具有在字符串间进行比较的功能,其语法格式如下:

```c
strcmp(字符数组1,字符数组2

功能 :按照ASCII码顺序比较两个数组中的字符串,并返回比较结果。两个字符串比较时,若出现不同的字符,则以第一个不同字符的比较结果作为整个比较的结果。返回值如下:
☑字符串1=字符串2,返回值为0。
☑字符串1>字符串2,返回值为正数。
☑字符串1<字符串2,返回值为负数。

注意
	字符串比较不能使用关系运算符,也不能使用赋值运算符。因此,下面的语句是错误的:
	if(str[2]==microsoft)...
	str[2]=microsoft;...

【例9.21】模拟银行取钱
在自动取款机上取钱时,需要输入密码,密码正确方能取到钱。本实例中,正确的密码为574824,只有3次密码输入机会,如果3 次都输错了,就提示“请到人工处办理解锁”。
在这里插入图片描述运行程序,字符串比较效果如上图所示。
4.strupr函数和strlwr函数
字符串的大小写转换需要使用strupr和strlwr函数。strupr函数的语法格式如下:

strupr(字符数组名)

功能:将数组中存入的字符串的小写字母转换成大写字母,其他字母不变。
strlwr函数的语法格式如下:

strlwr(字符数组名)

功能:将数组中存放的字符串的大写字母转换成小写字母,其他字母不变。
【例9.22】字符串大小写转换
将张三的邮箱地址ZhangSan@MRSOFT.COM全部转换为小写或大写。
在main函数体中定义两个字符数组,分别用来存储要转换的字符串和转换后的字符串,然后根据用户输入的操作指令,判断应调用strupr函数还是strlwr函数,进行大小写转换。
在这里插入图片描述运行程序,字符串大小写转换效果如上图所示。

5.strlen函数
有时需要知道字符串的长度,虽然通过循环判断字符串结束标志“\0"也能获得字符串的长度,但毕竟实现起来相对烦琐。这时可以使用strlen函数来计算字符串的长度。
strlen函数的语法格式如下:

strlen(字符数组名)

功能:计算数组中存放的字符串的实际长度(不包含字符串结束标志"\0")。

【9.23】判断用户输入的密码是否为6位
在本实例中的main函数体中定义一个字符数组,用来存储用户输入的字符串,然后调用strlen函数计算字符串长度,并使用if…else…语句判断密码长度是否等于6。
在这里插入图片描述运行程序,输出效果如上图所示。

编程训练
训练15:判断地铁两站间行车时间 已知某地铁线路有5站,将从始发站发车行驶到各个站的时间集合{5,10,15,20,25}保存成一个一维数组a,编写程序,判断任意两站之间需要行车多长时间。运行结果如下:

请任意输入两站:
3 1
两站之间行车需要10分钟

训练16:寻找座位号 使用isdigit函数判断输入的座位号是否是数字,运行效果如下:
结果一:

请输入您的位置号:
--------------------------------------------------
|		对不起,您输入的座位号格式不正确              |
|			不能找到您的位置											|
--------------------------------------------------

结果二:
请输入您的位置号:5

请输入您的位置号:5************
您的位置号是5
您输入的格式正确,请坐......************

训练17:显示天气预报 使用strcat函数,输出2021年1月23日的实时天气预报。输出结果如下:

2021123日  晴,20℃到7℃,微风转西风3~408:0013℃ 		微风			
12:0019℃			微风
16:0018℃			微风
20:0015℃            微风
00:0012℃		     微风

训练18:更新公告 利用strcpy函数,将包子店的招牌“包子一元一个”换成“包子壹圆壹个”。
输出结果如下:

原来的招牌内容是:
包子一元一个
经过处理之后的招牌内容是:
包子壹圆壹个

9.8 实践与练习

综合练习1:模拟12306售票系统 12306售票系统全国联网,数据同步。因此,每售出一张票,全国各地的系统显示都会减少一张票数。利用全局变量模拟12306抢票系统,输出效果如下:

始发地:上海			目的地:长春			时间:202141016:20出发
3个城市剩余的票数分别为:
上海的12306系统剩余票数:99张
北京的12306系统剩余票数:99张
深圳的12306系统剩余票数:99张
我抢到一张票数之后剩余票数:98
我抢到一张票之后3个城市剩余的票数分别为:
上海的12306系统剩余票数:98张
北京的12306系统剩余票数:98张
深圳的12306系统剩余票数:98

综合练习2:为和尚写诗 自定义一个poetry函数,为和尚写一首诗。诗句如下:

空门有路不知处
头白齿黄犹念经
何年饮着声闻酒
迄至如今醉未醒

综合练习3:一棵松树的梦 在源文件中定义一个全局变量pinetree,并为它赋值。再定义一个christmastree函数,在这个函数里定义名称为pinetree的局部变量,并输出,最后在主函数中调用christmastree函数,并输出全局变量pinetree的值。输出结果如下:

下雪了......
===============开始做梦......==========================
挂上彩灯、礼物......我变成一棵圣诞树@^.^@
===============梦醒了......============================

我身上落满雪花,我是一棵松树-_-
综合练习4:确定女主角			某导演有一个剧本,需要寻找演员来饰演对应的角色。利用函数的实参和形参知识编写代码,实现为剧本选女主角的功能。输出结果如下:

```c
导演选定女主角是:Lucy
➵**


Lucy开始参演李美丽角色

综合练习5:为C语言归类		要想在腾讯课堂上查看某个课程,一般需要知道其分数。例如,C语言的所属类别从大到小依次为“IT—互联网―编程语言―C",利用函数嵌套找到C语言课程。效果如下:

```c
(1)找到IT分类
(2)IT分类中找到互联网分类
(3)互联网分类中找到编程语言分类
(4)编程语言分类找到C语言课程

综合练习6:递归求年龄 甲乙丙丁戊5个人坐在一起聊天。大家猜戊的年龄,他说比丁大2岁;问丁的岁数,他说比丙大2岁;问丙的岁数,他说比乙大2岁;问乙的岁数,他说比甲大2岁;问甲的岁数,他说自己10岁。编写程序求戊的年龄。输出结果如下:

--------------------------------
戊的年龄是:18--------------------------------

综合练习7:将美元兑换成人民币 编程实现美元兑换人民币功能。美元与人民币之间的汇率经常变更,这里按1美元等于6.28元人民币计算。输出结果如下:

您要兑换的美元金额:500
*********************************
*		兑换成人民币金额是:3140.00  *
*********************************

综合练习8:你的心跳正常吗? 利用把函数作为参数使用,编写程序判断输入的心跳数是否是正常心跳数。当心跳数在60~100次/分钟,显示心跳数正常,运行结果如图9.29所示;当心跳大于100次/分钟或小于60次/分钟时,就显示心跳不正常,运行结果如图9.30所示。

______________________________
|   请输入每分钟心跳数:			|
|		96									|
|	当前心跳数是:96					|
|  ●→●→●→●→●→●→●→●→●→●→●→●→	|
|      心跳正常						|
|  ●→●→●→●→●→●→●→●→●→●→●→●→	|
—————————————————————————————

————————————————————————————————
|		请输入每分钟心跳数:				|
|		59										|
|		当前心跳数是:59					|
|		¤ ¤ ¤ ¤ ¤ ¤ ¤ ¤ ¤ ¤ ¤ ¤		|
|			心跳不正常						|
|		¤ ¤ ¤ ¤ ¤ ¤ ¤ ¤ ¤ ¤ ¤ ¤		|
——————————————————————————————————

综合练习9:注册微软VIP账号 在微软注册账号时,账号长度要求为4~12位,使用strlen函数判断注册的微软账号是否符合要求。输出结果如下:

请输入您想注册的微软账号:
microsoft
注册成功

综合练习10:判断车牌归属地 根据车牌号可以知道一辆车的归属地。编写程序,利用strcmp函数判断车牌的归属地。运行结果如下:

车牌号归属地查询:

津 A • 12345	这个车牌号的归属地是:天津
沪	A • 23456		这个车牌号的归属地是:上海
京 A • 34567	这个车牌号的归属地是:北京
  • 23
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

manyoftenvictory

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值