c++虚函数及常见问题汇总

 更新时间:2025年12月01日 10:00:35   作者:王YAN龙  
文章详细介绍了面向对象编程中的四大基本特性:封装、抽象、继承和多态,重点解释了多态的概念及其在C++中的实现方式,包括虚函数、虚函数表、单继承、多继承以及纯虚函数的使用,通过代码示例展示了多态在不同情况下的表现,以及纯虚函数的约束作用,感兴趣的朋友一起看看吧

封装,抽象,继承,多态是面向对象编程语言的特点。个人认为抽象和继承都是手段,多态才是目的,继承是多态的基础。

封装:

(1)将属性和函数封装到一个类里边,属性和函数组成一个完整的对象

(2)权限管理,增强安全性

(3)暴露有限的接口,增强了易用性,增强了安全性

抽象:

(1)抽象这个概念存在于很多场景,我们使用函数,本身就是通过函数隐藏具体的实现细节,这也算抽象

(2)c++ 中的抽象,可以使用抽象类来认识,抽象类是包含纯虚函数的类,抽象类不能创建对象

继承:

(1)技术来源于生活,生活中的对象,比如植物,动物,都存在继承

(2)继承带来的好处是子类可以复用父类的属性和函数

(3)c++ 中继承是实现多态的基础

多态:

同一个父类,可以派生出多个子类,每个子类都有自己专有的属性和方法,多姿多态,丰富多彩

1 多态

提到多态我们一般会想到这样一种使用场景:有一个基类,基类里边有虚函数;不同的类继承基类,然后分别在虚函数中实现自己的逻辑,覆盖基类的虚函数;派生类的指针也可以赋值给基类指针,通过基类指针调用虚函数的时候调用的是派生类实现的函数。

上边说的是典型的多态的场景,在有些文章或者书中,也把重载说成是一种多态。本节分别举一个虚函数多态的例子,一个重载多态的例子。

1.1 虚函数

如下代码, Phone 是一个基类,类中有一个虚函数 virtual boid CallUp()。Phone 派生出 3 个类,ApplePhone,MiPhone,OppoPhone。在 main 函数中针对 3 个派生类,分别创建了一个对象。定义了 3 个函数 CallUp1(),CallUp2(),CallUp3(),3 个函数的形参分别是 Phone 引用,Phone 指针,Phone 对象,在 main() 函数中分别调用这 3 个函数。

#include <iostream>
#include <string>
class Phone {
public:
    virtual ~Phone() {
      std::cout << "~Phone()" << std::endl;
    }
    virtual void CallUp() {
      std::cout << "Phone call up" << std::endl;
    }
};
class ApplePhone : public Phone {
public:
    void CallUp() {
        std::cout << "ApplePhone call up" << std::endl;
    }
};
class MiPhone : public Phone {
public:
    void CallUp() {
        std::cout << "MiPhone call up" << std::endl;
    }
};
class OppoPhone : public Phone {
public:
    void CallUp() {
        std::cout << "OppoPhone call up" << std::endl;
    }
};
void CallUp1(Phone &phone) {
  std::cout << "CallUp(Phone &phone)" << std::endl;
  phone.CallUp();
}
void CallUp2(Phone *phone) {
  std::cout << "CallUp2(Phone *phone)" << std::endl;
  phone->CallUp();
}
// 派生类可以赋值给基类
// 因为基类中的数据成员,在派生类中都有
// 派生类赋值给基类的过程,就是成员赋值的过程
void CallUp3(Phone phone) {
  std::cout << "CallUp3(Phone phone)" << std::endl;
  phone.CallUp();
}
int main() {
    ApplePhone apple;
    MiPhone mi;
    OppoPhone oppo;
    CallUp1(apple);
    CallUp1(mi);
    CallUp1(oppo);
    std::cout << std::endl;
    CallUp2(&apple);
    CallUp2(&mi);
    CallUp2(&oppo);
    std::cout << std::endl;
    CallUp3(apple);
    CallUp3(mi);
    CallUp3(oppo);
    std::cout << std::endl;
    return 0;
}

代码运行结果如下,可以看到,调用 CallUp1() 和 CallUp2() 能体现出多态的特点,分别调用的是类自己的 CallUp();CallUp3() 没有体现多态,调用的都是基类的虚函数。引用传递和指针传递都能体现多态的特点;值传递不能体现多态的特点,还是调用的基类中的函数。

2 虚函数和虚函数表

2.1 函数名即地址

在 c 和 c++ 中函数和全局变量类似,都是有地址的,函数名就表示函数的入口地址。

如下代码,声明了一个函数指针 PF,形参和返回值与 Hello 是一致的,这样就可以直接通过 pf 来对 Hello 做函数调用了。

#include <iostream>
#include <string>
void Hello(std::string name) {
  std::cout<< "hello " << name << std::endl;
}
typedef void (*PF)(std::string name);
int main() {
  std::cout<< (void *)Hello << std::endl;
  Hello("marry");
  PF pf = Hello;
  std::cout << "pf = " << (void *)pf << std::endl;
  pf("tom");
  return 0;
}

2.2 虚表

虚函数表是虚函数工作的基础,也是实现多态的核心。对于有虚函数的类来说,都会有一个虚函数表。如下图所示,有虚函数的类,这个类的每个对象的第一个 8 字节(64 位系统是 8 字节,32 位系统是 4 字节) 中保存的是虚函数表的地址。

如果一个有虚函数的类,没有其它成员变量,那么 sizeof() 计算这个类的大小是 8(64 位系统上),8 字节也就是存放虚函数表的空间。

#include <iostream>
#include <string>
class Base {
public:
  virtual void SayHello1() {
    std::cout << "hello1" << std::endl;
  }
};
int main() {
  std::cout << "sizeof(Base) = " << sizeof(Base) << std::endl;
  return 0;
}

一个类的所有对象共用一张虚函数表,所以每个对象的第一个 8 字节里边存储的数据内容是相等的,都是虚函数表的地址。虚表中保存的是函数的地址,而函数又属于代码段,代码段是共享的,所以一个类的对象共享虚表。

如下代码,有一个包含虚函数的类 Base,b1 和 b2 都是 Base 的对象,我们获取对象第一个 8 字节中的内容,发现 b1 和 b2 是相等的,这也验证了一个类的对象指向同一个虚表。

#include <iostream>
#include <string>
class Base {
public:
  virtual void SayHello1() {
    std::cout << "hello1" << std::endl;
  }
  virtual void SayHello2() {
    std::cout << "hello2" << std::endl;
  }
  virtual void SayHello3() {
    std::cout << "hello3" << std::endl;
  }
};
int main() {
  Base b1;
  Base b2;
  printf("&b1 = %p, *(long *)(&b1) = 0x%llx\n", &b1, *(long *)(&b1));
  printf("&b1 = %p, *(long *)(&b2) = 0x%llx\n", &b2, *(long *)(&b2));
  return 0;
}

运行结果如下,从打印信息来看,虚函数表的地址是 0x55f2dd2dbd58。

虚函数表中每一个表项都是一个函数的地址,在 64 位机器上,一个函数地址的长度是 8 字节,所以一个表项占  8 字节。如下代码,在类 Base 中有 3 个虚函数,另外定义了一个函数指针类型。在 main 函数中首先找到了虚函数表,然后依次取出了虚函数的地址,通过函数地址可以直接进行调用。

#include <iostream>
#include <string>
class Base {
public:
  virtual void SayHello1() {
    std::cout << "hello1" << std::endl;
  }
  virtual void SayHello2() {
    std::cout << "hello2" << std::endl;
  }
  virtual void SayHello3() {
    std::cout << "hello3" << std::endl;
  }
};
// 定义一个函数指针类型
typedef void (*PF)(void);
int main() {
  std::cout << "sizeof(Base) = " << sizeof(Base) << std::endl;
  Base b;
  // vfptr 是指向虚函数表的地址
  long *vfptr = (long *)*(long *)(&b);
  // 虚函数表的第一个 8 字节就是函数 SayHello1 的地址
  long f1_function = *(vfptr + 0);
  // 虚函数表的第二个 8 字节是函数 SayHello2 的地址
  long f2_function = *(vfptr + 1);
  // 虚函数表的第三个 8 字节是函数 SayHello3 的地址
  long f3_function = *(vfptr + 2);
  printf("function1 = %p\n", f1_function);
  printf("function2 = %p\n", f2_function);
  printf("function3 = %p\n", f3_function);
  PF pf;
  pf = (PF)(f1_function);
  pf();
  pf = (PF)(f2_function);
  pf();
  pf = (PF)(f3_function);
  pf();
  return 0;
}

运行结果如下:

2.3 单继承,没有虚函数覆盖

如下代码, Base 中有 3 个虚函数,Derived 继承了 Base 类, Derived 中有 2 个虚函数。Derived  中没有覆盖 Base 中的函数,所以 Derived 的虚表有 5 个表项,Base 中的虚表有 3 个表项。

#include <iostream>
#include <string>
class Base {
public:
  virtual void faa1() {
    std::cout << "faa1" << std::endl;
  }
  virtual void faa2() {
    std::cout << "faa2" << std::endl;
  }
  virtual void faa3() {
    std::cout << "faa3" << std::endl;
  }
};
class Derived : public Base{
public:
  virtual void fbb1() {
    std::cout << "fbb1" << std::endl;
  }
  virtual void fbb2() {
    std::cout << "fbb2" << std::endl;
  }
};
typedef void (*PF)(void);
void CallPF1() {
  // Base b; // Base 中的虚表只有 3 个函数
  // 直接对象赋值,b 中的虚表也是只用 3 个表项
  // Derived d;
  // Base b = d;
  Base b;
  long *vfptr = (long *)*(long *)(&b);
  long f1_function = *(vfptr + 0);
  long f2_function = *(vfptr + 1);
  long f3_function = *(vfptr + 2);
  long f4_function = *(vfptr + 3);
  long f5_function = *(vfptr + 4);
  PF pf;
  pf = (PF)(f1_function);
  pf();
  pf = (PF)(f2_function);
  pf();
  pf = (PF)(f3_function);
  pf();
  pf = (PF)(f4_function);
  pf();
  pf = (PF)(f5_function);
  pf();
}
void CallPF2() {
  Derived b;
  long *vfptr = (long *)*(long *)(&b);
  long f1_function = *(vfptr + 0);
  long f2_function = *(vfptr + 1);
  long f3_function = *(vfptr + 2);
  long f4_function = *(vfptr + 3);
  long f5_function = *(vfptr + 4);
  PF pf;
  pf = (PF)(f1_function);
  pf();
  pf = (PF)(f2_function);
  pf();
  pf = (PF)(f3_function);
  pf();
  pf = (PF)(f4_function);
  pf();
  pf = (PF)(f5_function);
  pf();
}
void CallPF3() {
  Base *b = new Derived();
  long *vfptr = (long *)*(long *)(b);
  long f1_function = *(vfptr + 0);
  long f2_function = *(vfptr + 1);
  long f3_function = *(vfptr + 2);
  long f4_function = *(vfptr + 3);
  long f5_function = *(vfptr + 4);
  PF pf;
  pf = (PF)(f1_function);
  pf();
  pf = (PF)(f2_function);
  pf();
  pf = (PF)(f3_function);
  pf();
  pf = (PF)(f4_function);
  pf();
  pf = (PF)(f5_function);
  pf();
}
int main() {
  std::cout << "sizeof(Base) = " << sizeof(Base) << std::endl;
  std::cout << "sizeof(Derived) = " << sizeof(Derived) << std::endl;
  CallPF2();
  std::cout << std::endl;
  CallPF3();
  std::cout << std::endl;
  CallPF1();
  std::cout << std::endl;
  return 0;
}

运行结果如下,因为 Base 中的虚函数表的表项还是 3 个,所以调用 f4_function 和 f5_function 的时候会出现段错误。

2.4 单继承,有虚函数覆盖 --> 表项替换

在子类中,可以覆盖父类的虚函数,覆盖虚函数就是将虚函数表中的表项覆盖,改成子类中的虚函数的地址。当调用成员函数的时候,如果是虚函数,那么会查虚函数表,找到表之后再调用对应的函数。不同的子类,使用自己的虚函数地址覆盖了虚函数表的表项,那么函数调用的时候当然是调用子类中的函数。

如下代码,Base 类中有 3 个虚函数;Derived 继承了 Base,覆盖了 Base 中的 faa1 和 faa2;Derived1 继承了 Derived,覆盖了 faa1 和 faa2。

#include <iostream>
#include <string>
class Base {
public:
  virtual void faa1() {
    std::cout << "faa1" << std::endl;
  }
  virtual void faa2() {
    std::cout << "faa2" << std::endl;
  }
  virtual void faa3() {
    std::cout << "faa3" << std::endl;
  }
};
class Derived : public Base{
public:
  virtual void faa1() {
    std::cout << "derived faa1" << std::endl;
  }
  void faa2() {
    std::cout << "derived faa2" << std::endl;
  }
  virtual void fbb1() {
    std::cout << "fbb1" << std::endl;
  }
  virtual void fbb2() {
    std::cout << "fbb2" << std::endl;
  }
};
class Derived1 : public Derived {
public:
  void faa1() {
    std::cout << "Derived1 faa1" << std::endl;
  }
  void fbb1() {
    std::cout << "Derived1 fbb1" << std::endl;
  }
};
typedef void (*PF)(void);
void CallPF1() {
  // Base b; // Base 中的虚表只有 3 个函数
  // 直接对象赋值,b 中的虚表也是只用 3 个表项
  // Derived d;
  // Base b = d;
  Base b;
  long *vfptr = (long *)*(long *)(&b);
  long f1_function = *(vfptr + 0);
  long f2_function = *(vfptr + 1);
  long f3_function = *(vfptr + 2);
  long f4_function = *(vfptr + 3);
  long f5_function = *(vfptr + 4);
  PF pf;
  pf = (PF)(f1_function);
  pf();
  pf = (PF)(f2_function);
  pf();
  pf = (PF)(f3_function);
  pf();
  pf = (PF)(f4_function);
  pf();
  pf = (PF)(f5_function);
  pf();
}
void CallPF2() {
  Derived b;
  long *vfptr = (long *)*(long *)(&b);
  long f1_function = *(vfptr + 0);
  long f2_function = *(vfptr + 1);
  long f3_function = *(vfptr + 2);
  long f4_function = *(vfptr + 3);
  long f5_function = *(vfptr + 4);
  PF pf;
  pf = (PF)(f1_function);
  pf();
  pf = (PF)(f2_function);
  pf();
  pf = (PF)(f3_function);
  pf();
  pf = (PF)(f4_function);
  pf();
  pf = (PF)(f5_function);
  pf();
}
void CallPF3() {
  Derived1 b;
  long *vfptr = (long *)*(long *)(&b);
  long f1_function = *(vfptr + 0);
  long f2_function = *(vfptr + 1);
  long f3_function = *(vfptr + 2);
  long f4_function = *(vfptr + 3);
  long f5_function = *(vfptr + 4);
  PF pf;
  pf = (PF)(f1_function);
  pf();
  pf = (PF)(f2_function);
  pf();
  pf = (PF)(f3_function);
  pf();
  pf = (PF)(f4_function);
  pf();
  pf = (PF)(f5_function);
  pf();
}
int main() {
  std::cout << "sizeof(Base) = " << sizeof(Base) << std::endl;
  std::cout << "sizeof(Derived) = " << sizeof(Derived) << std::endl;
  CallPF2();
  std::cout << std::endl;
  CallPF3();
  std::cout << std::endl;
  CallPF1();
  std::cout << std::endl;
  return 0;
}

运行结果如下:

2.5 多继承

如下代码,有 3 个基类,Base1,Base2 和 Base3。3 个基类中都有虚函数,也都有一个虚函数表。Derived 类继承 3 个基类,那么 Derived 中就有 3 个虚函数表。从打印的  Derived 的大小也能看出来,Base1、Base2、Base3 的大小是 8,Derived 的大小是 24。

#include <iostream>
#include <string>
class Base1 {
public:
  virtual void foo1() {
    std::cout << "Base1::foo1" << std::endl;
  }
  virtual void faa2() {
    std::cout << "Base1::faa2" << std::endl;
  }
  virtual void faa3() {
    std::cout << "Base1::faa3" << std::endl;
  }
};
class Base2 {
public:
  virtual void foo1() {
    std::cout << "Base2::foo1" << std::endl;
  }
  virtual void fbb2() {
    std::cout << "Base2::fbb2" << std::endl;
  }
  virtual void fbb3() {
    std::cout << "Base2::fbb3" << std::endl;
  }
};
class Base3 {
public:
  virtual void foo1() {
    std::cout << "Base3::foo1" << std::endl;
  }
  virtual void fcc2() {
    std::cout << "Base3::fcc2" << std::endl;
  }
  virtual void fcc3() {
    std::cout << "Base3::fcc3" << std::endl;
  }
};
class Derived : public Base1, public Base2, public Base3 {
public:
  virtual void fbb1() {
    std::cout << "Derived::fbb1" << std::endl;
  }
  void foo1() {
    std::cout << "Derived::foo1" << std::endl;
  }
  void fbb2() {
    std::cout << "Derived::fbb2" << std::endl;
  }
};
int main() {
  std::cout << "sizeof(Base1) = " << sizeof(Base1) << std::endl; // 8
  std::cout << "sizeof(Base2) = " << sizeof(Base2) << std::endl; // 8
  std::cout << "sizeof(Base3) = " << sizeof(Base3) << std::endl; // 8
  std::cout << "sizeof(Derived) = " << sizeof(Derived) << std::endl; // 24
  Derived d;
  d.foo1();
  d.fbb2();
  d.fcc2();
  return 0;
}

运行结果如下:

2.6 一个类的多个对象是不是共享一个虚函数表 ?

是,一个包含虚函数的类的多个对象共享一个虚函数表。

如下代码,Base 是基类,Derived 是派生类。两个类分别有自己的虚函数表,对于一个类的多个对象来说,多个对象共享这个类的虚函数表。

对于一个对象来说,第一个 8 字节保存的就是虚函数表的地址,代码中打印出了每个对象的虚函数表。

#include <iostream>
#include <string>
class Base {
public:
  Base() {
    std::cout << "Base()" << std::endl;
  }
  ~Base() {
    std::cout << "~Base()" << std::endl;
  }
  virtual void Do() {
    std::cout << "Base() Do()" << std::endl;
  }
};
class Derived : public Base {
public:
  Derived() {
    std::cout << "Derived()" << std::endl;
  }
  ~Derived() {
    std::cout << "~Derived()" << std::endl;
  }
  void Do() {
    std::cout << "Derived() Do()" << std::endl;
  }
};
typedef void (*PF)(void);
void TestBase() {
  Base b1;
  Base b2;
  Base b3;
  long *vfptr1 = (long *)*(long *)(&b1);
  long *vfptr2 = (long *)*(long *)(&b2);
  long *vfptr3 = (long *)*(long *)(&b3);
  std::cout << "1, virtual function table addr " << vfptr1 << std::endl;
  std::cout << "2, virtual function table addr " << vfptr2 << std::endl;
  std::cout << "3, virtual function table addr " << vfptr3 << std::endl;
  long f1 = *(vfptr1 + 0);
  long f2 = *(vfptr2 + 0);
  long f3 = *(vfptr3 + 0);
  PF pf1 = (PF)(f1);
  PF pf2 = (PF)(f2);
  PF pf3 = (PF)(f3);
  pf1();
  pf2();
  pf3();
}
void TestDerived() {
  Derived d1;
  Derived d2;
  Derived d3;
  long *vfptr1 = (long *)*(long *)(&d1);
  long *vfptr2 = (long *)*(long *)(&d2);
  long *vfptr3 = (long *)*(long *)(&d3);
  std::cout << "1, virtual function table addr " << vfptr1 << std::endl;
  std::cout << "2, virtual function table addr " << vfptr2 << std::endl;
  std::cout << "3, virtual function table addr " << vfptr3 << std::endl;
  long f1 = *(vfptr1 + 0);
  long f2 = *(vfptr2 + 0);
  long f3 = *(vfptr3 + 0);
  PF pf1 = (PF)(f1);
  PF pf2 = (PF)(f2);
  PF pf3 = (PF)(f3);
  pf1();
  pf2();
  pf3();
}
void TestDerivedBase() {
  Base *d1 = new Derived;
  Base *d2 = new Derived;
  Base *d3 = new Derived;
  long *vfptr1 = (long *)*(long *)(d1);
  long *vfptr2 = (long *)*(long *)(d2);
  long *vfptr3 = (long *)*(long *)(d3);
  std::cout << "1, virtual function table addr " << vfptr1 << std::endl;
  std::cout << "2, virtual function table addr " << vfptr2 << std::endl;
  std::cout << "3, virtual function table addr " << vfptr3 << std::endl;
  long f1 = *(vfptr1 + 0);
  long f2 = *(vfptr2 + 0);
  long f3 = *(vfptr3 + 0);
  PF pf1 = (PF)(f1);
  PF pf2 = (PF)(f2);
  PF pf3 = (PF)(f3);
  pf1();
  pf2();
  pf3();
}
int main() {
  std::cout << "Base ------------------------" << std::endl;
  TestBase();
  std::cout << "Derived ---------------------" << std::endl;
  TestDerived();
  std::cout << "DerivedBase -----------------" << std::endl;
  TestDerivedBase();
  return 0;
}

打印结果如下:

(1)对于同一个类的多个对象来说,虚函数表是相同的

(2)基类和派生类的虚函数表是不同的,各自有各自的虚函数表,但是把派生类指针或者引用赋值给基类指针或者基类引用,那么基类中的虚函数表是派生类中的虚函数表

2.7 一个基类的多个派生类之间是共享一个虚表吗 ?

不是,虚函数表是以类为单位而存在的,一个基类的多个派生类分别有各自的虚函数表。

如下代码,Base 是基类,有 3 个派生类 Derived1,Derived2 和 Derived3。分别打印了 3 个派生类的虚函数表,虚函数表是不一样的。

#include <iostream>
#include <string>
class Base {
public:
  Base() {
    std::cout << "Base()" << std::endl;
  }
  ~Base() {
    std::cout << "~Base()" << std::endl;
  }
  virtual void Do() {
    std::cout << "Base() Do()" << std::endl;
  }
};
class Derived1 : public Base {
public:
  Derived1() {
    std::cout << "Derived1()" << std::endl;
  }
  ~Derived1() {
    std::cout << "~Derived1()" << std::endl;
  }
  void Do() {
    std::cout << "Derived1() Do()" << std::endl;
  }
};
class Derived2 : public Base {
public:
  Derived2() {
    std::cout << "Derived2()" << std::endl;
  }
  ~Derived2() {
    std::cout << "~Derived2()" << std::endl;
  }
  void Do() {
    std::cout << "Derived2() Do()" << std::endl;
  }
};
class Derived3 : public Base {
public:
  Derived3() {
    std::cout << "Derived3()" << std::endl;
  }
  ~Derived3() {
    std::cout << "~Derived3()" << std::endl;
  }
  void Do() {
    std::cout << "Derived3() Do()" << std::endl;
  }
};
typedef void (*PF)(void);
void TestBase() {
  Base b1;
  Base b2;
  Base b3;
  long *vfptr1 = (long *)*(long *)(&b1);
  long *vfptr2 = (long *)*(long *)(&b2);
  long *vfptr3 = (long *)*(long *)(&b3);
  std::cout << "1, virtual function table addr " << vfptr1 << std::endl;
  std::cout << "2, virtual function table addr " << vfptr2 << std::endl;
  std::cout << "3, virtual function table addr " << vfptr3 << std::endl;
  long f1 = *(vfptr1 + 0);
  long f2 = *(vfptr2 + 0);
  long f3 = *(vfptr3 + 0);
  PF pf1 = (PF)(f1);
  PF pf2 = (PF)(f2);
  PF pf3 = (PF)(f3);
  pf1();
  pf2();
  pf3();
}
void TestDerived1() {
  Derived1 d1;
  Derived1 d2;
  Derived1 d3;
  long *vfptr1 = (long *)*(long *)(&d1);
  long *vfptr2 = (long *)*(long *)(&d2);
  long *vfptr3 = (long *)*(long *)(&d3);
  std::cout << "1, virtual function table addr " << vfptr1 << std::endl;
  std::cout << "2, virtual function table addr " << vfptr2 << std::endl;
  std::cout << "3, virtual function table addr " << vfptr3 << std::endl;
  long f1 = *(vfptr1 + 0);
  long f2 = *(vfptr2 + 0);
  long f3 = *(vfptr3 + 0);
  PF pf1 = (PF)(f1);
  PF pf2 = (PF)(f2);
  PF pf3 = (PF)(f3);
  pf1();
  pf2();
  pf3();
}
void TestDerived2() {
  Derived2 d1;
  Derived2 d2;
  Derived2 d3;
  long *vfptr1 = (long *)*(long *)(&d1);
  long *vfptr2 = (long *)*(long *)(&d2);
  long *vfptr3 = (long *)*(long *)(&d3);
  std::cout << "1, virtual function table addr " << vfptr1 << std::endl;
  std::cout << "2, virtual function table addr " << vfptr2 << std::endl;
  std::cout << "3, virtual function table addr " << vfptr3 << std::endl;
  long f1 = *(vfptr1 + 0);
  long f2 = *(vfptr2 + 0);
  long f3 = *(vfptr3 + 0);
  PF pf1 = (PF)(f1);
  PF pf2 = (PF)(f2);
  PF pf3 = (PF)(f3);
  pf1();
  pf2();
  pf3();
}
void TestDerived3() {
  Derived3 d1;
  Derived3 d2;
  Derived3 d3;
  long *vfptr1 = (long *)*(long *)(&d1);
  long *vfptr2 = (long *)*(long *)(&d2);
  long *vfptr3 = (long *)*(long *)(&d3);
  std::cout << "1, virtual function table addr " << vfptr1 << std::endl;
  std::cout << "2, virtual function table addr " << vfptr2 << std::endl;
  std::cout << "3, virtual function table addr " << vfptr3 << std::endl;
  long f1 = *(vfptr1 + 0);
  long f2 = *(vfptr2 + 0);
  long f3 = *(vfptr3 + 0);
  PF pf1 = (PF)(f1);
  PF pf2 = (PF)(f2);
  PF pf3 = (PF)(f3);
  pf1();
  pf2();
  pf3();
}
int main() {
  std::cout << "Base ------------------------" << std::endl;
  TestBase();
  std::cout << "Derived1 ---------------------" << std::endl;
  TestDerived1();
  std::cout << "Derived2 ---------------------" << std::endl;
  TestDerived2();
  std::cout << "Derived3 ---------------------" << std::endl;
  TestDerived3();
  return 0;
}

运行结果如下:

Base ------------------------
Base()
Base()
Base()
1, virtual function table addr 0x5565ee1b8d08
2, virtual function table addr 0x5565ee1b8d08
3, virtual function table addr 0x5565ee1b8d08
Base() Do()
Base() Do()
Base() Do()
~Base()
~Base()
~Base()
Derived1 ---------------------
Base()
Derived1()
Base()
Derived1()
Base()
Derived1()
1, virtual function table addr 0x5565ee1b8cf0
2, virtual function table addr 0x5565ee1b8cf0
3, virtual function table addr 0x5565ee1b8cf0
Derived1() Do()
Derived1() Do()
Derived1() Do()
~Derived1()
~Base()
~Derived1()
~Base()
~Derived1()
~Base()
Derived2 ---------------------
Base()
Derived2()
Base()
Derived2()
Base()
Derived2()
1, virtual function table addr 0x5565ee1b8cd8
2, virtual function table addr 0x5565ee1b8cd8
3, virtual function table addr 0x5565ee1b8cd8
Derived2() Do()
Derived2() Do()
Derived2() Do()
~Derived2()
~Base()
~Derived2()
~Base()
~Derived2()
~Base()
Derived3 ---------------------
Base()
Derived3()
Base()
Derived3()
Base()
Derived3()
1, virtual function table addr 0x5565ee1b8cc0
2, virtual function table addr 0x5565ee1b8cc0
3, virtual function table addr 0x5565ee1b8cc0
Derived3() Do()
Derived3() Do()
Derived3() Do()
~Derived3()
~Base()
~Derived3()
~Base()
~Derived3()
~Base()

2.8 虚函数重载

如下代码 Base 是基类,Derived 是派生类。Base 中有两个虚函数 func1() 和 func2(),Derived 中也有两个虚函数 func1() 和 func2()。Derived 中的 func1() 和 Base 中的 func1() 的形参列表是不一样的,所以 Derived 中的 func1 不会覆盖 Base 中的 func1。

#include <iostream>
#include <string>
class Base {
public:
  Base() {
    std::cout << "Base()" << std::endl;
  }
  ~Base() {
    std::cout << "~Base()" << std::endl;
  }
  virtual void func1(int a) {
    std::cout << "Base()::func1(int a), a = " << a << std::endl;
  }
  virtual void func2(int a = 100) {
    std::cout << "Base()::func2(int a = 100), a = " << a << std::endl;
  }
};
class Derived : public Base {
public:
  Derived() {
    std::cout << "Derived()" << std::endl;
  }
  ~Derived() {
    std::cout << "~Derived()" << std::endl;
  }
  virtual void func1(double a) {
    std::cout << "Derived()::func1(double a), a = " << a << std::endl;
  }
  virtual void func2(int a = 200) {
    std::cout << "Derived()::func2(int a = 200), a = " << a << std::endl;
  }
};
typedef void (*PF)(int);
typedef void (*PF1)(double);
int main() {
  Base *b = new Derived;
  Derived *d = new Derived;
  std::cout << "----------------" << std::endl;
  b->func1(10);
  b->func1(1.123);
  b->func2();
  std::cout << "----------------" << std::endl;
  d->func1(10);
  d->func1(1.123);
  d->func2();
  return 0;
}

运行结果如下:

(1)派生类中的虚函数表如下

第一个表项是 Base 中的 func1。

第二个表项在基类中是 Base 中的 func2,在派生类中,Derived 中的 func2 对 Base 中的 func2 进行了覆盖。

第三个表象是派生类中的 func1,因为派生类中的 func1 和 Base 中的 func1 形参不一样,所以不会对 Base 中的 func1 进行覆盖。

(2)运行结果分析

① 使用 Base 类型的指针或者 Base 类型的引用,当指针指向 Derived 对象的时候,调用 func1(),不管传参是 int 类型还是 double 类型,都是调用的 Base 中的 func1()。当传参是 double 类型的时候也不是调用的 Derived 中的 func1,也就是派生类和基类形不成重载。

② 使用 Base 类型的指针或者引用,当指针指向 Derived 对象的时候,调用 func2,调用的函数是 Derived 中的 func2,但是默认参数还是 Base 中初始化的。这个现象让人看起来有点奇怪,函数和默认参数不是配套的。

③ 使用 Derived 指针或者引用,调用的 func1() 都是 Derived 中的函数,不管入参是 int 还是 double。

④ 使用 Derived 指针或者引用,调用 func2() 调用的是 Derived 中的 func2(),并且形参默认是 200。

#include <iostream>
#include <string>
class Base {
public:
  Base() {
    std::cout << "Base()" << std::endl;
    pb = new int[8];
  };
  virtual ~Base() {
    std::cout << "~Base()" << std::endl;
    if (pb) {
      std::cout << "delete pb" << std::endl;
      delete[] pb;
    }
  };
  virtual void Do() {
    std::cout << "Base() Do()" << std::endl;
  }
  virtual void F() = 0;
  int *pb = nullptr;
};
class Derived : public Base {
public:
  Derived() {
    std::cout << "Derived()" << std::endl;
    pd = new int[16];
  };
  ~Derived() {
    std::cout << "~Derived()" << std::endl;
    if (pd) {
      std::cout << "delete pd" << std::endl;
      delete[] pd;
    }
  };
  void Do() {
    std::cout << "Derived() Do()" << std::endl;
  }
  virtual void F() {
    std::cout << "Derived() F()" << std::endl;
  }
  int *pd = nullptr;
};
int main() {
  Derived *d1 = new Derived();
  d1->Do();
  delete d1;
  Base *b1 = new Derived;
  b1->Do();
  delete b1;
  return 0;
}

3虚函数和纯虚函数

虚函数,之所以说是虚的,说的是在派生类中,可以覆盖基类中的虚函数;相对于虚函数来说,没有 virtual 修饰的函数可以叫做实函数,实函数的实现逻辑是不会变化的,不像虚函数,被继承之后就可以被覆盖,实现逻辑发生了变化。虚函数是实现多态的核心。虚函数和纯虚函数比较的话,虚函数可以在派生类中被覆盖,但是虚函数也是有自己的实现的,可以被直接调用;纯虚函数没有自己的实现,在派生类中可以被覆盖,并且必须实现。包含纯虚函数的类是抽象类,不能创建对象,如果抽象类的派生类中没有实现纯虚函数 ,那么派生类也是抽象类,不能创建对象。

虚函数和纯虚函数并没有严格的优劣之分。

从实际使用中,纯虚函数有一个优点,假如一个基类中的函数,派生类中必须实现自己的逻辑,而不能使用基类中的逻辑,那么就可以使用纯虚函数,这样在派生类中如果忘记实现了,那么编译器就会提示错误,起到了一个约束的作用;如果用虚函数实现,那么派生类中忘记实现的话,编译器也不会报错,起不到约束提醒的作用。

纯虚函数有以下两点:

(1)纯虚函数的声明方式

函数声明之后加 = 0,而不是花括号

(2)抽象类不能创建对象,抽象类的派生类如果没有实现所有的纯虚函数,派生类也是抽象类

(3)抽象类可以定义指针,并且可以使用派生类的指针给它赋值,也就是说抽象类虽然不能创建对象,但是抽象类的指针却可以指向一个对象

如下是使用抽象类的一个例子:

#include <iostream>
#include <string>
class Phone {
public:
  Phone() {
    std::cout << "Phone()" << std::endl;
  }
  ~Phone() {
    std::cout << "~Phone()" << std::endl;
  }
  virtual void Call() = 0;
  virtual void SendMessage(std::string msg) = 0;
};
class Apple : public Phone {
public:
  Apple() {
    std::cout << "Apple()" << std::endl;
  }
  ~Apple() {
    std::cout << "~Apple()" << std::endl;
  }
  virtual void Call() {
    std::cout << "Apple Call()" << std::endl;
  }
  virtual void SendMessage(std::string msg) {
    std::cout << "apple send msg: " << msg << std::endl;
  }
};
class Oppo : public Phone {
public:
  Oppo() {
    std::cout << "Oppo()" << std::endl;
  }
  ~Oppo() {
    std::cout << "~Oppo()" << std::endl;
  }
  virtual void Call() {
    std::cout << "Oppo Call()" << std::endl;
  }
};
class Vivo : public Phone {
public:
  Vivo() {
    std::cout << "Vivo()" << std::endl;
  }
  ~Vivo() {
    std::cout << "~Vivo()" << std::endl;
  }
  virtual void Call() {
    std::cout << "Vivo Call()" << std::endl;
  }
  virtual void SendMessage(std::string msg) {
    std::cout << "vivo send msg: " << msg << std::endl;
  }
};
int main() {
  // 不能创建 Phone 对象,因为 Phone 是抽象类
  // Phone phone;
  // 不能创建 Oppo 对象,因为 Oppo 没有实现 Phone 中的 SendMessage 函数
  // 所以 Oppo 也是抽象类
  // Oppo oppo;
  std::cout << "sizeof(Phone) = " << sizeof(Phone) << std::endl;
  std::cout << "sizeof(Apple) = " << sizeof(Apple) << std::endl;
  std::cout << "sizeof(Oppo) = " << sizeof(Oppo) << std::endl;
  std::cout << "sizeof(Vivo) = " << sizeof(Vivo) << std::endl;
  Phone *phone;
  Apple apple;
  Vivo vivo;
  phone = &apple;
  phone->Call();
  phone->SendMessage("this is apple");
  phone = &vivo;
  phone->Call();
  phone->SendMessage("this is vivo");
  return 0;
}

运行结果如下:

抽象类中也有虚表,从上边的打印来看,sizeof(Phone) 计算出来的结果是 8。

3.1 为什么引入纯虚函数

一般在定义抽象类的时候,使用纯虚函数,抽象类也可以叫做接口类,起到接口约束的作用,但是这个类有没有创建对象的必要。

(1)包含纯虚函数的类是抽象类,抽象类不能创建对象

这是符合现实意义的,因为在现实中,有一个抽象类是植物,植物可以派生出玫瑰花,梧桐树等,但是植物本身创建对象是没有意义的。显示世界中没有任何一种生物,它就叫植物,而没有属于自己的名字。

(2)抽象类起到接口约束的作用

抽象类的派生类,如果不实现抽象类中所有的纯虚函数,那么派生类也是不能创建对象的,如果创建了对象,那么在编译的时候会报错误,在编译的时候就可以提醒开发者不能这么使用。如果使用虚函数,而不使用纯虚函数,那么起不到这样的规范作用。

3.2 纯虚函数和虚函数的相同点和不同点

相同点:

(1)在函数声明时都用 virtual 来修饰

(2)都有虚函数表,都可以用于实现多态

声明虚函数的类和声明纯虚函数的类中都有虚函数表,抽象类虽然不能创建对象,但是也有虚函数表。

不同点:

(1)声明方式不同

纯虚函数的声明方式为  virtual ReturnType FunctionName(Parameter) = 0,纯虚函数不需要实现;虚函数在基类中的声明方式为 virtual ReturnType FunctionName(Parameter)  {},虚函数在基类中必须实现,如果虚函数没有实现会报编译错误。

#include <iostream>
#include <string>
class Shape {
public:
  Shape() {
    std::cout << "Shape()" << std::endl;
  }
  /*
  ~Shape() {
    std::cout << "~Shape()" << std::endl;
  }
*/
  virtual double Area(); /*{
    std::cout << "Shape() Area()" << std::endl;
    return 0;
  }; */
};
class Square : public Shape {
public:
  Square(int length) : length_(length) {
    std::cout << "Square(), length_ " << length_ << std::endl;
  }
  virtual double Area() {
    std::cout << "Sauqre() area " << length_ * length_ << std::endl;
    return length_ * length_;
  }
private:
  int length_;
};
int main() {
  Square s1(10);
  Square s2(20);
  s1.Area();
  s2.Area();
  return 0;
}

(2)纯虚函数,在派生类中必须要实现,否则派生类也是抽象类,不能创建对象;虚函数没有这个要求。

3.3 abstract class 官方文档 —— 纯虚函数可以有实现

Abstract class - cppreference.com

我们上边说的虚函数必须有实现,那么纯虚函数是不是就不能有实现 ?其实不是的,纯虚函数也可以有实现。

(1)纯虚函数可以有实现

(2)纯虚函数的实现不能在类内声明的时候实现,而是可以在类外实现

(3)纯虚函数有实现,那么包含纯虚函数的类仍然是抽象类,不能实例化;继承抽象类的派生类,如果不实现基类中的纯虚函数,那么派生类也是抽象类,不能实例化

(4)纯虚函数如果没有实现,那么这个函数是不能直接调用的

(5)纯虚函数如果有实现,那么这个函数可以通过  类::函数 这样的方式调用,仍然不能直接调用函数,必须通过类来调用才可以

#include <iostream>
#include <string>
class Abstract
{
public:
    // 纯虚函数,不能在声明的时候定义
    virtual void f() = 0; /* {
      std::cout << "A::f()\n";
    } */
    virtual void g() {
      // 纯虚函数不能这么调用, 会抛异常,pure virtual method called
      // f();
      // 如果纯虚函数没有实现,那么不能这么调用
      Abstract::f();
      std::cout << "Abstract() g()" << std::endl;
    }   // non-pure virtual
    ~Abstract() {
        std::cout << "~Abstract() ----------------" << std::endl;
        g();           // OK: calls Abstract::g()
        // 纯虚函数,即使有实现,也不能这么调用
        // f();        // undefined behavior
        // 如果纯虚函数没有实现,则不能这么调用
        Abstract::f(); // OK: non-virtual call
    }
};
// 纯虚函数不能在声明的时候定义,但是可以在类的外部定义
// definition of the pure virtual function
void Abstract::f() {
    std::cout << "A::f()\n";
}
struct Concrete : public Abstract
{
    void f() override {
        // 如果纯虚函数没有实现,那么不能这么调用
        Abstract::f(); // OK: calls pure virtual function
    }
    void g() override {
      std::cout << "Concrete() g()" << std::endl;
    }
    ~Concrete() {
        std::cout << "~Concrete() ----------------" << std::endl;
        g(); // OK: calls Concrete::g()
        f(); // OK: calls Concrete::f()
    }
};
int main() {
  // 虽然纯虚函数有实现,但是 Abstract 仍然会抽象类,不能创建对象
  // Abstract a;
  Concrete c;
  return 0;
}

到此这篇关于c++虚函数及常见问题的文章就介绍到这了,更多相关c++虚函数内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C++ 实现线程安全的频率限制器(推荐)

    C++ 实现线程安全的频率限制器(推荐)

    这篇文章主要介绍了在 C++ 中实现一个线程安全的频率限制器,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-05-05
  • C++实现的一个可以写递归lambda的Y函数

    C++实现的一个可以写递归lambda的Y函数

    这篇文章主要介绍了C++实现的一个可以写递归lambda的Y函数,在Y函数的帮助,这个lambda表达是可以成功看到自己,然后递归调用的,需要的朋友可以参考下
    2014-07-07
  • C语言输入一个字符串的方法有哪些

    C语言输入一个字符串的方法有哪些

    字符串输入是C语言编程中非常重要的部分,其中scanf函数是一种广泛使用的输入字符串的方法,下面这篇文章主要给大家介绍了关于C语言输入一个字符串的方法有哪些的相关资料,需要的朋友可以参考下
    2023-06-06
  • C语言文件操作大全

    C语言文件操作大全

    这篇文章主要介绍了C语言文件操作大全的相关资料,需要的朋友可以参考下
    2018-03-03
  • C++中fstream,ifstream及ofstream用法浅析

    C++中fstream,ifstream及ofstream用法浅析

    这篇文章主要介绍了C++中fstream,ifstream及ofstream用法,适合C++初学者学习文件流的操作,需要的朋友可以参考下
    2014-08-08
  • C++指针 详细介绍及总结

    C++指针 详细介绍及总结

    这篇文章主要介绍了C++指针 详细介绍及总结的相关资料,需要的朋友可以参考下
    2016-09-09
  • Qt创建项目实战之手把手创建第一个Qt项目

    Qt创建项目实战之手把手创建第一个Qt项目

    我们在进行软件开发学习时,有时候需要qt软件进行代码的敲写,下面这篇文章主要给大家介绍了关于Qt创建项目实战之手把手创建第一个Qt项目的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-04-04
  • C++中智能指针weak_ptr的原理及使用

    C++中智能指针weak_ptr的原理及使用

    weak_ptr是C++11引入的一种智能指针,本文主要介绍了C++中智能指针weak_ptr的原理及使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2025-06-06
  • C++实现二维图形的傅里叶变换

    C++实现二维图形的傅里叶变换

    这篇文章主要介绍了C++实现二维图形的傅里叶变换的方法,是C++程序设计里一个重要的应用,需要的朋友可以参考下
    2014-08-08
  • C语言实现奇数阶魔方阵的方法

    C语言实现奇数阶魔方阵的方法

    这篇文章主要介绍了C语言实现奇数阶魔方阵的方法,涉及数组及相关数学函数的使用技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-02-02

最新评论