第4章 函数1207.docx
- 文档编号:27035153
- 上传时间:2023-06-26
- 格式:DOCX
- 页数:41
- 大小:670.72KB
第4章 函数1207.docx
《第4章 函数1207.docx》由会员分享,可在线阅读,更多相关《第4章 函数1207.docx(41页珍藏版)》请在冰豆网上搜索。
第4章函数1207
第4章函数
学习目标
◆掌握如何定义函数及调用函数
◆了解内存四区的概念及栈区、堆区的工作原理
◆掌握外部函数与内部函数的概念及其区别
◆掌握局部变量与全局变量的概念及其屏蔽规则
通过前面几章的学习,相信大家会编写一些简单的C语言程序了,但是,随着程序功能的增多,main()函数中的代码也会越来越多,导致main()函数中的代码繁杂、可读性太差,维护也变的很困难。
此时,可以将功能相同的代码提取出来,将这些代码模块化,在程序需要的时候直接调用。
这就好比组装机器,需要什么直接装上就可以了。
C语言的函数类似于机器的组装部件,它用于实现某些特定的功能,本章将针对函数的相关知识进行详细地讲解。
初识函数
4.1.1函数的概念
日常生活中解决实际问题时,经常把一个大任务分解为多个较小任务后,由多人分工协作完成。
用C语言编写程序时也采用类似的方法,即把一个较大的程序分解为多个程序模块,然后逐步编写每一个程序模块。
当面对的程序有成千上万行代码时,一般先将它划分为若干程序模块,每个模块用来实现一个特定的功能;然后再分别实现各个模块,组成一个完整的程序。
这样的思路不仅易于理解、便于操作,而且由于“好”的模块便于重复使用,还可以大量减少编写重复代码的工作量,提高编程效率。
在C语言中,最简单的程序模块就是函数。
函数被视为程序设计的基本逻辑单位,一个C程序是由一个main()函数和若干个其他函数组成的。
程序执行从main()函数开始,由main()函数调用其他函数,函数之间可以相互调用。
在使用函数时,有几点需要注意一下,接下来针对这几点进行说明,具体如下:
1、C程序的执行是从main()函数开始的。
2、一个C程序是由一个或多个程序模块组成,每一个程序模块作为一个源程序文件。
而一个源程序文件由一个或多个函数以及其他有关内容(如指令、数据声明与定义等)组成。
3、所有函数都是平行的,即在定义函数时是分别进行的,是互相独立的。
一个函数并不从属于另一个函数,即函数不能嵌套定义。
4、从用户使用的角度看,函数分为两种:
一是库函数,它由系统提供,用户不必自己定义即可直接使用;二是用户自己定义的函数,它是用以解决用户专门需要的函数。
5、从函数的形式看,函数分为两种:
一是无参函数,即不带参数的函数;二是有参函数,即带有参数的函数。
4.1.2函数的定义
在C语言中,定义一个函数的具体语法格式如下:
返回值类型函数名([[参数类型参数名1],[参数类型参数名2],……,[参数类型参数n]])
{
执行语句
………
return返回值;
}
为了让读者更好地理解函数的各个组成部分,接下来对上述语法格式进行简要说明,具体如下:
●返回值类型:
用于限定函数返回值的数据类型;
●函数名:
表示函数的名称,该名称可以根据标识符命名规范来定义;
●参数类型:
用于限定调用方法时传入参数的数据类型;
●参数名:
用于接收调用方法时传入的数据;
●return关键字:
用于结束函数,并返回函数指定类型的值;
●返回值:
被return语句返回的值,该值会返回给调用者。
如果函数没有返回值,则返回值类型要声明为void,此时,函数体中的return语句可以省略不写。
在上面的语法格式中,函数中的“[[参数类型参数名1],[参数类型参数名2],…,[参数类型参数n]]”被称作参数列表,它用于描述函数在被调用时需要接收的参数。
如果函数不需要接收任何参数,则参数列表为空,这样的函数被称为无参函数。
相反地,参数列表不为空的函数就是有参函数。
接下来分别讲解这两种函数。
1、无参函数
在C语言中,无参函数的定义很简单,先来看一个定义无参函数的示例代码,具体如下:
voidfunc()
{
printf("这是我的第一个函数!
\n");
}
上述示例代码中,func()函数就是一个无参函数,参数列表为空。
要想执行这个函数,需要在main()函数中调用它,接下来通过一个案例来演示,如例4-1所示。
例4-1
1#include
2voidfunc()
3{
4printf("这是我的第一个函数!
\n");
5}
6voidmain()
7{
8func();
9}
运行结果如图4-1所示。
图4-1运行结果
从图4-1中可以看出,func()函数被成功调用了。
在程序中,第2行代码定义了一个无参函数func(),第4行代码将字符串打印到控制台,第8行代码在main()函数中调用该无参函数。
下面通过一张流程图来说明上面例子中函数的调用过程,具体如图4-2所示。
图4-2主函数中调用子函数func()的流程图
从图4-2可以看出程序是由上至下按顺序执行的,程序首先从主函数开始执行,遇到“func();”语句后跳转到func()函数,执行func()函数体中的代码。
执行完func()函数后返回到主函数原来的调用点(即“func()”语句),接着执行调用点后面的语句,如果后面没有其他语句,则主函数执行结束。
2、有参函数
与无参函数相比,有参函数在函数定义时,需要在函数名称后面的括号中填写参数。
所谓的参数是一个变量,用于接收调用者传入的数据。
定义有参函数的示例代码如下:
voidfunc(intx,inty)
{
intsum=x+y;
printf("x+y=%d\n",sum);
}
上述代码中,定义了一个实现加法运算的函数func(),并指定了两个int类型的参数x和y。
为了初学者更好地掌握有参函数的用法,接下来在main()函数中调用func(intx,inty)函数,如例4-2所示。
例4-2
10#include
11voidfunc(intx,inty)
12{
13intsum=x+y;
14printf("x+y=%d\n",sum);
15}
16voidmain()
17{
18func(3,5);
19}
运行结果如图4-3所示。
图4-3运行结果
在例4-2中,第2~6行代码定义了一个函数func(),该函数包含两个参数,分别是x和y。
当在main()函数中调用func()函数时,传入函数中的参数是3和5,即在func(intx,inty)中执行了3+5的操作,因此,程序打印的结果为“x+y=8”。
接下来通过一张图例来描述func()函数的调用过程,具体如图4-4所示。
图4-4主函数中调用子函数func()的流程图
从图4-4可以看出,有参函数和无参函数的调用过程类似,但在调用有参函数时,需要传入实参,并将传入的实参赋值给形参,然后在函数体中执行3+5的操作,最终将结果输出到控制台。
值得一提的是,在定义有参函数时指定的参数x和y是形式参数,简称形参,它们只在形式上存在。
调用函数时传入的参数(如案例中的3和5)是实际参数,简称实参,与形参相对,实参则是指实际参加运算的参数。
4.1.3函数的返回值
通过前面的讲解可知,函数的返回值是指函数被调用之后,返回给调用者的值。
函数的返回值具体语法格式如下:
return表达式;
对于返回值类型为void的函数,可以直接在return语句后面加分号,具体语法格式如下:
return;
为了让读者更好的学习如何使用return语句,接下来对例4-2进行改写,使func(intx,inty)函数能够返回求和计算的结果,修改后的具体代码如例4-3所示。
例4-3
20#include
21intfunc(intx,inty)
22{
23intsum=x+y;
24returnsum;
25}
26voidmain()
27{
28intsum=func(3,5);
29printf("x+y=%d\n",sum);
30}
运行结果如图4-5所示。
图4-5运行结果
从图4-5中可以看出,例4-3与例4-2实现了同样的功能。
接下来通过一个图例来演示func()函数的整个调用过程以及return语句的返回过程,如图4-6所示。
图4-6func()函数的调用过程
从图4-6可以看出,在程序运行期间,参数x和y相当于在内存中定义的两个变量。
当调用func(intx,inty)函数时,传入的参数3和5分别赋值给变量x和y,并将x+y的结果通过return语句返回,整个函数的调用过程结束,变量x和y被释放。
return语句还有一个重要的作用,就是能提前结束函数的执行,即当函数执行到return语句时,就会立即跳出,不再执行后面的代码。
下面通过一个案例来了解return语句提前跳出函数,如例4-4所示。
例4-4
31#include
32intfunc(inta,intb)
33{
34intresult;
35printf("开始计算%d与%d的和\n",a,b);
36result=a+b;
37returnresult;
38printf("开始计算%d与%d的乘积\n",a,b);
39result=a*b;
40returnresult;
41}
42voidmain()
43{
44intresult=func(3,5);
45printf("func()函数的返回值是%d\n",result);
46}
运行结果如图4-7所示。
图4-7运行结果
例4-4中,第2-11行代码定义了func()函数,该函数的return语句后面还有一段代码。
从图4-7中可以看出,func()函数在执行到第一个return语句后就跳出函数体,不再继续往下执行了,说明return语句可以提前结束函数的执行。
需要注意的是,return后面表达式的类型和函数定义返回值的类型应保持一致。
如果不一致,就有可能会报错。
为保证程序的可读性和逻辑性,没有返回值的函数都应定义为void。
4.1.4printf()函数和scanf()函数
在C语言开发中,经常会进行一些输入输出操作,为此,C语言提供了printf()和scanf()函数,其中,printf()函数用于向控制台输出字符,scanf()函数用于读取用户的输入,下列将分别讲解这两个函数的用法。
1、printf()函数
在前面的章节中,经常使用printf()函数输出数据,它可以通过格式控制字符输出多个任意类型的数据。
表4-1列举了printf()函数中常用的格式控制字符。
表4-1常用printf()格式字符
常用格式字符
含义
%s
输出一个字符串
%c
输出一个字符
%d
以十进制输出一个有符号整型
%u
以十进制输出一个无符号整型
%o
以八进制输出一个整数
%x
以十六进制输出一个小写整数
%X
以十六进制输出一个大写整数
%f
以十进制输出一个浮点数
%e
以科学计数法输出一个小写浮点数
%E
以科学计数法输出一个大写浮点数
表4-1中列举了很多格式控制字符,使用这些格式控制符可以让printf()输出指定类型的数据,接下来通过一个具体的案例来演示这些格式控制符的使用,如例4-5所示。
例4-5
47#include
48voidmain()
49{
50printf("%c%c",'H','\n');
51printf("%s","Hello,world!
\n");
52printf("%d%d%d\n",1,2,3);
53printf("%f%f\n",2.1,2.2);
54}
运行结果如图4-7所示。
图4-8运行结果
在例4-5的printf()函数中,通过格式控制字符“%c”、“%s”、“%d”、“%f”,分别输出了字符、字符串、整数、浮点数。
脚下留心:
程序中处处都能看到逗号的身影,但不是所有逗号都是逗号运算符,例如下面的程序:
inta=1,b=2,c=3;
printf("%d\n",a,b,c);
该程序的运行结果是“1”。
printf()函数虽然用了三个逗号,但在这里逗号的作用是分隔开函数参数,该函数打印出变量“a”的值。
但若我们加入小括号,例如下面的程序:
inta=1,b=2,c=3;
printf("%d\n",(a,b,c));
该程序的运行结果就变成了“3”。
小括号的加入将“a,b,c”变成了一个表达式,此时该表达式的逗号就是逗号运算符。
printf()函数打印出了逗号表达式“a,b,c”的值。
2、scanf()函数
scanf()函数负责从标准输入设备(一般指键盘)上接收用户的输入,它可以灵活接收各种类型的数据,如字符串、字符、整型、浮点数等,scanf()函数也可以通过格式控制字符控制用户的输入,其用法与printf()函数一样。
接下来,通过一个获取整数的案例讲解scanf()函数的用法,如例4-6所示。
例4-6
55#include
56voidmain()
57{
58inti;
59printf("请输入一个整数:
");
60scanf("%d",&i);
61printf("您刚才输入的整数是:
%d\n",i);
62}
运行结果如图4-9所示。
图4-9运行结果
例4-6中,第4行代码定义了整型变量i,第6行代码调用scanf()函数将用户从键盘上输入的字符保存到变量i中,第7行代码将变量i的值打印在控制台上。
从图4-9中可以看出,输入的数值被成功打印到控制台上,说明scanf()函数可以将用户从键盘上输入的数据赋给某个变量。
需要注意的是,例4-6中第6行代码我们将“&i”作为函数参数传给scanf()函数,这是因为数据从实参传递给形参是单向传递的(详细说明请看第4.3.2小节),scanf()函数无法将数据直接赋给变量i,所以scanf()函数通过变量i的地址来直接改变变量i所在内存空间的数值,而表达式“&i”返回的就是变量i的地址。
这里的内容只需了解即可,读者只需知道scanf()函数接收的是变量的地址即可,详细的原因将在第6章(指针)中说明。
脚下留心:
C语言中的终止符
在使用scanf()函数获取用户输入的信息时,如果输入的信息中包含某个终止符,scanf()函数就认为输入结束,接下来就列举一些常见的终止符,具体如表4-2所示。
表4-2scanf()输入字符串的终止符
字符
含义
0x20
空格
\t
水平制表符(tab键)
\n
换行
\v
垂直制表符
\f
换页
\r
回车
接下来,以例4-6为例,当程序运行后,如果输入的字符串包含空格,例如“Helloworld”,此时程序的运行结果如图4-10所示。
图4-10运行结果
从图4-10中可以看出,尽管输入的字符串是Helloworld,但是程序只打印了Hello。
这是因为Helloworld中包含一个空格,空格也是一个终止符,因此scanf()只能读到空格之前的内容。
内存四区
C语言程序运行时,操作系统会为其分配内存空间,这段空间主要分为四个区域,分别是栈区、堆区、数据区和代码区,也就是“内存四区”。
要学好C语言,需要了解内存四区。
1、栈区
是一块连续的内存区域,该区域由编译器自动分配和释放,一般用来存放函数的参数、全局变量等。
由于栈顶的地址和栈区的最大容量是由系统预先规定好的,因此这块区域的内存大小是固定的。
假如申请的内存空间超过栈区的剩余空间,那么系统会提示栈溢出,因此,别指望栈区能存储较多的数据。
虽然栈区的容量小,但由于是系统自动分配的,同时栈区的内存空间是连续的,不会产生内存碎片,因此栈区中数据的执行速度很快。
栈区的特点是先进后出,后进先出。
2、堆区
是不连续的内存区域,各块区域由链表将它们串联起来。
该区域一般由程序员分配或释放,若程序员不释放,程序结束时可能由操作系统回收(程序不正常结束则回收不了)。
堆区的上限是由系统中有效的虚拟内存来定的,因此获得的空间较大,而且获得空间的方式也比较灵活。
虽然堆区的空间较大,但必须由程序员自己申请,而且在申请的时候需要指明空间的大小,不使用的时候还需要手动注销掉。
非常麻烦,同时堆区的内存空间不是连续的,容易产生内存碎片,因此堆中数据的执行速度比较慢。
3、数据区
根据功能又可以分为静态全局区和常量区两个域。
全局区(静态区)(static):
用于存储全局变量和静态变量的区域,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和静态变量在相邻的另一块区域。
该区域在程序结束后由操作系统释放。
常量区:
用于存储字符串常量和其他常量的区域,该区域在程序结束后由操作系统释放。
4、代码区
用于存放函数体的二进制代码。
程序中每定义一个函数,代码区都会添加该函数的二进制代码,用于描述如何运行函数。
当程序调用函数时,就会在代码区寻找该函数的二进制代码并运行。
下面我们通过一段代码来熟悉一下数据在内存的存储,如例4-7所示。
例4-7
63#include
64#include
65inta=100;//全局变量a在全局初始化区
66intb;//全局变量b在全局未初始化区
67staticintc;//静态全局变量c在全局未初始化区
68intadd(intx,inty)//参数x和y在栈中
69{
70intz;//局部变量z在栈中
71z=x+y;
72returnz;
73}
74voidmain()
75{
76intx=0;//局部变量x在栈区中
77printf("hello\n");//字符串常量hello在常量区中
78staticintd=1;//静态局部变量d在全局初始化区
79printf("&a:
%p\n",&a);//输出变量a的地址
80printf("&b:
%p\n",&b);//输出变量b的地址
81printf("&c:
%p\n",&c);//输出变量c的地址
82printf("&d:
%p\n",&d);//输出变量d的地址
83printf("&x:
%p\n",&x);//输出变量x的地址
84malloc(10);//堆区中分配10个字节大小的内存空间
85}
上述案例的运行结果如图4-11所示。
图4-11运行结果
在例4-7中,分别定义了全局变量a、b、c,静态局部变量d和局部变量x,并将其地址打印出来,第22行代码调用malloc()函数在堆区开辟了10个字节的内存空间。
从图4-11中可以看出,字符串常量“hello”和变量a、b、c、d、x的地址都被打印出来了,说明不管是局部变量、全局变量还是常量,都可以用相同的方式来调用。
函数调用
在C语言中,一个完整的应用程序不可能在一个函数中实现所有的功能。
通常程序都是由若干个功能不同的函数来实现的,并且函数之间会存在互相调用的情况。
本节将针对函数的调用方式、嵌套调用以及递归调用进行详细地讲解。
4.1.5函数的调用方式
函数是C语言的基本组成元素,要想实现函数的功能,必须学会正确调用函数。
在C语言中,调用函数的具体语法格式如下:
函数名([[实参列表1],[实参列表2],...]);
从上面的语法格式可以看出,当调用一个函数时,需要明确函数名和实参列表。
实参列表中的参数可以是常量、变量、表达式或者为空,多个参数之间使用英文逗号分隔。
需要注意的是,如果调用的是无参函数,实参列表为空,但是不可以省略括号。
要求实参与形参必须个数相等、顺序对应、类型匹配。
根据函数在程序中出现的位置,可以分为下列三种函数调用方式:
1、将函数作为表达式调用
将函数作为表达式调用时,函数的返回值参与表达式的运算,此时要求函数必须有返回值。
示例代码如下所示:
intret1=max(100,150);
上述代码中,函数max是表达式的一部分,其返回值赋给变量ret1。
2、将函数作为语句调用
函数以语句的形式出现时,可以将函数作为一条语句进行调用。
示例代码如下所示:
printf("Hello,world!
\n");
上述代码中,不要求被调用函数有返回值,只要求函数完成一定的操作即可。
3、将函数作为实参调用
将函数作为另一个函数的实参时,要求该函数有返回值。
示例代码如下所示:
printf("%d\n",max(100,150));
在上面的语句中,将函数max()的返回值作为printf()函数的实参来使用。
C语言调用函数其实是将函数的变量压入栈区中进行操作的。
接下来通过一个案例来演示函数调用在栈区中的表现,具体代码如例4-8所示。
例4-8
86#include
87voidfunc2(intm,intn)
88{
89printf("func2()函数中,变量m的值是%d,变量n的值是%d\n",m,n);
90}
91voidfunc1(intm,intn)
92{
93func2(m,n);
94printf("func1()函数中,变量m的值是%d,变量n的值是%d\n",m,n);
95}
96voidmain()
97{
98inta=1;
99intb=2;
100func1(a,b);
101printf("main()函数中,变量a的值是%d,变量b的值是%d\n",a,b);
102}
运行结果如图4-12所示。
图4-12运行结果
上面案例中程序的执行流程如图4-13所示。
图4-13例4-8中程序的入栈流程
(1)程序首先执行main()函数,将变量a、b压入栈区中,如图4-13中图①所示。
(2)程序执行到func1()函数,将变量a、b的值作为函数参数传入func1()函数中,然后跳转到func1()函数中,将变量m、n压入栈区中并赋入函数参数,如图4-13中图②所示。
(3)程序执行到func2()函数,将变量m、n的值作为函数参数传入func2()函数中,然后跳转到func2()函数中,将变量m、n压入栈区中,并执行printf()函数,如图4-13中图③所示。
图4-14例4-8中程序的出栈流程
(4)执行完func2()函数后,栈区内与func2()函数有关的内存空间被销毁并回收,程序回到func1()函数体内,继续执行剩余的代码
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 第4章 函数1207 函数 1207