c语言中的自定义类型

阿鲤将以以下顺序总结自定义类型的学习;
一:结构体
1,结构体类型的声明
2,结构体变量的定义和初始化
3,结构的自引用
4,结构体内存对齐
5,结构体传参
6,结构体实现位段(位段的填充&可移植性)
二:枚举
1,枚举类型的定义
2,枚举的优点
3,枚举的使用
三:联合
1,联合类型的定义
2,联合体的特点
3,联合体大小的计算

一:结构体;

1:结构体的声明
eg1:描述一个学生

struct Stusent
{
	char name[20];
	int age;
}stu1;

struct Student 是该结构体的类型;
char name[20]int age 是结构体的成员列表
stu1 是变量的名字

2,结构体变量的定义和初始化
eg1:

struct Point
{
	int x;
	int y;
}p1;//声明类型的同时定义变量p1(全局变量)
struct Point p2;//定义变量p2(局部变量)
struct Point p3 = {1,2 };//定义变量的同时初始化p3(局部变量)

eg2:

struct Stu //类型声明
{
	char name[15]; //名字
	int age;//年龄
};
struct Stu s = { "BelongAl",18 };

int main()
{
	printf("%s\n", s.name);
	printf("%d\n", s.age);
	system("pause");
	return 0;
}

输出结果:

c语言中的自定义类型_第1张图片
(在结构体的指向操作符中分为,点操作符点和箭头操作符,操作符左边的操作数是一个“结果为结构”的表达式; 箭头操作符左边的操作数是一个指向结构的指针)

3,结构的自引用
在介绍结构体的自引用前,我们先看一下这幅图
c语言中的自定义类型_第2张图片
图中的每个大长方形代表一个结构体,而date里存的便是它存储的数据,至于next里保存的是下一个数据的地址,指向下一个结构体,结构体的自引用就是这样实现的;
eg1:

struct Node
{
	int date;
	struct Node *next;
};
int main()
{
	printf("%d\n", sizeof(struct Node));
	system("pause");
	return 0;
}

这个例子中就是自引用的使用,*next指向下一个节点(Node)
运行结果:
c语言中的自定义类型_第3张图片
4,结构体内存对齐
在介绍结构体的内存对齐前,先看一段代码:
eg1:

struct S2
{
	char c1;
	int i;
	char c2;
};
struct S1
{
	char c1;
	char c2;
	int i;
};
int main()
{
	printf("%d\n", sizeof(struct S1));
	printf("%d\n", sizeof(struct S2));
	system("pause");
	return 0;
}

这段代码是计算结构体,S1,S2大小的,运行结果如下:
在这里插入图片描述
看到运行结果大家会发现,明明成员列表内容是一样的,为啥大小不一样呢?
这是因为结构踢体中存在内存对齐,接下来请看这么一段代码;、
eg2:

#include
struct S1
{
	char c1;
	char c2;
	int i;
};
int main()
{
	printf("%d\n", offsetof(struct S1, c1));
	printf("%d\n", offsetof(struct S1, c2));
	printf("%d\n", offsetof(struct S1, i));
	system("pause");
	return 0;
}

这段代码中,offsetof函数是求结构体成员偏移量的,运行结果如下:
c语言中的自定义类型_第4张图片
那为什么ch1,ch2,i、的偏移量是0,1,4,呢?请看下面这幅图

c语言中的自定义类型_第5张图片
在结构体的存储中,它会由系统选择一个字节大小的地址,作为下标为0的地址,其他成员要对齐到他的对齐数的整数倍处。
那么什么是对齐数呢?
对齐数 = 成员的类型大小和系统默认对齐数中的较小值。
eg3:

struct S1
{
	char c1;// 1 8 1  [0]  //类型大小1 默认对齐数8 所以对齐数为1
	char c2;//1 8 1  [1]   //类型大小1 默认对齐数8 所以对齐数为1
	int i;//4 8 4   [4]-[7]   //类型大小为4 默认对齐数为8 所以对齐数为4
}

所起他们的偏移量分别为,1,1,4
那么偏移量和结构体的大小又有什么关联呢?
这里面有这样一个规律,结构体的总大小等于结构体最大偏移量的整数倍;
下来我们再用下图来看回头看eg1:
c语言中的自定义类型_第6张图片
如图,struct S1存储了8字节,其最大偏移量为4,8是4的整数倍,所以struct S1的大小为8;struct S2的存储了9个字节,其最多偏移量为4,所以struct S2的大小是12。

那么,如果是下面这种情况,结构体的大小应该是多大?
eg4:

struct S3
{
	char c1;
	int i;
	char c2;
};
struct S4
{
	char c1;
	struct S3 i;
};
int main()
{
	printf("%d\n",sizeof( struct S3));
	printf("%d\n",sizeof( struct S4));
	system("pause");
	return 0;
}

运行结果如下:
c语言中的自定义类型_第7张图片
在结构体嵌套计算结构体大小要遵循: 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

总结:
1. 第一个成员在与结构体变量偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所
有最大对齐数(含嵌套结构体的对齐数)的整数倍。

(内存对齐的目的:用空间换取时间)

5,结构体传参
请看下面这段代码:

eg1:
struct S
{
	char c1;
	int i;
	char c2;
};
struct S s = { 'A',3,'L' };
void Fun1(struct S s)
{
	printf("%d\n", s.i);
}
void Fun2(struct S *ps)
{
	printf("%d\n", ps ->i);
}
int main()
{
	Fun1(s);
	Fun2(&s);
	system("pause");
	return 0;
}

在这段代码中,有两种传参方式,Fun1和Fun2;Fun1传结构体对象,Fun2传结构体地址;
但是在函数传参时,会有时间和空间上的系统开销。如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降;所以首选Fun2传地址。

6,结构体实现位段(位段的填充&可移植性)
位段是利用结构体所实现的,位段有以下两点要求。
1:位段成员必须是int或unsigned int ;
2:位段的成员名后有一个冒号和一个数字;
那么请看下面一段代码:
eg1:

struct A
{
	int _a : 2;
	int _b : 3;
	int _c : 7;
	int _d : 6;
};
int main()
{
	printf("%d\n", sizeof(struct A));
	system("pause");
	return 0;
}

运行结果:
在这里插入图片描述
eg2:

struct B
{
	char a : 3;
	char b : 4;
	char c : 5;
	char d : 4;
};
int main()
{
	struct B s = { 0 };
	s.a = 10;
	s.b = 12;
	s.c = 3;
	s.d = 4;
	system("pause");
	return 0;
}

那么位段的空间是如何开辟的?请看下图:
c语言中的自定义类型_第8张图片
如图,在vs中,位段在开辟空间时,会在下一个位段接口处开辟,如果一个类型不够,就开辟下一个;
那么它又是怎样存储呢?请看下图:
代码eg2的运行s的内存
在这里插入图片描述
c语言中的自定义类型_第9张图片
在eg2中,首先对其进行了初始化,让s为0,然后对其进行赋值,在vs中赋值时会优先填前面,所以 对应的00110010 = 64 , 再依次是3,4 所以有以上内存;

最后是位段的跨平台问题:
1:位段的最大数目不确定(16位,32为。。。。)
2:存储数字的左右分配规则
3:当下一个位段大于上一个存储单元是,是舍弃还是利用未知;

二:枚举

1,枚举类型的定义
枚举,顾名思义就是一一列举的意思;
eg1:

enum Day//星期
{
	mon,
	tues,
	wed,
	thur,
	fri,
	sat,
	sun,
};
enum Sex//性别
{
	male, 
	female,
	searrt,
};

再eg1中就是对我们日常的枚举,星期,性别…这就是枚举的定义,其中enum Sex,是枚举的类型,male;是枚举的常量(可能取值);

2,枚举的优点

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

3,枚举的使用
eg1:

enum Colur
{
	black = 0,
	Red,
	Green,
	Blue,
};
int main()
{
	int k = 0;
	scanf("%d", &k);
	switch(k)
	{
		case Red:
			printf("红色");
			break;
		case Green:
			printf("绿色");
			break;
		case Blue:
			printf("蓝色");
			break;
		default:
			break;
	}
	system("pause");
	return 0;
}

输出结果:
在这里插入图片描述

三:联合体

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

eg1:

union un
{
	char c;
	int i;
};

其中union un 是联合体的名字,c,i是它的单位成员。

2,联合体的特点
请看下代码;
eg1;

union un
{
	char c;
	int i;
};
int main()
{
	union un x;
	printf("%d\n", &(x.i));
	printf("%d\n", &(x.c));
	x.i = 0x12345678;
	printf("%x\n", x.i);
	x.c = 0x66;
	printf("%x\n", x.i);
	system("pause");
	return 0;
}

运行结果:
c语言中的自定义类型_第10张图片
由运行结果可得,联合体公用地址,所以可以用它来判断计算机的大小端;

3,联合体大小的计算
联合体的大小至小是其最大成员的大小;
当最大成员大小不是最大对齐数的整数倍时,要对其到最大对齐数的整数倍处;
eg1:

union un1
{
	char c[5];
	int i;
};
union un2
{
	short c[7];
	int i;
};
int main()
{
	printf("%d\n", sizeof(union un1));
	printf("%d\n", sizeof(union un2));
	system("pause");
	return 0;
}

运行结果:
在这里插入图片描述
其中最大对其数是int型 = 4;

你可能感兴趣的:(c)