【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)//调整默认对齐数为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.枚举类型是有作用域的,而宏常量没有,可以再某个函数体内单独使用

总结

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

AI赋能OFFICE 智能化办公利器!

ONLYOFFICE在线编辑器的最新版本8.1已经发布&#xff0c;整个套件带来了30多个新功能和432个bug修复。这个文档编辑器无疑成为了办公软件中的翘楚。它不仅支持处理文本文档、电子表格、演示文稿、可填写的表单和PDF&#xff0c;还允许多人在线协作&#xff0c;并支持AI集成&…

linux 基础命令、gcc的基础用法

1、ls——>列出目录下的内容 语法&#xff1a;ls [-a -l -h] [Linux路径] &#xff08;1&#xff09;-a -l -h 是可选的选项 &#xff08;2&#xff09;Linux路径是此命令的可选参数 ①当不使用选项和参数&#xff0c;直接使用 ls 命令本体&#xff0c;表示&#xff1a;…

以终为始,胜意费控云「包干管控」助力精细管控与体验提升

在全球宏观经济环境的波动和内在经济逻辑的推动下&#xff0c;我国经济正经历着关键的结构调整期。如何稳健穿越周期&#xff0c;是企业必须直面的课题。与此同时&#xff0c;企业成本管控也面临着更为精细和严格的挑战。 企业需要一种更为灵活合理的费用管控策略。胜意费控云升…

3d模型墙模糊怎么回事?---模大狮模型网

在展览3D模型设计行业中&#xff0c;技术细节常常是设计师们需要面对和解决的关键问题之一。其中&#xff0c;3D模型墙模糊的现象可能会影响整个展览的视觉效果和观众的体验。本文将深入探讨这一问题的起因及解决方法&#xff0c;帮助设计师们更好地处理类似挑战。 一、问题的起…

Windows Server 2012 R2查看IIS版本

文章目录 一、方法一1.win R 键打开运行窗口 → 输入 "regedit" → 点击【确定】2.HKEY_LOCAL_MACHINE → SOFTWARE → Microsoft → InetStp 二、方法二1.win R 键打开运行窗口 → 输入 "inetmgr" → 点击【确定】2.点击 【帮助】 → 选择【关于 Intern…

【CVPR 2024】GART: Gaussian Articulated Template Models

【CVPR 2024】GART: Gaussian Articulated Template Models 一、前言Abstract1. Introduction2. Related Work3. Method3.1. Template Prior3.2. Shape Appearance Representation with GMM3.3. Motion Representation with Forward Skinning3.4. Reconstruct GART from Monocu…

字节码编程javassist之获取指令码

写在前面 本文看下使用javassist如何查看方法对应的字节码指令信息。 1&#xff1a;程序 测试类 package com.dahuyou.javassist.huohuo.cc;import java.math.BigDecimal;public class MyApiTestNoAnnotation {public double queryUserInfo(String uId){return BigDecimal.…

SimLab 流体网格建模工具详解 Part2: Mesh

SimLab的建模功能 SimLab中和流体网格建模相关的功能主要集成在 Geometry 和 Mesh 两个标签中。在上期文章中&#xff0c;我们详细介绍了Geometry标签中的内容&#xff0c;本期文章我们将分享继续分享 Mesh 标签的内容&#xff0c;一起来看看吧。 Mesh 标签 标签下的工具生成网…

8.2结构体的定义和使用

代码 #include <iostream> using namespace std; #include <string>//1、创建学生数据类型&#xff1a;学生包括(姓名&#xff0c;年龄&#xff0c;分数) //自定义数据类型&#xff0c;一些类型集合组成一个类型 //语法 struct 类型名称{成员列表} struct Stduent…

西瓜杯CTF(1)

#下班之前写了两个题&#xff0c;后面继续发 Codeinject <?php#Author: h1xaerror_reporting(0); show_source(__FILE__);eval("var_dump((Object)$_POST[1]);"); payload 闭合后面的括号来拼接 POST / HTTP/1.1 Host: 1dc86f1a-cccc-4298-955d-e9179f026d54…

【java计算机毕设】医院信息管理系统MySQL springboot html html maven前后端一体 小组项目设计源码+文档

目录 1项目功能 2项目介绍 3项目地址 1项目功能 【java计算机毕设】医院信息管理系统MySQL springboot html maven项目设计代码文档 小组项目 2项目介绍 系统功能&#xff1a; java医院信息管理系统包括管理员、门诊两种角色&#xff08;初始化两个角色&#xff0c;可自由分…

多文件编程:c/c++分文件写法(入门)

前言 一个 C 项目通常会采取 声明与定义分离 的方式进行编写&#xff0c;其基本遵循&#xff1a;头文件中写声明&#xff0c;源文件中写定义。 此外&#xff0c;为了区分头文件与源文件&#xff0c;会采用不同的文件后缀&#xff1a; .h: 头文件 .cpp: 源文件 (当然还有其他的…

小白轻松上手,Python编程常用的30个经典操作以及代码演示

当谈到经典的Python编程案例时&#xff0c;通常涉及各种基础和进阶的编程任务. 30个常见的案例&#xff0c;涵盖了从基本操作到稍复杂的应用&#xff1a; 基础操作 1.Hello World: 打印"Hello, World!"到控制台。 print("Hello, World!")2.变量和数据…

智能猫砂盆不好用?三款热门智能猫砂盆推荐!

为什么现在那么多人会淘汰掉普通的猫砂盆&#xff0c;转而去购买智能猫砂盆呢&#xff1f;因为智能猫砂盆的自动铲屎功能是真的香啊&#xff0c;有智能猫砂盆在&#xff0c;就不用每天都自己去铲屎了&#xff0c;我只需要隔三四天去清理一下集便仓就好了&#xff0c;对于我们这…

电脑桌面日历记事本怎么弄 好用的桌面日历记事本

在这个数字化的时代&#xff0c;电脑已成为我们日常生活中不可或缺的伙伴。我常常在电脑上记录各种事项&#xff0c;以便随时查看和提醒自己。而我最钟爱的记事方式&#xff0c;莫过于使用桌面日历记事本。 想象一下&#xff0c;你的电脑桌面上有一个直观的日历&#xff0c;每…

[经典]Axrue部件库:Fluent Design部件库

部件库预览链接&#xff1a;&#xff08;请与班主任联系获取文档&#xff09; 支持版本: Axrure RP 8 文件大小: 2.66 MB 文档内容介绍 基本部件&#xff1a; 常规&#xff1a;3款 基本输入&#xff1a;50款 集合&#xff1a;50款 对话框/弹窗&#xff1a;3款 文本&#…

【BUG】RestTemplate发送Post请求后,响应中编码为gzip而导致的报错

BUG描述 20240613-09:59:59.062|INFO|null|810184|xxx|xxx||8|http-nio-xxx-exec-1|com.xxx.jim.xxx.XXXController.?.?|MSG接收到来自xxx的文件请求 headers:[host:"xxx", accept:"text/html,application/json,application/xhtmlxml,application/xml;q0.9,*…

谷歌摸鱼神器来了:推出AI会议替身,一键总结提问发言_会议预约 ai对话

饱受会议折磨的打工人&#xff0c;终于可以解放了&#xff01; 就在刚刚举办的Google Cloud Next’23大会上&#xff0c;谷歌宣布了一系列科技新进展&#xff0c;最瞩目的要属其中的“开会AI替身”了。 只需要一句“帮我参加”&#xff0c;AI就能替你开会&#xff0c;并在合适…

接口测试课程结构

课程大纲 如图&#xff0c;接下来的阶段课程&#xff0c;依次专项讲解如下专题&#xff0c;能力级别为中级&#xff0c;进阶后基本为中高级&#xff1a; 1.接口基础知识&#xff1b; 2.抓包工具&#xff1b; 3.接口工具&#xff1b; 4.mock服务搭建&#xff08;数据模拟服务&am…

解决了一个java Bug:Exception in thread “main“ java.lang.NullPointerException

写代码&#xff0c;遇到了个问题。 很纳闷&#xff0c;跟着人家写的代码。只能去查资料。 赶紧去找&#xff0c;自己的代码 逆天&#xff0c;赶紧改&#xff01; 成功了&#xff01;&#xff01;&#xff01;