讲解一下C语言中的自定义数据类型

目录

一.结构体

1.1 结构体的声明

1.2 结构体的自引用

1.3 结构体变量的定义与初始化

1.4 结构体的内存对齐

1.5 修改结构体的默认对齐数

1.6 结构体传参

二. 位段

2.1 什么是位段

2.2 位段的内存分配

三. 枚举

3.1 枚举类型的定义

3.2 枚举类型的值

四. 联合体-共用体

4.1 联合体的定义

4.2 联合体的大小计算


一.结构体

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

1.1 结构体的声明

声明一个学生类型,是通过学生类型来创建学生变量(对象),属性-姓名-年龄-性别-学号。

示例:

struct Stu
{
    char name[20]; //名字;
    int age;// 年龄;
    char sex[5];  //性别;
    char id[20]; //学号;
} s4,s5,s6;//分号不能丢,s4,s5,s6都是全局的结构体变量;
struct Stu s3;// 全局变量;
int main()
{
    //创建结构体变量;
    struct Stu s1;
    struct Stu s2; 
}

匿名结构体:

struct
{
    int a;
    char c;
}sa;   //该方式为匿名结构体,只能用一次;
struct 
{
    int a;
    char c;
}* psa;
int main()
{
    psa=&sa;  //是不行的,因为上面两种匿名结构体,系统当成了不同的两种类型;
}

1.2 结构体的自引用

顾名思义,即结构体的成员变量中有结构体。

示例:

struct Node
{
    int data;
    struct Node* next; //链表中,节点中,一个存放数值,一个存放下一个节点的地址;
}

1.3 结构体变量的定义与初始化

struct Point
{
    int x;
    int y;
}p1;  //声明结构体变量的同时定义变量p1;
struct Point p2;  //定义结构体变量p2;

初始化:

struct s
{
    char c;
    int a;
    double d;
    char arr[20];
};
int main()
{
    struct s s1={'c',100,3.14,"hello bit"};  //结构体的初始化;
    printf("%c %d %lf %s",s1.c,s1.a,s1.d,s1.arr);  //打印结构体成员;
}

嵌套结构体的初始化:

struct T
{
    double weight;
    short age;
};
struct S
{
    char c;
    struct T st;   //嵌套结构体的初始化;
    int a;
    double d;
    char arr[20];
};
int main()
{
    struct S s{'c',{55.6,30},100,3.14,"hello bit"};
    printf("%lf\n",s.st.weight);
}

1.4 结构体的内存对齐

先看下面这个示例:

struct S1
{
    char c1;
    int a;
    char c2;
};
struct S2
{
    char c1;
    char c2;
    int a;
};
int main()
{
    struct S1 s1={0};
    printf("%d\n",sizeof(s1));
    struct S2 s2={0};
    printf("%d\n",sizeof(s2));  //结果为12 和 8
    return 0;
}

我们可以发现结构体S1的大小为12,结构体S2的大小为8,并不是简单的将各个成员变量的大小进行相加,这主要是由于结构体存在内存对齐,其计算规则如下:

计算规则:第一个成员在与结构体变量偏移量为0的地址处,其他成员变量要对齐到某个数字(对齐数)的整数倍的地址;

对齐数=编译器默认的一个对齐数 与 该成员 大小的较小值;(VS中默认的值为8)

结构体的总大小为最大对齐数的整数倍。

解释:像上面的结构体S1,其第一个类型char类型为一个字节,此时大小为1,然后第二个数据类型为int,int的大小为4,与默认对齐数进行比较,4<8,故此时对齐数为4,故下一个数据开始存放的地方要是4的倍数,即第二个数据要从4的地方开始存储,然后int类型占4个字节,此时总大小为8; 随后,第三个为char类型,此时8是char的整数倍,故此时总大小就是9,三个数据的对齐数分别是1,4,1,其中的最大值为4,而最后的总大小要是最大对齐数的整数倍,故此时9应该再找3个空间,使得结构体的大小变为12.

同理,看结构体S2,其内存计算过程一样,第一个占据1个字节,第二个还是char类型此时占据的还是1个字节,第三个是int类型,故第三个要从4的倍数即4开始进行存储4个字节到8,然后8是4的整数倍,故最终大小就是8.

以下也提供两个示例供大家进行练习:

练习1:

struct s3
{
    double d;
    char c;
    int i;
};
printf("%d\n",sizeof(struct s3));

练习2:

struct s4
{
    char c1;
    struct s3 s3;
    double d;
};
printf("%d\n",sizeof(struct s4)); 

1.5 修改结构体的默认对齐数

可以使用 #pragma pack()这个预处理命令来修改结构体的默认对齐数

示例:

#pragma pack(4)  //设置默认对齐数为4;
struct s
{
    char c1; //
    //7
    double d;// 8
};
#pragma pack()  //取消设置的默认对齐数,为了性能故不会设置成1;

1.6 结构体传参

在使用结构体时,常用的传参方式有传值与传址两种方式,以下做示例,其中最好使用传址的方式

示例:

struct S
{
    int a;
    char c;
    double d;
};
void Init(struct S* ps)
{
    ps->a=100;
    ps->c='w';
    ps->d=3.14;
}
void Print1(struct S tmp)
{
    printf("%d %c %lf\n",tmp.a,tmp.c,tmp.d);
}
void Print2(struct S* ps)
{
    printf("%d %c %lf\n",ps->a,ps->c,ps->d);
}
int main()
{
  struct S s={0};
    Init(&s);
        Print1(s);
    Print2(&s);  //尽量使用这种传址的方式,原因:Print1需要压栈的,如果传过去的结构体大小过大,会导致性能的下降。
}

二. 位段

结构体讲完后,就得讲讲结构体实现位段的能力。

2.1 什么是位段

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

1.位段的成员必须是int、unsigned int或者 signed int;

2.位段的成员名后面有一个冒号和一个数字;

比如:

//位段——其中的位为二进制位;
struct A
{
    int a:2;  //a只需要2个bit位,对应2个二进制位;
    int b:5;  //b只需要5个bit位, 对应5个二进制位; 
    int c:10;
    int d:30;
};  //A就是一个位段类型; //总共需要47个bit位,换算可以得到4个字节(一个字节8个bit位)
int main()
{
    struct A a;
    printf("%d\n",sizeof(a));  //结果为8,单位为字节;

int 4个字节,32个bit位,当放下2,5,10后只剩下15个bit位了,放不下30,此时舍弃掉剩下的15,重新来一块32的放30,故最终是两个4字节,即8字节

2.2 位段的内存分配

1.位段的成员可以是int, unsigined int, signed int 或者是char类型;

2.位段的空间上是按照需要4个字节或者1字节的方式来开辟的;

3.位段涉及很多不确定因素,不可以跨平台,注意可移植程序避免使用位段。

位段可以很好的节省空间,但是有跨平台的问题存在。

三. 枚举

枚举顾名思义就是——列举,把可能的取值一一列举。

3.1 枚举类型的定义

//枚举类型
enum Sex //性别
{
    //枚举的可能取值,也叫枚举常量
    MALE,
    FEMALE,
    SECRET
};

3.2 枚举类型的值

enum Sex
{
    MALE,
    FEMALE,
    SECRET
};
enum Color
{
    RED=2,
    YELLOW=4,
    BLUE=8
};
int main()
{
    enum Sex s=MALE;
    s=FEMALE;
    enum Color c=BLUE;
    printf("%d %d %d",MALE,FEMALE,SECRET); //打印结果为0  1  2,第一个默认为0,后面的+1;
    printf("%d %d %d",RED,YELLOW,BLUE); //打印结果为2,4,8;
    return 0;
    
}

如果枚举类型有进行初始化,则按照初始化的值进行打印,若没有,则第一个默认为0,后面的加一,若上面的blue不进行初始化,则此时Blue的值将变为5,即yellow的值进行加一。

四. 联合体-共用体

4.1 联合体的定义

联合也是一种特殊的自定义类型,这种类型的变量包含一系列的成员,特征是这些成员共用一块空间。

比如:

union Un
{
    char c;
    int i;
};
int main()
{
    union Un u;
    printf("%d\n",sizeof(u));  //大小为4,联合变量的大小是最大成员的大小;
    printf("%p\n",&(u.c));
    printf("%p\n",&(u.i));
    printf("%p\n",&u);  //三个得到的地址完全相同;
    return 0; 
}

4.2 联合体的大小计算

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

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

示例:

union Un
{
    int a;//4 ,默认对齐数为8,4比较小,所以为4
    char arr[5];//5,char 为1,1比8小,为1;
    //最大对齐数为4
};
int main()
{
    union Un u;
    printf("%d\n",sizeof(u));  //结果为8
    return 0;
}

猜你喜欢

转载自blog.csdn.net/yss233333/article/details/123761594