网站建设案例教程,河北建设厅查询网站首页,刷关键词怎么刷,开源手机网站建站系统目录 基类和派生类的内存分配基类和派生类的成员归属多态的实现 基类和派生类的内存分配
类包括成员变量#xff08;data member#xff09;和成员函数#xff08;member function#xff09;。 成员变量分为静态数据#xff08;static data#xff09;和非静态数据data member和成员函数member function。 成员变量分为静态数据static data和非静态数据non-static data成员函数分为静态成员函数static function、非静态成员函数non-static function和虚拟成员函数vritual function。
C编译器将类的静态数据、静态成员函数以及非静态成员函数存储在类对象存储空间之外并且无论该类声明了多少对象在内存中只存有一份。 虚拟成员函数也存储在类对象存储空间之外编译器为每个虚拟成员函数产生一个指针并将这些指针存储在一个被称为虚基表的表格中。
类对象的存储空间中包括非静态数据以及指向虚基表的指针。 看个例子为了方便观察做个字节对齐。
//4字节对齐
#pragma pack(push, 4)class C
{int age;static int year;//静态成员变量不占用类的空间
public:C(){age 12;printf(C()\n);}~C() default;
// virtual ~C() default;//定义了虚函数则是多态类,会生成虚函数地址表void TestFunc(){printf(age%d\n,age);}
};class D : public C
{int price;
public:D(){price 2000;printf(D()\n);}void TestFunc(){printf(price%d\n,price);}
};
#pragma pack(pop)//测试调用D dd;printf(sizeof(C)%ld\n,sizeof(C));printf(sizeof(D)%ld\n,sizeof(D));打印
sizeof(C)4
sizeof(D)8观察一非多态类 类C的析构函数不是虚函数此时C和D都不是多态类也就是普通的类。类的大小就是非静态成员变量的大小之和。 观察D dd的内存分配
dd 0x7fffffffe388 D[C] 0x7fffffffe388 Cage 12 intyear optimized out price 2000 int观察dd占用的内存共8字节前4字节是0c转换成十进制是12也就是基类C中age的大小后4字节转换成十进制是2000也就是派生类D中price的大小。
0c 00 00 00 d0 07 00 00观察二多态类 把上面例子基类C的虚函数定义为virtual虚函数则C和D是多态类打印
sizeof(C)12
sizeof(D)16C和D的大小分别比非多态类大了8字节多的8字节其实是指向虚函数表的指针看下面比上面多了个vptr。
dd 0x7fffffffe380 D[C] 0x7fffffffe380 C[vptr] _vptr.C age 12 intyear optimized out price 2000 int观察dd占用的内存共16字节前8字节是指向虚函数表的指针后8字节的前4字节转换10进制是12也就是基类C中age的大小后4字节转换成十进制是2000也就是派生类D中price的大小。
50 d9 55 55 55 55 00 00 0c 00 00 00 d0 07 00 00一个总结 1、一个类的对象所占用的空间大小非静态成员变量之和多态类再加上指向虚基表的指针大小。 2、静态变量year是全局变量被优化不占用类的大小。 3、类D的对象dd和基类C的指针地址一样。 4、多态类占用空间比非多态类大8字节多的8字节其实是指向虚函数表的指针[vptr]。 5、创建一个派生类对象时先执行基类的构造再执行派生类的构造因此内存分配中前面是基类的非静态成员变量后面是派生类新增的非静态成员变量。 6、虚函数表指针是在基类构造时创建的属于基类的一个成员但派生类也可以访问。
一个多态派生类的对象所占用的内存空间
基类和派生类的成员归属
访问范围 1、保护成员的可访问范围比私有成员大比共有成员小。能访问私有成员的地方都能访问保护成员。 2、基类的私有成员只能在基类访问派生类不能访问。 3、基类的保护成员可以在派生类的成员函数访问。 4、私有成员只能在类的成员函数访问这和普通类的定义一致。
覆盖和扩充 1、派生类是对基类进行扩充和修改得到的基类的所有成员自动成为派生类的成员私有成员除外。 2、所谓扩充指的是派生类中可以添加新的成员变量和成员函数。 3、所谓覆盖指的是派生类中可以重写从基类继承得到的成员。
一个总结 1、构造与析构顺序构造时先执行基类的构造函数再执行派生类的构造函数析构时先执行派生类的析构函数再执行基类的构造函数。 2、基类的私有成员不能在派生类的成员函数访问。 3、基类的保护成员可以在派生类的成员函数中访问。 4、派生类可以定义和基类中同名的成员变量和非虚成员函数比如例中的age基类内存中有个age派生类新增成员内存中也有一个age这两个成员变量没有联系。 5、派生类成员函数访问基类非私有成员可以使用基类::访问。 6、基类的析构函数要定义为虚函数否则在释放基类指针时不会执行派生类的析构函数造成隐式的内存泄漏。 7、非多态情况下派生类和基类是包含和被包含的关系派生类包含了基类因此派生类指针可以转换为基类指针但基类指针不能转换为派生类指针(‘A’ is not polymorphic)。 8、多态情况下基类和派生类指针可以相互转换但要关注转换后指针是否有效可以使用dynamic_cast转换返回nullptr则转换失败。 //4字节对齐
#pragma pack(push, 4)
class A //基类
{
private:int price;//私有成员只能在基类的成员函数访问
protected:int age;//保护成员可以在派生类的成员函数中访问
public:char name[20] chw;//公有成员可以在任何地方访问A(){price 2000;age 17;printf(A()\n);}virtual ~A(){printf(~A()\n);}void TestFunc(){printf(price%d\n,price);printf(age%d\n,age);printf(name%s\n,name);}virtual void PrintThis(){printf(A%p\n,this);}
};class B : public A //派生类
{
private:int age;//派生类中可以重写从基类继承得到的成员char addr[20];//派生类可以扩充新的成员变量
public:B(){age 27;printf(B()\n);}~B(){printf(~B()\n);}//覆盖了基类的同名成员函数void TestFunc(){//不能访问基类的私有成员
// printf(price%d\n,price);// error: price is a private member of A//可以访问基类的保护成员和公有成员printf(age%d\n,age);printf(name%s\n,name);printf(A::age%d\n,A::age);//基类成员被派生类覆盖可以使用A::访问基类的成员
// A::TestFunc();//使用A::也可以访问基类的同名成员函数printf(B%p\n,this);A::PrintThis();}
};//测试调用B* bb new B;bb-TestFunc();printf(**********分割线***********\n);A* bb_a dynamic_castA*(bb);bb_a-TestFunc();printf(sizeof(A)%ld\n,sizeof(A));printf(sizeof(B)%ld\n,sizeof(B));delete bb;//基类不能转换为派生类因为类A没有虚函数不是多态的//如果类A成员函数TestFunc定义为virtual的可以转换但转换完成后aa_bnullptr不能使用
// A* aa new A;
// B* aa_b dynamic_castB*(aa);//error: A is not polymorphic
#pragma pack(pop)打印
A()
B()
age27
namechw
A::age17
B0x5555559e15b0
A0x5555559e15b0
**********分割线***********
price2000
age17
namechw
sizeof(A)36
sizeof(B)60
~B()
~A()内存占用
bb 0x5555559e15b0 B[A] 0x5555559e15b0 A[vptr] _vptr.A age 17 intname chw\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000 char[20]price 2000 intaddr nj\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000 char[20]age 27 int打印分析 1、派生类和基类指针地址是一样的0x5555559e15b0。 2、派生类重新定义了age和基类的age是两个没有联系的变量。 3、sizeof(A) 虚函数表指针(8字节) price(4字节) age(4字节) name(20字节) 36。 4、sizeof(B) sizeof(A) age(4字节) addr(20字节) 60。
多态的实现
多态的介绍参考https://blog.csdn.net/weixin_40355471/article/details/124368317#_844。
通过基类指针或基类引用实现多态 1、对于普通函数不用管指针是指向基类还是派生类只和指针变量的数据类型相关即定义指针变量时的指针数据类型如果是基类则始终调用基类的普通函数如果是派生类则始终调用派生类的普通函数。 2、对于虚函数要看基类指针当前指向的是基类还是派生类如果指向基类则调用基类的虚函数如果指向派生类则调用派生类的虚函数。 3、派生类指针可以赋值给基类指针但基类指针赋值给派生类指针时要注意转换的有效性通常使用dynamic_cast转换失败时返回nullptr。 4、因此通常使用基类指针或引用根据基类指针是指向基类还是派生类实现多态。
class A
{
public:void out1()//普通函数{printf(A(out1)\n);};virtual ~A(){};virtual void out2()//虚函数{printf(A(out2)\n);}
};class B:public A
{
public:virtual ~B(){};void out1(){printf(B(out1)\n);}void out2(){printf(B(out2)\n);}
};//测试调用A *aa new A;//基类指针无论aa后面指向基类还是派生类普通函数都是调用基类的普通函数B *bb new B;//派生类指针aa-out1();//A(out1)aa-out2();//A(out2)bb-out1();//B(out1)bb-out2();//B(out2)aa bb;//派生类指针赋值给基类指针bb dynamic_castB*(aa);//基类指针可以转换成派生类指针转换失败时返回nullptraa-out1();//A(out1)aa-out2();//B(out2)bb-out1();//B(out1)bb-out2();//B(out2)打印
A(out1)
A(out2)
B(out1)
B(out2)
A(out1)
B(out2)
B(out1)
B(out2)