自定义类型详解(结构体,枚举,联合体)

1. 结构体struct

结构体是由一批数据组合而成的一种新的数据类型。组成结构型数据的每个数据称为结构型数据的“成员”。

1.1 结构体类型的声明

struct 结构体名
{
	成员列表;
}

例:声明一个学生结构体类型,其中包含学生的姓名,学号,年龄,性别,地址。

struct Student
{
	char NAME[20];
	char Number[11]
	char sex[3];
	int age;
	char address[20];
}

特殊的结构体声明:匿名结构体类型
在声明结构体时,不指出结构体名。当然由于没有具体的名字,所以该类结构体只能在声明的时候定义。
例如:

struct
{
	int a;
	char b;
	float c;
}x;
struct
{
	int a;
	char b;
	float c;
}a[20], *p;

1.2 结构体的自引用

错误的自引用方式:

struct Node
{
	int vale;
	struct Node node;
}
  • 这种声明是错误的,因为这种声明实际上是一个无限循环,成员node是一个结构体,node的内部还会有成员是结构体,依次下去,无线循环。在分配内存的时候,由于无限嵌套,也无法确定这个结构体的长度,所以这种方式是非法的。

正确的自引用方式:

struct Node
{
	int vale;
	struct Node *Next;
}
  • 由于指针的长度是确定的(在32位机器上指针长度为4),所以编译器能够确定该结构体的长度。

1.3 结构体变量的定义和初始化

定义结构体变量
示例1:定义结构体变量不初始化。

#include<stdio.h>
struct Node
{
	int value1;
	int Value2;
}
int main
{
	 int Vale;
	 return 0;
}

示例2.定义结构体变量并初始化。

#include<stdio.h>
struct Node
{
	int value1;
	int Value2;
}
struct Student
{
	char NAME[20];
	int age;
}
struct Point1
{
	struct Node node;
	int a;
	struct Student student;
}
struct Point2
{
	int b;
	struct Point2 *next;
}
int main
{
	 struct Node node={12};
	 struct Student student={"wangzhen",21};
	 struct Point1 point1={{1,2},3,{"wangzhen",21}};
	 struct Point2 point2={1,NULL};
	 return 0;
}

1.4 结构体内存对齐

近年来的热门考点,让你计算一个结构体所占内存大小。
也就是考察我们对内存对齐的概念的理解。
结构体内存对齐规则:

  • 结构体中元素是按照定义顺序一个一个放到内存中去的,但并不是紧密排列的。从结构体存储的首地址开始,每一个元素放置到内存中时,它都会认为内存是以它自己的大小来划分的,因此元素放置的位置一定会在自己宽度的整数倍上开始(以结构体变量首地址为0计算)。
  • 在经过第一原则分析后,检查计算出的存储单元是否为所有元素中最宽的元素的长度的整数倍,是,则结束;若不是,则补齐为它的整数倍。
//练习1
struct S1
{
	//所有元素中最宽的元素为(int)类型,占四个字节
	char c1;//1+3
	int i;	//4
	char c2;//1+3
};
printf("%d\n", sizeof(struct S1));//输出12,1+3+4+1+3=12.

在这里插入图片描述

//练习2
struct S2
{
	//所有元素中最宽的元素为(int)类型,占四个字节
	char c1;//1
	char c2;//1+2
	int i;//4
};
printf("%d\n", sizeof(struct S2));//输出8,1+1+2+4=8.

在这里插入图片描述

//练习3
struct S3
{
	double d;//8
	char c;//1
	int i;//1+6,//由于结构体存储单元需要为其最宽成员的整数倍,所以应该补6个字节
};
printf("%d\n", sizeof(struct S3));//输出16

在这里插入图片描述

//练习4-结构体嵌套问题
struct S4
{
	char c1;
	struct S3 s3;
	double d;
};
printf("%d\n", sizeof(struct S4));//输出32

为什么要内存对齐?

  • 平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能
    在某些地址处取某些特定类型的数据,否则抛出硬件异常。
  • 性能原因: 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的
    内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
    总的来说,结构体对齐就是拿着空间换时间,在没有特殊要求得情况下尽量把空间小得成员集中在一起,有助于节省空间。

1.5 结构体传参

#include<stdio,h>
#include<string.h>
typedef struct Student
{
	char Name[10];
	int num;
}Student;
void test01(Student s)//结构体传参
{
	printf("the student name is %s",s.Name);
}
void test02(Student* s)//结构体指针传参
{
	printf("the student name is %s",s->Name);
}
int main()
{
	Student student={"wangwu",10086};
	test01(student);//传结构体
	test02(&student);//传结构体的地址
}

test01与test02相比,明显test02更快,因为函数在传参的时候,参数是需要压栈,会有时间和空间上的系统开销。如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。而传结构体地址的时候,就只有四个字节(64位8个字节)的内容需要压栈。所以test02更快。

综上:结构体传参的时候要传地址。因为传地址更快,效率更高。

1.6 位段

什么是位段
C语言允许在一个结构体中以位为单位来指定其成员所占内存长度,这种以位为单位的成员称为“位段”或称“位域”( bit field) 。利用位段能够用较少的位数存储数据。
位段的声明

struct s
{
	int a:2;
	int b:5;
	int c:10;
	int d:30;
};

位段的内存分配:

  1. 位段的成员可以是 int unsigned, int ,signed int 或者是 char (属于整形家族)类型
  2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
  3. 位段涉及很多不确定因素,位段是不跨平台的,注意:可移植的程序应该避免使用位段。
printf("%d",sizeof(s));//占8个字节
//因为位段的成员是int类型,是按照四个字节的方式来开辟的,a,b,c所占空间总共是17个bit,
//剩余的空间无法存储d,只能另外开辟四个字节的空间来存放d,
//所以改结构体s所占空间为8个字节。

例:

#include<stdio.h>
#include<Windows.h>
typedef struct S
{
	char a : 3;
	char b : 4;
	char c : 5;
	char d : 4;
}S;
int main()
{
	S s = {0};
	s.a = 10;
	s.b = 12;
	s.c = 3;
	s.d = 4;
	printf("%d", sizeof(S));
	system("pause");
	return 0;
}

在这里插入图片描述
位段不能跨平台的原因是

  1. int 位段被当成有符号数还是无符号数是不确定的。
  2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。
  3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
  4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。
    与结构体相比,位段可以达到同样的效果,但位段跨平台会引起各种问题的出现

2. 枚举enum

枚举:在数学和计算机科学理论中,一个集的枚举是列出某些有穷序列集的所有成员的程序,或者是一种特定类型对象的计数。

2.1枚举类型的定义

enum Day//星期
{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
enum Sex//性别
{
MALE,
FEMALE,
SECRET
};

以上定义都是枚举类型,{}中的内容是枚举类型的可能取值,也叫枚举常量。
这些值默认从一开始,依次递增1,同样也可以在定义的时候赋初值。

2.2枚举的优点

  1. 增加代码的可读性和可维护性
  2. 和#define定义的标识符比较枚举有类型检查,更加严谨。
  3. 防止了命名污染(封装)
  4. 便于调试
  5. 使用方便,一次可以定义多个常量

2.3枚举的应用

enum Day//星期
{
	Mon,
	Tues,
	Wed,
	Thur,
	Fri,
	Sat,
	Sun
};

3. 联合(共用体)union

联合也是一种特殊的自定义类型 这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)。

3.1联合类型的定义

union Un//联合体声明
{
	char c;
	int i;
	short s;
};
int main()
{
	union Un un;//联合体定义
}

3.2联合的特点

联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)

#include<stdio.h>
union Un//联合体声明
{
	char c;
	int i;
	short s;
};
int main()
{
	union Un un;//联合体定义
	printf("%p\n",&un.c);
	printf("%p\n",&un.i);
	printf("%p\n",&un.s);
	un.i=0x11223344;
	un.c=0x00;
	un.s=0x1111;
	printf("%d\n",un.i);
	return 0;
}

在这里插入图片描述

3.3联合所占内存大小的计算

  • 联合的大小至少是最大成员的大小。
  • 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

RONIN_WZ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值