"C语言-结构和联合"

  "C语言基础复习六"

Posted by Xu on January 22, 2018

结构和联合

struct_union

结构声明:

struct tag{member-list} variablr-list;

两种声明:

1. 声明创建一个名为x的变量,包含三个成员,一个整数,字符,和浮点数

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

2. 声明创建y和z,y为一个数组,包含20个结构,z是一个指针,指向这个类型的结构

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

上面两个声明被编译器当成两种截然不同的类型,y和z的类型和x的类型不同,所以z =&x;是非法的

通过使用标签可以解决这个问题,利用标签可以声明并创建同一类型的结构

struct SIMPLR{
   int a;
   char b;
   float c;
};
struct SIMPLE x;
struct SIMPLE y[20],*z;

这样创建的x ,y ,z都是同一类型的结构变量,这种声明中SIMPLE是一个标签而非类型名

声明结构类型的另一种方法就是使用类型名:

typedef struct{
    int a ;
    char b ;
    flaot c ;
} Simple;

Simple x;
Simple y[20],*z;

引用:

  • 结构成员的直接访问: s.a
  • 结构成员的间接访问: struct Simple *p; p->a
  • 结构的自引用:
struct SELF_REF1{
   int a;
   struct SELF_REF1 b;
   int c;
};//非法,因为SELF_REF1的引用一直递归,永无止境

struct SELF_REF2{
   int a;
   struct SELF_REF2 *b;
   int c;
};//合法,声明一个指向该结构体的指针,还没有进行初始化,指针的数据长度是可以确定的。

陷阱:

typedef struct{
    int a ;
    struct SELF_REF3 *b ;
    int c ;
}SELF_REF3;//非法,在声明末端,SELF_REF3才被定义,在结构声明内部,该类型名还没有被定义

//解决方案,加一个标签
typedef struct SELF_REF3_TAG{
    int a ;
    struct SELF_REF3_TAG *b ;
    int c ;
}SELF_REF3;
  • 不完整声明:
struct B;//不完整声明
struct A{
    struct B *partner;//声明指针不需要知道成员的长度
};//利用不完整声明来构建依赖关系
struct B{
    struct A *partner;
    };
  • 结构初始化:结构初始化使用一个位于花括号内部、由逗号分隔的初始化列表可用于各成员的初始化,若初始化列表的值不够,使用类型默认的缺省值

结构,指针和成员

表达式:

typedef struct EX2{
     int a ;
     short b[2];
} ;

typedef struct EX{
   int a;
   char b[3];
   EX2 c;
   struct EX *d;
}Ex;
Ex x = {10,"Hi",{5,{-1,25}},0}
Ex *px = &x;

针对Ex *px = &x;

分析px+1;

  • 作为左值,px+1表达式的值存放的位置不确定非法
  • 作为右值,如果px指向一个结构数组的元素,这个表达式将指向该数组的下一个结构,是合法的,但在该表达式中Ex *px = &x;的px是非法的,px+1指向的下一个结构并不知道是什么类型的数据。

分析*px:

  • 作为左值,该表达式的值存放在指定位置,合法,相当于给整个结构体赋值
  • 作为右值,表达式值确定,合法,对于*px+1则是非法的,没有定义结构体和整型值之间的运算,对于*(px+1)在数组中为下一个结构元素的值,但这里px指向的x为标量,所以非法

*px 和px->a的区别,虽然指向的地址是一样的,但是指针的类型不同,*px指向的是整个结构体,&px->a指向一个整型值。访问结构体中第一个元素的方式:

  1. 强制类型转换(int *)px;//不推荐,危险,改变了指针类型避开了编译器类型检查
  2. 直接访问:px->a

分析px->b:

  1. 作为左值非法,因为b是一个数组,数组名为指针常量,不可修改
  2. 作为右值,作为一个数组作为右值,对数组进行间接访问:px->b[1];

分析*px->c.b:根据优先级 先计算px->c.b,该函数指向Ex中成员c结构体中的数组b,所以px->c.b实际上是一个数组名,指针常量。*px->c.b即为该数组。

分析px->d:

  1. 作为右值:即为它本身的值0
  2. 作为左值:有确定的存储位置,合法

当d中值为0时,解引用是非法的。当d指向一个Ex结构时,解引用是合法的。

结构体的存储分配

边界对齐要求:

  • 规则1: 结构体中成员存储的位置必须能够被该成员类型所占字节数整除,比如一个整型值在某机器上的长度为四个字节,所以该类型数据存储位置必须能够可以被4整除。
  • 规则2: 同时一个结构体的首地址不允许跳过几个字节来满足边界对齐要求,所以对结构体首地址的要求更为严格,它需要满足,结构体成员中最严格的数据类型所要求的存储地址的边界对齐规则。

例如:

struct ALIEN{
   char a;
   int b;
   char c;
};

alien

其中:

  • 对于规则2:结构体首地址要满足成员最严格的边界对齐规则,所以a的首地址必须满足,整型b的4个字节的边界对齐要求,即能被4整除。
  • 对于规则1:b的首地址满足整型的边界对齐要求,需要跳3个字节才能满足。同时如果声明了相同类型ALIENT(首地址满足整型的边界对齐要求)的第二个变量,c同样要跳过三个字节。

字节利用率为:6/12

优化后的声明:

struct ALIEN2{
    int b;
    char a;
    char c;
};

alien2

  • 尽量将边界对齐的要求最严格的放到前面
  • 同样如果第二个变量为相同类型的结构体ALIENT2,这里只需要8个字节

字节利用率为:6/8

考虑边界对齐因素:sizeof可以得到一个结构的整体长度,offsetof(type,member)宏可以确定某个成员相对于结构体首地址的字节偏移量

参数

  1. 作为参数,直接传入结构体并不是一个明智的选择,因为在传递参数的过程当中,需要拷贝一份结构体作为形参压入堆栈,当结构体数据量较大时,会降低处理效率。所以我们选择传递指向结构体的指针,拷贝指针变量比拷贝结构体要节省很多空间

  2. 并且如果我们不希望传递的指针指向的结构体数据被修改,我们可以将其声明为常量,并且传递指针可以直接对原结构体数据进行修改。

  3. 同时我们对指针变量的声明可以利用register来声明寄存器变量来提高处理效率(部分机器需要将参数从堆栈复制到寄存器进行处理)。

位段

指明一个结构体成员所占的位数:

//字符文本结构体
struct CHAR{
    unsigned ch:7;//可以表示128个字符
    unsigned font:6;//可以表示64个字体
    unsigned size:19;//利用字符和字体所剩余的位数(16+1+2),可以容纳2^19(524287)个单位长度
};
  • 位段成员必须声明为int、signed int、unsigned int
  • 位段声明的移植性较差

联合

联合所有成员引用的是内存中相同的位置。成员的值由联合中具体的类型来解释。

struct VARIABLE{
    enum {INT,FLOAT,STRING} type;
    int int_value;
    float float_value;
    char char_value;
}//一个变量int_value,float_value,char_value只有一个会存放值,但是结构体为三个变量都分配来内存,无疑会造成资源的浪费
//所以这种情况下可以使用联合
struct VARIABLE{
    enum {INT,FLOAT,STRING} type;
    union{
    int int_value;
    float float_value;
    char char_value;
    } value;
}//共享同一内存地址,value的实际值由value调用的类型来解释.
  • 联合的长度为其最长成员的长度
  • 为了防止不同类型的长度差异过大而造成的资源浪费,可以在联合中存放指针指向具体类型的数据,到时候通过动态分配内存来防止资源的浪费

联合变量的初始化只能初始化第一个成员:

union {
    int a ;
    float b;
    char c[5];
}x = {5};//如果该初始值为其他类型的值都会转换成整型赋值给x.a

总结

  • 结构体的声明(即使成员列表相同)会产生不同类型的变量;
  • 使用typedef自引用时应使用标签结构,因为类型名在成员声明之后声明
  • 结构体作为参数时,最好传递指针提高效率