在C语言中,自定义类型是一种应用非常广泛的,典型的以结构体为例,比如你要描述一个学生,这个学生具有:姓名+年龄+性别+学号这么几项特征,而通常我们有可能要把学生来包装成一个类型,这样就可以重复性的用这个类型来定义出不同的每个学生。而结构体的出现就可以使得ç语言具有了这样能描述复杂类型的能力。
一般的自定义类型我们主要学习这么几种:结构体,枚举,联合体
知识点:
>结构体类型创建
>结构体
初始化
> 结构体内存对齐 >位段,位段计算机大小。
>枚举+联合。
首先是看看结构体,关于结构体,其实我们可以联想到数组,因为结构体于数组是有一定程度的相似之处的,都是属于聚合性结构,可以用来存放多个元素,不同点在于数组只能存放不同类型的元素,而结构体则可以存放相同或不同类型的元素。
结构体类型创建
首先要声明一个结构体类型,我们需要用到关键字struct,这里给出声明一个结构体的一般格式:
1 struct tag
2 {
3 member-list;
4 }variable-list;
在这里要说明的是,这个标签被称为结构体的类型,也就是我们自己定义的这个结构体的名字,这个是可以省略的,当它被省略时,我们定义的结构体就被叫做匿名结构体。
而变量列表,也就是结构体的变量,这是代表我们实际上定义的结构体类型的这样的变量,可以在这里直接同时声明出多个同类型的变量。当然,也允许省略,省略的代价无非是我们只是在抽象出了一个结构体类型而已,等到在程序中要实际定义出这个结构体变量时,直接用这个类型来定义就可以。结构体的变量,可以被定义成一个指针,那么这个指针,也就是结构体类型的指针了。
但是,这里要强调的是,这个成员列表,也就是结构体的内部成员,就是比如当我们定义一个学生时,用来描述这个学生的,姓名,学号,性别等这些属性信息。那他可以省略吗那么我们要说的是,当我们省略结构体成员时,这就是一个空结构体了注意:。在ç语言中,空结构体的情况是不被允许的,你必须至少包含一个成员(PS:在C ++里,其实结构体就是类这么一个概念,而类是允许有空类存在的)。
这里的三点:结构体的类型,结构体变量以及结构体的内部成员,我们统称为结构体的三要素这三要素,是组成一个结构体类型的重要组成部分。
结构体初始化
结构体的初始化规则,与数组是一样的,当我们需要对结构体变量初始化,也就是定义它的同时直接赋给它初值,是可以进行整体赋值的,将一组要赋的初值用{}包起来,整体赋值就行了。比如:
struct Point
{
int x;
int y;
}p1; //声明类型的同时定义变量p1
struct Point p2; //定义结构体变量p2
struct Point p3={x,y}; //初始化:就是定义变量的同时赋初值
再比如定义一个学生:
struct student
{
char name[10];
int age;
};
struct student s={"zhangsan",20};//直接将学生初始化成某个具体的学生
结构体体的内存对齐
谈到结构体的内存对齐,结构体的内存对齐是作为计算结构体大小的时候,需要用到的一个重要概念,总体来说,属于一种依靠浪费空间来换性能的方法。这里我先给出一个例子,来简单说明。我们都知道,CPU访问内存时,我们以为它是可以在内存任意位置读取的,但实际上,某些平台可能只能在特定位置访问。比如,CPU读取内存数据,假设起始地址必须从偏移量为4的整数部分读取。那么,现在定义这么一个结构体,我们分别画图分析内存对齐和不进行对齐时,它是如何访问内存的。
1 struct {
2 char x;
3 int y;
4 };
可以看见,当我们在内存中读取结构体当中的成员时,由于char型的A占一个字节,而int型的B占四个字节,
那么假设他们都从偏移量为4的这个位置开始,只访问4的整数倍数。可以看出,不内存对齐时,访问甲只要一步,读够一个字节就好,访问乙时,接着甲的位置继续,就要先读完甲剩下的三个字节,再从8的位置起,再读一个字节,补够乙所需四个字节。这样读取乙就需要两次。而当我们进行内存对齐,也就是假设我甲后面三个字节不要了,我直接从8起,把乙放在这四个字节里。这时我读取A,只需要一步,再从8开始读取B,也只需一步就能读够所要的四个字节。速度上我就可以提高,这在某些需要性能的程序上是很重要的。
所以总的来说,内存对齐,就是一种用空间换时间的做法。至于深入的探究,这里暂时先不讨论。
位段,位段计算机大小
位段是一种与结构体很类似的自定义类型,在声明上,主要有两个特点:
- 位段成员必须是int,unsigned int或signed int也或者char(即整型家族);
- 位段成员名后面有一个冒号和一个数字(PS:数字就是用于表示该成员占据的字节数)。
struct A
{
int _a:2;
int _b:5;
int _c:10;
int _d:10;
};
那么我们计算位段一个位段类型变量时,其实就是将这些数字加在一块就可以了,而位段其实就是通过比特位压缩存储的方式来存储的。
当我们存储一个变量时,计算机会先看这个变量前面的一个变量是否将为之开辟的空间用完,如果发现前一个变量实际上没有用完空间。再存储这个变量,是接着前一个变量继续存储的,而不是继续开辟新的空间。可以看出,位段是这样子,与前面结构体内存对齐的概念相反,没有对内存的浪费,而是存储的非常紧促,是以牺牲性能的方法换取内存上的节省。
位段有一个很重要的限制在于,它是不支持跨平台的,所以如果是在考虑跨平台移植的程序里,就无法使用位段了。
枚举+联合
最后简单说说枚举和联合这两个概念,所谓枚举,可以理解为一一列举。
枚举在自定义类型里,有一个很与众不同的特性,就是它的内部所有成员,都不是变量,而是常量,并且成员之间是以逗号间隔的。也就是说,所谓枚举,只是简单的将其包含的成员一一列举出来,而不作任何改变举个例子:一个星期从周一周日,这是可以列举出来,而无法进行改变的;再比如颜色有好多种可一一列举,一年十二个月等等,这些都是可以的。枚举是以关键字枚举定义的。
enum Day
{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
使用枚举是可以用来定义变量的,既然是定义变量,那可以与另一个定义变量的工具 - 宏进行对比,我们可以发现,枚举,可以一次性定义多个常量,而宏只能一个一个定义了。从效率上就可以分出胜负了,并且枚举作为一种类型。是有着严格的类型检查的,所以相对来说,会更加严谨一点。
最后再看看联合体,联合体我们也叫作共用体,是以关键字工会来声明的。一个很重要的特征是,联合体当中所有的成员是公用的同一块空间,可以理解为其他自定义类型中,成员是一个跟在一个后面的排布,而联合体中成员是横排排列的。这样,所有的成员就都是第一个元素,联合体的地址,是所有成员的地址。每个成员的首地址,都是相同的。而整个联合体的大小,则只需要计算所有成员里面占据空间最大的那个就可以了。
union Un
{
int i;
char c;
};