继承与多态
*一、继承的基本概念*
人类:姓名、年龄、吃饭
学生是人:学号、学习
教师是人:工资、讲课
人类 - 基类,共性
/ \ 派生V^继承
学生 教师 - 子类,个性
*二、继承的语法*
class 子类名 : 继承方式1 基类1, 继承方式2 基类2, … {
…
};
继承方式:
公有继承 - public - 最常用方式
私有继承 - private - 缺省方式
保护继承 - protected - 特殊的私有继承
*三、公有继承*
1.通过继承,在基类中定义的任何成员,也都成为了子类的成员,但是基类的私有成员,子类虽然拥有却不能直接访问。
2.基类中的保护成员,可以被子类直接访问,但不能在无关的类和全局域中被访问。
3.任何一个子类对象中都包含着它的基类子对象。如果在子类的构造函数中没有明确指明其基类子对象如何被构造,系统将采用无参的方式构造该子对象。如果在初始化表中指明了基类子对象的构造方式,就调用相应的构造函数构造该子对象。
4.子类对象的构造和析构顺序
按照继承表的顺序依次构造每个基类子对象->按照声明的顺序依次构造每个成员变量->执行子类构造函数体中的代码
析构的过程与构造严格相反
5.一个子类对象在任何都可以被视为它的基类对象——IsA。
任何时候,一个子类对象的指针或者引用,都可以被隐式地转换为它的基类类型的指针或者引用,但是反过来,将基类类型的指针或者引用转换为它的子类类型必须显示地(static_cast)完成。
`Student s (...);`
`Human* h = &s; // OK !`
6.在子类中定义的任何和基类成员同名的标识符,都可以将基类中的该成员隐藏起来。通过作用域限定操作符“::”,可对该成员解隐藏。
*
**四、继承方式对访控属性的影响*
`class A {`
`X:`
`void foo (void) { ... }`
`};`
`class B : Y A {`
`void bar (void) {`
`foo (); // 仅需考虑X`
`}`
`};`
`int main (void) {`
`B b (...);`
`b.foo(); // 不仅要考虑X还要考虑Y`
`}`
`class C : Z B {`
`void fun (void) {`
`foo(); // 不仅要考虑X还要考虑Y`
`}`
`};`
当通过一个子类对象(B)访问它从基类(A)中继承过来的成员(foo)的时候,需要考虑子类(B)从基类(A)继承时所采用的继承方式。
*1.访控属性*
关键字 属性 基类 子类 外部 友员
public 公有 OK OK OK OK
protected 保护 OK OK NO OK
private 私有 OK NO NO OK
*2.基类的成员被继承到子类中以后,其访控属性会因不同的继承方式而异。*
基类 公有继承 保护继承 私有继承
公有 公有 保护 私有
保护 保护 保护 私有
私有 私有 私有 私有
*
**五、子类的构造函数和析构函数*
*1.子类隐式地调用基类的构造函数*
在子类的构造函数中没有显示地指明其基类部分如何构造,隐式地调用基类的无参构造函数。如果子类没有定义任何构造函数,其缺省无参构造函数同样会隐式地调用基类的无参构造函数。
*2.子类显式地调用基类的构造函数*
在子类构造函数的初始化表中指明其基类部分的构造方式。
`class A {`
`public:`
`A (void) : m_data (0) {}`
`A (int data) : m_data (data) {}`
`private:`
`int m_data;`
`};`
`class B : public A {`
`public:`
`B (int data) : A (data) {}`
`};`
`class A { ... };`
`class B : public A { ... };`
`class C : public B { ... };`
`C c (...);`
构造:A->B->C
析构:C->B->A****3.继承链的构造和初始化顺序****
任何时候子类中基类子对象的构造都要先于子类构造函数中的代码。
4.delete一个指向****子类对象*的基类指针,实际被执行的*基类的析构函数****,基类的析构函数不会调用子类析构函数,因此子类所特有的资源将形成内存泄漏。
`Human* p = new Student (...);`
`delete p; // ->Human::~Human()`
`delete static_cast<Student*> (p);`
*
**六、子类的拷贝构造和拷贝赋值*
子类的缺省拷贝构造和拷贝赋值除了复制子类的特有部分以外,还会复制其基类部分。如果需要自己定义子类的拷贝构造和拷贝赋值,一定不要忘记在复制子类特有部分的同时,也要复制其基类部分,否则将无法得到完整意义上的对象副本。
七、私有继承和保护继承
用于防止或者限制基类中的公有接口被从子类中扩散。
`class DCT {`
`public:`
`void codec (void) { ... }`
`};`
`class Jpeg : protected DCT { ***\*//只有自己类内可以用\****`
`public:`
`void render (void) {`
`codec (...);`
`}`
`};`
`Jpeg jpeg;`
`jpeg.codec (...); ***\*// ERROR !防止公有接口被从子类中扩散\****`
``
`***\*Jpeg Has A DCT,实现继承\****`
``
`class Jpeg2000 : public Jpeg {`
`public:`
`void render (void) {`
`codec (...); ***\*// OK ,code在Jpeg类中是保护型的,而通过公有继承,可以访问\****`
`}`
`};`
*例子:*
\#include <iostream>
using namespace std;
class Human {
public:
Human (const string& name, int age) :
m_name (name), m_age (age) {} ***\*//构造函数\****
void who (void) const {
cout << m_name << "," << m_age << endl;
}
void eat (const string& food) const {
cout << "我在吃" << food << endl;
}
protected:
string m_name;
int m_age;
};
class Student : ***\*public Human\**** {
public:
Student (const string& name, int age, int no) :
Human (name, age), m_no (no) {} ***\*//正确的构造函数创建Student类的对象的同时先创建基类Huamn类,所以会调用Human类的构造函数,这里指定Human所调用的构造函数与所写的构造函数相匹配,所以不会出错。\****
***\*/\*\****
***\*Student (const string& name, int age, int no) :\****
***\*m_no (no) {\****
***\*m_name=name;\****
***\*m_age=age;\****
***\*}\****
***\**/ 错误的构造函数会报错,原因是这里调用Human的构造函数时没有指定方式,默认使用无参构造,但是在基类Human类中没有无参构造,所以会报错\****
Student (const Student& that) :
Human (that), m_no (that.m_no) {} ***\*//拷贝构造,显式的指明了调用基类的拷贝构造函数\****
Student& operator= (const Student& that) { ***\*//操作符重载\****
if (&that != this) {
Human::operator= (that);***\*//显式的调用基类的拷贝赋值\****
m_no = that.m_no;
}
return *this;
}
void learn (const string& lesson) const {
cout << "我(" << m_name << "," << m_age
<< "," << m_no << ")在学" << lesson
<< endl;
}
using Human::eat;***\*//如果没有这句,则Human的eat与这里的eat作用域不再一起,则下面的eat不会与Human中的eat构成重载,而是构成隐藏关系\****
***\*//但是有了这句之后,将Human的eat在这里可见,既作用域也被声明在这里,则两个eat构成了重载关系\****
void eat (void) const {
cout << "我绝食!" << endl;
}
***\*//\**** ***\*int eat;\****
private:
int m_no;
};
int main (void) {
Student s1 ("张飞", 25, 1001);
s1.who ();
s1.eat ("包子");
s1.learn ("C++");
Human* h1 = &s1;***\*//子类的指针可以隐式转换为基类的指针,因为访问范围缩小了,是安全的\****
h1 -> who ();
h1 -> eat ("KFC");
***\*//\**** ***\*h1 -> learn ("C");//基类的指针或对象不可以访问子类中的成员\****
Student* ps = static_cast<Student*> (h1);***\*//基类的指针不可以隐式的转换为子类的指针,因为访问范围扩大,不安全,所以必须显式的进行转换,但是这样有风险\****
ps -> learn ("C");
Student s2 = s1;
s2.who (); ***\*//子类的指针或对象可以方位基类的成员\****
s2.learn ("英语");
Student s3 ("赵云", 20, 1002);
s3 = s2;
s3.who ();
s3.learn ("数学");
return 0;
}
*
**七、私有继承和保护继承*
*用于防止或者限制基类中的公有接口被从子类中扩散。*
***\*class DCT {\****
***\*public:\****
***\*void codec (void) { ... }\****
***\*};\****
***\*class Jpeg : protected DCT {\****
***\*public:\****
***\*void render (void) {\****
***\*codec (...);\****
***\*}\****
***\*};\****
***\*Jpeg jpeg;\****
***\*jpeg.codec (...); // ERROR !\****
***\*//Jpeg Has A DCT,实现继承\****
***\*class Jpeg2000 : public Jpeg {\****
***\*public:\****
***\*void render (void) {\****
***\*codec (...); // OK !\****
***\*}\****
***\*};\*******\*
*****八、多重继承*
从多于一个基类中派生子类。
电话 媒体播放器 计算机
\ | /
智能手机
****1.****多重继承的语法和语义与单继承并没有本质的区别,只是子类对象中包含了更多的基类子对象。它们在内存中按照继承表的先后顺序从低地址到高地址依次排列。
****2.****子类对象的指针可以被隐式地转换为任何一个基类类型的指针。无论是隐式转换,还是静态转换,编译器都能保证特定类型的基类指针指向相应类型基类子对象。但是重解释类型转换,无法保证这一点。
*3.尽量防止名字冲突。*
*例子:智能手机*
\
#include <iostream>
using namespace std;
class Phone { ***\*//基类1\****
public:
Phone (const string& numb) : m_numb (numb) {}
void call (const string& numb) {
cout << m_numb << "致电" << numb << endl;
}
void foo (void) {
cout << "Phone::foo" << endl;
}
private:
string m_numb;
};
class Player { ***\*//基类2\****
public:
Player (const string& media) : m_media (media){}
void play (const string& clip) {
cout << m_media << "播放器播放" << clip
<< endl;
}
void foo (int data) {
cout << "Player::foo" << endl;
}
private:
string m_media;
};
class Computer { ***\*//基类3\****
public:
Computer (const string& os) : m_os (os) {}
void run (const string& prog) {
cout << "在" << m_os << "上运行" << prog
<< endl;
}
private:
string m_os;
};
class SmartPhone : public Phone, public Player,
public Computer { ***\*//多重继承\****
public:
SmartPhone (const string& numb,
const string& media, const string& os) :
Phone (numb), Player (media),
Computer (os) {}
using Phone::foo; ***\*//将Phone中的foo函数的作用域声明到这里\****
using Player::foo; ***\*//将Player中的foo函数的作用域声明到这里\****
***\*//这样就构成了重载\****
};
int main (void) {
SmartPhone sp ("13910110072", "MP3", "Android");
sp.call ("01062332018");
sp.play ("High歌");
sp.run ("愤怒的小鸟");
Phone* p1 = reinterpret_cast<Phone*> (&sp);
Player* p2 = reinterpret_cast<Player*> (&sp);
Computer* p3 = reinterpret_cast<Computer*>(&sp);
cout << &sp << ' '<< p1 << ' ' << p2 << ' '
<< p3 << endl; ***\*//地址都相同,但如果不用reinterpret的话,用隐式或者静态转换,p1 p2 p3将sp地址段一分为三,所以p1 p2 p3 地址会不同\****
sp.foo ();
sp.foo (100);
return 0;
}
---------------------------------------------------------------------
*4.钻石继承和虚继承*
*1)钻石继承*
A
/ \
B C
\ /
D
class A { ... };
class B : public A { ... };
class C : public A { ... };
class D : public B,public C{ ...};
在最终子类(D)对象中存在公共基类(A)子对象的多份实例,因此沿着不同的继承路径访问公共基类子对象中的成员,会发生数据不一致的问题。
*2)虚继承*
在继承表中通过virtual关键字指定从公共基类中虚继承,这样就可以保证在最终子类对象中,仅存在一份公共基类子对象的实例,避免沿着不同的继承路径访问公共基类子对象中的成员时,所引发的数据不一致的问题。
只有当所创建对象的类型回溯(su)中存在钻石结构时,虚继承才起作用,否则编译器会直接忽略virtual关键字。
*例子:钻石继承和虚继承*
\
#include <iostream>
using namespace std;
***\*class A\**** { ***\*//公共基类\****
public:
A (int i) : m_i (i) {}
protected:
int m_i;
};
***\*class B\**** : ***\*virtual\**** public A {
public:
B (int i) : A (i) {}
void set (int i) {
m_i = i;
}
};
***\*class C\**** : ***\*virtual\**** public A { ***\*//virtual是虚继承\****
public:
C (int i) : A (i) {}
int get (void) {
return m_i;
}
};
***\*class D\**** : public B, public C {
public:
D (int i) : B (i), C (i), ***\*A (i)\**** {}//真正起作用的是A(i),不用B(i),C(i),但要写
};
int main (void) {
D d (1000);//B里的m_i与C里的m_i都存的是2000
cout << d.get () << endl; ***\*// 1000,调用C中的get,返回C中的m_i的值\****
d.set (2000);***\*//调用B类中的set,给B中的m_i赋值\****
cout << d.get () << endl; ***\*// 输出为2000,---如果B,C没有virtual调用C中的get,D的初始化表中没有A(i),返回C中的m_i的值,则会输出1000.----因为有了,所以制定从公共基类中虚继承,所以在最终子对象中只有一份公共基类子对象的实例\****
***\*//B b(3000); //B创建的对象没有钻石结构,所以写了virtual也不起作用,依然拥有A的基类子对象\****
return 0;
}
------------------------------------------------------------------------------
*九、虚函数与多态*
如果将基类中的一个成员函数声明为虚函数,那么子类中的同型函数就也成为虚函数,并且对基类版本形成覆盖。这时,通过一个指向子类对象的基类指针,或者一个引用子类对象的基类引用,调用该虚函数时,实际被调用的函数不由该指针或引用的类型决定,而由它们的目标对象决定,最终导致子类中覆盖版本被执行。这种现象称为多态。
*图形:位置,绘制*
**/ **
*矩形:宽和高 圆:半径*
*绘制 绘制*
*例子:图形绘制*
\
#include <iostream>
using namespace std;
class Shape {
public:
Shape (int x, int y) : m_x (x), m_y (y) {}
virtual void draw (void) {
cout << "形状(" << m_x << ',' << m_y << ')'
<< endl;
}
protected:
int m_x, m_y;
};
class Rect : public Shape { ***\*//矩形\****
public:
Rect (int x, int y, int w, int h) :
Shape (x, y), m_w (w), m_h (h) {}
void draw (void) { ***\*//隐藏shape中的draw,构成隐藏关系\****
cout << "矩形(" << m_x << ',' << m_y << ','
<< m_w << ',' << m_h << ')' << endl;
}
private:
int m_w, m_h;
};
class Circle : public Shape { ***\*//圆形\****
public:
Circle (int x, int y, int r) :
Shape (x, y), m_r (r) {}
void draw (void) {
cout << "圆形(" << m_x << ',' << m_y << ','
<< m_r << ')' << endl;
}
private:
int m_r;
};
void render (Shape* shapes[]) {
for (size_t i = 0; shapes[i]; ++i)***\*//挨个解析\****
shapes[i]->draw ();}
***\*//因为在shape中的draw()有virtual修饰为虚函数,而另外两个子类中的同名draw也变为虚函数,覆盖了基类shape中的draw\****
***\*且调用时,有指针的指向的目标类型决定执行哪一个函数,真正执行的是覆盖版本的draw\****
***\*所以就通过指针调用各自的draw(),这样就可以用各自的绘制方法画出图形;\*******\*(有了virtual修饰,则是按照指针指向的对象来找draw)\****
***\*但是如果没有在基类shape中的draw()没有用virtual修饰,则shape类型的指针会访问shape类中的draw,则全部会用基类shape中的draw绘制图形\*******\*(是根据指针类型来找draw)\****
int main (void) {
Shape* shapes[1024] = {}; ***\*//定义的基类类型的指针数组,这样就可以指向不同子类类型的对象\****
shapes[0] = new Rect (1, 2, 3, 4);
shapes[1] = new Circle (5, 6, 7);
shapes[2] = new Circle (8, 9, 10);
shapes[3] = new Rect (11, 12, 13, 14);
shapes[4] = new Rect (15, 16, 17, 18);
render (shapes);
return 0;
}
*十、函数覆盖的条件*
overload - 重载
override - 覆盖、重写、改写
1.基类版本必须是虚函数。
2.函数名、形参表和常属性必须严格一致。
3.如果返回基本类型或者对象,那么也必须严格一致。如果返回类类型的指针或引用,那么子类版本也可以返回基类版本的子类。
class B : public A { … };
基类:virtual A* foo (void) {…}
子类:A* foo (void) { … }
B* foo (void) { … }
4.子类的覆盖版本不能比基类版本声明更多的异常抛出。
5.子类覆盖版本的访控属性与基类无关。
class A {
public:
virtual void foo (void) { ... }
};
class B : public A {
private:
void foo (void) { ... }
};
int main (void) {
B* b = new B;
b->foo (); ***\*// ERROR !foo在B中是私有的\****
A* a = new B;
a->foo (); ***\*// OK ! -> B::foo 访控属性是看指针类型的,在A中,foo 是公共部分的,所以可以访问,但真正执行的是覆盖版本的B中的foo。\****
}
*十一、多态=虚函数+指针/引用*
Rect rect (...);
Shape shape = rect;***\*//shape只能代表shape代表不了rect。\****
shape->draw (); ***\*// Shape::draw\****
Shape& shape = rect;
shape->draw (); ***\*// Rect::draw\****
***\*-------------------------------------------------\****
class A {
public:
A (void) {
bar (); ***\*// A::bar //构造函数中调用虚函数,永远没有多态型,构造A的时候,B还没有构造好,没法调用B中的尚未构造好的覆盖版本。\****
}
~A (void) {
bar (); ***\*// A::bar //析构函数中调用虚函数,永远没有多态性,因为析构的顺序和构造相反,当执行基类中的析构函数时,子类已经析构后释放完了,无法调用析构后的覆盖版本\****
}
void foo (void) {
This->bar (); ***\*// B::bar\****
}
virtual void bar (void) {
cout << 'A' << endl;
}
};
class B : public A {
void bar (void) {
cout << 'B' << endl;
}
};
int main (void) {
B b; ***\*// A\****
b.foo (); ***\*// B 因为foo函数是A类中的成员函数,所以this指针是A类型的,这个this指针指向B类型的对象b,调用那个虚函数的覆盖版本看指针指向的目标对象,所以调用B中的bar\****
return 0;
}
*十二、纯虚函数、抽象类、纯抽象类*
形如:
virtrual 返回类型 成员函数名 (形参表) = 0;
的虚函数被称为纯虚函数。
一个包含了纯虚函数类称为抽象类,抽象类不能实例化为对象。
如果一个类继承自抽象类,但是并没有为其抽象基类中的全部纯虚函数提供覆盖,那么该子类就也是一个抽象类。
class A { ***\*// 纯抽象类\****
virtual void foo (void) = 0;
virtual void bar (void) = 0;
virtual void fun (void) = 0;
};
class B : public A { // 抽象类
void foo (void) { ... }
};
class C : public B { // 抽象类
void bar (void) { ... }
};
class D : public C { // 具体类
void fun (void) { ... }
};
除了构造和析构函数以外,所有的成员函数都是纯虚函数的类称为纯抽象类。
*例子:*
\
#include <iostream>
using namespace std;
class Shape {
public:
Shape (int x, int y) : m_x (x), m_y (y) {}
virtual void draw (void) = 0; ***\*//空函数,纯虚函数,因为有了这个纯虚函数,所以shape为抽象类。所以shape不能实例化,不能创建对象,如果该类中除了构造和析构函数之外,都是纯虚函数,则该类为纯抽象类,同样不能实例化。\****
protected:
int m_x, m_y;
};
class Rect : public Shape {
public:
Rect (int x, int y, int w, int h) :
Shape (x, y), m_w (w), m_h (h) {}
void draw (void) {
cout << "矩形(" << m_x << ',' << m_y << ','
<< m_w << ',' << m_h << ')' << endl;
}
***\*// int draw (void){} //不构成任何合法关系\****
***\*//\**** ***\*int draw (void) const {} //会隐藏,因为形参不同(这里的this是const类型)\****
***\*//\**** ***\*int draw (int){} //隐藏\****
private:
int m_w, m_h;
};
class Circle : public Shape {
public:
Circle (int x, int y, int r) :
Shape (x, y), m_r (r) {}
void draw (void) {
cout << "圆形(" << m_x << ',' << m_y << ','
<< m_r << ')' << endl;
}
private:
int m_r;
};
void render (Shape* shapes[]) {
for (size_t i = 0; shapes[i]; ++i)
shapes[i]->draw ();
}
int main (void) {
Shape* shapes[1024] = {};
shapes[0] = new Rect (1, 2, 3, 4);
shapes[1] = new Circle (5, 6, 7);
shapes[2] = new Circle (8, 9, 10);
shapes[3] = new Rect (11, 12, 13, 14);
shapes[4] = new Rect (15, 16, 17, 18);
render (shapes);
***\*//\**** ***\*Shape shape (1, 2);\****
return 0;
}
*十三、动态绑定(后期绑定、运行时绑定)*
1.虚函数表
class A {
public:
virtual void foo (void) { ... }
virtual void bar (void) { ... }
};
class B : public A {
public:
void foo (void) { ... }
};
A* pa = new A;
pa->foo (); // A::foo
pa->bar (); // A::bar
\---------------------
A* pa = new B;
pa->foo (); // B::foo
pa->bar (); // A::bar
2.动态绑定
当编译器看到通过指向子类对象的基类指针或者引用子类对象的基类引用,调用基类中的虚函数时,并不急于生成函数调用代码,相反会在该函数调用出生成若干条指令,这些指令在程序的运行阶段被执行,完成如下动作:
1)根据指针或引用的目标对象找到相应虚函数表的指针;
2)根据虚函数表指针,找到虚函数的地址;
3)根据虚函数地址,指向虚函数代码。
由此可见,对虚函数的调用,只有运行阶段才能够确定,故谓之后期绑定或运行时绑定。
3.动态绑定对程序的性能会造成不利影响。如果不需要实现多态就不要使用虚函数。
*例子:*
\
#include <iostream>
using namespace std;
class A {
public:
virtual void foo (void) {
cout << "A::foo()" << endl;
}
virtual void bar (void) {
cout << "A::bar()" << endl;
}
};
class B : public A {
public:
void foo (void) { ***\*//覆盖\****
cout << "B::foo()" << endl;
}
};
int main (void) {
A a; //a
void (**vft) (void) = *(void (***) (void))&a; ***\*//使vft(二级指针)指向a的虚函数表,因为a是A类型的,所以强制转换为void(\*\*\*),虚函数表是一个函数指针数组,要指向他,应该用指向指针的指针--二级指针。\****
cout << (void*)vft[0] << ' '
<< (void*)vft[1] << endl; ***\*//A类中foo函数与bar函数的地址\****
vft[0] ();***\*//调用了A类的foo\****
vft[1] ();***\*//调用了A类的bar\****
B b;
vft = *(void (***) (void))&b;***\*//使vft指向B类的虚函数表\****
cout << (void*)vft[0] << ' '
<< (void*)vft[1] << endl;***\*//B类中的foo函数与A类中的bar函数地址\****
vft[0] ();***\*//调用了B类的foo函数\****
vft[1] ();***\*//调用了A类的bar函数\****
return 0;
}
*---------------------------------------------------*
*十四、运行时类型信息(RTTI)*
*1.typeid操作符*
A a;
typeid (a)返回typeinfo类型的对象的常引用。
typeinfo::name() - 以字符串的形式返回类型名称。
typeinfo::operator==() -类型一致
typeinfo::operator!=() -类型不一致
#include
*例子:*
\
#include <iostream>
\#include <typeinfo>
\#include <cstring>
using namespace std;
class A {
public:
virtual void foo (void) {}
};
class B : public A {};
void print (A* pa) {
***\*//\**** ***\*if (! strcmp (typeid (\*pa).name (), "1A"))\****
if (typeid (*pa) == typeid (A))
cout << "pa指向A对象!" << endl;
else
***\*//\**** ***\*if (! strcmp (typeid (\*pa).name (), "1B"))\****
if (typeid (*pa) == typeid (B))
cout << "pa指向B对象!" << endl;
}
int main (void) {
cout << typeid (int).name () << endl; ***\*//'i'\****
cout << typeid (unsigned int).name () << endl; ***\*//'j'\****
cout << typeid (double[10]).name () << endl; ***\*//A10_d\****
cout << typeid (char[3][4][5]).name () << endl; ***\*//A3_A4_A5_c\****
char* (*p[5]) (int*, short*); ***\*//函数指针数组\****
cout << typeid (p).name () << endl; ***\*//A5_PFPcPiPsE\****
cout << typeid (const char* const* const).name (//
) << endl; ***\*//PKPKc 指针指向一个常量,这个常量是个指针,这个指针指向一个常量,这个常量是char类型的。\****
cout << typeid (A).name () << endl;//1A
A* pa = new B;
cout << typeid (*pa).name () << endl;***\*//A中有虚函数,所以'1B’,如果A中没有虚函数,则是‘1A’。没有多态,则会按照指针本身的类型,有多态则会按照指针指向的目标对象类型。\****
print (new A);***\*//“pa指向A对象”\****
print (new B);***\*//“pa指向B对象”\****
}
--------------------------------------------------------
2.*dynamic_cast*
*例子:*
#include
*//动态类型转换*
using namespace std;
class A { virtual void foo (void) {} };
class B : public A {};
class C : public B {};
class D {};
int main (void) {
B b;
A* pa = &b;***\*//指向子类对象的基类指针。\****
cout << pa << endl; //地址
cout << "-------- dc --------" << endl;
***\*//动态类型转换,运行期间检查。\****
***\*// A是B的基类,pa指向B对象,成功\****
B* pb = dynamic_cast<B*> (pa);
cout << pb << endl; ***\*//地址\****
***\*// A不是C的基类,pa没有指向C对象,失败,安全\****
C* pc = dynamic_cast<C*> (pa);
cout << pc << endl; ***\*// 0\****
A& ra = b; ***\*//引用子类对象的基类引用。\****
try {
C& rc = dynamic_cast<C&> (ra);
}
catch (exception& ex) {
cout << "类型转换失败:" << ex.what ()
<< endl;
***\*// ...\****
}
***\*// pa没有指向D对象,失败,安全\****
D* pd = dynamic_cast<D*> (pa);
cout << pd << endl;
cout << "-------- sc --------" << endl;
***\*//静态类型转换,编译期间检查。\****
***\*// B是A的子类,成功\****
pb = static_cast<B*> (pa);
cout << pb << endl;
***\*// C是A的孙子类,成功,危险!\****
pc = static_cast<C*> (pa);
cout << pc << endl;
***\*// D不是A的后裔,失败,安全\****
***\*//\**** ***\*pd = static_cast<D\*> (pa); //两个方向都不能做隐式转换,所以任何方向也不能做静态转换。\****
***\*//\**** ***\*cout << pd << endl;\****
cout << "-------- rc --------" << endl;
***\*//重解释类型转换\****
***\*// 无论在编译期还是在运行期都不做检查,危险!\****
pb = reinterpret_cast<B*> (pa);
cout << pb << endl;
pc = reinterpret_cast<C*> (pa);
cout << pc << endl;
pd = reinterpret_cast<D*> (pa); cout << pd << endl; return 0; }
*十五、虚析构函数*
将基类的析构函数声明为虚函数,delete一个指向子类对象的基类指针,实际被执行的将是子类的析构函数,而子类的析构函数可以自动调用基类的析构函数,进而保证子类特有的资源,和基类子对象中的资源都能够得到释放,防止内存泄漏。
如果基类中存在虚函数,那么就有必要为其定义一个虚析构函数,即使该函数什么也不做。
*思考:*
1.虚函数可以内联吗? *不可以*
2.一个类的构造函数可以被定义为虚函数吗? *不可以(调用虚函数要用虚函数表,但调用构造函数时还没有创建好对象,没有虚函数表,自相矛盾)*
3.一个类的静态成员函数可以被定义为虚函数吗? *不可以*
4.一个类的成员函数形式的操作符函数可以被定义为虚函数吗? *可以*
5.一个全局函数可以被定义为虚函数吗? *不可以*
class PDFParser {
public:
void parse (const char* file) {
// 解析出一行文字
on_text (...);
// 解析出一幅图片
on_image (...);
// 解析出一张图形
on_graph (...);
}
private:
virtual void on_text (...) = 0;
virtual void on_image (...) = 0;
virtual void on_graph (...) = 0;
};
class PDFRender : public PDFParser {
private:
void on_text (...) { ... }
void on_image (...) { ... }
void on_graph (...) { ... }
};
PDFRender render (...);
render.parse ("test.pdf");
模板方法模式
MFC
*例子:*
\
#include <iostream>
using namespace std;
class A {
public:
A (void) {
cout << "A构造" << endl;
}
virtual ~A (void) { ***\*//析构函数只有一个,声明为virtual则会形成覆盖,这个叫做虚析构函数\****
cout << "A析构" << endl;
}
};
class B : public A {
public:
B (void) {
cout << "B构造" << endl;
}
~B (void) {
cout << "B析构" << endl;
}
};
int main (void) {
B* pb=new B; ***\*//先调用A再调用B的构造\****
delete pb; ***\*//调用B的析构,再自动调用A的析构\****
A* pa = new B; ***\*//指向子类的基类指针,先调用A的构造,再调用B的构造\****
delete pa; ***\*//因为基类中的析构函数是虚函数,调用B的析构,B的析构自动调用A的构析。如果基类中的析构函数没有被声明为虚函数,则会调用基类的析构,不会调用子类的析构,会出现泄露\****
return 0;
}