15-3 虚函数

调用时解析

使用基类的引用或指针调用虚函数时,会发生动态绑定

所以,知道运行时才会知道到底调用了哪个版本的虚函数

因此:

  1. 虚函数是在运行时才被解析
  2. 虚函数必须被定义

注意:

  1. 普通类型(非指针和引用类型)调用虚函数时不会发生动态绑定
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传入的不是引用

  1. 调用一般的重载函数而不是虚函数时不会发生动态绑定
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关键字,这样,如果没有成功覆盖,程序就会报错

image-20220302175735534

另外,当把一个函数指定为final的时候,任何尝试覆盖该函数的行为都会报错

image-20220302175903282

虚函数与默认实参

  • 虚函数可以拥有默认实参

  • 实参的值由本次调用的静态类型决定

动态类型与静态类型的概念详见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递归

posted @ 2022-03-04 12:43  咪啪魔女  阅读(41)  评论(0编辑  收藏  举报