zoukankan      html  css  js  c++  java
  • C++ Struct

    一.为何

    struct tagNode                  struct tagNode

    {                         {

      ElemType data;                   ElemType data;

      struct tagNode LeftNode;             struct tagNode* LeftNode;

      struct tagNode RightNode;            struct tagNode* RightNode;

    };//无法通过编译                  };//正常通过编译

    C/C++采用静态编译模型。程序运行时,结构大小会在编译后确定。程序要正确编译,编译器必须知道一个结构所占用的空间大小。除此之外还有一个逻辑方面的问题,在这种情况下,想想可以有LeftNode.LeftNode.LeftNode.LeftNode这类错误也称为类和结构的递归定义错误

    如果采用指针形式,指针的大小与机器的字长有关,不管是什么类型,编译后指针的大小总是确定的,所以这种情况下不需要知道结构struct tagNode的确切定义。例如,在32位字长的CPU中,指针的长度为4字节。所以,如果采用指针的形式,struct tagNode的大小在其编译后即可确定,为sizeof(int)+4+4.对于对象形式的struct tagNode定义,其长度在编译后是无法确定的。

    二.结构体对齐

    C语言结构体对齐也是老生常谈的话题了。基本上是面试题的必考题。结构体到底怎样对齐?下面总结了对齐原则,在没有#pragma pack宏的情况下:

    原则1、普通数据成员对齐规则:第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小的整数倍开始(比如int在32位机为4字节,则要从4的整数倍地址开始存储)。

    原则2、结构体成员对齐规则:如果一个结构里有某些结构体成员,则该结构体成员要从其内部最大元素大小的整数倍地址开始存储。(struct a里存有struct b,b里有char,int,double等元素,那b应该从8的整数倍开始存储。)

    原则3、结构体大小对齐规则:结构体大小也就是sizeof的结果,必须是其内部成员中最大的对齐参数的整数倍,不足的要补齐。

    补充一点,如果数组作为结构体成员,比如:char a[3]。它的对齐方式和分别写3个char是一样的,也就是说它还是按1个字节对齐。如果写: typedef char Array3[3];Array3这种类型的对齐方式还是按1个字节对齐,而不是按它的长度3对齐。如果共用体作为结构体成员,则该共用体成员要从其内部最大元素大小的整数倍地址开始存储。

    还有一种对齐原则,和上面三条一致,可以参考着理解。

    成员对齐有一个重要的条件,即每个成员按自己的方式对齐。其对齐的规则是,每个成员按其类型的对齐参数(通常是这个类型的大小)和指定对齐参数(这里默认是8字节)中较小的一个对齐。并且结构的长度必须为所用过的所有对齐参数的整数倍,不够就补空字节。

    这三个原则具体怎样理解呢?我们看下面几个例子,通过实例来加深理解。

    例1:struct {
                  short a1;
                  short a2;
                  short a3;
           }A;

           struct{
                  long a1;
                  short a2;
          }B;

    sizeof(A) = 6; 这个很好理解,三个short都为2。

    sizeof(B) = 8; 这个比是不是比预想的大2个字节?long为4,short为2,整个为8,因为原则3。

    例2:struct A{
                   int a;
                   char b;
                   short c;
            };

            struct B{
                   char b;
                   int a;
                   short c;
           };

    sizeof(A) = 8; int为4,char为1,short为2,这里用到了原则1和原则3。

    sizeof(B) = 12; 是否超出预想范围?char为1,int为4,short为2,怎么会是12?还是原则1和原则3。

    深究一下,为什么是这样,我们可以看看内存里的布局情况。

                        a        b   c
    A的内存布局:1111, 1*,11

                        b       a       c
    B的内存布局:1***,1111,11**

    其中星号*表示填充的字节。A中,b后面为何要补充一个字节?因为c为short,其起始位置要为2的倍数,就是原则1。c的后面没有补充,因为b和c正好占用4个字节,整个A占用空间为4的倍数,也就是最大成员int类型的倍数,所以不用补充。B中,b是char为1,b后面补充了3个字节,因为a是int为4,根据原则1,起始位置要为4的倍数,所以b后面要补充3个字节。c后面补充两个字节,根据原则3,整个B占用空间要为4的倍数,c后面不补充,整个B的空间为10,不符,所以要补充2个字节。

    再看一个结构中含有结构成员的例子:

    例3:struct A{
                   int a;
                   double b;
                   float c;
           };

           struct B{
                  char e[2];
                  int f;
                  double g;  
                  short h;
                  struct A i;
           };

    sizeof(A) = 24; 这个比较好理解,int为4,double为8,float为4,总长为8的倍数,补齐,所以整个A为24。

    sizeof(B) = 48; 看看B的内存布局。

                        e         f       g                h                      i 
    B的内存布局:11* *,1111,11111111, 11 * * * * * *,1111* * * *, 11111111, 1111 * * * * 。 i其实就是A的内存布局。i的起始位置要为24的倍数,所以h后面要补齐。把B的内存布局弄清楚,有关结构体的对齐方式基本就算掌握了。

    以上讲的都是没有#pragma pack宏的情况,如果有#pragma pack宏,对齐方式按照宏的定义来。比如上面的结构体前加#pragma pack(1),内存的布局就会完全改变。sizeof(A) = 16; sizeof(B) = 32;有了#pragma pack(1),内存不会再遵循原则1和原则3了,按1字节对齐。没错,这不是理想中的没有内存对齐的世界吗。

                        a       b               c
    A的内存布局:1111,11111111,1111

                        e    f       g               h   i 
    B的内存布局:11,1111,11111111,11,1111, 11111111, 1111 

     那#pragma pack(2)的结果又是多少呢?#pragma pack(4)呢?留给大家自己思考吧,相信没有问题。

     还有一种常见的情况,结构体中含位域字段。位域成员不能单独被取sizeof值。C99规定int、unsigned int和bool可以作为位域类型,但编译器几乎都对此作了扩展,允许其它类型类型的存在。

    使用位域的主要目的是压缩存储,其大致规则为: 
    1) 如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止; 
    2) 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍; 
    3) 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方式,Dev-C++采取压缩方式; 
    4) 如果位域字段之间穿插着非位域字段,则不进行压缩; 
    5) 整个结构体的总大小为最宽基本类型成员大小的整数倍。

    还是让我们来看看例子。

    例4:struct A{ 
                   char f1 : 3; 
                   char f2 : 4; 
                   char f3 : 5; 
           };

                        a      b          c
    A的内存布局:111,1111 *,11111 * * *

    位域类型为char,第1个字节仅能容纳下f1和f2,所以f2被压缩到第1个字节中,而f3只能从下一个字节开始。因此sizeof(A)的结果为2。

    例5:struct B{ 
                   char f1 : 3; 
                   short f2 : 4; 
                   char f3 : 5; 
           };

    由于相邻位域类型不同,在VC6中其sizeof为6,在Dev-C++中为2。

    例6:struct C{ 
                   char f1 : 3; 
                   char f2; 
                   char f3 : 5; 
           };

    非位域字段穿插在其中,不会产生压缩,在VC6和Dev-C++中得到的大小均为3。

    考虑一个问题,为什么要设计内存对齐的处理方式呢?如果体系结构是不对齐的,成员将会一个挨一个存储,显然对齐更浪费了空间。那么为什么要使用对齐呢?体系结构的对齐和不对齐,是在时间和空间上的一个权衡。对齐节省了时间。假设一个体系结构的字长为w,那么它同时就假设了在这种体系结构上对宽度为w的数据的处理最频繁也是最重要的。它的设计也是从优先提高对w位数据操作的效率来考虑的。有兴趣的可以google一下,人家就可以跟你解释的,一大堆的道理。

    最后顺便提一点,在设计结构体的时候,一般会尊照一个习惯,就是把占用空间小的类型排在前面,占用空间大的类型排在后面,这样可以相对节约一些对齐空间。

    三.既有结构,为何引入联合

    联合定义

    union WordToType

    {

      unsigned char Tbyte[2];

      unsigned short Tword;

    }Word;

    从定义形式上看,“联合”和“结构体”非常相似,但二者在本质上却有很大差距。首先,在结构体中每个成员都有自己的内存,一个结构体变量的总长度等于各个成员长度的总和。而在联合中,各个成员共享一段内存空间,一个联合变量的长度等于各个成员中长度最大的那个成员的长度。联合体对数据的引用与结构体对数据的引用方式相同,都必须通过联合或结构体中的元素引用,而不能引用联合体变量。例如,对于上面的联合,下面的操作是错误的

    printf("%d",Word);

    联合体的共享不是指把各个成员同时装入一个联合体内,而是数据根据联合体的定义可以展现成不同的形式,同时向用户传递不同信息的过程。

    几个特点:

    1.同一个联合体可用来存放几种不同类型的成员,但是在每一瞬间只能存放其中的和种,而不是同时存放几种,换句话说,每一瞬间只有一个成员起作用,其他的成员不起作用。

    2.共用体变量中起作用的成员是最后一次存放的成员,在存入一个新成员后,原来的成员就失去作用

    3.共用体变量的地址和其各成员的地址都是同一个地址

    4.不能地共用体变量名赋值,也不能企图引用变量名来得到一个值,并且也不能在定义共用体变量时对它进行初始化

    5.不能把共用体变量作为函数参数,也不能作为函数的返回值,但可使用指向使用指向共用体变量的指针

    6.共用体类型可出现在结构体类型的定义中,也可定义共用体数组;反之,结构体也可出现在共用体类型的定义中,数组也可作为共用体的成员

  • 相关阅读:
    php (一)
    php 运算符
    Python 元组
    Python 深拷贝和浅拷贝的区别
    Python 列表
    Python 字符串
    Python 循环控制
    Python 随机数,数学
    bzoj5018 [Snoi2017]英雄联盟
    bzoj5015 [Snoi2017]礼物
  • 原文地址:https://www.cnblogs.com/fenghuan/p/4850839.html
Copyright © 2011-2022 走看看