c++语言涉及有一个要求:要求对普通成员函数的调不应该比全局函数效率差
基于这种设计要求,编译器内部实际上是对成员函数的调用转换成了对全局函数的调用
成员函数有独立的内存地址,是跟着类走的,并且成员函数的地址是在编译器的时候就确定好了的
#include
#include
class MyClass
{
public:
int m_i;
virtual void virt_func()
{
printf("virt_func call,this = %p\n", this);
}
static void static_func()
{
printf("static_func called\n");
}
};
int main(int argc, char **argv)
{
MyClass obj;
obj.virt_func(); // 用对象调用虚函数,就和调用普通成员函数一样
// 不需要通过虚函数表
MyClass *pobj = new MyClass();
pobj->virt_func(); // 通过虚函数表指针查找虚函数表,通过虚函数表找到虚函数的入库地址,完成对虚函数的调用
// (*pobj->vptr[0])(pobj)
// vptr:编译器给生成的虚函数表指针,指向虚函数表
// [0]:虚函数表中第一项,即virt_func()的地址
// 传递一个参数进去,即this
// *:得到虚函数地址
// 静态成员函数调用方式
obj.static_func();
pobj->static_func();
MyClass::static_func();
// 静态成员函数特性
// 静态成员函数没有this指针
// 无法直接存、取类中普通的非静态成员变量
// 静态成员函数不能在后面使用const也不能设置为virtual
// 可以用类对象调用
// 静态成员函数等同于非成员函数,有需要提供回调函数的地方可以将静态成员函数作为回调函数
return 0;
}
#include
#include
#include
#include
class Base1
{
public:
virtual void f()
{
std::cout << "Base1::f()" << std::endl;
}
virtual void g()
{
std::cout << "Base1::g()" << std::endl;
}
virtual void h()
{
std::cout << "Base1::h()" << std::endl;
}
};
class Base2
{
public:
virtual void b2_func()
{
std::cout << "Base2::b2_func()" << std::endl;
}
};
class Derived : public Base1, public Base2
{
public:
virtual void i()
{
std::cout << "Derived::i()" << std::endl;
}
virtual void g()
{
std::cout << "Derived::g()" << std::endl;
}
void func()
{
std::cout << "Derived::func()" << std::endl;
}
};
int main(int argc, char **argv)
{
// 多继承下的虚函数
// 多重继承的复杂性体现再后面这个基类上(Base2)
Base2 *pb2 = new Derived();
// 编译器视角
/*
Derived *tmp = new Derived();
Base2 *pb2 = (Base2 *)((char *)tmp + sizeof(Base));
*/
// 如何删除用第二类指针new 出来的继承类对象
/*
要删除的实际是整个Derived()对象,需要保证Derived对象的析构函数被正常调用
执行delete pb2;
1. 如果Base2没有析构函数,编译器会直接删除以pb2开始的这段内存,系统一定报
异常,因为这段内存不是new的起始内存
2. 如果Base2里有一个析构函数,但整个析构函数是普通析构函数(非虚析构函数),
那么当执行delete pb2时这个析构函数就会被系统调用,但delete任然是以pb2
开始的这段内存,系统一定报异常,因为这段内存不是new的起始内存。这是因为
(析构函数如果不是虚函数,编译器会实施静态绑定,静态绑定意味着执行
delete pb2时,删除的内存开始地址就是pb2的当前位置)
3. 如果Base2的析构函数是虚析构函数
~Derived()
~Base2()
~Base1()
4. 凡是涉及到继承的,所有类加虚析构函数
*/
return 0;
}
子类继承了几个父类,子类就有几个虚函数表
多重继承下,有机制情况,第二个或者后续的基类会对虚函数的支持产生影响
#include
#include
#include
#include
class Base1
{
public:
virtual void f()
{
std::cout << "Base1::f()" << std::endl;
}
virtual void g()
{
std::cout << "Base1::g()" << std::endl;
}
virtual void h()
{
std::cout << "Base1::h()" << std::endl;
}
virtual ~Base1()
{
std::cout << "Base1::~Base1()" << std::endl;
}
};
class Base2
{
public:
virtual void b2_func()
{
std::cout << "Base2::b2_func()" << std::endl;
}
virtual ~Base2()
{
std::cout << "Base2::~Base2()" << std::endl;
}
};
class Derived : public Base1, public Base2
{
public:
virtual void i()
{
std::cout << "Derived::i()" << std::endl;
}
virtual void g()
{
std::cout << "Derived::g()" << std::endl;
}
void func()
{
std::cout << "Derived::func()" << std::endl;
}
virtual ~Derived()
{
std::cout << "Derived::~Derived()" << std::endl;
}
};
int main(int argc, char **argv)
{
// this指针调整的目的是让对象指针正确指向对象的首地址,
// 从而能正确的调用对象的成员函数或者说正确确定数据成员的存储位置
// 1.通过指向第二个基类的指针调用继承类的虚函数
Base2 *pb2 = new Derived();
delete pb2; //调用继承类的虚析构函数
// 2. 一个指向派生类的指针,调用第二个基类种的虚函数
Derived *pd2 = new Derived();
pd2->b2_func();
// 3. 允许虚函数的返回值类型有所变化
return 0;
}
#include
#include
#include
#include
class Base
{
public:
virtual void f()
{
std::cout << "Base::f()" << std::endl;
}
virtual ~Base()
{
std::cout << "Base::~Base()" << std::endl;
}
int m_base;
};
class Derived : public virtual Base
{
public:
virtual ~Derived()
{
std::cout << "Derived::~Derived()" << std::endl;
}
int m_derive;
};
int main(int argc, char **argv)
{
printf("sizeof(Base) = %ld\n", sizeof(Base));
printf("sizeof(Derived) = %ld\n", sizeof(Derived));
printf("Base::m_base offset = %d\n", &Base::m_base);
printf("Derived::m_derive offset = %d\n", &Derived::m_derive);
Derived *pd = new Derived();
pd->m_derive = 0xdd;
Base *pb = (Base *)pd;
pb->m_base = 0xbb;
return 0;
}
gdb查看
gdb main
(gdb) set height 0
(gdb) set print pretty on
(gdb) set print vtbl on
(gdb) set print object on
(gdb) b 47
Breakpoint 1 at 0x12f7: file main.cpp, line 47.
(gdb) r
sizeof(Base) = 16
sizeof(Derived) = 32
Base::m_base offset = 8
Derived::m_derive offset = 8
Breakpoint 1, main (argc=1, argv=0x7fffffffdbc8) at main.cpp:47
47 return 0;
(gdb) p pb
$1 = (Derived *) 0x55555556b2c0
(gdb) p pd
$2 = (Derived *) 0x55555556b2c0
(gdb) p *pd
$3 = (Derived) {
<Base> = {
_vptr.Base = 0x555555557cf0 <vtable for Derived+72>,
m_base = 187
},
members of Derived:
_vptr.Derived = 0x555555557cc0 <vtable for Derived+24>,
m_derive = 221
}
(gdb) info vtbl *pd
vtable for 'Base' @ 0x555555557cf0 (subobject @ 0x55555556b2d0):
[0]: 0x555555555372 <Base::f()>
(gdb) x/8xw pd
0x55555556b2c0: 0x55557cc0 0x00005555 0x000000dd 0x00000000
0x55555556b2d0: 0x55557cf0 0x00005555 0x000000bb 0x00000000
由gdb信息可知pd的内存布局为: vptr_derived| m_derive| padding4 | vptr_base | m_base | padding4 |
c++运行时类型识别RTTI要求父类中至少有一个虚函数
RTTI就可以再执行期间查询一个多态指针,或者多态引用信息
RTTI能力靠typeid和dynamic_cast运算符来体现
RTTI实现原理
typeid返回的是一个常量对象的引用,这个常量对象的类型一般是type_info(类)