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

news2024/11/25 7:11:44

🌟🌟作者主页: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)//调整默认对齐数为1

struct 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.coloradmin.cn/o/1898161.html

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

相关文章

[深度学习]卷积理解

单通道卷积 看这个的可视化就很好理解了 https://github.com/vdumoulin/conv_arithmetic/blob/master/README.md 多通道卷积 当输入有多个通道时,卷积核需要拥有相同的通道数. 假设输入有c个通道,那么卷积核的每个通道分别于相应的输入数据通道进行卷积,然后将得到的特征图对…

tinyshop商城学习

1、使用badboy屏幕录制工具&#xff0c;获得服装购物业务的结果&#xff0c;生成.jmx文件 2、在JMeter中新建线程组&#xff0c;导入.jmx文件 3、完成进入商城&#xff0c;登录&#xff0c;服装页面进入&#xff0c;随机选择服装&#xff0c;添加购物车&#xff0c;开始结算&…

Qt扫盲-QRect矩形描述类

QRect矩形描述总结 一、概述二、常用函数1. 移动类2. 属性函数3. 判断4. 比较计算 三、渲染三、坐标 一、概述 QRect类使用整数精度在平面中定义一个矩形。在绘图的时候经常使用&#xff0c;作为一个二维的参数描述类。 一个矩形主要有两个重要属性&#xff0c;一个是坐标&am…

6款天花板级的国产BI大盘点

以下是六款天花板级的国产BI工具的盘点&#xff0c;包括奥威BI、帆软BI、思迈特BI、永洪BI、观远BI和亿信华辰BI。这些工具各有其独特的优点和擅长的领域。 1. 奥威BI 优点&#xff1a; 无缝对接ERP系统&#xff1a;与金蝶、用友等全版本ERP系统无缝对接&#xff0c;方便用户…

软件游戏d3dcompiler_43.dll丢失怎么办,总结几种有效的方法

在使用电脑时&#xff0c;可能会碰到找不到d3dcompiler_43.dll的问题。即在使用过程中&#xff0c;突然弹出一个提示“d3dcompiler_43.dll丢失”&#xff0c;由于此文件的缺失&#xff0c;部分程序将无法启动。为恢复正常使用&#xff0c;我们需要修复此文件。接下来&#xff0…

el-table封装点击列筛选行数据功能,支持筛选,搜索,排序功能

数据少的话&#xff0c;可以前端实现&#xff0c;如果多的话&#xff0c;建议还是请求接口比较合理父组件&#xff1a; <template> <div class"home"> <!-- <img alt"Vue logo" src"../assets/logo.png"> <HelloWorld …

Spring源码十一:事件驱动

上一篇Spring源码十&#xff1a;BeanPostProcess中&#xff0c;我们介绍了BeanPostProcessor是Spring框架提供的一个强大工具&#xff0c;它允许我们开发者在Bean的生命周期中的特定点进行自定义操作。通过实现BeanPostProcessor接口&#xff0c;开发者可以插入自己的逻辑&…

ServiceImpl中的参数封装为Map到Mapper.java中查询

ServiceImpl中的参数封装为Map到Mapper.java中查询&#xff0c;可以直接从map中获取到key对应的value

吴恩达机器学习作业ex7:K 均值聚类和主成分分析(Python实现)详细注释

文章目录 1 K 均值聚类1.1 实施 K-means1.1.1 寻找最近的中心点1.1.2 计算中心点均值 1.2 示例数据集上的 K-means1.3 随机初始化1.4 用 K-means 压缩图像1.4.1 对像素进行 K 均值分析 2 主成分分析2.1 样例数据集2.3 利用 PCA 降低维度2.3.1 将数据投影到主成分上2.3.2 重建数…

满足GMSL静电防护要求的方案

什么是GMSL&#xff1f;它是做什么用的&#xff1f;它有什么优点&#xff1f;设计GMSL防静电有啥难度&#xff1f; 带着这些疑问我们先了解下什么是GMSL。 一&#xff0e;简述 GMSL GMSL&#xff08;Gigabit Multimedia Serial Link&#xff09;即千兆多媒体串行链路&#xf…

vs code 波浪线报错

这种红色波浪线的 VS code 报错&#xff0c;之前我都是直接忽略&#xff0c;因为不影响运行&#xff0c;但是我看着就很闹心想要给它去掉。 明明这个module 在啊&#xff0c;为啥一直报错 Cannot find module 今天知道原因了&#xff1a; 为了图方便&#xff0c;我 的 VS …

HPR3B-30A1-201、HPR3B-30A2-211-M4液压比例减压阀放大器

HANDOK HYDRAULIC比例减压阀HPR2P-30A1-201、HPR2P-30A1-201-M5、HPR3S-40A1-201-M3、HPR3Q-40A1-201-M2、HDPR3Q-40A1-201-M0、HPR3B-30A2-211、HPR3B-40F1-212 FOR SBS120/140、HDSV4B-A1-232、HPR3NB-30A-221、HDSV3B-A1-232-GO-PVD、HPR3B-30A1-213、HSVD3B-A1-232-GO-PSVD…

网络-calico问题分析

项目场景&#xff1a; calico-node日志提示 Failed to auto-detect host MTU - no interfaces matched the MTU interface pattern. To use auto-MTU, set mtuifacePattern to match your hosts’s interfaes. 同时&#xff0c;cali开头网卡的mtu是1440大小 原因分析&#xff…

每日复盘-20240705

今日关注&#xff1a; 20240705 六日涨幅最大: ------1--------300391--------- 长药控股 五日涨幅最大: ------1--------300391--------- 长药控股 四日涨幅最大: ------1--------300391--------- 长药控股 三日涨幅最大: ------1--------300391--------- 长药控股 二日涨幅最…

windows安装jdk21

下载 下载zip解压 设置环境变量 设置JAVA_HOME环境变量 Path环境变量添加如下值%HAVA_HOME%\bin 打开新的cmd&#xff0c;输入java --version查看效果

C++学习第十三天——stack/queue的使用及底层剖析双端队列容器适配器

✨ 少年的旅途应是星辰大海 &#x1f30f; &#x1f4c3;个人主页&#xff1a;island1314 &#x1f525;个人专栏&#xff1a;C学习 &#x1f680; 欢迎关注&#xff1a;&#x1f44d;点赞 &#x1f442;&am…

安装Linux虚拟机

点击创建新的虚拟机 选择高级 系统自定义推荐 选择稍后安装 选择Linux 虚拟机命名并且选择创建位置 系统自定义 系统自定义推荐 系统自定义推荐 选择安装好的iOS文件 点击完成 选择编辑虚拟机设置 进入后选择第一个Install red hat enterprise 选择常用语言 设置…

【数据结构】(6.3)堆的应用——堆排序(C语言)

系列文章目录 文章目录 系列文章目录前言1. 堆排序的基础知识2. 堆排序详解2.1 堆排序整体思路2.2 思路详解2.2.1 建堆2.2.2 堆排序完整代码2.2.3 输出数据 3. 时间复杂度分析 前言 1. 堆排序的基础知识 堆排序&#xff08;Heap Sort&#xff09;就是对直接选择排序的一种改进…

springboot dynamic配置多数据源

pom.xml引入jar包 <dependency><groupId>com.baomidou</groupId><artifactId>dynamic-datasource-spring-boot-starter</artifactId><version>3.5.2</version> </dependency> application配置文件配置如下 需要主要必须配置…

NET程序开发可能会用到的一些资料文档

NET程序开发使用的一些资料文件&#xff0c;NET高级调试&#xff0c;NET关键技术深入解析&#xff0c;WPF专业编程指南&#xff0c;程序员求职攻略&#xff0c;WPF编程宝典等。 下载链接&#xff1a;https://download.csdn.net/download/qq_43307934/89518582