C语言——结构体、联合、枚举

C语言中自定义类型

  • 结构体
    • 结构体变量的创建和初始化
    • 结构体传参
    • 结构体内存对齐(如何存储)
  • 联合体(共用体)
    • 联合体创建和初始化
    • 联合体大小(如何存储)
  • 枚举类型
    • 枚举类型创建
    • 枚举类型初始化
    • 枚举的优点(相较于define)

前言
C语言中有内置类型和自定义类型,内置类型就像int 、double等等,其自带的一些类型,但有时候这些类型满足不了一些特定的要求,所以C语言也提供了一些自定义类型:结构体、联合、枚举


结构体

struct tag
{//结构体成员变量member-list;
}variable-list;//结构体变量名称
//可以在使用时候创建,也可以在创建结构体时创建

例如:创建一个学生的结构体

struct Stu {char name[20];int age;char sex[5];
}; //这里的分号不可以省略

这上面就相当于一个结构体的声明,创建了一个结构体类型,那如何使用呢?

结构体变量的创建和初始化

可以创建的同时初始化

struct Stu {char name[20];int age;char sex[5];
};int main() 
{
//创建结构体类型变量并初始化struct Stu s = { "张三",18,"male" };struct Stu s1 = {"lisi",18,"male"};
}
#include<stdio.h>
struct Stu {char name[20];int age;char sex[5];
};int main() 
{
//创建一个结构体变量并初始化struct Stu s = { "张三",18,"male" };printf("%s\n", s.name);printf("%d\n", s.age);printf("%s\n", s.sex);//可以使用指针来指向这个结构体,来进行访问struct Stu* p = &s;printf("%d\n", (*p).age);printf("%s\n", p->name);return 0;
}
这里使用变量名.结构体成员来访问
如果是一个指针指向一个结构体的话,有两种访问形式
变量名->成员变量
(*变量名).结构体成员

运行结果如下
在这里插入图片描述

结构体传参

结构体传参分为传值调用和传址调用

#include<stdio.h>
struct Stu {int age;char name[20];
};
//传值
void print1(struct Stu p)
{printf("%d\n", p.age);
}
//传址
void print2(struct Stu* p)
{printf("%d\n", (*p).age);
}
int main()
{struct Stu s = { 18,"sansan" };print1(s);print2(&s);return 0;
}

运行结果如下
在这里插入图片描述
传址调用可以修改结构体变量的值,但是传值调用则不可以

#include<stdio.h>
struct Stu {int age;char name[20];
};
//传值
void print1(struct Stu p)
{p.age = 19;
}
//传址
void print2(struct Stu* p)
{(*p).age = 20;
}
int main()
{struct Stu s = { 18,"sansan" };print1(s);printf("调用传值调用后age:%d\n", s.age);print2(&s);printf("调用传址调用后age:%d\n", s.age);return 0;
}

这里我们通过传值调用和传地址调用,发现这里的传地址调用可以修改结构体的变量的值,但是传值调用则不可以
在这里插入图片描述
结构体在进行传参的时候,传递地址比较好,因为如果传值调用的话,形参是实参的一份临时拷贝,如果这个结构体变量非常大,这可能在是将和空间上的开销比较大,所以传地址调用更好一点

结构体内存对齐(如何存储)

上面我们已经对结构体有所了解了,但是这有个新问题,结构体存储的时候是占多少字节呢?这就引出了:结构体内存对齐

对齐规则

1.结构体的第⼀个成员对⻬到和结构体变量起始位置偏移量为0的地址处
2.剩下的成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。
这里的对⻬数=编译器默认的⼀个对⻬数与该成员变量⼤⼩整数倍的较⼩值
-VS 编译器中默认的值为 8
-Linux系统中gcc没有默认对⻬数,对⻬数就是成员⾃⾝的⼤⼩
3.结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的整数倍。
4.如果嵌套了其他结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,这个结构体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍
也就是要考虑结构体变量以及嵌套结构体变量其中的最大对齐数

我们就以下面这两个结构体来举例

#include<stdio.h>
struct S1
{char c1;int i;char c2;
};
struct S2
{char c1;char c2;int i;
};
int main()
{//计算S1与S2分别的字节大小printf("%zd\n", sizeof(struct S1));printf("%zd\n", sizeof(struct S2));return 0;
}

运行结果如下
在这里插入图片描述

从运行结果我们发现一个问题这里的结构体变量的大小,并不是简单的成员变量所占字节数相加,而是经过内存对齐得出的结果
S1 和 S2结构体带下分析如下

在这里插入图片描述


在这里插入图片描述
这里的两个结构体S1大小为12,S2大小8

嵌套类型的结构体总大小计算

#include<stdio.h>
struct S3
{double d;char c;int i;
};//结构体嵌套问题 
struct S4
{char c1;struct S3 s3;double d;
};
int main()
{printf("%zd\n", sizeof(struct S3));printf("%zd\n", sizeof(struct S4));return 0;
}

在这里插入图片描述


在这里插入图片描述
这里我们在VS2022这个编译器下,分析的结果是结构体S3的总大小是16,S4的总大小是32

运行结果如下
和我们推测一样
在这里插入图片描述
为什么要有这种对齐规则呢

1.平台原因(硬件原因)
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,
否则出现硬件异常
2.性能原因
数据结构(尤其是栈)应该尽可能地在⾃然边界上对⻬,原因是这种访问需要访问两次,假设一个编译器只能8个地
地址进行存储,这样我们每次保证存储是8的倍数,这样就方便访问,提高效率
3.我们可以发现上面结构体就是按照对齐规则存储,这是为了快捷访问,相当于牺牲了一部分空间来换取时间

但是我们可以减少浪费
在这里插入图片描述
就像上面的两个结构体S1和S2,虽然成员变量的一样,但位置不同结果也不同,我们可以让占用小的空间尽量集中一起,这样可以节省空间

我们这里的VS2022有自己对齐规则,我们可以修改其 默认的对齐数,来改变其大小
#pragma 这个预处理指令,可以改变编译器的默认对⻬数

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

在这里插入图片描述
我们如果按照VS编译器的话,这里的大小是12,但是我们这里设置默认对齐数为1,也就是每个变量是紧挨着存储,所以这里大小是其相加也就是6

联合体(共用体)

联合体创建和初始化

联合体和结构体类似,都是由多个成员组成,这些成员可以是不同类型
但是与结构体不同的是,联合体是所有成员共用同一块内存,所以联合体也叫做共用体

union tag
{//成员变量member-list;
}variable-list;

创建了一个共用体类型

#include<stdio.h>
union Un 
{char c1;int i;char c2;
};

共用体创建和初始化
以及共用体是所有成员占用一块内存吗?我们以下面这个代码来举例

union Un 
{char c1;int i;char c2;
};
int main()
{union Un un = { 0 };printf("%zd\n", sizeof(un));return 0;
}

运行结果如下
在这里插入图片描述
从结果上可以看出,它并不是连续存储,也不像结构体那样根据对齐原则存储,那他是如何存储的呢
上面的联合体的大小是4个字节,因为这里的最大成员变量是int 占4个字节,并且是最大对齐的数4的整数倍

联合体大小(如何存储)

联合体大小计算

联合的⼤⼩⾄少是最⼤成员的⼤⼩。
当最⼤成员⼤⼩不是最⼤对⻬数的整数倍的时候,就要对⻬到最⼤对⻬数的整数倍

那我们来看下面的代码

#include<stdio.h>
union Un1
{char c[5];int i;
};
union Un2
{short c[7];int i;
};
int main()
{//下⾯输出的结果是什么? //最大的是5,但又要是最大对齐数4的整数倍数,所以这里是8printf("%zd\n", sizeof(union Un1));//最大是14,但又要是最大对齐数4的整数倍,所以结果是16printf("%zd\n", sizeof(union Un2));return 0;
}

运行结果如下
在这里插入图片描述

在这里插入图片描述

他们真的是占用同一块内存吗?

#include<stdio.h>
union Un 
{char c1;int i;char c2;
};
int main()
{union Un un = { 0 };//%p是打印地址的,我们看他们是不是真的存储在同一内存printf("%p\n", &(un.c1));printf("%p\n", &(un.i));printf("%p\n", &(un.c2));return 0;
}

运行结果如下
在这里插入图片描述
从这个结果可以看出,共用体它们所占用的是同一个内存
这里如果修改一个成员数值有成员的结果就会改变

#include<stdio.h>
union Un 
{char c1;int i;char c2;
};
int main()
{union Un un = { 0 };//%p是打印地址的,我们看他们是不是真的存储在同一内存un.i = 200;printf("%d\n", un.c1);printf("%d\n", un.c2);printf("%d\n", un.i);return 0;
}

在这里插入图片描述

在这里插入图片描述
char类型的范围是-128 ~ 127,如果超过范围结果是怎样呢
在这里插入图片描述

虽然它们占用同一块内存,但所占的字节数不同,这也导致结果不一样,就像这里的char只会占1个字节8个比特位,而int占4个字节,32比特位,所以这里的char类型打印的和int类型数值不同

枚举类型

枚举顾名思义就是将其成员一一列举
就像生活中一年有12个月,一周有7天一样可以一一列举

枚举类型创建

enum Day//星期
{Mon,Tues,Wed,Thur,Fri,Sat,Sun
};
enum Color//三颜⾊
{Red,Green,Blue
};

这里的enum Day 和 enum Color都是枚举类型
{ }下都是可能取值,枚举常量
这些枚举常量都是从0开始,依次增加当然我们也可以给它赋值

枚举类型初始化

默认初始化
我们知道枚举常量都是从0开始,我们那上面的Color枚举来举例

#include<stdio.h>
enum Color//三颜⾊
{Red,//默认存储的是0Green,//1Blue//2
};
int main()
{//创建一个c来接收Redenum Color c = Red;printf("%d\n", c);//0printf("%d\n", Red);//0printf("%d\n", Green);//1printf("%d\n",Blue);//2return 0;
}

运行结果如下
在这里插入图片描述

从结果中可以看出,枚举常量是都是从0开始的,依次增加,并且创建的变量也是存储其常量值

为什么我们这里是说这是枚举常量
在这里插入图片描述

这里我们给枚举常量进行修改,发现是不可以的,因为常量值是不可以修改的,枚举常量是常量

就地初始化
全部初始化

#include<stdio.h>
enum Color//三颜⾊
{Red = 1,Green = 3,Blue = 7
};
int main()
{printf("%d\n", Red);printf("%d\n", Green);printf("%d\n",Blue);return 0;
}

这里我们对Color这个枚举中的枚举常量全部都初始化了,但是不可以在枚举类型外部修改
运行结果如下
在这里插入图片描述
那如果我们只是部分初始化

#include<stdio.h>
enum Color//三颜⾊
{Red = 1,Green,Blue
};
int main()
{printf("%d\n", Red);printf("%d\n", Green);printf("%d\n",Blue);return 0;
}

这里原本第一个是从0开始的,但是我们初始化为1,但是依旧满足依次增加的原则,所以结果为
在这里插入图片描述
那我们只修改中间的呢,依然满足上面依次增加的规则,如果初始化的话,按照初始化的值
就像下面这个,我们只初始化了前面两个枚举常量,那第三个常量值就是第二个常量值+1
在这里插入图片描述

枚举的优点(相较于define)

我们可以使⽤ #define 定义常量,为什么⾮要使⽤枚举?
就像上面的代码,我们用#define来写

#include<stdio.h>
#define Red 1
#define Green 2
#define Blue 3
int main()
{printf("%d\n", Red);printf("%d\n", Green);printf("%d\n", Blue);return 0;
}

运行结果如下,可以生成与枚举类型相同的结果
我们可以发现这里的#define每次只能定义一个,而枚举类型一个可以定义多个对象
在这里插入图片描述

枚举的优点
1.可以增强代码的可读性 ,因为define只是简单的代码替换,无法调试观察
2.和#define使用枚举比较严谨
3.方便使用,可以连续创建多个常量,而#define依次只能定义一个

到这里就结束了,希望大家有所帮助,欲知后事如何,请听下回分解

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

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

相关文章

利用pprof对golang进行性能分析

利用pprof进行性能分析 pprof性能分析的5个方面 一、性能分析的五个核心维度 CPU分析 - 剖析程序的CPU使用情况&#xff0c;定位高耗时函数 内存分析 - 追踪内存分配与泄露&#xff0c;优化内存使用模式 IO分析 - 监控文件/网络IO操作&#xff0c;发现瓶颈资源 Goroutine分…

IntelliJ IDEA 调试技巧指南

在日常开发中&#xff0c;调试是不可或缺的一部分。掌握调试工具的使用可以让我们更高效地定位和解决问题。本文将介绍一些在 IntelliJ IDEA 中常用的调试技巧&#xff0c;希望能帮助你在开发过程中更顺畅地解决问题。 1. 方法断点&#xff1a;快速定位实现类 方法断点可以帮…

gitlab 提交pr

在 GitLab 中&#xff0c;提交合并请求&#xff08;Merge Request, MR&#xff09;的大致流程如下&#xff1a; 1. 创建新分支 如果你还没有创建新的功能分支&#xff0c;可以使用以下命令创建并切换到新分支&#xff1a; git checkout -b feature-branch说明&#xff1a;f…

halcon几何测量(二)计算距离和角度的函数

目录 一、计算两条线之间的夹角二、计算一条直线和水平轴之间的夹角三、计算两个轮廓之间的最小距离四、计算两个轮廓之间的最小距离和对应的点五、计算直线和区域之间的最小和最大距离六、计算点到轮廓线之间的距离七、计算点到直线的距离八、计算点到点的距离九、计算点和区域…

【Linux操作系统——学习笔记二】Linux简单导航命令操作

一、前言 学习Linux&#xff0c;本质上是学习在命令行下熟练使用Linux的各类命令。 命令行&#xff1a;是一种通过输入命令和参数与计算机系统进行交互的方式&#xff0c;可以使用各种字符化命令对系统发出操作指令&#xff0c;打开Linux终端&#xff0c;进入命令行界面。 …

新安装的cursor安装不了插件

我安装的cursor版本0.47.5 直接说解决办法 找到安装路径cursor\resources\app下的product.json 修改https://marketplace.cursorapi.com为https://marketplace.visualstudio.com

算法基础篇(蓝桥杯常考点)

算法基础篇 前言 算法内容还有搜索&#xff0c;数据结构&#xff08;进阶&#xff09;&#xff0c;动态规划和图论 数学那个的话大家也知道比较难&#xff0c;放在最后讲 这期包含的内容可以看目录 模拟那个算法的话就是题说什么写什么&#xff0c;就不再分入目录中了 注意事…

《解锁华为黑科技:MindSpore+鸿蒙深度集成奥秘》

在数字化浪潮汹涌澎湃的当下&#xff0c;人工智能与操作系统的融合已成为推动科技发展的核心驱动力。华为作为科技领域的先锋&#xff0c;其AI开发框架MindSpore与鸿蒙系统的深度集成备受瞩目&#xff0c;开启了智能生态的新篇章。 华为MindSpore&#xff1a;AI框架的创新先锋…

双3060、Ubuntu22.04、cuda12.8安装deepseek 32b-Q8

以下是针对双RTX 3060显卡&#xff08;12GB显存&#xff09;在Ubuntu 22.04系统部署DeepSeek-R1-32b-qwen-distill-q8模型的完整流程&#xff0c;结合最新技术规范与魔塔社区资源&#xff1a; 一、驱动与CUDA环境配置 1. 禁用开源驱动 bash sudo tee /etc/modprobe.d/blackli…

K8S学习之基础三十四:K8S之监控Prometheus部署pod版

使用 Kubernetes Pod 的方式部署 Prometheus 是一种常见的方法&#xff0c;尤其是在容器化和微服务架构中。以下是详细的步骤&#xff1a; 1. 创建命名空间&#xff08;可选&#xff09; 为了方便管理&#xff0c;可以为 Prometheus 创建一个单独的命名空间。 yaml 复制 a…

Linux top 命令详解:从入门到高级用法

Linux top 命令详解&#xff1a;从入门到高级用法 在 Linux 系统中&#xff0c;top 是一个强大的实时监控工具&#xff0c;用于查看系统资源使用情况和进程状态。它可以帮助你快速了解 CPU、内存、负载等信息&#xff0c;是系统管理员和开发者的日常利器。本文将从基本用法开始…

uniapp-x vue 特性

生命周期 在组合式API中&#xff0c;组件可以监听应用和页面的生命周期。但由于应用和页面都有onShow和onHide&#xff0c;导致重名。所以在组合式的组件中监听页面的显示隐藏&#xff0c;改为了onPageShow和onPageHide。 这个和uniapp不一样&#xff0c;uniapp自定义组件无法…

HTML5扫雷游戏开发实战

HTML5扫雷游戏开发实战 这里写目录标题 HTML5扫雷游戏开发实战项目介绍技术栈项目架构1. 游戏界面设计2. 核心类设计 核心功能实现1. 游戏初始化2. 地雷布置算法3. 数字计算逻辑4. 扫雷功能实现 性能优化1. DOM操作优化2. 算法优化 项目亮点技术难点突破1. 首次点击保护2. 连锁…

Qt之自定义界面组件 一

通过qt中的painter绘图事件绘制一个电池电量图的变化。效果如下图 创建一个基于界面widget工程&#xff0c;在wdiget界面添加一个widget界面,将添加的widget界面的类提升为Tbattery.在Tbattery类中重写painEvent电池电量代码 文件目录结构 主要部分代码 //Tbattery.cpp #inc…

LeRobot源码剖析——对机器人各个动作策略的统一封装:包含ALOHA ACT、Diffusion Policy、VLA模型π0

前言 过去2年多的深入超过此前7年&#xff0c;全靠夜以继日的勤奋&#xff0c;一天当两天用&#xff0c;抠论文 抠代码 和大模型及具身同事讨论&#xff0c;是目前日常 而具身库里&#xff0c;idp3、π0、lerobot值得反复研究&#xff0c;故&#xff0c;近期我一直在抠π0及l…

数据结构篇——线索二叉树

一、引入 遍历二叉树是按一定规则将二叉树结点排成线性序列&#xff0c;得到先序、中序或后序序列&#xff0c;本质是对非线性结构线性化&#xff0c;使结点&#xff08;除首尾&#xff09;在线性序列中有唯一前驱和后继&#xff1b;但以二叉链表作存储结构时&#xff0c;只能获…

汽车保养记录用什么软件记录,汽车维修记录查询系统,佳易王汽车保养维护服务记录查询管理系统操作教程

一、概述 本实例以佳易王汽车保养维护服务记录查询管理系统为例说明&#xff0c;其他版本可参考本实例。试用版软件资源可到文章最后了解&#xff0c;下载的文件为压缩包文件&#xff0c;请使用免费版的解压工具解压即可试用。 软件特点&#xff1a;1、功能实用&#xff0c;操…

Sqlmap注入工具简单解释

安装 1. 安装 Python SQLMap 是基于 Python 开发的&#xff0c;所以要先安装 Python 环境。建议安装 Python 3.9 或更高版本&#xff0c;可从 Python 官方网站 下载对应操作系统的安装包&#xff0c;然后按照安装向导完成安装。 2. 获取 SQLMap 可以从 SQLMap 的官方 GitHu…

LLM自动化评测

使用的数据集&#xff1a;ceval-exam import requests from datasets import load_dataset, concatenate_datasets import re from tqdm import tqdm import re, time, tiktoken, ollama from ollama import ChatResponse from ollama import Optionsdef llm(model, query, te…

Python IP解析器 ip2region使用

说明&#xff1a;最近需要在python项目内使用IP定位所在城市的需求&#xff0c;没有采用向外部ISP服务商API请求获取信息的方案&#xff0c;则翻了翻&#xff0c;在搞Java时很多的方案&#xff0c;在Python端反而可选择范围很小。 # 示例查询 ips ["106.38.188.214"…