C语言自定义类型(结构体,枚举,联合):

大家好久不见,今天我们来学习一下C语言中的自定义类型:

C语言的自定义类型包括:结构体,枚举和联合,接下来大家跟我来一起认识一下这三种类型。

目录

1. 结构体

1.1.1 结构体类型的声明

1.1.2 结构的特殊声明

1.1.3 结构体的自引用

1.2. 结构体的内存对齐

1.2.1 对⻬规则

1.2.2 为什么有内存对齐

1.2.3 修改对齐数 

1.3 结构体实现位段

1.3.1 什么是位段

1.3.2 位段的内存分配 

1.3.3 位段的问题

2. 联合体

2.1.1 联合体类型的声明

2.2 联合体的特点

2.2.1 联合体大小的计算 

2.3 联合体的使用案例

 3. 枚举类型

3.1 枚举类型的声明

2.2 枚举类型的优点 


1. 结构体

结构是⼀些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
1.1.1 结构体类型的声明
struct tag
{
    member- list ;
}variable- list ;

 例如我们要写一个学生的结构体,包含学生的年龄,性别,姓名和学号,

struct Stu
{
     char name[ 20 ]; // 名字
     int age; // 年龄
     char sex[ 5 ]; // 性别
     char id[ 20 ]; // 学号
}; // 分号不能丢
1.1.2 结构的特殊声明

在声明结构的时候,可以不完全的声明。

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

 当创建无命名的结构体时,我们称该结构体为匿名结构体,那这种结构体有没有什么问题呢?

p=*x; 

当执行p=*x代码时,编译器会将这个结构体认成是两种不同的类型,但实际上这是同一个结构体。

所以对于匿名结构体来说,应该将所有该结构体变量在声明时创建。 

1.1.3 结构体的自引用
struct Node
{
     int data;
     struct Node next ;
};

 那 么将结构体作为它自身的一个成员 这样行不行得通呢?

如果这样的话,在计算结构体类型的大小时就会像套娃一样无穷的计算下去。所以是不可行的。

那么如果我们想让结构体作为成员,应该怎么来声明呢?

我们知道,指针的大小只与机器本身有关,如果我们使用指向结构体的指针作为成员,不就解决这个问题了吗?

struct Node
{
     int data;
     struct Node* next ;
};

 那如果我们在定义结构体时对结构体进行了typedef重命名,在指针部分可以使用重命名的名字吗?

答案是不行的,因为结构是在成员创建之后才进行的重命名,所以要用原名。

typedef struct
{
    int data;
    Node* next;    //错误
}Node;
 
typedef struct
{
    int data;
    struct Node* next;     //正确
}Node;
1.2. 结构体的内存对齐
我们已经掌握了结构体的基本使⽤了。现在我们深⼊讨论⼀个问题:计算结构体的⼤⼩。
结构体是一种类型,所以可以通过sizeof()来计算结构体的大小。那么问题来了,结构体的大小应该怎么计算呢?
我们接下来来学习一下结构体的内存对齐规则。
1.2.1 对⻬规则
⾸先得掌握结构体的对⻬规则:
1. 结构体的第⼀个成员对⻬到和结构体变量起始位置偏移量为0的地址处。
2. 其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。
对⻬数 = 编译器默认的⼀个对⻬数 与 该成员变量⼤⼩的较⼩值。

- VS 中默认的值为 8
- Linux中 gcc 没有默认对⻬数,对⻬数就是成员⾃⾝的⼤⼩
3. 结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍。
对其数与机器也有关,一般在32位机器下是4,64位机器下是8。
我们来通过图来详细了解一下:
struct S1
{
    char c1;
    int i;
    char c2;
};
比如对于上面的结构体S1,

 因为结构体的第⼀个成员对⻬到和结构体变量起始位置偏移量为0的地址处。所以结构体中第一个char类型占内存大小为1,位置从0到1,而对于int来说,它的对齐数为4,位置应该从4到8,对于第二个char类型来说,它的对其数为1,所以可以从位置为8的位置紧接着,位置为8到9。所有的成员类型都花完了,在计算结构体大小时,还要考虑最大对其数,在该结构体中为4,所以结构体的大小为4的整数倍,对与该结构体来说,大小应该为12。

1.2.2 为什么有内存对齐
1. 平台原因 (移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2. 性能原因:
数据结构(尤其是栈)应该尽可能地在⾃然边界上对⻬。原因在于,为了访问未对⻬的内存,处理器需要作两次内存访问;⽽对⻬的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对⻬成8的倍数,那么就可以⽤⼀个内存操作来读或者写值了。否则,我们可能需要执⾏两次内存访问,因为对象可能被分放在两个8字节内存块中。
总体来说:结构体的内存对⻬是拿空间来换取时间的做法。
所以我们在创建结构体时应该 让占⽤空间⼩的成员尽量集中在⼀起,对于上文的结构体S1,可以写成
struct S1
{
    char c1;
     char c2;
    int i;
};

此时结构体的大小为8,节省了空间。

1.2.3 修改对齐数 
#pragma 这个预处理指令,可以改变编译器的默认对⻬数。
# include <stdio.h>
# pragma pack(1) // 设置默认对⻬数为 1
struct S
{
    char c1;
    int i;
    char c2;
};
# pragma pack() // 取消设置的对⻬数,还原为默认
int main ()
{
    printf ( "%d\n" , sizeof ( struct S));
    return 0 ;
}
1.3 结构体实现位段
1.3.1 什么是位段
位段的声明和结构是类似的,有两个不同:
1. 位段的成员必须是 int unsigned int signed int ,在C99中位段成员的类型也可以选择其他类型。
2. 位段的成员名后边有⼀个冒号和⼀个数字。
例如下面是一个位段:
struct A
{
    int _a: 2 ;
    int _b: 5 ;
    int _c: 10 ;
    int _d: 30 ;
};

位段与结构体十分相似,那么位段的大小又是多少呢?下面我们来学习一下位段的内存分配。

1.3.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 ;

在位段声明时冒号后带的数字为该成员的空间大小,而数据是在创建变量后才能赋值。

 

这就是S位段的内存大小,当一个字节内剩余空间大于下一个成员的大小时,下一个成员将会存放到这个字节中。

1.3.3 位段的问题
1. 位段的成员可以是 int unsigned int signed int 或者是 char 等类型
2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的⽅式来开辟的。
3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使⽤位段。
4.  位段的⼏个成员共有同⼀个字节,这样有些成员的起始位置并不是某个字节的起始位置,那么这些位 置处是没有地址的。内存中每个字节分配⼀个地址,⼀个字节内部的bit位是没有地址的。
所以不能对位段的成员使⽤&操作符,这样就不能使⽤scanf直接给位段的成员输⼊值,只能是先输⼊放在⼀个变量中,然后赋值给位段的成员。

2. 联合体

2.1.1 联合体类型的声明
像结构体⼀样,联合体也是由⼀个或者多个成员构成,这些成员可以不同的类型。
但是编译器只为最⼤的成员分配⾜够的内存空间。联合体的特点是所有成员共⽤同⼀块内存空间。所以联合体也叫:共⽤体。
给联合体其中⼀个成员赋值,其他成员的值也跟着变化。
union Un
{
    char c;
    int i;
};
int main ()
{
// 联合变量的定义
    union Un un = { 0 };
// 计算连个变量的⼤⼩
    printf ( "%d\n" , sizeof (un));
    return 0 ;
}

 结算的结果是4,那为什么是这个结果?我们继续往下看。

2.2 联合体的特点
联合的成员是共⽤同⼀块内存空间的,这样⼀个联合变量的⼤⼩,⾄少是最⼤成员的⼤⼩(因为联合⾄少得有能⼒保存最⼤的那个成员)。
对于结构体和联合体:
struct S{
    char c;
int i;
};
 
union A{
char c;
int i;
};
 

在联合体中,成员公用一块空间,所以改变一个成员的值时,其他成员的值也会跟着改变。

2.2.1 联合体大小的计算 

根据上文,我们可以知道:联合体的大小至少是最大成员的大小,而当最⼤成员⼤⼩不是最⼤对⻬数的整数倍的时候,就要对⻬到最⼤对⻬数的整数倍。

2.3 联合体的使用案例

联合体的使用可以节省空间,

⽐如,我们要搞⼀个活动,要上线⼀个礼品兑换单,礼品兑换单中有三种商品:图书、杯⼦、衬衫。每⼀种商品都有:库存量、价格、商品类型和商品类型相关的其他信息。
图书:书名、作者、⻚数
杯⼦:设计
衬衫:设计、可选颜⾊、可选尺⼨
那我们不耐⼼思考,直接写出⼀下结构:

 

struct gift_list      //不使用联合体
{
    //公共属性
    int stock_number;//库存量
    double price; //定价
    int item_type;//商品类型

    //特殊属性
    char title[20];//书名
    char author[20];//作者
    int num_pages;//⻚数

    char design[30];//设计
    int colors;//颜⾊
    int sizes;//尺⼨
};


struct gift_list      //使用联合体
{
    int stock_number;//库存量
    double price; //定价
    int item_type;//商品类型

    union {
        struct
        {
            char title[20];//书名
            char author[20];//作者
            int num_pages;//⻚数
        }book;
        struct
        {
            char design[30];//设计
        }mug;
        struct
        {
            char design[30];//设计
            int colors;//颜⾊
            int sizes;//尺⼨
        }shirt;
    }item;
};

 3. 枚举类型

3.1 枚举类型的声明
枚举顾名思义就是⼀⼀列举。
把可能的取值⼀⼀列举。
⽐如我们现实⽣活中:
⼀周的星期⼀到星期⽇是有限的7天,可以⼀⼀列举
性别有:男、⼥、保密,也可以⼀⼀列举
⽉份有12个⽉,也可以⼀⼀列举
三原⾊,也是可以意义列举
这些数据的表⽰就可以使⽤枚举了。

enum Day//星期
{
    Mon,
    Tues,
    Wed,
    Thur,
    Fri,
    Sat,
    Sun
};

enum Sex//性别
{
    MALE,
    FEMALE,
    SECRET
};
enum Color//颜⾊
{
    RED,
    GREEN,
    BLUE
};

 

以上定义的 enum Day enum Sex enum Color 都是枚举类型。
{ }中的内容是枚举类型的可能取值,也叫 枚举常量 。
这些可能取值都是有值的,默认从0开始,依次递增1,当然在声明枚举类型的时候也可以赋初值。

enum Color//颜⾊
{
    RED = 2,
    GREEN = 4,
    BLUE = 8
}; 

2.2 枚举类型的优点 
我们可以使⽤ #define 定义常量,为什么⾮要使⽤枚举?
枚举的优点:
1. 增加代码的可读性和可维护性
2. 和#define定义的标识符⽐较枚举有类型检查,更加严谨。
3. 便于调试,预处理阶段会删除 #define 定义的符号
4. 使⽤⽅便,⼀次可以定义多个常量
5. 枚举常量是遵循作⽤域规则的,枚举声明在函数内,只能在函数内使⽤

 

那么我们今天的学习就到这里啦,我们下次再见。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/40435.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

钡铼RTU无线S270用于风力发电站机房远程状态监测和故障预警系统集成

在现代风力发电行业中&#xff0c;机房的远程监测和故障预警系统对于保障风力发电机组的稳定运行至关重要。钡铼第4代S270工业级4G远程遥测终端&#xff08;RTU&#xff09;&#xff0c;以其先进的技术和多功能应用&#xff0c;成为风力发电站机房智能化管理的理想选择。 技术…

深入理解计算机系统 CSAPP 8.4.2 fork函数

//fork.c #include <sys/types.h> #include <unistd.h> #include <stdio.h>int main() {pid_t fpid; //fpid表示fork函数返回的值int count 0;fpid fork();if (fpid < 0)printf("error in fork!");else if (fpid 0) {printf("\ni am th…

相机网线RJ45连接器双端带线5米8芯绿色网线注塑成型

相机网线RJ45连接器双端带线5米8芯绿色网线注塑成型&#xff0c;这款网线采用了环保的绿色材质&#xff0c;线长5米&#xff0c;足够满足大多数拍摄场景的需求。更重要的是&#xff0c;它采用了8芯设计&#xff0c;保证了数据传输的稳定性和高速性。在接口方面&#xff0c;它采…

RpcChannel的调用过程

目录 1. RPC调用方&#xff08;caller&#xff09;的调用(消费)过程 2.在caller下创建文件&#xff1a;calluserservice.cc 3.在src的include下创建文件&#xff1a;mprpcchannel.h 4.在src下创建mprpcchannel.cc 1. RPC调用方&#xff08;caller&#xff09;的调用(消费)过…

Android14之RRO资源文件替换策略(二百二十一)

简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏:多媒体系统工程师系列【原创干货持续更新中……】🚀 优质视频课程:AAOS车载系统+AOSP…

代码随想录算法训练营第67天:图论5[1]

代码随想录算法训练营第67天&#xff1a;图论5 ‍ 105.有向图的完全可达性 卡码网题目链接&#xff08;ACM模式&#xff09;(opens new window) 【题目描述】 给定一个有向图&#xff0c;包含 N 个节点&#xff0c;节点编号分别为 1&#xff0c;2&#xff0c;…&#xff0…

【操作与配置】VSCode配置Python及Jupyter

Python环境配置 可以参见&#xff1a;【操作与配置】Python&#xff1a;CondaPycharm_pycharmconda-CSDN博客 官网下载Python&#xff1a;http://www.python.org/download/官网下载Conda&#xff1a;Miniconda — Anaconda documentation VSCode插件安装 插件安装后需重启V…

matrix-breakout-2-morpheus靶场

1 信息收集 1.1 主机发现 arp-scan -l 1.2 端口与服务扫描 发现开放22、80、81端口 2 访问服务 2.1 访问80端口 查看源代码 2.2 访问81端口 3 目录扫描 3.1 dirsearch目录扫描 dirsearch -u 192.168.1.14 发现robots.txt文件和javascript文件 访问文件 http://192.168…

WordPress主题大前端DUX v8.7源码下载

全新&#xff1a;用户注册流程&#xff0c;验证邮箱&#xff0c;设置密码 新增&#xff1a;列表显示小视频和横幅视频 新增&#xff1a;文章内容中的外链全部增加 nofollow 新增&#xff1a;客服功能中的链接添加 nofollow 优化&#xff1a;产品分类的价格显示

如何使用小红书矩阵系统:提升内容管理与发布的指南

小红书作为一个集社区分享与电商功能于一体的平台&#xff0c;吸引了大量的用户和创作者。随着内容创作和账号管理的复杂性增加&#xff0c;小红书矩阵系统成为了一个强大的工具&#xff0c;帮助用户提高效率和扩大影响力。本文将详细介绍如何使用小红书矩阵系统&#xff0c;以…

如何在Python中拷贝类对象到数组

1、问题背景 在Python中&#xff0c;我们经常需要存储多个对象的集合。有时&#xff0c;我们需要拷贝这些对象&#xff0c;以便在不修改原始对象的情况下对它们进行操作。例如&#xff0c;在下述代码中&#xff0c;我们在colors列表中存储了多个Color对象&#xff0c;然后我们创…

Elasticsearch 8.x 存储有无压缩?能压缩到多少?

1、认知前提 Elasticsearch 支持压缩&#xff0c;压缩方式默认为&#xff1a;LZ4 压缩算法。 具体参见&#xff1a; The default value compresses stored data with LZ4 compression, but this can be set to best_compression which uses DEFLATE for a higher compression r…

生态共建 | 华宇TAS应用中间件与新华三服务器完成兼容互认证

近日&#xff0c;华宇TAS应用中间件完成与新华三技术有限公司的R4930系列和R4970 G7服务器的兼容适配&#xff0c;认证测试报告显示&#xff0c;双方产品兼容性良好&#xff0c;运行稳定、安全&#xff0c;可以满足用户对双方功能的要求。 新华三技术有限公司 新华三技术有限公…

行业洞察 | 2024应用程序安全领域现状报告

在信息爆炸的时代&#xff0c;我们每天都在使用各种应用&#xff0c;从社交娱乐到工作学习&#xff0c;应用已经成为我们生活中不可或缺的一部分。然而&#xff0c;你是否知道&#xff0c;在这些便捷的背后&#xff0c;隐藏着巨大的安全风险&#xff1f; 近年来&#xff0c;应用…

2024年前端面试题及答案

7、 nginx代理跨域 8、 nodejs中间件代理跨域 9、 WebSocket协议跨域 前端数据加密问题 1 一般如何处理用户敏感信息&#xff1f; 前端一般使用md5、base64加密、sha1加密&#xff0c;想要了解详情请自行百度。 前端http相关问题 1 HTTP常用状态码及其含义&#xff1f; …

css---before和after伪元素

1.什么是伪元素 伪元素不是真正的页面元素&#xff0c;html没有对应的元素&#xff0c;但是其所有用法和表现行为与真正的页面元素一样&#xff0c;可以对其使用如页面元素一样的CSS样式&#xff0c;表面上看上去貌似是页面的某些元素来展现&#xff0c;实际上CSS样式展现的行…

Python容器 之 字典--字典的遍历

字典存在 键(key), 值(value) , 遍历分为三种情况 1.遍历字典的键 循环拿到字典中的每个键名 # 方式一 for 变量 in 字典: print(变量) # 方式二 for 变量 in 字典.keys(): # 字典.keys() 可以获取字典所有的键 print(变量) my_dict {name: 小明, age: 18, sex: 男}…

Kamailio-Web管理页面Siremis的安装与部署

siremis 是针对于 Kamailio 的web管理接口&#xff0c;使用PHP书写&#xff0c;更新至2020年&#xff0c;相对不是太新但是是官方友链的 以下就采用 Ubuntu 22.04Siremis 5.8.0apache http server 2.4php7.0 如有疑问请参看官方指南 以下开始介绍操作步骤 安装apache2.4 we…

14-5 小语言模型SLM 百科全书

想象一下这样一个世界&#xff1a;智能助手不再驻留在云端&#xff0c;而是驻留在你的手机上&#xff0c;无缝理解你的需求并以闪电般的速度做出响应。这不是科幻小说&#xff1b;这是小型语言模型 (SLM) 的前景&#xff0c;这是一个快速发展的领域&#xff0c;有可能改变我们与…

MySQL数据库数据迁徙:从本地到Linux服务器

"男人的浪漫&#xff0c;绝对是拥有一台属于自己的服务器" MySQL数据库数据迁徙就两步&#xff1a;本地导出和服务器导入。 本地导出 本地导出的时候&#xff0c;需要注意你的CMD命令行必须是以管理员身份运行。如果你的计算机找不到mysqldump这个命令&#xff0c;…