一、多态与虚函数(virtual)
先理清楚下边三句话:
1.函数重载是处理同一层次上的同名函数问题,而虚函数处理的是不同派生层次上的同名函数问题。(覆盖)
2.同名隐藏则是基类与派生类同名函数的问题。
3.在基类与派生类中,只要不构成重写就是重定义!!!
虚函数功能:实现多态。在类的继承层次结构中,不同层次可以出现名字相同、参数个数相同、类型相同但是功能不同的函数。
通过指针或引用来访问基类和派生类中的同名函数。
使用方法:
(1)在基类中用virtual声明成员函数为虚函数。在类外定义时不必再加此关键字
(2)派生类中重新定义此函数,函数名、函数类型、函数参数个数和类型必须与基类虚函数相同(当一个成员函数被声明为虚函数时,派生类中同名函数都自动成为虚函数,virtual可加可不加)
(3)如果在派生类中没有对基类的虚函数重新定义,则派生类简单的继承其直接基类的虚函数。
注意:一个成员函数被声明为虚函数后,在同一类族中的类就不能再定义一个非virtual的但与该虚函数具有相同的参数和函数返回值类型的同名函数
什么情况下应当声明虚函数??
基类成员函数在类的继承后有无可能被更改功能,如果希望他更改,则声明为虚函数。
对成员函数的调用是通过对象名还是通过基类指针或引用去访问的,如果是基类指针或引用,则声明为虚函数
某些情况下,定义虚函数时,不定义函数体,它的作用只是定义了一个虚函数名,具体功能留给派生类去添加。
使用虚函数,系统要有一定的空间开销。当一个类有虚函数时,编译系统会为该类构造一个虚函数表,它是一个指针数组,存放每个虚函数的入口地址。
二.虚表剖析:
对于有虚函数的类,编译器都会维护一张虚表,对象的前四个字节就是指向虚表的指针
class CText
{
public:
/*virtual*/ void DH()
{}
private:
int iText;
};
加关键字 virtual 大小为8字节,不加为4字节
回避虚函数的机制:
在某些情况下,我们可能对虚函数的调用不要进行动态绑定,而是强迫其执行虚函数的某个特定版本,使用作用域运算符可以实现这一目的。那么,我们什么时候要使用此版本呢?
当一个派生类的虚函数调用他的覆盖基类虚函数时,基类版本通常完成继承层次中所有类型都要做的共同任务,派生类中的要执行与派生类有关的操作!
三、含虚函数的类在继承关系下,对象内存剖析及虚表的内部结构全解
先来搞清楚带虚函数类的对象在内存中的分布
class B
{
public:
B()
{
dataB = 1;
}
virtual void fun1()
{
cout << "B=funtest1" << endl;
}
private:
int dataB;
};
单继承:如果有虚函数表,那么只有一个虚函数表,并且按照虚函数声明的顺序顺序排列,派生类的虚函数紧接着基类的虚函数排列
class B
{
public:
B()
{
dataB = 1;
}
virtual void fun1()
{
cout << "B=fun1" << endl;
}
virtual void fun2()
{
cout << "B=fun2" << endl;
}
private:
int dataB;
};
class C1:public B
{
public:
C1()
{
dataC1 = 2;
}
virtual void fun1()//重写基类虚函数
{
cout << "C1=fun1" << endl;
}
virtual void fun3()//派生类自己的虚函数
{
cout << "C1=fun3" << endl;
}
void fun4()
{
cout << "C1=fun4()" << endl;
}
private:
int dataC1;
};
从上图得知派生类对象继承了基类对象虚表,并且如果基类对象虚函数在派生类中有重写,那么派生类会改写成自己的虚函数
但是我们并没有在c1的虚表内看到它自己的虚函数fun(3),这是因为编译器不显示,其实派生类自己的虚函数紧跟在基类虚函数的后边。
证明它我们可以通过打印出它的虚函数直观的看见。
多继承:
class Base1//基类
{
public:
virtual void fun1()
{
cout << "Base1:fun1()" << endl;
}
virtual void fun2()
{
cout << "Base1:fun2()" << endl;
}
private:
int b1;
};
class Base2//基类
{
public:
virtual void fun3()
{
cout << "Base3:fun3()" << endl;
}
virtual void fun2()
{
cout << "Base4:fun4()" << endl;
}
private:
int b2;
};
class Derive:public Base1,public Base2//派生类
{
public:
virtual void fun1()
{
cout << "Derive:fun1()" << endl;
}
virtual void fun5()
{
cout << "Derive:fun5()" << endl;
}
private:
int c;
};
由图清晰可知,派生类维护了两张虚表,并且其自己的虚函数跟在第一张虚表的后边。因此我们可得如下结论:
多重继承会有多个虚函数表,几重继承,就会有几个虚函数表。这些表按照派生的顺序依次排列,如果子类改写了父类的虚函数
,那么就会用子类自己的虚函数覆盖虚函数表的相应的位置,如果子类有新的虚函数,那么就添加到第一个虚函数表的末尾。
菱形非虚拟继承:
class Base//基类
{
public:
virtual void fun1()
{
cout << "Base1:fun1()" << endl;
}
virtual void fun2()
{
cout << "Base1:fun2()" << endl;
}
private:
int b;
};
class Base1:public Base
{
public:
virtual void fun1()//重写基类Base虚函数
{
cout << "Base1:fun1()" << endl;
}
virtual void fun3()
{
cout << "Base1:fun3()" << endl;
}
private:
int b1;
};
class Base2 :public Base
{
public:
virtual void fun1()//重写基类Base虚函数
{
cout << "Base2:fun1()" << endl;
}
virtual void fun4()
{
cout << "Base2:fun4()" << endl;
}
private:
int b2;
};
class Derive:public Base1,public Base2//派生类
{
public:
virtual void fun1()//重写基类Base1虚函数
{
cout << "Derive:fun1()" << endl;
}
virtual void fun5()
{
cout << "Derive:fun5()" << endl;
}
private:
int c;
};
此种情况其实是单继承与多继承的组合,派生类自己的虚函数依旧在Base1虚表后边。
带虚函数的虚拟继承:
class Base//基类
{
public:
virtual void fun1()
{
cout << "Base1:fun1()" << endl;
}
virtual void fun2()
{
cout << "Base1:fun2()" << endl;
}
private:
int b;
};
class Base1: virtual public Base
{
public:
virtual void fun1()//重写基类Base虚函数
{
cout << "Base1:fun1()" << endl;
}
virtual void fun3()
{
cout << "Base1:fun3()" << endl;
}
private:
int b1;
};
我们可以很清楚的看到,对象的存储方式与普通继承有不同。这里,派生类Base1所占字节数为20
菱形虚拟继承:
class Base//基类
{
public:
virtual void fun1()
{
cout << "Base1:fun1()" << endl;
}
virtual void fun2()
{
cout << "Base1:fun2()" << endl;
}
private:
int b;
};
class Base1: virtual public Base
{
public:
virtual void fun1()//重写基类Base虚函数
{
cout << "Base1:fun1()" << endl;
}
virtual void fun3()
{
cout << "Base1:fun3()" << endl;
}
private:
int b1;
};
class Base2 :virtual public Base
{
public:
virtual void fun1()//重写基类Base虚函数
{
cout << "Base2:fun1()" << endl;
}
virtual void fun4()
{
cout << "Base2:fun4()" << endl;
}
private:
int b2;
};
class Derive:public Base1,public Base2//派生类
{
public:
virtual void fun1()//重写基类Base1虚函数
{
cout << "Derive:fun1()" << endl;
}
virtual void fun5()
{
cout << "Derive:fun5()" << endl;
}
private:
int c;
};
内存分布如下:
虚拟继承虽然消除了二义性,但是开销是增加虚函数指针。