您现在的位置是:首页 >技术杂谈 >C++基础 虚函数网站首页技术杂谈

C++基础 虚函数

zbbzb 2023-06-26 00:00:02
简介C++基础 虚函数

参考

顺便记录下写的比较好的博客
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会调用指针类型的析构函数, 释放派生类对象中基类指向的内存, 不会释放派生类指向的内存
析构函数时虚的, 则会先调用对象析构函数先释放派生类指向的内存, 再调用基类析构函数释放基类指向的内存

友元函数
友元不能是虚函数, 友元不是类成员, 只有成员才能是虚函数, 可以用过友元函数使用虚成员函数来解决

派生类重新定义函数, 不会生成函数的两个重载版本, 会隐藏基类所有同名的类方法

  1. 重新定义继承的方法, 确保与原来原型完全相同, 如果返回类型是基类引用或指针, 则可以修改为指向派生类的引用或指针, 这种特性称为返回类型协变(convariance of return type)
  2. 基类声明被重载, 则应再派生类中重新定义所有的基类版本, 只定义一个, 则另外两个版本将被隐藏
    在这里插入图片描述

虚函数表

编译器处理虚函数是给对象添加隐藏成员, 隐藏成员中保存一个指向函数地址数组的指针, 这个数组称为虚函数表(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. 多重继承(有虚基类, 无虚函数覆盖)

#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 = 0)

	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
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。