结构体的基础知识,位段,联合+枚举

一、。结构体基础知识

结构体属于聚合数据类型,C语言提供了两种聚合数据类型,数组和结构体。
数组里面保存的是同类型的元素的集合,它的每个元素是通过下标引用或者指针间接访问来选择的。
结构也是一些值的集合,这些值称为它的成员,但一个结构的各个成员可能具有不同的类型,他们需要通过名字去访问,那个成员都有自己的名字。

1.结构声明

举个例子:

struct SIMPLE
{
    int a;
    char b;
    flaot c;
};  //注意这里必须有一个分号

这个结构体包含三个成员变量,分别为int型的a,char型的b,float型的c,SIMPLE为一个标签。这个声明把标签SIMPLE和这个成员列表联系在一起。注意这只是一个声明,并没有提供变量列表,所以它并未创建任何变量。已经声明之后的结构体struct SIMPLE 类似于int,double,它相当于一个结构体类型的标识符,可以用它来创建结构体变量。比如:

struct SIMPLE x;
struct SIMPLE y[20],*z;

这里的x,y和z都是同一类型的结构体,可以互相赋值,比如z=&x。
需要注意的是,如果不使用标签去声明结构体,必须在结构体后面直接创建结构体变量。

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

这里虽然声明了两个结构体类型,而且他们的成员变量一摸一样,但是它们并不是同一类型的结构体变量,没有标签系统会认为这是两个不同类型的结构体,因此z=&x是非法的。而且这种结构体只能创建一次,无法创建第二个变量。

2.结构成员

结构体成员可以是标量、数组、指针甚至是其他结构体。
这里有一个更为复杂的例子:

struct COMPLEX{
    float f;
    int a[20];
    long *lp;
    struct SIMPLE s;
    struct SIMPLE sa[10];
    struct SIMPLE *sp;
};

一个结构体的成员的名字可以和其他结构的成员名字相同,所以这个结构的成员a并不会与struct SIMPLE s 的成员a冲突。

3.结构成员的直接访问和间接访问

结构变量的成员是通过点操作符(.)访问的。点操作符接受两个操作数,左操作数解释结构变量的名字,有操作数就是需要访问的成员的名字。这个表达式的结果就是指定的成员。例如:

struct COMPLEX comp;
//假设使用上面的结构体
comp.a;  //访问comp结构体的a成员变量,a是一个数组,所以comp.a代表它的数组名
(comp.s).a;  //访问s结构体的a成员,因为(.)的结合性是从左向右的所以括号可以取消comp.s.a
((comp.sa)[4]).c;  //

当你有一个指向结构体地址的指针p时,要取出结构体成员变量,首先想到的是对指针进行解引用操作,拿出结构体名,然后用点操作符找到结构体成员变量。因为*操作的优先级低于(.)操作,所以必须加括号(*p).a。但是为了方便起见有一个更方便的操作符就有了,称为箭头操作符:

p->a;
p->b;
p->c;

结构体指针可以这样访问结构体成员变量。

4.结构的自引用

在一个结构内部包含一个类型为该结构本身的成员:

扫描二维码关注公众号,回复: 3256238 查看本文章
struct SELF_REF1{
    int a;
    struct SELF_REF1 b;
    int c;
};

这种类型的自引用是非法的,因为成员b里面又包含着一个同类型的结构体,这样下去就永无止境,更不知道这个结构体有多大。但如果用指针的方法声明却是合法的:

strcut SELF_REF2{
    int a;
    struct SELF_REF2 *b;
    int c;
};

这个声明和前面哪个声明的区别在于成员b是一个指针而不是一个结构,编译器在结构的长度确定之前就已经知道指针的长度。

5.不完整声明

看下面这个例子:

struct A {
    struct B *partner;
};
struct B {
    struct A *partner;
};

这是错误的,因为在声明A的时候发现它里面有结构体B的指针,但是因为结构体B没有声明,所以系统找不到B。当你把B放在前面那么B里面有结构体A的指针,会产生同样的错误。处理的办法是创建一个不完整声明:

struct B;
struct A {
    struct B *partner;
};
struct B {
    struct A *partner;
};

在A的上面加上B的不完整声明,这样告诉计算机有一个B类型的结构的,就不会出错了。

6.结构的初始化

结构的初始化方式和数组的初始化很相似,一个位于一堆花括号内部、由逗号分隔的初始值列表可用于结构各个成员的初始化,这些值根据结构成员的顺序写出。如果初始列表的值不够,剩余的结构成员将使用缺省值进行初始化。
这里有一个例子:

struct INIT_EX{
    int a;
    short b[10];
    Simple c;
}x = {
    10,
    {1,2,3,4,5},
    {25,'x',1.9}
};

二、位段

位段的成员必须是int、unsigned int和signed int的类型,其次,在成员的后面是一个冒号和一个整数,这个整数指定该位段所占用的位的数目。
下面是一个位段声明的例子:

struct CHAR{    
    unsigned ch  : 7;
    unsigned font: 6;
    unsigned size: 19;
};
struct CHAR ch1;

这个位段所占的空间大小为8个字节,位段的空间上需要按照以四个字节或者一个字节(字符)的方式来开辟的,6+7=13,13+19>32,所以得再开辟4个字节。
位段涉及很多不确定因素,所以位段是不跨平台的。

位段不能跨平台的因素:

1.系统的存储方式不一样(大小端),从左到右还是从右到左。
2.int被系统当作正号还是负号不确定。
3.32位机器定义的27位,位段,无法在16位机器中运行。
4.当第二个位段成员过大,第一个位段成员的剩余空间无法满足它时,是紧贴着第一个储存,还是重新开辟空间,浪费剩余空间。

总结

位段虽然可以很好的节省空间,但是它有跨平台的限制。

三、枚举

枚举就是一一列举,把可能的值一一列举出来。例如:

enum Color
{
    RED,
    GREEN,
    BLUE
};

以上定义的enum Color是枚举类型,{}内的是枚举类型的可能取值,也叫枚举常量,它们用 “,”隔开,默认值是0、1、2。。。也可以在定义的时候赋初值:

enum Color
{
    RED=1,
    GREEN=3,
    BLUE=7
};
enum Color s = RED;   //枚举变量初始赋值只能使用枚举常量
s = 10;     //之后的赋值没有上面这个要求

我们可以用#define来定义常量,为什么还要使用枚举?
1.枚举的可读性和可维护性更高。
2.枚举具有类型检查,更严谨。
3.防止命名污染。
4.便于调试。
5.使用方便,一次可以定义多个。

四、联合

联合也是一种特殊的自定义类型
这种类型定义的变量也包含一些成员,特点就是这些成员使用同一块空间(所以联合也叫共用体)。
例如:

union S 
{
    int a;
    char b;
};
struct S c;  //定义联合变量
printf("%d",sizeof(c));  //计算联合变量的大小

上面计算的大小是4,因为变量公用一块空间,大小至少是最大的成员的大小,并且要是最大对齐数的整数倍。
面试题大小端问题可以用联合很好的解决。
联合和结构体的巧妙使用:

//将long类型的IP地址,转化为点分十进制的表示形式
union ip_addr
{
    unsigned long addr,
    struct
    {
        unsigned char c1;
        unsigned char c2;
        unsigned char c3;
        unsigned char c4;
    }ip;
};
union ip_addr my_ip;
my_ip.addr = 176238749;
printf("%d.%d.%d.%d\n",my_ip.ip.c1,my_ip.ip.c2,my_ip.ip.c3,my_ip.ip.c4);

猜你喜欢

转载自blog.csdn.net/qq_39487033/article/details/80462587