c++类的内存结构(单继承)

来源:互联网 发布:数据加密的目的是 编辑:程序博客网 时间:2024/06/05 04:04

关于c++类的内存的文章在网上有许多,但是,大多是一段段代码和说明,偶尔有图文并茂的,总觉不是很详细,没有一目了然之感觉

并且大部分也没有涉及虚析构,也没有说明访问标号的影响(虽然事实是没有影响)。而这块内容对于理解多态、重写、继承的构造和析构又是足够重要的。

终于下定决心,自己动手,在参考多方资料后,终有此文。

一、基类内存结构

1.1 不带虚函数、虚析构的基类:

class Base{public:Base():i(3),j(4){}int i;private:int j;};
这是最简单的情况,直接给图:

数据成员的排列将基于声明顺序,尽管数据成员j是私有的,这并没有什么影响。

1.2 不带虚函数、带虚析构的基类:

class Base{public:Base():i(3),j(4){}virtual ~Base(){}int i;private:int j;};
稍复杂一点,像是这样:


多了一个虚函数表__vfptr[],它可以看做是一个指向函数的指针数组(void**),其成员都是指向函数的指针(void*),更确切的说,都指向此类的虚函数,除了最后一个结束标记(值为0,这里编译器是vs,其他编译器可能有所不同),大小就是此类中含虚函数的个数(含继承的)+1(结束标记),测试程序将在后面给出。

图中&bObj是Base类的一个对象bObj的地址,图中可以看到它的值是虚表的地址,那么&bObj的类型(void***),是指向虚表__vfptr的指针。

因为我们显式声明了虚析构,所以虚表的大小是2,但是__vfptr[0]并非像预想中的直接指向~Base();而是指向“析构代理函数”。

所谓析构代理函数,是编译器替我们生成的虚成员函数,它会动态的调用类的对应析构函数,并做析构相关的额外一些其他事情。

其类似实现原理参看这里:http://blog.csdn.net/jiangdf/article/details/8917020

1.3 带虚函数、不带虚析构的基类:

class Base{public:Base():i(3),j(4){} virtual void f() { std::cout << "Base::f" << std::endl; } virtual void g() { std::cout << "Base::g" << std::endl; }int i;private:virtual void h() { std::cout << "Base::h" << std::endl; }int j;};
这个基类有些怪异,含一个私有的虚函数,这比较少见,虽然他将被派生类继承,但不能被直接访问。更重要的是它没有虚析构函数,这是一种错误,为了全面起见,

罗列在此。

内存图将是这样的:

也是按照声明的顺序,虚函数表依次指向各虚函数,虽然h是私有的,这也没什么影响

1.4 含虚函数与虚析构的基类

class Base{public:Base():i(3),j(4){}virtual ~Base(){} virtual void f() { std::cout << "Base::f" << std::endl; } virtual void g() { std::cout << "Base::g" << std::endl; }        int i;private:        virtual void h() { std::cout << "Base::h" << std::endl; }int j;};
这是最复杂、最正常的基类,图如下:

为了清晰起见,图中给出某次运行时的实际地址,这里相邻地址总是正好4字节,因为我的系统int与指针类型是4字节,

如果是其他类型,未必是4,还可能要考虑内存对齐的问题,不属于这篇文章的范畴……

正常基类的虚表总是以虚析构代理函数开头、中间接虚函数、以结束标记结尾,就算f比~Base()先声明,结果也是一样。

1.5 含纯虚函数的抽象基类

class Base{public:Base():i(3),j(4){}virtual ~Base(){} virtual void f()=0; virtual void g(){ std::cout << "Base::g" << std::endl; }int i;private:virtual void h(){ std::cout << "Base::h" << std::endl; }int j;};

由于抽象基类的对象没有办法构造,其指针类型也只能指向派生类实例,所以似乎没有办法测试基类的内存结构,但可以从派生类对象上大体推断:

其内存结构与1.4的图并无区别

二、派生类内存结构

2.1 继承自抽象类的(1.5)、重写纯虚函数的派生类

class Derived: public Base{public:virtual void f() { std::cout << "Derived::f" << std::endl; }};
这里是为了承接1.5的"推断",给个最为简单的测试代码:

Derived dObj;Base* pb = &dObj;
调试,展开pb之+,可以看到:

这里虽然是dObj的虚表, 但基本可以证明Base的虚表内存结构(将图中Derived换成Base即可)

2.2 继承自1.4,重写f、添加自己的虚函数和数据成员的派生类

class Derived: public Base{public:Derived():k(5){}virtual void f() { std::cout << "Derived::f" << std::endl; }virtual void f1() { std::cout << "Derived::f1" << std::endl; }private:virtual void g1() { std::cout << "Derived::g1" << std::endl; }int k;};
这个是最正常用法,先上图

是时候给出测试代码了:

void Base::test(){typedef void (*Destructor)(unsigned int); // 函数指针typedef void(*Fun)(void); // 函数指针Destructor pfna, pfn0;Fun pfnb, pfnc, pfnd, pfn1, pfn2, pfn3, pfn4, pfn5;std::cout << "--------------------Base-------------------" << std::endl;Base bObj;// Base data member:// __vfptr指针的地址(void ***):__vfptr(void**)是指针数组(void *[]),成员是指向虚函数的指针(void *)std::cout << "指向Base虚函数表地址:__vfptr地址" << (int*)(&bObj) << std::endl; std::cout << "Base数据成员1(i)地址:" << (int*)(&bObj)+1 << std::endl;std::cout << "Base数据成员2(j)地址:" << (int*)(&bObj)+2 << std::endl;std::cout << "----------对应的值-----------" << std::endl;std::cout << "__vfptr值(vfptr[0]地址):" << (int*)*(int*)(&bObj) << std::endl; // void **std::cout << "i:" << *((int*)(&bObj)+1) << std::endl;std::cout << "j:" << *((int*)(&bObj)+2) << std::endl;std::cout  << std::endl;// Base virtual function member:std::cout << "----------Base虚函数表-----------" << std::endl; // void **类型(void*[])std::cout << "__vfptr[0]地址:" << (int*)*(int*)(&bObj) << std::endl; // void **, __vfptr[0](void *)std::cout << "__vfptr[1]地址:" << (int*)*(int*)(&bObj)+1 << std::endl; // 指向函数的指针所在地址std::cout << "__vfptr[2]地址:" << (int*)*(int*)(&bObj)+2 << std::endl;std::cout << "__vfptr[3]地址:" << (int*)*(int*)(&bObj)+3 << std::endl;std::cout << "__vfptr[4]地址:" << (int*)*(int*)(&bObj)+4 << std::endl;std::cout << "----------对应的值-----------" << std::endl; // void *类型 // 编译器自动生成的虚函数:void vector_deleting_destructor(unsigned int),动态处理对象的析构,调用~Base()std::cout << "__vfptr[0]值(析构的代理虚函数地址):" << (int*)*(int*)*(int*)(&bObj) << std::endl;std::cout << "__vfptr[1]值(第一个虚函数地址):" << (int*)*((int*)*(int*)(&bObj)+1) << std::endl; // void *:“指向虚函数的指针”,虚函数入口所在地址std::cout << "__vfptr[2]值(第二个虚函数地址):" << (int*)*((int*)*(int*)(&bObj)+2) << std::endl;std::cout << "__vfptr[3]值(第三个虚函数地址):" << (int*)*((int*)*(int*)(&bObj)+3) << std::endl;std::cout << "__vfptr[4]值(结束标记):" << *((int*)*(int*)(&bObj)+4) << std::endl; // 0std::cout << "------------调用虚函数-------------" << std::endl;pfna = (Destructor)*((int*)*(int*)(&bObj)); // Base::`vector_deleting_destructor(unsigned int)pfnb = (Fun)*((int*)*(int*)(&bObj)+1); // 指针解引用*(void**)-->指向函数入口指针(void*)-->(Fun)强制转换为函数指针pfnc = (Fun)*((int*)*(int*)(&bObj)+2);pfnd = (Fun)*((int*)*(int*)(&bObj)+3);pfnb(); // Base::fpfnc(); // Base::gpfnd(); // Base::hstd::cout  << std::endl << std::endl;std::cout << "--------------------Derived-------------------" << std::endl;Derived dObj;Base *pb = &dObj;// Derived data member:std::cout << "指向Derived虚函数表地址:__vfptr地址" << (int*)pb << std::endl; // (int*)pb:(int*)(&dObj)std::cout << "Derived中Base数据成员1(i)地址:" << (int*)pb+1 << std::endl;std::cout << "Derived中Base数据成员2(j)地址:" << (int*)pb+2 << std::endl;std::cout << "Derived数据成员(k)地址:" << (int*)pb+3 << std::endl;std::cout << "----------对应的值-----------" << std::endl;std::cout << "__vfptr值(vfptr[0]地址):" << (int*)*(int*)pb << std::endl;std::cout << "i:" << *((int*)pb+1) << std::endl;std::cout << "j:" << *((int*)pb+2) << std::endl;std::cout << "k:" << *((int*)pb+3) << std::endl;std::cout  << std::endl;// Derived virtual function member:std::cout << "----------Derived虚函数表------------" << std::endl;std::cout << "__vfptr[0]地址:" << (int*)*(int*)pb << std::endl;std::cout << "__vfptr[1]地址:" << (int*)*(int*)pb+1 << std::endl;std::cout << "__vfptr[2]地址:" << (int*)*(int*)pb+2 << std::endl;std::cout << "__vfptr[3]地址:" << (int*)*(int*)pb+3 << std::endl;std::cout << "__vfptr[4]地址:" << (int*)*(int*)pb+4 << std::endl;std::cout << "__vfptr[5]地址:" << (int*)*(int*)pb+5 << std::endl;std::cout << "__vfptr[6]地址:" << (int*)*(int*)pb+6 << std::endl;std::cout << "----------对应的值-----------" << std::endl;std::cout << "__vfptr[0]值(析构的代理虚函数地址):" << (int*)*(int*)*(int*)pb << std::endl;std::cout << "__vfptr[1]值(第一个虚函数地址):" << (int*)*((int*)*(int*)pb+1) << std::endl;std::cout << "__vfptr[2]值(第二个虚函数地址):" << (int*)*((int*)*(int*)pb+2) << std::endl;std::cout << "__vfptr[3]值(第三个虚函数地址):" << (int*)*((int*)*(int*)pb+3) << std::endl;std::cout << "__vfptr[4]值(第四个虚函数地址):" << (int*)*((int*)*(int*)pb+4) << std::endl;std::cout << "__vfptr[5]值(第五个虚函数地址):" << (int*)*((int*)*(int*)pb+5) << std::endl;std::cout << "__vfptr[6]值(结束标记):" << *((int*)*(int*)pb+6) << std::endl; // 0std::cout << "------------调用虚函数-------------" << std::endl;pfn0 = (Destructor)*(int*)*(int*)pb; // Derived::`vector deleting destructor'(unsigned int)pfn1 = (Fun)*((int*)*(int*)pb+1);pfn2 = (Fun)*((int*)*(int*)pb+2);pfn3 = (Fun)*((int*)*(int*)pb+3);pfn4 = (Fun)*((int*)*(int*)pb+4);pfn5 = (Fun)*((int*)*(int*)pb+5);((Fun)*((int*)*(int*)pb+1))(); // Derived::f((Fun)*((int*)*(int*)pb+2))(); // Base::g((Fun)*((int*)*(int*)pb+3))(); // Base::h((Fun)*((int*)*(int*)pb+4))(); // Derived::f1((Fun)*((int*)*(int*)pb+5))(); // Derived::g1}
它声明在Base类中,是static成员函数,作为一种测试用例,而又不失封装性:

static void test();
调用方式很简单:

int main(){Base::test();return 0;}    

这个测试代码将(以内存方式)打印基类和派生的数据表的地址、数据表的值、虚表的地址、虚表的值、并且调用虚表指向的虚函数

它将完全的验证1.4、2.2的两张图。
由于Derived类继承了Base类,所以以下语句:

Derived dObj;Base *pb = &dObj;
指向派生类的基类指针:pb指向的便是图中dObj对象的地址,由于pb的类型是Base*,通过它的所有调用将被严格限制在“派生类的基类部分”,即图中绿色框中的部分。

这便是通过pb访问的只能是Base所拥有的成员(或继承它的)的原因了。如果pb是Derived*类型,它就可以访问整个Derived。

派生类对象构造:因为每个派生类的开头总有基类部分,所以先构造基类的、再派生类

派生类的虚表:从图中可以看出派生类的虚函数表不止包含自身显式定义的,还包括隐式从基类继承的(含重写的)。按先基类、再派生类(声明顺序)

派生类的析构:由于Base声明了虚析构,派生类的析构默认也是虚的,所以虚表开头是派生类的虚析构代理函数,将由它确保对象析构的正确操作:先派生类的、再基类的

重写与多态:虚表的第二项标示的很清楚,原本应该是Base::f()的那部分被Derived::f()所取代,通过pb访问到的是实际指向的f,所以是Derived::f()。pb自然可以指向Base对象,而Base的虚表中是Base::f()。如果Derived没有重写Base中的虚函数,则Derived虚表中的对应位置是Base虚函数。

私有的虚函数被继承:虚表中含Base::h(),并且通过内存方式可被调用。

私有的数据成员被继承:如j。

调试,虚表:

Base:


Derived:

2.3 继承自1.3中的基类

(1)Derived不含虚析构:

代码同2.2,上图:


基类虚析构函数的重要性:上图基本同2.2,只不过此时没有了析构代理虚函数,实际指向派生类对象的指针或引用析构时将导致错误的行为:pb析构将只调用Base的析构函数,所以k所在的内存没有释放,如果pb是堆上分配的,就是内存泄露问题;

(2)如果Derived显式定义了虚析构:

代码:添加一句:virtual ~Derived() {}

含虚析构的Derived内存图:

特别注意:Derived析构代理函数的位置,是在中间,并非开头

结果仍然是一样的,虽有Derived析构代理函数,但是不能改变Base的析构函数非虚这一事实。甚至实际指向Derived后代类的基类引用或指针对象也只能调用Base构造函数。事实上pb的范围(绿框)不包含Derived析构代理虚函数。通过pb没有办法调用到它。

可能有必要提供测试代码:

void Base::test8(){typedef void (*Destructor)(unsigned int);Destructor pfn3;typedef void(*Fun)(void); // 函数指针Fun pfna, pfnb, pfnc, pfn0, pfn1, pfn2, pfn4, pfn5;std::cout << "--------------------Base-------------------" << std::endl;Base bObj;// Base data member:// __vfptr指针的地址(void ***):__vfptr(void**)是指针数组(void *[]),成员是指向虚函数的指针(void *)std::cout << "指向Base虚函数表地址:__vfptr地址" << (int*)(&bObj) << std::endl; std::cout << "Base数据成员1(i)地址:" << (int*)(&bObj)+1 << std::endl;std::cout << "Base数据成员2(j)地址:" << (int*)(&bObj)+2 << std::endl;std::cout << "----------对应的值-----------" << std::endl;std::cout << "__vfptr值(vfptr[0]地址):" << (int*)*(int*)(&bObj) << std::endl; // void **std::cout << "i:" << *((int*)(&bObj)+1) << std::endl;std::cout << "j:" << *((int*)(&bObj)+2) << std::endl;std::cout  << std::endl;// Base virtual function member:std::cout << "----------Base虚函数表-----------" << std::endl; // void **类型(void*[])std::cout << "__vfptr[0]地址:" << (int*)*(int*)(&bObj) << std::endl; // void **, __vfptr[0](void *)std::cout << "__vfptr[1]地址:" << (int*)*(int*)(&bObj)+1 << std::endl; // 指向函数的指针所在地址std::cout << "__vfptr[2]地址:" << (int*)*(int*)(&bObj)+2 << std::endl;std::cout << "__vfptr[3]地址:" << (int*)*(int*)(&bObj)+3 << std::endl;std::cout << "----------对应的值-----------" << std::endl; // void *类型 std::cout << "__vfptr[0]值(第一个虚函数地址):" << (int*)*(int*)*(int*)(&bObj) << std::endl; // void *:“指向虚函数的指针”,虚函数入口所在地址std::cout << "__vfptr[1]值(第二个虚函数地址):" << (int*)*((int*)*(int*)(&bObj)+1) << std::endl;std::cout << "__vfptr[2]值(第三个虚函数地址):" << (int*)*((int*)*(int*)(&bObj)+2) << std::endl;std::cout << "__vfptr[3]值(结束标记):" << *((int*)*(int*)(&bObj)+3) << std::endl;// 0std::cout << "------------调用虚函数-------------" << std::endl;pfna = (Fun)*((int*)*(int*)(&bObj)); // 指针解引用*(void**)-->指向函数入口指针(void*)-->(Fun)强制转换为函数指针pfnb = (Fun)*((int*)*(int*)(&bObj)+1);pfnc = (Fun)*((int*)*(int*)(&bObj)+2);pfna(); // Base::fpfnb(); // Base::gpfnc(); // Base::hstd::cout  << std::endl << std::endl;std::cout << "--------------------Derived-------------------" << std::endl;Derived dObj;Base *pb = &dObj;// Derived data member:std::cout << "指向Derived虚函数表地址:__vfptr地址" << (int*)pb << std::endl; // (int*)pb:(int*)(&dObj)std::cout << "Derived中Base数据成员1(i)地址:" << (int*)pb+1 << std::endl;std::cout << "Derived中Base数据成员2(j)地址:" << (int*)pb+2 << std::endl;std::cout << "Derived数据成员(k)地址:" << (int*)pb+3 << std::endl;std::cout << "----------对应的值-----------" << std::endl;std::cout << "__vfptr值(vfptr[0]地址):" << (int*)*(int*)pb << std::endl;std::cout << "i:" << *((int*)pb+1) << std::endl;std::cout << "j:" << *((int*)pb+2) << std::endl;std::cout << "k:" << *((int*)pb+3) << std::endl;std::cout  << std::endl;// Derived virtual function member:std::cout << "----------Derived虚函数表------------" << std::endl;std::cout << "__vfptr[0]地址:" << (int*)*(int*)pb << std::endl;std::cout << "__vfptr[1]地址:" << (int*)*(int*)pb+1 << std::endl;std::cout << "__vfptr[2]地址:" << (int*)*(int*)pb+2 << std::endl;std::cout << "__vfptr[3]地址:" << (int*)*(int*)pb+3 << std::endl;std::cout << "__vfptr[4]地址:" << (int*)*(int*)pb+4 << std::endl;std::cout << "__vfptr[5]地址:" << (int*)*(int*)pb+5 << std::endl;std::cout << "__vfptr[6]地址:" << (int*)*(int*)pb+6 << std::endl;std::cout << "----------对应的值-----------" << std::endl;std::cout << "__vfptr[0]值(第一个虚函数地址):" << (int*)*((int*)*(int*)pb) << std::endl;std::cout << "__vfptr[1]值(第二个虚函数地址):" << (int*)*((int*)*(int*)pb+1) << std::endl;std::cout << "__vfptr[2]值(第三个虚函数地址):" << (int*)*((int*)*(int*)pb+2) << std::endl;std::cout << "__vfptr[3]值(析构的代理虚函数地址):" << (int*)*((int*)*(int*)pb+3) << std::endl;std::cout << "__vfptr[4]值(第四个虚函数地址):" << (int*)*((int*)*(int*)pb+4) << std::endl;std::cout << "__vfptr[5]值(第五个虚函数地址):" << (int*)*((int*)*(int*)pb+5) << std::endl;std::cout << "__vfptr[6]值(结束标记):" << *((int*)*(int*)pb+6) << std::endl; // 0std::cout << "------------调用虚函数-------------" << std::endl;pfn0 = (Fun)*((int*)*(int*)pb);pfn1 = (Fun)*((int*)*(int*)pb+1);pfn2 = (Fun)*((int*)*(int*)pb+2);pfn3 = (Destructor)*((int*)*(int*)pb+3); // 注意,代理函数在这里pfn4 = (Fun)*((int*)*(int*)pb+4);pfn5 = (Fun)*((int*)*(int*)pb+5);((Fun)*((int*)*(int*)pb))();   // Derived::f((Fun)*((int*)*(int*)pb+1))(); // Base::g((Fun)*((int*)*(int*)pb+2))(); // Base::h((Fun)*((int*)*(int*)pb+4))(); // Derived::f1((Fun)*((int*)*(int*)pb+5))(); // Derived::g1}
调试,虚表:

Derived:


2.4 继承自1.1的派生类

class Derived: public Base{public:Derived():k(5){}        virtual ~Derived(){}        virtual void f1() { std::cout << "Derived::f1" << std::endl; }private:virtual void g1() { std::cout << "Derived::g1" << std::endl; }int k;};

无需基类虚析构函数的情况:一个类不作为基类;或者,如果一个类是基类,又不用于多态,就是你保证不会定义一个指向派生类的基类指针或者引用对象,毕竟,一个派生类的(非指针、引用)对象总能够被正确的析构。

这种情况恰好是无需基类析构函数的一种,基类无虚函数,由此可见1.2的设计虚析构有点冗余

上图:

注意此时的pb与&dObj位置不同,这是因为C++标准规定指向虚表位置必须是类的开头,而此时Base没有虚表(无虚函数)。

pb无所谓多态了,pb虽然实际指向Derived对象dObj,显然不能访问Derived虚函数,显然的,析构pb又将导致遗落k,既然基类没有虚析构,就不该定义指向派生类的基类指针或引用

有必要测试一下:

void Base::test3(){typedef void (*Destructor)(unsigned int);Destructor pfn0;typedef void(*Fun)(void);Fun pfn1, pfn2;std::cout << "--------------------Base-------------------" << std::endl;Base bObj;// Base data member:std::cout << "Base数据成员1(i)地址:" << (int*)(&bObj) << std::endl;std::cout << "Base数据成员2(j)地址:" << (int*)(&bObj)+1 << std::endl;std::cout << "----------对应的值-----------" << std::endl;std::cout << "i:" << *((int*)(&bObj)) << std::endl;std::cout << "j:" << *((int*)(&bObj)+1) << std::endl;std::cout  << std::endl << std::endl;std::cout << "--------------------Derived-------------------" << std::endl;Derived dObj;Base *pb = &dObj; //  注意!指针发生了偏移:(int*)pb-1 == &dObj// Derived data member:std::cout << "指向Derived虚函数表地址:__vfptr地址" << (int*)pb-1 << std::endl;std::cout << "Derived中Base数据成员1(i)地址:" << (int*)pb << std::endl;std::cout << "Derived中Base数据成员2(j)地址:" << (int*)pb+1 << std::endl;std::cout << "Derived数据成员(k)地址:" << (int*)pb+2 << std::endl;std::cout << "----------对应的值-----------" << std::endl;std::cout << "__vfptr值(vfptr[0]地址):" << (int*)*((int*)pb-1) << std::endl; // 注意!指针发生了偏移std::cout << "i:" << *((int*)pb) << std::endl;std::cout << "j:" << *((int*)pb+1) << std::endl;std::cout << "k:" << *((int*)pb+2) << std::endl;std::cout  << std::endl;// Derived virtual function member:std::cout << "----------Derived虚函数表------------" << std::endl;std::cout << "__vfptr[0]地址:" << (int*)*((int*)pb-1) << std::endl;std::cout << "__vfptr[1]地址:" << (int*)*((int*)pb-1)+1 << std::endl;std::cout << "__vfptr[2]地址:" << (int*)*((int*)pb-1)+2 << std::endl;std::cout << "__vfptr[3]地址:" << (int*)*((int*)pb-1)+3 << std::endl;std::cout << "----------对应的值-----------" << std::endl;std::cout << "__vfptr[0]值(析构的代理虚函数地址):" << (int*)*((int*)*((int*)pb-1)) << std::endl;std::cout << "__vfptr[1]值(第一个虚函数地址):" << (int*)*((int*)*((int*)pb-1)+1) << std::endl;std::cout << "__vfptr[2]值(第二个虚函数地址):" << (int*)*((int*)*((int*)pb-1)+2) << std::endl;std::cout << "__vfptr[3]值(结束标记):" << *((int*)*((int*)pb-1)+3) << std::endl;std::cout << "------------调用虚函数-------------" << std::endl;pfn0 = (Destructor)*((int*)*((int*)pb-1));pfn1 = (Fun)*((int*)*((int*)pb-1)+1);pfn2 = (Fun)*((int*)*((int*)pb-1)+2);((Fun)*((int*)*((int*)pb-1)+1))();// Derived::f1((Fun)*((int*)*((int*)pb-1)+2))();// Derived::g1}
调试,虚表:

Derived:

pb与&dObj:(int*)pb - 1 == &dObj;

2.5 继承自1.2的派生类

代码同2.4

直接上图:

此时,pb==&dObj,但pb虚表范围只有一个析构代理函数,定义pb的意图显然不够明显。

至此,本文就要结束了,若有任何错误,欢迎指正。

最后,给一些好的文章链接,供参考:这些条目涵盖了本文未涉及的多重派生的情况

http://www.cnblogs.com/Binhua-Liu/archive/2010/06/16/1759019.html

http://www.uml.org.cn/c%2B%2B/200811143.asp

http://blog.csdn.net/w18767104183/article/details/7006304

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 小儿3岁说话晚怎么办 1岁半了不会说话怎么办 宝宝2岁还说话晚怎么办 一岁宝宝不愿意学说话怎么办 2周岁宝宝不愿意学说话怎么办 三岁宝宝不爱说话应该怎么办 三岁宝宝不理人怎么办 两岁半宝宝不说话不连贯怎么办 宝宝三岁说话口齿不清怎么办 b超宝宝腿短怎么办 两岁宝宝学说话口吃怎么办 两岁宝宝说话少怎么办 三岁宝宝说话少怎么办 快4岁不会说话怎么办 宝宝2岁半不爱说话怎么办 两岁半宝宝注意力不集中怎么办 中国出生的外籍小孩怎么办签证 中国人入外籍后国内财产怎么办 中国人入外籍后国内资产怎么办 小孩去美国上小学怎么办 咳嗽震的胸口疼怎么办 高中孩子对手机着迷怎么办 小孩不肯读书沉迷游戏怎么办 小孩沉迷吃鸡游戏怎么办 高一数学成绩差怎么办 小孩子学数学用手指算怎么办 孩子d和b分不清怎么办 和家人走散后怎么办幼儿教案 小孩胃口不好不爱吃饭怎么办 幼儿园孩子学习记不住怎么办 幼儿园小孩数字记不住怎么办 大班教案走丢了怎么办 ppt加视频反了怎么办 拔罐之后背疼怎么办 拔完火罐后背疼怎么办 打印机红色的口堵了怎么办 打印机红色复印不出来怎么办 打印机加错颜色墨水怎么办 中班安全教案脚扭伤了怎么办 中班脚扭伤了怎么办教案 中班安全脚扭伤了怎么办