当前位置首页 > 建筑/施工 > 施工组织
搜柄,搜必应! 快速导航 | 使用教程  [会员中心]

C++程序设计第4章 函数和预处理教学课件

文档格式:PPT| 111 页|大小 2.63MB|积分 10|2024-12-11 发布|文档ID:253313215
第1页
下载文档到电脑,查找使用更方便 还剩页未读,继续阅读>>
1 / 111
此文档下载收益归作者所有 下载文档
  • 版权提示
  • 文本预览
  • 常见问题
  • 函数和预处理教学课件,函数与预处理,,第,4,章,,,4.1,函数的概念,4.2,函数的定义和声明,,4.3,函数的调用,,4.4,函数的重载,3,,4.5,变量,,习题,4.6,内部函数和外部函数,,4.7,预处理命令,4,第,4,章,,函数与预处理,前面已经介绍了常量、变量、表达式和语句,这些都是组成程序的基本要素本章将着重介绍程序中的另外一个重要概念,——,函数对于一个大型的程序,为便于实现,一般分为若干程序模块,每一个模块实现一个特定的功能在,C++,语言中,模块的功能由函数来实现C++,程序通过函数将上述基本要素结合在一起,完成一定的功能函数的实现将有利于信息的隐藏和数据共享,节省开发时间,增强程序的可靠性本章介绍函数的定义、声明、调用以及如何使用函数等内容,此外还将简略介绍有关预处理的概念5,4.1,函数的概念,“函数”这个名词是从英文,function,翻译过来的,其实,function,的原意是“功能”顾名思义,一个函数就是一个功能当然,读者可能会联想到数学中的函数数学中的函数可以根据输入的数值求得一个确定的值与此类似,,C++,中的函数也可以根据输入的数据,返回一个结果值。

    不同的是,,C++,中的函数涵盖的意义更加广泛,定义也更加灵活,不仅可以求值,还可以执行一组相关的操作在后文中,如不加说明,所涉及函数都是指,C++,中的函数一个函数就是一个语句的集合,这些语句组合在一起完成一项操作系统实际上按顺序执行了多条语句以返回所需要的结果6,4.1.1,使用函数的必要性,客观地说,函数是,C++,程序的构成基础从前面所遇到的程序来看,一个,C++,程序可由一个主函数,main(),和若干子函数构成,而且必须有一个,main(),函数因为任何一个,C++,程序都是从主函数(即,main(),)的左花括号开始执行,一直到,main(),的右花括号为止函数也是程序中的最小模块,可以重复使用7,4.1.2,函数的组成部分,,,函数由函数头和函数体两部分构成,而函数头又是由返回值类型、函数名和参数列表构成其一般形式如下[,数据类型,],),,<,函数名,> [<,形式参数列表,>] //,函数头,,{,,,语句,,//,函数体,,},,8,下面举个例子来帮助大家理解函数的概念例,4-1】,求出两个整数中的较小值include ,,using namespace std;,,int min(int x,int y) //,定义有参函数,min,,{,,int z;,,z=x>a >>b;,,c=min(a,b); //,调用,min,函数,,cout <<"min=" <

    程序执行结果如图,4-1,所示图,4-1 【,例,4-1】,执行结果,,10,,1,)数据类型,,数据类型是指函数返回值的数据类型如,【,例,4-1】,中的,min,函数返回一个,int,型数据(即,int,型,z,),就称该函数的返回值类型为,int,型如果某个函数不返回任何值,则其返回值类型是,void,,定义这样的函数的目的不是求一个值,而只是执行一组操作在此读者只需记住:当函数名前面出现,void,型返回值类型时,函数体的末尾无,return,语句注意:,C,语言中规定,如果在定义函数时不指定函数类型,系统会隐含指定函数类型为,int,型,因此,在,C,语言中,,int min(int x,int y),可以简写为,min(int x,int y),,但,C++,取消了这一规定,要求在定义函数时必须指定函数的类型,这样做更加严格和安全11,2,)函数名,,函数名就是函数的名字,即函数的标识符既然是标识符,就必须遵循标识符的命名规则由变量的标识符可知,函数的标识符也只能由字母、数字以及下划线组成,并且不能以数字开头值得注意的是,在给函数命名时,应尽量使函数名体现出该函数的功能或寓意,因为一个准确、有意义的函数名可以提高程序的可读性。

    3,)形式参数,,形式参数简称形参形参列表是包含在函数名后圆括号中的,0,个或多个以逗号分隔的变量定义它规定了函数将从调用函数中接收几个数据及它们的类型其中当函数形参个数为零时,称为无参函数;函数形参个数不为零时,称为有参函数之所以称为形参,是因为在定义函数时系统并不为这些参数分配存储空间,只有被调用时,向它传递了实际参数(简称实参),才为形参分配存储空间当然在调用结束后,形参所占用的存储单元又会被释放如,【,例,4-1】,中,变量,x,、,y,是形参,变量,a,、,b,是实参注意:,当形参个数不为,0,时,在定义相同类型的形参时,对于参数表中的每个参数,都要求明确地指定类型,如,min,(,int x,int y,)不能写成,min(int x,y),12,4,)函数体,函数体是一个用一对花括号“,{},”括起来的语句序列,它描述了函数实现一个功能的过程当函数体中有,return,语句时,函数执行完,return,后结束;当函数体中无,return,语句,即函数的返回值类型为,void,时,函数执行完最后一条语句遇到右花括号“,},”时结束在此对,return,语句作一些说明1,)一般来说,函数的返回值是通过函数中的,return,语句获得的。

    return,语句将被调用函数中的一个确定值带回主调函数中,且,return,语句执行后,函数就结束了例如:,,{,,…,,return 0,;,,//,函数返回,,cout <<",函数结束!," <

    函数执行完最后一条语句,遇到右花括号“,},”结束5,)一个函数体中可以有多个,return,语句,但每次只能通过一个,return,语句执行返回操作例如:,,int max(int x,int y) //,返回两个整数中的较大值,,{,,if(x>y),,return x;,,else,,return y;,,},,一个函数体中有多个,return,语句时,如,【,例,4-1】,中有两个,return,语句,每个,return,语句的返回值类型都应与函数定义一致15,4.2,函数的定义和声明,函数声明也称为函数原型声明,是指在函数尚未定义的情况下,先将函数的形式告知编译系统,以便编译能够正常进行函数的声明包括返回值类型、函数名和参数列表值得注意的是,函数的声明仅仅是一条语句,因此在函数声明后一定要加上分号与函数的声明不同,函数的定义除包含函数头外,还包括函数体,是一个独立的完整的函数单位函数的定义又称函数实现16,4.2.1,函数的定义,函数可以是系统预定义的,也可以是用户自定义的前者称为系统函数或标准库函数,后者称为用户自定义函数1,)系统函数,,系统函数是由编译系统提供的,用户不必自己定义这些函数就可以直接使用它们。

    当然在调用时也无须声明,用户只需用,#include,命令包含相应的头文件即可例如,前面已经用到过,#include ,,其中,cmath,是一个头文件,在,cmath,文件中包括了数学库函数所用到的一些宏定义信息和对函数的声明17,2,)用户自定义函数,用户自定义函数时应满足函数的,4,个组成部分:返回值类型、函数名、参数列表和函数体从函数的形式看,可分为有参函数和无参函数例如:,/*,定义有参函数,f1*/,,int f1(int x) //,有参函数,函数头,,{ //,函数体,,……,,return 0; //,返回,0,值,函数结束,,},,/*,定义无参函数,f2*/,,void f2() //,无参函数,函数头,,{ //,函数体,,……,,} //,无返回值,函数结束,上面两个程序,都是函数的完整定义18,函数不能嵌套定义,即在函数体中不能定义另外一个函数下面的情形是不允许出现的:,,void function1(),,{,,int function2() //,错误!函数定义不允许嵌套,,{,,…,,return 0,;,,},,},,虽然函数不允许嵌套定义,但可以在函数体中声明另外一个函数。

    下面的情形是可以出现的:,,void function1(),,{,,int function2(); //,正确!在函数,function1,中声明函数,function2,,},,读者不妨自己上机试一试19,4.2.2,函数的参数列表,前面已经提到,在函数的声明和定义中,函数的参数列表可以为空(即无参函数),也可以不为空(即有参函数)如果函数为无参函数,则可用空参数列表或带一个,void,关键字的参数列表例如:,,int function(),,{,,…,,return 0;,,},,int function(void),,{,,…,,return 0;,,},,20,这两种定义方式是等价的,都表示不接受任何参数如果函数为有参函数,则在声明函数时,参数列表中的每个参数可以有名字,也可以没有名字例如:,,int min(int x,int y);,,int min(int ,int );,,,这两种声明方式是等价的,都表示接受两个整型参数但在定义函数时,参数列表中的每个参数就必须给出名字,形如“,int min(int ,int ),”在编译时就会出错。

    此外,此处的参数名也有其特殊的意义:一方面,便于程序阅读者阅读;另一方面,可以提示该参数的含义,开发者可以根据参数的名字传入合适的参数21,4.2.3,函数的声明,,C++,标准规定:函数在调用之前,必须先声明在,C++,中,当函数定义在前,函数调用在后时,调用前可以不必声明,因为编译器已经知道了该函数的全部信息例,4-2】,求两个整数之和include ,,using namespace std;,,int add(int x,int y) //,求两个整数相加函数的定义,,{,,return (x+y); //,函数返回两个整数的和,,},,int main() //,主函数,,{,,int a,b,c; //,声明,3,个整型变量,,cout <<"please enter two integers: " <>a >>b; //,输入整数,,c=add(a,b); //,调用,add,函数,求和,,cout <<"c=" <

    图,4-2,求两个整数之和的运行结果,,,当编译器由上而下顺序编译下来,到第,12,行调用函数,add,时,因,add,函数接受的参数类型、参数个数及其返回值都已经明确,所以不需要另外声明如果一个函数的定义在后,调用在前,此时在调用前必须声明函数的原型,否则编译将会出错23,#include ,,using namespace std;,,int main() //,主函数,,{,,int a,b,c; //,声明,3,个整型变量,,cout <<"please enter two integers: " <>a >>b; //,输入整数,,int add(int x,int y); //,求两整数相加函数的声明,,c=add(a,b); //,调用,add,函数,求和,,cout <<"c=" <

    24,如果输入,a,的值为,4,,,b,的值为,6,,那么程序的运行结果将与,【,例,4-2】,的结果相同例,4-3,中的第,8,行就是函数,add,的声明,当编译器编译到这行代码时,就会知道,add,函数的基本信息:函数名为,add,,需要接受两个整型参数,返回一个整数对比,【,例,4-2】,和,【,例,4-3】,之后,或许读者会提出这样的疑问:编程时把函数定义都写在调用之前,把,main,函数写在最后,岂不是就可以省略函数声明这一步了吗?但是,这样做在安排函数顺序时要投入很多精力,在复杂的调用中,一定要考虑好谁先谁后,否则将发生错误而且,,C++,程序都是从,main,函数开始执行的,将,main,函数放在程序的开头可提高程序的可读性因此有经验的编程人员一般都把,main,函数写在最前面希望读者养成对所有用到的函数作声明的习惯25,(,1,)函数声明的位置比较灵活,可以出现在该函数调用前的任何位置,且对函数的原型声明次数没有限制2,)其实可以简单地照写已定义的函数的首部,再加一个分号,就是对函数的声明3,)如果一个函数不是被多个函数调用,一般是在调用该函数的前一行代码处声明如,【,例,4-3】,中的第,8,行对,add,函数的声明就是较好的例子。

    下面再对函数的声明作一些说明26,,C++,支持所谓的分别编译,这样程序可以由多个文件组成由前面的例题可知,对于只有一个源文件的简单程序,可以不需要函数声明,只要保证函数在调用之前定义即可但用户在以后的学习和工作中,将要面对的常常是一些复杂的大型程序,往往有很多源文件,而且一个源文件中定义的函数,往往会被其他源文件使用,因此就需要在其他源文件中声明该函数在有多个源文件的程序中,往往把函数的声明放在头文件中,而把函数的定义放在源文件中当别的源文件要声明函数时,只需用,#include,命令包含头文件4.2.4,在头文件中声明函数,27,(,1,)写一个头文件,function.h,,用来声明,add,函数,其内容如下int add(int x,int y); //,声明一个求两个整数之和的函数,add,,(,2,)写一个源文件,function.cpp,,用来定义,add,函数,其内容如下int add(int x,int y) //,定义,add,函数,,{ return (x+y); } //,返回两个数之和,,(,3,)在其他源文件中调用,add,函数时,先要包含头文件,function.h,,如在主函数中调用:,,#include "function.h" //,包含头文件,function.h,,也即声明函数,add,,……,,void main() //,主函数,无返回值,,{,,cout <

    下面用求两个整数之和的,add,函数来举例说明,28,在创建函数时,定义一个函数的目的是使用它要想使用函数,就要调用它调用函数时,开发者应当按照形参列表为函数传入所需的实参4.3.1,函数调用的一般形式,,函数调用的一般形式如下函数名(,<,实参列表,>,),,例如,下面的语句:,,c=add(a,b); //,调用,add(a,b),,并将返回值赋给整型变量,c,,cout <

    30,程序运行结果如图,4-3,所示图,4-3,在,main,函数中调用无参函数的结果,,,在,【,例,4-4】,中,当程序执行到第,6,行时,程序控制流就会转到被调用的,print_message,函数(即跳转至,9,行)当执行到,print_message,函数结尾大括号(第,14,行)时,又将控制权交还给调用者,即此程序中的,main,函数31,在,C++,中,形参就是函数定义时的参数,实参是函数调用时传入的参数在,4.1.2,中也曾提到,在定义函数时系统并不为形参分配存储空间,只有被调用时向它传递了实参,才为形参分配存储空间在调用结束后,形参所占的存储单元又会被释放实参才是程序运行时实际存在的参数例如,在,【,例,4-3】,中,参数,x,和,y,就是形参而通过调用,add,函数传入的变量,a,和,b,是实参4.3.2,函数的形参和实参,32,(,1,)形参和实参是相对的概念,有时会相互转化例如:,,void count_add(int a,int b) //count,函数,计算两个整数的和,,{,,int c=add(a,b); //,调用,add,函数,求两个整数,a,b,的和,,cout <<"c=" <

    2,)实参的类型与形参的类型应相同或赋值兼容,且实参的个数与形参的个数必须一致,即类型相同或赋值兼容,个数相等函数形参和实参的说明如下33,在调用有参函数时,实参变量对形参变量的数据传递是一一对应的“值传递”,此时编译系统将临时给形参分配存储单元但应当注意的是,实参单元与形参单元是不同的单元例如,当,【,例,4-3】,中的程序执行到第,9,行“,c=add(a,b);,”时,若,a=4,,,b=6,,则有图,4-4,所示的将实参,a,和,b,的值,4,和,6,传递给对应的形参,x,和,y,4.3.3,值传递,34,调用结束后,形参单元将被释放,实参单元仍保留并维持原值由于实参与形参之间的“值传递”方式,使得实参与形参之间的传递是单向的,即只由实参传给形参,而不能由形参回传给实参例如,若在执行,add,函数的过程中,形参,x,和,y,的值变为,8,和,9,,调用结束后,实参,a,和,b,仍为,4,和,6,,如图,4-5,所示图,4-4,值传递,1,图,4-5,值传递,2,,35,#include ,,using namespace std;,,void swap(int x,int y); //,函数声明,,int main(),,{,,int a=5,b=8;,,swap(a,b); //,调用,swap,函数,,cout <<"a=" <

    例如:,,void output_message(int num1=10,double num2=1.2345),,//,指定参数,num1,和,num2,的默认实参,,{,,cout <

    此时,至于在函数定义中给不给出默认值,要根据具体的编译系统而言,因为不同的编译系统有不同的处理规则2,)一个函数不能既作为重载函数,又作为有默认参数的函数,否则容易出现二义性,使系统无法执行因为当调用函数时如果少写一个参数,系统无法判定是利用重载函数还是利用有默认参数的函数42,#include ,,using namespace std;,,int max(int ,int ,int =0); //,函数声明,并指定了一个默认实参,,int main(),,{,,int a,b,c;,,cout <<"please input three integers:" <>a >>b >>c;,,cout <<"max(a,b)=" <>a >>b;,,cout <<"the greatest common divisor is:"<

    图,4-9,函数的嵌套调用实例,1,,,49,【,例,4-8】,用弦截法求方程,f,(,x,),=,x,3,-8,x,2,+12,x,+16=0,的根结合数学知识,其解决方法如下1,)确定求值区间输入两个不同的点,x,1,,,x,2,,直到,f,(,x,1,)和,f,(,x,2,)异号为止因为一旦,f,(,x,1,)和,f,(,x,2,)异号,则(,x,1,,,x,2,)区间内必有一根但应注意,x,1,,,x,2,的值不要相差太大,以保(,x,1,,,x,2,)区间内只有一根2,)连接(,x,1,,,f,(,x,1,))和(,x,2,,,f,(,x,2,)),,两点,此线(即弦)交,x,轴于,x,,如图,4-10,所示其中,,x,的坐标可由下式求出x=,,,再从,x,求出,f,(,x,)图,4-10,弦截法示意图,,,50,(,3,)若,f,(,x,)与,f,(,x,1,)同号,则根必在(,x,1,,,x,2,)区间内,此时将,x,作为新的,x,1,如果,f,(,x,)与,f,(,x,2,)同号,则表示根在(,x,1,,,x,2,)区间内,将,x,作为新的,x,2,重复步骤(,2,)和步骤(,3,),直到,,为止。

    此时认为,f,(,x,),,其中的,,为一个很小的正数,例如,10,-6,),,这就是弦截法的算法,在程序中分别用以下几个函数来实现各部分的功能:,,(,1,)用,f,(,x,)代表,x,的函数:,x,3,-8,x,2,+12,x,+16,2,)用函数,xpoint,(,x,1,,,x,2,)来求(,x,1,,,f,(,x,1,))和(,x,2,,,f,(,x,2,))两点的连线与,x,轴的交点,x,的坐标3,)用函数,root,(,x,1,,,x,2,)来求(,x,1,,,x,2,)区间的实根显然,执行,root,函数的过程中要用到函数,xpoint,,而执行,xpoint,函数的过程中要用到,f,函数51,根据以上算法,可编写出如下程序include ,,#include ,,#include ,,using namespace std;,,double f(double);,,double xpoint(double,double);,,double root(double,double);,,void main(),,{,,double x1,x2,x,f1,f2;,,do //,输入两个不同点,x1,、,x2,,直到,f,(,x1,)和,f,(,x2,)异号为止,,{,,cout <<"please input x1,x2:" <>x1 >>x2;,,f1=f(x1);,,f2=f(x2);,,},,while( (f1*f2) >0);,,x=root(x1,x2);,,cout <0),,{,,y1=y;,,x1=x;,,},,else,,x2=x;,,},,while(fabs(y)>=1e-6);,,return x;,,},,53,程序运行结果如图,4-11,所示。

    图,4-11,用弦截法求方程根的结果,,,注意:,main,函数可以调用其他函数,各函数间也可以互相调用,但不能调用,main,函数54,2,)函数的递归调用,,直接或间接调用自己的函数称为递归函数直接调用自身的函数称为直接递归调用,如在执行函数,fun1,的过程中,又要调用,fun1,函数,如图,4-12,所示间接调用自身的函数称为间接递归调用,如在执行函数,fun1,的过程中要调用函数,,,,,,,,,图,4-12,函数的直接递归调用,,图,4-13,函数的间接递归调用,,,fun1(),,{,,,……,,,fun1();,,,……,,},,fun1() fun2(),,{ {,,…… ……,,fun2(); fun1(),,…… ……,,} },,55,下面看一个简单的递归调用例子,——,用递归方法求,n,!的值我们知道,5,!,=5*4,!,也就是说,想要求,5,!的值必须知道,4,!的值,而,4,!,=4*3,!,同理,要求,4,!的值又必须知道,3,!的值,而,3,!,=3*2,!,,……,以此类推,只要知道了,1,!(值为,1,),反推回去就求得了,5,!。

    这就是递归思想用流程图表示该过程如图,4-14,所示图,4-14,递归调用求,5,!的值示意图,,,56,由图,4-14,可知,求解过程分为两个阶段:第,1,阶段(即①,~,④)是回推,即将,5,!表示为,4,!的函数,而,4,!仍然不知道,还要回推到,3,!,……,直到,1,!此时,1,!已知,不必再向前推了然后开始第,2,阶段(⑤,~,⑧),采用递推方法,从已知的,1,!推算出,2,!(值为,2,),从,2,!再推算出,3,!(值为,6,),……,一直推算出,5,!(值为,120,)为止显然,一个递归问题可以分为回推和递推两个阶段注意:,如果要求递归过程不是无限制地进行下去,那么就必须具有一个结束递归过程的条件通常使用,if,语句来控制有了上面的基础,就不难写出求,n,!的程序代码了例,4-8】,用递归方法求,n,!其中:,,,,非法,,n<0,,n!= 1 n=0,或,n=1,,n*(n-1)! n>1,,,,,57,编写程序如下include ,,using namespace std;,,long factorial(int); //,函数声明,形参为一个整型变量,,int main(),,{,,int n; //n,为需要求阶乘的整数,,long y;,,cout <<"please input an integer:" <>n;,,y=factorial(n); //,调用递归函数,factorial,,将返回值赋给,y,,cout <1,时,进行递归调用,,return fac; //,将,fac,的值作为函数返回值,,},,58,程序运行结果如图,4-15,所示。

    图,4-15 【,例,4-8】,程序运行结果,,,59,另一个递归调用的例子是求两个数的最大公约数例,4-9】,用递归函数求两个数的最大公约数结合,【,例,4-6】,中介绍的欧几里得算法,求两个数的最大公约数的过程可以递归地描述为如下的式子b a%b=0,,gcd(a,b)=,,gcd(b,a%b) a%b,≠,0,,,,60,编写程序如下include ,,using namespace std;,,int gcd(int a,int b); //,递归函数,gcd,原型声明,,void main(),,{,,int a,b,g;,,cout <<"please enter two integers:" <>a >>b;,,g=gcd(a,b); //,调用递归函数并将返回值赋给变量,g,,cout <<"the greatest common divisor is:" <

    图,4-16,用递归函数求两个数的最大公约数的结果,,由以上两个例题可以看出,当使用递归方法解决问题时,需要满足如下两个条件1,)应能减小问题的规模2,)应能确定终结条件还需说明的是,在实现递归时,需要大量的额外开销,执行效率较低但随着计算机性能的快速提升,大家首先考虑的往往不再是效率问题,而是程序的可读性问题用递归方法来处理问题,符合人们的思路,程序容易理解因此,建议读者优先考虑用递归方法编程62,通常的函数调用是将程序执行顺序转到被调用函数去执行,待被调用函数执行完毕后,再返回调用函数继续执行,见图,4-7,这种转移操作会消耗一定的时间,尤其是当某些函数体代码不是很大,而又需要频繁调用时,就大大降低了程序的执行效率为了解决这个问题,,C++,引入了内联函数C++,规定,如果在函数的定义或声明前加上关键字,inline,,就称为内联函数内联函数的执行机理,是在编译时将所调用函数的函数体代码直接嵌入主调函数中例如,对于,add,函数,如果其定义为:,,int add(int x,int y),,{,,return(x+y);,,},,在,main,函数中声明为:,,inline int add(int x,int y); //,在函数声明前加上关键字,inline,,,add,函数成为内联函数,,函数调用为:,,int c=add(3,5);,,则程序编译后,实际的代码如下。

    int c=3+5;,,,,即在编译后用,add,函数体的代码(“,x+y,”的返回值“,return(x+y);,”)代替“,add(3,5),”,同时用实参代替形参这样,代码“,int c=add(3,5);,”就被置换成“,int c=3 + 5;,”注意:,可以在声明函数和定义函数的同时写,inline,,也可以只在其中一处声明,inline,,效果相同,都能按,内联函数处理4.3.8,内联函数,63,#include ,,using namespace std;,,inline int min(int ,int ,int); //,声明,min,为内联函数,注意左端有关键字,inline,,int main(),,{,,int a,b,c,m;,,cout <<"please input three integers:" <

    点击阅读更多内容
    卖家[上传人]:无极剑圣
    资质:实名认证