结构体

       c提供了两种聚合类型,分别为数组和结构,数组是相同类型元素的集合,其每个元素是通过下标引用或指针间接访问来选择的,

       结构也是某些值的集合,这些值成为结构的成员,而各个成员的类型可能不同,因此每个成员的大小很可能不同,因此,不能通过下标引用来访问成员,一般是通过成员的名字来访问成员。和数组不同,结构变量属于标量类型,可以其他标量能进行的操作,结构变量也可以进行,而在使用时结构变量并不被替换程指针。

       上面叙述了结构与数组的区别,现在来探讨关于结构的话题。

       首先是结构的声明,我们来看                                   

 

struct
{
      int a;
      char b;
      float c;
}x;

 这样就声明了一个匿名结构,并定义了一个结构变量x,结构变量x的成员有a,b,c,三个成员的类型都不同。我们继续看下面

struct
{
      int a;
      char b;
      float c;
}x2,y[5],*z;

这里声明了一个匿名结构就,并创建了x2,y[5],*z,显然,这里的结构成员表和上面的相同,但是编译器在编译时这两种声明会被完全当成两种不同的类型,因此,这里的x2与上面的x是完全不同的,而语句z=&x也是非法的,可见,同一个程序中声明的两个结构无论如何都是不同的。

      在上面的匿名声明中,结构的创建只能在声明结构时创建,这样做的缺点是每创建结构变量都需要在声明的地方创建,不够灵活,我们现在用一种运用标签的方法来声明结构

struct TAG
{
      int a;
      char b;
      float c;
};
struct TAG x;
struct TAG y[5];
struct TAG *z;

这里声明了一个结构TAG,TAG是一个标签,它标识了一种模式,在后三行代码中,struct TAG就像是一种类型一样,如同  int  i   定义一个整型变量一般,struct TAG x; 定义了一个结构变量,struct TAG y[5]; 定义了一个结构数组,struct TAG *z; 定义了一个结构指针。在其他需要定义相同类型的结构变量的地方,只需要用struct TAG 就可以了。

       我们来看一种更好的声明技巧

typedef struct
{
     int a;
     char b;
     float c;
}Type;
Type x;
Type y[5];
Type *z;


这种声明效果与上面的声明效果完全相同,而这里创建新的结构变量时用Type,在上面代码中,标签TAG标识了一种模式,而这里的 Type 这是作为一种类型。

       我们再来看一段代码来探讨一下结构的自引用

typedef struct
{
        int a;
        Type b;
}Type;

你能看出这段代码有哪些错误吗?首先,结构的第二个成员b是一个完整的结构变量,其内又包含了另一个结构,这样层层包含下去是无限的,就像一个没有终止条件的递归函数一般,因此这样的写法是非法的。然后,我们再来看,类型名直到声明的末尾才定义,而在前面的结构成员表中就已经用于创建结构变量b,显然这种先用后声明的做法是极不正确的,我们再来看

 

typedef struct type
{
        int a;
        struct type *b;
}Type;

这里struct type *b 创建了一个结构指针变量,由于存在标签 type,且指针变量b的大小已知,显然这里的自引用是合法的。
 

 

       上面我们探讨了结构的声明,现在我们来看看结构的成员可以有哪些呢。虽然在前面的的示例中我们只是简单的用了三种类型,但其实任何一个可以在结构外面声明的变量都可以作为结构的成员,比如,结构的成员可以是标量、数组、指针甚至其他的结构。比如

typedef struct
{
         int a;
         char b[5];
         float *c;
         Type x;
}Def;


在这个声明中,有整型变量a,字符型数组b,指向单精度浮点型数据的指针变量c,还有结构变量x。

        我们前面了解了结构的成员组成,现在来探讨一下对结构成员进行初始化。与数组一样,结构成员的初始化在一个花括号中,不同成员用逗号隔开,而其内部的其他集合成员也用花括号,用逗号与其他成员数据分开。比如

struct EX
{
     int a;
     short b[5];
}x={1,{0,2,4,5,6}};

这样就对一个结构进行了初始化。

内存对齐

       现在我们来了解一下结构的储存分配,我们先写下一个结构

typedef struct
{
       char a;
       int b;
       char c[5];
}Type;


这里的类型 Type 有多大呢?按照数组的内存分配,我们试着猜想结构的内存大小就是结构的各个成员的大小相加,那么 Type 的大小就是三个成员的大小相加,则值为10个字节。真的是这样吗?我们来验证一下我们的猜想,在VS编译器下执行语句printf("%d\n",sizeof(Type)); 输出结果是否是10呢?我们来看一下

结果为16!这与我们的猜测相差6个字节。在我们的猜测中,每个成员在内存中都是紧挨在一起的,因此猜测中的大小为10个字节,而真实情况是此结构在内存中占用16个字节,所以可以肯定在结构所占用的内存中,有的存储位置上面没有存放数据。我们使用宏offsetof来得到结构中的变量a、b和c在内存中的存储位置相对于结构的起始位置偏移了多少个字节,我们在VS编译器中执行语句

printf("%d\n",offsetof(Type,a));
printf("%d\n",offsetof(Type,b));
printf("%d\n",offsetof(Type,c));


来看一下变量a、b和c的位置的偏移量

运行结果分别为0、4、8。可见,三个结构体成员在内存中并不是紧挨着的,而是像下面这样分配的

图中的6个白色框就是被浪费的内存。可见,结构的内存分配是按照某种机制来实现的,而这种内存分配方式叫做内存对齐。内存对齐的规则

(1) 结构的第一个成员永远都放在结构的0偏移处。

(2) 从第2个成员开始,都是对齐到某个对齐数的整数倍处。(对齐数:结构成员自身大小和默认对齐数的较小值。默认对齐数在VS环境下为8个字节,在32位linux环境下为4个字节,64位linux中为8个字节,可通过#pragma pack()调节)

(3) 结构的总大小必须是最大对齐数的整数倍。

(4)如A结构体中嵌套有B结构体对象,那么这个B结构体对象的对齐数为B结构体内部成员的最大对齐数。

         知道了内存对齐规则就不难理解为什么会浪费6个字节的空间了。a的位置对齐到0偏移处,b的对齐数为4,因此对齐到4偏移处,而数组c每个元素为字符型变量,对齐数为1,可在对齐在任意位置。现在我们可以调整结构成员的位置来节省某些浪费的空间

typedef struct
{
        int b;
        char a;
        char c[5];
}Type;

这样将a和b的位置交换,然后在VS编译器中执行语句

printf("%d\n",sizeof(Type));
printf("%d\n",offsetof(Type,b));
printf("%d\n",offsetof(Type,a));
printf("%d\n",offsetof(Type,c));

我们来看一下运行结果

可见,现在结构减小了4个字节。而三个成员在内存中的位置是这样的

可见,这里只浪费了2个字节的空间,这样就可以减小结构内存空间的浪费。

为什么要结构体对齐?

1、平台原因:某些硬件平台只能在某些特定的地址去读取特定类型的数据,否则就会抛出异常。

2、性能原因:在我们看来,内存空间的排放就是一个个小的地址空间线性排放的,然而在CPU看来,内存空间就是一个一个的“块”,这些块可以是2、 4、 8、 16字节的,当CPU访问内存时,实际上就是每次访问一个“块”,因此,如果进行了内存对齐,当CPU拿到这一个块之后就可以直接进行读取了,而如果不进行内存对齐,那么当CPU拿到一个块后,还有继续对块内进行二次读取。因此进行内存对齐后,其读取速度会大大的提高。

        讨论完结构的内存分配后,我们来探讨一下结构成员的访问。由于不能用下标访问的方式访问结构成员,因此我们要用别的方式来访问结构成员,分为直接访问和间接访问。

       首先是直接访问,直接访问要用到点操作符“.”,在上面的结构基础上,我们创建一个结构变量T,然后运用点操作符“.”,表达 T.a 就访问了结构的成员a,访问其他成员也可用类似的方法。

       然后是间接访问。在前面的结构的基础上,我们定义一个结构指针 Type *cp,此时cp就有能力指向Type型的结构变量,执行语句 cp=&T; 此时指针就指向了结构变量T,我们运用箭头操作符“->”,就可以访问变量T的成员了,比如访问成员a,我们可以用语句 cp -> a; 这样就访问了结构变量T的成员a。

 

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值