自定义类型详解:结构体,枚举,联合

目录

前言

一、结构体的声明:

        1.1结构体基础知识:

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

        1.3特殊的声明:

        1.4结构体的自引用:

        1.5结构体内存对齐:

 例1:普通结构体

例2:结构体中包含结构体

        1.6为什么存在结构体内存对齐:

        1.7修改默认对齐数:

        1.8结构体传参:

二、位段

        2.1什么是位段:

        2.2位段的内存分配:

        2.3位段跨平台问题:

        2.4位段的应用:

三、枚举

        3.1枚举的定义:

        3.2枚举的优点:

        3.3枚举的使用:

四、联合(公用体)

        4.1 联合体的类型定义:

        4.2联合体的特点:

        4.3 经典面试题(判断电脑大小端)

        4.4联合体大小的计算

总结



前言

大家好!我是Node_Hao,今天为大家带来的是c语言系列,自定义类型,话不多说咋们开始.        


一、结构体的声明:

        1.1结构体基础知识:

        类比数组是一种存放相同元素的集合,而结构也是一种集合,集合中存放各种不同的元素.例如:常量,数组,指针,甚至是其他结构体.

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

        结构体定义相当于描绘出一个房子的平面图,而结构体初始化才相当于建起一座房子.一座房子当它有平面图时占地面积就已经很清楚了,所以结构体变量在定义的时候就已经确定内存大小了.

        

        1.3特殊的声明:

        在声明结构体时可以不完全声明(匿名结构体类型):该结构体类型在声明时都忽略其标签,因此定义的结构体变量只能使用一次.而且每个结构体变量之间无任何联系,所以不存在*p = &x这种写法.

#include<stdio.h>
struct
{
	int a;
	char b;
	float c;
}x;
struct
{
	int a;
	char b;
	float c;
}a[20], * p;

        1.4结构体的自引用:

        既然结构体可以包含那么多的类型,是否也能包含结构体自身呢?下面我们来看几组代码:

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

        这段代码是否可行呢?如果可以sizeof(struct Node)为多少呢?这里明显是不能通过编译器的,因为我们并不知道struct Node next中到底含有多少元素,也就无法知道其大小.

        这里涉及到了后续的链表相关的知识,链表中含有数据域和指针域,数据域中存放自身的数值,而指针域中存放下一个元素的地址,正是这样的指针域才搭建起了链表之间的联系.

        所以代码可以这样改写:结构体中只能包含结构体指针.

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

        1.5结构体内存对齐:

        在掌握结构体的基础知识后,我们深入探讨一下结构体的内存大小.在计算前我们得先知道结构体内存大小的计算方法.

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

2.其他成员变量要对齐到对齐数的整数倍处,

        对齐数等于编译器默认对其数和成员大小的较小值.(vs默认对其数为8)

3.结构体总大小为最大对齐数的整数倍.(每个成员都有对齐数)

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

         在掌握计算方法后我们来看几个例子:

 例1:普通结构体

struct S1
{
char c1;
int i;
char c2;
};
printf("%d\n", sizeof(struct S1));

 

         已知编译器默认最大对齐数为8,c1和c2最大对齐数都是1,i的最大对齐数为4.将c1放于默认对齐数为0的位置,i放于其对其数的整数倍处,也就是偏移量为4的位置处,c2对齐数为1,放于偏移量为8的位置即可.结构体的总大小为成员最大对其数的整数倍,成员最大对齐数为4那么总大小可以为8.

例2:结构体中包含结构体

struct S3
{
	double d;
	char c;
	int i;
};
printf("%d\n", sizeof(struct S3));
struct S4
{
	char c1;
	struct S3 s3;
	double d;
};
printf("%d\n", sizeof(struct S4));

        1.6为什么存在结构体内存对齐:

        1.平台原因(移植原因):不是所有硬件平台都能访问任意位置的内存,某些硬件平台只能访问特定位置的数据类型.

        2.性能原因:数据结构(尤其是栈)应尽可能在自然边界上对齐,原因在于为了访问未对齐的内存,处理器需要两次访问,而对齐的内存只需要访问一次.因为在32位机器上一次可以读取4个字节,如果偏移量0的位置为char,紧随其后又是一个int,那么我们第一次读取了char和部分int,第二次才能读取整个int.但如果内存对齐我们在自然边界一次就能对齐.

         那么我们在设计结构体时既要满足内存对齐又要节省空间,只能将空间小的成员尽量集中在一起.

        1.7修改默认对齐数:

        #pragma这个预处理指令可以修改默认对其数.在对齐数不合适的时候我们可以自己修改默认对其数.当对其数为1时所有成员在内存中就会紧挨着储存.

#pragma pack(1)//设置默认对齐数为1
struct S2
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
int main()
{
//输出的结果是什么?
printf("%d\n", sizeof(struct S1));
printf("%d\n", sizeof(struct S2));
return 0;
}

        1.8结构体传参:

        结构体传参分为:1.传结构体 2.传地址.函数传参时是需要压栈的有时间和空间上的开销.如果传一个结构体对象时,结构体较大,参数压栈的系统开销较大,所以导致系统的性能下降.因此传址调用是首选.

#include<stdio.h>
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(struct S* s) {
	printf("%d\n", s->num);
}
int main() {
	print1(s);
	print2(&s);


	return 0;
}

二、位段

        2.1什么是位段:

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

1.. 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型

2.位段的成员后面有一个冒号和分号

例如: 

struct A {
 int _a:2;
 int _b:5;
 int _c:10;
 int _d:30;
}
printf("%d\n",sizeof(struct A));

那么struct A的大小又是多少呢?  由编译器的运行结果可知大小是8.具体原因在于位段的内存分配.

        2.2位段的内存分配:

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

例如:

#include<stdio.h>
struct S {
	char a : 3;
	char b : 4;
	char c : 5;
	char d : 4;
};
struct S s = { 0 };
int main() {
	s.a = 10;
	s.b = 12;
	s.c = 3;
	s.d = 4;
	return 0;
}

         看完内存分布之后很多人会疑惑为什么不按小端存储呢?其实我们应该搞清楚一个概念,大小端存储是建立在多个字节序上的,类似char这样只有一个字节的,存储方式与二进制书写格式一致.

        由以上内存分布我们可以看出 ,char类型的变量分配几个bit位的空间就开辟几个bit位空间,超出就会截取.例如:char a 只分配了3个bit位,而10的二进制位1010需要四个bit位所以就发生了截断,实际内存只存储了010.

 2.3位段跨平台问题:

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

综上:与结构体相比位段可以达到同样的效果,且节省空间.但存在跨平台问题.

        既然存在这么多限制又没有比结构体方便多少,那么位段真正的作用是什么呢?

2.4位段的应用:

        位段的应用主要体现在网络上通讯上,例如张三给小美发消息:"你好呀!",看似简单的一句话在数据传输的过程中会包含很多的信息,如果我们不给这些信息设置具体大小 ,全部统一为4个字节.那么数据量就会变得异常之大,如果把网络传输比作高速公路,那么使用位段 高速公路上行驶的都是小轿车无比通畅,要是统一为int型 ,那就相当于全是大卡车,不仅行驶速度慢,还有很高的丢包率.

三、枚举

        3.1枚举的定义:

        枚举顾名思义就是把可能的取值一一列举
        比如我们现实生活中:

一周的星期一到星期日是有限的7天,可以一一列举。
性别有:男、女、保密,也可以一一列举。
月份有12个月,也可以一一列举
 

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

以上定义的 enum Day , enum Sex , enum Color 都是枚举类型
{}中的内容是枚举类型的可能取值,也叫 枚举常量.
 

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

例如:

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

        3.2枚举的优点:

        有一定C语言基础的同学都知道,要定义常量我们可以用#define,那么为什么要用枚举?

枚举的优点:

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

        3.3枚举的使用:

#include<stdio.h>
enum Color {
	READ = 2,
	GREEN = 1,
	BULE = 3
};
int main() {
	enum Color s = READ;
	return 0;
}

     注意:只能拿枚举常量给枚举变量赋值才不会出现类型异常.
 

四、联合(公用体)

        4.1 联合体的类型定义:

        联合也是一种自定义类型,这种类型的变量包含一系列的成员,特征是这些成员公用一块空间.

例如:

#include<stdio.h>
union un {
	char c;
	int i;
};
int main() {
	union un s;
	s.c = 'a';
	s.i = '8';
	printf("%d\n", sizeof(s));//4
	return 0;
}

        4.2联合体的特点:

联合体是公用一块空间的,那么联合体的大小至少是最大成员变量的大小.

由这段代码的我们可以得出一个结论:结构体的存储方式类似于我们平时的合租房,无论指哪个人的地址只要是同一间房子的地址都是一样的,但这样也存在一个弊端.如果其中一个人要搬家其他人也要跟着走,这样别人的地址就发生了改变.

        4.3 经典面试题(判断电脑大小端)

        

        4.4联合体大小的计算

1.联合体的大小至少是最大成员的大小.

2.当最大成员不是最大对齐数的整数倍时,就要对其到最大对齐数的整数倍.        

        以上代码的最大成员为5,不是最大对其数的整数倍,要对其到最大对齐数的整数倍.所以必须创建8个字节的空间.(与结构的计算方法一致) 

        再看这组代码,最大成员为14,对齐到最大对齐数的整数倍处就是16. 


总结

        以上就是自定义类型的全部内容了,希望我的内容可以对你的学习有亿点点帮助,码字不易如果觉得文章不错麻烦关注一波,如有bug欢迎批评指正~

  • 11
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Node_Hao

您的支持是我创作的不懈动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值