关于结构体,枚举,联合的一些知识
首先我们来看一下什么是结构体
通俗来说,结构体便是各种类型的集合。
接下来便是各处所代表的含义了
例如,我们现在来描述一个人。
其中name,age,sex,addr,phone都是描述一个人的元素。记得不要忘记最后的分号噢
但其实,结构体声明的时候也可以省去标签,直接用struct进行声明,这种类型叫做匿名结构体类型。
请看下图:>
像这种直接省去标签的结构体类型比较特殊,但其中也有注意的点。
-
匿名结构体类型在创建变量的时候只能在这个位置创建:
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,那是因为:>