41
第5第 第第第第第第第 5.1 函函函函函函函 5.2 函函函函 5.3 函函函函函函函 5.4 函函函函函函函 5.5 第第第 第第 第第第第第第第第第第第第第第第第第第第第第第第第第第第第第第第第第第第第第第第第第第第第第第第第第第第第第第第第第

第 5 章 函数与数据控制

  • Upload
    ianna

  • View
    131

  • Download
    3

Embed Size (px)

DESCRIPTION

第 5 章 函数与数据控制. 学习目的: ① 掌握函数的定义与使用方法; ② 深入理解参数转递及变量作用域; ③ 了解函数的顺序控制机制; ④ 掌握函数指针概念及数组做函数参数。. 5.1 函数定义与说明 5.2 数据控制 5.3 函数的顺序控制 5.4 相关的其他语法 5.5 常用系统函数. 5.1 函数定义与说明. 函数在程序设计中用来描述相对独立的功能,其中包含实现所述功能的一系列具体操作步骤。更复杂的功能通过调用一系列相对简单的函数来完成,这些简单函数的功能更加单一,结构更加简单易读。 - PowerPoint PPT Presentation

Citation preview

Page 1: 第 5 章   函数与数据控制

第 5 章 函数与数据控制

5.1函数定义与说明5.2数据控制5.3函数的顺序控制5.4相关的其他语法5.5 常用系统函数

学习目的:① 掌握函数的定义与使用方法;② 深入理解参数转递及变量作用域;③ 了解函数的顺序控制机制;④ 掌握函数指针概念及数组做函数参数。

Page 2: 第 5 章   函数与数据控制

5.1 函数定义与说明

5.1.1函数定义5.1.2 函数说明5.1.3 函数的简单调用5.1.4 函数的重载

函数在程序设计中用来描述相对独立的功能,其中包含实现所述功能的一系列具体操作步骤。更复杂的功能通过调用一系列相对简单的函数来完成,这些简单函数的功能更加单一,结构更加简单易读。

使用函数的意义还在于,通过将函数头作为函数内部实现与外部调用环境之间的接口,实现了数据与程序代码的封装,隐藏了程序实现的细节问题,具有十分明显的模块化、参数化和结构化特征。

函数的定义及使用包括三个方面的内容:函数定义、说明及调用。

Page 3: 第 5 章   函数与数据控制

5.1.1 函数定义函数由函数头、函数体两个部分组成,规则如下:

参数表 类型 参数名空格

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

函数 函数头 函数体

函数体{ }语句

下面为函数定义示例,该函数的功能是返回两个浮点数的积:

double GetProduct(double x, double y){ double temp=x*y; return temp;}

Page 4: 第 5 章   函数与数据控制

5.1.2 函数说明

1 函数原型

函数在使用前必须进行定义,如果函数的调用不是位于同一个文件中函数定义之后,则必须在调用函数之前对函数进行引用说明。通常的做法是使用 #include 指令将含有函数引用说明的文件嵌入进来。

C++ 中函数原型给出函数引用说明,任何函数都有自己的函数原型,它可能由编译器从一个函数定义中自动抽取,也可由程序员在程序中通过函数说明语句给出。函数原型的真正价值是使编译器能够按照所声明的形式进行类型检查,从而发现函数调用中可能存在的错误。形式上函数原型与函数头相似,但参数表中的参数名可与函数定义中对应位置的参数名不同,也可省略,但类型必须相同。例如:

void Swap(int* pX, int* pY);

与下述形式代表的是同一个函数原型void Swap(int*, int*);

Page 5: 第 5 章   函数与数据控制

5.1.2 函数说明2 函数参数默认值

在函数说明中可以为函数的参数指定默认值。调用函数时如果没显式地指定参数的值,编译器就会使用相应的默认值,例如:

int Increase(int i=0); // 调用时可写为 Increase() ,意指 Increase(0)

对参数指定默认值必须从参数表最右边的参数开始连续指定,不允许跨越一个参数而指定其左侧参数的默认值,例如下述函数原型非法:

void MoveToPosition(int x=0, int y, int z=0);void MoveToPosition(int x=0, int y=0, int z);

而下述函数原型合法:void MoveToPosition(int x, int y=0, int z=0);

函数调用时,编译器将按从左至右的顺序将实参与形参结合,当实参数目少于形参数目时,将按顺序用默认值补足所缺少的实参,例如对于上面的函数说明,下面两种方式等价:

MoveToPosition(3, 8);MoveToPostion(3, 8, 0);

Page 6: 第 5 章   函数与数据控制

5.1.2 函数说明3 函数的返回值

[ 例 5.1] 编写函数比较两个浮点数的大小,要求:当第一个浮点数比第 二个大时返回 1 ;相等时返回 0 ;否则返回 -1 。

函数内操作完成后,使用返回语句将某值传递给调用函数,格式为:return <exp>;

程序运行至该语句时,计算 <exp> 的值并传递给调用函数,然后将程序执行的控制权移交给调用函数,执行紧跟当前函数调用语句后的语句。

int FloatCmp(float f1, float f2){ float f=f1-f2; if(fabs(f)<0.0001) return 0; else if(f<-0.0001) return -1; else return 1;}

Page 7: 第 5 章   函数与数据控制

5.1.3 函数的简单调用函数的使用方式很灵活,可以用作某个运算的操作数,也可以作为单独的一条语句出现在程序中,实际上作为一种表达式,函数调用几乎可以出现在程序中任何表达式能够出现的地方。在具体调用函数时,应该根据函数原型中对参数类型的要求用相应的实际数据替换函数的参数,例如下列程序段:

#include "math.h"#include "iostream.h“

int GetMinimum(int m, int n){ return m>n ? n : m; }

void main(){ cout<<sqrt(36+GetMinimum(64, 81))<<endl; }

Page 8: 第 5 章   函数与数据控制

5.1.4 函数的重载具有相同名字、参数类型或参数个数有所不同的函数称为重载函数。

C++ 中规定,重载函数的形式参数在类型或个数上必须有所区别,因此在定义重载函数时应注意这些函数的原型是不同的。例如:

// 参数类型不同的重载函数int GetSum(int x, int y){…}float GetSum(float x, float y){…} // 与上面函数同名,参数类型不同

// 参数个数不同的重载函数float GetSum(int x, int y, float z){…} // 上面函数同名,参数个数不同

// 错误的重载函数int GetSum(float x, float y){…} // 非法,与第一个函数比仅返回值类型不同

Page 9: 第 5 章   函数与数据控制

5.1.4 函数的重载[ 例 5.3] 通过函数求两个数据中的最大值

#include "iostream.h"int GetMax(int x, int y){ return x>y ? x:y; } //第一个函数

double GetMax(double x, int y){ return x>y ? x:y; } //第二个函数

double GetMax(double x){ return x; } //第三个函数

void main() { cout<<"The maxmium is "<<GetMax(3.6, 3)<<endl; // 调用第二个函数 cout<<"The maxmium is "<<GetMax(4, 8)<<endl; // 调用第一个函数 cout<<"The maxmium is "<<GetMax(7)<<endl; // 调用第三个函数}

Page 10: 第 5 章   函数与数据控制

5.2 数据控制

5.2.1 参数传递机制5.2.2 数据对象的引用 *

5.2.3 作用域

高级语言的数据控制机制决定了标识符与变量、函数定义等的映射关系,决定了操作结果如何存储、以及如何查询这些结果用于后续操作。由于数据对象的使用方式具有多样性,因此数据控制的含义很广,涉及函数参数的传递机制以及函数间数据信息传递、变量的作用域等重要概念。

Page 11: 第 5 章   函数与数据控制

5.2.1 参数传递机制形式参数函数定义中的参数并没有被具体赋值,该参数代表将来使用函数时的任何一个数据对象,所以该参数不具有真实的确定值,因此称为形式参数,简称形参。

如同使用数学中的函数进行数学运算,程序中调用函数时要为函数的形参指定具体的数据对象或取值,它们称为实在参数,简称实参。

实参用于调用函数时给函数的形式参数赋初值。

实在参数

由于作为实参的数据对象具有名字、值、地址等属性,因此必须对实参与形参之间如何对应(结合)做出规定。通常有三类接合方式:

传值调用 传址调用 引用调用

Page 12: 第 5 章   函数与数据控制

5.2.1 参数传递机制1 传值调用形式参数被赋予实在参数的(右)值,而在函数体中形式参数等价于在函数内定义的局部变量。由于形参仅仅被赋予了实参的右值,因此被调函数形参的任何变化都不会引起定义于调用函数中的实参的改变。

#include "iostream.h"int func(int a){ cout<<"The formal parameter is "<<a<<endl; a=8; return a;}

void main(int argc, char* argv[]){ int* pa=new int; *pa=5; func(*pa); cout<<"The real parameter is "<<*pa<<endl; // *pa并未改变}

[ 例 5.4] 传值调用示例

Page 13: 第 5 章   函数与数据控制

5.2.1 参数传递机制2 传址调用形式参数和实在参数代表内存中同一个数据对象,形实结合时,形式参数被赋予实在参数的左值,因此形式参数的变化就是实在参数的变化。

#include "iostream.h"int func(int a[]){ a[2]=8; return 0;}

void main(int argc, char* argv[]){ int a[]={1,2,3,4,5}; func(a); cout<<a[2]<<endl;}

[ 例 5.6] 传址调用示例

Page 14: 第 5 章   函数与数据控制

5.2.1 参数传递机制3 引用调用引用调用将作为实参的表达式的左值传递给被调函数,实现时相当于以实在为初值,定义一引用类型变量(实参的别名),形参和实参为同一数据对象的标识。

使用引用调用和传址调用可以实现相同的功能,但引用调用的使用方式更方便、直观,其使用方法示例如下:

#include "iostream.h"void Swap(int& p, int& q){ int temp=p; p=q; q=temp; cout<<"p="<<p<<", q="<<q<<endl;}void main(){ int x=2, y=3; Swap(x, y); cout<<"x="<<x<<", y="<<y<<endl;}

[ 例 5.8] 引用调用示例

Page 15: 第 5 章   函数与数据控制

5.2.2 数据对象的引用 *

1 引用方式

函数可以被反复多次调用,并且可以嵌套调用,另外函数的不同部分可能出现相同的标识符,如何确定标识符与具体数据对象之间的联系、如何引用数据对象,是数据控制的重要内容,与函数有密切关系。

常用引用方式有三种: 直接传递、通过对象名引用、通过指针名引用。

直接传递 数据对象作为中间计算结果直接传给另一操作,例如 x=y+2*z 中, 2*z的操作结果被存于一隐藏的临时变量中,并直接传递给语句 x=y+2*z 参加运算。

通过对象名引用 数据对象名字出现在表达式中表示对相关数据对象的引用,例如x=y+2 , y 的表示相应的数据对象参与加法操作,即通过对象名字引用数据对象。

通过指针名引用 可通过指向匿名对象的指针引用该对象,例如: char* pC=new char[20]; *(pC+1)=‘f’;

动态定义数据对象时未指定对象名字, pC本身是一个数据对象,被赋值后指向所创建的匿名数据对象,因而可以通过 pC 引用所指向的匿名数据对象。

Page 16: 第 5 章   函数与数据控制

5.2.2 数据对象的引用 *

2 实现步骤

程序设计语言提供给编程者的数据对象引用方式为通过标识符引用,但是由于函数可以被多次调用,因此通过函数中的标识符得到与它们所代表的数据对象的值是一个十分复杂的过程,一般经过三个步骤。

标识符到声明 出现于不同程序段的同一名字所代表的数据对象可可能相同,也可能不同,确定标识符代表的数据对象首先要判断它与哪个声明语句相关联,因为任何数据对象都由声明创建。因为任何标识符都出现在相应的数据对象声明作用域内,因此该关联由作用域规则决定。

从声明到存储地址 标识符代表某数据对象声明所创建的数据对象,实际情形更复杂:函数可不只一次被调用,甚至嵌套。尽管创建的时机不同,内存中可能同时存在由同一说明语句创建的多个数据对象,哪个数据对象与函数当前活动相联系,需在调用时将标识符约束到某确定存储地址。相关信息被记录在当前活动记录中。

从存储地址到值 得到存储地址也就是定位了数据对象,下一步需要判断标识符的引用究竟是代表数据对象的左值还是数据对象的右值。

Page 17: 第 5 章   函数与数据控制

由于引用,需要为各种标识符建立与数据对象之间的对应关系,即绑定(映射、关联),程序运行过程中将通过这种关联引用数据对象。

对于程序或函数总有一组关联,用于在执行期间指明该阶段将遇到的所用标识符与数据对象的对应,这组关联称为函数的引用环境,由下面几个部分组成:

5.2.2 数据对象的引用 *3 引用环

局部引用环境 一组表示形参、函数内部局部变量及调用函数的关联,每当进入该函数时重新创建一次;从函数退出时删除。利用局部引用环境 可确定函数本次调用期间各局部变量和所调用函数的引用含义。

非局部引用环境 并未在函数被调用初始时刻生成,但是在进入函数后仍可以使用的标识符的关联的集合。

全局引用环境 在主程序开始运行前就创建的一组关联,如果这些关联在函数中可以被引用,则这些关联构成了函数的全局引用环境。

预定义引用环境 在语言定义时就已经定义的关联,它们同样可以在函数中引用,但无必要再次创建这类标识符的关联,如 cout 、 cin 等。

Page 18: 第 5 章   函数与数据控制

5.2.2 数据对象的引用 *4 可见性

程序中的每个标识符都会在适当时段与数据对象间建立起确定的关联,如果某个关联是一个函数引用环境的一部分,那么这个关联中的标识符作为与之相联的数据对象的标识可以在该函数中被引用,我们称之为在该函数中可见。如果某标识符的关联存在但不是某函数引用环境的一部分,则称该关联对这个函数是隐藏的。可见的标识符可以在函数中访问,而不可见的标识符不能访问。

Page 19: 第 5 章   函数与数据控制

5.2.3 作用域

静态作用域

无论标识符标识的是变量、常量、语句标号、函数,都有一定的有效范围,超出这个范围标识符就失去效用,该范围称为标识符的作用域。

静态作用域 标识符在程序段中的作用域(指程序的部分文本)。

静态作用域规则:确定一个标识符静态作用域的规定。

高级语言编译器通过静态作用域规则确定标识符说明的静态作用域,从而将程序文本中的名字的引用同它的声明联系起来。

静态作用域的概念和规则有助于提高程序运行效率。

动态作用域用环境的一部分,在程序运行期间将标识符与数据对象的引用联系在一起,即,名字的使用与其所代表的数据对象之间的绑定是动态建立的,随着程序的运行可以发生变化。多数语言采用静态作用域规则。

Page 20: 第 5 章   函数与数据控制

5.2.3 作用域1 C++ 的作用域规

作用域分为如下 4 类:

程序级 作用域范围包括组成程序的所有文件,可在所有文件中引用该类标识符。具有该作用域的标识符常为外部函数和外部变量,它们都在某文件中定义,在其他文件中进行引用说明,此时标识符作用域从引用说明语句或定义语句开始到文件尾。

文件级 该类标识符只能被所定义文件访问,作用域从说明语句开始到文件结尾。具有该类作用域的有内部函数和外部静态变量。

函数级 函数内从说明语句位置开始直到该函数结束。函数内部定义的自动变量、内部静态变量及语句标号等具有函数级作用域。

块 级 从标识符定义处开始到分程序(块)结尾,即 { } 之间,从标识符定义处开始到 } 为止。

标识符只能在其说明或定义的范围内可见,在此范围外不可见。

Page 21: 第 5 章   函数与数据控制

5.2.3 作用域2 全局变量

文件 test.cpp 的内容:#include "iostream.h"extern int global; // 引用声明void func1(void) // 外部变量引用 { cout<<global<<" is from the variable defined in other file"<<endl; }void main(int argc, char* argv[]){ func1(); }

文件 test1.cpp 的内容:int global(10); // 外部变量定义int func2(){ return global+2; }

具有程序级或者文件级的变量,包含外部变量( external variable )和外部静态变量( external static variable )。

外部变量在所有函数体、类之外定义,作用域为:在定义该变量的文件中,从变量定义处开始到文件结束;在其他文件中,从外部变量引用声明开始到文件结尾。

[ 例 5.9] 外部变量定义及其引用示例

Page 22: 第 5 章   函数与数据控制

5.2.3 作用域2 全局变量

#include "iostream.h"static int nTotal=0;void Add1(void){ nTotal++;}void Add2(void){ nTotal+=2;}void main(int argc, char* argv[]){ Add1(); Add2(); cout<<"Total is "<<nTotal<<endl;}

[ 例 5.10] 静态外部变量的定义及引用示例。

Page 23: 第 5 章   函数与数据控制

5.2.3 作用域3 局部变量

内部变量 (internal variable)或局部变量( local variable )包括函数级变量和块级变量。函数级变量指函数的形式参数和函数体中定义的变量;块级变量指分程序两个匹配花括号 { } 之间定义的变量。函数形参的作用域为整个函数,而函数内部定义变量和分程序中定义的变量,其作用域从定义处开始到函数结尾或分程序结尾。

执行程序时,每次进入局部变量的作用域(包括自嵌套时的每次重复进入),都会为其间的所有局部变量分配存储空间,每次从函数或分程序退出后,这些局部变量变为无效,程序将自动释放局部变量所占用的存储空间,因此局部变量又常称为局部自动变量,在声明这类变量时可以使用关键字 auto ,也可以省略。

Page 24: 第 5 章   函数与数据控制

5.2.3 作用域3 局部变量

int x; // x 是全局变量void MyFunction(){ auto int y; // 局部变量 ; auto 可以省略 int x; // 局部变量 x 隐藏了全局变量 x x=1; for(int i=0; i<=2; i++) { int x; // 此处的局部变量 x又隐藏前一局部变量 x=2; // 对此处的局部变量 x赋值 static int nStatic=8; // 此处定义局部静态变量 nStatic++; } x=3; // 对第一个局部变量 x赋值 ::x=9; // 对全局变量 x赋值}int z=x; // 引用全局变量 x

[ 例 5.11] 局部变量定义及引用示例int x;void MyFunction()

{ … int x; … for(…)

…}

{ int x; …}

A

B

C

Page 25: 第 5 章   函数与数据控制

5.2.3 作用域4 外部函数与内部函

#include "iostream.h"static int GetASCII(char c){ return c; }

void main(){ cout<<GetASCII('a')<<endl; }

内部函数是只能在其定义所在文件中引用的函数,常称为静态函数。

静态函数的优点是在其他文件中不可见,有一些不希望其他文件引用的函数,比如可能以后进行大改动的函数,可以声明为静态函数。

静态函数的定义在形式上仅仅是在一般函数的定义前加一个关键字 static即可,如下例所示:

外部函数是可在其定义文件之外访问的函数,在非定义文件中对外部函数访问前必须进行外部引用声明 。(使用 VC++ 调试 例 5.12 )。

Page 26: 第 5 章   函数与数据控制

5.3 函数的顺序控制

5.3.1 函数执行模型 *

5.3.2 基于栈的实现 *

5.3.3 函数的自嵌套调用

开辟新的运行环境

保存现场

参数结合

执行分程序

恢复现场

释放本函数的运行环境

返回

函数的顺序控制是高级语言中操作顺序控制的重要组成部分,涉及到函数调用机制、数据传递机制的实现等方面的内容。

内部作用域内中所有变量的存储区(用于存储形式参数、局部变量、临时变量)、调用函数的返回地址存储单元(保存调用函数返回后将执行的第一个指令地址及其所在函数的环境地址)、调用函数返回值存储单元地址等,它们一起构成了函数的运行环境。

调用函数调用被调函数的操作过程是暂时中断当前函数的执行,将 CPU 使用和控制权移交给被调函数;从被调函数退出后,再将 CPU 控制权还给调用函数;为使调用函数能够在原来暂时中断的地方继续运行,执行被调函数前,将调用函数中所有保证程序正常运行的信息保存下来,这些信息包括调用函数

Page 27: 第 5 章   函数与数据控制

5.3.1函数执行模型 *当前指令指针 CIP :程序运行时,任何时刻都有某个指令被执行或将被

执行,这个指令被称为当前指令,它在内存中的位置保存在一个被称为当前指令指针。

当前环境指针 CEP :活动记录由函数的局部数据、参数、以及有关的其 它数据项记录组成。

C++ 编译器将为主程序生成一个活动记录,该活动记录与程序的其他代码一起在执行程序时调入内存,同时将 CEP 指向该活动记录, CIP 指向程序代码段的第一条指令。每次调用函数时都创建一个新的活动记录,将 CEP 指向该活动记录,将CIP 指向函数第一条指令,于是程序将顺序执行该函数。函数调用其他函数时,将重复这个过程,创建相应的活动记录并给 CEP 和 CIP赋值。为从函数正确返回,对 CEP 和 CIP赋新值前必须将它们的值保存在新调用函数活动记录中, CIP 的值保存为紧跟在该函数调用语句之后的指令地址,从函数中退出时,从被调函数活动记录中取得先前保存的值,然后对 CEP 和 CIP 重新赋值,从而程序将继续执行调用函数中的剩余语句。

Page 28: 第 5 章   函数与数据控制

5.3.1 函数执行模型 *

#include "iostream.h"

int B(int b1, int b2){ int x=b1+=b2; return x;}

void A(int a){ a+=B(4,5);}

void main(){ A(3); B(6,7); A(2);}

… … … …A 的调用指令 instruction1… … … …B 的调用指令 instruction2… … … …A 的调用指令 instruction3… … … …

常 量

… … … …

B 的调用指令 instruction4

… … … …

返 回

常 量

instruction5

record3

CIP

CEP

主程序 函数 A 函数 B

静态代码段

返回点

活动记录

instruction4

record2局部变量

record3record2

instruction1record1

局部变量

record1

… … … …

… … … …

局部变量

instruction5

… … … …

返 回常 量

Page 29: 第 5 章   函数与数据控制

5.3.2基于栈的实现 *数据对象,这些局部数据对象被放置在函数的本次调用活动记录中。

数据类别 存储单元 单元内容 内存组织

形式参数b2:0012FED

800000005 系统代码块

函数代码块 静态 存储

地址增加方向

b1:0012FED4

00000004栈底部

返回指针CIP:0012FED

000401072 ……

CEP:0012FFCC

0012FF28 本次活动记录 栈局部变量 x : 0012FFC8 …

其它(表达式计算、参数传递临时存储区等)

… ………

栈顶(增长方向 ↓ )自由存储空间

ebx:0012FEE4 00000000 堆顶(增长方向 ↑ )

esi:0012FEE0 00000000 堆(由 new分配)edi:0012FED

C7FFDFF0

0 堆底

C++ 与 C 语言采用栈实现上述运行模型,每个处于运行状态的程序都有一个动态变化的栈和内容不变、静态分配的代码段。栈用于保存调用函数时创建的被调函数活动记录,其中存有调用函数的 CEP 和 CIP 及被调函数的形式参数、局部变量等局部数据对象。因此重复运行函数相当于运行相同的函数代码,但函数体中的操作数(标识符)每次调用对应于不同的数据对象,这些局部数据对象被放置在函数的本次调用活动记录中。

Page 30: 第 5 章   函数与数据控制

5.3.3 函数的自嵌套调用自嵌套调用

C++ 函数调用机制的实现允许一个函数在其内部调用它函数自身。

函数自嵌套调用可以用作递归算法的实现。如果一种程序设计语言允许函数自嵌套调用,则一个函数就可以调用任何函数,包括自己。

从语法上看,函数调用自身并没什么特别,如果清楚函数定义与处于运行状态的函数之间的区别,理解静态代码段、活动记录、 CIP 、 CEP等概念,那么理解函数递归调用的概念就不会存在困难,因为无论如何进行调用,无论调用的是否是函数自身,每进入一个函数时便为这次调用创建一个活动记录,并记录返回点的 CIP 和 CEP ,从被调函数退出时会删除当前的活动记录,恢复调用函数的活动记录,函数运行期间对局部变量及参数的访问都是针对当前活动记录中的数据对象进行的,如果是自嵌套,那么当前的变量不同于上次调用时的同名变量,它们所代表的是位于存储空间不同位置的同名数据对象,上一次调用过程中函数的局部变量在函数的本次调用中是不可见的。

Page 31: 第 5 章   函数与数据控制

5.4 相关的其它语法

5.4.1 数组作函数参数5.4.2 函数指针

Page 32: 第 5 章   函数与数据控制

5.4.1 数组作函数参数1 . 形参与实参都用数

#include "iostream.h"int Sum(int nData[], int nNum, int nTest){ int nResult=0; if(nTest==0) { for(int i=0; i<nNum; i++) nData[i]=5-i; } else { for(int i=0; i<nNum; i++) nResult+=nData[i]; } return nResult;}

void main(){ int data[5]={1,2,3,4,5}; cout<<”The sum is ”<<Sum(data, 5, 1)<<endl; cout<<”The sum is ”<<Sum(data, 5, 0)<<endl; for(int i=0; i<5; i++) cout<<data[i]<<”, ”; cout<<endl;}

定义时函数参数用数组类型(该参数中可不指明数组所含元素数),调用函数时对应数组形参的位置以数组名作为实参。这种情况的形实结合属于传址调用,形参与实参共用内存中同一数组,调函数中改变某个元素的值,调用函数中相应数组元素的值也将改变。[ 例 5.14] 求数组元素的和。

Page 33: 第 5 章   函数与数据控制

5.4.1 数组作函数参数2. 形参与实参都用数组

#include "iostream.h"int Sum(int* pData, int nNum){ int nResult=0; for(int i=0; i<nNum; i++) nResult+=*(pData +i); return nResult;}

void main(){ int data[5]={1,2,3,4,5}; cout<<"The sum is "<<Sum(data, 5)<<endl;}

由于数组与指针之间的天然联系,在需要用数组作为函数的参数时用指针来实现这一功能,因此就有三种可能的用法:形参和实参都用指针,形参用指针而实参用数组 ,形参用数组而实参用指针。

[ 例 5.15] 分析下列程序输出结果。

Page 34: 第 5 章   函数与数据控制

5.4.1 数组作函数参数3 . 形参用数组名而形参用引用

#include "iostream.h"typedef int Array[5];int Sum(Array& refArray, int nNum){ int nResult=0; for(int i=0; i<nNum; i++) nResult+=refArray[i]; return nResult;}void main(){ int data[5]={1,2,3,4,5}; cout<<"The sum is "<<Sum(data, 5)<<endl;}

使用数组的引用要稍麻烦些,由于引用通常是对某种类型数据对象的引用,因此要使用数组的引用首先应该定义一个数组类型,然后再说明函数的参数为该类型的引用。

[ 例 5.18] 分析下列程序输出结果。

Page 35: 第 5 章   函数与数据控制

5.4.2 函数指针1. 指向函数的指针 函数在内存中是一段代码,有一个开始位置,每当函数被调用时,程序的控制就转移到这一点开始运行, C++ 中可以通过指向函数的指针指向某函数的起始地址,于是在程序中可以通过该指针调用这个函数。

void Func(char* str) { … … }void (*pFunc1)(char*)=&Func;void (*pFunc2)(char*)=Func; //Func 与 &Func 的含义相同void (*pFunc3)(char*);void Gunc(){ … … pFunc3=&Func; // 也可以采用 pFunc3=Func 的函数指针赋值方式 pFunc1(” 这是一种调用方式” ); (*pFunc1)(” 这是另一种调用方式” ); … …};

函数指针的说明格式: <ReturnType>(*pFunction)(<Arg_Type_List>);

可以使用函数指针指向具有相同类型参数及返回值的不同名字的函数。

Page 36: 第 5 章   函数与数据控制

5.4.2 函数指针2 . 函数指针类型可参考如下方式定义指针类型:

Typedef void (*OperationType)();OperationType pOperation=&func;

typedef int (*OperationType)();OperationType FileMenuItemOperations[ ]={&FileOpen, &FileNew, &FileClose,...} ;OperationType EditMenuItemOperatoins[ ]={&EditUndo, &EditCut, &EditPaste,...};OperationType ViewMenuItemOperatoins[ ]={&ViewTile, &ViewMax, &ViewMini, ...};main(){ ... switch(nIndexMenuSelected) { case 0: FileMenuItemOperations[ nIndexSelectedItem ](); break; case 1: EditMenuItemOperatoins[ nIndexSelectedItem ](); break; ... } ...}

Page 37: 第 5 章   函数与数据控制

5.5 常用系统函数

5.5.1 终止程序运行5.5.2 数学函数5.5.3 字符串处理函数5.5.4 面向对象的数据结构

编写程序是首先应该熟悉系统提供了哪些函数,本节所列仅为一些常用函数。

Page 38: 第 5 章   函数与数据控制

5.5.1终止程序运行

void main( void ){ fstream MyFile; MyFile.open("UnKonwnFile.txt", ios::nocreate); if(!MyFile ) { perror( "Couldn't open file" ); // 在标准错误输出设备上输出错误信息 abort(); } else MyFile.close();}

调用 exit() ,程序终止前进行必要的清理工作,如清除 atexit()注册的函数。而调用 _exit() 将立即中止程序运行而不做清理工作。

void abort( void );void exit( int status );void _exit( int status );

调用 abort() 退出程序前弹出对话框,上面附有信息“ abnormal program termination” ,选择其上的“重试”按钮可对非正常终止的程序调试。

Page 39: 第 5 章   函数与数据控制

5.5.2 数学函数文件 math.h 中定义了许多数学函数,下面所列为几个常用算术函数。double sqrt(double x); //平方根double sin(double x); // 正弦double power(double x , double y); //幂运算double fabs(double x); //求绝对值double abs(double x); //求绝对值

Page 40: 第 5 章   函数与数据控制

5.5.3 字符串处理函数

int strlen(const char* str); char* strcpy(char* dest, char* src); char* strcat(char* str1, char* str2); char *strchr( const char *string, int c ); int strspn( const char *string1, const char *string2 );

Page 41: 第 5 章   函数与数据控制

5.5.4 面向对象的数据结构

展示 VC++ 的常用数据结构类