虚函数的声明和定义:
★ 虚函数的定义很简单,只要在成员函数原型前加一个关键字 virtual 即可。
★ 如果一个基类的成员函数定义为虚函数,那么,它在所有派生类中也保持为虚函数;即使在派生类中省略了virtual关键字,也仍然是虚函数。
★ 派生类要对虚函数进行中可根据需要重定义,重定义的格式有一定的要求:
——与基类的虚函数有相同的参数个数;
——与基类的虚函数有相同的参数类型;
——与基类的虚函数有相同的返回类型。
★ 若虚函数名称相同,参数不同?
虚函数和基类的虚函数不是完全相同,既不构成覆盖,又不构成重载,但是可以编译成功。C++认为派生类中的虚函数是重定义函数,是隐藏的,失去了虚特性。在j基类指针引用或对象调用的过程中始终调用的是基类的虚函数。
|
对于派生类而言,基类的虚函数被派生类的虚函数隐藏了,如果用派生类的对象直接去调用则只存在派生类的虚函数。对于通过多态(基类指针或引用指向派生类之后)去调用虚函数,则始终是调用的基类的虚函数,此时派生类的虚函数被隐藏了。也就是说:相对基类而言,派生类的虚函数被隐藏,相对派生类而言,基类的虚函数被隐藏。只跟调用的对象有关,跟指向的内容没关。
|
构造函数为什么不能为虚函数?
根据虚函数的性质,如果A的构造函数为虚函数,且B类也给出了构造函数,则应该只执行B类的构造函数,不再执行A类的构造函数。这样A就不能构造了。
|
为什么需要虚函数:
virtual void base::disp()
{
cout<<"hello,base"<<endl;
}
通过指针访问disp():
不加virtual时,具体调用哪个版本的disp()只取决于指针本身的类型,和指针所指对象的类型无关。
而加virtual时,具体调用哪个版本的disp()不再取决于指针本身的类型,而是取决于指针所指对象的类型。
#include <iostream>
using namespace std;
class Base
{
public:
virtual int func(int x)
{
cout<<"Base::func(int)"<<endl;
return x;
}
};
class Derived : public Base
{
public:
int func(int x)
//虚函数要求严格,必须和基类保持一致,即使在前面没有加上virtual,也是虚函数
{
cout<<"Derived::func(int)"<<endl;
return x;
}
};
void test(Base & base)
{ cout<<"x="<<base.func(5)<<endl; }
int main()
{
Base base;
Derived derived;
test(base); //执行的是Base::func
test(derived); //执行的是Derived::func
cout<<endl;
Base & ref = derived;
ref.func(7);
cout<<endl;
Base * pbase = &derived;
pbase->func(10);
//通过基类指针指向派生类对象之后,再去调用虚析构函数,只跟对象有关,而与指针的类型无关
return 0;
}
|
//指针访问
#include <iostream>
using namespace std;
class Base
{
public:
virtual void display()
{ cout<<"Base::display()"<<endl; }
private:
};
class Child : public Base
{
public:
void display()
{ cout<<"Child::display()"<<endl; }
};
int main()
{//虚函数发生的条件:
//1、基类的指针或者引用指向派生类对象
//2、派生类覆盖了基类的虚函数
//3、通过基类的指针或引用调用虚函数,发生动态多态
Base base;
Base * pb = &base;
pb->display();
Child child;
pb = &child;
pb->display();
cout<<"sizeof(Base)"<<sizeof(Base)<<endl;
cout<<"sizeof(Child)"<<sizeof(Child)<<endl;
cout<<endl;
child.display(); //直接去代码区找到display(),然后去执行,没有多态机制
cout<<endl;
Child * pc = &child;
pc->display(); //指针访问,多态
cout<<endl;
pc=(Child*)&base; //一般不会这样做
pc->display();
pc->Base::display(); //没有多态机制,静态联编
return 0;
}
|
总结:形成多态的条件:有继承关系并且派生类中对基类的virtual函数现实了覆盖,利用基类的引用或指针指向派生类对象,再用基类的对象去调用virtual函数的时候会实现多态
虚函数的访问:
对虚函数的访问方式不同,程序具体调用哪个函数可能也会有所不同
1、对象名访问
●和普通函数一样,虚函数一样可以通过对象名来调用,此时编译器采用的是静态联编。
●通过对象名访问虚函数时, 调用哪个类的函数取决于定义对象名的类型。对象类型是基类时,就调用基类的函数;对象类型是子类时,就调用子类的函数。如:
obj_base.disp(); //调用基类虚函数
obj_child.disp(); //调用子类虚函数
●在子类中还可以使用作用域运算符来指定调用哪个类的函数。如:
obj_child.base::disp(); //调用基类虚函数
obj_child.child::disp();//调用子类虚函数
2、指针访问
●使用指针访问非虚函数时,编译器根据指针本身的类型决定要调用哪个函数,而不是根据指针指向的对象类型;
●使用指针访问虚函数时,编译器根据指针所指对象的类型决定要调用哪个函数(动态联编),而与指针本身的类型无关。
●使用指针访问是虚函数调用的最主要形式。
3、引用访问
●使用引用访问虚函数,与使用指针访问虚函数类似
●不同的是,引用一经声明后,引用变量本身无论如何改变,其调用的函数就不会再改变,始终指向其开始定义时的函数。因此在使用上有一定限制,但这在一定程度上提高了代码的安全性,特别体现在函数参数传递等场合中,可以将引用理解成一种“受限制的指针”
#include <iostream>
using namespace std;
class Base
{
public:
virtual void display()
{ cout<<"Base::display()"<<endl; }
void print()
{ cout<<"Base::print()"<<endl; }
};
class Child : public Base
{
public:
void display()
{ cout<<"Child::display()"<<endl; }
void print()
{ cout<<"Child::print()"<<endl; }
};
int main()
{
Base base;
Base * pb = & base;
pb->print();
Base & ref1 = base;
ref1.display();
cout<<endl;
Child child;
pb = &child;
pb->print();
pb->display();
cout<<endl;
Base & ref2 = child;
ref2.print();
ref2.display();
Base base2;
ref2=base2; //引用一经声明,调用的函数就不会在改变,因为地址是不会改变的,虽然值改变了
ref2.display()
return 0;
}
|
#include <iostream>
using namespace std;
class Base
{
public:
virtual void display()
{ cout<<"Base::display()"<<endl; }
void print()
{ cout<<"Base::print()"<<endl; }
void callBase1() //在成员函数中访问,也会发生虚函数机制
{ this->display(); } //成员函数访问虚函数,要用this指针
void callBase2()
{ Base::display(); }
};
class Child : public Base
{
public:
void display()
{ cout<<"Child::display()"<<endl; }
void print()
{ cout<<"Child::print()"<<endl; }
void callChild1()
{ this->display(); }
void callChild2()
{ Base::display(); }
};
int main()
{
Base base;
Child child;
base.callBase1();
child.callChild1();
cout<<endl;
Base * pb = & base;
pb->callBase1();
pb->callBase2();
cout<<endl;
pb=&child;
pb->callBase1();
pb->callBase2();
return 0;
}
|
4、类成员函数中访问
●在类内的成员函数中访问该类层次中的虚函数,采用动态联编,要使用this指针。
5、在构造函数或析构函数中访问
●构造函数和析构函数是特殊的成员函数,在其中访问虚函数时,C++采用静态联编,即在构造函数或析构函数内,即使是使用“this->虚函数名”的形式来调用,编译器仍将其解释为静态联编的“本类名::虚函数名”。即它们所调用的虚函数是自己类中定义的函数,如果在自己的类中没有实现该函数,则调用的是基类中的虚函数。但绝不会调用任何在派生类中重定义的虚函数。
虚函数表vftable:
如果类中包含有虚成员函数,在用该类实例化对象时,对象的第一个成员将是一个指向虚函数表(vftable)的指针(vfptr)。虚函数表记录运行过程中实际应该调用的虚函数的入口地址。
包含虚函数的类的sizeof:
包含有虚函数的类,在用sizeof求类对象所占用的内存空间的时候,因为此时对象有个指向虚函数表的指针,所以结果应该多四个字节,但是当同一个类中有多个虚函数的时候,也只有4个字节。当另一个类虚基继承时,又会多4个字节。如果派生类中又自己定义了新的虚函数,则又多4个字节。
#include<iostream>
using namespace std;
class A
{
int i;
virtual void fun();
virtual void fun1();
virtual void fun2();
};
// i 4个字节,vfptr指针8个字节,内存对齐 8+4 对齐 16
class B:virtual public A
{
virtual void fun();
virtual void fun1();
};
//8 8 4 = 24
int main()
{
cout<<"sizeof A="<<sizeof(A)<<endl;
cout<<"sizeof B="<<sizeof(B)<<endl;
return 0;
}
//sizeof A=16
//sizeof B=24
|
#include<iostream>
using namespace std;
class A
{
int i;
void f();
virtual void fun();
virtual void fun1();
virtual void fun2();
};
class B:virtual public A
{
virtual void fun1();
virtual void fun3();
};
// 虚继承已经有虚函数表了,fun3()特有的,只要
// 加入虚函数表就可以了
int main()
{
cout<<"sizeof A="<<sizeof(A)<<endl;
cout<<"sizeof B="<<sizeof(B)<<endl;
return 0;
}
//sizeof A=16
//sizeof B=24
|