《C++程序设计》 主讲教师:张玉宏
函数的概念与作用 函数的概念 把很多相关的程序指令放在一起,并加以命名,形成一个执行单元,C++中称呼这种执行单元为function(函数) 函数的目的 1.减少重复撰写类似功能的程序——将常用的算法包装成函数,以重复利用,简化程序的撰写。 2.易于调试和维护——把程序的各个主要部分分成几个模块,可以分别进行开发和测试。 升达大学资讯系
函数的语法 函数间可以相互调用。发起调用动作的函数称为调用函数(calling function),而被调用的函数称为被调函数(called function)从程序开发的观点来看,函数的语法可以分为下列3个部分: 1.函数的声明 建立一个函数的原型(prototype),以告知编译器本程序即将使用的函数名称,以及进出这个函数的数据类型和数量. 2.函数的定义 将函数的内容具体写成程序 3.函数的调用 使用已经定义过的函数 升达大学资讯系
范例程序: // TempConv2.cpp //目的:调用一个简单的函数实现摄氏和华氏温度的转换 #include <iomanip> using std::cin; using std::cout; using std::endl; using std::setw; //-----------函数C2F()的声明--------------------- float C2F(float); //-----------主程序-------------------------------- int main() { float CTemp; cout << " 摄氏 华氏 " << endl ; cout << "-----------------" << endl ; for ( int i = 1 ; i <= 10 ; i++ ) 升达大学资讯系
(接上) { CTemp = 10.0*i; cout << setw(5) << CTemp << " " << setw(5) << C2F(CTemp) << endl ; } cout << "-----------------" << endl ; return 0; //------------函数C2F 的定义---------------------------- float C2F(float C) float F; F = C*9.0/5.0 + 32.0; return F; 升达大学资讯系
函数的声明 下列是几种常见的函数原型 int wanring (); int maxint(int,int,int); int area(int width,int length); Area_2(int,int); void swap(double x,double y); 需要注意的是:函数原型的声明是一个标准的C++语句,所以其后一定用“ ; ”(也就是一个空语句),而真正的函数的定义则不需要。 升达大学资讯系
函数的声明(续) 函数在调用之前一定要先声明,这点和变量在使用前需要声明的情形非常类似。函数的声明也称函数原型,它声明了函数在调用时应该给与的数据类型,以及执行后所返回的数据类型。函数原型的通式为: 返回类型 函数名称(参数数据类型列表) 注意:函数原型的通常需要放置在main()函数之前,可供主程序和其它程序调用,如果放置在main()函数内,则只能从main()内调用。 升达大学资讯系
函数的定义 函数定义的语法和函数的声明一样,只是把原先函数声明后的“;”换成具体的用{ }括起来的复合语句。函数的复合语句与独立成行的单独的语句地位是相等的,因此可以由此理解某些版本的C++语句保留着函数的}后加“;”的习惯,现在的版本可以省略。同时在函数的定义中把要加入完整的函数名称。 定义格式:〔返回类型〕<函数名>(<形参表>) { <语句序列> return 返回值; } 需要注意的:return 返回的值即为函数名(参数列表)所代表的值,二者是一致的。 升达大学资讯系
函数的定义(续) 说明: (1)类型:是函数返回值的数据类型,可为任一基本数据类型或导出数据类型。 如在引例中函数类型为float,即函数的返回值为实型。 注意:函数返回值为整型时,可省略。当函数没有返回值时,可定义其类型为void。 (2)函数名:用户给函数起的名字,其命名规则与标识符相同。 如在引例中函数名为C2F。 (3)形参表:<类型> <形参1>,<类型> <形参2>… 类型为形参的数据类型。在上例中形参C的类型为float。形参也称为形式参数,形参可缺省,构成无参函数,无参函数的定义格式为: 〔类型〕<函数名>(void) {<语句序列> } 升达大学资讯系
函数的调用 调用函数时,必须给与合符参数列表规定的数据,即实参类型必须与形参类型一致,从返回参数的有无,函数的调用可以分为下列两类: (1)没有返回数据时,只用函数名称及函数调用运算符()以及调用运算符内的参数,就可自成一条语句,如: swap(a,b); (2)由函数类型时,则可以将函数的调用放在任何适合该数据类型的语句中,例如赋值语句或算术语句,如: N=maxint(x,y,z); m=2.5*area(x,y); cout << “转换后的温度为"<< C2F(CTemp) << endl 升达大学资讯系
无参函数的实例 # include <iostream.h> void print(void) { cout<<" This is no parameter example \n"; } void main(void) print(); 程序中定义了一个无参函数print,然后在主函数中调用无参函数,程序执行的输出结果是:This is no parameter example 函数体{}:由一系列语句组成。 当函数体为空时,称这种函数为空函数。空函数的定义格式为: 〔类型〕<函数名>(void){ } 函数定义后如何使用,即函数的调用。 升达大学资讯系
不使用函数原型的语法 如果我们把函数的定义置于函数原型的的位置(即在main()函数之前),则此函数的定义兼有函数声明和定义的功能,不再需要在使用函数的原型了。 # include <iostream> # include <cmath> float f(float x) { float y; y=5*x*x+3*x+1; return y;} void main(void) { float z1,z2,x=1; z1=sin(x); z2=f(x); cout<<"sin(x)= "<<z1<<endl; cout<<"f(x+1)= "<<z2<<endl; } 升达大学资讯系
函数调用应该注意的问题 1.函数的调用关系 调用其它函数的函数称为主调函数(如main为主调函数),被其它函数调用的函数称为被调函数(f(x)为被调函数)。 函数间可以互相调用,也可以嵌套调用,但不能调用主函数。 函数不能嵌套定义,即一个函数不能定义在另一个函数内。 2.库函数(标准函数):数学函数、图形处理函数、标准输入/输出函数等 C++将一些数学函数、图形处理函数、标准输入/输出函数作为库函数供用户使用。库函数可分成若干类,每类库函数都集中在一个头文件中加以说明,如数学库函数集中在头文件cmath.h中。 当用户使用任一库函数时,在程序中必须包含相应的头文件,如:# include <cmath>。 升达大学资讯系
例题分析 例题1.用自定义函数求三个整数的最大值。 # include <iostream.h> int max(int x,int y) { int z; if (x>y) z=x; else z=y; return z;} void main(void) { int a,b,c,m; cout<<"Input a,b,c:"; cin>>a>>b>>c; m=max(a,max(b,c));//函数的嵌套调用 cout<<"max(a,b,c)= "<<m<<endl;} 注:教材p34页例3.4有另一种写法。 升达大学资讯系
函数参数的传送方式 在C++中,实参和形参间数据传送的方式有三种:传值调用(pass by vaule)、引用传送(call by reference)和传地址。这里先介绍传值调用方式和引用传送,后一种方式在后文中介绍。 1.传值调用 实参传送给形参的数据传送过程: (1)调用函数时,先为形参分配存储单元,后将实参值传递给形参; (2)函数执行过程中均为形参参与运算; (3)函数调用后,形参所对应的存储单元被释放,实参保持原来的值不变。 其示意图如下所示: 升达大学资讯系
当函数被调用时,参数首先被复制到内存的一个特殊区域,称之为堆栈(stack)如图中步骤1,且程序的执行次序和控制劝都交给被调用的函数,如C2F()。被调用的函数先将参数(float c)初始化为堆栈的值(如步骤2),才开始进行运算。在运算完毕后才将结果复制一份到堆栈内,如步骤3,接着将控制权交给被调用函数。调用函数这时再把堆栈的结果复制一份回来(如步骤4),并恢复原先的执行次序。由于这种传值的调用方式,参数的值被层层复制,因此被调用的函数内对参数的任何更动都不会影响调用函数内的参数。 图 3.1 函数参数的传递方式 升达大学资讯系
传值调用流程的说明 当函数被调用时,参数首先被复制到内存的一个特殊区域,称之为堆栈(stack)如图中步骤1,且程序的执行次序和控制劝都交给被调用的函数,如C2F()。被调用的函数先将参数(float c)初始化为堆栈的值(如步骤2),才开始进行运算。在运算完毕后才将结果复制一份到堆栈内,如步骤3,接着将控制权交给被调用函数。调用函数这时再把堆栈的结果复制一份回来(如步骤4),并恢复原先的执行次序。由于这种传值的调用方式,参数的值被层层复制,因此被调用的函数内对参数的任何更动都不会影响调用函数内的参数。 升达大学资讯系
函数的return语句 语句格式:return <表达式>; 或:return (<表达式>); 作用:返回表达式的值,结束函数执行,并将控制转移到调用函数的地方继续执行。 例如:比较两个整数大小的max函数中可简写成如下形式: int max(int x,int y) { return (x>y?x:y); } 升达大学资讯系
函数的return语句(续) 说明: (1)不需函数返回值时可不写return语句; (2)函数中可有多个return语句; 例如:max( int x,int y) { if (x>y) return x; else return y;} (3)默认的函数返回值为整型(此时,可不指定返回值的类型) 例如,上面的max函数可定义为: max(int x,int y) {…} (4)当函数类型与return中表达式值类型不同时,表达式的值自动转换成函数返回值的类型。 升达大学资讯系
以引用的方式调用 利用传值调用我们可以写出很多有用的程序,但是我们也注意到了利用return语句只能回传一个值的限制。此外,有些时候我们也想改变参数的数值。要突破这些限制,可以使用引用(reference)的技巧。 引用是同一个变量的别名(alias)。例如: int N; int& M=N; //A 定义了变量N,同时定义了N的引用M。这里的&称为引用运算符,表示M和N所代表的变量位于同一个地址上。对于M所作的变化都等同于直接作用于N上,反之亦然。A语句也可以写成:int &M=N; 升达大学资讯系
以引用的方式调用(续) 也就是说,下面两个函数的标头部分(header line)是一样的: int fnc(int& N;float& x) int fnc(int &N;float &x) 特别注意的是:引用运算符和取地址运算符都使用相同的符号“&”,但是它们的意义却不是一样的。 int N; int M= &N; //所代表的意义是将N的地址(32bit)存入整数M中。 升达大学资讯系
一个引用的例程 // Alias.cpp #include <iomanip> using std::cin; using std::cout; using std::endl; int main(){ int N = 10; int& M = N; cout<< "M 的值原来是:"<< M << endl; cout<< "N 的值原来是:"<< N << endl; N = 5; cout<< "执行 “N = 5;” 之后" << endl; cout<< "M 的值目前是:" << M <<endl; M = 2; cout<< "执行 “M = 2;” 之后" << endl; cout<< "N 的值目前是:" << N <<endl; } 运行结果 升达大学资讯系
关于“引用”的讨论 一般而言,为了避免在不无意中变更调用函数内的变量值,并不鼓励用引用调用的方式来传递参数,除非以下三种情况: 1.参数本身必须改变 2.要回传两个以上的值 3.有大量参数需要传递,如果不用引用,则将耗费大量时间在参数的复制上。例如,参数为大型向量或矩阵的情况 升达大学资讯系
一个利用引用传值的例程 升达大学资讯系 SWAP2(A, B); #include <iostream> cout << "执行过 SWAP2()\n"; cout << " A 的值是:" << A<< "\n B 的值是:" << B << endl; } //------------------------- void Swap (int& x, int& y) { int Temp; Temp = x; x = y; y = Temp;} void SWAP2 (int x, int y) { int Temp; #include <iostream> using std::cin; // Swap.cpp using std::cout; using std::endl; void Swap (int&, int&); void SWAP2 (int, int); int main() { int A=5, B=10; Swap(A, B); cout << "执行过 Swap()\n"; cout << " A 的值是:" << A<< "\n B 的值是:"<< B << endl; cout << endl; 升达大学资讯系
inline(内联)函数 在我们以往的函数调用时,程序的主控权就会交给被调函数,执行完毕后才把主控权交还给调用函数。在这个过程中,必须在堆栈里记录每个阶段的执行程序代码的位置以待稍后接着执行;这个记录执行指令程序代码的位置的变量称为程序指针(instruction pointer,IP)或程序计数器(program counter,简称PC)。主控权的交接,以及参数的复制都需要时间。对于这样额外的负担,C++提供了一个适用于函数的特殊的语法,称为内联函数(inline function) 升达大学资讯系
inline函数(续) inline 函数的实际运作和一般的函数很不一样。在编译时,其程序代码在每个调用的地方直接展开加入,因此不需要浪费时间进行主控权的转换,直接变成调用程序的一段复合语句。 inline 函数的定义非常简单,只要直接在函数的定义前加上关键词“inline”就可以。 升达大学资讯系
inline 函数例程 运行结果 升达大学资讯系 // Inline.cpp #include <iomanip> using std::cin; using std::cout; using std::endl; using std::setw; inline float C2F(float C) // Inline 函数的声明和定义 { return C*9.0/5.0 + 32.0; } int main() {float CTemp; // 主程序 cout << " 摄氏 华氏 " << endl ; cout << "-----------------" << endl ; for ( int i = 1 ; i <= 10 ; i++ ) {CTemp = 10.0*i; cout << setw(5) << CTemp << " " << setw(5) << C2F(CTemp) << endl ;} cout << "-----------------" << endl ;} 运行结果 升达大学资讯系
变量的适用范围和生存期 一、局部变量 函数的主体可看为一个复合语句,因此包括函数表头部分的参数列表内所定义的传值,都只在该函数内才适用。在传值的情况下,每一个函数都占用自己独立的内存空间,函数内变量的工作空间不会与其它函数的工作空间重叠,这些变量称为局部变量(local variables)。 局部变量的声明分为auto,static和register三个存储种类: 1.auto是automatic的缩写,是所有函数内变量的默认存储方式。Auto变量只在函数被调用时才存在,该函数结束后就消失,不会保留最后的值 。 升达大学资讯系
变量的适用范围和生存期(续) 2.被声明为“static(静态)”的局部变量并不随函数调用结束而消失。除非整个程序结束,否则static变量的值具有连续性。也就是说,每一次函数被调用时, static变量的值是上一次调用结束时的值。 3.“register”原本是CPU内的一种内存,称之为寄存器。如果某个变量使用频繁的话,可以将其声明为register变量保留在CPU内,以提升函数的执行速度。但是寄存器的数量很有限,因此编译器可能会忽略这个声明。一般来说,一个函数内的变量数量很多,而且新一代的CPU内含有高速缓存(cache memory)以提升执行效率,因此此项功能的实际效益并不大。 需要说明的是: register不放在主存储器内,因此没有标准的地址 ,也就是说取址运算符号“&”不可以用在它上面 升达大学资讯系
一个关于局部变量的例程 // Local.cpp #include <iostream.h> using namespace std; void TestLocal(); int main() { for (int i=1; i<=3; i++) { cout << "第(" << i << ")次:" << endl; TestLocal(); cout << endl;} return 0; } 升达大学资讯系
一个关于局部变量的例程(续) 运行结果 void TestLocal() { int A = 1; static int S = 1; register int R = 1; cout << " A 的值是 " << A << endl; cout << " S 的值是 " << S << endl; cout << " R 的值是 " << R << endl; for (register int i=0; i<1000; i++) R += 2; cout << " 计算后 R 的值是 " << R << endl; A++; S++; R++; return; } 运行结果 升达大学资讯系
运行结果 升达大学资讯系
二、全局变量 如果把变量的声明放在所有的函数之前,则这些变量值变量称为全局变量(global variables)可以让其后的所有函数共享。全局变量的例程: 升达大学资讯系
二、全局变量(续) 运行结果 升达大学资讯系
全局变量(续) 讨论: 1.可以看出main()函数中的全局变量GLOBAL和函数TestFnc()的变量GLOBAL是共享的。函数TestFnc()的变化也反映在main()中。 2.函数TestFnc()中声明一个和main()函数一样名称的整数,并在TestFnc()中改变,但这个改变并没有反映到main()中,这个结果显示即使局部变量和全局变量有相同的名字,实际上它们是两个完全独立的变量。也就是局部变量在函数中覆盖全局变量,且不影响全局变量的值。 3.如果全局变量想在函数中不被局部变量覆盖,则可以通过在其变量前使用范围确认运算符号“::”,如程序中的“::GLOBAL_2” 升达大学资讯系
全局变量 提示: 1.全局变量虽然好用,但它破坏了函数原先所具有的独立性和严格的数据输入输出控制。特别是当程序很庞大的时候,如果对全局变量的使用不加以控制,将使得程序调试变得非常困难。因此,在撰写大型程序时,全局变量的数量常常严格控制在10个以下,而且往往全部大写字母,或故意使用很长的名称来突出显示。 2.如果没有对全局变量在声明时进行赋予初始值,则全局变量会在编译时被自动初始化为0,这个特性和static局部变量是一样的。但是,程序设计人员应该将变量的初始化视为自己的工作,而不是交给编译器来完成。 升达大学资讯系
全局变量的储存种类 全局变量在程序执行期间一直都存在,所以没有生存期的问题,对于一个大型的程序,通常把它分成好几个文件分别来撰写。函数可以放在同一个文件里,也可以在很多文件分别有不同的函数。每个存有C++程序的文件,不管它是主程序还是函数,都称为一个编译单元(compile unit)。 为了规范文件间变量的适用范围,有extern和static两种存储类型的声明: extern 全局变量——声明这个变量已经在其它文件内定义过了。对于变量而言,它是一种声明(declaration)而不是定义(definition),因为extern 全局变量的声明并不会在内存中使用新的记忆空间,而是使用它所指定的同一个变量。 升达大学资讯系
全局变量的储存种类(续) static 全局变量——声明这个全局变量只能在本文件中适用。这个语法在新的ANSI/ISO规范中虽然暂时保留,但即将淘汰,改用名称空间(namespace)的方式处理。后续课程将详细讲述名称空间 提示: 1.由于static 和extern是完全相反的声明,不同同时使用。 2.static 全局变量和函数内的static 局部变量都使用关键词static ,但意义决然不同: static 全局变量——声明全局变量只能在本文件中适用 static 局部变量——不随函数的调用结束而消失。除非整个程序结束,否则static 局部变量的值都具有连续性。 升达大学资讯系
范例程序(1-1) 升达大学资讯系
范例程序(1-2) 由于ExternB内的Func2()内有extern int An;的 运行结果 升达大学资讯系
运行结果 升达大学资讯系
本讲完毕 升达大学资讯系