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

news2024/11/6 9:35:35

1.结构体

1.1结构体类型的基础知识

结构体类型是一些值的集合,这些值被称为成员变量,成员变量可以是不同类型的变量。

1.2结构体类型的声明

结构体的声明格式如下:

struct tag//tag表示标签名
{
 	member-list;//成员列表
 	//由1或者多个成员组成
}variable-list;//变量列表
//定义1或者多个结构体变量
结构体声明举例如下
struct Stu
{
	char name[20];
	int ID;
}s;//分号不能丢

1.3结构体特殊声明

匿名结构体
struct 匿名结构体声明省略了tag(标签名)
{
	int i;
	char ch;
}x;

struct //匿名结构体声明省略了tag(标签名)
{
	int i;
	char ch
}*p;

上述代码中,声明了两个匿名结构体,分别定义了两个匿名结构体类型变量,struct x 和 struct* p。这两个匿名结构体变量只能使用一次。

//在上面代码的基础上,解释下面的代码的合法性
p=&x

[外链图片转存失败,源站可能有防盗在这里插入!链机制,建描述]议将图片上https://传(imbe.csdnimg.cn/bc9gYGW1502aa2cf4edea95e978d4bbb.png302)(img src=“https://img-blog.csdnimg.cn/260f8cd5c4e244369fb0fb358217b574.png” width=“60%”>)]< img src=“https://img-blog.csdnimg.cn/bc91502aa2cf4edea985e97dd2842bbb.png” width=“60%”>
p=&x这段代码是非法的,通过编译代码可知,p = &x这段代码,在编译器看来是不兼容的。虽然代码可以正常的运行,但是这并不代码代码是正确的。我们应该避免的去写这类隐形问题的代码。

1.4结构体的自引用

代码1

struct Node
{
	int data;
	struct Node next;
};
//这样自引用是否可行?

结构体中是否能够自己调用自己呢?
答案是不可以的。这样自引用是不行的,因为这样声明的结构体我们无从得知它的具体大小。
在这里插入图片描述

代码2
struct Node
{
	int data;
	struct Node* next;
};

正确的结构体自引用必须是在结构体中,通过同类型指针的形式来进行。因为这样结构体的大小才能够确定。

代码3
typedef struct Node
{
	int data;
	Node* next;
}Node;
//这样是否可行呢?

这样显然是不行的,这里对struct Node类型结构体进行重命名,但是代码3在这份声明里就直接使用了重命名后的Node。这样是非法的。正确代码如代码4.

代码4
typedef struct Node
{
	int data;
	struct Node* next;
}Node;

在此声明后便可以使用Node来表示struct Node这个结构体。

1.5结构体变量的定义和初始化

struct stu
{
	char ch;
	int i;
}s1;//定义是全局的结构体变量。

struct stu s2={'c',10};//定义并初始化。

struct Node
{
	struct Node* nex;
	struct stu;
}p = {NULL,{'g', 23}};//嵌套定义并初始化


int main()
{
	//定义的是巨变的结构体变量
	struct stu s4 = {.i = 20, .ch = 'x'};//乱序初始化
	return 0;
}

1.6结构体内存对齐

什么是结构体内存对齐?
结构体内存对齐指的是通过消耗空间换取时间效率的一种内存访问方式。在这里插入图片描述

下面将介绍结构体内存对齐的规则:
1、第一个成员的地址与结构体变量的起始地址偏移量为0。
2、其他成员要对齐到对应的对齐数处,取决于该成员的自身的大小所能够整除的倍数处。如int类型成员的对齐数就是基于0偏移量后4的倍数。
对齐数也就是:编译器默认对齐数与成员自身大小,两者中的较小值。 VS2019:默认对齐数是8。 Linux gcc:无默认对齐数。
3、结构体的大小必须是最大对齐数(成员对齐数中的最大值)的整数倍。
4、如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

下面通过例题来对此知识点进行巩固。

//练习1
struct s1
{
	char c1;
	int i;
	char c2;
};

int main()
{
	printf("%d\n", sizeof(struct s1));
	return 0;
}

在这里插入图片描述
在这里插入图片描述

//练习2
struct S2
{
	char c1;
	char c2;
	int i;
};

int main()
{
	printf("%d\n", sizeof(struct S2));
	return 0;
}

在这里插入图片描述
在这里插入图片描述

//练习3
struct S3
{
	double d;
	char c;
	int i;
};

int main()
{
	printf("%d\n", sizeof(struct S3));
	return 0;
}

在这里插入图片描述

在这里插入图片描述

//练习4-结构体嵌套问题

struct S3
{
	double d;
	char c;
	int i;
};

struct S4
{
	char c1;
	struct S3 s3;
	double d;
};


int main()
{
	printf("%d\n", sizeof(struct S4));
	return 0;
}

在这里插入图片描述
在这里插入图片描述
补充:从练习1和练习2中不难发现在没有特殊情况下,可以尽量将对其数较小的成员变量往前放,这样可以节省一定的空间。

1.7修改默认对齐数

当需要设计对结构体对齐数有特殊要求时,可以使用#pargma指令修改对齐数。

这里介绍修改默认对齐数指令:#pragma pack(要修改的对齐数)。若为#pragma pack()表示恢复默认对齐数。

#include <stdio.h>
#pragma pack(8)//设置默认对齐数为8
struct S1
{
    char c1;
    int i;
    char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
#pragma pack(1)//设置默认对齐数为1
struct S2
{
    char c1;
    int i;
    char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
int main()
{
    //输出的结果是什么?
    printf("%d\n", sizeof(struct S1));
    printf("%d\n", sizeof(struct S2));
    return 0;
}

在这里插入图片描述
在这里插入图片描述

1.8结构体传参

struct S
{
 int data[1000];
 int num;
};
struct S s = {{1,2,3,4}, 1000};
//结构体传参
void print1(struct S s)
{
 printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{
 printf("%d\n", ps->num);
}
int main()
{
 print1(s);  //传结构体
 print2(&s); //传地址
 return 0;
}

结论:在结构体传参传结构体名,当结构体过大时,传参压栈时,效率会下降。而传结构体的地址,可以有效的避免,因为结构体过大而导致的性能开销。所以,结构体传参建议使用传指针的方式。

2.位段

2.1什么是位段

位段就是结构体成员以二进制位来进行对数据的存储和使用。位段是结构体可以实现的一种能力。位段用来节省空间的。

位段的声明和结构体类似,但是有两点不同:
1、位段的成员必须是整型类型数据。如int 、unsigned int、char、unsigned char。char类型在内存中是以ASCII码存储的,所以本质上也是整型类型。
2、位段成员名后有一个冒号和一个数字。

2.2位段的声明

struct A
{
	int _a : 2;//2表示占用2个二进制位
	int _b : 5;
	int _c : 10;
	int _d : 30;
};

2.3位段在内存中的分配

上面我通过代码举例了一个位段A,它所占内存空间的大小是多少呢?

这里我就简单介绍位段在内存中的分配,便可以一探究竟。
位段的内存分配规则
1、位段的成员必须是整型类型
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;
//空间是如何开辟的?

注:上述代码在不同的环境下的结果可能会有所不同。当前作者使用的环境为VS2019。
在这里插入图片描述

在这里插入图片描述

2.4位段的跨平台问题

1、 int 位段被当成有符号数还是无符号数是不确定的。这是因为C语言标准没有明确规定。
2、不同的机器环境下,位段的最大数是不确定的,如32位机器的最大为段数就是32,而在16位机器下,位段的最大数为16,若在16位机器上运行位段数为27的程序,这将必然导致程序产生不可预知的错误。
3、位段中的成员在内存中申请空间是从低地址处向高地址处申请,还是从高地址处向低地址处申请。这是取决于编译器的。C语言标准中是没有相关规定的。
4、当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。在C语言的标准中也没有具体地规定。

2.5位段的应用

在这里插入图片描述

上图是一个网络网络协议的封装,这是位段的一个应用场景。为什么需要用位段来封装呢?
在这里插入图片描述
当数据在网络中进行传输时,使用位段进行封装可以有效地节省带宽的压力了,提升数据在网络中传输的效率。

3.枚举

3.1枚举的定义

枚举顾名思义就是值分别列举出来的一种自定义类型。枚举类型默认不初始化的情况下是从0开始依次加1。

enum Day//星期
{
 Mon,//0
 Tues,//1
 Wed,//2
 Thur,//3
 Fri,//4
 Sat,//5
 Sun//6
};
enum Sex//性别
{
 MALE,
 FEMALE,
 SECRET
}enum Color//颜色
{
 RED,
 GREEN,
 BLUE
};

这里我举几个特殊的例子

//例1,默认不给定值进行初始化
enum Sex//性别
{
	MALE,
	FEMALE,
	SECRET
};

int main()
{
	printf("%d\n", MALE);
	printf("%d\n", FEMALE);
	printf("%d\n", SECRET);
	return 0;
}

在这里插入图片描述

//例2,给定值进行初始化
enum Sex//性别
{
	MALE = 2,
	FEMALE = 6,
	SECRET = 8
};

int main()
{
	printf("%d\n", MALE);
	printf("%d\n", FEMALE);
	printf("%d\n", SECRET);
	return 0;
}

在这里插入图片描述

//例3,不完全给定值进行初始化
enum Sex//性别
{
	MALE ,
	FEMALE = 6,
	SECRET 
};

int main()
{
	printf("%d\n", MALE);
	printf("%d\n", FEMALE);
	printf("%d\n", SECRET);
	return 0;
}

<imgsrc="https://imgblog.csdnimg.cn/c4333c9a9ea54efc997b715d369b8821.png" width="60%">

枚举常量可以指定值进行初始化,若未指定值,起始位置为0,然后其他位置的值为上一个枚举常量的值+1。

3.2枚举常量的优点

在前面的学习中,我们还简单接触了#define定义的常量。那么枚举常量的优点是什么呢?

1、枚举常量可以增加代码的可读性和可维护性。
2、枚举常量相比于#define定义的常量来说,有类型检查,增加了代码的安全性。
3、使用方便,一次可以定义多个常量。还可以对枚举类型进行调试。

3.3枚举类型的使用

enum Color//颜色
{
 RED=5,
 GREEN=6,
 BLUE=7
};
enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。
clr = 5;               //ok??

初始化枚举类型变量必须使用枚举常量,这样左右两边的类型值才能够配对。若直接通过值大小一样的整数来初始化,虽然在部分C语言的编译器下可以跑得过去,但这绝不是一个好的习惯。

4联合体

4.1联合体的定义

联合体是一种特殊的自定义类型。联合体成员特征是该自定义类型的变量是共用同一块空间的。联合体又称为共用体。


//联合类型的声明
union Un
{
	char c;
	int i;
};

int main()
{
	//联合变量的定义
	union Un un;
	return 0;
}

4.2联合体的特点

联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)。

union Un
{
 int i;
 char c;
};
union Un un;
// 下面输出的结果是一样的吗?
printf("%d\n", &(un.i));
printf("%d\n", &(un.c));
//下面输出的结果是什么?
un.i = 0x11223344;
un.c = 0x55;
printf("%x\n", un.i);

在这里插入图片描述

在这里插入图片描述

4.3联合体大小的计算

1、联合体的大小至少是最大的成员体的大小。
2、当最大成员体的大小不是最大对齐数的整数倍时,则要对齐到对齐数的最大整数倍处。

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));
}

在这里插入图片描述
在这里插入图片描述

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

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

相关文章

Hadoop01【尚硅谷】

大数据学习笔记 大数据概念 大数据&#xff1a;指无法在一定时间范围内用常规软件工具进行捕捉、管理和处理的数据集合&#xff0c;是需要新处理模式才能具有更强的决策力、洞察发现力和流程优化能力的海量、高增长率和多样化的信息资产。 主要解决&#xff0c;海量数据的存储…

iOS开发AppleDeveloper中给别人授权开发者权限后,对方一直显示不了我的开发账号team

在iOS开发经常出现多人协作开发的情况。这时我们通常要发邮件邀请别的用户为开发者或者app管理就可以开发我们自己的项目了。但是这次我给别人授权开发者权限后&#xff0c;发现别人权限中没有证书相关权限如图&#xff1a;并且别人登录该账号后&#xff0c;在xcode中只有一个看…

【办公类-16-09】“2022下学期 大班运动场地分配表-跳过节日循环排序”(python 排班表系列)

样例展示&#xff1a;跳过节日的运动场地循环排序表&#xff08;8个班级8组内容 下学期一共20周&#xff09;背景需求&#xff1a;上学期做过一次大班运动场地安排&#xff0c;跳过节日。2023.2下学期运动场地排班&#xff08;跳过节日&#xff09;又来了。一、场地器械微调二、…

雅利安人有多强悍?灭掉三个文明古国,为何败在殷商的脚下

转自&#xff1a;雅利安人有多强悍&#xff1f;灭掉三个文明古国&#xff0c;为何败在中国脚下 (baidu.com)这个民族民风彪悍&#xff1b;这个民族曾一度将四大文明古国中的三个——古埃及、古印度、古巴比伦尽数消灭&#xff1b;这个民族甚至被称为“文明粉碎机”&#xff1b;…

EasyExcel的使用

这里写目录标题先导入依赖最简单的写最简单的读項目开发中的一些操作xml一定要默認放先导入依赖 <dependencies><dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>2.1.7</version><…

lombok的原理 和 使用

原理Lombok能以简单的注解形式来简化java代码&#xff0c;提高开发人员的开发效率。其实并没有改变字节码文件的任何内容&#xff0c;只是简化的程序员编写代码的方式。不使用lombok&#xff1a;使用lombok&#xff1a;lombok常用注解Setter &#xff1a;注解在类或字段&#x…

【面试总结】Linux篇·操作及原理篇

【面试总结】Linux篇原理篇1.介绍一下inode2.说一下Linux系统的目录结构3.说一下Linux系统的文件类型4.如何在Linux上配置一个IP地址5.Linux负载是什么&#xff1f;6.Linux中的软链接和硬链接有什么区别&#xff1f;1.介绍一下inode 硬盘的最小存储单位是扇区(Sector)&#xf…

2自由度悬架LQR控制

目录 1 悬架系统 1.1 悬架结构示意图 1.2 悬架数学模型 1.3 路面激励 2.仿真分析 2.1simulink模型 2.2 仿真结果 2.3 结论 3. 总结 1 悬架系统 1.1 悬架结构示意图 1.2 悬架数学模型 其中&#xff1a;x1为悬架动扰度&#xff0c;x2为车身加速度&#xff0c;x3为轮胎…

红黑树的历史和由来。

一个数组&#xff0c;1,2,3,4,5,...n; 一共n个数字。1、直接查找想要查询第n个数字&#xff0c;直接搜索&#xff0c;就是n次查询。ps:那么问题来了&#xff0c;这样查询也太慢了&#xff0c;有什么改进的呢&#xff1f;2、二分查找这个时候&#xff0c;二分查找更快。不过就是…

python中的.nc文件处理 | 04 利用矢量边界提取NC数据

利用矢量边界提取.nc数据 import osimport numpy as np import pandas as pd import matplotlib.pyplot as plt import cartopy.crs as ccrs import cartopy.feature as cfeature import seaborn as sns import geopandas as gpd import earthpy as et import xarray as xr # …

渲染有问题?怎么办?6种方法让你渲染无忧

简单点&#xff0c;解决问题的方式简单点。 日常工作中我们总会遇到各种各样的问题&#xff0c;比如渲不出图&#xff0c;速度太慢或效率太低&#xff0c;各种噪点和黑图等等&#xff0c;烦不胜烦&#xff0c;今天我就针对6个常见的问题给大家说下方法&#xff0c;一家之言仅供…

ASP .NET(基于.NET 6.0)源码解读

这几天一直在琢磨在我现有技术认知基础上&#xff0c;未来如何做技术提升。 日思夜想&#xff0c;我整理出了我自己的一套学习规划方案&#xff0c;并希望在实施过程中能够不断调整学习方案与方式&#xff0c;以接近自我提升的效率最大化。 从以下几个大的方面来得到提升&…

特征检测之HOG特征算法详解及Opencv接口使用

1. HOG特征简介 特征描述符是图像或图像补丁的表示形式&#xff0c;它通过提取有用信息并丢弃无关信息来简化图像。 通常&#xff0c;特征描述符将大小W x H x 3&#xff08;通道&#xff09;的图像转换为长度为n的特征向量/数组。对于 HOG 特征描述符&#xff0c;输入图像的…

ChatGPT会让程序员失业?ChatGPT:“ 是友军,我不从事任何职业。

毫无疑问&#xff0c;ChatGPT“出圈”了。 它似乎无所不能。 许多人担忧它是否会取代自己的饭碗&#xff0c;唯恐自己的进步赶不上 AI 的发展。 然而&#xff0c;有人在试用几次之后&#xff0c;又算是松了口气&#xff1a;打工人我呀&#xff0c;工作算是保住啦~ 那么&…

Lesson5.3---Python 之 NumPy 统计函数、数据类型和文件操作

一、统计函数 NumPy 能方便地求出统计学常见的描述性统计量。最开始呢&#xff0c;我们还是先导入 numpy。 import numpy as np1. 求平均值 mean() mean() 是默认求出数组内所有元素的平均值。我们使用 np.arange(20).reshape((4,5)) 生成一个初始值默认为 0&#xff0c;终止…

ArcGIS笔记3_如何编辑、修改和导出散点数据

本文目录前言Step 1 在ArcGIS中添加并显示坐标点Step 2 将坐标数据保存成shp文件Step 3 编辑或修改坐标数据Step 4 导出修改后的数据&#xff1a;法一&#xff1a;通过转换工具导出Step 5 导出修改后的数据&#xff1a;法二&#xff1a;通过dBASE表导出前言 本博文更多针对Arc…

【函数栈帧的创建和销毁】 -- 神仙级别底层原理,你学会了吗?

文章目录1.函数的调用方式 2.函数在栈区上的动作 1.函数的调用方式 相信你对调用函数一点都不陌生&#xff0c;但是在调用函数的过程中&#xff0c;却存在着很多你无法见到的东西&#xff0c;这是底层信息&#xff0c;想要理解透彻&#xff0c;就得深入底层去观察。 本文以…

Spring之AOP底层源码解析

Spring之AOP底层源码解析 1、动态代理 代理模式的解释&#xff1a;为其他对象提供一种代理以控制对这个对象的访问&#xff0c;增强一个类中的某个方法&#xff0c;对程序进行扩展。 举个例子 public class UserService {public void test() {System.out.println("test.…

LeetCode-216. 组合总和 III

目录题目分析回溯三部曲剪枝优化题目来源 216. 组合总和 III 题目分析 这个和leetcode77组合类似 本题k相当于树的深度&#xff0c;9&#xff08;因为整个集合就是9个数&#xff09;就是树的宽度。 例如 k 2&#xff0c;n 4的话&#xff0c;就是在集合[1,2,3,4,5,6,7,8,9]中…

我的车载开发—{ carservice启动流程 }—

carservice启动流程 大致流程&#xff1a; SystemServer启动CarServiceHelperService服务在调用startService后&#xff0c;CarServiceHelperService的onStart方法通过bindService的方式启动CarService&#xff08;一个系统级别的APK&#xff0c;位于system/priv-app&#xf…