读者注意:阅读这篇文章时,对继承中的对象模型要有一定了解;因为本篇文章有的例子没有给出类的成员变量,如果不了解继承的对象模型,琢磨起来可能会很慢,觉得自己不确定的话单击下面的“继承”。
多态
多态按字面的意思就是“多种状态”。在面向对象语言中,接口的多种不同的实现方式即为多态。直白点理解就是不同的对象收到相同的消息时,产生不同的动作。(随风倒)
多态可以分为静态多态和动态多态。
静态多态:是在编译器期间完成的,也叫早期绑定;
动态多态:在程序执行期间(非编译期)判断所引用对象的实际类型,根据其实际类型调用相应的方法,也叫动态绑定。
动态多态的构成条件:要有虚函数,并且一定要重写,一个在基类,一个在派生类。(虚函数的函数原型、返回值、函数名、参数列表都必须一样),必须通过基类类型的引用或者指针调用虚函数。
举个简单的例子:
class A
{
public:
A(int x)//构造函数
{
a = x;
}
virtual void print()//虚函数
{
cout << "A::" << a << endl;
}
private:
int a;
};
class B :public A
{
public:
B(int x, int y)
:A(x)
{
b = y;
}
virtual void print()//重写
{
cout << "B::" << b << endl;
}
private:
int b;
};
void test()
{
A a(10);
B b(15,20);
A *c = &a;
c->print();
c = &b;
c->print();
}
有一个例外:协变;返回值类型不同(基类里面返回基类的引用或指针,派生类里面返回派生类的引用或指针)
重载&同名隐藏&重写三者的区别
重载:重载只能是类的成员函数,必须在同一个作用域内(也就是同一个类里),成员函数的名字必须相同,参数列表(个数,顺序,类型)可以不同。
同名隐藏:只要成员(可以使成员变量也可以是成员函数)的名字一样(一个在基类,一个在派生类),和参数列表、返回值没有关系,并且要用派生类的对象来调用同名成员。
重写:有虚函数,并且一个在基类,一个在派生类,函数的原型相同(协变除外)
哪些成员函数可以作为虚函数,可不可采用?哪些不可以作为虚函数?
1.构造函数(拷贝构造)
不可以,因为有了构造函数才能构造出对象,对象中才能有虚表;而虚函数先调用虚表,在找到虚表中的构造函数;两者前后矛盾。
2.内联函数
不可以,原因其实很简单,那内联函数就是为了在代码中直接展开,减少函数调用花费的代价,虚函数是为了在继承后对象能够准确的执行自己的动作,这是不可能统一的。(再说了,inline函数在编译时被展开,虚函数在运行时才能动态的邦定函数)
3.静态成员函数
不可以,因为静态成员函数是全局的,对于所有的类来说,是一份公共的代码,它不归某个具体对象所有,所以他也没有要动态邦定的必要性。
4.友元函数
不可以,因为C++不支持友元函数的继承,对于没有继承特性的函数没有虚函数的说法。
5.赋值运算符重载
可以,但是不采用,虽然可以将operator=定义为虚函数,但最好不要这么做,使用时容易混淆。
6.析构函数
可以,而且最好将基类的析构函数声明为虚函数。因为析构函数比较特殊,因为派生类的析构函数跟基类的析构函数名称不一样,但是构成覆盖,这里编译器做了特殊处理,减少了空间开销。
继承
在上一篇博客中我们提到了继承的对象模型,这一次我们依然谈到继承的对象模型,只不过这次在类种新增了虚函数。说到虚函数,我们先剖析一下,举个简单的例子:
class A
{
public:
virtual void Funtest1()
{
cout << "A::Funtest1" << endl;
}
virtual void Funtest2()
{
cout << "A::Funtest2" << endl;
}
virtual void Funtest3()
{
cout << "A::Funtest3" << endl;
}
virtual void Funtest4()
{
cout << "A::Funtest4" << endl;
}
};
typedef void(*PVTF)();
void PrintVpt()
{
A a;
PVTF* pVtf = (PVTF*)(*(int*)&a);
while (*pVtf)
{
(*pVtf)();
pVtf++;
}
}
void test()
{
cout << sizeof(A) << endl;
PrintVpt();
}
也就是说在一个类里定义了虚函数,那么创建实例化对象之后,编译器会创建一张虚表,对象的前四个字节存放虚表的地址,虚表里存放各个虚函数的地址。
单继承
第一种情况:派生类中没有重写虚函数,也没有自己的虚函数,则直接继承基类的虚函数,派生类会重新生成一张虚表,基类里的虚表不会继承下来。(派生类中的虚表地址和基类的虚表地址不一样,虚表里地址也不一样,但是虚函数是一样的)
class A
{
public:
virtual void Funtest1()
{
cout << "A::Funtest1" << endl;
}
virtual void Funtest2()
{
cout << "A::Funtest2" << endl;
}
virtual void Funtest3()
{
cout << "A::Funtest3" << endl;
}
virtual void Funtest4()
{
cout << "A::Funtest4" << endl;
}
};
class B :public A
{
};
typedef void(*PVTF)();//函数指针
void PrintVpt()//打印虚表
{
B b;
PVTF* pVtf = (PVTF*)(*(int*)&b);
while (*pVtf)
{
(*pVtf)();
pVtf++;
}
}
void test()
{
cout << sizeof(A) << endl;
cout << sizeof(B) << endl;
PrintVpt();
}
第二种情况:派生类中重写了基类中的某个虚函数并且还有自己的虚函数。
如果在派生类中重写了基类里的虚函数,用派生类重写后的虚函数代替基类里的虚函数。
如果派生类定义了新的虚函数,按照声明顺序放在基类虚函数的后面。
class A
{
public:
virtual void Funtest1()
{
cout << "A::Funtest1" << endl;
}
virtual void Funtest2()
{
cout << "A::Funtest2" << endl;
}
virtual void Funtest3()
{
cout << "A::Funtest3" << endl;
}
virtual void Funtest4()
{
cout << "A::Funtest4" << endl;
}
};
class B :public A
{
public:
virtual void Funtest2()
{
cout << "B::Funtest2" << endl;
}
virtual void Funtest5()
{
cout << "B::Funtest5" << endl;
}
};
typedef void(*PVTF)();//函数指针
void PrintVpt()//打印虚表
{
B b;
PVTF* pVtf = (PVTF*)(*(int*)&b);
while (*pVtf)
{
(*pVtf)();
pVtf++;
}
}
void test()
{
cout << sizeof(A) << endl;
cout << sizeof(B) << endl;
PrintVpt();
}
多继承
在这里我把所有的可能性混合到一块:就是说派生类中对基类A、B的虚函数都进行了重写,并且还拥有自己的虚函数。
如果派生类中对基类A、B中的虚函数进行了重写,分别替换基类A、B中的虚函数;
如果派生类中有自己的虚函数,按照声明顺序放在第一个基类(也就是A类)虚表中虚函数的后面。
多继承中派生类不会重新生成表,而是直接将基类A、B的虚表直接继承下来。
class A
{
public:
virtual void Funtest1()
{
cout << "A::Funtest1" << endl;
}
};
class B
{
public:
virtual void Funtest2()
{
cout << "B::Funtest2" << endl;
}
};
class C :public A, public B
{
public:
virtual void Funtest1()
{
cout << "C::Funtest1" << endl;
}
virtual void Funtest2()
{
cout << "c::Funtest2" << endl;
}
virtual void Funtest4()
{
cout << "C::Funtest4" << endl;
}
};
typedef void(*PVTF)();//函数指针
void PrintVpt()//打印虚表
{
C c;
PVTF* pVtf = (PVTF*)(*(int*)&c);
while (*pVtf)
{
(*pVtf)();
pVtf++;
}
A a;
B &b = c;
pVtf = (PVTF*)*(int*)&b;
while (*pVtf)
{
(*pVtf)();
pVtf++;
}
}
void test()
{
cout << sizeof(A) << endl;
cout << sizeof(B) << endl;
cout << sizeof(C) << endl;
PrintVpt();
}
菱形继承(钻石继承)
这里我列举的这种情况是派生类中有自己的虚函数,继承时没有重写虚函数,这样举例是为了更能直观的看到它的对象模型。因为一旦有重写,在对象模型中你会分不清它到底是派生类的还是基类的。
包含虚函数的菱形继承,派生类的对象模型其实是这样的,本例中没有给出类中成员变量,加上成员变量(好理解点的话就理解为功能)的话,模型是这样的:
B1的虚表中包含了最初的基类A,以及派生类C的虚函数,B2的虚表中包含了最初的基类A的虚函数。同样的,如果派生类中对基类B1、B2中的虚函数进行了重写,分别替换基类B1、B2虚表中的虚函数,同理B1、B2中对A类的虚函数进行了重写,也会替换。
class A
{
public:
virtual void Funtest1()
{
cout << "A::Funtest1" << endl;
}
};
class B1:public A
{
public:
virtual void Funtest2()
{
cout << "B1::Funtest2" << endl;
}
};
class B2 :public A
{
public:
virtual void Funtest3()
{
cout << "B2::Funtest3" << endl;
}
};
class C :public B1,public B2
{
public:
virtual void Funtest4()
{
cout << "C::Funtest4" << endl;
}
};
typedef void(*PVTF)();//函数指针
void PrintVpt()//打印虚表
{
C c;
PVTF* pVtf = (PVTF*)(*(int*)&c);
while (*pVtf)
{
(*pVtf)();
pVtf++;
}
B2 &b = c;
pVtf = (PVTF*)*(int*)&b;
while (*pVtf)
{
(*pVtf)();
pVtf++;
}
}
void test()
{
cout << sizeof(B1) << endl;
cout << sizeof(B2) << endl;
cout << sizeof(C) << endl;
PrintVpt();
}
虚拟继承
单继承(虚拟继承)
对象模型应该是这样的:
class A
{
public:
virtual void Funtest1()
{
cout << "A::Funtest1" << endl;
}
};
class B :virtual public A
{
public:
virtual void Funtest2()
{
cout << "B::Funtest2" << endl;
}
virtual void Funtest1()
{
cout << "B::Funtest1" << endl;
}
};
typedef void(*PVTF)();//函数指针
void PrintVpt()//打印虚表
{
B b;
PVTF* pVtf = (PVTF*)(*(int*)&b);
while (*pVtf)
{
(*pVtf)();
pVtf++;
}
A &a = b;
pVtf = (PVTF*)*((int*)&a);
while (*pVtf)
{
(*pVtf)();
pVtf++;
}
}
void test()
{
cout << sizeof(A) << endl;
cout << sizeof(B) << endl;
PrintVpt();
}
菱形虚拟继承
class A
{
public:
virtual void Funtest1()
{
cout << "A::Funtest1" << endl;
}
int _a;
};
class B1 :virtual public A
{
public:
virtual void Funtest2()
{
cout << "B1::Funtest2" << endl;
}
int _b1;
};
class B2 :virtual public A
{
public:
virtual void Funtest3()
{
cout << "B2::Funtest3" << endl;
}
virtual void Funtest1()
{
cout << "B2::Funtest1" << endl;
}
int _b2;
};
class C :public B1, public B2
{
public:
virtual void Funtest3()
{
cout << "C::Funtest3" << endl;
}
virtual void Funtest4()
{
cout << "C::Funtest4" << endl;
}
int _c;
};
typedef void(*PVTF)();
void PrintVft()
{
C c;//创建派生类对象d
c._a = 1;
c._b1 = 2;
c._b2 = 3;
c._c = 4;
PVTF* pVtf= (PVTF*)*(int*)&c;
while (*pVtf)
{
(*pVtf)();
pVtf++;
}
cout << endl << endl;
pVtf = (PVTF*)*((int*)&c + 3);
while (*pVtf)
{
(*pVtf)();
pVtf++;
}
cout << endl << endl;
pVtf = (PVTF*)*((int*)&c + 7);
while (*pVtf)
{
(*pVtf)();
pVtf++;
}
}
void test()
{
cout << sizeof(C) << endl << endl;
PrintVft();
}
对象模型:
在这里总结一下:从包含虚函数的菱形虚拟继承中我们可以看出来,它其实和不包含虚函数的菱形虚拟继承对象结构差不了多少,只是派生类中的虚函数要写到第一个基类里面,在原有的偏移量表格地址上把虚表也继承过来了。