虚函数
虚函数:类的成员函数前加virtual关键字,则称这个成员函数为虚函数;
虚函数重写
虚函数重写:当在子类中定义了一个与父类完全相同的虚函数时,则称子类的虚函数重写(覆盖)了父类的虚函数。子类重写的虚函数可以没有virtual(c++语法坑)。
多态
多态:当使用父类的引用或指针调用重写的虚函数时,当指向父类调用的是父类的虚函数,指向子类调用的是子类的虚函数。
class Person
{
public:
virtual void BuyTickets()
{
cout << "成人买票" << endl;
}
protected:
string _name;
};
class Student: public Person
{
public:
virtual void BuyTickets() //子类里的virtual可以没有,由于是继承,继承了基类虚函数的属性
{
cout << "学生买票" << endl;
}
protected:
int _num;
};
// 调用重写的虚函数时,对象必须是基类的指针或引用.
// 函数调用和对象有关,指向谁,调用谁的虚函数。当指向父类调用的就是父类的虚函数,当指向子类调用的就是子类的虚函数
void Buy1(Person* p) //使用基类的指针
{
p->BuyTickets();
}
void Buy2(Person &p) //使用基类的引用
{
p.BuyTickets();
}
void Buy3(Person p)
{
p.BuyTickets();
}
int main()
{
Person p;
Student s;
p.BuyTickets(); //普通调用,对象类型是Person,会到Person类里找Buytickets();
s.BuyTickets(); //普通调用,对象类型是Student,会到S类里找Buytickets();
//基类是指针:必须要&
Buy1(&p); // 成人买票
Buy1(&s); // 学生买票
//基类是引用
Buy2(p); // 成人买票 多态调用:对象指向p
Buy2(s); // 学生买票 多态调用:对象指向s
//基类既不是指针也不是引用
Buy3(p); //成人买票
Buy3(s); //成人买票
system("pause");
return 0;
}
多态调用:具有灵活性,函数调用和对象有关,指向谁,调用谁;
普通调用:和类型有关,是那个类型的对象,就调用谁的虚函数;
虚表指针和虚表(多态机理)
从监视窗口我们可以看到,基类里变量不只有_name ,还有前4个字节为 _vfptr,_vfptr是虚表指针,虚表指针指向父类虚表,虚表里存的是父类虚函数地址。而派生类继承了基类,同样也有虚表指针,这个虚表指针指向的是派生类虚表,虚表里存放的是派生类虚函数地址。通过这个虚表指针就可以找到基类和派生类该调用的虚函数:
虚表指针是在调用构造函数初始化的,虚表存放于数据段(静态区或全局区),不能存放于栈和堆。
一个类有一个虚表指针,即一个虚表,但虚表里存放了这个类全部的虚函数地址。如下:
class A
{
public:
virtual void fun1()
{
}
virtual void fun2()
{
}
private:
int _a;
};
int main()
{
A a1;
}
在虚表里虚函数地址存放顺序和在基类中声明顺序一样,如下:先声明fun1,后声明fun2,那么就先存放fun1虚函数地址,后存放fun2虚函数地址。
单继承的虚函数表
class A
{
public:
virtual void fun1()
{
cout << "A::fun1()" << endl;
}
virtual void fun2()
{
cout << "A::fun2()" << endl;
}
public:
int _a;
};
class B : public A
{
//子类会继承父类的fun1()函数
virtual void fun2() //子类func2()会覆盖父类fun2()
{
cout << "B::fun2()" << endl;
}
virtual void fun3()
{
cout << "B::fun3()" << endl;
}
void fun4() //fun4()不是虚函数
{
cout << "B::fun3()" << endl;
}
};
int main()
{
A a1;
B b1;
}
从监视窗口,我们可以看到子类的虚表里有继承了父类的A::fun1( ),还有覆盖的B::fun2( )函数,由于fun4( )不是虚函数,所以虚表里当然没有fun4( ),尽管没有看见子类的fun3虚函数,但是这个函数就在虚表里的fun2()函数后,只是vs监视没有显示出来,可以认为是vs的bug。但我们可以从以下方式看到真实的虚表情况:
typedef void(*FUNC)();
void printVtable(int *vfter)
{
cout << "虚表地址:" << vfter << endl;
for (int i = 0; vfter[i] != 0; i++) //因为虚表最后一个元素是0
{
printf("第%d个虚函数地址:0x%x,->", i, vfter[i]);
FUNC f = (FUNC)vfter[i];
f(); //调用该虚函数
}
cout << endl;
}
int main()
{
A a1;
B b1;
int *vfter1 = (int*)(*(int*)(&a1)); //vfter是虚表地址
int *vfter2 = (int*)(*(int*)(&b1));
printVtable(vfter1);
printVtable(vfter2);
system("pause");
return 0;
}
多继承虚函数表
首先看多继承代码:
class A
{
virtual void func1()
{
cout << "A::func1()" << endl;
}
virtual void func2()
{
cout << "A::func2()" << endl;
}
protected:
int _a;
};
class B
{
virtual void func1()
{
cout << "B::func1()" << endl;
}
virtual void func2()
{
cout << "B::func2()" << endl;
}
protected :
int _b;
};
class C : public A, public B
{
virtual void func1()
{
cout << "C::func1()" << endl;
}
virtual void func3()
{
cout << "C::func3()" << endl;
}
protected :
int _c;
};
typedef void(*FUNC)(); //声明一个函数指针类型FUNC
void printTable(int *vfptr)
{
printf("vtable address:%p\n", vfptr);
for (int i = 0; vfptr[i] != 0; i++)
{
printf("第%d个虚函数地址:%p->", i, vfptr[i]);
FUNC f = (FUNC)(vfptr[i]);
f(); //调用这个函数
}
printf("\n");
}
int main()
{
C c;
cout<<sizeof(c)<<endl;
//因为c有两个直接父类:A和B
int *vfptr1 = (int*)(*((int*)(&c)));
printTable(vfptr1);
//因为第二个虚表指针存放在第一个父类成员变量下边,所以第二个虚表指针在&c后需要加上第一个父类的大小,
//但是由于指针+1,加的是所指类型的大小,所以需要把&a强转为char*,或者+sizeof(A)/4,
int *vfptr2 = (int*)(*(int*)(((char*)(&c)) + sizeof(A)));
printTable(vfptr2);
system("pause");
return 0;
}