自定义类型
C语言自定义类型
前言
首先来说,C语言得自定义类型有三种,分别是结构体类型,枚举类型和联合体(共用体)类型。下面,本文将会一一介绍这三种类型的特点和他们的使用。
一、结构体类型
结构体类型就是一些值得集合,这些值就是成员变量。意思就是,结构体可以把不同类型或者相同类型的成员放在一个结构体内部,形成一个新的类型。
1.1结构体的声明
首先我们来看一下结构体是如何声明的:
struct tag
{
member-list;
}variable-list;
struct tag 两者合起来就是这个新定义的结构体类型的名字,member-list就是结构体成员的列表,variable-list就是定义的相应结构体类型的变量,就好比是int a,a是int类型的变量一样。
下面我们用声明一个学生类型的结构体:
struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
}; //分号不能丢
这就是我们声明的一个学生类型的结构体,它包含了名字,年龄,性别,学号这四个成员变量。
那么如果我们不加tag标签,能成功定义结构体吗?例如:
struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
}a[20], * p;
这是可以的,它是一种匿名的结构体类型。因为其在定义的时候省略了结构体标签。那么问题来了,这两个结构体成员变量都一样,那他们是相同的结构体吗?
要注意,虽然这两个结构体完全一样,但是他们仍然会被编译器当成完全不同的两个类型,所以他们是非法的。
1.2结构体自引用
结构体可以把不同类型的变量包含在结构体内,那如果是结构体自己呢?能否作为一个结构体的成员呢?
struct Node
{
int data;
struct Node next;
};
这样可以嘛,如果可以的话,结构体的大小是多少呢?
其实这个是一种错误的方式,因为这样会一直循环下去,结构体中一直有结构体,根本停不下来!
所以,正确的结构体自引用的方式应该使用指针,例如:
struct Node
{
int data;
struct Node* next;
};
1.3结构体变量的初始化
请看下面的代码:
struct Point
{
int x;
int y;
}p1; //声明类型的同时定义变量p1
struct Point p2; //定义结构体变量p2
//初始化:定义变量的同时赋初值。
struct Point p3 = { x, y };
struct Stu //类型声明
{
char name[15];//名字
int age; //年龄
};
struct Stu s = { "zhangsan", 20 };//初始化
struct Node
{
int data;
struct Point p;
struct Node* next;
}n1 = { 10, {4,5}, NULL }; //结构体嵌套初始化
struct Node n2 = { 20, {5, 6}, NULL };//结构体嵌套初始化
要记得,初始化结构体时一定要带大括号。
1.4 结构体内存对齐
我们知道,各种数据类型都是有大小的,比如整型是四个字节,double是八个字节。那结构体的大小是多少呢?是把结构体所有类型都加起来吗?
当然不是的,要计算结构体变量所占内存的大小,首先要了解内存对齐:
- 结构体第一个成员在结构体变量偏移量为0的地址处(结构体成员的偏移量就是相较于结构体首地址增加的字节数,偏移量为0就是在结构体首地址加0个字节处)。
- 其他变量成员要对齐到对齐数的整数倍处,就是其偏移量应当是对齐数的整数倍。
对齐数是编译器的默认对齐数和该成员大小的较小值,在vs环境下,默认对齐数是8.
- 结构体的总大小为整个结构体变量的最大对齐数的整数倍。
- 如果有嵌套结构体的情况,嵌套结构体要对齐到自己最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(包含嵌套结构体的对齐数)的整数倍处。
如上图所示,结构体第一个变量为char类型,在结构体的0偏移处,而第二个类型是整型,默认对齐数为8,而整型大小是4,所以对齐数为4,偏移处应该在对齐数的整数倍处,也就是4偏移处,接下来是char类型,大小为1,默认对齐数为8,所以对齐数为1,此时所占空间为9个字节,而结构体所有成员的最大对齐数也就是4,并非为其整数倍,所以要再加上三个字节,一共十二个字节。
上图offsetof是一个宏,可以查看结构体成员的偏移量。
为何要存在内存对齐呢,不对齐的话不是更能节省空间吗?
内存对齐主要有以下两个原因:
- 平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的数据;某些硬件平台只能在某些地址处去取某些特定类型的数据,否则就会抛出硬件异常。 - 性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐,原因在于,为了访问未对齐的内存,处理器需要做两次内存访问;而对齐的内存访问仅需要一次访问。
所以,如果我们在内存对齐的情况下要节省空间的话,应当让占用空间小的成员尽量集中在一起。
另外,如果有需要,也可以对默认对齐数进行修改:
#pragma pack(8)//设置默认对齐数为8
struct S1
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
1.5 位段
位段的声明和结构体很类似,但是有两点不同:
- 位段的成员必须是int,unsigned int
- 位段成员后有一个冒号和一个数字
如下:
struct A
{
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 30;
};
这里的struct A 就是一个位段类型。
1.5.1 位段的内存分配
首先应当注意三点:
- 位段的成员可以是int,unsigned int,signed int或者是char(属于整型家族)
- 位段在空间上是按照需要以4个字节(int)或者1个字节(char)的方式来开辟空间的。
- 位段会涉及到很多的不确定因素,因而位段是不跨平台的,所以注重可移植性的程序应当避免使用位段。
例如:
struct S
{
char a : 3;
char b : 4;
char c : 5;
char d : 4;
};
struct S s = { 0 };
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;
其空间开辟是这样的:首先开辟一个字节的空间,a变量占三个比特位,这就还剩下五个比特位,接下来b变量占4个比特位,而之前开辟的空间还剩五个比特位能够容纳b,所以b就接着放入之前开辟的一个字节内,接下来是c变量,占五个比特位,所以需要重新开辟一个字节的空间将c变量放入,最后是d,而上次的开辟的c的空间只剩下三个比特位,是不够的,所以需要再次开辟一个字节的空间用来容纳d。
空间分配如下图所示:
1.5.2 位段的跨平台问题
- int位段被当成有符号数还是无符号数是不确定的。
- 位段中最大位的数目不能确定。例如:16位机器最大位是16,32位机器最大是32,写成27,在十六位机器下就会出问题。
- 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
- 同时,当一个结构体包含两个位段,第二个位段成员比较大,无法容纳第一个位段剩余位时,究竟是丢弃剩余的位还是利用,这是不确定的。
所以,位段可以很好的节省空间,但是会有跨平台的问题存在。
这是一个IP数据报,它就运用了位段来节省数据报头的空间。
二、枚举类型
1.枚举类型的定义
enum Color//颜色
{
RED,
GREEN,
BLUE
};
以上定义了一个enum Color的枚举类型,{}中的内容是枚举类型可能的取值,也叫枚举常量。其默认从0开始一次递增1,当然也可以在定义的时候赋初值,之后的枚举常量接着赋值的大小递增。
2.枚举类型的优点
既然是枚举常量,那我们是不是可以#define来定义,为什么要使用枚举类型呢?来,让我们来看一下枚类型的优点:
- 增加代码的可读性和可维护性
- 和#define定义的标识符相比,枚举有类型检查,更加严谨。(因为#define是在预处理时就替换掉的,在调试的时候我们看到的代码其实已经被替换过了)
- 防止命名污染
- 便于调试
- 方便使用,一次可以定义多个常量。
3.枚举类型的使用
enum Color//颜色
{
RED = 1,
GREEN = 2,
BLUE = 4
};
enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。
int main()
{
printf("%d\n", sizeof(enum Color));
printf("%d\n", sizeof(clr));
return 0;
}
枚举类型的大小就是一个整型的大小。
三、联合(共用体)类型
1. 联合类型的定义
联合也是一种特殊的自定义类型,这种类型的变量也包含一系列成员,而其特点就是这些个成员公用一块内存空间,所以联合体类型也叫做共用体类型。例如:
//联合类型的声明
union Un
{
char c;
int i;
};
//联合变量的定义
union Un un;
//计算连个变量的大小
printf("%d\n", sizeof(un));
该联合体的大小是4。
2.联合体的特点
联合体的成员是共用一块内存空间的,所以联合体成员的地址是一样的,同时一个联合体变量的大小至少是最大成员的大小。
当联合体最大成员的大小不是最大对齐数的整数倍时,联合体的大小就要对齐到最大对齐数的整数倍处。例如:
union Un1
{
char c[5];
int i;
};
int main()
{
printf("%d\n", sizeof(union Un1));
return 0;
}
结果为: