}
};
intmain(void)
{
Bb;
b.foo();
return0;
}
A:
bar
B:
bar
A:
bar
6)重载,隐藏,覆盖
classBase{
virtualvoidfoo(void);//1
virtualvoidfoo(void)const;//2
};
classDerived:
publicBase{
virtualvoidfoo(void);//3
virtualcharfoo(void)const;//4
};
1,2重载。
3,4重载
3隐藏2
3覆盖1
4隐藏1
4非法覆盖2
7).纯虚函数,抽象类和纯抽象类
形如
virtual返回类型函数名(形参表)[const]=0;
的虚函数,称为纯虚函数或者抽象方法。
表示一种抽象化的行为,为具体实现在子类的覆盖版本中体现,至少包含一个纯虚函数的类的称为抽象类,抽象类不能被实例化为对象。
一个抽象类的子类如果没有将基类中的纯虚函数完全覆盖,那么该子类就是抽象类。
如果一个抽象类除了构造和析构函数以外所有的非静态成员函数都是纯虚函数,那么该抽象类就是一个一个纯抽象类,也叫接口类。
classDetector{
public:
voiddectect(void){
采集温度数据;
发送温度数据;
sendemp(...);
采集空气质量数据;
sendair(...);
发送空气质量数据;
......
}
virtualvoidsendTemp(...)=0;
virtualvoidsendAir(...)=0;
....
};
classTCPNetwork:
publicDetector{
voidsendTemp(...){
通过tcp协议发送温度数据...
}
voidsendAir(...){
通过tcp协议发送空气质量数据...
}
};
TCPNetworktn;
tn.detevt();
解(除)耦(合)
模版方法模式
8)基于虚函数的多态机制————虚函数表和动态绑定
包含虚函数类
classB{
virtualintf1(void);
virtualvoidf2(int);
virtualintf3(int);
};
编译器会为每个包含虚函数的类生成一张虚函数表,即存放虚函数地址的函数指针数组,简称虚表(vtbl),每个虚函数对应一个虚函数表中的索引号
vtbl
+-------+-------+-------+
vptr->|B:
:
f1|B:
:
f2|B:
:
f3|
+-------+-------+-------+
012
除了为包含虚函数的类生成虚函数表以外,编译器还会为该类增加一个隐式的成员变量,通常放在该类实例化对象的起始位置,用于存放该类虚函数表的首地址,该变量被称为虚函数表指针,简称虚指针(vptr)
代码
B*pb=newB;
pb->f3(12);
被编译为
pb->vptr[2](pb,12);//B:
:
f3
虚表是一个类一张,而不是一个对象一张,同一个类的多个对象,通过各自的虚指针,指向同一张虚表
+-------+-------+-------+
|B:
:
f1|B:
:
f2|B:
:
f3|
+-------+-------+-------+
012
^^^
+---|--++---|--++---|--+
B:
|vptr||vptr||vptr|
|...||...||...|
+------++------++------+
继承自基类的子类
classD:
publicB{
intf1(void);
intf3(int);
virtualvoidf4(void);
};
子类覆盖了基类的f1和f3,继承了基类的f2,增加了自己的f4,编译器同样会为该子类生成一张专属于它的虚表
+-------+-------+-------+-------+
|D:
:
f1|B:
:
f2|D:
:
f3|D:
:
f4|
+-------+-------+-------+-------+
0123
指向子类虚表的虚指针就存放在子类对象的基类子对象中
+-------+-------+-------+-------+
+--->|D:
:
f1|B:
:
f2|D:
:
f3|D:
:
f4|
|+-------+-------+-------+-------+
|0123
+---|----+
D:
|+--|---+|
||vptr||
||...||
|+------+|
|...|
+--------+
代码
B*pb=newD;//指向子类对象的基类指针
pb->f3(12);
被编译为
pb->vptr[2](pb,12);//D:
:
f3
而这就是所谓的多态!
以上这样一种根据虚函数表中的虚函数地址调用虚函数的过程被称为动态绑定,以区别于由调用对象的类型定位函数的静态绑定。
作业:
设计一个实验验证上述理论的真实性。
#include
usingnamespacestd;
classA{
public:
A(void):
m_ch('A'){}
virtualvoidfoo(void){
cout<:
foo()"<}
virtualvoidbar(void){
cout<:
bar()"<}
private:
charm_ch;
};
classB:
publicA{
private:
charm_ch;
public:
B(void):
m_ch('B'){}
voidfoo(void){
cout<:
foo()"<}
};
intmain(void){
Aa;
void(**vptr_a)(A*)=*(void(***)(A*))&a;//foobar的虚地址
cout<<(void*)vptr_a<<"->"<<(void*)vptr_a[0]<<''<<(void*)vptr_a[1]<vptr_a[0](&a);
vptr_a[1](&a);
Bb;
void(**vptr_b)(B*)=*(void(***)(B*))&b;
cout<<(void*)vptr_b<<"->"<<(void*)vptr_b[0]<<''<<(void*)vptr_b[1]<vptr_b[0](&b);
vptr_b[1](&b);
return0;
}
0x8048b90->0x804899a0x80489dc
A:
:
foo()
A:
:
bar()
0x8048b80->0x8048a420x80489dc
B:
:
foo()
A:
:
bar()
基于虚函数表结构的动态绑定机制会为程序的性能带来一些负面影响:
增加一些额外的空间开销;
增加函数调用的时间开销;
妨碍编译器对函数内联优化。
因此只有在确实需要多态的场合才会使用虚函数。
9)运行时类型信息(RTTI,RunTimeTypeInformation)
a.typeid类型信息操作符
既可以作用于类型,也可作用于变量,返回保存了类型信息的对象(typeinfo)引用,typeinfo本身是一个类,声明在头文件中,提供成员函数name(),返回一个c风格字符串形式类型标签,同时它还提供了==和!
=操作符,用于对类型进行相等和不等的判断
当typeid作用于基类类型的指针或者引用时,如果是普通继承,那么该操作符获取的仅仅是指针或者引用本身的类型信息,如果是多态继承,则获取指针或者引用目标对象的类型信息
b.多态类型转换动态类型转换(dynamic_cast)
用于在多态继承中做向下造型,检查目标类型和实际对象的类型是否一致。
若一致则转换成功,否则失败。
如果所转换的是指针,通过返回空指针表示失败,如果所转换的是引用,则通过抛出bad_cast异常表示失败
a.
#include
#include
usingnamespacestd;
voidfoo(intf[5]){
cout<}
voidbar(int(&f)[5]){//数组的引用
cout<}
intmain(void){
doublea;
cout<charb;
cout<cout<double*c;
cout<<"---------"<cout<unsignedinte;
cout<intf[5];
cout<cout<foo(f);//首地址
bar(f);//数组整体
return0;
}
d
c
i
---------
Pd
j
A5_i
20
Pi
A5_i
b.
#include
usingnamespacestd;
classA{
virtualvoidfoo(void){}
};
classB:
publicA{};
classC:
publicB{};
classD{};
intmain(void){
Bb;
A*pa=&b;//向上造型
cout<<"pa="<B*pb=dynamic_cast(pa);//安全
cout<<"pb="<C*pc=dynamic_cast(pa);
cout<<"pc="<D*pd=dynamic_cast(pa);
cout<<"pd="<try{
A&ra=b;
C&rc=dynamic_cast(ra);
}
catch(exception&ex){
cout<<"类型转换失败"<}
cout<<"----------------"<pb=static_cast(pa);//一般安全
cout<<"pb="<pc=static_cast(pa);
cout<<"pc="<cout<<"--------------"<pb=reinterpret_cast(pa);
/*最不安全目标类型变量名=reinterpret_cast<目标类型>(源类型变量名)
功能:
主要用于任意两个指针类型/指针和整型之间的转换*/
cout<<"pb="<pc=reinterpret_cast(pa);
cout<<"pc="<pd=reinterpret_cast(pa);
cout<<"pd="<return0;
}
pa=0xbff2206c
pb=0xbff2206c
pc=0
pd=0
类型转换失败std:
:
bad_cast
----------------
pb=0xbff2206c
pc=0xbff2206c
--------------
pb=0xbff2206c
pc=0xbff2206c
pd=0xbff2206c
10)虚析构
如果将基类的析构函数声明为虚函数,那么当delete一个指向子类对象的基类指针时,实际被调用的将是子类的析构函数,该函数首先析构子类对象的扩展部分,然后再通过基类的析构函数析构子类对象中的基类子对象,最终实现完整资源释放
#include
usingnamespacestd;
classA{
private:
intm_data;
public:
A(intdata=0):
m_data(data){}
virtual~A(void){}//空虚析构,否则不执行B的析构
};
classB:
publicA{
private:
char*m_buf;
public:
B(void):
m_buf(newchar[1024]){}
~B(void){
cout<<"B析构"<delete[]m_buf;
}
};
intmain(void){
A*pa=newB;
deletepa;
return0;
}
B析构
为什么全局函数,静态成员函数和构造函数不能被声明为虚函数?
?
?
虚函数要用对象和指针,构造函数正在构造对象
这写函数的调用都不能依赖对象,因此也就与虚表指针无关,也就不能声明为虚函数
10.异常
1)在分析,设计,编码,测试阶段无法预见的各种意外情况
2)异常处理:
就是针对系统中各种可能发生的异常所提供的对应措施
3)传统的异常处理机制
a.通过返回表示成功或失败:
避免局部对象的内存泄露,逐层判断返回值。
流程繁琐
b.远程跳转