C++

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

16:24:30
25
虚函数的特性

派生类通过继承的成员函数调用虚函数时,也遵 守多态性的特点,访问派生类自己的版本。
class B{ public: void f(){ g(); } virtual void g(){ cout << "B::g"; } }; class D : public B{ public: void g(){ cout << "D::g"; } }; void main(){ D d; d.f();//基类继承而来的成员函数 } 16:24:30
class Manager:public Employee{ public: Manager(string Name,string id,float s=0.0):Employee(Name,id) { WeeklySalary=s; } void print(){cout<<"经理:"<<name<<"\t\t 编号: "<<id <<"\t\t 周工资: "<<WeeklySalary<<endl; } private: float WeeklySalary; //周薪 }; void main(){ Employee e("黄春秀","NO0009"),*pM; Manager m("刘大海","NO0001",128); m.print(); pM=&m; pM->print(); Employee &rM=m; rM.print(); }
虚函数的背后

每当创建一个包含虚函数的类或从包含虚函数的类派生一个类时, 编译器就为这个类创建一个唯一的vptr,指向自身的虚函数表 vtable(包含了所有的虚函数)
Base ::add地址 Base vptr
Base ::toString地址
int data; Base ::add地址 Derived vptr
面向对象程序设计——C++
第六章 多态性与虚函数 Chapter 6 Polymorphism and Virtual Function
16:24:30
1
第5章 多态性


执行程序后,程序中的 每个函数都有其地址, 即函数的入口地址, “多态”就是将函数名 称动态的绑定到函数入 口地址的一种机制。 比如各个CWnd的派生 类都可能有Close函数 用于关闭自身窗口,多 态性允许不必考虑窗口 类型,使用一个CWnd 类型的指针或引用便可 以关闭各种窗口
虚函数的特性

只有通过基类对象的指针和引用访问派生类对象 的虚函数时,才能体现虚函数的特性。
#include <iostream> using namespace std; class B{ public: virtual void f(){ cout << "B::f"<<endl; }; }; class D : public B{ public: void f(){ cout << "D::f"<<endl; }; }; 16:24:30
16:24:30
5
#include <iostream> #include <string> using namespace std; class Employee{ public: Employee(string name ,string id){ this->name=name; this->id=id; } void print(){//输出姓名和身份证号 cout<<"姓名: "<<name<<"\t\t 编号: "<<id<<endl; } protected: string name; string id; };
虚函数的意义



程序的运行结果如下: 经理:刘大海 编号: NO0001 姓名: 刘大海 编号: NO0001 姓名: 刘大海 编号: NO0001 输出的第2、3行表明,通过 基类对象的指针和引用只访 问到了在基类中定义的 print函数。
周工资: 128
16:24:30
8
虚函数的意义
16:24:30 2
编译期绑定和执行期绑定


普通的函数调用是在编译的时候就将绑定入口地 址。 而我们所说的多态性,则是在执行期间才能绑定 具体的函数入口地址。
普通函 数调用
void f(){ cout<<"Hello"<<endl; } int main(){ f(); }
16:24:30
3
没有虚函数的情况16:来自4:30 20虚函数的特性

如果基类定义了虚函数,当通过基类指针或引用调用 派生类对象时,将访问到它们实际所指对象中的虚函 数版本。把上例main的pA指针修改为pB,将会体现 虚函数的特征。
void main(){ A *pA,a; B *pB, b; C c; D d; // pB=&a; pB->f(1); //错误,派生类不能访问基类对象 pB=&b; pB->f(1); //调用B::f pB=&c; pB->f(1); //调用C::f pB=&d; pB->f(1); //调用D::f } 16:24:30 21
B::f
17
虚函数

虚函数版
D::f
class B{ public: virtual void f ( ) {cout << "B::f";}; }; class D : public B{ public: void f ( ) { cout << "D::f"; }; }; void main(){ D d; B * pb = & d; pb->f( ); }
16:24:30 19
虚函数的特性
class D: public C{ public: void f (int){cout<<"…D"<<endl;} }; void main(){ A *pA, a; B *pB, b; C c; D d; pA=&a; pA->f(1);//调用A::f pA=&b; pA->f(1);//调用A::f pA=&c; pA->f(1);//调用A::f pA=&d; pA->f(1);//调用A::f }
D::g
26
class B{ public: void f ( ) { cout << "bf "; }; virtual void vf ( ) { cout << "bvf "; }; void ff ( ) { vf(); f(); }; virtual void vff ( ) { vf(); f(); }; }; bf dvf bf dvf dvf bf class D: public B{ public: void f ( ) { cout << "df "; }; void ff ( ) { f(); vf(); }; void vf ( ) { cout << "dvf "; }; }; void main(){ D d; B * pB = &d; pB->f(); pB->ff(); pB->vf(); pB->vff(); }
将基类Employee的print指定为虚函数,如下形式: class Employee{ …… virtual void print(){ cout<<"姓名: "<<name<<"\t\t 编号: "<<Id<<endl; } }; 经理:刘大海 编号: NO0001 周工资: 128 结果: 经理:刘大海 编号: NO0001 周工资: 128 经理:刘大海 编号: NO0001 周工资: 128
… D d;B b, *pb = &d; pb->f( ); pb->g( );
16:24:30
16
虚函数

没有虚函数的情况
class B{ public: void f ( ) {cout << "B::f";}; }; class D : public B{ public: void f ( ) { cout << "D::f"; }; }; void main(){ D d; B * pb = & d; pb->f( );//f不是虚函数 } 16:24:30
派生类对象赋值给基类对象或指针时,发生了切割现象
16:24:30
4
举例

某公司有经理、销售员、小时工等多类人员。经 理按周计算薪金;销售员每月底薪800元,然后加 销售提成,每销售1件产品提取销售利润的5%; 小时工按小时计算薪金。每类人员都有姓名和身 份证号等数据。为简化问题,把各类人员的共有 信息抽象成基类Employee,其他人员则继承该类 的功能。
16:24:30
9
基类指针或引用指向派生类对象时,虚函数与 非虚函数的对象,左图为非虚函数,右图为虚 函数
16:24:30
10
多态性的前提条件


1、必须有类的层次结构 2、类中必须有同名成员函数,并且在基类中设定为虚函数 虚函数的定义形式
class x{ …… virtual f(参数表); …… }
16:24:30
总结:通过指向派生 类对象的基类指针访 问函数成员时, •非虚函数由指针类型 决定调用的版本 •虚函数由指针指向的 实际对象决定调用的 版本 18
虚函数的特性
一旦将某个成员函数声明为虚函数,它在继承体系中就
永远为虚函数
class A { public: void f(int i){cout<<“…A”<<endl;}; //f不是虚函数 }; class B: public A { public: virtual void f(int i){cout<<“…B”<<endl;}//这里才开始 }; class C: public B { public: void f(int i){cout<<“…C”<<endl;}//f仍然为虚函数,虽然没有virtual };

3、至少有一个基类的指针或者引用来调用虚函数 注意:虚函数只能用于成员函数,不能用于友元函数或者 外部函数。
16:24:30 11
class Base { int data; virtual int add(int i){return i;}; virtual string toString(){ return string(“Base”) }; }; class Derived: public Base { public: string toString()//与基类的函数同名 { return string(" Derived "); } }; //调用代码 Base* s = new Derived();基类的指针指向或引用派生类对象 cout<<s->toString()<<endl; delete s;
16:24:30
14
虚表vtable与虚表指针vptr
class B { int i, j, k; virtual f ( ) {}; virtual g ( ) {}; }; … B a, b, c;
16:24:30
15
class B { int i, j, k; virtual f ( ) {}; virtual g ( ) {}; }; class D: public B { int i, m; f ( ) {}; };
16:24:30
Derived ::toString地址
13
多态的实现


Derived因为重写了基类的虚函数toString, Derived的虚函数表记录了Derived的toString地 址。 当执行Base* s = new Derived(); s实际指向的是Derived类对象的内存区域,所以 调用方法的时候会根据这片内存记录的函数地址 进行调用,多态就是这么实现的。
22
虚函数的特性
void main(){ D d; B *pB = &d, &rB=d, b; b=d; b.f();//通过对象访问不能体现多态特性 pB->f(); rB.f(); } 运行结果如下:第1行输出没有体现虚函数特征 B::f D::f D::f
16:24:30 23
虚函数的特性


派生类中的虚函数要保持其虚特征,必须与基类虚函 数的函数原型完全相同,否则就是普通的重载函数, 与基类的虚函数无关。 基类B和派生类D都具有成员函数f ,但它们的参数类 型不同,因此不能体现函数f在派生类D中的虚函数特 性。
#include <iostream> using namespace std; class B{ public: virtual void f(int i){ cout << "B::f"<<endl; }; };
16:24:30 24
虚函数的特性
class D : public B{ public: int f(char c){ cout << "D::f..."<<c<<endl; } }; void main(){ D d; B *pB = &d, &rB=d, b; pB->f('1'); 本程序的运行结果如下: rB.f('1'); B::f } B::f
相关文档
最新文档