自定义类型的总结

在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,也只需一步就能读够所要的四个字节。速度上我就可以提高,这在某些需要性能的程序上是很重要的。

所以总的来说,内存对齐,就是一种用空间换时间的做法。至于深入的探究,这里暂时先不讨论。

位段,位段计算机大小

位段是一种与结构体很类似的自定义类型,在声明上,主要有两个特点:

  1. 位段成员必须是int,unsigned int或signed int也或者char(即整型家族);
  2. 位段成员名后面有一个冒号和一个数字(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;         

};

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值