首页 > 编程语言 > 详细

C语言自定义数据类型:结构体、位段和枚举以及联合体

时间:2021-04-25 00:22:02      阅读:25      评论:0      收藏:0      [点我收藏+]
第一:结构体(struct)

1.1 定义

    结构体是包含一组类型可以不同的成员的集合(类似于python的类结构,struct -->class; 成员:--->属性)

1.2 声明

    如声明一个Student的结构体,包含name和age以及grade三个成员;
// 只定义和声明一个Student结构体类型
struct Student
{   
    char name[20];
    int age;
    double grade;
};

// 只定义和声明一个struct Student结构体类型并进行重命名为Student
typedef struct Student
{
    char name[20];
    int age;
    double grade;
}Student;

// 声明Student结构体类型的同时定义2个结构体变量
struct Student
{
    char name[20];
    int age;
    double grade;
}stu1, stu2;// 此时不能用typedef进行重命名操作。

// 声明匿名结构体类型的同时定义2个结构体变量
struct 
{
    char name[20];
    int age;
    double grade;
}stu1, stu2;// 此时不能用typedef进行重命名操作。

1.3 定义结构体变量

struct Student s1,s2;
Student s3,s4; // 这里的Student是typedef重命名后的名字

1.4 结构体的嵌套使用

    一个定义好的结构体变量,作为另外一个结构体的成员名字,如:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
typedef struct Grade
{
    double mathg;
    double chineseg;
    double historyg;
};
// 只定义和声明一个Student结构体类型并重命名
typedef struct Student
{   
    char name[20];
    int age;
    struct Grade grade; // 为一个结构体类型变量
}Student;
int main()
{
    printf("ok\n");
    return 0;
}

1.5 结构体自引用(链表的节点)

    该结构体类型的指针变量作为该结构体的一个成员名,称为结构体的自引用,主要应用于链表数据结构中,例如:
#include <stdio.h>
#include <stdlib.h>
typedef struct Student
{   
    char name[20];
    int age;
    struct Student* next; // 注意这里的struct关键字不能省略,否则报语法错误。即先有成员,然后再有结构体类型
}Student;
int main()
{
    printf("ok\n");
    return 0;
}

1.6 结构体变量的定义和初始化以及解引用

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

typedef struct Grade
{
    double mathg;
    double chineseg;
}Grade;
typedef struct Student
{   
    char* name;
    int age;
    Grade grade;
}Student;
int main()
{
    Student stu1;   // 定义一个Student结构体类型的变量
    Student stu2 = { "Lilei", 18, {88.0,99.0} }; // 定义一个Student结构体类型的变量,并初始化
    Student stu3 = { "Hanmei", 19, {77, 88} };
    Student* pstu;  // 定义一个Student结构体类型的指针变量
    pstu = &stu3;
    // 结构体变量成员赋值
    stu1.name = "Luxi";
    stu1.age = 17;
    stu1.grade.mathg = 99.0;
    stu1.grade.chineseg = 100.0;
    // 解引用操作,如果是结构体变量,    用.进行解引用
    // 解引用操作,乳沟是结构体指针变量,用->进行解引用
    printf("name = %s age = %d math = %.2lf, chinese = %.2lf\n", stu1.name, stu1.age, stu1.grade.mathg, stu1.grade.chineseg);
    printf("name = %s age = %d math = %.2lf, chinese = %.2lf\n", stu2.name, stu2.age, stu2.grade.mathg, stu2.grade.chineseg);
    printf("name = %s age = %d math = %.2lf, chinese = %.2lf\n", pstu->name, pstu->age, pstu->grade.mathg, pstu->grade.chineseg);
    return 0;
}

技术分享图片

1.7 计算结构体所占字节大小

1.7.1 结构体的对齐原则:

    1. 第一个成员的地址与结构体变量的地址相同,即其地址为结构体变量地址的0偏移量。
    2. 其他成员变量要对齐到"对齐数"的整数倍的地址处。
    3. 对齐数 = min{编译器默认的对齐数, 该成员所占字节大小}。
            VC默认对齐数为:8
            Linux默认对齐数为:没有默认大小,一般为变量类型大小
    4. 结构体大小为所有成员中的对齐数中的最大的对齐数的的整数倍     
    5. 对于嵌套结构体,嵌套的结构体对齐到自身的最大对齐数的整数倍,而结构体的整体大小为所有成员中的最大对齐数的整数倍。

1.7.2 存在内存对齐的原因:

    1. 有些硬件平台是不能访问任意地址上的数据的。
    2. 提高内存的访问效率,在32位系统上,一次读取四个字节,对于char a; int b;只需要访问1次就可以访问到b,否则需要访问两次,才能访问到b。
    3. 是用空间来换时间的做法。因为空间是可以增加的,但是时间是宝贵的。

1.7.3 设计结构体的成员时注意事项

    1. 让占字节小的成员写到前面
    2. 让占字节大的成员写到后面
    3. 类型相同的成员放到一起

1.7.4 修改默认对齐数

    #pragma pack(4); // 修改默认对齐数为4
    ...
    #pragma pack(); // 取消设置的默认对齐数,还原为默认值
    例子:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
struct Person
{
    int age; // 第一个成员的地址与结构体变量的地址之间的偏移量为0,占4个字节,此时偏移量为4
    double weight; // 对齐数为min{sizeof(double), 8} = 8,偏移量为对齐数的整数倍,偏移1倍8个字节,与第一个成员之间浪费4个字节,此时偏移量为16
    char addr;     // 对齐数为min{sizeof(char), 8} = 1, 偏移量为对齐数的整数倍,即其地址紧挨着weight,此时偏移量为17
    // 结构体的大小为最大对齐数max{4,8,1} = 8的整数倍,最后一个成员之后偏移17,3 * 8 = 24,后面浪费7个字节为8的整数倍
    // 该结构体大小为24
};
struct Student1
{
    char name;  // 第一个成员的地址与结构体变量的地址之间的偏移量为0,占1个字节,此时偏移量为1
    int age;    // 对齐数为min{ sizeof(int), 8 } = 4, 偏移量为对齐数的整数倍:4,放在偏移量为4的位置上之后,此时偏移为8
    char sex;   // 对齐数为1,放在偏移量为8的位置上,此时偏移量为9
    // 结构体大小为最大对齐数4的整数倍,故为偏移量为12,后面再浪费3个字节空间
    // 该结构体大小为12
};
struct Student2
{
    char name; // 同上
    char sex;  // 对齐数为1,放在偏移量为1的位置,此时偏移量为2
    int age;   // 对齐数为4, 放在偏移量为4的位置,此时偏移量为8
    // 结构体大小为最大对齐数4的倍数,且8 是4的整数倍
    // 该结构体大小为8
};
struct Student3
{
    char name;   // 同上
    struct Person p; // 该结构体占24个字节,对齐数为8,放在偏移量为8的位置上,此时偏移量为32
    char sex;        // 对齐数为1,放在偏移量为32的位置,此时偏移量为33
    int age;         // 对齐数为4, 放在偏移量为对齐数4的整数倍36的位置,此时偏移量为40
    // 结构体大小为最大对齐数max{4 8 1 1 1 4} = 8的整数倍,40是8的整数倍
    // 该结构体大小为40
};
int main()
{
    printf("Student1 %d\n", sizeof(struct Student1));
    printf("Student2 %d\n", sizeof(struct Student2));
    printf("Student3 %d\n", sizeof(struct Student3));
    printf("Person %d\n", sizeof(struct Person));
}

技术分享图片

1.8 查看成员偏移量offsetof(结构体变量名, 成员名)

#include <stdio.h>
#include <stddef.h>
struct Person
{
    int age; // 第一个成员的地址与结构体变量的地址之间的偏移量为0,占4个字节,此时偏移量为4
    double weight; // 对齐数为min{sizeof(double), 8} = 8,偏移量为对齐数的整数倍,偏移1倍8个字节,与第一个成员之间浪费4个字节,此时偏移量为16
    char addr;     // 对齐数为min{sizeof(char), 8} = 1, 偏移量为对齐数的整数倍,即其地址紧挨着weight,此时偏移量为17
    // 结构体的大小为最大对齐数max{4,8,1} = 8的整数倍,最后一个成员之后偏移17,3 * 8 = 24,后面浪费7个字节为8的整数倍
    // 该结构体大小为24
};
struct Student3
{
    char name;   // 同上
    struct Person p; // 该结构体占24个字节,对齐数为8,放在偏移量为8的位置上,此时偏移量为32
    char sex;        // 对齐数为1,放在偏移量为32的位置,此时偏移量为33
    int age;         // 对齐数为4, 放在偏移量为对齐数4的整数倍36的位置,此时偏移量为40
    // 结构体大小为最大对齐数max{4 8 1 1 1 4} = 8的整数倍,40是8的整数倍
    // 该结构体大小为40
};
int main()
{
    printf("%d\n", offsetof(struct Student3, name)); 
    printf("%d\n", offsetof(struct Student3, p));
    printf("%d\n", offsetof(struct Student3, sex));
    printf("%d\n", offsetof(struct Student3, age));
    return 0;
}

技术分享图片

第二:位段

2.1 定义

    位段的声明和结构结构体是一样的,关键字也是struct,或者是结构体的一种特例。

2.2 特点

    1. 其成员中必须是int 、unsigned int、short、char等整型类型的变量
    2. 成员的类型最好相同,比如都是int 或者都是short等
    3. 其成员名后面必须有一个冒号和一个数字
    4. 冒号后面的数字代表的是该成员所占的二进制位(比特位)的个数

2.3 位段大小

    位段在内存空间上的开辟方式是:一次开辟4个字节(int类型)或者1个字节(char类型)的。
    如果开辟的空间上有足够的位来存放一个成员,则存放,否则直接浪费剩余的位,直接开辟新的空间
    注意:位段是不跨平台的,因此可移植性较差。
    讲解下面的例子:
    1. 由于成员的类型时int,所以一次开辟4个字节空间给成员
    2. 成员a占3个比特位,放到了第一个字节的前3个比特位上,该字节剩余29个比特位。
    3. 成员b占4个比特位,开辟的空间中有足够的比特位来存放该成员,因此该成员放到开辟空间的第四-第七个比特位上,此时开辟的空间还有25个比特位。
    4. 成员c占10个比特位,开辟的空间有足够的空间,将其放在开辟空间的第八个比特位上,此时还剩下15个比特位。
    5. 成员d占23个比特位,开辟的空间中没有足够的空间,因此需要再开辟4个字节,将d放在开辟空间的前23个比特位上,此时剩下9个比特位。
    6. 成员e占29个比特位,开辟的空间没有足够的空间,需要再开辟4个字节。将e放到开辟空间的前29个比特位上,此时剩余3个比特位。
    7. 共开辟了3次空间(3 *4 = 12)个字节,因此该位段所占字节为12,即大小为12.
    a:1010,占三个字节为:010
    b:10100,占4个字节为:0100
    c:11:
    d:100
    e:101
    abc在内存中为:
    00000000 00000000 00000001 10100010
    d在内存为:
    00000000 00000000 00000000 00000100
    e在内存为:
    00000000 00000000 00000000 00000101
    00000000 00000000 00000000 00000000
                                   |c                   |b    |a   |
    00000000 00000000 00000000 00000000
                       |d                                            |
    00000000 00000000 00000000 00000000
          |e                                                         |

因此打印的结果为:418

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

// 声明一个位段结构体:WEIDUAN
struct WD0
{
    int a : 3;
    int b : 4;
    int c : 10;
    int d : 23;
    int e : 29;
};
int main()
{
    struct WD0 wd0 = {0}; // 创建一个位段类型的变量 weiduan
    int i;
    int* p = (int*)&wd0;
    wd0.a = 10;
    wd0.b = 20;
    wd0.c = 3;
    wd0.d = 4;
    wd0.e = 5;
    printf("%d\n", sizeof(struct WD0)); // 12
    for (i = 0; i < sizeof(struct WD0)/sizeof(int); i++)
    {
        printf("%d\n", *(p+i));
    }
    return 0;
}

技术分享图片
技术分享图片

例子2:char类型的成员

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

struct WD1
{
    char a : 3;
    char b : 4;
    char c : 5;
    char d : 4;
};
int main()
{
    int a = 0x1102ccff;
    struct WD1 wd1 = { 0 };
    char* p = NULL;
    p = (char*)&wd1;
    wd1.a = 10;
    wd1.b = 20;
    wd1.c = 3;
    wd1.d = 4;
    printf("%d\n", sizeof(wd1));
    printf("%#x\n", *(p+0));
    printf("%#x\n", *(p + 1));
    printf("%#x\n", *(p + 2));
    return 0;
}

技术分享图片

技术分享图片

技术分享图片

技术分享图片

技术分享图片

技术分享图片

第三:枚举

3.1 定义

    将有限个能够取到的值,放到一起,用enum关键字进行修饰的类型,称为枚举类型,枚举类型变量的关键字:enum,其中每个元素称为枚举元素,即枚举的可能取值。
    如:enum Day {Mon, Tues, Wed, Thur, Fri, Sat, Sun};就是一个枚举类型的变量。
    注释:枚举的可能取值是不能用双引号进行修饰。
               枚举元素是有默认值的,第一个枚举元素的默认值为0,后面的值依次增加1.
               枚举元素的值时可以修改的,如第一个枚举元素的值修改为3,则后面元素的值依次分别为4 5 6 ...
               枚举元素的值可以是重复的,如Mon = 3, Fri = 3;

3.2 枚举的使用

枚举元素的值不相同

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
enum Day1 { Mon, Tues, Wed, Thur, Fri, Sat, Sun };
//enum Day2 { Mon = 2, Tues, Wed, Thur = 2, Fri = 4, Sat = 6, Sun };
int main()
{
    enum Day1 d1 = Mon;
    if (Mon == 0)
    {
        printf("周末结束啦,又要上班啦\n");
    }
    printf("%d %d %d %d %d %d %d\n", Mon, Tues, Wed, Thur, Fri, Sat, Sun);
    return 0;
}

技术分享图片
枚举元素值有相同时

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
//enum Day1 { Mon, Tues, Wed, Thur, Fri, Sat, Sun };
enum Day2 { Mon = 2, Tues, Wed, Thur = 2, Fri = 4, Sat = 6, Sun };
int main()
{
    enum Day1 d2 = Mon;
    if (d2 == 0)
    {
        printf("周末结束啦,又要上班啦\n");
    }
    else
    {
        printf("哈哈,工作日居然不上班,爽!\n");
    }
    printf("%d %d %d %d %d %d %d\n", Mon, Tues, Wed, Thur, Fri, Sat, Sun);
    return 0;
}

技术分享图片

3.3 枚举的优势(#define)

    1. 增加代码的可维护性以及可读性。
    2. 枚举有类型检查的功能,而#define仅仅做了替换(预编译阶段,该阶段会把所有的注释全部删除)的工作(源代码-预编译-编译-链接-可执行结果)
    3. 防止命名交叉感染
    4. 使用方便,因为可以一次定义多个常量,且该常量集合的性质一样。

第四:联合体(共用体)

4.1 定义

    是一种特殊的自定义类型,和结构体有点类似(只是关键字不同,这里为union),也会包含一系列的成员。
    但是这些成员公用一块内存空间,即同一时刻只能使用一个成员。
    例如:union desc {char sex;int age};

4.2 联合体大小

    联合体大小为至少是最大成员所占字节的大小。
    最大成员不是最大对齐数的整数倍时,就要对齐到最大对齐数的整数倍。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
union Desc 
{ 
    char sex; 
    int age;
};
int main()
{
    union Desc des;
    printf("%d\n", sizeof(des));
    printf("%p\n", &des);
    printf("%p\n", &(des.age));
    printf("%p\n", &(des.sex));
    return 0;
}

技术分享图片

4.3 判断字节的大小端存储

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

// 返回1:小端
// 返回0:大端
int bs_byte()
{
    union BS
    {
        int a;
        char c;
    } bs;
    bs.a = 1;
    return (int)(bs.c);
}
int main()
{
    int ret = bs_byte();
    if (1 == ret)
    {
        printf("小端\n");
    }
    else
    {
        printf("大端\n");
    }
    return 0;
}

技术分享图片

C语言自定义数据类型:结构体、位段和枚举以及联合体

原文:https://blog.51cto.com/u_15132389/2729488

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!