复习——虚函数表的实现&typeid

class Base {
public:
	virtual void f() { cout << "Base::f" << endl; }
	virtual int g() { cout << "Base::g" << endl; return 0; }
	virtual int h(int a) { cout << "Base::h" << endl; return 0; }

};

int main()
{
	typedef void(_stdcall*Fun1)(void);//函数指针
	typedef int(_stdcall*Fun2)(void);
	typedef int(_stdcall*Fun3)(int);
	Base b;
	Fun1 pFun1 = NULL;
	Fun2 pFun2 = NULL;
	Fun3 pFun3 = NULL;
	cout << "vptr的地址:" << (int*)&b << endl;
	cout << "虚函数表(也即第一个函数地址)的地址:" << (int*) *(int*)&b << endl;
	pFun1 = (Fun1) *((int*) *(int*)&b);
	pFun1();
	pFun2 = (Fun2) *((int*) *(int*)&b + 1);
	pFun2();
	pFun3 = (Fun3) *((int*) *(int*)&b + 2);
	pFun3(1);

	system("pause");
	return 0;
}

在这里插入图片描述

typeid

参考:typeid详解
typeid是C++的关键字之一,等同于sizeof这类的操作符。typeid操作符的返回结果是名为type_info的标准库类型的对象的引用。
type_info类提供了public虚析构函数,以使用户能够用其作为基类。它的默认构造函数和拷贝构造函数及赋值操作符都定义为private,所以不能定义或复制type_info类型的对象。程序中创建type_info对象的唯一方法是使用typeid操作符(由此可见,如果把typeid看作函数的话,其应该是type_info的 友元)。type_info的name成员函数返回C-style的字符串,用来表示相应的类型名。
编译器在编译阶段无法确定pb指向哪个对象,也就无法获取*pb的类型信息,但是编译器可以在编译阶段做好各种准备,这样程序在运行后可以借助这些准备好的数据来获取类型信息。
编译器会在虚函数表vtable的开头(-1位置)插入一个指向type_info对象的指针。程序运行时通过对象指针pb找到派生类的虚函数表指针vptr,再通过vptr找到type_info对象的指针,进而获得类型信息。

对于有虚函数的类,typeid和dynamic_cast都会去查询虚函数表中的type_info,例子如下:

#include<iostream>
#include<string>
using namespace std;
int main()
{
	int i = 1;
	string s = "shenhang";
	const type_info&t1 = typeid(i);
	const type_info&t2 = typeid(s);
	const char*a = t1.name();
	const char*b = t2.name();
	cout << a << endl;
	cout << b << endl;
	system("pause");
	return 0;
}

在这里插入图片描述

#include <iostream>
using namespace std;

class Base {};
class Derived : public Base {};

int main()
{
	Derived d;
	Base *pb = &d;
	Base &rb = d;
	cout
		<< typeid(pb).name() << endl//class Base*
		<< typeid(*pb).name() << endl//class Base
		<< typeid(rb).name() << endl;//class Base
	system("pause");
	return 0;
}

在这里插入图片描述

#include <iostream>
using namespace std;

class Base {
public:
	virtual ~Base() {}
};
class Derived : public Base {};

int main()
{


	Derived d;
	Base *pb = &d;
	Base &rb = d;
	cout
		<< typeid(pb).name() << endl//class Base*
		<< typeid(*pb).name() << endl//class Derived
		<< typeid(rb).name() << endl;//class Derived
	system("pause");
	return 0;
}

在这里插入图片描述

基类指针指向派生类对象的情况(动态联编的实现原理!!!)

在这里插入图片描述

类对象的存储方式

内存分布大致如下:
1.类对象中,只有成员变量与vptr.
2.普通成员函数在内存的某一位置放着。它们与c语言中定义的普通函数没有区别。 当我们通过对象或对象指针调用普通成员函数时, 编译器会拿到它。怎么拿到呢?当然是通过名字了,编译器都会对我们写的函数的名字进行修饰映射,让它们变成内存中唯一的函数名。
3.无论基类还是子类,每一种类类型的虚函数表只有一份,它里面存放了基类的类型信息和指向基类中的虚函数的指针。 某一类类型的所有对象都指向了相同的虚函数表。

class base
{
public:
    virtual ~base() {};
    virtual string GetName() { return "base"; }
    GetA();
    int a;
};

class derived : public base
{
public:
    virtual ~derived() {};
    virtual string GetName() { return "derived";}
    GetB();
    int b;
};base B1, B2;derived D1, D2;

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值