在c++中,虚函数(Virtual Function)就是在一个类中用保留字Virtual定义的成员函数。基类的虚函数在其派生类中仍然是虚函数,并且一般需要在派生类中重定义。
即使在派生类中没有保留字Virtual,该函数也是虚函数。
class Base{
public:
void print(){
cout << "I am Base." << endl;
}
};
class Student:public Base{
public:
void print(){
cout << "I am Student." << endl;
}
};
int main(){
Base a;
Student b;
Base* ptr; //结果
a.print(); //I am Base.
b.print(); //I am Student.
ptr = &a;
ptr->print(); //I am Base.
ptr = &b;
ptr->print(); //I am Base.
return 0;
}
可以看到,我们没有使用virtual保留字时,使用基类指针然后访问函数,每次访问到的都是基类的print。如果加上virtual就不一样了。
class Base{
public:
virtual void print(){ //仅仅是这里加上virtual
cout << "I am Base." << endl;
}
};
class Student:public Base{
public:
void print(){
cout << "I am Student." << endl;
}
};
int main(){
Base a;
Student b;
Base* ptr; //结果
a.print(); //I am Base.
b.print(); //I am Student.
ptr = &a;
ptr->print(); //I am Base.
ptr = &b;
ptr->print(); //I am Student.
return 0;
}
仅仅是在基类的print函数前面加上virtual保留字,就能达到使用基类指针而访问子类的print的效果。这个效果,就是多态。
事实上,多态的实现是使用一个叫做虚函数表指针的方法来实现的。
class Base{
public:
virtual void print(){ //仅仅是这里加上virtual
cout << "I am Base." << endl;
}
};
class Base1{
public:
void print(){ //没有virtual
cout << "I am Base." << endl;
}
};
int main(){
Base b;
Base1 c;
cout << sizeof(b) << endl; //输出 8
cout << sizeof(c) << endl; //结果 1
return 0;
}
当一个类里,只有普通的成员函数时,这个类的大小是1,换句话说,即使是个空类,类的大小也是1。而在Base里,因为有虚函数,使用sizeof得出的值竟然变成了8。这就是刚刚所说的虚函数表指针。
这个指针是我们在编译时,编译器自动为我们加上的一个指针,它指向了虚函数表。这个表里的内容,就是它的虚函数的函数地址。
class Base{
public:
virtual void print(){ //仅仅是这里加上virtual
cout << "I am Base." << endl;
}
virtual void A(){
cout << "It is A Function." << endl;
}
};
class Student:public Base{
public:
void print(){
cout << "I am Student." << endl;
}
//Student没有重定义A()
};
int main(){
Base b;
Student c;
Base* d;
d = &b;
d->A();
d->print();
d = &c;
d->A();
d->print();
return 0;
}
实际上,在内存里是这样的。
不仅可以使用基类指针访问子类函数的方式,还可以使用基类的引用,访问子类函数的方法。
同样的,这个虚函数表是跟随着类的,而不是跟随着对象。在这些带有虚函数的类初始化的时候,编译器会自动的在构造函数里加上指针的初始化语句,并指向这个类的虚函数表。