从多态实现的时间来看,可分为编译时多态(静态多态)和运行时多态(动态多态)
静态多态:函数重载,运算符重载,泛型编程等
动态多态:通过虚函数实现。调用规则:动态绑定(使用基类的指针或引用调用虚函数时,根据指针或引用所绑定的对象的类型不同来确定调用基类还是派生类函数)
静态多态:
class A
{
public:
void Funtest()
{
cout << "A::Funtest()" << endl;
}
};
class B:public A
{
public:
void Funtest()
{
cout << "B::Funtest()" << endl;
}
};
class C:public B
{
public:
void Funtest()
{
cout << "C::Funtest()" << endl;
}
};
int main()
{
A a, *pa;//定义基类对象a,指向基类的指针pa
B b;
C c;
pa = &a;
pa->Funtest();
pa = &b;
pa->Funtest();//期望调用B的函数,但是调用了A的
pa = &c;
pa->Funtest(); //期望调用C的函数,但是调用了A的
pa = &a;
cout << "pa" << pa << "a address is " << &a << endl;
pa = &b;
cout << "pa" << pa << "b address is " << &b<< endl;
}
//指针的值分别与a,b,c,的地址一致,指针也分别指向了A,B,C,但由于静态连编,指针没有绑定到派生类的成员函数上,依然只绑定在了基类成员函数上
动态联编:
先来了解一下虚函数:在基类中通过关键字virtual声明,并在一个或多个派生类中被重新定义的成员函数
virtual 返回值类型 函数名 (参数表)
{
函数体;
}
虚函数的内存分以及调用规则:
class A
{
public:
A()
:pub(1)
{}
virtual void Funtest() //虚函数
{
cout << "A::Funtest()" << endl;
}
private:
int pub;
};
class B:public A
{
public:
B()
:pro(2)
{}
virtual void Funtest() //重写
{
cout << "B::Funtest()" << endl;
}
private:
int pro;
};
class C:public B
{
public:
C()
:pri(3)
{}
virtual void Funtest()//重写
{
cout << "C::Funtest()" << endl;
}
private:
int pri;
};
void fun(A& s) //通过基类对象的引用调用虚函数
{
s.Funtest();
}
int main()
{
A a, *pa;//定义基类对象a,指向基类的指针pa
B b;
C c;
pa = &a; //通过对象的指针调用虚函数
pa->Funtest();
pa = &b;
pa->Funtest();//期望调用B的函数,但是调用了A的
pa = &c;
pa->Funtest(); //期望调用C的函数,但是调用了A的
pa = &a;
cout << "pa" << pa << "a address is " << &a << endl;
pa = &b;
cout << "pa" << pa << "b address is " << &b<< endl;
fun(a);//传入对象
fun(b);
cout << sizeof(A) << endl;
cout << sizeof(B) << endl;
cout << sizeof(C) << endl;
}
已知基类只有一个成员pub 为什么内存大小是8呢?这就是因为存在虚函数引起的
虚函数在虚表的顺序:
- 首先与基类中虚函数声明顺序相同
2.当派生类中有重写基类中的虚函数时,会替换基类中相同偏移量的虚函数
3.派生类自身虚函数会填充在基类虚函数后面
多继承时:
则我们可以得出:创建派生类对象时,先构造基类虚表,再构造基类对象,然后创建派生类虚表(看是否覆盖基类虚函数),再去构建对象。
构造函数是否可以写为虚函数?
答:不可以,虚函数对应存储在对象的内存空间。如果构造函数是虚的,就需要通过 虚表指针来调用,可是对象还没有实例化,也就是内存空间还没有,无法找到虚表指针,所以构造函数不能是虚函数。虚函数时运行时多态的基础,主要是针对对象的。只有对象创建成功,才会去调用虚表。
还有就是假如重写为虚函数,在创建对象时,不知道该调用基类的还是派生类的
静态成员函数是否可以重写为虚函数?
答:不可以。因为静态成员函数调用时不会隐式传入this指针。
为什么基类析构函数要写成虚函数?
答:基类指针可以指向派生类的对象(多态性),如果删除该指针,就会调用该指针指向的派生类析构函数,而派生类的析构函数又自动调用基类的析构函数,这样整个派生类的对象完全被释放。如果析构函数不被声明成虚函数,则编译器实施静态绑定,在删除基类指针时,只会调用基类的析构函数而不调用派生类析构函数,这样就会造成派生类对象析构不完全。所以,将析构函数声明为虚函数是十分必要的。
class A
{
public:
A()
:pub(1)
{}
virtual void Funtest()
{
cout << "A::Funtest()" << endl;
}
virtual ~A() //虚析构函数
{
cout << "~A" << endl;
}
private:
int pub;
};
class B:public A
{
public:
B()
:pro(2)
{}
virtual void Funtest()
{
cout << "B::Funtest()" << endl;
}
~B()
{
cout << "~B" << endl;
}
private:
int pro;
};
int main()
{
A *p = new B; //通过基类的指针或引用
delete p;
}
当基类析构函数定义为虚函数时:
当基类析构函数不为虚函数数:(没有形成动态绑定,释放不完整,可能存在内存泄露)