参考
顺便记录下写的比较好的博客
C++ Primer Plus (第6版)
C++虚函数表
C++内存模型
关于vtordisp知多少?
【VC++】虚函数 内存结构 - 第四篇(多重继承,无虚函数覆盖)
C++ 虚函数表剖析
虚函数
静态联编: 在编译过程中函数实现与函数关联
动态联编: 在程序执行阶段函数实现和调用关联
基类中没有把函数声明为虚, 根据指针类型调用调用对应的函数实现, 编译器对非虚方法使用静态联编
基类中把函数声明位虚, 根据对象类型调用对应的函数实现, 编译器对虚方法使用动态联编
向上强制转换: 派生类的引用或指针转换为基类引用或指针
向下强制转换: 基类的引用或指针转换为派生类的引用或指针(不能隐式转换, 会导致不安全)
虚函数: 在基类方法的神明中使用关键字virtual可使改方法在基类以及所有派生类(包括从派生类派生出来的类)中是虚的
使用指向对象的引用或指针来调用虚方法, 程序会使用对象类型定义的方法, 而不是引用或指针类型定义的方法
#include <iostream>
using namespace std;
class A {
public:
A(int a):m_a(a) {
cout << "A地址:" << this << endl;
}
virtual void Show() {
cout << "A::Show()" << endl;
}
public:
int m_a;
};
class B : public A{
public:
B(int a, int b):A(a), m_b(b) {
cout << "B 地址:" << this << endl;
}
virtual void Show() {
cout << "B::Show()" << endl;
}
public:
int m_b;
};
void fr(A& rb) {
rb.Show();
}
void fp(A* rb) {
rb->Show();
}
void fv(A a) {
a.Show();
}
int main() {
A a(10);
B b(10, 11);
fr(a); // 使用 A::Show()
fr(b); // 使用 B::Show()
fp(&a); // 使用 A::Show()
fp(&b); // 使用 B::Show()
fv(a); // 使用 A::Show()
fv(b); // 使用 A::Show()
}
虚函数的注意事项
虚函数内存和执行速度方面有一定成本:
- 每个对象都会增大, 增大存储地址的空间
- 每个类, 编译器都创建一个虚函数地址表
- 对于每个函数调用, 都需要执行一项额外的操作, 表中查地址
构造函数
构造函数不能是虚函数, 创建派生类对象, 将先调用基类的构造函数, 在调用派生类的构造函数
析构函数
析构函数需要为虚函数, 默认静态联编, delete会调用指针类型的析构函数, 释放派生类对象中基类指向的内存, 不会释放派生类指向的内存
析构函数时虚的, 则会先调用对象析构函数先释放派生类指向的内存, 再调用基类析构函数释放基类指向的内存
友元函数
友元不能是虚函数, 友元不是类成员, 只有成员才能是虚函数, 可以用过友元函数使用虚成员函数来解决
派生类重新定义函数, 不会生成函数的两个重载版本, 会隐藏基类所有同名的类方法
- 重新定义继承的方法, 确保与原来原型完全相同, 如果返回类型是基类引用或指针, 则可以修改为指向派生类的引用或指针, 这种特性称为返回类型协变(convariance of return type)
- 基类声明被重载, 则应再派生类中重新定义所有的基类版本, 只定义一个, 则另外两个版本将被隐藏
虚函数表
编译器处理虚函数是给对象添加隐藏成员, 隐藏成员中保存一个指向函数地址数组的指针, 这个数组称为虚函数表(virtual function table, vtbl)
虚函数表存储了为类进行声明的虚函数的地址
1. 同类对象的虚函数表
#include <iostream>
using namespace std;
class A {
public:
virtual void func2() {
cout << "A func2" << endl;
}
virtual void func1() {
cout << "A func1" << endl;
}
virtual void func3() {
cout << "A func3" << endl;
}
};
int main(){
A a;
A a1;
return 0;
}
会发现同一类的对象用的虚函数是相同的, 都指向一个指针
2. 继承(无虚函数覆盖)
#include <iostream>
using namespace std;
class A {
public:
virtual void func2() {
cout << "A func2" << endl;
}
virtual void func1() {
cout << "A func1" << endl;
}
virtual void func3() {
cout << "A func3" << endl;
}
};
class B : public A {
public:
virtual void func4() {
cout << "B func4" << endl;
}
virtual void func5() {
cout << "B func5" << endl;
}
virtual void func6() {
cout << "B func6" << endl;
}
};
int main(){
A a;
B b;
return 0;
}
基类虚函数在子类虚函数前面, 虚函数按照声明顺序放于表中
3. 继承(虚函数覆盖)
#include <iostream>
using namespace std;
class A {
public:
virtual void func2() {
cout << "A func2" << endl;
}
virtual void func1() {
cout << "A func1" << endl;
}
virtual void func3() {
cout << "A func3" << endl;
}
};
class B : public A {
public:
virtual void func6() {
cout << "B func6" << endl;
}
virtual void func1() {
cout << "B func1" << endl;
}
virtual void func2() {
cout << "B func3" << endl;
}
virtual void func7() {
cout << "B func6" << endl;
}
};
派生类的函数被放到原来基类虚函数的位置(覆盖)
虚函数表添加顺序还是先基类, 后按声明顺序添加派生类的虚函数
4. 多重继承(无虚基类, 无虚函数覆盖)
#include <iostream>
using namespace std;
class A {
public:
virtual void func2() {
cout << "A func2" << endl;
}
virtual void func1() {
cout << "A func1" << endl;
}
virtual void func3() {
cout << "A func3" << endl;
}
};
class AA {
public:
virtual void func2_AA() {
cout << "AA func2" << endl;
}
virtual void func1_AA() {
cout << "AA func1" << endl;
}
virtual void func3_AA() {
cout << "AA func3" << endl;
}
};
class B : public A, public AA {
public:
virtual void func6() {
cout << "B func6" << endl;
}
virtual void func4() {
cout << "B func4" << endl;
}
virtual void func5() {
cout << "B func3" << endl;
}
virtual void func7() {
cout << "B func6" << endl;
}
};
int main(){
AA aa;
A a;
B b;
return 0;
}
派生类有两个虚函数表, 根据继承先后排列, 派生类的虚函数添加到第一个继承基类的虚函数表中
5. 多重继承(无虚基类, 有虚函数覆盖)
#include <iostream>
using namespace std;
class A {
public:
virtual void func2() {
cout << "A func2" << endl;
}
virtual void func1() {
cout << "A func1" << endl;
}
virtual void func3() {
cout << "A func3" << endl;
}
};
class AA {
public:
virtual void func2_AA() {
cout << "AA func2" << endl;
}
virtual void func1_AA() {
cout << "AA func1" << endl;
}
virtual void func3_AA() {
cout << "AA func3" << endl;
}
};
class B : public A, public AA {
public:
virtual void func1() {
cout << "B func1" << endl;
}
virtual void func4() {
cout << "B func4" << endl;
}
virtual void func5() {
cout << "B func3" << endl;
}
virtual void func2_AA() {
cout << "B func2" << endl;
}
};
int main(){
AA aa;
A a;
B b;
return 0;
}
6. 多重继承(有虚基类, 无虚函数覆盖)文章来源:https://www.toymoban.com/news/detail-428985.html
#include <iostream>
using namespace std;
class Base {
public:
Base(int base):m_base(base){ cout << "Base地址:" << this << endl; }
virtual void func10() {
cout << "Base func10" << endl;
}
public:
int m_base;
};
class A: virtual public Base {
public:
A(const Base& base, int a): Base(base), m_a(a) { cout << "A的地址:" << this << endl;}
virtual void func2() {
cout << "A func2" << endl;
}
virtual void func1() {
cout << "A func1" << endl;
}
virtual void func3() {
cout << "A func3" << endl;
}
public:
int m_a;
};
class AA: virtual public Base {
public:
AA(const Base& base, int aa) : Base(base), m_aa(aa) { cout << "AA的地址:" << this << endl; }
virtual void func2_AA() {
cout << "AA func2" << endl;
}
virtual void func1_AA() {
cout << "AA func1" << endl;
}
virtual void func3_AA() {
cout << "AA func3" << endl;
}
public:
int m_aa;
};
class B : public A, public AA{
public:
B(const Base& base, int a, int aa) : Base(base), A(base, a), AA(base, aa) { cout << "B的地址:" << this << endl; }
virtual void func11() {
cout << "B func11" << endl;
}
public:
int m_b;
};
int main(){
B b(Base(2), 10, 11);
return 0;
}
多了vbptr和vbtable
A对应vbptr指针位置 + vbtable[0][0] = A对应vfptr的指针位置(4 + -4 = 0)
A对应vbptr指针位置 + vbtable[0][1] = Base对应vfptr的指针位置(4 + 24 = 28)文章来源地址https://www.toymoban.com/news/detail-428985.html
int** t = (int**)(int*)(((int*)&b + 1) + 0); // 指向A的vbtable
cout << t[0][0] << endl; // -4
int** baseFun = (int**)((int*)&b + 7); // 指向base的vbptr
cout << baseFun[0][0] << endl; // 7607512
pFun = (Fun)(baseFun[0][0]); // 指向Base::func10()函数的地址
pFun(); // Base func10
到了这里,关于C++基础 虚函数的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!