【c语言】轻松拿捏自定义类型

🌟🌟作者主页:ephemerals__

🌟🌟所属专栏:C语言

目录

前言

一、结构体

1.结构体类型的定义和使用

1.1 结构体类型声明

1.2 结构体变量的创建和初始化

1.3 结构体变量成员的访问

1.4 结构体的特殊声明(匿名结构体类型)

1.5 结构体的自引用

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

3.结构体传参

二、联合体

1.联合体类型的声明

2.联合体的特点

3.联合体大小的计算

4.联合体的使用

三、枚举类型

1.枚举类型的声明方法

2.枚举类型的优点

总结


前言

        在c语言当中,除了内置的数据类型之外,还有自定义类型,它能够让我们更加方便、灵活地实现各种功能。今天我们主要来学一学三种自定义类型:结构体、联合体和枚举类型。

一、结构体

1.结构体类型的定义和使用

1.1 结构体类型声明

结构体可以含有多个结构成员,成员的类型可以不同。它的声明方式是:

struct xxxx

{

        type1 x;

        type2 y;

        ......

};

这里的struct是结构体的关键字,xxxx是结构体标签,x和y是结构成员变量。例如,我们想要用结构体来描述一个学生的信息:

struct student
{char name[20];//姓名char id[20];//学号int age;//年龄char sex[5];//性别
};

1.2 结构体变量的创建和初始化

        在声明了结构体之后,我们就可以尝试创建一个结构体变量并对其初始化。例如:
 

#include <stdio.h>struct student
{char name[20];char id[20];int age;char sex[5];
};int main()
{struct student s1 = { "zhangsan","000001",18,"男" };struct student s2 = { .age = 15,.name = "wangwu",.sex = "女",.id = "000002" };return 0;
}

这里需要注意:struct student是一个整体,表示的是该结构体的类型名;s1,s2是变量名

结构体的初始化与数组类似,都是使用大括号,中间用逗号隔开。初始化的内容要按照顺序,如果不按照顺序来初始化,则在成员变量名前加一个点,再采用赋值的方法初始化

结构体变量创建也可以在声明大括号之后:

struct student
{char name[20];char id[20];int age;char sex[5];
}s1;

要注意:这里的s1是全局变量

1.3 结构体变量成员的访问

        接下来,我们在之前代码的基础上,打印学生的信息。

#include <stdio.h>struct student
{char name[20];char id[20];int age;char sex[5];
};int main()
{struct student s1 = { "zhangsan","000001",18,"男" };printf("%s\n", s1.name);//结构成员的访问printf("%s\n", s1.id);printf("%d\n", s1.age);printf("%s\n", s1.sex);return 0;
}

运行结果:

可以看到,结构成员的值被一一打印出来。这里使用了“ . ”操作符来访问结构体成员变量。如果是结构体指针类型,在访问成员变量时,则要解引用之后再使用“ . ”操作符或者使用“->”操作符

1.4 结构体的特殊声明(匿名结构体类型)

        在声明结构体的时候,可以不完全声明。例如:

struct
{int a;char c;
}x;

这样的结构体声明省略了结构体标签,并且一般会同时创建一个结构体变量,否则就无法使用。注意:匿名结构体类型只能使用一次,无法在主函数中创建该结构体的新变量

1.5 结构体的自引用

        首先看一个结构体的声明:

struct stu
{int a;struct stu b;
};

上述代码是否正确?

        我们可以看到,这个结构体的成员变量中,有一个变量的类型是结构体本身。这就导致这个结构体是无限嵌套的,它所占的内存大小不可知。所以这种写法是错误的。但是,我们可以使这个结构体成员变量为一个结构体本身的指针类型:

struct stu
{int a;struct stu* p;
};

由于这是一个指针类型,它的大小是确定的(4/8字节),所以这种写法是正确的。我们将这种结构体声明称为结构体的自引用

        结构体的自引用常常用于一些数据结构的定义。

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

        我们首先看一段代码:

#include <stdio.h>struct stu
{char c;int a;
};int main()
{printf("%zd\n", sizeof(struct stu));return 0;
}

这段代码用于计算这个结构体的内存大小。按理来说,char占一个字节,int占四个字节,这个结构体总共应该占5个字节。那么真实结果是这样吗?我们看看运行结果:

为什么是8个字节而不是5个呢?这就需要我们学习一个概念:结构体内存对齐

首先介绍一下结构体内存对齐的规则

1.结构体的第一个成员对齐到和结构体的起始地址的偏移量为0的地址处,也就是说第一个成员的偏移量记为0。

2.其他的成员要对齐到该成员的对齐数整数倍的地址处。

对齐数:编译器默认对齐数与该成员内存大小的较小值;在VS环境中,默认对齐数是8;linux系统中,没有默认对齐数,对齐数就是该成员内存大小)

3.结构体的总大小为结构成员中最大的对齐数的整数倍

4.嵌套结构体的情况:则内层的结构体要对齐到自己成员中最大对齐数的整数倍处;结构体的总大小为结构成员中最大对齐数的整数倍(结构成员包含内层结构体的成员)。

根据以上规则,我们来计算一下刚才结构体的内存大小:

我们可以看到,内存对齐还造成了三个字节空间的浪费。那为什么会有内存对齐呢?

        原因如下: 

1.平台原因(移植原因):不是所有硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取到某些特定类型的数据,否则会抛出硬件异常。

2.性能原因:为了访问未对齐的内存,处理器需要做两次内存访问;而对齐的内存只需要做一次内存访问。假设一个处理器总是从内存中取八个字节,则地址必须是八的倍数。如果我们能够保证将所有double类型的数据地址都对齐成八的倍数,那么就可以节省大量的内存访问时间。说白了,结构体内存对齐就是以空间换时间的做法。

所以,当我们想要在满足时间需求的情况下,尽量节省空间,我们可以在结构体声明时,将内存小的结构成员聚集在一起。例如:

#include <stdio.h>struct s1
{char a;int b;char c;
};struct s2
{char a;char c;int b;
};int main()
{printf("s1的内存大小是%zd\n", sizeof(struct s1));printf("s2的内存大小是%zd\n", sizeof(struct s2));return 0;
}

运行结果:

当然,如果你想要使结构体所占的内存达到最小,也可以通过修改默认对齐数的方式实现。修改方法是:

#pragma pack(n)   n是你想要的默认对齐数值。

我们试着使用一下它:

#include <stdio.h>#pragma pack(1)//调整默认对齐数为1struct s
{char c;int a;
};#pragma pack()//还原默认对齐数int main()
{printf("%zd\n", sizeof(struct s));
}

运行结果:

3.结构体传参

        当我们写的程序需要对结构体进行操作的时候,常常会定义函数,然后将结构体作为参数。举一个例子:

#include <stdio.h>struct s
{int arr[1000];int m;
};void fun1(struct s s1)
{printf("%d\n", s1.arr[3]);
}void fun2(struct s* s1)
{printf("%d\n", s1->arr[3]);
}int main()
{struct s s1 = { {0},1 };fun1(s1);fun2(&s1);return 0;
}

fun1和fun2哪个更好呢?

实际上,fun2更好。原因如下:

1.函数形参是实参的一份临时拷贝,在函数中修改结构体的内容,主函数中的结构体内容不会改变。

2.如果结构体内存较大,函数就要开辟一块和结构体同样大小的内存空间,占用内存较大。而传递结构体指针时,函数只开辟了4/8个字节的内存空间。

二、联合体

        在学习了结构体之后,我们来了解一下联合体。

1.联合体类型的声明

        和结构体一样,联合体也含有多个成员,成员的类型可以不同。它的声明方法和结构体类似:

union xxxx

{

        type1 x;

        type2 y;

        ......

};

只不过它的关键字是union,结构体是struct。

2.联合体的特点

        联合体有如下特点:

1.联合体所有成员共用同一块内存空间,所以联合体也叫做共用体

2.给其中一个成员变量赋值,其他成员变量的值也跟着变化。

我们来看一段代码:

#include <stdio.h>union un
{int a;char c;
};int main()
{union un x = { 0 };printf("%p\n", &(x.a));printf("%p\n", &(x.c));printf("%p\n", &x);return 0;
}

运行结果:

可以看到,三者的地址相同,说明两个成员变量确实用的是同一块内存空间。接着我们尝试修改一下成员变量的值:

#include <stdio.h>union un
{int a;char c;
};int main()
{union un x = { 0 };x.a = 0x11223344;x.c = 0x55;printf("%x\n", x.a);return 0;
}

可以看到,当修改成员c的时候,成员a的第一个字节内容也被修改了。根据它,我们就可以画图表示一下联合体的内存占用情况:

3.联合体大小的计算

        由于联合体的成员变量是共用同一块内存空间的,所以它的内存大小计算并没有结构体那般复杂:

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

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

4.联合体的使用

        联合体可以用于判断当前机器的大小端。这里举个例子:

#include <stdio.h>union un 
{int a;char c;
};int main()
{union un x = { 0 };x.a = 1;printf("%d", x.c);return 0;
}

运行结果:

这里我们将整形成员a赋值为1。如果此时字符型的c值为1,则说明整形的最低位的值放在了最低地址上,就是小端;若是0则为大端。

三、枚举类型

        所谓枚举,就是一一列举的意思,对于某个事件,将可能的取值一一列举出来,就变成了枚举类型。比如:一个星期有七天,分别是周一、周二...可以一一列举出来。再比如:一年有十二个月,可以一一列举出来。

1.枚举类型的声明方法

        拿一周七天来举例,它的声明方法如下:

enum week//枚举类型
{Mon,Tue,Wed,Thor,Fri,Sat,Sun
};

这里要注意:这些成员就像宏常量一样,都是有值的,如果不特定赋值,则第一个成员的值为0,之后的成员依次+1递增。

2.枚举类型的优点

        既然枚举值就像宏常量一样,那么为什么还要使用枚举呢?

1.它增加了代码的可读性和可维护性

2.与宏常量相比,枚举类型有类型检查,更加安全。

3.由于调试时#define定义的符号会被替换,而枚举不会,就便于调试。

4.使用方便,一次可以定义多个相关的变量

5.枚举类型是有作用域的,而宏常量没有,可以再某个函数体内单独使用

总结

        今天咱们学习了三种自定义类型:结构体、联合体和枚举,以及它们的定义方式、特点和使用。之后博主会和大家分享动态内存管理的相关知识,如果你觉得博主讲的还不错,就请留下一个小小的赞在走哦,感谢大家的支持❤❤❤

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

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

相关文章

三万字带你一遍跑通uer

三万字带你一遍跑通uer 参考文档 今天给大家介绍个非常强大的项目uer&#xff0c;集成了许多可以做自然语言的东西&#xff0c;效果的话也非常好&#xff0c;很适合企业级的应用&#xff01; 1. 先将项目uer从github拉取下来&#xff08;zip或git都ok&#xff09; 2. 用pycha…

HTTP代理服务器:深度解析与应用

“随着互联网的飞速发展&#xff0c;HTTP代理服务器在网络通信中扮演着越来越重要的角色。它们作为客户端和服务器之间的中介&#xff0c;不仅优化了网络性能&#xff0c;还提供了强大的安全性和隐私保护功能。” 一、HTTP代理服务器的概念与作用 HTTP代理服务器是一种能够接…

价值499的从Emlog主题模板PandaPRO移植到wordpress的主题

Panda PRO 主题&#xff0c;一款精致wordpress博客主题&#xff0c;令人惊叹的昼夜双版设计&#xff0c;精心打磨的一处处细节&#xff0c;一切从心出发&#xff0c;从零开始&#xff0c;只为让您的站点拥有速度与优雅兼具的极致体验。 从Emlog主题模板PandaPRO移植到wordpres…

兴业小课堂|什么是法拍房助拍机构?如何挑选靠谱的助拍机构?

随着法拍房市场的不断发展和扩大 使法拍房数量的增加 其交易的复杂性和专业性需求也日益凸显 这促使了专门机构的出现来满足市场需求 法拍房助拍机构存在的原因主要有以下几点&#xff1a; 1.专业知识和经验&#xff1a; 法拍房的交易流程相对复杂&#xff0c;涉及到法律法…

【全网最全ABC三题完整版】2024年APMCM第十四届亚太地区大学生数学建模竞赛(中文赛项)完整思路解析+代码+论文

我是Tina表姐&#xff0c;毕业于中国人民大学&#xff0c;对数学建模的热爱让我在这一领域深耕多年。我的建模思路已经帮助了百余位学习者和参赛者在数学建模的道路上取得了显著的进步和成就。现在&#xff0c;我将这份宝贵的经验和知识凝练成一份全面的解题思路与代码论文集合…

第六节:如何解决@ComponentScan只能扫描当前包及子包(自学Spring boot 3.x的第一天)

大家好&#xff0c;我是网创有方&#xff0c;继上节咱们使用了Component和ComponentScan的方法实现了获取IOC容器中的Bean&#xff0c;但是存在一个问题&#xff0c;就是必须把AppConfig和要扫描的bean类放在同一个目录下&#xff0c;这样就导致了AppConfig类和bean类在同一个目…

6.8应用进程跨网络通信

《计算机网络》第7版&#xff0c;谢希仁 理解socket通信

成都仅需浏览器即可快速查看的数据采集监控平台!

LP-SCADA数据采集监控平台无需额外客户端&#xff0c;只需要一个标准的Web浏览器&#xff0c;用户可以迅速访问系统并开始使用&#xff0c;同时支持跨平台访问。一个用户可监控多个过程&#xff0c;多个用户可以监控同一过程&#xff0c;真正实现了数据的开放性及过程信号的透明…

CVPR2024自动驾驶轨迹预测方向的论文整理

2024年自动驾驶轨迹预测方向的论文汇总 1、Producing and Leveraging Online Map Uncertainty in Trajectory Prediction 论文地址&#xff1a;https://arxiv.org/pdf/2403.16439 提出针对在线地图不确定性带给轨迹预测的影响对应的解决方案。 在轨迹预测中&#xff0c;利用在…

【产品与技术双视角】初创团队利用小程序云基础设施“低成本试错”

文章目录 前言一、产品视角之三大困难二、技术视角之难以抉择三、利用小程序云基础设施“低成本试错” 前言 学生团队和初创团队在没有得到风投之前&#xff0c;想要做出一款产品太难了&#xff0c;难在哪呢&#xff1f;难在没有资源。用最狭隘的视角看这个资源&#xff1a;人…

SSM中小学生信息管理系统-计算机毕业设计源码02677

摘要 随着社会的发展和教育的进步&#xff0c;中小学生信息管理系统成为学校管理的重要工具。本论文旨在基于SSM框架&#xff0c;采用Java编程语言和MySQL数据库&#xff0c;设计和开发一套高效、可靠的中小学生信息管理系统。中小学生信息管理系统以学生为中心&#xff0c;通过…

hitcontraining_uaf

BUUCTF[PWN][堆] 题目&#xff1a;BUUCTF在线评测 (buuoj.cn) 程序del是没有将申请的指针清零&#xff0c;导致可以再次调用输出print。 查看add_note函数&#xff1a;根据当前 notelist 是否为空&#xff0c;来申请了一个8字节的空间将地址(指针)放在notelist[i]中&#xff…

野指针的概念 如果规避野指针

目录 野指针的概念 有关野指针的代码 如何规避野指针 野指针的概念 野指针就是指针指向的位置是不可知的&#xff08;随机的&#xff0c;不正确的&#xff0c;没有明确限制的&#xff09; 有关野指针的代码 指针未初始化&#xff1a; #include<stdio.h> int main…

使用 mongo2neo4j 和 SemSpect 通过各种方式进行图探索

用于可视化和探索每个 MEAN 堆栈背后的数据图的 ETL 您是否正在努力回答有关 MEANS Web 服务数据的紧急问题&#xff1f;哪里有 BI 可以快速回答“上个季度哪些亚洲的artisan.plus 用户触发了订单&#xff1f;”这个问题&#xff0c;而无需编写查询&#xff1f;使用 mongo2neo4…

深度学习每周学习总结N3(文本分类实战:基本分类(熟悉流程)、textCNN分类(通用模型)、Bert分类(模型进阶))

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 | 接辅导、项目定制 目录 0. 总结&#xff1a;1. 前期准备环境安装 2. 文本分类基本流程a. 加载数据b.构建词典c.生成数据批次和迭代器d.定义模型及实例e. 定义…

Linux搭建hive手册

一、将hive安装包上传到NameNode节点并解压 1、删除安装MySQL时的.rpm文件 cd /opt/install_packages/ rm -rf *.rpm 2、将安装包拖进/install_packages目录 3、解压安装包 tar -zxvf apache-hive-3.1.2-bin.tar.gz -C /opt/softs/ 4、修改包名 cd /opt/softs mv apache-…

力扣双指针算法题目:复写零

1.题目 . - 力扣&#xff08;LeetCode&#xff09; 2.解题思路 本题要求就是对于一个数组顺序表&#xff0c;将表中的所有“0”元素都向后再写一遍&#xff0c;且我们还要保证此元素之后的元素不受到影响&#xff0c;且复写零之后此数组顺序表的总长度不可以改变&#xff0c;…

OpenCV 灰度直方图及熵的计算

目录 一、概述 1.1灰度直方图 1.1.1灰度直方图的原理 1.1.2灰度直方图的应用 1.1.3直方图的评判标准 1.2熵 二、代码实现 三、实现效果 3.1直方图显示 3.2 熵的计算 一、概述 OpenCV中的灰度直方图是一个关键的工具&#xff0c;用于分析和理解图像的灰度分布情况。直…

12 Dockerfile详解

目录 1. Dockerfile 2. Dockerfile构建过程 2.1. Dockerfile编写规则&#xff1a; 2.2. Docker执行Dockerfile的大致流程 2.3. 总结 3. Dockerfile指令 3.1. FROM 3.2. MAINTAINER 3.3. RUN 3.4. EXPOSE 3.5. WORKDIR 3.6. USER 3.7. ENV 3.8. VOLUME 3.9. ADD …

mac 11 变编译安装nginx

mac 11 变编译安装nginx 记录一次安装过程 所需要的包 pcre: https://sourceforge.net/projects/pcre/files/pcre/OpenSSL: https://www.openssl.org/source/Nginx: https://nginx.org/en/download.html如果没有pcre 和Openssl,报错如下 把pcre和Openssl 解压到nginx 目录下…