C进阶:自定义类型:结构体、枚举、联合体

news2024/12/29 11:09:50

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

  • 自定义类型:结构体、枚举、联合体
  • 结构体
    • 结构的定义:
    • 结构体的声明:
    • 特殊的声明(匿名)
    • 结构体的自引用:
    • 结构体变量的定义和初始化
    • 结构体内存对齐
    • 为什么要有内存对齐
    • 修改默认对齐数
    • 结构体传参
  • 位段
    • 位段的定义
    • 位段的内存分配
    • 位段的跨平台问题
    • 位段的应用
  • 枚举
    • 枚举类型的定义
    • 枚举的优点
    • 枚举的使用
  • 联合体(共用体)
    • 联合类型的定义
    • 联合的特点
    • 联合大小的计算

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

结构体

结构的定义:

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

我们再回想一下数组是怎么定义的:一些相同类型值的集合。而结构体类型是可以放不同类型的变量的。

结构体的声明:

最简单的声明是这样的:

//例如描述一个学生
struct Stu 
{
	char name[20];
	int age;
	char sex[10];
}s1,s2;//注意最后分号不能省略

但是你可能还见过这样的形式:

typedef struct Stu
{
	char name[20];
	int age;
	char sex[10];
}Stu;

其实这两种写法都正确,区别在于:

image-20230113152021815

并且s1和s2是两个全局变量。

特殊的声明(匿名)

还有一种匿名结构体类型,我们只用一次以后就不需要的话,可以不给它命名,

//匿名结构体类型
struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
}a[20], *p;  

但是需要注意的是如果这时候你写这样一句代码:

p=&x

这样绝对是错误的,因为这是两个完全不同的结构体类型,即使它们成员一模一样,定义一个结构体就是一个单独的类型。

匿名结构体使用还是比较少的,并不常见,了解即可。

结构体的自引用:

结构体的自引用也是一个需要注意的点,先来看错误的写法:

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

这是我们最直观的想法,但其实这样是错误的,为什么呢?我们可以来分析一下:

image-20230113154859432

所以我们正确的结构体自引用方式是通过指针:

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

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

结构体变量的怎么进行定义和初始化呢?

#include <stdio.h>
struct Point
{
	int x;
	int y;
}s1 = {0,0};//第一种
struct Point s2 = { 0,0 };//第二种
int main()
{
	struct Point s3 = { 1,2 };//第三种
	return 0;
}

以上是最简单的方式,如果结构体比较复杂:

cstruct Point
{
	int x;
	int y;
};
struct S
{
	int num;
	char a;
	struct Point b;
	float c;
};
int main()
{
	struct S s4 = { 1,'w',{3,4},3.14f };//按顺序初始化
	struct S s5 = { .a = 'w',.b.x = 5,.b.y = 6,.c = 3.14f,.num = 7 };//乱序
	return 0;
}

结构体内存对齐

结构体内存对齐可以说是结构体这块的重点了,也是考察最多的地方,一般用来计算结构体大小,我们先来看一个例子:

//练习1
struct S1
{
	char c1;
	int i;
	char c2;
};
//练习2
struct S2
{
	char c1;
	char c2;
	int i;
};
int main()
{
	printf("%d\n", (int)sizeof(struct S1));
	printf("%d\n", (int)sizeof(struct S2));
	return 0;
}

输出结果:image-20230113163311018

看起来结构体大小似乎并不像我们想象中的那样简单的每个成员大小之和,然后我们就来看一下到底是什么原因呢?

我们先来看一下结构体内存对齐的规则:

image-20230113164352954

这四句话是一定要加上自己的理解去记忆,我们下面来举例解释,看上述引例:

image-20230113170351767

上面就是我们画图来解释,最直观的就是画图来理解,再次阅读上面结构体内存对齐规则,理解着来进行记忆。

可以看出为了内存对齐我们还是浪费了不少空间的,那么为什么要有内存对齐呢?

为什么要有内存对齐

其实无非就两种原因:

  • 平台原因(移植原因):
    不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
  • 性能原因:
    数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

总的来说:内存对齐就是一种拿空间换时间的做法

在设计结构体的时候,我们既要满足对齐,又要节省空间, 我们最好让占用空间小的成员尽量在一起。

修改默认对齐数

有时候默认对齐数不满足我们的要求的时候我们就需要修改默认对齐数,怎么修改呢,\#pragma 这个预处理指令 就要派上用场了,

修改默认对齐数的方式是这样的:

#pragma pack(num)

num为默认对齐数。

我们也可以验证一下,例如:

#pragma pack(1)
struct S1
{
	char c1;
	int i;
	char c2;
};

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

看结果其实是成功了:image-20230113171838558

结构体传参

其实函数传参无非就传值调用和传址调用,这两种我们怎么选择呢?

//结构体传参
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;
}  

答案是:首选传址调用

原因:函数传参是需要压栈的,那么就必然会有时间和空间上的开销,如果传递结构体过大,就会造成系统性能下降。

位段

位段的定义

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

1.位段的成员必须是 int、unsigned int 或signed int

2.位段的成员后跟一个冒号和数字(数字代表指定占用的空间大小)

struct A
{
	int a : 2;
	int b : 5;
	int c : 10;
	int d : 30;
};
int main()
{
	printf("%d\n", (int)sizeof(struct A));
	return 0;
}

输出:image-20230113175115575

如果没有使用位段结构的话,结构体大小应该是16字节,但是使用位段结构变成了8字节。

因为我们指定了结构体成员占用多少位,有些数据用4或5位就能有效存储数据,没有必要直接占用1个整型空间。这时候使用位段就能够起到节省空间的作用。

位段的内存分配

  • 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
  • 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
  • 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。

一个例子:

位段的内存分配实际上C语言没有标准规定,但是我们可以用一个例子来看一下我们当前vs环境下的内存是怎样分配的。

//一个例子
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;
//空间是如何开辟的?  

image-20230113182257379

这样吗就理解了位段结构的内存分配。

位段的跨平台问题

  • int 位段被当成有符号数还是无符号数是不确定的。
  • 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。
  • 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
  • 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。

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

位段的应用

image-20230113182611165

我们的网络传输用位段还是比较多的,有些部分并不需要那么多的空间即可完成封装,这时候用位段可以很好的节省空间,避免网络传输过程中的“堵塞”问题。

枚举

枚举类型的定义

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

像这样的enum Day,enum Sex等都是一种类型,我们可以用他们来定义变量,{}内部都是枚举常量,枚举常量也是常量,这些常量都是有值的,默认第一个值为0,向下一次递增。

我们也可以进行初始化:

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

枚举的优点

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

  1. 增加代码的可读性和可维护性
  2. 和#define定义的标识符比较枚举有类型检查,更加严谨。
  3. 防止了命名污染(封装)
  4. 便于调试
  5. 使用方便,一次可以定义多个常量

其实前两个优点是比较明显好理解的,其他优点我们只能以后写的多了才能明显体会到。

枚举的使用

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

注意:只能拿枚举常量给枚举变量赋值,才不会出现类型的差异 ,在C语言中检查没有太严格,但是在C++中这种类型不一致你去赋值的话会直接报错。

联合体(共用体)

联合类型的定义

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

//联合体类型声明
union UN
{
	char a;
	int i;
};
//联合体变量定义
union UN un;
//联合体变量大小计算
printf("%d\n", sizeof(un));

联合的特点

联合体的特点是成员共用一块空间,这样的话,联合体大小至少是联合体最大成员的大小。

我们来看一个示例:

union UN
{
	int i;
	char a;
};

int main()
{
	union UN un;
    printf("%d\n",sizeof(un));
	printf("%p\n", &un);
	printf("%p\n", &(un.a));
	printf("%p\n", &(un.i));
	return 0;
}

输出:image-20230113194142095

可以看到三个地址是一样的,并且un的大小为4,这样就验证了a和i确实是共用一块地址的,并且共同占用4个字节。那么实际内存中是怎么存放的呢?我们看下图。

image-20230113194543623

联合大小的计算

可能你会注意到上面联合类型的大小为什么是4呢?难道一定就是最大成员的大小吗?其实不是的,联合体也存在一定的对齐。

image-20230113195036490

举例:

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

输出:image-20230113195300635

根据计算规则计算即可。
以上就是自定义类型:结构体类型、枚举类型、联合体类型的全部内容介绍。

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

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

相关文章

李宏毅ML-自动调整学习速率

自动调整学习速率 文章目录自动调整学习速率1. RMS and Adagrad2. RMSProp and Adam3. Learning rate scheduling3. 总结1. RMS and Adagrad 在下面有两幅图&#xff0c;如第一幅图所示&#xff0c;随着 iteration 的增加&#xff0c;loss 在不断减少&#xff0c;最后趋近于 0…

广告业务系统 之 智能保险丝 —— “智能流控”

文章目录广告业务系统 之 智能保险丝 —— “智能流控”智能流控常规流量调控数据源计算智能流控功能挂载阈值存储架构长短板服务构建及部署广告业务系统 之 智能保险丝 —— “智能流控” 除了 在 AB 环节 设计了出色的 重试机制 —— “ 双发 ” 外&#xff0c;在 ADX 系统的…

【Redis】Redis实现全局唯一ID

【Redis】Redis实现全局唯一ID 为什么要使用Redis实现全局唯一ID去替代传统的数据库自增ID&#xff0c;主要原因如下&#xff1a; 数据库自增ID的规律性太明显受单表数据量的限制&#xff0c;数据量很大时分表会出现ID重复的现象 1. 全局ID生成器 出于以上原因&#xff0c;我…

Vue3——第十三章(插槽 Slots)

一、插槽内容与出口 这里有一个 <FancyButton> 组件&#xff0c;可以像这样使用&#xff1a; 而 <FancyButton> 的模板是这样的&#xff1a; <slot> 元素是一个插槽出口 (slot outlet)&#xff0c;标示了父元素提供的插槽内容 (slot content) 将在哪里被…

excel图表技巧:如何用填充单元格制作比率分析图

在工作中&#xff0c;我们经常要向上级领导汇报某个指标的进度或完成情况。有时候&#xff0c;我们会用仪表盘或温度计图来展示数据。不会这类型图表的朋友&#xff0c;不用担心&#xff0c;因为今天&#xff0c;我将教给大家一种更简单的方法&#xff01;公司新来的职员小明&a…

你是真的“C”——详解C语言函数模块知识(下篇)

详解C语言函数模块知识(下篇&#xff09;&#x1f60e;前言&#x1f64c;1、 函数的嵌套调用和链式访问&#x1f64c;1.1 嵌套调用&#x1f49e;1.2 链式访问&#x1f49e;2、函数的声明和定义&#x1f64c;2.1函数声明&#x1f49e;2.1函数定义&#x1f49e;3、函数递归&#…

【算法题解】9. 邻值查找

文章目录题目解题思路代码实现复杂度分析这是一道中等难度的题。 题目来自&#xff1a;AcWing 题目 给定一个长度为 n 的序列 A&#xff0c;A 中的数各不相同。 对于 A 中的每一个数 Ai&#xff0c;求&#xff1a; min|Ai−Aj|&#xff0c;其中 1 < j < i。 以及令上式…

10行代码带你轻松抓取博客清单

一、前言 今天在网上偶遇一款html解析利器HtmlAgilityPack&#xff0c;免费下载地址&#xff1a;入口。 HtmlAgilityPack是.net下的一个HTML解析类库&#xff0c;支持用XPath来解析HTML。通过该类库&#xff0c;先通过浏览器获取到xpath获取到节点内容然后再通过正则表达式匹…

【operator bool】while(cin >> str)是什么意思?

文章目录一、前言二、cin是什么&#xff1f;三、隐式类型转化如何发生&#xff1f;一、前言 在oj题中&#xff0c;为了实现多行输入&#xff0c;我们经常可以看到这样的写法&#xff1a;while(cin >> str)&#xff0c;这究竟是什么意思呢&#xff1f;为了理解其中的含义&…

c++ 可变参数的三种实现方式

c 可变参数 方法一&#xff1a; C语言的: va_list1 #include <stdio.h> #include <stdarg.h>int add_nums(int count, ...) {int result 0;va_list args;va_start(args, count); // C23 起能省略 countfor (int i 0; i < count; i) {result va_arg(args, i…

bresenham algorithm

#! https://zhuanlan.zhihu.com/p/598780689 bresenham algorithm 全象限区域bresenham algorithm计算的python/c实现 bresenham algorithm为计算机图形学中使用像素点显示直线的算法&#xff0c;算法使用整数运算&#xff0c;能大幅提升计算速度。最近概率栅格建图算法中涉及…

CloudCanal实战-Oracle数据迁移同步到PostgreSQL

简述 本篇文章主要介绍如何使用 CloudCanal 构建一条 Oracle 到 PostgreSQL 的数据同步链路 技术要点 缩小的数据库权限要求 CloudCanal 对 Oracle 数据库的高权限要求&#xff0c;主要来自两个面向 DBA 的操作&#xff0c;自动构建字典和 自动切换归档日志&#xff0c;这两…

详解 strtok 函数以及模拟实现

目录 一、strtok 函数的介绍 二、strtok 函数的模拟实现 一、strtok 函数的介绍 函数原型&#xff1a; char* strtok(char* str, const char* delimiters); delimiter n.[计]分隔符&#xff0c;定界符&#xff08;a character that marks the beginning or end of a unit o…

KMP算法详解+动图演示

目录 一、KMP算法简介 二、KMP算法的详细图解 1. 先了解BF算法的基本思路 2. 简单了解KMP算法 3. next数组的引入 4. next数组的代码实现&#xff08;含动态演示&#xff09; 三、KMP算法完整代码 一、KMP算法简介 KMP算法是一种改进的字符串匹配算法&#xff0c;由 …

【算法】二分图判定

目录1.概述2.代码实现3.应用本文参考&#xff1a; LABULADONG 的算法网站 1.概述 &#xff08;1&#xff09;二分图 (Bipartite Graph)&#xff0c;又称为二部图&#xff0c;是图论中的一种特殊模型。 设 G (V, E) 是一个无向图&#xff0c;如果顶点 V 可分割为两个互不相交的…

Unity 和vs2022对接问题

第一个问题&#xff1a;在vs中编写好的程序在unity中预览出现乱码&#xff1b;提示&#xff1a;只要是乱码的问题90%离不开编码表Unity中的编码表是utf-8,而vs中默认的应该是GB2312。英文还好&#xff0c;中文可定就会出现乱码&#xff0c;解决方法也很简单&#xff1a;把vs中的…

【Python基础四】入门级朋友看的超详教程

前言 这是最后一篇基础的文章啦 往期文章&#xff1a; 【Python基础一】入门级朋友看的超详教程 【Python基础二】入门级朋友看的超详教程 【Python基础三】入门级朋友看的超详教程 刚开始接触Python的宝子&#xff0c;有什么不懂的都可以私信我哦 我还准备了大量的免费…

目标检测:YOLO V2思路解读

目标检测&#xff1a;YOLO V2思路解读YOLO V1存在的问题主要改进Batch NormalizationHigh Resolution ClassifierConvolutional With Anchor BoxesDimension ClusterDirect location PredictionFine-Grained FeaturesMulti-Scale TrainingLoss FunctionYOLO V1存在的问题 对于…

使用Redis代替Session实现短信登陆

1.集群的Session共享问题 多台Tomcat并不共享Session存储空间&#xff0c;当请求切换到不同tomcat服务器时会导致数据丢失&#xff1a; 当用户量增多&#xff0c;我们需要进行负载均衡、对tomcat做水平扩展&#xff0c;可是存储在Tomcat里的Session不是共享的&#xff0c;这…

从C和C++内存管理来谈谈JVM的垃圾回收算法设计-上

从C和C内存管理来谈谈JVM的垃圾回收算法设计-上引言C内存模型malloc堆内存分配过程malloc为什么结合使用brk和mmapmalloc如何通过内存池管理Heap区域垃圾收集器引言 本文想和大家来探讨一下JVM是如何对堆内存进行管理和垃圾回收,相关书籍如深入理解JVM第三版中已经介绍过了相关…