结构体/位段/枚举

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>

//结构体:
//结构体类型的声明
    //struct tag        //struct是结构体关键字,tag是结构体标签名(人为给出)
    //{
    //    member - list;    //{}内部是一个或多个结构体的成员变量,每个成员变量都有自己的类型,它们的类型相互之间没有必然联系,可以相同也可以不同
    //}variable-list;    //variable-list是变量列表,可以是一个也可以是多个。{}后边的‘;’一定要有
//例如描述一个学生
struct stu    //struct stu是结构体类型;stu是结构体标签名
{
    char name[20];    //名字
    int age;        //年龄
    char sex[5];    //性别
    char id[20];    //学号
}s1;    //s1是一个结构体变量,这是一种结构体变量定义方式,
int main()
{
    struct stu s2;    //s2也是一个结构体变量,这是另一种结构体变量定义方式。s1和s2都是结构体变量,但是它们的区别在于s1是一个全局变量,s2是一个局部变量

    return 0;
}

//特殊结构体的声明
    //在声明结构体的时候,可以不完全声明。比如:
//匿名结构体类型
    //(同一个匿名结构体类型只能使用一次,不能重复使用)
struct        //和常规结构体类型的声明相比只有struct关键字,没有结构体标签名(tag)
{
    int a;
    char b;
    float c;
}x;        //匿名结构体类型的变量只能紧跟在在结构体类型的{}后边定义

//结构体的自引用
    //结构体的自引用是在结构体中包含一个给结构体类型的指针(指针指向下一个该结构体类型的数据),而不是包含一个该结构体类型的成员变量
struct Node
{
    int data;
    struct Node next;    //这个是错误写法
    struct Node* next;    //这是正确写法
};

//结构体变量的定义和初始化
struct stu
{
    char name[15];
    int age;
};
struct stu s = { "zhangsan",25 };    //s是定义的结构体变量,struct stu是它的类型;用{ "zhangsan",25 }对s进行初始化
struct stu s2 = { .age = 25,.name = "zhangsan"};    //结构体初始化也可以不安成员顺序来,但是需要用结构体成员访问操作符‘.’来初始化
struct Node
{
    int data;
    struct stu a;
    struct Node* next;
}n1 = { 20,{"lisi",23},NULL };        //结构体嵌套初始化:n1为结构体变量,struct Node是n1的类型;
struct Node n2 = { 18,{"wangwu",24},NULL };

//结构体内存对齐(计算结构体大小的关键知识点)
    //结构体的内存对齐是拿空间(内存对齐会导致部分内存空间的浪费)换取时间(处理器访问对齐的内存时,只需要做一次内存访问,未对齐的需要访问两次)的做法 
    //为了节省空间、减少内存浪费,尽量让结构体中占用内存空间小的成员变量集中在一起。可见下面的struct s1和struct s2:成员一样,但是结构体大小不同
    //结构体的对齐规则:
        //1.第一个成员在与结构体变量偏移量为0的地址处。偏移量:指的是相对于起始位置的位置差,而第一个成员就放在起始位置处,所以偏移量为0
        //2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
            //对齐数:编译器默认的一个对齐数(VS中默认值为8)与该成员大小(如:int类型大小是4字节,char类型大小是1字节)的较小值(单位是字节)。
            // 举例:
                // 若结构体的第一个成员是char类型,那么第一个成员放在0偏移处,占一个字节
                //第二个成员是int类型,大小是4个字节,小于VS默认值8,所以int类型的对齐数是4,那么这个int型成员需要对齐到(放在)偏移量为4的倍数处,0偏移之后有偏移量为1、2、3的三个字节均不是4的倍数,不能存放这个int成员(偏移量为1、2、3的三个字节会被浪费掉),所以这个int型成员只能对齐到(放在)偏移量为4的位置处,占用4个字节,分别是偏移量为4、5、6、7的四个字节
        //3.结构体总大小为最大对齐数(指的是所有成员变量的对齐数中的最大值)的整数倍。
            //举例:如果结构体有四个成员,他们各自的对齐数分别是1、4、2、8,则最大对齐数就是8;
            //四个成员都对齐之后的最后的位置是偏移量为32的位置,即一共占用了33(从偏移量为0到偏移量为32一共33个字节)个字节(必须包括中间浪费掉的字节数),则结构体的大小必须是大于等于33的满足8的倍数的最小值,即结构体大小是40个字节
        //4.如果嵌套了结构体的情况,嵌套的结构体的对齐数是自己的所有成员变量的对齐数中的最大值,结构体的整体大小的计算规则不变。
struct s1
{
    char c1;    //对齐数:1        对齐到0偏移处        占一个字节:偏移量为0的一个字节
    int i;        //对齐数:4        对齐到4偏移处        占四个字节:偏移量为4、5、6、7的四个字节(偏移量为1、2、3的三个字节被浪费掉)
    char c2;    //对齐数:1        对齐到5偏移处        占一个字节:偏移量为8的一个字节
};
struct s2
{
    char c1;    //对齐数:1        对齐到0偏移处        占一个字节:偏移量为0的一个字节
    char c2;    //对齐数:1        对齐到1偏移处        占一个字节:偏移量为1的一个字节
    int i;        //对齐数:4     对齐到4偏移处        占四个字节:偏移量为4、5、6、7的四个字节(偏移量为2、3的两个字节被浪费掉)
};
struct s3
{
    double d;    //对齐数:8        对齐到0偏移处        占八个字节:偏移量为0、1、2、3、4、5、6、7的八个字节
    char c;        //对齐数:1        对齐到8偏移处        占一个字节:偏移量为8的一个字节
    int i;        //对齐数:4        对齐到12偏移处        占四个字节:偏移量为12、13、14、15的四个字节(偏移量为9、10、11的三个字节被浪费掉)
};
struct s4
{
    char c1;    //对齐数:1        对齐到0偏移处        占一个字节:偏移量为0的一个字节
    struct s3 s;//对齐数:8        对齐到8偏移处        占十六个字节:偏移量为8~偏移量为23的十六个字节(偏移量为1、2、3、4、5、6、7的七个字节被浪费掉)
        //结构体作为另一个结构体的成员变量被嵌套使用时,被嵌套的结构体的对齐数是自己的所有成员变量对齐数中的最大值
    double d;    //对齐数:8        对齐到24偏移处        占八个字节:偏移量为24、25、26、27、28、29、30、31的八个字节
};
int main()
{
    printf("%d\n", sizeof(struct s1));    //12        
        //成员变量的对齐数分别是:1、4、1        最大对齐数:4        内存对齐的成员变量共占用9个字节(偏移量:0~8)        大于等于9的满足4(最大对齐数)的倍数的最小值:12
    
    printf("%d\n", sizeof(struct s2));    //8
        //成员变量的对齐数分别是:1、1、4        最大对齐数:4        内存对齐的成员变量共占用8个字节(偏移量:0~7)        大于等于8的满足4(最大对齐数)的倍数的最小值:8

    printf("%d\n", sizeof(struct s3));    //16
        //成员变量的对齐数分别是:8、1、4        最大对齐数:8        内存对齐的成员变量共占用16个字节(偏移量:0~15)    大于等于16的满足8(最大对齐数)的倍数的最小值:16

    printf("%d\n", sizeof(struct s4));    //32
        //成员变量的对齐数分别是:1、8、8        最大对齐数:8        内存对齐的成员变量共占用32个字节(偏移量:0~31)    大于等于32的满足8(最大对齐数)的倍数的最小值:32

    return 0;
}

//修改默认对齐数
    //结构体的对齐方式不合适时,可以自己更改默认对齐数:用#pragma这个预处理指令
struct s1
{
    char c1;        //对齐数:1        对齐到0偏移处        占一个字节:偏移量为0的一个字节
    int i;            //对齐数:4        对齐到4偏移处        占四个字节:偏移量为4、5、6、7的四个字节
    char c2;        //对齐数:1        对齐到8偏移处        占一个字节:偏移量为8的一个字节
};
#pragma pack(1)    //设置默认对齐数为1。#pragma pack(1)写在结构体之前是修改默认对齐数
struct s2
{
    char c1;        //对齐数:1        对齐到0偏移处        占一个字节:偏移量为0的一个字节
    int i;            //对齐数:1        对齐到1偏移处        占4个字节:偏移量为1、2、3、4的四个字节
    char c2;        //对齐数:1        对齐到5偏移处        占一个字节:偏移量为5的一个字节
};
#pragma pack()    //#pragma pack()写在结构体之后,取消设置的默认对齐数,还原为系统默认
int main()
{
    printf("%d\n", sizeof(struct s1));
    //成员变量的对齐数分别是:1、4、1        最大对齐数:4        内存对齐的成员变量共占用9个字节(偏移量:0~8)        大于等于9的满足4(最大对齐数)的倍数的最小值:12
    printf("%d\n", sizeof(struct s2));
    //成员变量的对齐数分别是:1、1、1        最大对齐数:1        内存对齐的成员变量共占用6个字节(偏移量:0~5)        大于等于6的满足1(最大对齐数)的倍数的最小值:6
    return 0;
}

//结构体传参
    //结构体传参:传变量和传地址可以达到同样的效果,但是结构体通常比较大,当传递一个结构体变量时,参数压栈的系统开销较大,会导致性能下降;而指针大小不是4个字节就是8个字节。所以结构体传参最好选择传递结构体的地址,减少系统开销
struct s
{
    int data[1000];
    int num;
};
struct s s1 = { {1,2,3,4},100 };
void print1(struct s s1)
{
    printf("%d\n", s1.num);
}
void print2(struct s* s1)
{
    printf("%d\n", s1->num);
}
int main()
{
    print1(s1);        //100
    print2(&s1);    //100
    return 0;
}


//位段
// 什么是位段
    //位段中的位指的是二进制位(bit位)
    //位段的声明和结构体类似,但有两个不同:
        //1.位段的成员必须是int、unsigned int、signed int或char(属于整型家族)
        //2.位段的成员后边有一个冒号和一个数字(为成员变量开辟的内存空间的大小,单位是bit位(即二进制位))
struct A
{
    int a : 5;
    int b : 10;
    int c : 7;
    int d : 28;
};    //大小是50个bit位
int main()
{
    printf("%d\n", sizeof(struct A));    //8(单位是字节)        sizeof的单位是字节
        //50个bit位只需要7个字节就能放的下,为什么打印出来却是8?具体原因看下面的内存分配
    return 0;
}

//位段的内存分配
    //位段的空间上是按照需要以4个字节(int)或者以1个字节(char)的方式来开辟的
    //VS中位段分配到的内存中的比特位是由右向左使用(不同的平台方式不同),如果剩余的比特位不够用时,浪费掉,并重新按照需要的方式(4个字节或者是1个字节)来开辟新的空间
    //位段可以达到和结构体相同的效果,而且可以节省很多空间,但是,位段存在很多跨平台问题,所以可移植程序应该避免使用位段
struct A
{
    int a : 5;
    int b : 10;
    int c : 7;
    int d : 28;
};    //大小是50个bit位
int main()
{
    printf("%d\n", sizeof(struct A));    //8(单位是字节)        sizeof的单位是字节
        //我们计算出来的位段struct A的大小是50个bit位,而位段struct A的空间上是以4个字节(int)的方式来开辟的,当4个字节不够用时在开辟4个字节的空间,所以sizeof(struct A)一定是4的倍数
    return 0;
}


//枚举
    //顾名思义,枚举就是一一列举,把可能的取值一一列举
//枚举的定义
enum Sex//enum Sex枚举类型
{
    MALE,
    FEMALE,
    SECRET
};
enum Color//enum Color枚举类型
{
    RED,
    GREEN,
    BLUE
};
    //以上定义的enum Sex和enum Color都是枚举类型,{}中的内容是枚举类型的可能取值,也叫作枚举常量,这些枚举常量都是有值的,默认从0开始,一次递增1。
    //当然,在定义的时候也可以给枚举常量赋初值,如:
enum Color
{
    RED=1,
    GREEN=3,
    BLUE=5
};
    //部分枚举常量被赋初值的情形:
int main()
{
    enum Day
    {
        Mon,
        Tues,
        Wed = 6,
        Thur,
        Fri,
        Sat = 20,
        Sun
    };
    enum Day enum1 = Mon;
    enum Day enum2 = Tues;
    enum Day enum3 = Wed;
    enum Day enum4 = Thur;
    enum Day enum5 = Fri;
    enum Day enum6 = Sat;
    enum Day enum7 = Sun;
    printf("%d %d %d %d %d %d %d\n", enum1, enum2, enum3, enum4, enum5, enum6, enum7);    //0 1 6 7 8 20 21
    return 0;
}
    //在既包含为赋初值的枚举常量也包含赋初值的枚举常量的情形中:在第一个赋初值的枚举常量之前的所有未赋初值的枚举常量的值均遵守默认从0开始一次递增1的规则;
    //赋初值的枚举常量后面的未赋初值枚举常量的值根据该赋初值的枚举常量的值一次递增1

//枚举的优点
    //1.增加代码的可读性和可维护性
    //2.和#define定义的标识符常量相比,枚举有类型检查,更加严谨
    //3.便于调试
    //4.使用方便,一次可以定义多个常量

//枚举的使用
    //只能用枚举常量给枚举变量赋值,可以保证类型不出现差异。不能用单纯的数字给枚举变量赋值
enum Color
{
    RED=1,
    GREEN=3,
    BLUE=5
};
enum Color clr = BLUE;    //clr是一个enum Color类型的枚举变量,用枚举常量BLUE给枚举变量clr赋值

猜你喜欢

转载自blog.csdn.net/libj2023/article/details/131521665