自定义类型总结

简介: 自定义类型总结

前言:

自定义类型作为C语言中可以在一个变量中同时存储多个变量,或者同时存储可选用的多个变量等一系列作用的数据类型,其对于后期数据的掌控和管理起到很重要的作用,下面让我们来总结一下常见的自定义类型变量:

1.枚举体:

枚举一词,顾名思义,即列举的意思,我们可以这样去理解枚举:比如你要买一块面包作为午餐,当然你选择了一家你常去的面包店,对于里面卖的面包价格你了如指掌,货架上现在排列着打特价的面包,而你正要选择其中的一款,你选择了一款并支付对应的价格,这便是枚举体的使用方式。

有了这个例子,我们去深入探索一下枚举体:

枚举体的关键字为enum,其创建用法同结构体相似,如下:

enum p1
{
  MONDAY,//0
  TUESDAY,//1
  WDENESDAY,//2
  SATURDAY,//3
  FRIDAY,//4
  SATURDAY,//5
  SUNDAY//6
};

这便是一个枚举体,首先我们看到,枚举体的每一个成员要以逗号分隔,而不是分号,这一点很特殊,预示着它创建的并不是变量,它更可以说是一种预处理操作。其次,枚举体每个成员变量的本质是一种整型,按照次序从0开始到你创建的变量的结束,这意味着,我们这里的每一个字符并不代表着一个变量,它只是一个整型变量的名字,你可以理解为:预处理:#define 1 MONDAY;诸如此类。

那么枚举体的数字只能按照顺序去依次修改么?

并不是,例如我们可以FRIDAY=12,这样的话,从FRIDAY后续的值就会从12这个数字开始按次序进行,也就是0 1 2 3 12 13 14,这样,我们就可以对里面的数值进行随意的调控。

那枚举体变量的大小是多少呢?

这就要再一次想我们的例子,你去买面包,货架上放着各种类型的面包,但关键在于你只要一个,也就是说,枚举体变量的大小就是一个整型即4字节。

sizeof(enum p1 s)=4!

总结枚举体:

我对枚举体的理解是:枚举实际上可以理解为一种预处理的集合,将整型数字本身按照次序预处理为相应的枚举符号,从而可以让我们直接更具体的知道我们要调用的数字代表着什么意思,但主要,本质上仍为整型数字,而不是对应的符号,故枚举体的大小即为一个整型4个字节的长度。枚举体一般用于对于工程中一些数字的下一步语句控制进行更加具象化的描述,以帮助程序员更好的理解其代码意思。

2.联合体(共用体):

联合体,也叫共用体,关键字为union即为英文联合的意思,我们对联合的理解或许并不清晰,但对于共用体这个名字,我们似乎就知道它的作用了。粗浅的理解就是:对于联合体的成员,他们似乎是共用同一个内存的。

联合体的关键字为union,创建如下:

union p2
{
  int x;
  char c;
};

就如同枚举体一样,其开辟方式基本相同只是换了一个关键字,不过注意,联合体内部存储的是数据类型了,故要用分号隔开,而不是逗号了,它不是预处理操作了。

1.联合体的特点:

1.联合体内部成员是共用一块内存的,故联合体的大小即为成员内开辟所占字节最多的成员的字节数,但当最大对其数的大小不是最大对其数的整数倍的时候,就要对齐到最大对其数的整数倍。

2.联合体由于其共用性,当修改一个成员时,另一个成员也会受到影响,故联合体每次只能修改一个成员。

2.联合体的一个特殊点:

如下:

union p2
{
  short s[5];
  int a;
};

对于这个联合体,它的大小是多少呢?

我们可能会脱口而出5,但其实不然,这里我们要说到:

当联合体内部存在数组时,其对应的最大对其数并不是数组总共的大小与VS默认去比,而是数组内部的每一个元素去与VS默认对其数去比,这个得到的才是这个数组的对其数,但数组的总字节数还是要这个对其数乘元素个数,在这里就是:首先s数组的每一个元素的大小是2字节,VS默认为4故我们取2,2*5=10,int a的大小为4,故其最大对其数要找int a 的最大对其数的整数倍,故为12而不是5。

3.联合体的实际应用:

考虑到实际应用,我们必然要考虑到其特性,联合体每次可以修改一个数据,但由于共用,它一定程度上还节省了很多的空间,由此我考虑到两个情况:

1.大小端分辨:

代码如下:

typedef union p1
{
   int s;
   char a;
};
p1;
int main()
{
  p1 a;
  a.s = 1;
  int m = a.i;
  if (m == 1)
  {
    printf("小端\n");
  }
  else
  {
    printf("大端\n");
  }
  return 0;
}

由于共用,故我们的char访问的就是int的第一个字节,即正好符合我们要找的int第一个字节的存储数据到底是什么,这里使用联合体是非常巧妙的。

2.商品信息修改:

让我们考虑下面这个场景:

要搞一个活动,上线一个礼品兑换单,礼品兑换单中有3种商品:图书,杯子,衬衫,每一种商品固有的信息有:库存量,价格,商品类型,而每种商品还有其独有的一些商品信息:

图书:书名,作者,页数

杯子:设计

衬衫:设计,可选颜色,可选尺寸

让我们分析一下:首先对于这种商品信息繁多的情况,我们想要全盘修改,必然需要利用结构体去创建其信息,然后我们发现,商品分为必要信息的独有信息,这意味着,3种商品的信息做不到同时修改。故我们这样创建结构体:

struct goods
{
   int stock_number;
   double price;
   int item_type;
   union
   {
     struct 
     {
       char title[20];
       char author[20];
       int num_pages;
     }book;
     struct
     {
       char design[20];
     }mug;
     struct
     {
       char design[30];
       int cdors;
       int sizes;
     }shirt;
   }item;
};

我们首先创建一个结构体,内部包括三个基本信息变量,还有一个联合体变量,在联合体变量内部,我们每次要操作一个商品专属信息的结构体,这样节省了空间同时满足我们修改的要求。

3.结构体:

结构体是自定义类型的鼻祖,其实应该安排到第一个位置去讲的,但重头戏总是最后出场,所以我们把结构体放在最后去讲解。

1.结构体的创建:

结构体的关键字为struct ,创建过程如下:

struct p1
{
  int a;
  int b;
  char c;
};

我现在创建的便是一个为struct p1类型的结构体,内部包含三个成员,int a,int b,char c。这里要注意:我们创建的是一个类型,也就是说这个类型的名字应为struct p1而不是struct本身,我们在创建类型,千万别忘了这点。

2.结构体的传参和调用:

1.传参

结构体本身由于其特性,我们传参的时候传指针或者结构体名称都是可以的,但我更偏向于传指针,假设我们一个结构体内部有一个2000个元素的数组呢?我们传结构体本身的话,根据函数栈帧的方式,我们是要为了函数开辟一个2000个元素字节数的内存的,但倘若传入指针,我们仅仅需要一个指针变量大小的空间,就可以直接对结构体实现调整。

同时,我们也要注意一个问题,结构体内部是不允许存自己本身的结构体的,因为这样的话内存大小是接近无限大的,会爆栈。

但传自身结构体的指针是可以的,在数据结构的链表和二叉树,在结构体内部存储结构体指针是很常见的方式,也很重要。

2.调用:

调用的逻辑很简单,结构体本身的名字就用点号来访问成员,传入指针的名字就用->来访问成员。

3.结构体的内存对齐规则:

  1. 结构体的第⼀个成员对⻬到相对结构体变量起始位置偏移量为0的地址处
  2. 其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。
    对⻬数 = 编译器默认的⼀个对⻬数 与 该成员变量⼤⼩的较⼩值。
  • VS中默认的值为8
  • Linux中没有默认对⻬数,对⻬数就是成员⾃⾝的⼤⼩
  1. 结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的
    整数倍。
  2. 如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构
    体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍。
    #progma()可以修改默认对齐数,当不传参时,#progma又会恢复其默认的对其数。
    offsetof(结构体成员),可以用来计算结构体成员偏移量。
    **注意:结构体的对齐操作,是一种以空间换时间的做法,方便资源包等一系列数据的存储和读取,内存是不支持一个数据类型跨两个存储单元的。
    **

4.位段:

注意:位段的位是二进制位的意思,位段的段即分段的意思,所以位段就是一种用来分隔二进制位使其分段的类型,其写法如下:

struct A
{
  int _a : 2;
  int _c : 3;
  char _s : 6;
};

位段最突出的特点是,冒号加数字。表示其实际存储这个数据到底需要几个二进制位,位段的成员必须是 int、unsigned int 或signed int ,在C99中位段成员的类型也可以是其他类型的成员。位段的开辟方式是:位段从右向左存储,且不会跨两个内存块存储数据,所以位段不满就会开辟一个新的存入。

由于位段的几个成员有时共有一个字节,这样有些成员的起始位置并非字节为单位,而是二进制位为单位,所以这些位置的地址是访问不到的,所以对于位段的成员不能使用&符号,也不能使用任何传指针的函数,例如scanf这种的函数,只能先将其放在一个为位置里,然后赋值给位段中的数据,或者直接赋值给其一个定值。(注意,指针访问的最基本的单位是一个字节)

4.总结:

自定义类型是C语言中很重要的一个数据类型,它赋予数据更大的灵活性和强大的统筹数据的能力,特别是结构体,对于后期学习数据结构以及其他更为优秀的算法方面有重要的作用。

5.补充:

struct p1

{

int a;

int c;

}P:

自定义类型中P的位置是可以直接给这个P赋予一个结构体或者其他自定义类型并且作为全局变量来使用。

倘若没有P,也不给自定义类型赋予名字,则这个结构体类型只能使用一次,否则即为违规操作,故我们使用结构体或者其他自定义类型去操作的时候最好的习惯是写上名字使用,或者使用typedef也可以。

目录
相关文章
|
1月前
|
存储 Linux C++
自定义类型讲解
自定义类型讲解
67 0
|
5天前
|
存储 编译器 Linux
自定义类型详解(1)
自定义类型详解(1)
22 5
|
5天前
自定义类型详解(2)
自定义类型详解(2)
22 1
|
1月前
|
存储 移动开发 API
其他内置类型
本文介绍了 .NET 中的 Console 类和 Environment 类。Console 类提供了控制台输入输出的功能,如设置背景色和前景色、打印文本、读取行和发出蜂鸣声。而 Environment 类则包含有关全局环境的信息和方法,如当前目录、进程路径、处理器数量、操作系统信息等。另外,文章还提及了 .NET Framework 的 AppDomain(用于表示应用程序域,但在 .NET Core 中功能减弱)和 .NET Core 中新引入的 AppContext 类,用于存储全局数据和开关。
|
29天前
|
编译器 Linux C++
自定义类型详解
自定义类型详解
|
1月前
|
编译器 C++
自定义类型
自定义类型
|
1月前
|
C++
c++基本内置类型
c++基本内置类型
34 0
|
7月前
|
存储
自定义类型超详细解答!!!!!(下)
自定义类型超详细解答!!!!!
|
7月前
|
编译器 C++
自定义类型超详细解答!!!!!(上)
自定义类型超详细解答!!!!!
|
11月前
|
编译器 Linux C语言
自定义类型详解(上)
自定义类型详解(上)
自定义类型详解(上)