C语言自定义类型详解(结构体、枚举、联合体和位段)

目录
  • 前言
  • 一、结构体
    • 1、结构体类型的声明
    • 2、结构体的自引用
    • 3、结构体变量的定义和初始化
    • 4、结构体内存对齐
    • 5、结构体传参
  • 二、位段
    • 1、位段的定义
    • 2、位段的内存分配
    • 3、位段的应用
  • 三、枚举
    • 1、枚举类型的定义
    • 2、枚举的优点
    • 3、枚举的使用
  • 四、联合体(共用体)
    • 1、联合体的定义
    • 2、联合体的特点
    • 3、联合体的大小计算
  • 总结

前言

一、结构体

1、结构体类型的声明

当我们想要描述一个复杂变量——学生,可以这样声明。

✒️代码展示:

struct Stu
{
    char name[20];//名字
    int age;//年龄
    char sex[5];//性别
    char id[20];//学号
}s1;//分号不能丢
int main()
{
    struct Stu s2;
    return 0;
}

🔖解释说明:

  • struct是结构体的关键字
  • Stu是结构体标签名
  • struct Stu是结构体的类型
  • 大括号内包围的是结构体成员变量的列表
  • 变量s1是类型为struct Stu的全局变量,变量s2是该类型的局部变量

在声明结构时,也有特殊的声明,比如不完全声明——匿名结构体类型,省略掉了结构体标签。

✒️代码展示:

struct
{
    int a;
    char b;
    float c;
}x;
struct
{
    int a;
    char b;
    float c;
}a[20], *p;

那么,此时,问题来了!

在上面的代码基础上,p = &x,这样的代码合理吗?

而且,像这样的匿名结构体类型只能使用一次,因为没有标签名。

2、结构体的自引用

众所周知,函数可以自己调用自己,叫做函数的递归,那么结构体是否也有自己引用自己呢?如果有又是如何实现的呢?

✒️代码展示:

//代码一:
struct N
{
    int data;
    struct N next;
};
//代码二:
struct Node
{
    int data;
    struct Node* next;
};
//代码三:
typedef struct
{
    int data;
    Node* next;
}Node;
//代码四:
typedef struct Node
{
    int data;
    struct Node* next;
}Node;

🔖解释说明:

代码一:

这样自引用是不正确的。当想要计算struct N类型所占空间大小时,就会出现疯狂套娃现象,无法计算结果,因此是不可取的

代码二:

这才是自引用的正确打开方式。data中存放的数据,next中存放着下一个struct Node类型数据的地址

代码三:

该代码想要实现匿名结构体的自引用,但这样做是不可取的。因为需要完整的定义了该结构体才可以重新命名为Node。然而定义的成员列表中又有Node*,先后问题产生了。

代码四:

可以通过这种重定义方式实现自引用。

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

既然已经有了结构体类型,那么对其定义和初始化就变得非常的简单

✒️代码展示:

struct Point
{
    int x;
    int y;
}p1; //声明类型的同时定义变量p1
struct Point p2; //定义结构体变量p2
//初始化:定义变量的同时赋初值。
struct Point p3 = {x, y};
struct Stu     //类型声明
{
    char name[15];//名字
    int age;    //年龄
};
struct Stu s = {"zhangsan", 20};//初始化
struct Node
{
    int data;
    struct Point p;
    struct Node* next;
}n1 = {10, {4,5}, NULL}; //结构体嵌套初始化
struct Node n2 = {20, {5, 6}, NULL};//结构体嵌套初始化

4、结构体内存对齐

掌握了结构体的基本使用,还应当重点了解结构体内存对齐问题从而计算结构体的大小,这是一个关于结构体的重点考点

结构体的对齐规则:

  1. 第一个成员在与结构体变量偏移量为0的地址处。
  2. 其他成员变量需要对齐到对齐数的整数倍的地址处。
    对齐数 = 编译器默认的一个对齐数与该成员大小的较小值。
    VS中默认的值为8,Linux没有默认对齐数
  3. 结构体总大小为最大对齐数的整数倍。
  4. 当嵌套结构体时,嵌套的结构体对齐需要到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数的整数倍(包含嵌套结构体的对齐数)。

✒️代码展示:

//练习1
struct S1
{
    char c1;
    int i;
    char c2;
};
printf("%d\n", sizeof(struct S1));
//练习2
struct S2
{
    char c1;
    char c2;
    int i;
};
printf("%d\n", sizeof(struct S2));
//练习3
struct S3
{
    double d;
    char c;
    int i;
};
printf("%d\n", sizeof(struct S3));
//练习4-结构体嵌套问题
struct S4
{
    char c1;
    struct S3 s3;
    double d;
};
printf("%d\n", sizeof(struct S4));

👁效果展示:

🔖解释说明:

结构体类型struct S1和struct S2两者的成员组成是一样的,但是定义顺序有所差别,后者与前者相比将占用空间小的变量集中在了一起,导致两者在遵循结构体对齐条件下,所占内存大小不一样。做个对比吧!

结构体类型struct S3和struct S4是另外两个典型例子,后者嵌套前者。

简而言之,该做法就是为了拿空间换取时间

如果。。。

另外。。。

结构在对齐方式不合适的时候,我么可以自己更改默认对齐数。

这里我们将使用预处理指令#pragma来改变默认对齐数

✒️代码展示:

#include <stdio.h>
#pragma pack(8)//设置默认对齐数为8
struct S1
{
    char c1;
    int i;
    char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
#pragma pack(1)//设置默认对齐数为1
struct S2
{
    char c1;
    int i;
    char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
int main()
{
    printf("%d\n", sizeof(struct S1));
    printf("%d\n", sizeof(struct S2));
    return 0;
}

👁效果展示:

🔖解释说明:

5、结构体传参

✒️代码展示:

struct S
{
    int data[1000];
    int num;
};
struct S s = {{1,2,3,4}, 1000};
//结构体传参
void print1(struct S s)
{
    printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{
    printf("%d\n", ps->data[2]);
}
int main()
{
    print1(s);  //传结构体
    print2(&s); //传地址
    return 0;
}

👁效果展示:

🔖解释说明:

函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,导致性能的下降。比如在这里,如果直接传值s的话,由于结构体中创建了一个很大的数组data,导致结构体过大,传参时浪费的内存空间很大,效率低下。但是如果传址&s的话,作为一个指针,占四个字节,极大提高了运行效率。

简而言之,结构体传参时,传结构体的地址更好

二、位段

1、位段的定义

位段,C语言允许在一个结构体中以位为单位来指定其成员所占内存长度,这种以位为单位的成员称为“位段”或称“位域” 。利用位段能够用较少的位数存储数据。

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

  • 位段的成员必须是 int、unsigned int 、signed int、char 。
  • 位段的成员名后边有一个冒号和一个数字(指该成员占的比特位)。

✒️代码展示:

struct A
{
    int _a:2;
    int _b:5;
    int _c:10;
    int _d:30;
};

2、位段的内存分配

位段的内存分配规则

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

✒️代码展示:

struct S
{
    char a:3;
    char b:4;
    char c:5;
    char d:4;
}
struct S s = {0};
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;

🔖解释说明:

在VS编译器中开辟了空间以后,先使用低地址再使用高地址。并且剩余的比特位不够下一个变量存储时,那这一片空间将会被浪费。

简而言之,跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。

3、位段的应用

🔖解释说明:

上图是网络上IP数据包的格式,当你想要在网络上发一条消息给你的好友,信息是需要进行分装的,消息作为数据只是传输的一部分,还有一部分传输的是分装中的其他信息。比如4位版本号,4位首部长度,这些信息只需要4个bit,如若不使用位段,直接每个部分一个整形的给空间,就会造成空间的大量浪费。

三、枚举

1、枚举类型的定义

在数学和计算机科学理论中,一个集的枚举是列出某些有穷序列集的所有成员的程序,或者是一种特定类型对象的计数。这两种类型经常(但不总是)重叠。枚举在日常生活中很常见,例如表示星期的SUNDAY、MONDAY、TUESDAY、WEDNESDAY、THURSDAY、FRIDAY、SATURDAY就是一个枚举。

2、枚举的优点

枚举的优点

  1. 代码的可读性变高和可维护性变强
  2. 和#define定义的标识符相比较枚举更加严谨,因为有类型检查。
  3. 防止命名污染的现象
  4. 方便调试,且使用方便,可以一下子定义很多常量

3、枚举的使用

枚举的说明与结构和联合相似, 其形式为:

enum 枚举名
{
    标识符[=整型常数],
    标识符[=整型常数],
    ...
    标识符[=整型常数]
} 枚举变量;

如果枚举没有初始化,即省掉"=整型常数"时, 则从第一个标识符开始,顺次赋给标识符0, 1, 2, …但当枚举中的某个成员赋值后,其后的成员按依次加1的规则确定其值。

✒️代码展示:

//代码1
enum Num1
{
    x1,
    x2,
    x3,
    x4
}x;
//代码2
enum Num2
{
    y1,
    y2 = 0,
    y3 = 50,
    y4
};
int main()
{
    printf("%d %d %d %d\n", x1, x2, x3, x4);
    printf("%d %d %d %d\n", y1, y2, y3, y4);
    return 0;
}

👁效果展示:

注意

  1. 枚举中每个成员(标识符)结束符是==","== 不是";", 最后一个成员可省略","。
  2. 初始化时可以赋负数, 以后的标识符仍依次加1。
  3. 枚举变量只能取枚举说明结构中的某个标识符常量。
  4. 枚举值是常量,不是变量,不能在程序中用赋值语句再对它赋值(比如上面的代码出现y3 = 3; ❎)。
  5. 只能把枚举值赋予枚举变量,不能把元素的数值直接赋予枚举变量,除非进行了强制类型转换(比如上面的代码出现x = x2✔️ x = 1❎x = (enum Num1)1✔️)

四、联合体(共用体)

1、联合体的定义

需要使几种不同类型的变量存放到同一段内存单元中。也就是使用覆盖技术,几个变量互相覆盖。这种几个不同的变量共同占用一段内存的结构,在C语言中,被称作“共用体”类型结构,简称共用体,也叫联合体。

2、联合体的特点

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

✒️代码展示:

//联合类型的声明
union Un
{
    char c;
    int i;
};
//联合变量的定义
union Un un;
int main()
{
    //例①
    printf("%p\n", &(un.i));
    printf("%p\n", &(un.c));
    //例②
    un.i = 0x11223344;
    un.c = 0x55;
    printf("%x\n", un.i);
    return 0;
}

👁效果展示:

🔖解释说明:

通过例①的结果,我们可以直观发现成员变量c和成员变量i共用地址

例②更加证实这一点,由于大小端存储,变量i是以44 33 22 11这样的顺序存储的,因为变量c与其公用地址,因此55将44覆盖,在内存中变量i为55 33 22 11,打印出来为11 22 33 55

联合体的相关应用

在之前我们已经学会了判断计算机大小端的方法,这里可以通过共用体的特点来实现

#include <stdio.h>union Un{    char c;    int i;}num;int main(){    num.i = 1;    if(num.c == 1)    {        printf("小端存储")    }    else    {        printf("大端存储")    }    return 0;}

向成员变量i中存放一个1,查看成员变量c的值,由于该变量是char类型,因此只访问了第一个字节。

3、联合体的大小计算

联合体大小计算规则

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

✒️代码展示:

#include <stdio.h>
union Un
{
    char c;
    int i;
}num;
int main()
{
    num.i = 1;
    if(num.c == 1)
    {
        printf("小端存储")
    }
    else
    {
        printf("大端存储")
    }
    return 0;
}

👁效果展示:

总结

到此这篇关于C语言自定义类型的文章就介绍到这了,更多相关C语言自定义类型内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C语言自定义数据类型的结构体、枚举和联合详解

    结构体基础知识 首先结构体的出现是因为我们使用C语言的基本类型无法满足我们的需求,比如我们要描述一本书,就需要书名,作者,价格,出版社等等一系列的属性,无疑C语言的基本数据类型无法解决,所以就出现了最重要的自定义数据类型,结构体. 首先我们创建一个书的结构体类型来认识一下 struct Book { char name[20]; char author[20]; int price; }; 首先是struct是结构体关键字,用来告诉编译器你这里声明的是一个结构体类型而不是其他的东西,然后是Boo

  • C语言自定义类型的保姆级讲解

    前言 在我们日常写代码时,经常会遇到结构体类型的使用,今天带读者了解结构体类型的使用. 一.初始结构体 在了解结构体之前,我们先来了解一下结构体的基础只是,结构体到底是什么? 结构是一些值的集合,这些值称为成员变量.结构的每个成员可以是不同类型的变量. 下面举一个例子: struct tag { menber_list; //成员列表 }variable_list; //变量列表 例如我们使用结构体描述一台电脑 struct computer { int price;//价格 char name

  • C语言自定义类型详解(结构体、枚举、联合体和位段)

    目录 前言 一.结构体 1.结构体类型的声明 2.结构体的自引用 3.结构体变量的定义和初始化 4.结构体内存对齐 5.结构体传参 二.位段 1.位段的定义 2.位段的内存分配 3.位段的应用 三.枚举 1.枚举类型的定义 2.枚举的优点 3.枚举的使用 四.联合体(共用体) 1.联合体的定义 2.联合体的特点 3.联合体的大小计算 总结 前言 一.结构体 1.结构体类型的声明 当我们想要描述一个复杂变量--学生,可以这样声明. ✒️代码展示: struct Stu { char name[20

  • C语言:自定义类型详解

    目录 一.结构体 1.结构体变量的定义及初始化 2.结构体内存对齐 3.为什么要内存对齐呢? 二.位段 1.什么是位段 2.位段的内存分配 三.枚举 1.枚举的定义 2.枚举的优点 四.联合(共用体) 1.联合类型的定义 2.联合的特点 3.联合大小的计算 总结 一.结构体 1.结构体变量的定义及初始化 直接上代码: struct Point { int x; int y; }p1; //创建结构体时顺便创建变量,分号一定不能掉 struct Point p2; //单独创建变量 struct

  • C语言中自定义类型详解

    目录 结构大小 offsetof 结构体对齐规则 存在原因 总结 结构大小 我们先随便给出一个结构体,为了计算他的大小,我给出完整的打印方案: typedef struct num { char c; int n; char cc; }num; int main() { printf("%d\n", sizeof(num)); return 0; } 好了,按道理来说我计算一个结构体大小就看他的各个成员需要消耗多大的空间, num 结构体中三个成员分别是 char ,int ,char

  • 详解C语言结构体,枚举,联合体的使用

    目录 一.匿名结构体 二.结构体的自引用 1.声明时不要自己引用自己 2.结构体重命名时不能使用重命名 三.结构体内存对齐规则 1.结构体内存计算 2.结构体嵌套 3.通过调整结构体成员顺序,压缩内存 四.存在内存对齐的原因 五.修改默认对齐数 六.结构体传参 七.位段 1.位段在内存中的存储 2.位段的跨平台问题 八.枚举 1.枚举的定义 2.枚举的优点 九.联合体(共用体) 1.联合体大小的计算 2.使用联合体判断计算机的大小端字节序 一.匿名结构体 struct { char name[2

  • C语言详解结构体的内存对齐与大小计算

    目录 结构体的内存对齐 1.计算结构体的大小 2.结构体的对齐规则 3.为什么存在内存对齐? 4.总结 结构体的内存对齐 1.计算结构体的大小 struct S1 { char c1; // 1 byte,默认对齐数为8,所以c1的对齐数是1,第一个成员变量放在与结构体变量偏移量为0的地址处 int i; // 4 byte,默认对齐数为8,所以i的对齐数是4,所以i要放到偏移量为 4的整数倍 的地址处 char c2; // 1 byte,默认对齐数为8,所以c2的对齐数是1,所以c2要放到偏

  • C语言自定义类型超详细梳理之结构体 枚举 联合体

    目录 一.什么是结构体 1.结构体实现 2.匿名结构体类型 3.结构体自引用 4.结构体的内存对齐 5.结构体位段  二.什么是枚举 1.枚举类型的定义 2.枚举的优点 三.联合(共用体) 1.什么是联合(共用体) 2.联合(共用体)的定义 3.联合(共用体)的初始化 总结 一.什么是结构体 结构是一些值的集合,这些值称为成员变量.结构的每个成员可以是不同类型的变量. //结构体声明 struct tag //struct:结构体关键字,tag:标签名,合起来是结构体类型(类型名) { memb

  • C语言结构体,枚举,联合体详解

    目录 1.什么是结构体.枚举.联合体 2.定义结构体 2.1 包含结构体成员变量.variable 2.2 tag.结构体成员变量 2.3 用结构体声名变量 2.4 用typedef 创建新类型 2.5 两个结构体相互包含 2.6 结构体变量初始化 2.7 结构体指针 3.枚举 3.1 定义方式 3.2 为什么用枚举 3.3 枚举变量的定义 3.4 实例 3.5 枚举实际用途 4.联合体 4.1 与结构体区别 4.2 定义 总结 1.什么是结构体.枚举.联合体 结构体(struct)是由一系列具

  • Go语言struct类型详解

    struct Go语言中,也和C或者其他语言一样,我们可以声明新的类型,作为其它类型的属性或字段的容器.例如,我们可以创建一个自定义类型person代表一个人的实体.这个实体拥有属性:姓名和年龄.这样的类型我们称之struct.如下代码所示: 复制代码 代码如下: type person struct {     name string     age int } 看到了吗?声明一个struct如此简单,上面的类型包含有两个字段. 1.一个string类型的字段name,用来保存用户名称这个属性

  • C语言自定义类型全解析

    目录 前言 结构体类型 结构体的声明 结构体变量的定义与初始化 结构体的自引用 结构体的访问 结构体的传参 传结构体 传地址 结构体的内存对齐(强烈建议观看) 位段 位段的声明 位段的内存管理 位段的跨平台性  枚举类型 枚举类型的定义 枚举类型赋予初始值 枚举类型的优点 联合体类型 联合体的定义 联合体的特点  联合体内存大小的计算 前言 初学C语言 我们先接触的都是内置的类型 比如说int char short float double long等等 这一期就来聊一聊自定义类型的知识 结构体

  • 详解Swift语言中的类与结构体

    类 在 Swift 中类是建立灵活的构建块.类似于常量,变量和函数,用户可以定义的类的属性和方法.Swift给我们提供了声明类,而无需用户创建接口和实现文件的功能.Swift 允许我们创建类作为单个文件和外部接口,将默认在类一次初始化来创建. 使用类的好处: 继承获得一个类的属性到其他类 类型转换使用户能够在运行时检查类的类型 初始化器需要处理释放内存资源 引用计数允许类实例有一个以上的参考 类和结构的共同特征: 属性被定义为存储值 下标被定义为提供访问值 方法被初始化来改善功能 初始状态是由初

随机推荐