关于结构体,枚举,联合的一些知识

关于结构体,枚举,联合的一些知识

首先我们来看一下什么是结构体

在这里插入图片描述

通俗来说,结构体便是各种类型的集合。

接下来便是各处所代表的含义了

在这里插入图片描述

例如,我们现在来描述一个人。

在这里插入图片描述

其中name,age,sex,addr,phone都是描述一个人的元素。记得不要忘记最后的分号噢

但其实,结构体声明的时候也可以省去标签,直接用struct进行声明,这种类型叫做匿名结构体类型。

请看下图:>

在这里插入图片描述

像这种直接省去标签的结构体类型比较特殊,但其中也有注意的点。

  1. 匿名结构体类型在创建变量的时候只能在这个位置创建:

2.请看以下代码:>

struct
{
	int i;
	char a;
	double c;
}a;

struct
{
	int i;
	char a;
	double c;
}*p;




int main()
{
	p = &a;
	return 0;
}

我们看到,这两个匿名结构体的成员变量是一模一样的,我们以此创建了一个变量a,和一个指针p,如果我们想要用p来存放a变量的地址的话,用p = &a,其实是非法的,我们编译这个代码后,编译器会报一个警告:

在这里插入图片描述

因此我们要避免出现上述代码的情况。

我们在学习函数时,曾经介绍过函数可以自己调用自己,也即函数的递归,那么结构体是否也能自己调用自己呢?,这就是我们要介绍的,结构体的自引用

结构体的自引用

首先请看以下代码:>

struct stu
{
	int age;
	char name[20];
	struct stu s;
};

像以上这种在结构体成员中包含自己类型的变量,可不可行呢?,如果可行的话,请说出以下代码的结果:>

struct stu
{
	int age;
	char name[20];
	struct stu s;
};

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

是否已经感觉到不对劲了呢? 如果这段代码能跑起来的话,结果也应该为无穷大。

在这里插入图片描述

我们在vs环境中编译这段代码的话,你也会发现是跑不过去的。

在这里插入图片描述

正确的结构体自引用如下代码所示:>

struct stu
{
	int age;
	char name[20];
	struct stu*s;
};

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

即在结构体成员中定义一个自己类型的结构体指针,我们让程序跑起来的话,结果为:>

在这里插入图片描述

(此处结构体大小的计算我们将会在后文介绍到)

结构体与typedef的一些联名

typedef为类型重命名的关键字,它同样适用于对结构体类型的重命名,以此来帮助我们更好的将代码简洁,明亮。

首先我们来看一下其基本模板:>

在这里插入图片描述

上图中,我们已经把struct student类型重命名为stu,因此,可以这样创建一个结构体变量

typedef struct student
{
	int age;
}stu;

int main()
{
	stu s2 = { 0 };
	return 0;
}

在上面的代码中,我们用stu成功定义了一个结构体变量s2.

但是!在这个时候可能有些同学会说,那可不可以把代码写成这样呢?
在这里插入图片描述

即在结构体成员中率先使用stu创建一个变量s1,我们编译一下程序,会得到以下结果:>

在这里插入图片描述

编译器会报错。

那是因为,重命名的stu是在下图的方式开始起作用的:>

在这里插入图片描述

因此,编译器也就不认识stu这个标识符了。

结构体的定义和初始化

既然我们已经基本认识了结构体,接下来请观看结构体是如何定义和初始化的

最基本的两种方式如下所示:>

struct stu
{
	int age;
	char name[20];
}s1;

int main()
{
	struct stu s2;
	return 0;
}

在这里我们定义了两个变量,一个是s1,一个是s2,它们分别的区别为:>
在这里插入图片描述

此外,在定义结构体变量的同时还可以对其初始化:

在这里插入图片描述

这里初始化的方式就和数组有点类似了,即用一个大括号来对其初始化。

有时候我们会遇到这种情况,即在结构体成员中又有另外一个结构体,那么我们应该如何初始化呢,请看下图

在这里插入图片描述

那我们在初始化时,只需要在大括号中再套一个大括号即可,即:>在这里插入图片描述

里头的大括号完成的是对s1的初始化。

接下来便是有点小重要的部分,那便是结构体的内存对齐

结构体内存对齐

首先我们来看一下以下这段代码

struct test
{
	char a;
	int age;
	char b;
};

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

我曾经也认为这个结果时6个字节,但是,程序运行起来之后,得到的结果为:>

在这里插入图片描述

这是因为存在内存对齐现象,以下图将是对该代码的讲解:>

在这里插入图片描述

这里我们来了解一下对齐数应该如何计算。

在这里插入图片描述

举例:>
在这里插入图片描述

同理,变量age 和 b变量的对齐数为4, 1;请看下图

在这里插入图片描述

这样的话,我们就能将刚才的age和b变量放置在内存中了

在这里插入图片描述

黄色的区域就是age变量所占的区域,因为其对齐数为4,所以其要对齐到4的倍数处的地址处。1处便是b变量所占的区域,毕竟1是任何数字的倍数嘛。

此时,结构体所占的字节大小为:>

在这里插入图片描述

这个时候就有同学说了,此时结构体大小所占的内存空间不是9吗,实则不然,这里我们引出第三条规则:>

在这里插入图片描述

这里又引申出了最大对齐数的概念,大伙还记得我们在上面所算的对齐数吗,

在这里插入图片描述

最大对齐数即这些对齐数中最大的那个数,因此,在此处,最大对齐数为4, 结构体的总大小为4的倍数,因此,请看下图

在这里插入图片描述

这样的话,结构体的总大小就为12了,划×的部分是内存对齐而浪费掉的内存。

再来看下面这个例子

struct test1
{
	char a;
	int age;
	char b;
};



struct test2
{
	char c;
    struct test1 s1;
    char d;
	double e;
};



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

在一个结构体变量有其他结构体变量成员时,该如何计算呢,这里的话, 我们引申出第四条规则:>

在这里插入图片描述

同样的,我们来计算test2的大小。

首先,便是第一个成员防止在0偏移处的位置。

在这里插入图片描述

其次,第二个成员因为为结构体成员,因此,其对齐到其最大对齐数的整数倍处,又因为上面计算出了其最大对齐数为4,因此从偏移处为4的地方开始:>

在这里插入图片描述

其占用的内存空间为12个字节,这里我们上面已经计算过。

再然后,就是成员d了,成员d的对齐数为1,因此其对齐到16偏移处即可:

在这里插入图片描述

再接来就是最后一个成员变量e,其自身大小为8字节,vs默认编译器为8字节,因此其对齐数为8字节,其对齐到8字节的整数倍处:

在这里插入图片描述

粉色部分就是成员变量e所占的空间。

接下来是计算结构体类型的总大小,因为该结构体包含一个结构体变量,因此,其对齐到所有最大对齐数的整数倍,

在这里插入图片描述

因此,所有的最大对齐数最大为8,结构体的总大小要对齐到8的倍数的偏移处。

在这里插入图片描述

此处刚刚好占用内存空间为32,为8的整数倍,因此,结果为32,我们让程序运行起来,结果为:>

在这里插入图片描述

修改默认对齐数

我们可以通过 #pragma pack() 预处理指令来修改默认对齐数。

例如

#pragma pack(4) // 修改默认对齐数为4

struct test1
{
	char a;
	int age;
	char b;
};


#pragma pack() // 取消我们所设置的默认对齐数,还原为默认

位段

既然已经讲完了结构体,那就不得不谈一下结构体实现位段的能力

首先我们先来见识一下位段:>

struct A
{
	int a : 4;
	int b : 2;
	int c : 4;
};

那么A就是一个位段类型,要注意的是,位段的成员的类型只能是int、unsigned int或signed int。

]

请看下面以下这段代码,该代码运行的结果是什么?


struct A
{
	int a : 2;
	int b : 5;
	int c : 10;
	int d : 30;
};



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

我们让程序运行起来,得到的结果将会是:>

在这里插入图片描述

那么是如何计算的呢,请看下图:>
在这里插入图片描述

其次便是使用位段要注意的点:>

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

枚举

枚举,按照字面意思来说,就是一一列举,把所有可能的值一一列出来

例如我们的一周:>

enum day
{
	MONDAY,
	TUESDAY,
	WENSDAY,
	THURSDAY,
	FRIDAY,
	SATURDAY,
	SUNDAY
};

又如我们的性别:>

enum sex
{
    MALE,
    FEMALE,
    SECRET
};

上述的定义的两种枚举类型day, 和sex 中的可能取值都是有值的,让我们来看看它的值为多少

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

在这里插入图片描述

也即:>

在这里插入图片描述

也即说明了这些可能取值是有值的,并且从0开始。

当然,我们在定义的时候也能给它赋初值,例如:>

enum Color//颜色
{
	RED,
	GREEN = 7,
	BLUE
};

这样的话,RED的值依然为0,但GREEN的值变为7,BLUE的值为8。

接下来便是枚举的使用了。例如:>

enum Color//颜色
{
	RED,
	GREEN = 7,
	BLUE
};

int main()
{
	enum Color s1 = BLUE;
	return 0;
}

同时,还有其他用途,本人最常用的是用在这种情况

在这里插入图片描述

即在这种有菜单的情况下,通过枚举能够更加让读者更加清楚的了解代码.

联合的相关知识

联合是一种特殊的自定义类型,这种类型定义的变量包含一系列成员,但是这些成员共用一块空间,因此联合体也被称为共用体。

接下来请看以下代码:>

union un
{
	char a;
	int b;
};

在这里插入图片描述

这便是联合体最基本的样子了。

接下来,我们观看一段代码:>

union un
{
	char a;
	int b;
};

int main()
{
	printf("%d\n", sizeof(union un));
	return 0;
}

它的结果是什么呢?,答案是4

要知道,联合体是共用一块内存块的,因此,联合体大小最小也是成员变量中最大的那个数。在此处,b变量为4个字节,因此总体的大小为4个字节。

但是为什么要强调最小呢? 因为联合体也存在对齐的情况,即当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。

请看下面这段代码:>

union un
{
	char a[5];
	int b;
};

int main()
{
	printf("%d\n", sizeof(union un));
	return 0;
}

这一段代码的结果是8,那是因为:>

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值