从 C 向 C++ 进阶系列导航
1. 函数重写
对于子类中与父类成员函数同名的成员函数,称其为子类的重写函数。函数重写一般发生在父类的成员函数不满足需求的情况下。
同时,父子类是相互兼容的:
- 子类对象可以直接赋值给父类对象。
- 子类对象可以直接初始化父类对象。
- 父类指针可以指向子类对象。
- 父类引用可以引用子类对象。
- 子类可以当作父类使用。
但当使用父类类型的指针(或引用)调用子类的重写函数时,会发现实际调用的是父类中的同名函数。这是因为指针(或引用)是按照其所属类型即父类类型对对象进行解析的,并不清楚所指(或所引用)的对象究竟是属于子类类型还是父类类型。因此,不能使用父类指针(或引用)直接调用子类新加的成员函数。
- 实验:
class Parent
{
public:
void Print()
{
cout << "in Parent" << endl;
}
};
class Child : public Parent
{
public:
void Print()
{
cout << "in Child" << endl;
}
void Fun(){}
};
int main(int argc, char *argv[])
{
Child obj;
obj.Print(); // in Child
Parent* p = &obj;
p->Print(); // in Parent
// p->Fun(); // error: ‘class Parent’ has no member named ‘Fun’
Parent& ref = obj;
ref.Print(); // in Parent
// ref.Fun(); // error: ‘class Parent’ has no member named ‘Fun’
}
2. 虚函数
为了解决函数重写中带来的问题,C++ 提出了虚函数的概念,当成员函数定义为虚函数时,指针(或引用)会根据所指(或所引用)的实际对象调用其类型的成员函数。这便是大名鼎鼎的多态特性。
在父类中使用 virtual 关键字修饰成员函数便可以得到虚函数,在子类的重写函数中可以也使用 virtual 关键字进行修饰,也可以不显式修饰,编译器会自动把该函数认为为虚函数。但为了提高可读性,应在子类中也进行显式修饰。当子类发生函数重写时,必须在父类中把同名函数定义为虚函数。
- 实验:
class Parent
{
public:
virtual void vir_Print()
{
cout << "in Parent" << endl;
}
void Print()
{
cout << "in Parent" << endl;
}
};
class Child : public Parent
{
public:
virtual void vir_Print()
{
cout << "in Child" << endl;
}
void Print()
{
cout << "in Child" << endl;
}
};
int main(int argc, char *argv[])
{
Parent parent_obj;
Child child_obj;
Parent* p = &parent_obj;
p->vir_Print(); // in Parent
p->Print(); // in Parent
p = &child_obj;
p->vir_Print(); // in Child
p->Print(); // in Parent
Parent& ref = child_obj;
ref.vir_Print(); // in Child
ref.Print(); // in Parent
}
对于虚函数的调用,称为动态联编,表示在程序实际运行后才能确定具体的函数调用。相应的,对于普通成员函数的调用,称为静态联编,表示在程序编译期间就能确定具体的函数调用。