当前位置:   article > 正文

看了这篇自定义数据类型讲解还不会,可以放下你手中的键盘了

自定义数据类型

目录

什么是自定义数据类型

结构体(struct)

枚举(enum)

联合体(union)


什么是自定义数据类型

自定义数据类型就是用户可以随时在程序中自行定义新的数据类型。自定义数据类型时需要设置数据类型的名称及其成员。数据类型成员各属性的设置方法等同于变量设置时相应属性的设置方法。

C中定义数据类型是为了容易编程,定义了数据类型之后又定义了数据类型之间的各种运算,这样对编程人来说就会方便很多。

在C语言中自定义数据类型分为结构体类型、枚举类型、联合体类型和typedef。

结构体(struct)

平常我们定义一个变量只能赋予它一种数据类型,例如一个数组一旦定义为int类型,那么他里面所有的元素都是整型,如果此时想塞一个浮点类型的数据进入数组就会出现问题

  1. #include<stdio.h>
  2. int main()
  3. {
  4. int arr[3]={1,2};
  5. arr[2] = 4.1;
  6. for (int i = 0; i < 3; i++)
  7. {
  8. printf("%d ", arr[i]);
  9. }
  10. return 0;
  11. }

此时系统提示warning C4244: “=”: 从“double”转换到“int”,可能丢失数据。我们调试看一下会发现arr[2]=4,造成了数据损失;无法存入其他类型的数据。

在实际应用中,我们通常需要由不同类型的数据来构成一个整体,比如学生这个整体可以由姓名、年龄、身高等数据构成,这些数据都具有不同的类型,姓名可以是字符串类型,年龄可以是整型,身高可以是浮点型。

面对这种问题,C语言提供了一种结构体类型来解决此问题,它内部成员变量可以是不同数据类型。

声明一个结构体

  1. struct stu
  2. {
  3. //结构体成员变量
  4. char name[20];//姓名
  5. char sex;//性别
  6. int age;//年龄
  7. float height;//身高
  8. }s1,s2;//s1,s2全局变量

struct是关键字,stu为声明结构体名,括号里面的为结构体成员变量,可以是不同数据类型,紧挨着结构体声明的s1,s2为结构体全局变量,如果在其他函数内声明struct stu s即为结构体局部变量,结构体变量可以调用该结构体内部成员。如果结构体没有名字怎么办呢?

匿名结构体

  1. struct
  2. {
  3. //结构体成员变量
  4. char name[20];
  5. char sex;
  6. int age;
  7. }s1,s2;//该结构体只有s1,s2两个结构体变量,无法以其他方式定义

此时该结构体只有s1,s2两个全局变量,并且无法以其他方定义。

结构体变量初始化

  1. //声明一个结构体类型stu
  2. struct stu
  3. {
  4. //结构体成员变量
  5. char name[20];
  6. char sex;
  7. int age;
  8. };
  9. int main()
  10. {
  11. struct stu s3 = { "wu yan zu",'m',47 };
  12. struct stu *pa = &s3;
  13. printf("%s,%c,%d\n", s3.name, s3.sex, s3.age);
  14. printf("%s,%c,%d\n", pa->name, pa->sex, pa->age);
  15. return 0;
  16. }

声明一个结构体变量,初始化结构体变量内的成员信息,采用 . 的方式;如果定义的是结构体指针,采用->的方式。注意s3.name,pa->name,是数组首元素地址。如果只申明一个结构体指针而没有指向任何地方,此时是不能访问成员变量对他进行任何操作,如果没有初始化,指针里面存的是一个随机地址,那就非法访问内存空间。

结构体的大小

我们知道,不同数据类型都有他自己的大小,int占用4个字节,char占用一个字节等。那么结构体如何计算他本身的大小呢?

  1. struct stu
  2. {
  3. //结构体成员变量
  4. char name[20];
  5. char sex;
  6. int age;
  7. };

上述代码占用大小是20+1+4=25吗?

显然不是,使用sizeof得到的结果为28。sizeof的用法见详解C语言中sizeof的使用_TangguTae的博客-CSDN博客

因为存在结构体内存对齐

引入一个概念叫对齐数

结构体在存储时,需要根据每个成员变量自己的对齐数并按照一定规则来存储。

首先VS下系统默认的最大对齐数为8,gcc下没有对齐数,默认为本身大小。

如何计算每个成员变量的对齐数

根据本身的字节大小与VS默认的最大对齐数进行比较,选取小的那个作为该成员的对齐数。

依然用上述例子,int age;int类型占用4个字节,4小于最大对齐数8,所以他的对齐数为4。同理char sex 的对齐数为1。

对于第一个成员char name[20],数组的对齐数为该数组元素类型来计算,而不是数组大小来计算,char类型所以他的对齐数为1。

拥有了他们的对齐数,此时开始在内存中存储,从第一个成员char name[20]开始,占用20个字节;然后存第二个成员char sex,他的对齐数是1,可以被前面所占用的字节数整除,所以直接存储,占用一个字节;最后第三个成员int age对齐数为4,但是前面占用的字节数为21,不能被对齐数整除,所以需要额外空出3个字节从第25个字节处开始存储,占用四个字节,总共占用28个字节,到这里并没有结束,还要判断现在所占用的字节数是否能被这几个成员中最大对齐数整除,如果不能,还需要额外开辟空间使其能被整除,28可以被4整除,所以最终该结构体占用28个字节。

最终内存相对位置如图所示

结构体嵌套结构体如何计算内存空间大小

这里只需要注意结构体的对齐数为其成员最大对齐数,其他的过程和上面所讲的相同。读者可自寻理解下面的代码段。

  1. struct S3
  2. {
  3. //结构体成员变量
  4. double d;//8个字节
  5. char c;//1个字节
  6. int i;//对齐数4,从第13个字节开始存储,占用4个字节
  7. };//总共16个字节
  8. struct S4
  9. {
  10. char c;//1个字节
  11. struct S3 a;//这里的对齐数是指自己内部成员最大的对齐数,所以对齐数为8,从第9个字节开始存储,占用16个字节
  12. double d;//对齐数为8,从第25个字节开存储,占用8个字节
  13. };//总共32个字节

这里额外补充一个知识,为什么会存在内存对齐

1、平台原因

不是所有的硬件平台都能访问任意地址上的任意数据;某些硬件平台在某些地址处取某些特定类型的数据,否则抛出硬件异常。

2、性能原因

数据结构应该尽可能地在自然边界上对齐。原因在于为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。假设在32位平台上,32根数据线,每次cpu读取内存访问4个字节,如果此时不采用对齐的方式,char c,int a;只分配5个字节的空间,如果要拿取c,a,从c开始读,第一次四个字节包括c和a的前半部分,取出c,第二次读后四个字节,取出a的后半部分还得进行拼接。如果采用对齐的方式,取出a就直接是完整的a。读取效率更高了。

综上其根本就是采用空间换时间的做法。现在内存都可以做的很大,一般电脑标配都达到8G,为了追求性能牺牲一点内存问题不大。

结构体的传参

对于函数的传参,既可以是结构体变量,也可以是结构体指针。如果传结构体变量,则相当于在栈区开辟一整个结构体大小的字节的内存,浪费资源,建议作为参数还是传结构体指针比较好,因为只需要开辟4个字节的空间,节约资源。

结构体中的位段

1、位段的成员可以是int, unsgined int ,signed int 或者char(属于整型家族)类型。

2、位段的空间上是按照需要,并以4个字节(int)或者1个字节(char)的方式来开辟的。

3、位段涉及很多不确定因素,位段是不跨平台,注重可移植的程序应该避免使用

  1. struct S
  2. {
  3. int a : 2;
  4. int b : 3;
  5. int c : 10;
  6. int d : 5;
  7. };

如何分配内存大小,a:2说明a只需要2个bit位,所以结构体内部总共只需要20位,因为是int类型,所以只需要4个字节就能满足。

  1. struct S
  2. {
  3. int a : 2;
  4. int b : 5;
  5. int c : 10;
  6. int d : 30;
  7. };

这个计算出来需要47个bit位,所以4个字节不够用,直接增加到8个字节。


枚举(enum)

如果前面结构体的内容理解了,下面来理解枚举类型、联合体都很轻松了。

枚举就是列出所有可能的取值,例如一个人的性别可能有三种,男,女和中性

  1. enum sex
  2. {
  3. //所有可能的取值,常量
  4. MALE,
  5. FEMALE,
  6. SECRECT
  7. };

定义一个枚举类型的变量enum sex s=MALE;

初始值

 MALE,

 FEMALE,

 SECRECT分别为0,1,2

如果要修改,只能在枚举类型里面修改,在外部无法修改,因为是常量

  1. enum sex
  2. {
  3. //所有可能的取值,常量
  4. MALE=2,
  5. FEMALE=4,
  6. SECRECT
  7. };

SECRECT的值就在FEMALE的值上自增1。

枚举类型的变量占用四个字节。

枚举的优点(与#define的区别)

1、可读性强

2、enum是一种类型,而#define声明的没有具体类型,所以枚举可以进行类型检查

3、防止命名污染

4、便与调试

5、使用方便,一次定义多个常量


联合体(union)

是一种特殊的自定义类型,里面所有的成员公用一块空间

所以,在同一时刻,不能同时使用,不然会互相干涉

  1. union U
  2. {
  3. int age;
  4. char a[5];
  5. }

内存大小:

联合变量的大小至少是最大成员的大小。

当最大成员大小不是联合体内成员最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。

上述例子最大对齐数为4,而最大成员为5,所以内存大小为8个字节。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/518921
推荐阅读
相关标签
  

闽ICP备14008679号