结构体

来源:互联网 发布:html矢量图标js下载 编辑:程序博客网 时间:2024/06/05 03:41

       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个字节,在linux环境下为4个字节)

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

         知道了内存对齐规则就不难理解为什么会浪费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个字节的空间,这样就可以减小结构内存空间的浪费。

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

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

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