当前位置:网站首页>[C语言]自定义类型(结构体~枚举~联合体)
[C语言]自定义类型(结构体~枚举~联合体)
2022-07-19 19:01:00 【小蜗牛向冲】
前言
作者:小蜗牛向前冲
名言:我可以接收失败,但我不能接收放弃
如果觉的博主的文章还不错的话,还请点赞,收藏,关注支持博主。如果发现有问题的地方欢迎*大家在评论区指正。
目录
在C语言中常见的数据类型:
整形:int short long long long char(字符类型)
浮点型 flaot double
这些类型是C语言都帮我们定义好的,下面我们将继续学习自定义类型。
自定义类型
简单的来说就是不由系统定义,而是由程序设计者自己定义的类型,在本篇博主中,我们重点分享结构体,位段,枚举,联合相关知识。
结构体
理解
结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
结构体的声明
是借助关键字struct来进行的。
struct关键字是用来定义一个新的类型,这个新类型里面可以包含各种其他类型,称为结构体。结构体 (struct)是一种自定义的数据类型,就是把一组需要在一起使用的数据元素组合成一个新的类型。
1形式
struct tag
{
member-list;
}
variable-list;
tag:是结构体的标签
member-list:是结构体的成员
variable-list:这是给结构体取的变量名
注意:
定义结构体时成员是不用初始化的。
结构体的变量名可以在定义是就声明,也可以在需要的时候定义。
代码演示:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
struct studen
{
char* name;//姓名
int num;//学号
int age;//年龄
};
这样我们就简单的声明来一个结构体,结构体的标签为studen,我们可以看到这里并没有给结构体取变量名。
2 特殊声明(匿名结构体)
就是指在声明结构体的时候,不写结构体的标签。因为没有结构体的标签,所以只能用一次。
#include<stdio.h>
//匿名结构体类型
struct
{
int a;
float b;
char c;
}s1;
struct
{
int a;
float b;
char c;
}a[20],*p;
int main()
{
p = &a;//?
}
我们可以看的到什么的代码,这样是可以行的吗?我们编译起来
发现出现一个警告,为什么呢?这二个结构体的成员不都是一样的吗?他们不相同吗?
在上面我也说过匿名结构体只能用一次,所以说匿名结构体即使成员相同,但由于是匿名的,只能使用一次,二者结构体是不相同的,我们应该慎重使用匿名结构体(可以在只使用一次的场景使用)。
3结构体的自引用
在结构中包含一个类型为该结构本身的成员是否可以呢?
struct data
{
int a;
struct data* next;
};
其实是可以的,但我们不能直接存储结构体本身,而要存放结构体的地址。
为什么呢?
其实如果存放结构体本身,因为要不断自引用结构体,这使得结构体的大小就不确定了,而且这个结构体的大小将会非常大,无法存储。所有我们存放结构体的地址就这样就使得结构体的大小确定(指针的大小是确定的),我们还可以通过结构体的地址找到结构体的成员,这样链表就得以实现。
4 结构体变量的定义和初始化
结构体变量的初始化我把他归类为3类:
#include<stdio.h>
//类型1
struct data
{
int a;
int b;
int c;
}p1;//定义结构体的时候,定义变量名p1
//类型2
struct studen
{
struct data;
const char* name;
int age;
};
//类型3
struct age
{
int year;
int month;
int day;
}p3;
void print(struct age* p3)
{
p3->year = 1949;
p3->month = 10;
p3->day = 1;
printf("year = %d month = %d day = %d\n", p3->year, p3->month, p3->day);
}
int main()
{
struct data p1 = { 1,2,3 };//赋值
printf("%d %d %d\n", p1.a, p1.b, p1.c);
struct studen p2 = { 4,5,6,"zhangshan",18 };
printf("%d %d %d\tname = %s age = %d\n", p2.a, p2.b, p2.c, p2.name, p2.age);
//struct age p3 = { 1949,10,1 };
print(&p3);
return 0;
}
类型1
在struct data结构体定义的时候为结构体定义了名字p1,初始化直接用大括号初始就可以了。
类型2
在struct studen结构体定义的时候并没有为结构体取名字,在初始化的在取名为p2,要是可以的,值得说明的是在结构体在包含结构体初始化也是没说明区别的。
类型3
其实这个类型3和其他类型的区别主要是,他在初始化的时候是在函数中,我们为函数传递了结构体strcut age的地址,所有要初始化结构体要用" -> "。
我们知道结构体是怎么定义和初始化的,那么我们又是如何访问结构体中的成员的呢?
结构体变量是指针用:" -> "访问。
结构体变量非是指针用:" . "访问。
5 结构体内存对齐
上面我们提到结构体自引用时,传的是指针,而不是结构体本身是为防止结构体大小过大。那么结构体的大小又是如何计算的呢?
#include<stdio.h>
//1
struct studen
{
const char* name;
int age;
}p1;
//2
struct data
{
int a;
double b;
char c;
}p2;
//3
struct datas
{
char c;
int a;
double b;
}p3;
int main()
{
printf("%u %u %u\n", sizeof(p1), sizeof(p2), sizeof(p3));
}
下面这些结构体的大小是多少呢?
是把结构体成员的大小都加起来吗?
结构体1
1+4 =5?
结构体2和结构体3的成员相同
1+4+8=13?
是上面这么结果对面,下面我们打印常出来看看。
8 24 16 为什么啊,这就不得不提结构体大小的计算方法结构体内存对齐。
结构体内存对齐的规则:
1. 第一个成员在与结构体变量偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 VS中默认的值为8
3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
下面我们将画图来理解为什么结构体1,2,3的大小是8,24,16。
图1
图2
图3
不知道大家观察到没,p2和p3的成员是一样的就是定义的顺序不同,但分配的内存空间大小是不一样的,而且字节大小越小的成员先定义,那么分配到的空间就更少,浪费的内存空间也跟小。
总结
总体来说:
结构体的内存对齐是拿空间来换取时间的做法。
那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:
让占用空间小的成员尽量集中在一起。
为什么要存在内存对齐呢?
1. 平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特 定类型的数据,否则抛出硬件异常。
2. 性能原因: 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
上面我们提到Vs是有默认对齐数(8)的,由于对齐数影响结构体在内存在的分配,有时候我们为了控制分给结构体的内存需要更改默认对齐数,可以通过#pragma 这个预处理指令,这里我们再次使用,可以改变我们的默认对齐数。
#pragma pack(1)//设置默认对齐数为1
struct day
{
int a;
int b;
int c;
};
#pragma pack();//恢复默认对齐为8
6结构体传参
struct s
{
int data[10];
int num;
}p1;
//传值
void print1(struct s p)
{
printf("%d\n", p.num);
}
//传地址
void print2(struct s* p)
{
printf("%d\n", p->num);
}
int main()
{
struct s p1 = {
{1,2,3,4,5},10};
print1(p1);//传值
print2(&p1);//传地址
}
对于上面二种传参方式,我们选择那种呢?
肯定是print2
为什么呢?
原因:
函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。 如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。
结论:
结构体传参的时候,要传结构体的地址。
位段
位段又是啥子呢?下面我们先了解位段的声明
位段的声明和结构是类似的,有两个不同:
1.位段的成员必须是 int、unsigned int 或signed int或者char 。
2.位段的成员名后边有一个冒号和一个数字。
1 声明
struct data
{
char a : 2;
int b : 2;
long long c : 4;
};
int main()
{
printf("%u\n", sizeof(struct data));
}
data就是一个位段,那么的占几个字节呢?
从中可以看出位段也是遵循内存对齐规则的。
2 位段的内存分配
1. 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
下面我们继续研究一下位段的内存分配。
struct s
{
char a : 2;
char b : 4;
char c : 5;
};
int main()
{
struct s p1 = { 0 };
p1.a = 2;
p1.b = 4;
p1.c = 10;
return 0;
}
从图中我们可以看出,位段其实就是可以自己分配内存给自己使用。其中" : "后面的数字是分配的给内存几个bit位用来存储成员。(vs的分配方式)
3位段的跨平台问题
1. int 位段被当成有符号数还是无符号数是不确定的。
2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。
3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是 舍弃剩余的位还是利用,这是不确定的。
4 位段的应用
主要运用于数据传输中。
枚举
枚举顾名思义就是一一列举。
把可能的取值一一列举。
1枚举的定义
enum peo//个人信息
{
name,
sex,
Stature,
age
};
以上定义的 enum peo是枚举类型。 {}中的内容是枚举类型的可能取值,也叫枚举常量 。 这些可能取值都是有值的,默认从0开始,一次递增1,当然在定义的时候也可以赋初值。
2枚举的优点
为什么使用枚举?
我们可以使用 #define 定义常量,为什么非要使用枚举? 枚举的优点:
1. 增加代码的可读性和可维护性
2. 和#define定义的标识符比较枚举有类型检查,更加严谨。
3. 防止了命名污染(封装)
4. 便于调试
5. 使用方便,一次可以定义多个常量
对于枚举很多人可能觉的没什么用,其实不然,枚举还是有许多优点的,我们在以后的编译时遇到一定要细细体会。
3 枚举的使用
enum peo//个人信息
{
name,
sex,
Stature,
age
};
//使用
enum peo mam = name;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。
枚举的使用场景还需大家在合适的场景使用。
联合(共用体)
1 联合类型的定义
联合也是一种特殊的自定义类型 这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)。
比如:
//联合体
//声明
union s
{
char a;
int b;
};
int main()
{
//联合体变量的定义
union s un;
//计算联合体的大小
printf("%d\n", sizeof(un));
}
2 联合的特点
联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联 合至少得有能力保存最大的那个成员)。
我们思考一个问题?
&(un.a)和&(un.b)得到的地址是一样的吗,可以肯定是一样的,为什么怎么说呢?因为二者公用一个内存的话,二者指针指向的位置必须是一样的这样才能找到相应的空间。
3联合大小的计算
联合的大小至少是最大成员的大小。
当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍
下面看到思考题,来结束今天的分享。
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;
}
这里就不过多解释了。
边栏推荐
- 4. Figure network classification
- Samsung exynos 9820 core photo exposure: large area increase under 8nm process, integrated dual core NPU!
- STM32开发笔记119:使能FPU需要哪些宏?
- Programming examples of stm32f1 and stm32subeide -bh1750 ambient light intensity sensor drive
- 软测总监:“ 工作五年你连服务端接口测试还不知道?“
- Programming examples of stm32f1 and stm32subeide -hmc5883 electronic compass drive
- TCP related knowledge and interview site details
- CAD框选对象的两种方式、AUTOCAD——删除重复线段
- Open a window and pop up a tool with favorite links
- 微信小程序中image组件用作背景图片时
猜你喜欢
2. Figure machine learning graph embedding
3、Graph Neural Network
The last technical problem baffles me: how to avoid the risk of production environment performance testing?
数据分析小案例:招聘数据可视化,查看领域最需技术~
3、Graph Neural Network
Minitouch click principle
Leetcode55. 跳跃游戏
"Xiaodeng in operation and maintenance" searches log data as network security intelligence
LLVM pass pwn 入门 (3)
60 AI pharmaceutical enterprises on the map of China
随机推荐
【无标题】
Two methods of selecting objects in CAD frame, AutoCAD -- deleting duplicate line segments
Google hardware business layoffs, involving Tablet PC and other projects
【直播回顾】AI客服“应势而变”,人机对话可以更轻松
Pure domestic! Ziguang SSD starts batch shipment!
【TS】Class
揭开,字节跳动全链路压测的实践之路
Programming examples of stm32f1 and stm32subeide -bmp280 pneumatic temperature sensor drive
iNFTnews | 音乐在Web3中的未来
1.84 亿元、数据库大单
minitouch点击原理
AUTOCAD——自定义特殊线型
Array, string, object related methods and Boolean judgment
2022-7-15总结
3、3D点云基础入门——pointnet
iNFTnews | 拥有99年历史的《TIME》正引领传统媒体进军NFT
6041 万、阿里云中标:北方健康《北方中心2022年云服务项目(服务器、安全设备)》
「运维有小邓」搜索日志数据以作为网络安全情报
【AUTOSAR-RTE】-1-基于Queue队列的Sender-Receiver communication
STM32开发笔记118:在STM32CubeIDE中使用CMSIS DSP库