15-3 虚函数
调用时解析#
使用基类的引用或指针调用虚函数时,会发生动态绑定
所以,知道运行时才会知道到底调用了哪个版本的虚函数
因此:
- 虚函数是在运行时才被解析
- 虚函数必须被定义
注意:
- 普通类型(非指针和引用类型)调用虚函数时不会发生动态绑定
class A{
public :
//基类的print是虚函数
virtual void print() const {cout<<"class A"<<endl;};
};
class B : public A{
public:
//B覆盖了A的虚函数
void print() const {cout<<"class B"<<endl;}
};
void Test_Print(const A a){ //传入基类的拷贝而不是引用
a.print();
}
int main(){
A a;
B b;
Test_Print(a); //打印 class A
Test_Print(b); //打印 class A
a.print(); //打印 class A
b.print(); //打印 class B
return 0;
}
Test_Print(b)
没有发生动态绑定,因为Test_Print传入的不是引用
- 调用一般的重载函数而不是虚函数时不会发生动态绑定
class A{
public :
//基类的print不是虚函数
void print() const {cout<<"class A"<<endl;};
};
class B : public A{
public:
//B重载了A的print函数
void print() const {cout<<"class B"<<endl;}
};
void Test_Print(const A &a){ //传入基类的引用
a.print();
}
int main(){
A a;
B b;
Test_Print(a); //打印 class A
Test_Print(b); //打印 class A
a.print(); //打印 class A
b.print(); //打印 class B
return 0;
}
Test_Print(b)
没有发生动态绑定,因为print不是虚函数
派生类中的虚函数#
基类中的虚函数在派生类中隐含地也是一个虚函数。
当派生类覆盖了某个虚函数时,该函数在基类中的形参必须与派生类中的形参严格匹配。
final和override说明符#
当派生类想要覆盖基类的虚函数时,由于需要严格的参数匹配,如果派生类中的参数列表不小心写错了,那么派生类中与基类虚函数同名的函数,并没有覆盖掉基类的虚函数,这样的写法是合法的,编译器不会报错,错误非常难以察觉。
当我们明确地想要让派生类的某个函数覆盖掉基类的虚函数时,可以加上override
关键字,这样,如果没有成功覆盖,程序就会报错
另外,当把一个函数指定为final
的时候,任何尝试覆盖该函数的行为都会报错
虚函数与默认实参#
-
虚函数可以拥有默认实参
-
实参的值由本次调用的静态类型决定
动态类型与静态类型的概念详见15.2.3
换句话说,如果我们通过基类的引用或指针调用函数,则使用基类中定义的默认实参,即使实际运行的是派生类中的函数版本也是如此。
class A{
public :
virtual void print(string s = "class A") const
{cout<<s<<endl;};
};
class B : public A{
public:
void print(string s = "class B") const
{cout<<s<<endl;}
};
void Test_Print_A(const A &a){//参数的静态类型是A
a.print(); //调用A的默认参数,即使A绑定的是派生类B
}
int main(){
A a;
B b;
Test_Print_A(a); //打印 class A
Test_Print_A(b); //打印 class A
return 0;
}
如果虚函数使用默认实参,则基类和派生类中定义的默认实参最好一致
回避虚函数机制#
在某些情况下,我们希望对虚函数的调用不要进行动态绑定,而是强迫其执行虚函数的某个特定版本。使用作用域运算符可以实现这一目的,例如下面的代码:
//强行调用基类中定义的函数版本而不管baseP的动态类型到底是什么
double undiscounted = baseP->Quote::net_price(42);
通常情况下,只有成员函数(或友元)中的代码才需要使用作用域运算符来回避虚函数的机制。
什么时候我们需要回避虚函数的默认机制呢?
通常是当一个派生类的虚函数调用它覆盖的基类的虚函数版本时。在此情况下,基类的版本通常完成继承层次中所有类型都要做的共同任务,而派生类中定义的版本需要执行一些与派生类本身密切相关的操作。
如果一个派生类虚函数需要调用它的基类版本,但是没有使用作用域运符,则在运行时该调用将被解析为对派生类版本自身的调用,从而导致无F递归
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Ollama——大语言模型本地部署的极速利器
· 使用C#创建一个MCP客户端
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· Windows编程----内核对象竟然如此简单?
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用