C语言中的自定义类型详解(结构体 + 枚举 + 联合(共用体))

文章目录

  • 1. 结构体
    • 1.1 结构体的声明
    • 1.2 结构体成员的访问
    • 1.3 匿名结构体
    • 1.4 结构体的自引用
    • 1.5 结构体内存对齐(计算结构体的大小)
    • 1.6 结构体传参
      • 1.6.1 传值传递
      • 1.6.2 传址传递(使用指针)
  • 2. 位段
    • 2.1 什么是位段?
    • 2.2 位段的内存分配
    • 2.3 位段的跨平台问题
  • 3. 枚举
    • 3.1 枚举的定义
    • 3.2 枚举的好处
  • 4. 联合(共用体)
    • 4.1 联合类型的定义
    • 4.2 联合大小的计算

1. 结构体

结构体是C语言中用于表示一组相关数据的自定义数据类型。结构体允许将多个不同类型的数据组组合在一起,以便更方便地管理和操作这些数据。

1.1 结构体的声明

struct tag
{member-list;
}variable-list;
  • 使用 struct 关键字声明结构体的名称。
  • 在大括号 {} 内部定义结构体的成员变量,成员变量的类型可以是标量、数组、指针,甚至是其他结构体,每个成员变量都有一个类型和一个名称,成员之间用分号 ; 分隔。
  • 最后以分号 ; 结束结构体的定义。

以下代码,展示了如何定义一个简单的结构体以用来表示学生信息:

#include <stdio.h>// 定义一个结构体 Student
struct Student 
{char name[20];//名字int age;//年龄double score;//成绩
};int main() 
{// 声明一个结构体变量并初始化struct Student s1 = { "zzb", 20, 98};// 访问结构体成员并打印信息printf("学生姓名:%s\n", s1.name);printf("学生年龄:%d\n", s1.age);printf("学生成绩:%.2lf\n", s1.score);return 0;
}

1.2 结构体成员的访问

在C语言中,结构体变量访问其成员一共有如下两种方式:

  1. 使用点运算符(.):点运算符用于访问结构体变量的成员。其语法格式如下:

结构体变量.成员名

例如,上面代码中有一个名为 s1 的结构体变量,并且结构体中有一个成员叫做 name,我们就可以通过以下方式访问该成员:

s1.name = "zzb";
  1. 使用箭头运算符(->):箭头运算符用于访问结构体指针所指向的结构体的成员变量。其语法格式如下:

结构体指针->成员名

例如,如果有一个名为 ps1 的指向结构体的指针,并且结构体中有一个成员叫做 age,我们就可以通过以下方式访问该成员:

ps1->age = 20;

1.3 匿名结构体

在C语言中,我们可以创建匿名结构体,这是一种没有名字的结构体,结构体在声明的时候省略掉了结构体标签。该结构体通常用于简单的数据封装或临时数据组织,而不需要定义一个有具体名字的结构体类型。
其定义方式如下:

  • 匿名结构体在声明和定义的同时创建,不需要使用结构体名称。可以直接在需要的地方定义结构体,并在大括号 {} 内部定义结构体的成员。
struct {// 成员定义
} 结构体变量名;
  • 注意:
    匿名结构体无法在不同的地方重复使用,因为没有结构体名称来引用它。如果需要在多个地方使用相同的结构体结构,建议定义具名的结构体类型。

1.4 结构体的自引用

结构体的自引用是指结构体中包含自身类型的成员。这种自引用的结构体通常用于创建复杂的数据结构,如树、链表和图等。在C语言中,要实现结构体的自引用,通常需要使用指针来解决
以下代码,展示了如何创建一个简单的自引用结构体来表示链表节点:

#include <stdio.h>// 定义一个链表节点的结构体
struct ListNode 
{int data;             // 节点数据struct ListNode* next; // 指向下一个节点的指针
};int main() 
{// 创建链表节点struct ListNode node1, node2, node3;node1.data = 1;node2.data = 2;node3.data = 3;// 连接节点,形成链表node1.next = &node2;node2.next = &node3;node3.next = NULL; // 链表结束// 遍历链表并打印节点数据struct ListNode *cur = &node1;while (cur != NULL) {printf("%d ", cur->data);cur = cur->next;}return 0;
}

1.5 结构体内存对齐(计算结构体的大小)

在C语言中,结构体内存对齐是编译器为了提高内存访问效率而采取的一种优化策略。关于结构体内存对齐的详细介绍,这里可以参考之前写的文章:如何计算一个结构体的大小?(C语言)
这里就不再赘述了。

1.6 结构体传参

在C语言中,我们可以将结构体作为参数传递给函数。结构体传参的方式有两种:传值传递和传址传递(使用指针)。

1.6.1 传值传递

在传值传递时,函数形参是实参的一份临时拷贝,这也就意味着在函数内部操作的是结构体的一份拷贝,不会影响实参结构体。这种方式适用于结构体较小且复制开销较小的情况。这是因为函数传参的时候,参数是需要压栈的。如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降
以下代码,展示了结构体传值传递的方式

#include <stdio.h>// 定义一个结构体
struct Point
{int x;int y;
};// 以传值传递的方式
void modify_point(struct Point p) 
{p.x = 10;p.y = 20;
}int main() 
{struct Point p = {5, 5};modify_point(p);printf("x: %d, y: %d\n", p.x, p.y); // 输出仍为原始值return 0;
}

1.6.2 传址传递(使用指针)

在传址传递时,函数接收的是结构体的指针,可以通过指针修改实参结构体的内容。这种方式适用于当结构体较大且复制开销较大的情况。

#include <stdio.h>// 定义一个结构体
struct Point 
{int x;int y;
};// 以传址传递的方式
void modify_point(struct Point *p) 
{p->x = 10;p->y = 20;
}int main() 
{struct Point p = {5, 5};modify_point(&p);printf("x: %d, y: %d\n", p.x, p.y); // 输出已修改后的值return 0;
}

2. 位段

2.1 什么是位段?

位段是一种C语言的结构体成员,它可以在结构体定义时,指定某个成员变量所占用的二进制位数,这是因为有些数据在存储时并不需要占用一个完整的字节,而是需要占用一个或几个二进制位即可
位段的主要目的是节省内存
其声明语法如下:

struct 
{type member_name : width;
};
  • type 表示要使用的整数类型(通常是 int、unsigned int 、char(整型家族)等),在C99之后也可以是其他类型。
  • member_name 是位段的名称。
  • width 是该位段占用的比特位数。

以下是一个位段的示例:

#include <stdio.h>// 定义一个包含位段的结构体
struct A
{int _a:2;//2个比特位int _b:5;//5个比特位int _c:6;//10个比特位int _d:8;//30个比特位
};
int main() 
{struct A a;// 设置位段的值a._a = 1;a._b = 0;a._c = 5;a._d = 8;printf("_a: %d\n", a._a);printf("_b: %d\n", a._b);printf("_c: %d\n", a._c);printf("_d: %d\n", a._d);return 0;
}

2.2 位段的内存分配

位段会按照其在结构体中的声明顺序依次分配内存

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

我们通过下面的例子,探究以下位段在vs2013这个平台如何开辟空间的?

#include <stdio.h>struct S
{char a:3;char b:4;char c:5;char d:4;
};int main()
{struct S s = {0};s.a = 10;s.b = 12;s.c = 3;s.d = 4;
}

这里先假设在一个字节内部先使用低比特位且当一个字节剩余的bit位不足以存下下一个变量时,舍弃剩余的bit位,为下一个变量重新申请一个字节。
在这里插入图片描述
我们打开vs,进行调试,看下内存里面是如何存储的。
在这里插入图片描述
在vs下,具体的内存布局和我们假设的一致。

2.3 位段的跨平台问题

  • int 位段被当成有符号数还是无符号数是不确定的。
  • 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机
    器会出问题。
  • 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
  • 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是
    舍弃剩余的位还是利用,这是不确定的。
    在这里插入图片描述
    因此,跟结构相比,位段可以达到同样的效果,虽然可以很好的节省空间,但是有跨平台的问题存在。

3. 枚举

枚举是C语言中的一种自定义数据类型,用于定义一组命名的整数常量,这些常量通常用于表示一组相关的离散值。

3.1 枚举的定义

其基本语法如下:

enum enum_name 
{a,b,c,// ...
};
  • num_name 是枚举类型的名称。
  • a, b, c等是枚举常量,它们是整数值,通常从0开始自动递增。当然在定义的时候也可以赋初值。

以下代码展示了枚举的使用:

#include <stdio.h>// 定义一个枚举类型
enum Color 
{RED,    // 0GREEN,  // 1BLUE    // 2
};int main() {// 声明一个枚举类型的变量enum Color col;// 给枚举变量赋值col = RED;// 使用枚举常量if (col == RED) {printf("The color is red.\n");} else if (col == GREEN) {printf("The color is green.\n");} else if (col == BLUE) {printf("The color is blue.\n");}return 0;
}

3.2 枚举的好处

枚举常量在整个程序中具有全局作用域,可以用作整数值的符号名称,以提高代码的可读性。

  • 增加代码的可读性和可维护性
  • 和#define定义的标识符比较枚举有类型检查,更加严谨。
  • 防止了命名污染(封装)
  • 便于调试
  • 使用方便,一次可以定义多个常量

4. 联合(共用体)

联合也是C语言中的一种自定义的数据类型,与结构体类似,但与结构体不同的是:

  • 联合的成员共用同一块内存空间,只能同时存储其中一个成员的值。这也说明联合的大小虽少是其最大成员的大小。
  • 由于联合的成员共用同一块内存空间,因此在给一个成员赋值后,其他成员的值可能会受到影响。

下面代码的运行结果说明了其特性:

#include <stdio.h>//联合类型的声明
union Un
{char c;int i;
};int main()
{//联合变量的定义union Un un;// 下面输出的结果是一样的吗?printf("&(un.i) = %d\n", &(un.i));printf("&(un.c) = %d\n", &(un.c));//下面输出的结果是什么?un.i = 0x11223344;un.c = 0x55;printf("%x\n", un.i);return 0;
}

分析如下:
在这里插入图片描述

4.1 联合类型的定义

联合的基本语法如下:

union union_name 
{// 成员定义
};
  • union_name 是联合类型的名称。

4.2 联合大小的计算

其大小的计算规则遵循以下两个规则:

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

思考一下,下面代码的运行结果是什么?

#include <stdio.h>
union Un1
{char c[5];int i;
};union Un2
{short c[7];int i;
};int main()
{//下面输出的结果是什么?printf("%d\n", sizeof(union Un1));printf("%d\n", sizeof(union Un2));
]

分析如下:
在这里插入图片描述
Un2的分析和上述分析一致,Un2的大为16。
运行结果如下:
在这里插入图片描述
这里需要注意的是,不同编译器和平台对于联合的对齐方式可能会有所不同,这可能会影响联合的总大小。但无论如何,联合的大小都不会小于最大成员的大小。

至此,本片文章就结束了,若本篇内容对您有所帮助,请三连点赞,关注,收藏支持下。
创作不易,白嫖不好,各位的支持和认可,就是我创作的最大动力,我们下篇文章见!
如果本篇博客有任何错误,请批评指教,不胜感激 !
在这里插入图片描述

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

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

相关文章

铅华洗尽,粉黛不施,人工智能AI基于ProPainter技术去除图片以及视频水印(Python3.10)

视频以及图片修复技术是一项具有挑战性的AI视觉任务&#xff0c;它涉及在视频或者图片序列中填补缺失或损坏的区域&#xff0c;同时保持空间和时间的连贯性。该技术在视频补全、对象移除、视频恢复等领域有广泛应用。近年来&#xff0c;两种突出的方案在视频修复中崭露头角&…

miRNA测序数据生信分析——第四讲,未知物种的生信分析实例

miRNA测序数据生信分析——第四讲&#xff0c;未知物种的生信分析实例 miRNA测序数据生信分析——第四讲&#xff0c;未知物种的生信分析实例1. 下载测序数据2. 原始数据质控——软件fastqc3. 注释tRNA和rRNA&#xff0c;使用Rfam数据库——软件blast&#xff0c;Rfam_statisti…

监控系列(六)prometheus监控DMHS操作步骤

一、监控的操作逻辑 给操作系统安装expect命令expect脚本执行dmhs_console脚本执行 cpt / exec 命令用脚本进行过滤字符串过滤dm_export读取脚本与当前日期作比较&#xff0c;然后返回差值 二、安装步骤 1. linux中Expect工具的安装及使用方法 https://blog.csdn.net/wangta…

mybatis拦截器源码分析

mybatis拦截器源码分析 拦截器简介 mybatis Plugins 拦截器由于Mybatis对数据库访问与操作进行了深度的封装,让我们应用开发效率大大提高,但是灵活度很差拦截器的作用:深度定制Mybatis的开发抛出一个需求 :获取Mybatis在开发过程中执行的SQL语句(执行什么操作获取那条SQL语句…

ChatGPT,AIGC 制作按年份选择的动态条形图

在数据分析与可视化中,条形图是用来进行对比分析,在正负条形图中都有哪些好处与优点呢? 正负条形图是一种常用的数据可视化方式,它的优点和好处包括: 1.明确展示:正负条形图可以清晰地显示出数据中的正负差异,使读者能直观地看出数据的变化和趋势。 2.直观比较:正负条…

苹果电脑其他内存怎么清理?

苹果电脑中的应用程序大部分是可以通过将其拖拽至废纸篓并倾倒来卸载的。但是部分程序在卸载后仍有残留文件&#xff0c;比如support文件和pref设置等文件的。小编今天介绍下苹果电脑清理内存怎么清理卸载残留以及好用的清理技巧分享。 一、苹果电脑清理内存怎么清理 苹果电脑…

简单实现一个todoList(上移、下移、置顶、置底)

演示 html部分 <!DOCTYPE html> <html> <head><title>表格示例</title> </head> <body><table border"1"><thead><tr><th>更新时间</th><th>操作</th></tr></thead…

thinkphp5.1 获取缓存cache(‘cache_name‘)特别慢,php 7.0 unserialize 特别慢

thinkphp5.1 获取缓存cache(‘cache_name’)特别慢&#xff0c;php 7.0 unserialize 特别慢 场景&#xff1a; 项目中大量使用了缓存&#xff0c;本地运行非常快&#xff0c;二三百毫秒&#xff0c;部署到服务器后 一个表格请求就七八秒&#xff0c;最初猜想是数据库查询慢&am…

KY258 日期累加

KY258 日期累加 int main() {int n 0; //样例个数cin >> n;//for循环处理n个样例for (int i 0; i < n; i){int y, m, d, num;int days[12] { 31,28,31,30,31,30,31,31,30,31,30,31 };//输入年月日 要加的天数cin >> y >> m >> d >>…

完整教程:Java+Vue+Websocket实现OSS文件上传进度条功能

引言 文件上传是Web应用开发中常见的需求之一&#xff0c;而实时显示文件上传的进度条可以提升用户体验。本教程将介绍如何使用Java后端和Vue前端实现文件上传进度条功能&#xff0c;借助阿里云的OSS服务进行文件上传。 技术栈 后端&#xff1a;Java、Spring Boot 、WebSock…

【Java学习之道】GUI开发的基本概念

引言 在这一章&#xff0c;我们将一起走进Java的图形用户界面&#xff08;GUI&#xff09;开发的世界。在你阅读完这篇文章后&#xff0c;你将能够了解什么是GUI&#xff0c;以及如何使用Java进行GUI的开发。 一、什么是GUI 首先&#xff0c;让我们来解答一个许多初学者都会…

AN基础工具——变形工具

【AN基础工具——变形工具】 基本使用方法&#xff1a;任意变形工具基础动画 本篇内容&#xff1a;学会使用变形工具 重点内容&#xff1a;变形工具 工 具&#xff1a;Adobe Animate 2022 基本使用方法&#xff1a; 任意变形工具 《任意变形工具&#xff08;快捷键Q&#xff0…

深度学习系列51:hugging face加速库optimum

1. 普通模型 Optimum是huggingface transformers库的一个扩展包&#xff0c;用来提升模型在指定硬件上的训练和推理性能。Optimum支持多种硬件&#xff0c;不同硬件下的安卓方式如下&#xff1a; 如果是国内安装的话&#xff0c;记得加上-i https://pypi.tuna.tsinghua.edu.c…

八大排序算法(含时间复杂度、空间复杂度、算法稳定性)

文章目录 八大排序算法(含时间复杂度、空间复杂度、算法稳定性)1、&#xff08;直接&#xff09;插入排序1.1、算法思想1.2、排序过程图解1.3、排序代码 2、希尔排序3、冒泡排序3.1、算法思想3.2、排序过程图解3.3、排序代码 4、&#xff08;简单&#xff09;选择排序4.1、算法…

Springcloud笔记(2)-Eureka服务注册中心

Eureka服务注册 Eureka作为一个微服务的治理中心&#xff0c;它是一个服务应用&#xff0c;可以接收其他服务的注册&#xff0c;也可以发现和治理服务实例。 服务治理中心是微服务&#xff08;分布式&#xff09;架构中最基础和最核心的功能组件&#xff0c;它主要对各个服务…

kafka生产者发送消息报错 Bootstrap broker localhost:9092 (id: -1 rack: null) disconnected

报这个错误是因为kafka里的配置要修改下 在config目录下 server.properties配置文件 这下发送消息就不会一直等待&#xff0c;就可以发送成功了

CTFHub SSRF 题目

文章目录 CTFHub SSRF 通关教程1. 内网访问&#xff0c;伪协议利用1.1 内网访问1.2 伪协议读取文件1.3 端口扫描 2. POST请求&#xff0c;上传文件&#xff0c;FastCGI协议&#xff0c;Redis协议2.1 POST请求2.2 上传文件2.3 FastCGI协议2.4 Redis协议 3. Bypass系列3.1 URL By…

Macos数字音乐库:Elsten Software Bliss for Mac

Elsten Software Bliss for Mac是一款优秀的音乐管理软件&#xff0c;它可以帮助用户自动化整理和标记数字音乐库&#xff0c;同时可以自动识别音乐信息并添加标签和元数据。 此外&#xff0c;Bliss还可以修复音乐库中的问题&#xff0c;例如重复的音乐文件和缺失的专辑封面等…

Apache Dubbo 首个 Node.js 3.0-alpha 版本正式发布

作者&#xff1a;蔡建怿 关于Apache Dubbo3 Apache Dubbo 是一款易用、高性能的 WEB 和 RPC 框架&#xff0c;同时为构建企业级微服务提供服务发现、流量治理、可观测、认证鉴权等能力、工具与最佳实践。经过近几年发展&#xff0c;Dubbo3 已在阿里巴巴集团各条业务线实现全面…

关于如何进行ChatGPT模型微调的新手指南

微调是指在预训练的模型基础上&#xff0c;通过进一步的训练来调整模型以适应特定任务或领域。预训练的模型在大规模的文本数据上进行了广泛的学习&#xff0c;从中获得了一定的知识和语言理解能力。然而&#xff0c;由于预训练并不针对具体任务&#xff0c;因此需要微调来使模…