第13讲 多态 友员函数 多态性与虚函数 纯虚函数和抽象类
学习目标 掌握友元函数的定义和使用 理解多态的概念、多态性的分类 理解虚函数作用、什么情况下声明虚函数 理解纯虚函数、抽象类、虚析构函数的作用, 并掌握其使用 理解掌握动态多态性的实现
第12讲 多态 友员函数 多态性与虚函数 纯虚函数和抽象类
友元函数 类中私有和保护的成员在类外不能被访问。 友元函数是一种定义在类外或类中的普通函数, 其特点是能够访问类中私有成员和保护成员,即 类的访问权限的限制对其不起作用。 友元函数不是成员函数,用法也与普通的函数完 全一致,只不过它能访问类中所有的数据。友元 函数破坏了类的封装性和隐蔽性,使得非成员函 数可以访问类的私有成员。 一个类的友元可以自由地用该类中的所有成员。
友元函数需要在类体内进行说明,在前面加上关键字friend。 一般格式为: friend <type> FuncName(<args>); 函数名 friend float Volume(A &a); 函数参数 返回值类型 关键字 第一章 VC集成开发环境
友元函数近似于普通的函数,它不带有this指针,因此必须将对象名或对象的引用作为友元函数的参数,这样才能访问到对象的成员。 有关友元函数的使用,说明如下: 友元函数不是类的成员函数 友元函数近似于普通的函数,它不带有this指针,因此必须将对象名或对象的引用作为友元函数的参数,这样才能访问到对象的成员。 第一章 VC集成开发环境
A(float a, float b){ x=a; y=b;} float Sum(){ return x+y; } class A{ float x,y; public: A(float a, float b){ x=a; y=b;} float Sum(){ return x+y; } friend float Sum(A &a){ return a.x+a.y; } }; void main(void) { A t1(4,5),t2(10,20); cout<<t1.Sum()<<endl; cout<<Sum(t2)<<endl; } 友元函数只能用对象名引用类中的数据。 成员函数 友元函数 私有数据 成员函数的调用,利用对象名调用 友元函数的调用,直接调用 第一章 VC集成开发环境
友元函数与一般函数的不同点 友元函数必须在类的定义中说明,其函数体 可在类内定义,也可在类外定义; 友元函数必须在类的定义中说明,其函数体 可在类内定义,也可在类外定义; 它可以访问该类中的所有成员(公有的、私 有的和保护的),而一般函数只能访问类中 的公有成员。
A(float a, float b){ x=a; y=b;} float Getx(){ return x; } class A{ float x,y; public: A(float a, float b){ x=a; y=b;} float Getx(){ return x; } float Gety(){ return y; } float Sum(){ return x+y; } friend float Sum(A &); }; float Sumxy(A &a){ return a.Getx()+a.Gety(); } float Sum(A &a){ return a.x+a.y; } void main(void) { A t1(1,2),t2(10,20), t3(100,200); cout<<t1.Sum()<<endl; cout<<Sum(t2)<<endl; cout<<Sumxy(t3)<<endl; } 成员函数 友元函数 普通函数,必须通过公有函数访问私有成员 友元函数,可以直接调用类中私有成员 对象调用成员函数 调用友元函数 调用一般函数
友元函数 友元函数不受类中访问权限关键字的限制,可 以把它放在类的私有部分,放在类的公有部分 或放在类的保护部分,其作用都是一样的。换 言之,在类中对友元函数指定访问权限是不起 作用的。 友元函数的作用域与一般函数的作用域相同。 谨慎使用友元函数 通常使用友元函数来取对象中的数据成员值, 而不修改对象中的成员值,则肯定是安全的。
友元函数 为了增强类与类之间的沟通,可以将A类中的 某个成员函数是B类中的友元函数,这个成员 函数可以直接访问B类中的私有数据。这就实 现了类与类之间的沟通。 class A{ ... void fun( B &); }; class B{ ... friend void A::fun( B &); }; 既是类A的成员函数 又是类B的友元函数
友元函数 注意:一个类的成员函数作为另一个类的友元 函数时,应先定义友元函数所在的类。
友元函数 类A中的成员函数fun()是类B的友元函数。即在fun()中可以直接引用类B的私有成员。 class B ; //先定义类A,则首先对类B作引用性说明 class A{ ...... //类A的成员定义 public: void fun( B & );//函数的原型说明 }; class B{ ...... friend void A::fun( B & );//定义友元函数 }; void A::fun ( B &b) //函数的完整定义 { ...... //函数体的定义 } 类A中的成员函数fun()是类B的友元函数。即在fun()中可以直接引用类B的私有成员。
类A中有一个函数可以直接引用类B的私有成员 class B; //必须在此进行引用性说明, class A{ float x,y; public: A(float a, float b){ x=a; y=b;} float Sum(B &); //说明友元函数的函数原型,是类A的一成员函数 }; class B{ float m,n; B(float a,float b){ m=a;n=b; } friend float A::Sum(B &);//说明类A的成员函数是类B的友元函数 } float A::Sum( B &b) //定义该友元函数 { x=b.m+b.n; y=b.m-b.n; } void main(void) { A a1(3,5); B b1(10,20); a1.Sum(b1); //调用该函数,因是类A的成员函数,故用类A的对象调用 类A中有一个函数可以直接引用类B的私有成员 a1.x=30 a1.y=-10 直接引用类B的私有成员 第一章 VC集成开发环境
友元类 class A{ class B{ ..... ..... friend class B; } } 对于类B而言,类A是透明的 类B必须通过类A的对象使用类A的成员
类B中的任何函数都能使用类A中的所有私有成员。 const float PI =3.1415926; class A{ float r ; float h; public: A(float a,float b){r=a; h=b;} float Getr(){return r;} float Geth(){return h;} friend class B;//定义类B为类A的友元 }; class B { int number; public: B(int n=1) {number=n;} void Show(A &a) { cout<<PI*a.r*a.r*a.h*number<<endl; }//求类A的某个对象*n的体积 void main(void) { A a1(25,40),a2(10,40); B b1(2); b1.Show (a1); b1.Show (a2); } 类B中的任何函数都能使用类A中的所有私有成员。 直接引用类A的私有成员 第一章 VC集成开发环境
友元函数在继承中的应用 不管是按哪一种方式派生,基类的私有成员在派生类中都是不可见的。 如果在一个派生类中要访问基类中的私有成员,可以将这个派生类声明为基类的友元。 class Base { friend class Derive; ..... } class Derive { ..... } 直接使用Base中的私有成员
#include<iostream.h> class M { friend class N; //N为M的友元,可以直接使用M中的私有成员 private: int i , j; void show(void){cout<<"i="<<i<<'\t'<<"j="<<j<<'\t';} public: M(int a=0,int b=0){ i=a; j=b;} }; class N :public M{ //N为M的派生类 public: N(int a=0,int b=0):M(a,b){ } void Print(void){ show(); cout<<"i+j="<<i+j<<endl; } void main(void) { N n1(10,20); M m1(100,200); // m1.show(); //私有成员函数,在类外不可调用 n1.Print(); } 直接引用类M的私有成员函数和私有成员
第12讲 多态 友员函数 多态性与虚函数 纯虚函数和抽象类
多态 多态性也是面向对象程序设计的重要特征之一, 多态性是指发出同样的消息被不同类型的对象 接收时导致完全不同的行为。例如,动物都有 吃的行为,而羊和狼吃的方式和内容都不一样。
多态性:调用同一个函数名,可以根据需要实现不同的功能。 虚函数 多态性是面向对象的程序设计的关键技术。 多态性:调用同一个函数名,可以根据需要实现不同的功能。 编译时的多态性(函数重载) 运行时的多态性(虚函数) 多态性 运行时的多态性是指在程序执行之前,根据函数名和参数无法确定应该调用哪一个函数,必须在程序的执行过程中,根据具体的执行情况来动态地确定
静态联编和动态联编 联编分类 联编(或绑定) 是指程序之间的匹配、连接的关联过程,即编译系统在 决定运行某一程序时,根据标识符、参数等信息能确定 调用执行哪段程序代码的一个关联过程。 联编分类 静态联编:程序之间的匹配、连接工作在编译阶段,即 程序运行之前完成。实现方式——函数重载、运算符重 载 动态联编:编译程序在编译阶段不能确定被调用的函数, 只有在程序运行阶段才能确定。实现方式——虚函数
虚函数 若要访问派生类中相同名字的函数,必须将基类中 的同名函数定义为虚函数。 虚函数是被virtual关键字修饰的成员函数。 若要访问派生类中相同名字的函数,必须将基类中 的同名函数定义为虚函数。 虚函数是被virtual关键字修饰的成员函数。 虚函数的作用,用专业术语来解释就是实现多态性 (Polymorphism),即将接口与实现进行分离。 将不同的派生类对象的地址赋给基类的指针变量后 ,就可以动态地根据这种赋值语句调用不同类中的 函数。
basep ->Show(); basep=&d; 需要将基类中的Show()说明为虚函数 basep Base *basep,b; Derive d; basep ->Show(); 即指向派生类新增的成员函数 basep=&b; basep=&d; 需要将基类中的Show()说明为虚函数 派生类对象 基类对象 basep basep x Show() x Show() Base b; Derive d; y Show() basep->Show()
虚函数 虚函数是用关键字virtual修饰的某基类中的 protected或public成员函数。它可以在派生 类中重新定义,以形成不同版本。只有在 程序的执行过程中,依据指针具体指向哪 个类对象,或依据引用哪个类对象,才能 确定激活哪一个版本,实现动态聚束。
virtual <type> FuncName(<ArgList>); 虚函数的定义和使用 可以在程序运行时通过调用相同的函数名而实现不同功能的函数称为虚函数。定义格式为: virtual <type> FuncName(<ArgList>); 一旦把基类的成员函数定义为虚函数,由基类所派生出来的所有派生类中,该函数均保持虚函数的特性。 在派生类中重新定义基类中的虚函数时,可以不用关键字virtual来修饰这个成员函数 。 第一章 VC集成开发环境
输出:92.7011 class Point{ float x,y; public: Point(){ } Point(float i,float j){ x=i; y=j; } virtual float area(void) { return 0.0; } }; const float Pi=3.14159; class Circle:public Point{ //类Point的派生类 float radius; public: Circle(float r){ radius=r; } float area(void) { return Pi*radius*radius;} void main(void) { Point *pp; //基类指针,可以将派生类对象的地址赋给基类指针 Circle c(5.4321); pp=&c; cout<<pp->area ()<<endl; //调用虚函数 } 输出:92.7011 声明为虚函数 虚函数再定义 将area()声明为虚函数,编译器对 其进行动态聚束,按照实际对象c 调用了Circle中的函数area()。使 Point类中的area()与Circle类中的 area()有一个统一的接口。 调用虚函数
class A{ protected: int x; public: A(){x =1000;} virtual void print(){ cout <<“x=”<<x<<‘\t’; }//虚函数 }; class B:public A{ int y; public: B() { y=2000;} void print(){ cout <<“y=”<<y<<‘\t’; }//派生虚函数 class C:public A{ int z; public: C(){z=3000;} void print(){ cout <<“z=”<<z<<‘\n’; }//派生虚函数 void main(void ) { A a, *pa; B b; C c; a.print(); b.print(); c.print(); //静态调用 pa=&a; pa->print();//调用类A的虚函数 pa=&b; pa->print();//调用类B的虚函数 pa=&c; pa->print();}//调用类C的虚函数
两个Set()函数参数不一致,是重载,不是虚函数 class Base { public : virtual int Set(int a, int b) { ..... } .... }; class Derive:public Base{ public : int Set(int x, int y) { ..... } ..... }; int Set(int ,int )是虚函数 class Base { public : virtual int Set(int a, int b) { ..... } .... }; class Derive:public Base{ public : int Set(int x, int y=0) { ..... } ..... }; 两个Set()函数参数不一致,是重载,不是虚函数
虚函数的几点说明 当在基类中把成员函数定义为虚函数后,在其 派生类中定义的虚函数必须与基类中的虚函数 同名,参数的类型、顺序、参数的个数必须一 一对应,函数的返回的类型也相同。 若函数名相同,但参数的个数不同或者参数的 类型不同时,则属于函数的重载,而不是虚函 数。 若函数名不同,显然这是不同的成员函数。
虚函数的几点说明 类B与类C均为类A的公有派生。 实现这种动态的多态性时,必须使用基类类型的 指针变量,并使该指针指向不同的派生类对象, 并通过调用指针所指向的虚函数才能实现动态的 多态性。 类A 类B 类C x Show() x Show() x Show() y Show() z Show() Show()定义为虚函数 类B与类C均为类A的公有派生。 即在程序运行时,通过赋值语句实现多态性 A *p; B b; C c; p=&b ; p->Show(); p=&c ; p->Show();
虚函数的几点说明 虚函数必须是类的一个成员函数,不能是友 元函数,也不能是静态的成员函数。 虚函数必须是类的一个成员函数,不能是友 元函数,也不能是静态的成员函数。 在派生类中没有重新定义虚函数时,与一般 的成员函数一样,当调用这种派生类对象的 虚函数时,则调用其基类中的虚函数。 可把析构函数定义为虚函数,但是,不能将 构造函数定义为虚函数。
虚函数的几点说明 虚函数与一般的成员函数相比较,调用时的执行 速度要慢一些。为了实现多态性,在每一个派生 类中均要保存相应虚函数的入口地址表,函数的 调用机制也是间接实现的。因此,除了要编写一 些通用的程序,并一定要使用虚函数才能完成其 功能要求外,通常不必使用虚函数。 一个函数如果被定义成虚函数,则不管经历多少 次派生,仍将保持其虚特性,以实现“一个接口 ,多个形态”。
第12讲 多态 友员函数 多态性与虚函数 纯虚函数和抽象类
纯虚函数和抽象类 在C++中的类族中,为了提供一个统一的访问 接口,往往会提供一个具有抽象意义的基类。 Shape类 Point类 Circle类 Point类 Cylinder类 Shape类
纯虚函数和抽象类 该抽象基类应具有的特点: 1、描述该类族所有派生类的共性成员。 2、不需要建立对象,只用于派生。 3、为了实现动态联编,同样需要定义虚函数。
virtual 返回类型 函数名(参数列表) =0; 纯虚函数和抽象类 纯虚函数 (一)作用 在基类中为其所有派生类提供一个公共接口,便于派生类 根据需要重新定义。 (二)定义格式 virtual 返回类型 函数名(参数列表) =0; (三)与虚函数的区别 纯虚函数没有函数体,且所属的类不能定义对象。 虚函数允许派生类中不再进行重新定义,此时调用的是基类 中的虚函数。纯虚函数若要调用必须在派生类中重新定义。
y=2000 z=3000 class A{ 抽象类 protected: int x; public: A(){x =1000;} virtual void print()=0; //定义纯虚函数 }; class B:public A{ //派生类 private: int y; public: B(){ y=2000;} void print(){cout <<“y=”<<y<<‘\n’;}//重新定义纯虚函数 class C:public A{ //派生类 int z; public: C(){z=3000;} void print(){cout <<“z=”<<z<<‘\n’;}//重新定义纯虚函数 void main(void ) { A *pa; B b; C c; pa=&b; pa->print(); pa=&c; pa->print(); A a; pa=&a; pa->print( ); } 抽象类 y=2000 z=3000 不能定义抽象类的对象
纯虚函数 注意事项 在定义纯虚函数时,不能定义虚函数的实现部分。 纯虚函数没有函数体。 最后面的“=0”并不表示函数返回值。 只是在声明纯虚函数,不是定义,所以后面必须要 有“;”。 不能被调用。只有在派生类中重新定义后才具备函 数功能。
抽象类与具体类 包含一个或多个纯虚函数的类称为抽象类。 特点 不能说明抽象类的对象,只能用于继承; 只有当派生类所有继承的纯虚函数都被定义时,这 样的派生类才产生对象。 作用 作为一个类族的共同基类,提供公共接口。 纯虚函数的作用是作为派生类中的成员函数的基础, 并实现动态多态性。
第12讲 多态 友员函数 多态性与虚函数 纯虚函数和抽象类
作业: 在下列程序的基础上,派生出一个新类Sector,运用改 写后的虚函数,实现扇形面积的计算。 class Point{ float x,y; public: Point(){ } Point(float i,float j){ x=i; y=j; } virtual float area(void) { return 0.0; } }; const float Pi=3.14159; class Circle:public Point{ float radius; public: Circle(float r) { radius=r; } float area(void) { return Pi*radius*radius;} void main(void) { Point *pp; Circle c(5.4321); pp=&c; cout<<pp->area ()<<endl; }