【进阶C语言】自定义类型:结构体,枚举,联合

news2024/11/20 15:16:07

在这里插入图片描述
前言

作者简介:热爱跑步的恒川,正在学习C/C++、Java、Python等。
本文收录于C语言进阶系列,本专栏主要内容为数据的存储、指针的进阶、字符串和内存函数的介绍、自定义类型结构、动态内存管理、文件操作等,持续更新!
相关专栏Python,Java等正在发展,拭目以待!


自定义类型:结构体,枚举,联合

  • 1. 结构体
    • 1.1 结构的基础知识
    • 1.2 结构的声明
    • 1.3 特殊的声明
    • 1.4 结构的自引用
    • 1.5 结构体变量的定义和初始化
    • 1.6 结构体内存对齐
    • 1.7 修改默认对齐数
    • 1.8 结构体传参
  • 2. 位段
    • 2.1 什么是位段
    • 2.2 位段的内存分配
    • 2.3 位段的跨平台问题
    • 2.4 位段的应用
  • 3. 枚举
    • 3.1 枚举类型的定义
    • 3.2 枚举的优点
    • 3.3 枚举的使用
  • 4. 联合(共用体)
    • 4.1 联合类型的定义
    • 4.2 联合的特点
    • 4.3 联合大小的计算

1. 结构体

1.1 结构的基础知识

结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。注意:之前学过的数组里的每个成员是相同类型的变量。

1.2 结构的声明

struct tag//tag结构体的名字
{
     member-list;
}variable-list;

例如描述一个学生:

struct Stu
{
    //成员变量
	char name[20];//名字
	int age;//年龄
	char sex[5];//性别
	char id[20];//学号
}; //分号不能丢
//定义学生类型
struct Stu
{
	//成员变量
	char name[20];
	int age;
	float weight;
} s4, s5, s6;//s4、5、6都是学生//全局变量

int main()
{
	//int num = 0;
	//我们用类型创建变量,s1是第一个学生,s2是第二个学生,s3是第三个学生
	struct Stu s1;//局部变量
	struct Stu s2;
	struct Stu s3;

	return 0;
}

1.3 特殊的声明

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

//匿名结构体类型
struct 
{
	char c;
	int a;
	double d;
}s1;

struct
{
	char c;
	int a;
	double d;
}* ps;

int main()
{
	//ps = &s1;//err

	return 0;
}

上面的两个结构在声明的时候省略掉了结构体标签(stu)。
那么问题来了?

//在上面代码的基础上,下面的代码合法吗?
ps = &s1;

警告:
编译器会把上面的两个声明当成完全不同的两个类型。
所以是非法的。

1.4 结构的自引用

在结构中包含一个类型为该结构本身的成员是否可以呢?
在这里插入图片描述
我想将这样一个链表连接起来,那要怎么样连接呢?

struct Node
{
	int data;
	struct Node n;
};
//可行否?
如果可以,那sizeof(struct Node)是多少?

答案是当然不可行的

正确的自引用方式:

struct Node
{
	int data;//4
	struct Node* next;//4/8
};

int main()
{
	struct Node n1;
	struct Node n2;
	n1.next =  &n2;

	return 0;
}

当一个结构体可以找到另外一个跟自己同类型的结构体的时候就可以用这种方法,自己的类型包含一个自己的变量是绝对不行的,而应该是自己类型包含一个自己类型的指针才是可行的

拓展一个新的知识点

typedef struct
{
	int data;
	char c;
} S;

typedef可以对一个匿名结构体重命名定义一个新的名字,这个S不是一个变量名而是一个类型名

在思考一个问题

typedef struct 
{
	int data;
	Node* next;
}Node;

这样行不行?

答案是不可以,结构体是要有一个先后顺序才行,先进行大括号里面的内容,后看大括号外面的,这里的Node还没有重命名就使用时错误的,如果你硬想这样使用,那应该这样写

typedef struct Node
{
	int data;
	struct Node* next;
}Node;

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

有了结构体类型,那如何定义变量,其实很简单。
变量的定义:

struct S
{
	int a;
	char c;
}s1;//全局变量

struct S s3;//全局变量

int main()
{
	struct S s2;//局部变量
	return 0;
}

这样写行不行?

当然是可行的

初始化:定义变量的同时赋初值

struct S
{
	int a;
	char c;
}s1;

struct B
{
	float f;
	struct S s;
};

int main()
{
	//int arr[10] = {1,2,3};
	//int a = 0;
	struct S s2 = {100, 'q'};
	struct S s3 = {.c = 'r', .a = 2000};//用.操作可以指定我的顺序,其他都是默认顺序
	struct B sb = { 3.14f, {200, 'w'}};//结构体里面含有结构体要再用一个大括号
	printf("%f,%d,%c\n", sb.f, sb.s.a, sb.s.c);

	return 0;
}

含有指针的的结构体初始化

struct S
{
	char name[100];
	int* ptr;
};

int main()
{
	int a = 100;
	struct S s = {"abcdef", NULL};

	return 0;
}

1.6 结构体内存对齐

我们已经掌握了结构体的基本使用了。
现在我们深入讨论一个问题:计算结构体的大小。
这也是一个特别热门的考点: 结构体内存对齐
直接上代码

struct S1
{
	int a;
	char c;
};

struct S2
{
	char c1;
	int a;
	char c2;
};

struct S3
{
	char c1;
	int a;
	char c2;
	char c3;
}; 

int main()
{
	//探讨到底是下面三个答案的哪个
	//5 6 7
	//8 8 8
	//8 12 12
	printf("%d\n", sizeof(struct S1));
	printf("%d\n", sizeof(struct S2));
	printf("%d\n", sizeof(struct S3));
	return 0;
}

答案却是 8 12 12,这是为什么呢?

首先得掌握结构体的对齐规则:

  1. 结构体的第一个成员永远都放在0偏移处
  2. 从第二个成员开始,以后的每个成员都要对齐到某个对齐数的整数倍处
    这个对齐数是:成员自身大小和默认对齐数的较小值
    备注:
    VS环境下 没有默认对齐数,没有默认对齐数时,对齐数就是成员自身的大小
  3. 当成员全部存放进去后
    结构体的总大小必须是,所以成员的对齐数中最大对齐数的整数倍
    如果不够,则浪费空间对齐。
  4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
    在这里插入图片描述
    当换一下位置时:
    在这里插入图片描述

如果不相信可以求一下他们的偏移量
求偏移量需要用到一个offsetof宏

#include <stddef.h>


struct S
{
	char c;
	int a;
};

int main()
{
	struct S s = {0};
	printf("%d\n", offsetof(struct S, c));//0
	printf("%d\n", offsetof(struct S, a));//4

	return 0;
}

当了解这个规则后,我们再练习一个

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

图片讲解:
在这里插入图片描述
再练习一个

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

在这里插入图片描述
再练习一个结构体嵌套的问题

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. 性能原因:
    数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
    原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

总体来说:

结构体的内存对齐是拿空间来换取时间的做法。

那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:

让占用空间小的成员尽量集中在一起。

//例如:
struct S1
{
	char c1;
	int i;
	char c2;
};
struct S2
{
	char c1;
	char c2;
	int i;
};

在这里插入图片描述
S1和S2类型的成员一模一样,但是S1和S2所占空间的大小有了一些区别。

1.7 修改默认对齐数

之前我们见过了 #pragma 这个预处理指令,这里我们再次使用,可以改变我们的默认对齐数。

#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()
{
	//输出的结果是什么?//结果为6
	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;
}

上面的 print1 和 print2 函数哪个好些?
答案是:
首选print2函数。
原因:

函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。

结论:
结构体传参的时候,要传结构体的地址。

2. 位段

结构体讲完就得讲讲结构体实现 位段 的能力。

2.1 什么是位段

位段的声明和结构是类似的,有两个不同:

  1. 位段的成员必须是 int、unsigned int 或signed int 。
  2. 位段的成员名后边有一个冒号和一个数字。

比如:

struct A
{
	int _a : 2;
	int _b : 5;
	int _c : 10;
	int _d : 30;
};//47个比特位

A就是一个位段类型。
那位段A的大小是多少?

printf("%d\n", sizeof(struct A));

打印结果是8

2.2 位段的内存分配

  1. 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型

  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;

//空间是如何开辟的?

在这里插入图片描述

2.3 位段的跨平台问题

  1. int 位段被当成有符号数还是无符号数是不确定的。

  2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机
    器会出问题。

  3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。

  4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是
    舍弃剩余的位还是利用,这是不确定的。

总结:

跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。

2.4 位段的应用

在这里插入图片描述

3. 枚举

枚举顾名思义就是一一列举。
把可能的取值一一列举。
比如我们现实生活中:

一周的星期一到星期日是有限的7天,可以一一列举。
性别有:男、女、保密,也可以一一列举。
月份有12个月,也可以一一列举

这里就可以使用枚举了。

3.1 枚举类型的定义

枚举的可能取值,默认是从0开始的,递增1的

enum Day//星期

{
	Mon,
	Tues,
	Wed,
	Thur,
	Fri,
	Sat,
	Sun

};

enum Sex//性别

{
	MALE,
	FEMALE,
	SECRET

};

enum Color//颜色

{
	RED,
	GREEN,
	BLUE

};

以上定义的 enum Day , enum Sex , enum Color 都是枚举类型。

{}中的内容是枚举类型的可能取值,也叫 枚举常量 。
这些可能取值都是有值的,默认从0开始,一次递增1,当然在定义的时候也可以赋初值。
例如:

enum Color//颜色

{
	RED = 1,
	GREEN = 2,
	BLUE = 4

};

3.2 枚举的优点

为什么使用枚举?

我们可以使用 #define 定义常量,为什么非要使用枚举?
枚举的优点:

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

  2. 和#define定义的标识符比较枚举有类型检查,更加严谨。

  3. 防止了命名污染(封装)

  4. 便于调试

  5. 使用方便,一次可以定义多个常量

3.3 枚举的使用

enum Color//颜色

{
	RED = 1,
	GREEN = 2,
	BLUE = 4

};

enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。

clr = 5;               //ok??

4. 联合(共用体)

4.1 联合类型的定义

联合也是一种特殊的自定义类型
这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)。
比如:

//联合类型的声明

union Un

{
	char c;
	int i;
};

//联合变量的定义

union Un un;

//计算连个变量的大小

printf("%d\n", sizeof(un));

打印结果是4

4.2 联合的特点

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

union Un
{
	char c;//1
	int i;//4
};

int main()
{
	union Un u;
	printf("%d\n", sizeof(u));
	printf("%p\n", &u);
	printf("%p\n", &(u.i));
	printf("%p\n", &(u.c));

	return 0;
}

代码结果:
在这里插入图片描述
图片讲解:
在这里插入图片描述

共用体的成员是共用同一块空间的

来一道笔试题

判断当前计算机的大小端存储

在这里插入图片描述
这是之前求大小端的图讲,现在将用联合体求大小端

union Un
{
	char c;//1
	int i;//4
};

/int main()
{
	union Un u;
	u.i = 1;
	if (u.c == 1)
		printf("小端\n");
	else
		printf("大端\n");


	return 0;
}

4.3 联合大小的计算

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

比如:

union Un1

{
	char c[5];
	int i;
};

union Un2

{
	short c[7];
	int i;
};

//下面输出的结果是什么?

printf("%d\n", sizeof(union Un1));//8

printf("%d\n", sizeof(union Un2));//16

如果这份博客对大家有帮助,希望各位给恒川一个免费的点赞作为鼓励,并评论收藏一下,谢谢大家!!!
制作不易,如果大家有什么疑问或给恒川的意见,欢迎评论区留言。

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

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

相关文章

ECM8.0——CCCM相关代码

ECM8.0——CCCM相关代码概览predIntraCCCMxCccmCalcModelsxCccmApplyModelxCccmCreateLumaRefxCccmGetLumaRefBufxCccmGetLumaPuBufxCccmCalcRefAverxCccmCalcRefArea概览 函数名称函数作用predIntraCCCM总体代码&#xff0c;根据CCCM技术预测输出xCccmCalcModels计算CCCM滤波器…

Idea+maven+spring-cloud项目搭建系列--14 整合请求参数校验

前言&#xff1a;当我们在进行web 项目的开发时&#xff0c;对于前端传入的参数&#xff0c;都需要进行一些非空必填等的验证&#xff0c;然后在进行业务逻辑的处理&#xff0c;如果写一堆的if 判断很不优雅&#xff0c;那么有没有好的方式来帮忙处理&#xff0c;本文通过hiber…

光电隔离转换器 直流信号放大器 导轨安装DIN11 IPO OC系列

概述&#xff1a; 导轨安装DIN11 IPO OC系列模拟信号隔离放大器是一种将输入信号隔离放大、转换成按比例输出的直流信号混合集成厚模电路。产品广泛应用在电力、远程监控、仪器仪表、医疗设备、工业自控等需要直流信号隔离测控的行业。此系列产品内部采用了线性光电隔离技术相…

DSP中定点与浮点运算

一、定点数及其定标 在定点DSP芯片中&#xff0c;采用的是定点数据数值运算&#xff0c;其操作数一般采用整形数来表示。一个整形数的最大表示范围由DSP芯片给定字长决定。字长越长&#xff0c;表示的范围越大&#xff0c;精度越高。 举例16位字长 每个16数位用1个符号位表示正…

九龙证券|这一刻,资本市场进入全新时代!

2023年4月10日&#xff0c;第一批10家主板注册制企业上市鸣锣敲钟&#xff0c;奏响了触及本钱商场灵魂深处革新的序曲。 动能切换中的我国对于高效资源配置的渴望&#xff0c;与革新进行时的本钱商场对于全面注册制的探究&#xff0c;一起凝集成一股连绵有力之暖流&#xff0c;…

学习安全攻防技能30讲-开篇|别说你没有被安全困扰过

文章目录学习安全攻防技能30讲-开篇|别说你没有被安全困扰过研读开篇安全重要吗&#xff1f;安全难学吗&#xff1f;学习安全攻防技能30讲-开篇|别说你没有被安全困扰过 研读开篇 文中说到一个竞赛叫CTF&#xff0c;这个之前从来没有听过的&#xff0c;作为开发人员涨知识了。…

Java封装

Java封装\huge{Java封装}Java封装 JavaJavaJava的三大特征之一。 作用 告知如何设计对象的属性和方法&#xff0c;将对象完全独立起来。 ❗❗封装原则 对象代表什么&#xff0c;就要封装对应的数据&#xff0c;并且提供对应数据的行为。&#xff08;尤其是后半句非常重要&a…

012:Mapbox GL显示弹窗Popup

第012个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+mapbox中显示弹窗。 直接复制下面的 vue+mapbox源代码,操作2分钟即可运行实现效果 文章目录 示例效果配置方式示例源代码(共70行)相关API参考:专栏目标示例效果 配置方式 1)查看基础设置:https://xiaozhuan…

PHP医院安全(不良)事件管理系统源码,十多种不良事件类型,上百种报告内容数据表

医院不良事件上报系统源码&#xff0c;PHP医院安全&#xff08;不良&#xff09;事件管理系统源码 技术架构&#xff1a;前后端分离&#xff0c;仓储模式&#xff0c; 开发语言&#xff1a;PHP 开发工具&#xff1a;vscode 前端框架&#xff1a;vue2element 后端框架&…

2023年mathorcupB题城市轨道交通列车时刻表思路分析

B 题 城市轨道交通列车时刻表优化问题 列车时刻表优化问题是轨道交通领域行车组织方式的经典问题之一。 列车时刻表规定了列车在每个车站的到达和出发(或通过)时刻&#xff0c;其在实 际运用过程中&#xff0c;通常用列车运行图来表示。图 1 为某一运行图的示例&#xff0c;图…

文件操作【上篇】

文章目录&#x1f5c3;️1.为什么使用文件&#x1f5c3;️2.什么是文件&#x1f4c1;2.1.程序文件&#x1f4c1;2.2.数据文件&#x1f4c1;2.3.文件名&#x1f5c3;️3.文件的打开和关闭&#x1f4c1;3.1.文件指针&#x1f4c1;3.2.文件的打开和关闭&#x1f5c3;️4.文件的顺序…

I-型糖尿病患者的福音,皮下燃料电池将多余的血糖转化为电能产生胰岛素

I-型糖尿病患者体内不产生胰岛素&#xff0c;患者必须从外部获得激素来调节血糖水平。当前&#xff0c;患者主要通过将胰岛素泵直接连接到身体以获得胰岛素。这些胰岛素泵以及其他医疗器械&#xff08;如起搏器&#xff09;需要可靠的能源供应&#xff0c;主要通过一次性或可充…

#mvn 打包ik分词器报错#

场景&#xff1a;在window上安装ik分词器&#xff0c;需要先mvn打包&#xff0c;结果报错 原因&#xff1a;由于jdk版本的问题导致 解决过程 1&#xff1a;打包流程 git clone https://github.com/medcl/elasticsearch-analysis-ik #git clone https://gitcode.net/mirrors/me…

线性分类算法:逻辑回归和Softmax回归

目录&#xff08;一 &#xff09;逻辑回归1.1 逻辑回归概述&#xff1a;1.2 逻辑回归的作用与Sigmoid 函数&#xff1a;1.2.1 Sigmoid 函数作用1.3 指数族分布1.4 逻辑回归的损失函数1.5逻辑回归如何求解得到最优解模型方法1.6 逻辑回归鸢尾花分类1.7 逻辑回归做多分类&#xf…

拉取gradle项目报错Could not find method compile() for arguments

拉取gradle项目, 依赖拉不下来, 报错如下: Could not find method compile() for arguments XXXXXX on object of type org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler. 找了很多篇文章都没有解决,后来在stack overflow上面找到了答案: Note t…

Linux中快速搭建RabbitMQ

目录一、简介1、关于RabbitMQ2、RabbitMQ主要端口介绍二、RabbitMQ安装1、安装依赖项socat2、下载Erlang和RabbitMQ(1) Erlang和RabbitMQ版本关系要求(2) 下载操作系统支持的Erlang和RabbitMQ版本(3) 安装Erlang和RabbitMQ三、启动和关闭1、启动服务2、查看状态3、停止服务4、设…

核心业务3:借款人借款申请

核心业务3&#xff1a;借款人借款申请 1.借款人借款申请业务流程图 2.借款项数据库绑定 ---------------------借款申请流程---------------------- 3.借款申请流程 4.前端代码逻辑 5.后端代码逻辑 ---------------------借款申请流程---------------------- 核心业务3&…

用SSH登陆Centos系统时,命令行最前面显示“的提示符[root@www myapp]”是什么意思?

用SSH登陆Centos系统时&#xff0c;命令行最前面显示“的提示符[rootwww myapp]”是什么意思&#xff1f; 在SSH登录到CentOS系统时&#xff0c;提示符 [rootwww myapp] 中的 www 表示当前登录的主机名&#xff08;hostname&#xff09;&#xff0c;也就是指当前运行的CentOS系…

基于html+css的盒子展示2

准备项目 项目开发工具 Visual Studio Code 1.44.2 版本: 1.44.2 提交: ff915844119ce9485abfe8aa9076ec76b5300ddd 日期: 2020-04-16T16:36:23.138Z Electron: 7.1.11 Chrome: 78.0.3904.130 Node.js: 12.8.1 V8: 7.8.279.23-electron.0 OS: Windows_NT x64 10.0.19044 项目…

蓝桥杯web备赛——Node.js

node.js之前只能说是略有了解&#xff0c;这次好好了解一下吧&#xff01; 东西还是比较多的。 目前来看就了解比赛会用到的http模块就可以了&#xff0c;其他的暂且不做了解 const http require("http");//1.引入http模块const app http.createServer();//2.创建…