自定义类型详解(结构体+枚举+联合)

1、结构体

1.1、结构体的基础知识

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

 1.2、结构体的声明

struct tag
{
    member - list;
}variable-list;


1.3、特殊声明
 

struct //匿名结构体类型
{
    int a;
    char b;
    float c;
}x;//匿名结构体类型没有标签名,创建结构体变量只能在创建结构体类型时一起创建
struct 
{
    int a;
    char b;
    float c;
}*p;
int main()
{
    p = &x;//这个代码是错误的,虽然上面两个结构体内容相同,但在内存中是两个不同的类型
    return 0;
}


1.4、结构的自引用

struct node
{
    int data;
    struct node* next;//需要使用指针
};
typedef struct
{
    int data;
    struct node* next;
}node;//此处的自引用是不合法的,结构体在自引用之前是匿名结构体,在引用后才进行重命名

1.5、结构体变量的定义和初始化
 

struct SN
{
    char c;
    int i;
}sn1 = { 'a',10 }, sn2 = { .c = 'b',.i = 11 };//全局变量

struct S
{
    double b;
    struct SN sn;
    int arr[10];
};
int main()
{
    struct SN sn3, sn4;//局部变量
    printf("%c %d\n", sn2.c, sn2.i);
    struct S s= { 3.14,{'x',100},{1,2,3,4,5,6} };
    printf("%f %c %d\n", s.b, s.sn.c, s.sn.i);
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        printf("%d ", s.arr[i]);
    }
    return 0;
}

1.6、结构体内存对齐-主要考察计算结构体的大小

#include<stddef.h>/*offsetof*/
struct S1
{
    char c1;
    int i;
    char c2;
};

struct S2
{
    int i;
    char c1;
    char c2;
};

int main()
{
    printf("%d\n", sizeof(struct S1));
    printf("%d\n", sizeof(struct S2));
    printf("%d\n", offsetof(struct S1, c1));//offsetof可以计算结构体成员相较于结构体的偏移量
    printf("%d\n", offsetof(struct S1, i));
    printf("%d\n", offsetof(struct S1, c2));
    printf("%d\n", offsetof(struct S2, i));
    printf("%d\n", offsetof(struct S2, c1));
    printf("%d\n", offsetof(struct S2, c2));
    return 0;
}

结构体成员不是按照顺序在内存中连续存放的,而是有一定的对齐规则的
1、结构体的第一个成员永远放在相较于结构体变量起始位置的偏移量为0的位置;
2、从第二个成员开始,往后的每个成员都要对齐到某个对齐数的整数倍处;
         对齐数:结构体成员自身的大小和默认对齐数的较小值,VS上默认对齐数是8,gcc没有默认对齐数,对齐数就是结构体成员的自身大小
3、结构体的总大小,必须是最大对齐数的整数倍
        最大对齐数;所有成员的对齐数中最大的值
4、如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍


struct S3
{
    double d;
    char c;
    int i;
};
struct S4
{
    char c1;
    struct S3 s3;
    double d;
};
int main()
{
    printf("%d\n", sizeof(struct S3));
    printf("%d\n", sizeof(struct S4));
    return 0;
}

1.7、修改默认对齐数


#pragma pack(1)//将默认对齐数修改为1
struct S
{
    char c1;
    int a;
    double d;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
int main()
{
    printf("%d\n", sizeof(struct S));
    return 0;
}

1.8、结构体传参

struct S
{
    int arr[1000];
    char c;
};
print1(struct S tmp)
{
    printf("%c\n", tmp.c);
}
print2(const struct S* tmp)//此处需加const修饰,防止结构体变量被修改
{
    printf("%c\n", tmp->c);
}
int main()
{
    struct S s = { {1,2,3,4},'c'};
    print1(s);
    print2(&s);//此处传参首选print2传地址,因为函数传参的时候参数需要压栈,当传递的结构体过大时,参数压栈的系统开销过大,会导致性能下降
    return 0;
}

2、位段

2.1、什么是位段

位段的声明和结构是类似的,有两个不同:
        1、位段的成员必须是int、unsigned int或signed int
        2、位段的成员名后边有一个冒号和一个数字

struct A
{
    int _a : 2;//数字2代表成员_a占2个比特位,下面_b、_c、_d依此类推,则位段A占37个比特位,1个整型占32比特位,32<37,则占两个整型,8字节
    int _b : 5;
    int _c : 10;
    int _d : 20;
};
int main()
{
    printf("%d\n", sizeof(struct A));
    return 0;
}

2.2、位段的内存分配

        1、位段的成员可以是int、unsigned int、signed int 或者是char(属于整型家族)类型
        2、位段的空间上是按照需要以4个字节(int)或者1个字节(char)的方式来开辟的
        3、位段涉及很多不确定因素,位段是不跨平台的,注意可移植的程序需避免使用位段的


2.3、位段的跨平台问题

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

2.4、位段的应用


3、枚举

3.1、枚举类型的定义

3.2、枚举的优点

我们可以使用#define定义常量,为什么非要使用枚举

1、增加代码的可读性和可维护性

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

3、便于调试

4、使用方便,一次可以定义多个常量

enum Color
{
    RED,
    GREEN=4,
    BLUE//{}中的内容都是枚举类型的可能值,也叫枚举常量,这些取值都是有值的,默认从0开始,一次递增1,在声明枚举类型的时候也可以赋初值(如上GREEN=4)
};
int main()
{
    enum Color c = GREEN;
    printf("%d\n", RED);
    printf("%d\n", GREEN);
    printf("%d\n", BLUE);

    return 0;
}

4、联合

4.1、联合类型的定义

union Un
{
    char c;
    int i;
};

int main()
{
    printf("%d\n", sizeof(union Un));
    union Un un = { 0 };
    printf("%p\n", &un);
    printf("%p\n", &(un.c));
    printf("%p\n", &(un.i));//联合的两个成员的地址相同
    return 0;
}

4.2、联合的特点

联合的成员是共用同一块内存空间的,这样的一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)

union Un
{
    char c;
    int i;
};

int main()
{
    union Un un = { 0 };
    printf("%p\n", &(un.c));
    printf("%p\n", &(un.i));
    un.i = 0x11223344;
    un.c = 0x55;
    printf("%x\n", un.i);
    return 0;
}

使用联合判断机器是大端存储还是小端存储

union Un
{
    char c;
    int i;
};

int main()
{
    union Un un = { 0 };
    un.i = 1;//将联合体的内容修改位0x00 00 00 01
    if (un.c == 1)//c为字符类型,只从低地址位取出一个字节
        printf("小端存储\n");
    else
        printf("大端存储\n");
    return 0;
}

封装为函数

int check_sys()
{
    union
    {
        char c;
        int i;
    }un = { .i = 1 };
    return un.c;
}

int main()
{
    int ret = check_sys();
    if (ret == 1)
        printf("小端存储\n");
    else
        printf("大端存储\n");
    return 0;
}

4.3、联合大小的计算

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

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

union Un
{
    char c[7]; 
    int i;
};

int main()
{
    printf("%d\n", sizeof(union Un));//Un的大小为8
    return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值