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;