C_C语言的enum、struct、union的使用详解

2016-08-29

OK今天我们讲解C语言的三个特殊结构的使用,他们都不是基本类型,但是他们让C语言变得更加强大,更加易用,下面就让我们一一分析:

首先我们介绍enum---->枚举:

为了更加直观的对枚举有一个印象我们还是老规矩,看代码:

我们看到我们定义了一个枚举类型,然后再在主函数中将枚举的一个元素打印了出来,结果正如我们所料,既然我们已经直观的感受了enum的定义
以及其最基本的用法,那么下面我们就要说说他的语法规定了:
首先,如果你想要定义一个枚举就必须使用关键字enum,关键字enum就是声明接下来我们定义的是一个枚举器,然后紧跟着的就是这个枚举器的名称
了,在名称的后面是一对大括号,最后分号,在枚举器的大括号内设定都有哪些枚举,以及每个枚举的值是多少(在本例中,我们枚举器的名称为bool
,共有两个枚举,分别为值为1得到true和值为0的false),那么下面我们来讨论一个比较深入的问题,每个枚举究竟是什么,是变量吗?为了弄清
这个问题,其实有经验的读者就会想到,我们看一看他的汇编是什么样子?好,接下来就让我们看一看他的汇编是什么样子:

我们可以看到汇编的第一行表明了c文件的名称为"enu.c",这正是我们所写的C文件。第七行表明我们定义了一个main函数,而第八行正是我们的这个
main函数的入口。我们看第四行有一个 .string "%d " 我们发现这正是我们在printf函数的第一个参数所写。最后也是我们要看的重点:第十六行
有一个"$1"这正是我们定义的枚举true的值,这个时候我们知道我们的定义直接显示在了代码段中(学过操作系统的读者知道,一个程序就为一个
进程,每个进程系统都会为他分配独立的内存空间,而这个内存空间分为:代码段,常量段,全局栈,局部栈,堆区。我们写的代码放在堆区,而
我们定义的变量的值是放在栈区的)这就说明的他不是变量不能和const最类比,不过倒是和#define挺像......
那么下面我们讨论枚举的值:
首先枚举的值类型是什么?我可以提前告诉大家是整型。那么是不是可以是其他类型呢?这我说了不算,我们看示例:

显然不行,记住枚举的值仅能是整型。
好下面我们说说两个枚举能不能使用同一个值?如果我们不给枚举赋值,那么枚举的值是什么?我们还是看代码:

显然是可以的,我们可以讲两个枚举赋同一个值。我们也可以看到如果我们不为枚举赋值,那么他就会从零开始,依次递增赋值。
下面我们来看enum的一些高级用法:
每个枚举可不可以在使用时赋值?
如果我们想使用枚举类型定义变量来接收枚举的值应该怎么做?好我们先看代码再做解释;

从代码中我们可以看到如果要定义一个枚举类型的变量,我们可以使用enum 枚举名 变量名 = 变量值;其中最前面的enum是不能缺少的,有读者就会想了
如果每次都这样定义岂不是很麻烦?请各位继续向下看,我们可以使用typedef来为我们的枚举类型定义一个别名,那么就不会那么麻烦了,而且我们看到
他们的结果完全相同。如果你还是嫌麻烦,那么轻向上看,枚举weekday经过我们的重新定义,我们发现我们在定义枚举是就可以为他起别名了,很方便吧!

下面我来来介绍一下union--->联合。由于现在计算机性能的提高我们一般不再使用(除非一些特定场合)所以我们不再过多讲解,力求简单给大家一个印象即可:

好关于联合我们仅出示上图这一个代码示例,接下来我们共同分析一下上述大码;
我们可以见到代码的第14~17行我们定义了一个联合,而且我们还给联合取了一个别名叫做“IP”,我们来看联合的定义,首先还是使用关键字union来声明
接下来我们要定义一个联合。接着就是我们的联合的名称。紧接着一对大括号,括号内定义我们的要定义的内容。可以看到我们定义了一个char数组和一个整数。
下面我们来看主函数的代码,首先我们打印了char,int,IP的内存大小--->char占一个字节,int占四个字节,IP占四个字节。看到这我们就奇怪了,根据结果
char数组应该占四个字节,int占四个字节在一起应该是八个字节,可为什么总共只占了四个字节呢?这就是联合的特性了,联合内的变量共享内存,也就是说
联合内部仅有一个内存块,同一时刻仅能保存一份数据,而所有变量共享这一份数据。每当我们对其中的某个变量赋值时,其他所有的变量的数据都会变化而,
下面打印的结果也证实这一点。当我们给char数组进行赋值时,我们看到int得到了同样的也发生了变化,当我们对int进行赋值时char数组也发生了变化同时得
到了同样的数据。最后一行我们将char和int的地址(关于内存地址的问题我们稍后讲解)打印出来。我们发现他们处于同一地址,这一就不奇怪之前的结果了。
不知道大家有没有注意到我们的数组和int的结果是相反的,这是由于我们的系统是大端系统。


好接下来我们讲解本文的最后一个主题------struct,结构体:

我们首先讨论一下我们为什么要使用结构体?请各位思考下面的问题,我们要保存一个我们自定义的类型(例如我们要定义一个学生类型:包含姓名,学号,性别
,成绩,联系方式,家庭住址)怎么办?想一想有没有什么好的办法可以将一个“数据包”组合起来组成我们的自定义类型?就目前我们所学过的知识来讲还不行,
这就是我们要使用结构体的原因:我们要将几个数据组合起来构成一个自定义类型。
下面我们看结构体的定义,首先还是一样,如果我们想要定义一个结构体必须要使用struct来声明接下来我们要定义的是一个结构体,然后是结构体的名称,接着
一对大括号,在大括号内部定义各种属性,但是他的每个属性都是有独立的存储空间而不是象union那样仅使用一份内存。我们让每个属性单独保留一份数据。以供
我们使用。最后 大家看代码不知道各位有没有看出什么问题?我们可以看到我为每个属性的末尾添加了注释,而注释的内容就是属性的大小,我们相加只有52个
字节,但是我们使用sizeof函数获得了56字节,其实这就是内存对齐。

下面我们来看结构体的声明,初始化,与赋值:
下面的代码展示了四种结构体的声明有初始化,当我们每次声明一个结构体变量时,都会为我们分配相应的存储空间,那么我们要使用这个变量就要为结构体的、
每个属性赋值,而赋值可以分为初始化时按顺序赋值,初始化时按属性名赋值,以及使用变量属性分别赋值,还有就是直接使用另一个结构体变量赋值。首先我们
来看代码的第25行,我们声明了一个结构体变量stu1,然后我们在之后的大括号内,根据属性的类型以及顺序,依次为各属性赋值。接下来我们看第32行,我们先
思考当属性过于复杂时,我们并不知道他们的顺序我们应该如何初始化?我们自然就想到,是不是可以使用属性名来进行初始化,那么本行就是范例。我们再看第
33行,我们亦可以使用另外一个结构体变量来初始化我们的新变量,我们通过下面的打印结果就可以看出,新的变量完全复制了用来初始化的变量。最后我们再看
第26行,他首先声明了一个空的结构体变量,然后再使用.来引用各属性进行依次赋值(注意看第27,28,31行我们通过复制函数来对char数组进行复制)。而最后的结果也是意料之中。

下面我们就来看一下结构体的数组。我们知道任何一个基本类型都可以声明为对应类型的数组,那么我们声明了一个结构体类型后是否也可以声明一个本类型的
数组,答案是肯定的,当然可以,由于我们已经学习过数组,所以我们直接看代码:

根据代码我们可以看到,他就是结构体的初始化赋值,与数组的赋值,访问,初始化的联合使用。我在此就不在做过多的解释,还请各位能够自行理解。
在本主题的最后,我还想讲结构体的最后一个用法,我们来思考以下场景,我们想使用类似于位移的功能使用一个字节的每一个位,但是我们还想单独访问他的每
一个位我们应该如何实现?其实我们可以使用结构体就是可以的。好我们下面来看看这是如何实现的。首先它的定义与普通的结构体没什么不同,只是它内部的属
性只能是unsigned int或unsigned char两种类型,接下来就是变量的名称,但接下来就是重点了:紧接着后面我们要跟一个冒号然后冒号的后面我们再跟一个数字
这个数字代表的就是这个变量占用的位数(一位或数位)。好了下面我们就来看一下代码示例:

我们可以看到我们定义了Po1、Po2、Po3三个类型他们分别使用了unsigned char和unsigned int两个类型,首先我们知道,一个char占用一个字节(八个位),而
unsigned int 占用4个字节(32个位)。我们先看Po1使用了八个一位的unsigned char由于每个unsigned char占一个为一共八个所有共占用了一个字节,而Po2共
使用了九个占一字节的unsigned char共占九位,但我们看结果Po2却占用了两个字节(16位)这也就是所谓的内存对齐。最后我们看Po3我们在其内部分别设置了三
个占一字节的unsigned int、三个占两字节的unsigned int、三个占三字节的unsigned int共占18位,但是我们使用的是int所以使用了4个字节。
最后我们再看我们为属性赋值,我们看到我们根据位数的大小最多可以赋值一个同等位数的二进制数,如果赋值的这个十进制数大于最多可以使用的二进制数就会溢
出,造成错误的结果,我们根据最后打印的结果也可以大致推算出来....
好了我们今天就讲解这些了,再见。