[C语言]自定义类型详解:结构体、联合体、枚举

目录

🚀结构体

🔥结构体类型的声明

🔥结构的自引用

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

🔥结构体内存对齐

🔥结构体传参

🔥结构体实现位段(位段的填充&可移植性)

🚀枚举

🔥枚举类型的定义

🔥枚举的优点

🔥枚举的使用

🚀联合(共用体)

🔥联合联合类型的定义

🔥联合的特点

🔥联合大小的计算



🚀结构体

🔥结构体类型的声明

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

结构的声明:

struct Stu
{
    char name[20];//姓名
    int age;//年龄
    char sex[5];//性别
    char id[20];//学号
};//分号不能省略

//struct——>结构体关键字;Stu——>结构体标签这里表示的是学生属性

特殊声明:

//匿名结构体类型
struct
{
    int a;
    char b;
}x;//匿名结构体类型只能使用一次
struct
{
    int a;
    char b;
}a[20],* p;//p指向的是这个结构体指针的地址
int main()
{
    p = &x;
    return 0;
} //编译器会把上面两个声明当成两个完全不同的类型

🔥结构的自引用

在结构体中包含一个类型为该结构本身的成员

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

这种类型的结构自引用是非法的,成员next中又会包含一个struct Node的结构,如此递归下去永无止境。在计算sizeof(struct Node)时无法求出。合法声明:

struct Node
{
    int data;
    struct Node* next;
};//也就是说线性结构中链表,每个节点包括了这个节点的数据和指向下一节点的地址的指针的信息。即数据域和指针域。

 当使用typedef类型定义和自引用时要注意下面这种陷阱:

typedef struct
{
    int data;
    Node* next
}Node;//匿名结构体类型,typedef重定义一个名字Node

这种写法是非法的,因为类型名知道整个定义结束才遇到,在结构体内部的Node是未定义的

//解决方案:

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

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

struct Point
{
    int x;
    int y;
}s1;         //声明类型的同时定义变量s1
struct Point s2;//定义结构体变量s2

//结构体嵌套初始化

struct Score
{
    int n;
    char ch
};
struct Stu
{
    char name[20];
    int age;
    struct Score s
};
int main()
{
    struct Stu s1 = { "zhangsan",20,{20,'q'} };

🔥结构体内存对齐

如何来计算结构体的大小

#include<stdio.h>
struct S1
{
	char a;
	int i;
	char b;
};
struct S2
{
	char a;
	char b;
	int i;
};
int main()
{
	printf("%d\n", sizeof(struct S1));
	printf("%d\n", sizeof(struct S2));
	return 0;
}

明明只是调换了一下位置,为什么所占字节大小会不同呢?

结构体的对齐规则:

1、第一个成员在与结构体变量偏移量为0的地址处

2、其他成员变量要对齐到某个数字(对其数)的整数倍的地址处。

      对其数=编译器默认的一个对其数与该成员大小的较小值。

           vs中默认的值是8

3、结构体总体大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。

4、如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。 

为什么会存在结构体内存?

1、平台原因:

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

2、性能原因:

数据结构(尤其是栈)应尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要做两次内存访问;而对齐的内存访问仅需要一次访问。

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

设计结构体的时候,我们既要满足对齐又要满足节省空间,尽量把小的类型集中在前面,从而减少空间的浪费

修改默认对其数

#pragma pack(8)//设置默认对齐数为8
struct S1
{
    char a;
    int i;
    char b;
};
#pragma pack()//取消设置的默认对齐数,还原为默认

#pragma pack(1)//设置默认对齐数为1
struct S2
{
    char a;
    int i;
    char b;  
};

建议不要随意修改,当我们不去追求效率,而是追求空间浪费最少时可以考虑修改默认对齐数。

🔥结构体传参

#include<stdio.h>
struct S
{
	int data[100];
	int num;
};
void print1(struct S ss)
{
	int i = 0;
	for (i = 0; i < 2; i++)
	{
		printf("%d ", ss.data[i]);
	}
	printf("%d", ss.num);
}
void print2(struct S* ss)
{
	int i = 0;
	for (i = 0; i < 2; i++)
	{
		printf("%d ", ss->data[i]);
	}
	printf("%d", ss->num);
}
int main()
{
	struct S s = { {1,2},100 };
	print1(s);//传值调用
	print2(&s);//传址调用
	return 0;
}

 在进行结构体传参时我们传地址是更好的,这是因为函数传参的时候,参数是需要压栈的,会有时间和空间上的系统开销。如果传递一个结构体对象的时候,结构以过大,参数压栈的系统开销比较大,会导致性能的下降。

🔥结构体实现位段(位段的填充&可移植性)

位段(位指的是比特位)的声明和结构是类似的,有两个不同:

1、位段的成员只能是整型:int、unsigned int,signed int或者char类型的

2、位段的成员后面有一个冒号和数字

#include<stdio.h>
struct A
{
	int a : 2;
	int b : 5;
	int c : 10;
	int d : 30;
};

位段的内存分配

1、位段成员可以是int 、unsigned int、signed int或者char类型

2、位段的空间上是按照需要以4(int)个字节或 1(char)个字节的方式来开辟的

3、位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应避免使用位段

//举个栗子:

#include<stdio.h>

struct S
{
    //先开辟一个字节的空间,8个比特位
    char a : 3;
    //用了3个还剩5个比特位
    char b : 4;
    //用了4个还剩1个比特位
    char c : 5;
    //不够用了,再开辟一个字节,8个比特位,用了5个,剩下3个
    char d : 4;
    //又不够用了,再开辟一个字节,8个比特位用了4个,剩下4个
};
int main()
{
    struct S s = { 0 };
    printf("%d\n", sizeof(struct S));//3
    s.a = 10;
    s.b = 12;
    s.c = 3;
    s.d = 4;
    return 0;
}

 

 调试一走,我们发现vs的规则和我们计算的是没有差别的

但我们在使用位段的时候会有很多问题:

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

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

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

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

总结:与结构相比,位段可以达到同样效果,但是可以很好的节省空间,但是有跨平台的问题存在。 当然位段在计算机网络中有其独有的作用,能节省不少空间浪费(数据越少,状态越好),从而达到网络环境较优的状态

🚀枚举

枚举顾名思义就是一一列举,比如:一年12个月可以一一列举、一周7天可以一一列举。

🔥枚举类型的定义

enum Day//星期
{
    Mon,
    Tues,
    Wed
    Thur,
    Fri,
    Sat,
    Sun
};

enum Sex//性别
{
    MALE,
    FEMALE,
    SECRET
};//与结构体是非常相似的,但其内部是用逗号分隔开的,且内部只包含符号

{ }里面的内容是枚举类型的可能取值,也叫枚举常量。

这些可能取值都是有值的,默认从0开始,一次递增一,当然在定义的时候也可以赋初值。

 enum Color//颜色
{
    RED = 1,
    GREEN=2,
    BLUE=3
};

🔥枚举的优点

我们可以使用 #define 定义常量,为什么要用枚举呢?

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

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

3、防止命名污染(封装)

4、便于调试

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

🔥枚举的使用

enum Color//颜色
{
	RED = 1,
	GREEN=2,
	BLUE=4
};
int main()
{
	enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异
	clr = 5;
	return 0;
}

🚀联合(共用体)

🔥联合联合类型的定义

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

//举例:

union Un
{
    int a;
    char c;
};
int main()
{
    union Un u;
    printf("%d\n", sizeof(u));//4,说明共用了一份空间
    return 0;
}

 从这里我们就能看出来a与c共用一份空间

🔥联合的特点

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

union Un
{
	int a;
	char c;
};
int main()
{
	union Un u;
	u.a = 0x11223344;
	u.c = 0x55;
	printf("%x\n", u.a);
	return 0;
}

 判断机器是大端存储还是小端存储(用联合的方法)

int check_sys()
{
	union Un
	{
		int a;
		char b;
	}u;
	u.a = 1;
	return u.b;
}
int main()
{
	int ret = check_sys();
	if (ret == 1)
	{
		printf("小端\n");
	}
	else
		printf("大端\n");
	return 0;
}

🔥联合大小的计算

·联合的大小至少是最大成员的大小。

·当最大成员大小不是最大对齐数的整数倍时,就要对齐到最大对齐数的整数倍。

union Un1
{
    char c[5];
    int i;
};//本来应该是5,最大对齐数为4,所以大小为4的倍数即8

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

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

相关文章

企业异地网络组网:SD-WAN解决方案的优势

在当今全球化的商业环境中&#xff0c;企业的业务扩展已不再局限于本地或单一国家。随着分公司和子公司的不断增加&#xff0c;企业总部与这些分支机构之间的数据通信和资源共享变得尤为重要。然而&#xff0c;传统的网络访问方式&#xff0c;如点对点电路和多协议标签交换&…

17.7K星开源产品分析平台:Posthog

Posthog&#xff1a;开源洞察&#xff0c;产品优化的得力助手 - 精选真开源&#xff0c;释放新价值。 概览 PostHog是一个全面开源的平台&#xff0c;旨在帮助团队构建更好的产品。它提供了从产品分析到会话回放、功能标志和A/B测试等一系列工具&#xff0c;支持自托管&#x…

个人博客网站搭建笔记1

文章目录 前言要求自己的理解资源过程视频教程SpringBoot开发一个小而美的个人博客p1课程介绍p2需求和功能 前言 自己之前其实就想搭建一个属于自己的网站&#xff0c;但是不知道怎么操作&#xff0c;没找到合适的教程&#xff0c;&#xff08;手把手的那种&#xff09;&#…

Prometheus+Grafana监控服务器、mysql数据库并配置报警规则推送邮箱

文章目录 一、安装prometheus1.1下载1.2 安装1.3 开机启动1.4 验证 二、安装 Grafana2.1 下载2.2 安装2.3 启动2.4 验证 三、安装服务器监控 node_exporter3.1 下载3.2 安装3.3 设置 node_exporter 系统服务3.4 设置开机自动启动3.5 验证3.6配置Prometheus3.7 修改 Prometheus …

Tomcat部署项目的方式

目录 1、Tomcat发布项目的方式 方式1&#xff1a; 直接把项目发布到webapps目录下 方式2&#xff1a;项目发布到ROOT目录 方式3&#xff1a;虚拟路径方式发布项目 方式4&#xff1a;(推荐)虚拟路径&#xff0c;另外的方式&#xff01; 方式5&#xff1a;发布多个网站 1、…

[windows系统安装/重装系统][step-4][番外篇-2]N卡驱动重装 |解决:开机几小时后电脑卡顿 | 后台自动运行了上千个Rundll32进程问题

现象 开机几小时后&#xff0c;电脑变卡&#xff0c;打开后台管理器都卡&#xff0c;后台管理去转圈圈一小会儿后看到后台进程上千个&#xff0c;好多个Rundll32进程 重启下运行会稍快 重启后运行快&#xff0c;后台管理器反应也快 打开后台管理器不卡&#xff08;几小时后打…

Python语法(全)

前言&#xff1a; 下面是Python基本的语法&#xff0c;大家耐心观看&#xff01; 1.基础语法 1.1字面量 字面量&#xff1a;在代码中&#xff0c;被写下来的的固定的值&#xff0c;称之为字面 1.2字符串 字符串&#xff08;string&#xff09;&#xff0c;又称文本&#xff…

C语言数据结构栈的概念及结构、栈的实现、栈的初始化、销毁栈、入栈、出栈、检查是否为空、获取栈顶元素、获取有效元素个数等的介绍

文章目录 前言栈的概念及结构栈的实现一、 栈结构创建二、 初始化结构三、销毁栈四、入栈五、出栈六、检查是否为空七、获取栈顶元素八、获取有效元素的个数九、测试 1十、测试 2总结 前言 C语言数据结构栈的概念及结构、栈的实现、栈的初始化、销毁栈、入栈、出栈、检查是否为…

blender 布尔运算,切割模型。

1.创建一个立方体和球体。 2.选中立方体&#xff0c;在属性面板添加布尔修改器。点击物体属性右边的按钮选中球体。参数如下。 3.此时隐藏球体&#xff0c;就可以看到被切掉的效果了。

C结构详解

目录 1、结构模板 1. 建立结构声明 2. 定义结构变量 3. 访问结构成员 4. 初始化结构 声明结构数组 声明和初始化结构指针 1、结构模板 1. 建立结构声明 struct book{char title[MAXTITL];char author[MAXAUTL];float value; }&#xff1b; 该声明描述了一个又两个字符…

【漫画算法】哈希表:古代皇帝的秘密魔法书

❤️❤️❤️ 欢迎来到我的博客。希望您能在这里找到既有价值又有趣的内容&#xff0c;和我一起探索、学习和成长。欢迎评论区畅所欲言、享受知识的乐趣&#xff01; 推荐&#xff1a;数据分析螺丝钉的首页 格物致知 终身学习 期待您的关注 导航&#xff1a; LeetCode解锁100…

三个有意思的链表面试题的完成

上一篇博客我们已经完成了链表的所有内容&#xff0c;那么这一篇博客我们来看一下三个特别有意思的链表题目。 **第一个题目如下&#xff1a;**相信不少朋友看到这题目就已经晕了&#xff0c;那就简单说明下这个题目&#xff0c;题目就是创建一个链表&#xff0c;其中每个节点…

软件构造复习1

一、软件构造的多维度视图&#xff1a; 共有三个维度&#xff1a;1.按阶段划分&#xff1a;构造时/运行时视图&#xff0c;2.按动态性划分&#xff1a;时刻/阶段视图&#xff0c;3.按构造对象层次划分&#xff1a;代码/构件视图 具体可如图所示&#xff08;图片来自PPT&#…

数据库-SQL性能分析

SQL执行频率 慢查询日志 慢查询日志记录了所有执行时间超过指定参数&#xff08;long_query_time&#xff0c;单位&#xff1a;秒&#xff0c;默认10秒&#xff09;的所有 SQL语句的日志。 MySQL的慢查询日志默认没有开启&#xff0c;我们可以查看一下系统变量 slow_query_l…

掩码生成蒸馏——知识蒸馏

摘要 https://arxiv.org/pdf/2205.01529 知识蒸馏已成功应用于各种任务。当前的蒸馏算法通常通过模仿教师的输出来提高学生的性能。本文表明&#xff0c;教师还可以通过指导学生的特征恢复来提高学生的表示能力。从这一观点出发&#xff0c;我们提出了掩码生成蒸馏&#xff08…

Redis常见基本类型(5)-List, Set

List 命令小结 命令及解释时间复杂度lpush/rpush key value[key value...](向右/左端插入元素)O(k), k是元素个数linsert key before | after pivot value(在某个坐标之前/右插入元素)O(n), n是pivot距离头尾的距离lrange start end(获取从start到end部分的元素)O(s n): s是…

与用户沟通获取需求的方法

1 访谈 访谈是最早开始使用的获取用户需求的技术&#xff0c;也是迄今为止仍然广泛使用的需求分析技术。 访谈有两种基本形式&#xff0c;分别是正式的和非正式的访谈。正式访谈时&#xff0c;系统分析员将提出一些事先准备好的具体问题&#xff0c;例如&#xff0…

Java使用apache.poi生成excel插入word中

加油&#xff0c;新时代打工人&#xff01; 工作需求&#xff0c;上个文章我们生成好的word&#xff0c;这次将生成好的excel表格数据&#xff0c;插入word中。需要准备好excle数据&#xff0c;然后插入到word中。 最后个需要&#xff0c;就是把这些生成好的word文档转成pdf进行…

基础技术-ELF系列(1)-ELF文件基础

成就更好的自己 本篇是基础技术系列中ELF相关技术的首篇文章。 尽管网上有许多关于ELF相关内容的文章&#xff0c;但总体而言&#xff0c;要么是一些非常基础且重复性强的内容&#xff0c;要么直接深入探讨相对高深的主题&#xff0c;缺乏系统化分析和解释。 接下来&#xf…

C++技能进阶指南——多态语法剖析

前言&#xff1a;多态是面向对象的三大特性之一。顾名思义&#xff0c; 多态就是多种状态。 那么是什么的多种状态呢&#xff1f; 这里的可能有很多。比如我们去买火车票&#xff0c; 有普通票&#xff0c; 学生票&#xff1b; 又比如我们去旅游&#xff0c; 有儿童票&#xff…