全网最细的自定义类型详解(结构体,枚举,联合),友友们快来接收吧

本文主要是介绍全网最细的自定义类型详解(结构体,枚举,联合),友友们快来接收吧,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

各位csdn的友友们肯定都掌握了c语言中char,short, int, long, float, double的类型,这些都是我们c语言中的一些内置类型,其实c语言是可以允许我们创造一些类型的,今天阿博就带领友友们一起掌握这些新的自定义类型😊😊😊

文章目录

  • 结构体
    • 1.结构体类型的声明
    • 2.结构的自引用
    • 3.结构体变量的定义和初始化
    • 4.结构体内存对齐
    • 5.结构体传参
    • 6.结构体实现位段(位段的填充&可移植性)
  • 枚举
    • 1.枚举类型的定义
    • 2.枚举的优点
    • 3.枚举的使用
  • 联合
    • 1.联合类型的定义
    • 2.联合的特点
    • 3.联合大小的计算

结构体

1.结构体类型的声明

2.结构的自引用

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

4.结构体内存对齐

5.结构体传参

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

枚举

1.枚举类型的定义

2.枚举的优点

3.枚举的使用

联合

1.联合类型的定义

2.联合的特点

3.联合大小的计算

结构体类型的声明

struct stu
{char name[20];  //名字int  age;       //年龄char sex[5];    //性别char id[20];    //学号
};

注意最后那个分号千万不能丢哦!!!

这里的struct stu就是定义一个学生类型这里的name,age,sex,id就是成员变量,好了当我们了解这些后,就可以试着用结构体类型来创建变量了

struct stu
{char name[20];  //名字int  age;       //年龄char sex[5];    //性别char id[20];    //学号
}s4,s5,s6; 
int  main()
{struct stu s1;struct stu s2;struct stu s3;
}

友友们注意这里s1,s2,s3,s4,s5,s6都是我们用结构体创建出来的结构体变量,但是是s1,s2,s3是局部变量,s4,s5,s6是全局变量

结构体特殊的声明

struct
{char c;int a;double d;
}s1;
struct
{char c;int a;double d;
}*ps;
int  main()
{/*ps = &s1;*/   //errorreturn  0;
}

以上两种结构体没有类型名,这就是传说中的匿名结构体了,友友们注意这种结构体定义出来的全局变量只能使用一次,还有虽然两个匿名结构体内容是相同的,但是我们也不能写*ps=&s1,因为在编译器看来,这两个地址类型是不一样的

结构体的自引用

/这是一个错误的示范
//struct Node
//{
//	int data;          //这里我们无法求得结构体的大小,它会无限循环下去
//	struct Node n;
//};
//int main()
//{
//	return  0;
//}
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 struct 
{int data;Node* next;    //error
}Node;
typedef struct Node
{int data;struct Node* next;   //correct
}Node;

注意typedef 是把struct 这个匿名结构体重新起名为s和Node,这里的s和Node是结构体类型名,不是定义的结构体变量!!!而且第二个typedef不能这样用,因为我们在没有重新命名Node之前,结构体变量中就使用了Node,这个顺序应放在命名之后使用!!!

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

struct S
{int a;char c;
}s1;       //全局变量
struct S s3;     //全局变量
struct C
{float f;struct S s;
};
int main()
{struct S s2={100,'q'};   //局部变量struct C sc = { 3.14f,{200,'w'} };     //结构体嵌套初始化struct S s3 = { .c = 'a',.a = 150 };   //注意这里我们也可以不按照顺序进行初始化return 0;
}

友友们注意结构体初始化的时候没有我们想象的那么复杂,其实就是定义变量的同时赋初值,不同的结构体初始化的时候可能是不相同的,因为它们所包含的类型可能不一样,结构体嵌套初始化的时候就是在大括号里面在加一个大括号然后进行赋初值

结构体内存对齐

友友们,重点来了哈,来一起和阿博打起12.1分的精神拿捏它吧🦹‍♂️🦹‍♂️🦹‍♂️

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()
{printf("%d\n", sizeof(struct S1));printf("%d\n", sizeof(struct S2));printf("%d\n", sizeof(struct S3));return  0;
}

在这里插入图片描述
这里我们惯性思维会告诉我们是5, 6,7,但是当我生成结果的时候,可能会大吃一惊,下面来一起跟阿博探秘吧.这里阿博先给大家传输一些内功😁😁

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

在这里插入图片描述
这里可以给友友们测试下,在测试前先给友友们介绍一个函数offsetof
在这里插入图片描述
在这里插入图片描述

这里我们也可以看出它们的偏移量.
2.
在这里插入图片描述
3.
在这里插入图片描述
4.

友友们我们来个结构体嵌套来测试一下我们的实力,加油哦😊😊😊

在这里插入图片描述

这里友友们可能会想为什么存在内存对齐呢,这里阿博分两方面给友友们讲.
1.平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常.
2.性能原因 :数据结构(尤其是栈)应该尽可能的在自然边界上对齐.原因在于,为了访问未对齐的内存,处理器需要做两次内存访问,而对齐的内存仅需要一次访问.
总体来说:结构体内存对齐是拿空间来换取时间的做法.这里阿博可以教大家一种既满足对齐,又节省空间的做法:让占用空间小的成员尽可能的集中在一起

这里给友友们看下图解哦
在这里插入图片描述

修改默认对齐数

#pragma pack(1)    //设置默认对齐数为8
struct S1
{char  c1;//1,1,1int i;   //4,1,1char c2;  //1,1,1    //默认是1,就相当于没有对齐,结果是6
};
#pragma pack()  //取消设置的默认对齐数,还原为默认

在这里插入图片描述

结论:结构在对齐方式不合适的时候,我们可以自己更改默认对齐数

结构体传参

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(const struct S*ps)   //防止被修改
{printf("%d\n", ps->num);
}
int main()
{print1(s);  //传结构体print2(&s); //传地址return  0;
}

这里友友们注意我们首选print2函数,因为函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销,如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,所以会导致性能的下降.所以结构体在传参的时候,要传结构体的地址.

结构体实现位段

什么是位段呢,可能会有很多友友疑问,因为我们都只知道段位😄😄😄,下面阿博就给大家普及一下
1.位段的成员通常是 int ,unsigned int ,signed int
2.位段的成员名后边有一个冒号和一个数字.

在这里插入图片描述
这里A就是一个位段.

//位段--二进制位
struct	A
{int _a : 2;int _b : 5;int _c : 10;int _d : 30;
};  //47 bit
int main()
{struct A sa = { 0 };printf("%d\n", sizeof(sa));return  0;
}

在这里插入图片描述

这里一共有47个比特位,按常理说6个字节,但为什么是8个字节呢,下面来和阿博一起探索吧

位段的内存分配

1.位段的空间上是按照需要以4个字节(int)或1个字节(char)的方式来开辟的
2.位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

这里我们调试一下,发现和我们的假设一样,但是这只代表在vs上是这样,不代表其他编译器也是这样
位段的跨平台问题总结
1.int 位段被当成有符号数还是无符号数是不确定的
2.位段中最大的数目不能确定(16位机器最大16,32位机器最大32,写成27,在16位机器上就会出问题)

枚举类型的定义

enum Sex
{//枚举的可能取值 ,默认是从0开始,递增1的//枚举常量MALE=5,FAMALE,SECRET
};
int main()
{//enum Sex s=MALE;    //enum Sex=1;这里在c语言中是支持的,但是在c++中就不支持了printf("%d\n", MALE);printf("%d\n", FAMALE);  printf("%d\n", SECRET);return  0;
}

在这里插入图片描述

这里我们也可以改变它的初值,注意枚举常量是用逗号隔开的,最后一个逗号可以不加,枚举的好处可以让我们代码的可读性大大提高,枚举本身有类型检查,可以让我们的代码更加严谨,还便于我们代码的调试,一次可以定义多个常量

联合类型的定义

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

在这里插入图片描述
在这里插入图片描述

联合体的应用

#include<stdio.h>
union  Un
{char c;int a;
};
int main()
{union Un u;u.a = 1;if (u.c == 1){printf("小端\n");}elseprintf("大端\n");return   0;
}

在这里插入图片描述

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

联合体大小的计算

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

union  Un
{char arr[5];   //5int n;        //4
};
int main()
{printf("%d\n", sizeof(union Un));return  0;
}

在这里插入图片描述

这里有5个char 的类型,大小是5,int 的大小是4,所以该联合体的大小至少是5,但是5不是4的整数倍,所以我们这里取了8个字节

这里给友友们出个题测试一下

union  Un
{short s[7];int n;
};
int main()
{printf("%d\n", sizeof(union Un));return  0;
}

这里有7个短整型,大小是14,有一个整形,大小是4,所以该联合体大小至少是14,但是14不是4的整数倍,所以需要往后浪费2个字节,所以大小是16

在这里插入图片描述
在这里插入图片描述

好了友友们,本期就到此结束了,如果你们感觉对自己有帮助的话,可以给阿博点个关注哦,后续阿博会继续给大家带来一些干货,下期再见💕💕💕

这篇关于全网最细的自定义类型详解(结构体,枚举,联合),友友们快来接收吧的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/289266

相关文章

Spring IOC的三种实现方式详解

《SpringIOC的三种实现方式详解》:本文主要介绍SpringIOC的三种实现方式,在Spring框架中,IOC通过依赖注入来实现,而依赖注入主要有三种实现方式,构造器注入、Setter注入... 目录1. 构造器注入(Cons编程tructor Injection)2. Setter注入(Setter

Java中注解与元数据示例详解

《Java中注解与元数据示例详解》Java注解和元数据是编程中重要的概念,用于描述程序元素的属性和用途,:本文主要介绍Java中注解与元数据的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参... 目录一、引言二、元数据的概念2.1 定义2.2 作用三、Java 注解的基础3.1 注解的定义3.2 内

Redis的Zset类型及相关命令详细讲解

《Redis的Zset类型及相关命令详细讲解》:本文主要介绍Redis的Zset类型及相关命令的相关资料,有序集合Zset是一种Redis数据结构,它类似于集合Set,但每个元素都有一个关联的分数... 目录Zset简介ZADDZCARDZCOUNTZRANGEZREVRANGEZRANGEBYSCOREZ

JavaScript中的isTrusted属性及其应用场景详解

《JavaScript中的isTrusted属性及其应用场景详解》在现代Web开发中,JavaScript是构建交互式应用的核心语言,随着前端技术的不断发展,开发者需要处理越来越多的复杂场景,例如事件... 目录引言一、问题背景二、isTrusted 属性的来源与作用1. isTrusted 的定义2. 为

使用Python实现操作mongodb详解

《使用Python实现操作mongodb详解》这篇文章主要为大家详细介绍了使用Python实现操作mongodb的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、示例二、常用指令三、遇到的问题一、示例from pymongo import MongoClientf

一文详解Python中数据清洗与处理的常用方法

《一文详解Python中数据清洗与处理的常用方法》在数据处理与分析过程中,缺失值、重复值、异常值等问题是常见的挑战,本文总结了多种数据清洗与处理方法,文中的示例代码简洁易懂,有需要的小伙伴可以参考下... 目录缺失值处理重复值处理异常值处理数据类型转换文本清洗数据分组统计数据分箱数据标准化在数据处理与分析过

详解如何在React中执行条件渲染

《详解如何在React中执行条件渲染》在现代Web开发中,React作为一种流行的JavaScript库,为开发者提供了一种高效构建用户界面的方式,条件渲染是React中的一个关键概念,本文将深入探讨... 目录引言什么是条件渲染?基础示例使用逻辑与运算符(&&)使用条件语句列表中的条件渲染总结引言在现代

详解Vue如何使用xlsx库导出Excel文件

《详解Vue如何使用xlsx库导出Excel文件》第三方库xlsx提供了强大的功能来处理Excel文件,它可以简化导出Excel文件这个过程,本文将为大家详细介绍一下它的具体使用,需要的小伙伴可以了解... 目录1. 安装依赖2. 创建vue组件3. 解释代码在Vue.js项目中导出Excel文件,使用第三

SQL注入漏洞扫描之sqlmap详解

《SQL注入漏洞扫描之sqlmap详解》SQLMap是一款自动执行SQL注入的审计工具,支持多种SQL注入技术,包括布尔型盲注、时间型盲注、报错型注入、联合查询注入和堆叠查询注入... 目录what支持类型how---less-1为例1.检测网站是否存在sql注入漏洞的注入点2.列举可用数据库3.列举数据库

Linux之软件包管理器yum详解

《Linux之软件包管理器yum详解》文章介绍了现代类Unix操作系统中软件包管理和包存储库的工作原理,以及如何使用包管理器如yum来安装、更新和卸载软件,文章还介绍了如何配置yum源,更新系统软件包... 目录软件包yumyum语法yum常用命令yum源配置文件介绍更新yum源查看已经安装软件的方法总结软