870 likes | 980 Views
Visual C++ 实用教程. 第 2 章 面向对象程序设计基础. 河北经贸大学信息技术学院. 教学目标:. 了解面向对象的基本概念 熟练掌握类、对象、派生类的定义和使用 掌握类的构造函数和析构函数的定义和特点 能够利用虚函数实现多态性 熟悉友元的特性. 教学内容:. 2.1 面向对象的基本概念 2.2 类和对象的定义 2.3 继承性和派生类 2.4 多态性 2.5 友元 2.6 模板. 2.1 面向对象的基本概念. 观点: 自然界是由实体(对象)所组成。 程序设计方法:
E N D
Visual C++实用教程 第2章 面向对象程序设计基础 河北经贸大学信息技术学院
教学目标: • 了解面向对象的基本概念 • 熟练掌握类、对象、派生类的定义和使用 • 掌握类的构造函数和析构函数的定义和特点 • 能够利用虚函数实现多态性 • 熟悉友元的特性
教学内容: • 2.1 面向对象的基本概念 • 2.2 类和对象的定义 2.3 继承性和派生类 2.4 多态性 • 2.5 友元 • 2.6 模板
2.1面向对象的基本概念 • 观点: • 自然界是由实体(对象)所组成。 • 程序设计方法: • 使用面向对象的观点来描述、模仿并处理现实问题。 • 要求: • 高度概括、分类、和抽象。 • 目的: • 实现软件设计的产业化。 程序=多个对象+消息
类和对象 • 对象是现实世界中一个实际存在的事物 • 对象的静态特征 • 对象的行为 • 类是具有相同属性和行为的一组对象的集合,它为属于该类的全部对象提供了统一的抽象描述,其内部包括属性和行为两个主要部分。 • 对象之间的交互:发送消息
封装 • 封装的目的是隐藏对象的内部的实现细节。 • 通过封装,可以将对象的外部接口与内部的实现细节分开。 • 目的是增强安全性和简化编程,使用者不必了解具体的实现细节,而只需要通过外部接口,以特定的访问权限,来使用类的成员。
继承 • 继承是C++中支持层次分类的一种机制,允许程序员在保持原有类特性的基础上,对新类进行更具体的说明。 • 实现:声明派生类——2.3
多态性 • 多态:同一名称,不同的功能实现方式。 • 目的:标识统一,减少程序中标识符的个数; 接口统一,增加程序的灵活性 • 实现:——2.4 • 编译时的多态性:重载函数 • 运行时的多态性:虚函数
2.2 类与对象的定义 • 类是C++的灵魂,如果不真正掌握类,就不能真正掌握C++ • C++中的类就是一种用户自己定义的数据类型,和其它数据类型不同的是,组成这种类型的不仅可以有数据,而且可以有对数据进行操作的函数。 • 为了封装的需要,一般将类的定义放在一个.h文件中,而将类的成员函数的实现放在一个.cpp文件中。
2.2.1 类的定义 class 类名称 { public: 公有成员(外部接口) private: 私有成员 protected: 保护成员 } ; 类与外部的接口 只允许本类中的 函数访问(可省略) 成员访问 说明符 只能由本类及其派生类 的成员函数访问 是必需的
例2.1:圆类的定义见教材24页 • //Circle.h文件,类CCircle的定义 • const double PI = 3.14159; // 定义圆周率为符号常量 • class CCircle // 定义类 • { • public: • CCircle() //构造函数 • { radius = 1; } • virtual ~CCircle() { } // 析构函数 • inline double Area()const; // 计算圆的面积 • double Circumference() const; // 计算圆的周长 • double GetRadius() const • // 取得圆的半径 • { return radius; } • void SetRadius( double r) // 为半径设置新值 • { radius = r>0 ? r : 1; } • private: • double radius; // 数据成员 • }; • inline double CCircle::Area() const • // 计算圆的面积 • { • return PI * radius * radius; • } //Circle.cpp文件,类CCircle的成员函数的实现 #include “Circle.h” double CCircle::Circumference() const // 计算圆的周长 { return 2 * PI * radius; }
说明: • 类的数据成员(成员变量) :与一般的变量声明相同,但需要将它放在类的声明体中,一般为私有访问属性。 • 类的成员函数定义: • 在类中说明原型,类外给出函数体实现,函数名之前必须加类名和作用域区分符限定 • 将成员函数定义为内联函数: • 成员函数在类的内部定义,此时无须使用inline关键字,成员函数自动为内联函数。 • 成员函数在类内声明,在类外定义。但在类外定义时,要加关键字inline。类的成员函数允许声明为
const成员函数 • const成员函数: • 不修改数据成员的值, • 在程序中如果不小心修改了这个成员函数中的对象,则编译器会产生一个语法错误信息。 • const 成员函数的定义方法: • 在函数的原型和定义中,在函数参数表和函数定义的左花括号之间加入const关键字。 • 类的成员函数允许重载,允许带缺省参数值。
2.2.2 构造函数 作用:对象的初始化 性质: • 与类同名、无返回类型 • 在对象创建时由系统自动调用 • 允许重载、带缺省值 • 如果类中未声明,则系统自动生成一个缺省形式的构造函数,形如:类名(){ }
构造函数举例--- CCircle类 class CCircle { public: CCircle() // 无参(缺省)构造函数 { radius = 1; } CCircle(double r); // 带参构造函数 …… private: double radius; }; CCircle::CCircle(double r) { radius = r>0 ? r : 1; } 两个构造函数合成一个带缺省值的构造函数 CCircle(double r=1); // 构造函数原型 CCircle::CCircle(double r) // 构造函数定义 { radius = r>0 ? r : 1; }
拷贝构造函数 拷贝构造函数是一种特殊的构造函数,其形参为本类对象的引用。 class 类名{ public : 类名(形参);//构造函数 类名(类名 &对象名);//拷贝构造函数 …… }; 类名:: 类名(类名 &对象名)//拷贝构造函数的实现 { 函数体 }
拷贝构造函数举例 class CCircle { public: CCircle (float r=1 ); CCircle ( const CCircle &c ) // 拷贝构造函数 { radius = c.radius; } …… };
拷贝构造函数调用之一 • 当用类的一个对象去初始化该类的另一个对象时系统自动调用它实现对象的拷贝赋值。 void main( ) { CCircle c1; CCircle c2(c1);//拷贝构造函数被调用 }
拷贝构造函数调用之二 • 若函数的形参为类对象按值传递时,实参赋值给形参,将自动调用复制构造函数,例如: void fun1( CCircle c ) { cout<<c.GetRadius ( )<<endl; } void main ( ) { CCircle c1(10); //P26错误 fun1(c1); // 调用复制构造函数 }
拷贝构造函数调用之三 • 当函数的返回值是类对象时,系统自动调用复制构造函数 。例如: CCircle fun2 ( ) { CCircle c2(10); return c2; // 调用复制构造函数 } void main ( ) { CCircle c3; c3=fun2 ( ); }
拷贝构造函数说明 • 对于任何类,如果程序员没有定义拷贝构造函数,则系统自动生成一个拷贝构造函数,这种拷贝构造函数在执行时,只是进行简单的成员复制,这对于含有指针成员的类是非常危险的,在这种情况下,必须自定义拷贝构造函数。
2.2.3 析构函数 • 作用:是释放对象所占的动态内存空间。 • 特点: • 函数名:~类名(); • 没有参数,也不返回任何值 • 不允许重载 • 如果类中未定义析构函数,编译器将自动生成一个缺省的析构函数。形如: ~类名( ) { }
析构函数的调用 在下列两种情况下会被自动调用: • 对象定义在一个函数体中,该函数调用结束后,自动调用析构函数; • 用new生成的动态对象,当使用delete删除时,自动调用析构函数。 • 通常利用析构函数删除对象中指针成员所指向的动态存储空间,当类中没有指针成员时,则不需要定义析构函数。
2.2.4 对象的定义和使用 • 类的对象是该类的某一特定实体,即类类型的变量。 • 声明形式: 类名 对象名; • 例:CCircle C1; CCircle *pCircle; CCircle ArrayCircle[ 3 ];
类成员的访问方式 • 类内成员互访 • 直接使用成员名 • 可以访问任意属性成员 • 类外访问 • 只能访问public属性的成员 • 使用“对象名.成员名”方式 • 使用“对象指针名->成员名”方式
this指针 • 在C++中为每个非静态成员函数提供了一个名字为this的指针,当进行成员函数调用时,系统自动将调用此成员函数的对象的地址作为一个隐含的参数传递给this指针,即让this指针指向调用此成员函数的对象,从而使成员函数知道该对哪个对象进行操作。 • 使用this指针,保证了每个对象可以拥有不同的数据成员,但处理这些数据成员的代码可以被所有的对象共享。
过道 游泳池 例2.2 类的应用举例(教材28页) 一圆型游泳池如图所示,现需要在其周围建一圆型过道,并在其四周围上栅栏。栅栏价格为35元/米,过道造价为20元/平方米。过道宽度为3米,游泳池半径由键盘输入。要求编程计算并输出过道和栅栏的造价。 问题分析: 圆类及其应用
第1步定义圆类 • // Circle.h文件 • const double PI = 3.14159; • class CCircle • { • public: • CCircle(double r=1); • double Circumference(); • // 计算圆的周长 • double Area(); // 计算圆的面积 • virtual ~CCircle() {} • private: • double radius; • }; // Circle.cpp文件 #include "Circle.h" CCircle::CCircle(double r) { radius = r>0 ? r : 1; } double CCircle::Area() { return PI * radius * radius; } double CCircle::Circumference() { return 2 * PI * radius; }
第2步 使用圆类求解问题 • // Ex2_2Main.cpp文件 • #include<iostream.h> • #include “Circle.h” • const double FencePrice = 35; // 栅栏单价 • const double ConcretePrice = 20; // 过道单价 • void main() • { • double radius, FenceCost, ConcreteCost; • cout<<" 请输入游泳池的半径: "; • cin>>radius; • CCircle Pool(radius); // 声明 Circle 对象 • CCircle PoolRim( radius+3 ); • // 计算栅栏造价并输出 • FenceCost = PoolRim.Circumference() * FencePrice; • cout << " 栅栏造价是: ¥" << FenceCost << endl; • // 计算过道造价并输出 • ConcreteCost = (PoolRim.Area() - Pool.Area())*ConcretePrice; • cout << " 过道造价是: ¥" << ConcreteCost << endl; • }
2.2.5 静态类成员 类的每个对象有自己的所有数据成员的副本,有时类的所有对象应共享数据的一个副本,这时可以使用静态数据成员。 静态类成员:使用关键字 static 进行修饰的 类成员。 静态类成员可以声明为public、private或 protected。
1、静态数据成员 1、静态数据成员表示的是类范围中所有对象共享的信息, 相当于局部于类中的“全局变量”,为该类的所有对象 共享。因为静态数据成员只有一个数据副本,所以可 以节省存储空间。 2、静态数据成员必须在文件作用域内进行初始化,而 且只能初始化一次。 3、由于数据隐藏的需要,静态数据成员通常被声明为 私有的,而通过定义公有的静态成员函数来访问静 态数据成员。
静态数据成员的定义和初始化 class CCircle{ public: …… private: …… static int NumOfObject; // 程序中生成的对象的个数 }; int CCircle::NumOfObject=0; // 静态数据成员的初始化
2、静态成员函数 静态成员函数:是用关键字 static 进行修饰的成员函数。 定义格式: static 成员函数的原型 ; 1、静态成员函数没有this指针,因此当其访问非静态 数据成员时,必须通过对象名或对象指针访问。 2、静态成员函数不能是虚函数。 因为静态数据成员和静态成员函数是独立于类对象而存在的。即使在类还没有实例化任何对象时,静态数据成员和成员函数就已经存在并可使用。
3、静态成员的访问 在类内:可直接访问。 在类外:只能访问公有静态成员 类名::成员名 ( 常用 ) 对象名.成员名(.和->) 使用静态类成员(教材30页)
2.3 继承性和派生类 • 继承是面向对象程序设计的最重要的特点之一,是软件重用的一种重要形式,是对实际问题中分层特性的一种自然描述。 • 继承的实质:是从已有的类建立新类。 • 通过继承,派生类自动拥有基类的所有成员(数据成员和成员函数) • 基类和派生类 • 单继承和多继承
2.3.1 派生类的定义 class 派生类名:继承方式 基类名 { 成员声明; };
表2-1 继承方式对派生类的影响 继承方式 说明 public(公有) 基类的public和protected成员被派生类继承后,保持原来的访问属性不变 private(私有) 基类的public和protected成员被派生类继承后,变成派生类的private成员 protected(保护) 基类的public和protected成员被派生类继承后,变成派生类的protected成员 继承方式 三种继承方式:public(公有)、private(私有)、protected(保护),
派生类构成示意图: 基类部分 派生类成员 新定义部分 每个派生类对象所占有的存储空间的大小等于其基类部分的所有数据成员占有的存储空间的大小与新定义部分的所有数据成员占有的存储空间的大小的总和。
例2.3类的派生示例教材31页 点----圆-----圆柱体 CPoint::CPoint( double xx, double yy ) { X=xx; Y=yy; cout<<"CPoint 类构造函数被调用\n"; } CPoint::~CPoint() { cout<<"CPoint 类的析构函数被调用\n"; } 1 定义基类CPoint • //Point.h文件 • #include <iostream.h> • class CPoint // 基类CPoint • { • public: • CPoint ( double xx=0, double yy=0 ); • virtual ~CPoint(); • double GetX() { return X; } • double GetY() { return Y; } • double area() { return 0.0; } • private: • double X,Y; • };
定义派生类CCircle • //Circle.h文件 • #include "Point.h" • const double PI=3.14159; • class CCircle : public CPoint • { • public: • CCircle(double x=0, double y=0, double r=1); • //派生类构造函数 • virtual ~CCircle(); • double GetRadius() • { return radius; } • double area() // 重新定义基类中的成员 • { return PI*radius*radius; } • private: // 新增私有数据成员 • double radius; • }; CCircle::CCircle(double x, double y, double r) : CPoint(x,y) // 初始化基类的数据成员 { radius = r>0 ? r : 1; cout<<"CCircle 类构造函数被调用\n"; } CCircle::~CCircle() {cout<<"CCircle 类的析构函数被调用\n"; }
//Cylinder.h文件 • #include "Circle.h" • class CCylinder : public CCircle • { • public: • CCylinder(double x=0, double y=0, double r=1, double h=1); //派生类构造函数 • virtual ~CCylinder(); • double GetHeight () • { return height; } • double area( ) // 重新定义基类中的成员 • { • return 2 * CCircle::area() + 2* PI * GetRadius() * height; //访问基类的同名成员 • } • private: //新增私有数据成员 • double height; • };
CCylinder::CCylinder(double x, double y, double r, double h) //派生类构造函数 • : CCircle(x,y,r) // 初始化基类的数据成员 • { • height = h>0 ? h : 1 ; • cout<<"CCylinder 类构造函数被调用\n"; • } • CCylinder::~CCylinder() • { • cout<<"CCylinder 类的析构函数被调用\n"; • }
派生类的使用 • // Ex2_3Main.cpp文件 • #include <iostream.h> • #include "Cylinder.h" • void main() • { • CCylinder cy1( 2, 3, 20, 10); • //通过派生类对象访问基类公有成员 • cout<<cy1.GetX()<<" " <<cy1.GetY()<<" " • <<cy1. GetRadius() <<" "<<cy1. GetHeight () <<endl; • // 访问基类和派生类中的同名成员 • cout<< cy1.area()<<" "<< cy1.CCircle::area()<<" "<< cy1. CPoint::area() • << endl; • }
2.3.2 派生类的构造函数与析构函数 • 基类的构造函数不能被继承,需要在派生类中负责基类成员的初始化。 • 通过在成员初始化表中显式调用基类的构造函数来实现的 • 单一继承时的构造函数 派生类名::派生类名(基类成员和本类成员所 需的形参) : 基类名(实参) { 本类成员初始化; }
派生类构造函数与析构函数的执行顺序 • 在创建派生类的对象时,派生类的构造函数总是先调用其基类的构造函数来完成基类成员的初始化。 • 如果在基类和派生类中都包含其他类的对象(即有对象成员),则在创建派生类的对象时,首先执行基类的对象成员的构造函数,接着执行基类的构造函数,然后执行派生类的对象成员的构造函数,最后才执行派生类的构造函数。 • 当派生类对象的生命周期结束时,析构函数的执行顺序和构造函数的执行顺序正好相反。
2.3.3 多继承 • 如果一个派生类有多个直接基类,则称为多继承。 • 多继承意味着一个派生类可以继承多个基类的成员,这种强大的功能支持了软件的重用性,但可能会引起大量的二义性(歧义性)问题。
1. 多继承的定义格式 class <派生类名> : [<继承方式>] <基类名>,[<继承方式>] <基类名>,… { [<派生类的成员>] }; • 其中,继承方式的使用与单继承完全相同。 • 【例2.4】使用多继承 (教材35页)
多继承举例 • class CBase1 • { • public: • CBase1( int x=0 ) { value=x; } • int GetValue ( )const { return value; } • protected: • int value; // 保护成员,能被派生类直接访问 • }; • class CBase2 • { • public: • CBase2( int y=0 ) • { value=y; } • int GetValue ( )const • { return value; } • protected: • int value; // 保护成员,能被派生类直接访问 • }; class CDerived : public CBase1, public CBase2 { public: CDerived( int x=0, int y=0, int v=0 ) : CBase1( x ), CBase2( y ) // 基类成员的初始化 {value=v; } int GetData ( ) { return CBase1::value + CBase2::value; } // 访问不同基类的同名成员 int Get Value ( )const { return value; } private: int value; };
派生类的使用 • void main( ) • { • CDerived d( 10, 20, 500 ); • cout<<"CBase1的value值:"<< d.CBase1::GetValue ( ) // 访问基类的同名成员 • <<"\nCBase2的value值:"<< d.CBase2::GetValue ( ) // 访问基类的同名成员 • <<"\nCDerived的value值:"<< d. GetValue ( ) // 访问派生类的同名成员 • <<"\nCBase1与CBase2的value值之和:"<< d.GetData( ) <<endl; • }
2. 多继承派生类对象的初始化 • 多继承派生类对象的初始化与单继承类似,利用成员初始化值语法显式调用所有直接基类的构造函数对派生类中的基类成员进行初始化,这些构造函数之间用逗号隔开。