《Visual C++ 6.0程序设计》课件第3章_第1页
《Visual C++ 6.0程序设计》课件第3章_第2页
《Visual C++ 6.0程序设计》课件第3章_第3页
《Visual C++ 6.0程序设计》课件第3章_第4页
《Visual C++ 6.0程序设计》课件第3章_第5页
已阅读5页,还剩88页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第3章面向对象编程3.1综述3.2类与对象3.3继承与派生3.4多态性3.5类、继承与派生、多态性综合实例思考题

3.1综述

面向对象的程序设计(Object-OrientedProgramming,OOP)以分析真实世界为出发点,与其他设计概念相比更接近人类处理问题的一般方法。封装性、继承性和多态性是OOP的三个特征,本章将针对这三个特征来学习面向对象编程,使读者掌握类、对象、继承、虚函数等基本概念,为后续内容的学习打下基础。

通常我们认为VisualC++就是面向对象。其实,VisualC++是一个基于VisualC语言的程序语言,然后加上支持面向对象概念机制而成为面向对象的程序语言。因此,VisualC++并不是一个纯粹的面向对象的语言,而现在流行的另一种语言——JAVA才是一个纯粹面向对象的语言。我们总是把世界看做对象的集合。例如,把飞机看作一个对象而不是一个发动机,不同形状的金属片和一些电缆之类的东西;看见一只活泼可爱的小猫,没人把它当作是皮毛包裹的骨头、肌肉和内脏器官。在这里,小猫是一个实例,是生物学树状结构中某一种类的一个对象。扩展开来,对象其实就是东西,也就是周围的一切有形或无形的东西。

建立了对象的思想后,我们将其运用到编程当中。面向对象的程序设计的本质在于用面向对象的概念去寻找、分析出现实世界中对象与对象之间的关系,然后运用程序语言模拟这些现实世界的对象,最后完成电脑系统的建构。

面向对象的程序设计有三个基本原则——封装性、继承性和多态性,下面分别进行讲解。

1.封装性

一个面向对象的语言必须提供一种能够把与对象相关的所有信息封装到对象自身中的机制,即封装性。

封装机制允许把一个对象中的各个独立的元素——变量和函数定义看待成一个整体,而无需考虑对象内部的具体结构。现实生活中有很多关于封装的例子。例如,电视机是一个对象,我们使用时只需要它能够播放电视节目,完全不必关注它是等离子电视或液晶电视。又如集成电路芯片,我们在使用时只需要了解它的各个引脚的功能而无需了解它内部的连接线路。事实上,通过使用对象的方法或者成员函数,可以完全不必知道对象中的属性或者变量的标识符。

2.继承性

语言必须提供一种方法,让程序可以使用对象的定义来创建一个新的对象,即继承性。

继承性提高了软件的再利用率。怎么理解呢?例如,狗是一种哺乳类动物,狗具有哺乳类动物的所有特性——会哺乳。狗除了具有哺乳类动物的共性之外,也有自己的特性,否则所有其他的哺乳类动物都可以被称为“狗”。因此狗类会再定义狗特有的特性,比如:爱啃骨头、叫声为“汪汪”、会摇尾巴等。到此为止,还看不出继承的好处。如果有一天要扩充程序,增加对猫的模拟,在建立一个猫类时,其优点就体现出来了。因为猫也是哺乳类动物,所以猫类也应该继承于哺乳类动物。此时,不需再次编写描述猫类中哺乳类动物的特性的代码,只要将猫类继承于哺乳类动物即可。因此,在狗、猫类里,都不需要描述它们共同具备的会哺乳类特性,这就再利用了定义哺乳类动物的程序代码。同样猫类的特性,如会抓老鼠,则必须在猫类里定义。

3.多态性

必须要有一种方法可以修改新对象的行为,即使它是从已经存在的对象中派生的。这种特性即称为多态性。

多态性是指一个对象既可以继承父辈的特点,也可以有它自己的属性和方法,甚至还可以修改从父辈继承的行为。我们生活中有很多多态性的例子。化学中的“同素异形体”,同样都是碳,煤炭可以燃烧,用于取暖和作为燃料;而钻石,则是另一种完全不同的性质。汽车是继承于“车辆”类中的,车辆的一个属性就是能够自动推进,并能够滑行。汽车继承了这些属性,但它重新定义了推进方法,并且声明了它必须用四个轮子来完成运动,而不是两个轮子。由于多态性,每个对象可以有独特的表现方式。使用多态性,同一函数对于一个对象有一种表现方式,而对于另一个对象有另一种表现方式。

3.2类与对象

3.2.1类和对象的含义

类和对象是VisualC++语言中的两个重要的概念。

1.类

类可以被看成是一些对象的特性描述,即对某种对象的抽象描述。C++语言中提供了类这种工具使得应用中的实体(对象)在程序中可以直接地被表示为一个标识符,并且可以对它进行引用和操作。

例如,猫是一种动物,你家里养了一只叫美丽的猫。如果写一个模拟猫的程序,程序中一定要有一个猫类,里面有猫的特性,比如:猫有四条腿、猫有毛、猫“咪咪”叫、猫走路爪子会收起来,猫爱吃鱼,等等。当要用这个程序来模拟家里的美丽时,就要用猫类来生成一只名叫美丽的对象。因此,我们可以把类看成是建立对象的模型,当在程序中建立一个对象时,就必须以类里的特性描述作为模型,产生一个对象,而这个对象就具有这几个类的属性与方法。所以,某一对象只有一个类,但类的对象可以有好几个,建立的对象都具有其类的所有特性,就像一个模子可以做出好多个零件一样。

类是用来确定一类对象的行为的,而这些行为是通过类的内部数据结构和相关的操作来确定的。类和对象的关系就是抽象(猫类)和具体(美丽)的关系。类里应该包含对类的静态属性的描述——称为数据成员变量的描述,如猫有四条腿,一个脑袋,一条尾巴,身披毛发等,以及动态属性的描述——由称为成员函数的函数来描述,如猫看到老鼠会去抓,看到鱼会“咪咪”叫等。

2.类的声明

类的声明格式一般分为说明部分和实现部分。说明部分用来说明该类中的成员,包含数据成员的说明和成员函数的说明。实现部分是对成员函数的定义。概括说来,说明部分将告诉使用者“做什么”,而实现部分是告诉使用者“怎么做”。

类的声明以关键字class开始,后面跟随类的名字,如狗、猫、汽车等抽象的名称。

一般的形式如下:

class类名称//关键字,类名称是一种标识符。

{

public:

数据成员或成员函数的说明;

private:

数据成员或成员函数的说明;

protected:

数据成员或成员函数的说明;

};

<成员函数的实现>

这里有几点需要说明:

(1) class是定义类的关键字,类名称是一种标识符,一般第一个字母大写,如Dog、Cat、Chair等。

(2)大括号内是类的说明部分,说明该类的数据成员或成员函数,末尾的分号不能省。关键字private、protected、public表征类中数据成员和函数的访问权限。

(3)关键字public、private和protected在类体内出现的先后顺序无关,并且允许多次出现,用它们来说明类成员的访问权限。缺省的访问权限是私有的(private)。

(4)<成员函数的实现>是类定义中的实现部分,这部分包含所有在类体内说明的函数的定义。如果一个成员函数在类体内定义了,实现部分将不出现。

(5)如果所有的成员函数都在类体内定义了,则实现部分可以省略。

3.类成员的访问权限

1)公有类型(public)

公有类型允许对函数或者变量的访问,它们是类与外部的接口,任何外部函数都可以访问公有类型的数据成员或函数。

2)保护类型(protected)

保护类型的访问级别比较严格,只有类、派生类的成员函数可以访问。保护类型仿佛将函数或数据成员安放在一道安全的墙壁之后,只有那些特定的类才能打开它们。

3)私有类型(private)

私有类型给数据成员和函数最严密的保护,只有成员函数才能修改私有变量或执行私有函数。通常类中声明的数据和函数如果没有特别指明,都视为私有类型。公有类型就像集成芯片的引脚和芯体,对外界是可见的;私有类型就像芯片中的电路,对外界是不可见的。

4.类的建立举例

classStudent //说明这是一个名为Student的类

{

private:

intage; //成员变量

charsex; //成员变量

charname[20]; //成员变量

public:

voidregist(char*name,intage,charsex);//输入个人信息成员函数

char*getname(); //得到姓名成员函数

intgetage(); //得到年龄成员函数

chargetsex(); //得到性别成员函数

voidshowme(); //显示学生信息成员函数

};//类定义完毕过以上的语句就定义了一个名为Student的类,其属性包括姓名、年龄、性别,成员函数包括输入个人信息、得到姓名、得到年龄、得到性别、显示学生信息。3.2.2对象

如果说类是抽象的,那么对象则是类的实例,也就是说对象总属于某个已知的类。因此,在定义对象之前,一定要先定义好该对象所属的类。

对对象的声明可以在声明类时直接定义对象,也可以在声明类后再单独声明对象。

对象的声明方法如下:

<类名称><对象名称>;

也可以定义指向类的指针:

<类名称><*指针变量名>;

我们可以定义Student类的两个对象,student1和student2,一个用对象名称声明,一个用指针变量声明。

Studentstudent1,*student2;

对象声明后就可以使用,通过对象可以访问类中的公有数据成员和成员函数,访问的方法为:

<对象名称>.<成员>

如果使用指向类的指针方式,则访问的方法为:

<指针变量名>→<成员>

<*指针变量名>.<成员>

在使用对象时,就可以调用类中的成员,比如:

student1.getname();

student2→showme();

(*student2).getsex();

注意:如果企图执行,则是错误的,因为name数据成员是private类型,对外界是不可见、不可访问的。3.2.3构造函数和析构函数

构造函数与析构函数是类的两个特殊的成员函数。

1.构造函数

在创建了一个对象后,需要对该对象的数据成员进行初始化。VisualC++允许通过一个专门的函数——构造函数来自动完成这些工作。如果没有在类的定义中声明它,编译器也会生成一个空的构造函数。构造函数没有返回值,甚至连void类型的返回值都没有,如果企图为构造函数添加返回值,则会导致编译器报错。

构造函数是由用户定义的,它必须与类名同名,以便系统能识别它并把它作为构造函数。构造函数的特点如下:

(1)构造函数是成员函数,函数体可写在类体内,也可写在类体外。

(2)构造函数是一个特殊的函数,该函数的名字与类名相同,一般声明为public函数,无返回值,不指定类型,也不需要加void类型声明。

(3)程序中不能直接调用构造函数,在创建对象时系统自动调用构造函数。

构造函数的声明格式为:

类名(形式参数)

{

函数体;

}

2.析构函数

析构函数与构造函数功能相反,当对象所在的函数已调用完毕,系统自动执行析构函数,并将对象占用的资源释放。

析构函数的特点:

(1)析构函数也是成员函数,函数体可写在类体内,也可以写在类体外。

(2)析构函数的名字同类名,并在前面加“~”字符,以与构造函数区别。析构函数不指定数据类型,并且也没有参数。

(3)一个类中只可能定义一个析构函数,不能重载。

(4)如果用户没有编写析构函数,编译系统会自动生成一个缺省的析构函数。析构函数的声明格式为:

类名()

{

函数体

}

例如,对类Student建立其构造函数和析构函数的方法为:

classStudent//说明这是一个名为Student的类

{

private:

public:

Person()//构造函数

{ cout<<"这是构造函数"<<endl;

}

~Person()//析构函数

{

cout<<"这是析构函数"<<endl;

}

}; //类定义完毕

如果在主函数main()中声明Student类的一个对象,则程序为:

voidmain() //主函数

{

Personp1;//声明Person类的一个对象p1

}其运行结果会在一开始就自动执行构造函数,出现“这是构造函数”的输出;当我们对对象使用完毕后,为了释放系统资源,系统会自动调用类的析构函数,出现“这是析构函数”的输出。3.2.4类的对象成员

当一个类中的数据成员是某一个类的一个对象时,就称这种成员是新建类的子对象或者对象成员。

定义对象成员的方法为:

classX

{

类名1成员名1;

类名2成员名2;

类名n成员名n;

//其他成员

};

这里X为新建类的类名,类名1、类名2、…、类名n,必须为已定义过的类。如要将前述建立的Student类作为Person类,其方法为:

classStudent //说明这是一个名为Student的类

{

private:

intage; //成员变量

charsex; //成员变量

charname[20]; //成员变量

public:

voidregist(char*name,intage,charsex);//输入个人信息成员函数

char*getname(); //得到姓名成员函数

intgetage(); //得到年龄成员函数

chargetsex(); //得到性别成员函数

voidshowme();

//显示学生信息成员函数

}; //类定义完毕

classPerson

{

private:

Students; //定义一个Student类的成员变量s,作为B的对象成员

public:

protected:

}3.2.5成员函数

如果说成员变量说明类具有哪些基本的属性,那么成员函数可以认为是类具有的具体功能。好比猫类除了具有年龄、身高、性别这些基本的量化属性外,还会“咪咪”叫,见了耗子就抓等行为功能属性,这些都用成员函数来实现。

成员函数的基本格式为:

(类型)类名::函数名(形式参数)

{

函数体

}从基本格式中可以看出,除去“类名::”部分,与一般的函数定义没有差别。由于C++中允许同一个函数名出现在不同的类中,如eat()函数既可以出现在Dog类中,也可以出现在Cat类中。显然不同的类eat()包含不同的内容。为了将同名成员函数进行区别,引入符号“::”,该符号叫做域运算符,它用来指定哪个函数属于哪个类。

如:

voidCat::eat()或voidDog::eat()3.2.6*this指针

*this指针是一个指向当前被操作对象的特殊指针。每个成员函数都有一个this指针变量,在类的成员函数中*this指针代表当前对象。*this指针可以显式使用也可以隐式使用。当定义一个类的对象时,该对象的成员均含有由系统自动生成的指向当前对象的this指针。

类中的成员函数可以直接访问该类的其他成员,且同一个类的不同对象都共享同一组成员函数。当某个对象(设为A)调用其成员函数如MEM()时,如何保证MEM()(MEM()中有访问其他成员的操作)所访问的就是对象A的成员呢?这就要通过隐含的*this指针来确定。比如当对象A调用一个成员函数时,系统将自动为该函数指定一个隐含的参数,该参数是一个指向对象A的指针,这个指针就是*this指针。在实现成员函数的过程中,当访问该对象的成员时,系统将自动使用这个隐含的指针。*this指针只允许在成员函数内使用,不允许修改指针的值,但可以改变指针所指向数据的值。成员函数访问类中成员变量的格式可以写成:

this->成员变量3.2.7类与对象建立举例

【例3-1】

建立一个名为Point的点类,具有x,y两个坐标成员变量,并有其构造函数和析构函数;再建立一个名为Rect的长方形类,有左上角和右下角两个坐标成员变量,能计算长方形的宽度、长度和面积。在主函数中输入正方形参数,验证是否正确。

操作步骤如下:

(1)打开VisualC++6.0。选择“文件/新建”命令。

(2)在弹出的“新建”对话框中,单击“工程”选项卡,其下选择“Win32ConsoleApplication”项,在右边的“工程”框中输入工程名“chap3_1”,选择保存位置后单击按钮,如图3-1所示。图3-1“新建“对话框

(3)在随后弹出的“Win32ConsoleApplication-Step1of1”对话框中,选中,单击按钮,如图3-2所示。图3-2“Win32ConsoleApplication-Step1of1”对话框

(4)随后弹出“新建工程信息”对话框,单击按钮。

(5)在项目工作区中,单击选项卡,这时发现展开的文件夹里没有文件,如图3-3所示。图3-3“ClassView”选项卡

(6)选择“文件/新建”命令,弹出“新建”对话框。

(7)单击“文件”选项卡,选择“C++SourceFile”选项,在右边“文件”下编辑框中输入“chap3_1”,为新添加的C++源文件命名,单击“确定”按钮。

(8)在右边出现了用户输入代码的编辑区,在其中输入如下代码:

#include"iostream.h"

classPoint //说明这是一个名为Point的类

{

public:

intx; //成员变量

inty; //成员变量

Point() //构造函数

{

cout<<"这是Point构造函数"<<endl;

}

~Point() //析构函数

{

cout<<“这是Point析构函数”<<endl;

}

};//类定义完毕

classRect //定义长方形类

{

public:

PointLefttop;

//建立类对象成员变量

PointRightbottom; //建立类对象成员变量

intgetlength()

//求长方形长成员函数

{ intlength;

length=Rightbottom.x-Lefttop.x;

returnlength;

}

intgetwide() //求长方形的宽成员函数

{

intwide;

wide=Rightbottom.y-Lefttop.y;

returnwide;

}

intsurface() //求长方形面积成员函数

{

return(Rightbottom.x-Lefttop.x)*(Rightbottom.y-Lefttop.y);

}

};voidmain()//主函数

{

Rectrect;

cout<<"请输入左上角坐标"<<endl;

cin>>rect.Lefttop.x>>rect.Lefttop.y;

cout<<"请输入右下角坐标"<<endl;

cin>>rect.Rightbottom.x>>rect.Rightbottom.y;

cout<<"长方形的长为:"<<rect.getlength()<<endl;

cout<<"长方形的宽为:"<<rect.getwide()<<endl;

cout<<"长方形的面积为:"<<rect.surface()<<endl;

}

(9)其运行结果如图3-4所示。图3-4建立类与对象运行结果

3.3继 承 与 派 生

3.3.1继承与派生的概念

C++语言中的继承是类和类之间一种特殊关系的表示,是基于已存在的类创建新类的方法。类的继承性是C++语言与C语言之间的最大不同,是OOP的重要特点之一。

在解释继承的意义前,仍然采用前面猫类和狗类继承于哺乳动物的例子说明与继承有关的名词。

基类(baseclass):猫、狗类继承于哺乳动物类,因此哺乳类就是猫、狗类的基类,也称为父类。

派生类(derivedclass):在猫、狗类的例子里,猫、狗类就是继承于哺乳类的派生类,也称为子类,如图3-5所示。图3-5哺乳动物类层次图继承的概念来自于分类与遗传。

人类习惯利用分类来管理与了解现实世界里的对象。以生物分类为例:界、门、纲等这些分类等级中,低层次的分类都具备其高层次分类的特性。

另一个理解继承的概念来自于遗传,每个人的特征或多或少都来自于父母的遗传,但每个人都不可能完全与父亲或母亲一模一样,有着自己的特征。

因此,在OOP里继承使得派生类具有基类的特性,同时又允许在派生类的声明时增加自己的特性,或修改基类的特性。基类派生类的关系如图3-6所示。图3-6基类派生类的关系一个派生类既可以从一个基类派生也可以从多个基类派生。从一个基类派生称为单继承,从多个基类派生称为多重继承。

派生类的构造如下:

class派生类名:访问方式基类名1,访问方式基类名2,…,访问方式基类名n

{

派生类中的新成员

};

其访问方式可以为public(公有继承),protected(保护继承),private(私有继承)任何一种,下面分别进行讲解。

1.公有继承

如果A类采用公有继承方式继承B类,A类将继承B类的public成员与protected成员,且继承后原成员函数的访问级别不变。

2.保护继承

如果A类采用保护继承方式继承B类,A类将继承B类的public成员与protected成员,继承后成员函数的访问级别均为protected。

3.私有继承

如果A类采用私有继承方式继承B类,A类将继承B类的public成员与protected成员,继承后成员函数的访问级别均为private。如果没有特别指明则为private方式。3.3.2继承与派生的实例

【例3-2】建立一个名为Person的基类,其属性包括年龄、性别、姓名和输入信息及输出信息成员函数;建立一个名为School的基类,其属性包括专业、学院和输入信息及输出信息成员函数;再建立一个名为Student的继承派生类,继承Person类和School类,同时增加学号、成绩输入信息及输出信息成员函数;输入相关信息,显示其属性。

操作步骤如下:

(1)如例3-1,建立一个“Aemptyproject”。

(2)为例3-2增加一个“C++ResourceFile”,文件名为“chap3_2”。

(3)在工作区中输入代码:#include"iostream.h"

#include"string.h"

classPerson //说明这是一个名为Person的类

{

public:

intage; //年龄数据

charsex; //性别数据

charname[20]; //姓名数据

voidshowp()

{

cout<<name<<""<<age<<""<<sex<<"";

}

};//类定义完毕

classSchool//说明这是一个名为Company的类

{

public:

charcollege[30]; //学院数据

charmajor[20]; //专业数据

voidshowc() //显示公司信息

{

cout<<college<<“”<<major<<“”;

}

};

classStudent:publicPerson,publicSchool//Student类继承于Person类和School类

{

public:

intID; //Student自己特有的数据成员--学号

intscore; //Student自己特有的数据成员--成绩

voidshows() //显示学生信息

{

cout<<ID<<""<<score<<""<<endl;

}

};

voidmain()

{

Students1; cout<<"请输入你的姓名"<<endl;

cin>>;

cout<<"请输入你的年龄"<<endl;

cin>>s1.age;

cout<<"请输入你的性别"<<endl;

cin>>s1.sex;

cout<<"请输入你的学院"<<endl;

cin>>s1.college;

cout<<"请输入你的专业"<<endl;

cin>>s1.major;

cout<<"请输入你的学号"<<endl;

cin>>s1.ID;

cout<<"请输入你的成绩"<<endl;

cin>>s1.score;

s1.showp();

s1.showc();

s1.shows();

}

(4)按“Ctrl+F5”键运行程序,按照提示输入姓名、年龄、性别、学院、专业、学号、成绩,执行结果如图3-7所示。图3-7派生类Student具有Person类和School类的共同属性,也有自己的特性该例中,派生类Student派生于Person类和School类,属于多重继承,具有Person类和School类的共性,如图3-8所示。图3-8Student继承层次结构

Student信息既包含作为独立人的个人信息,也包含所在学校的信息。从上图中可以看出,采用继承方式可以体现出问题本身的层次。

在输入Student信息中,调用了其基类成员变量并为其进行赋值。如果没有采用继承的方式,Student类中将重复输入个人信息和学校信息的指令,增加了工作量。由此,继承的主要作用体现在提高编程效率上。

在Student信息中,还添加了其基类中没有的成员——

ID和score。其作为自身特有的成员,单独写在Student类中。

此例中,继承的方式为public公有继承方式,派生类可以访问基类的所有public成员。

3.4多态性

函数重载和运算符重载是简单的一类多态性。重要的多态性是建立在虚函数的概念和方法基础之上的,下面重点讲解。

3.4.1函数重载

函数重载就是赋给同一个函数名多个含义,其特点有如下两个:

(1) C++中允许在相同的作用域内以相同的名字定义几个不同的函数,可以是成员函数也可以是非成员函数。

(2)定义重载函数要求函数的参数至少有一个类型不同,或者个数不同。重载函数的意义在于它可以用相同的名字访问一组相互关联的函数,由编译程序来进行选择,这将有助于解决程序复杂性问题。

函数的返回类型对确定重载函数没有意义,因为在没有确定一个函数要调用哪个重载函数之前,函数的返回类型也是不确定的。当然,如果确定调用某个重载函数,则函数调用表达式的类型也就被惟一地确定下来。

【例3-3】

建立一个类Reload,为output成员函数配置不同的输入形式参数,实现函数重载。

操作步骤如下:

(1)建立一个名为“chap3_3”的“Aemptyproject”。

(2)增加一个“C++ResourceFile”,文件名为“chap3_3”。

(3)在工作区中输入代码:

#include"iostream.h"

classReload //定义Reload类

{

public:

voidoutput(inti) //定义带一个整型形式参数的output函数

{

cout<<i<<endl;

}

voidoutput(doublef) //定义带一个浮点型形式参数的output函数

{

cout<<f<<endl;

} voidoutput(char*s)

//定义带一个字符形指针参数的output函数

{

cout<<s<<endl;

}

voidoutput(char*s,intn)//定义带两个形式参数的output函数

{

cout<<s<<""<<n<<endl;

}

};

voidmain()//主函数

{

Reloadr;//定义一个A类对象a

r.output(6);//调用形式参数为整型的output函数

r.output(56.9);//调用形式参数为浮点型的output函数

r.output(“helloworld”);//调用形式参数为字符指针型的output函数

r.output(“helloworld",10);//调用形式参数为两个的output函数

}

(4)按“Ctrl+F5”键,运行程序,执行结果如图3-9所示。

在A类里定义了4个同名的成员函数output,前三个靠参数类型进行识别,而最后一个函数则依靠参数个数进行区别。图3-9形式参数不同的函数重载运行结果需要注意的是,在类中不能进行下述形式的重载声明:

classReload

{

public:voidoutput(char*s)

{

cout<<s<<endl;

}

voidoutput(char*s,intn=10)

{

cout<<s<<""<<n<<endl;

}

};如果使用“r.output(“Welcomeyou”);”,则会发生逻辑混乱,造成调用时的二义性,无法惟一确定调用的是哪个重载函数。因此,不会出现期望的输出结果,编译过程中会出现如下报错信息:

这种缺省参数的形式应避免。3.4.2静态联编与动态联编

由于函数重载的存在,当程序中出现调用同名函数时,编译器会根据函数的参数类型、个数决定调用哪一个同名函数的代码。这种把一个函数的调用与适当的函数代码联系在一起的过程叫做联编。

按照联编所进行的阶段不同,可分为两种不同的联编方法,即静态联编和动态联编。

1.静态联编

静态联编是在程序编译阶段确定一个函数调用与函数代码间的对应关系,这种对应关系确定以后,在程序运行过程中就根据这个对应关系去调用并执行相关的函数代码,并且这种对应关系在程序运行过程中保持不变,下面我们先通过一个例子来看看静态联编的效果。

【例3-4】

建立一个基类Point,具备属性包括X、Y坐标和求面积公式;再建立一个派生类Rectangle,具备属性为长、宽、四个角的坐标及求面积计算公式,计算输入坐标后矩形的面积。操作步骤如下:

(1)建立一个名为“chap3_4”的“Aemptyproject”。

(2)为例3-4增加一个“C++ResourceFile”,文件名为“chap3_4”。

(3)在工作区中输入代码:

#include"iostream.h"

#include"math.h"

classPoint//点类

{

private:

doublex,y;public:

Point(inti,intj)

{

x=i;

y=j;

}

doublearea()

{

return0.0;

}

};

classTriangle:publicPoint//基类为Point类的派生类 - 直角三角形类

{

private:

floatbottom,height;public:

Triangle(inti,intj,intk,intl,intm,intn);//构造函数输入直角三角形三点坐标

doublearea()

{

return0.5*bottom*height;

}

};

Triangle::Triangle(inti,intj,intk,intl,intm,intn):Point(i,j)//初始化Point参数并计算矩形宽、高

{

bottom=m-k;

height=l-j;

}voidfun(Point&s)

{

cout<<s.area()<<endl;

}

voidmain()

{

Triangletri(10,10,10,50,100,50);//初始化矩形参数

fun(tri);//调用函数fun求面积

}

(4)按“Ctrl+F5”键运行程序,执行结果如图3-10所示。图3-10静态联编执行结果为何结果不是期望的三角形面积?这是因为在fun()函数中,s所引用的对象执行的area()操作被关联到Point::area()的代码上。在程序编译阶段,对s引用的对象所执行的area()操作只能约束到Point类的函数上,因此输出结果为0。

而我们期望的是fun()函数执行后能得到三角形的面积,也就是说s引用的对象所执行的area操作应约束到Triangle类的area()函数上,这是静态联编无法实现的。

2.动态联编

动态联编在编译阶段不能决定执行哪个同名的被调函数,只在程序运行过程中根据需要处理的对象类型来决定执行哪个类的成员函数。

动态联编实际上是进行动态识别。前面分析静态联编时,fun()函数中s的对象被约束到Point类上。我们期望的是,程序运行时能将s的对象约束到Rectangle类上。可见,同一个对象引用在不同阶段被约定的类的对象是不同的。那么如何来确定是静态联编还是动态联编呢?C++规定动态联编是在虚函数支持下实现的。

通过上述分析可以看出,静态联编和动态联编也是属于多态性的,是不同阶段对不同实现进行不同的选择。

3.虚函数

虚函数是动态联编的基础。虚函数是成员函数。虚函数的声明方法为:

virtual<类型说明符><函数名>(<参数表>)

如果某类中的一个成员函数声明为虚函数,这意味着该成员函数在派生类中可能有不同的实现。

【例3-5】

建立一个名为Point的基类,有X、Y坐标属性,并定义一个虚函数area()。再定义一个派生类Rectangle,其属性包括四个角的坐标位置和求面积的成员函数area(),计算给定四个角坐标的矩形面积。操作步骤如下:

(1)建立一个名为“chap3_5”的“Aemptyproject”。

(2)为例3-5增加一个“C++ResourceFile”,文件名为“chap3_5”。

(3)在工作区中输入代码:

#include"iostream.h"

#include"math.h"

classPoint//点类

{

private:

doublex,y;

public:

Point(inti,intj)

{ x=i;

y=j;

}

virtualdoublearea()

{

return0.0;

}

};

classTriangle:publicPoint//基类为Point类的派生类 - 直角三角形类

{

private:

floatbottom,height;

public:

Triangle(inti,intj,intk,intl,intm,intn);//构造函数输入直角三角形三点坐标 doublearea()

{

return0.5*bottom*height;

}

};

Triangle::Triangle(inti,intj,intk,intl,intm,intn):Point(i,j)//初始化Point参数并计算矩形宽、高

{

bottom=m-k;

height=l-j;

}

voidfun(Point&s)

{

cout<<s.area()<<endl;

}

voidmain()

{

Triangletri(10,10,10,50,100,50);//初始化矩形参数

fun(tri);//调用函数fun求面积

}

(4)按“Ctrl+F5”键运行程序,执行结果如图3-11所示。图3-11给定四个角坐标下矩形面积试比较chap3_5与chap3_4代码有何不同?可以看到,chap3_5中Point类成员函数area()前添加了virtual关键字。在此例中,Point中area()函数被声明为虚函数。在程序运行时,s被动态联编,被约束为Triangle类中的area()函数。通过这个例子可以看到,派生类中对基类的虚函数进行替换时,要求派生类中说明的虚函数与基类中的被替换的虚函数满足如下条件:

(1)与基类的虚函数有相同的参数个数。

(2)其参数的类型与基类的虚函数的对应参数类型相同。

3.5类、继承与派生、多态性综合实例

【例3-6】计算图形面积。给出4种简单的几何图形,求出其面积。

计算公式:

三角形(Triangle)面积公式:底×高÷2

矩形(Rectangle)面积公式:长×宽

圆(Circle)面积公式:π×半径×半径

梯形(Trapeziod)面积公式:(上底+下底)×高÷2

操作步骤如下:

(1)建立一个名为“chap3_6”的“Aemptyproject”。

(2)为例3-6增加一个“C++ResourceFile”,文件名为“chap3_6”。(3)在工作区中输入代码:

#include"iostream.h"

#include"string.h"

classShape//定义基类

{

public:

virtualchar*n()

{

return0;

}

virtualdoublearea()

{

return0.0;

}

};classTriangle:publicShape//三角形类继承于Shape类

{

private:

doublebottom;

doubleheight;

charname[20];

public:

Triangle(doubleb,doubleh)//构造函数

{

bottom=b;//底

height=h;//高

}

char*n()

{ strcpy(name,"三角形");

returnname;

}

doublearea()//计算面积

{

returnbottom*height*0.5;

}

};

classRectangle:publicShape

//Rectangle类继承于Shape类

{

private:

doubleheight;

//高

doublewide;

//宽

charname[20];

public: Rectangle(doubleh,doublew)//构造函数

{

height=h;

wide=w;

}

doublearea()//计算面积

{

returnheight*wide;

}

char*n()

{

strcpy(name,“矩形”);

returnname;

}

};classCircle:publicShape//圆形类继承于Shape类

{

private:

doubleradius;//半径

charname[20];

public:

Circle(doubler)//构造函数

{

radius=r;

}

doublearea()//计算面积

{

return3.14*radius*radius;

}

char*n()

{

strcpy(name,"圆形");

returnname;

}

};

classTrapeziod:publicShape//梯形类继承于Shape类

{

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论