结构体和类

来源:互联网 发布:淘宝达人帖子范文 编辑:程序博客网 时间:2024/06/12 01:30


结构体和类

    在C++中,结构体和类惟一的区别是默认访问权限和默认继承访问级别不同。结构体的默认访问权限为public,派生类默认为public继承;而类的默认访问权限为private,派生类默认为private继承。它们都具有构造函数、析构函数和成员函数。它们都是抽象的概念,只能表示某个群体,无法确定这个群体中的某个独立个体;而对象则是群体中独立存在的个体。

对象

    对象的大小(作sizeof操作)只包含数据成员,类成员函数属于执行代码,不属于类对象的数据。先定义的数据成员存放在低地址处,后定义的数据成员存放在高地址处。
    在类中不能定义自身的对象作为数据成员,原因:在定义该类的对象时,需要为其分配内存空间,需要计算出对象的大小。如果类中定义了自身的对象作为数据成员,则在计算各数据成员的长度时,又会回到自身,形成递归,导致无限循环,无法计算,因此不能定义自身对象作为类的数据成员。
    但可以定义自身类型的指针或引用类型作为其数据成员,因为指针在32位下始终占有4字节空间。
    
    对象长度 = sizeof(数据成员1) + sizeof(数据成员2) + ... + sizeof(数据成员n)
    用上式计算对象长度时不一定正确。原因如下:

  • 空类

空类中没有任何数据成员,但空类的长度为1字节。如果对象完全不占用内存空间,则空类将无法获得实例对象的地址,this指针失效,因此不能被实例化;但没有数据成员的类还有成员函数,因此需要实例化,因此分配了1字节的空间用于类的实例化,这个字节的数据并没用被使用。

  • 内存对齐

由于内存对齐的原因,数据成员并不一定会连续存放。数据类型不同,占用的内存空间大小也不相同,在申请内存时,会遵守相应的规则。(详见内存对齐篇)

  • static数据成员

    static数据成员可看做是具有作用域的特殊的全局变量,因此static数据成员的初值会被写入编译链接后的执行文件中。当程序被加载时,操作系统将执行文件中的数据读到对应的内存单元中,这时static数据成员便已经存在,而这时类的对象还未创建。因此,static数据成员和对象的生命周期不同。静态数据成员实现了同一类对象间的数据共享,与对象是一对多的关系。在计算对象长度时,static数据成员不计算在内。
    同时,static数据成员和普通数据成员的存放空间也是不同。static数据成员存放在常量地址中,可通过立即数间接寻址方式访问;而普通数据成员在对象创建后存放在栈空间中(若为局部对象的情况下),只能以寄存器相对间接寻址的方式访问。在成员函数中,由于static数据成员不属于任何对象,因此访问时不需this指针;而普通数据成员属于对象所有,访问时需要this指针。
    此外,当类中定义了虚函数和类为派生类等情况下,对象的内存布局将更为复杂,长度计算也更复杂,这个以后再考虑...

this指针

    使用指针访问结构体或类成员的公式:(假设type为某个正确定义的结构体或类,member为type中可以访问的成员)
    type *p;    // 此处略去p的赋值   
    p->member的地址 = 指针p的地址值 + member在type中的偏移量
因此,可以定义如下宏,用于在不产生对象的情况下取得成员偏移量。在VC的stddef.h中有offsetof的官方定义。
    #define offsetof(s,m)  (size_t)&(((s*)0)->m)
    C++中默认调用成员函数方式为thiscall(C语言中没有这种调用方式),隐含形参this指针(保存对象的首地址)通过ecx以寄存器传参的方式进行传递,其他形参从右向左依次压入栈中,被调方平衡参数所用栈空间,如图1所示。。thiscall不是关键字,因此不能被程序员指定。若将成员函数显式指定为其它调用方式,如_stdcall,则this指针不再使用ecx传递,而改用栈传递,如图2所示。
图1 thiscall调用
    thiscall调用通过ecx传递this指针,参数自右向左一次入栈,被调方(成员函数)平衡参数所用栈空间(ret 8)。
图2 _stdcall调用使用栈传递this指针
_stdcall调用栈传递this指针(为第一个参数),各参数依次自右向左压栈,被调方平衡参数栈(ret 0Ch)。
   

对象作为函数参数

    对象作为函数参数时,传参过程为先将对象中的所有数据成员进行备份(复制),将复制的数据作为形参依次压栈传递到调用函数中使用,最先定义的数据成员最后入栈,最后定义的数据成员最先入栈。
图3 对象作为函数的参数
图4 含有数组数据成员的对象作为函数参数
    注意:当对象作为函数参数时,由于重新复制了对象,当退出被调函数退出时,复制的对象作为函数内的局部变量,将会被销毁。若存在指针数据类型,并且没有定义复制构造函数的情况下,则实参对象中的指针成员和复制的对象的指针成员将指向同一内存地址,当复制对象被撤销后,对实参对象指针成员所指内存空间进行访问将出错。
    在使用类对象作为参数时,如无特殊需求,应尽量使用指针或引用,这样做不但可以避免资源释放的错误隐患,还可以在调用函数过程中避免复制对象,提高程序的效率。


对象作为返回值

    当以基本类型作为返回值时,或者用eax保存返回值,或者用eax和edx保存返回值。当以对象作为返回值时,寄存器无法满足需求。对象作为返回值与对象作为参数的处理方式十分类似。    对象作为参数时,进入函数前预先将对象使用的栈空间保留出来,并将实参对象中的数据复制到栈空间中,该栈空间作为函数参数,用于函数内部使用。同理,对象作为返回值时,进入函数后将申请返回对象使用的栈空间,在退出函数时,将返回对象中的数据复制到临时的栈空间,以这个临时栈空间的首地址作为返回值。
原创粉丝点击