自定义类型:结构体,枚举,联合

一、结构体

结构是一些值的集合,这些值称为成员变量。结构的每个成员可以使不同类型的变量。

结构体的声明
struct student   //标签Tag,可以省略但是尽量不要省略。  
{  
 char name[];    //结构体成员,c语言中不能为空  
 int age;  
 char sex[];  
 char id[20];  
}list;   //变量列表,可以省略。  注意切记不要忘记分号。
结构的成员
结构的成员可以是标量、数组、指针,甚至是其他结构体。

结构体变量访问成员是通过点操作符(.)访问的。点操作符接受两个操作数。
例如:
struct Stu
{
   char name[20];
   int age;
};
struct Stu s;//定义结构体变量
struct S s;  
strcpy(s.name,"zhangsan");  
s.age = 20;  
结构体访问指向变量的成员 有时候我们得到的不是一个结构体变量,而是指向一个结构体的指针,那么则应该这样访问:
struct S  
{  
    char name[20];  
    int age;  
}s;  
  
void print(struct S* ps)  
{  
    printf("name=%s  age=%d\n", (*ps).name, (*ps).age);  
    printf("name=%s  age=%d\n", ps->name, ps->age);  
} 
结构的自引用
结构中包含一个类型为该结构本身的成员。
typedef struct Node  
{  
    int date;  
    struct Node* next;  
}Node;  

结构的不完整声明
若两个结构体互相包含,且要正常使用,则需要这样:
struct B;  
struct A  
{  
    int _a;  
    struct B* pb;  
};  
struct B  
{  
    int _b;  
    struct A* pa;  
}; 
结构体变量的定义和初始化

定义的时候有两种方式:

1.声明类型的同时定义变量;  2.直接定义结构体变量;

struct Point     //1
{  
    int x;  
    int y;  
}p1;  
struct Point p2;    //2
初始化:定义变量的同时赋初值
struct Point p3 = { x, y };  
  
struct Stu    //类型声明  
{  
    char name[15];  
    int age;  
};  
struct Stu s = { "zhangsan", 20 }; //初始化

结构体的嵌套初始化
struct Node  
{  
    int date;  
    struct Point p;  
    struct Node* next;  
}n1 = { 10, { 4, 5 }, NULL };  
  
struct Node n2 = { 20, { 5, 6 }, NULL };  

结构体的内存对齐
对齐规则:

1.第一个成员在与结构体变量偏移量为0的地址处(所以结构体成员的第一个元素你需要内存对齐,默认对齐);

2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。     对齐数:编译器默认的一个对齐数与该成员大小得到较小值; VS中默认为8   Linux中默认为4

3.结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。

4.如果嵌套了结构体,嵌套的结构体对齐到自己最大对齐数的整数倍处,结构体的整体大小为所有最大对齐数的整数倍。

为什么存在内存对齐?

①平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
②性能原因:
数据结构(尤其是栈)应该尽可能的在自然边界上对齐。原因是要访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问只需要一次。
当我们了解了结构体的内存对齐后,就要来计算结构体的大小了:
//练习1  
    struct S1  
    {  
        char c1;   //偏移量为1        
        int i;     //大小为4,所以需要对齐,所以总大小为1+3+4          
        char c2;   //大小为1,不需要对齐   1+3+4+1  
    };             //所以总大小为最大对齐数的倍数: 12   
        printf("%d\n", sizeof(struct S1));  
//练习2  
    struct S2  
    {  
        char c1;  
        char c2;  
        int i;  
    };                //这次的结果是8      只是变换了一下成员的位置,结果就不一样,正好说明了结构体的对齐规则  
    printf("%d\n", sizeof(struct S2)); 
//练习3  
    struct S3  
    {  
        double d;  
        char c;  
        int i;  
    };               //按照规则,结果为16  
    printf("%d\n", sizeof(struct S3));  
//练习4  
    struct S4  
    {  
        char c1;         //1+3  
        struct S3 s3;    //由上可知   16  
        double d;        //8  
    };                       //∴  1+3+16+8=28    但要满足总大小为最大对齐数的倍数  ∴结构体总大小为32  
    printf("%d\n", sizeof(struct S4));  

#pragma pack()可以设置默认对齐数
#pragma pack(4)
struct S
{
  char c1;
  double d;
  char c2;
};
#pragma pack()

结构体传参
结构体传参时不会发生降维,如果传参时传递一个结构体对象,结构体过大,参数压栈的系统开销比较大,所以会导致性能下降。 所以结构传参时,要传结构体地址。
位段

位段的声明和结构是类似的,但有两个不同:

①位段的成员必须是int,unsigned int,signed int。

②位段的成员名后边有一个冒号和数字。
struct S  
    {  
        int _a : 2;  
        int _b : 5;  
        int _c : 10;  
        int _d : 30;  
    };     //因为它的单位是bit位,所以它的大小是8  

位段的内存分配:

①位段的成员可以是int,unsigned int,signed int或者是char(属于整形家族)类型;

②位段的空间上是按照需要以4个字节(int)或者一个字节(char)的方式来开辟的;

③位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序尽量避免使用位段。

位段的跨平台问题:

①int位段被当成是有符号数还是无符号数是不确定的;

②位段中最大位的数目不能确定;

③位段中的成员在内存中从左行右分配,还是从右向左标准尚未定义;

④当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,也不确定。

总结:位段跟结构相比,可以达到同样的效果,还可以节省空间,但是有跨平台的问题存在。

二、枚举


枚举其实就是把可能的取值一一列举;

这里就不介绍枚举了,我们来看看它的优点吧:

①增加代码的可读性和可维护性;

②和#define定义的标识符比较枚举有类型检查,更加严谨;

③防止了命名污染(封装)

④便于调试;

⑤使用方便,一次可以定义多个常量。

、联合(共用体)


联合其实也是一种特殊的自定义类型,它定义的变量也包含一系列的成员,最大的特点就是这些成员共用同一块空间; 

那么一个联合变量的大小,就至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员);
联合大小的计算:

①联合的大小至少是最大成员的大小;

②当最大成员大小不是最大对齐数的整数倍时,就要对齐到最大对齐数的整数倍。

联合和结构体的巧妙使用:

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.c4, my_ip.ip.c3, my_ip.ip.c2, my_ip.ip.c1);  //可以将long类型的IP地址转化为点分十进制的表示形式。

利用联合来判断当前机器的字节序:
int check_sys()
{
  union
  {
    int i;
    char c;
  }un;
 un.i=1;
 return un.c
}

猜你喜欢

转载自blog.csdn.net/qq_41268108/article/details/80527620