C语言——自定义类型

本文探讨了结构体的声明、变量定义、内存对齐及优化,包括匿名结构、指针结构和自引用。同时讲解了位段的概念、内存分配、跨平台问题,以及枚举的使用和联合(共用体)的特性。最后介绍了如何通过调整对齐和结构设计来提高程序效率。
摘要由CSDN通过智能技术生成


前言

结构体,
枚举,
联合。


提示:以下是本篇文章正文内容,下面案例可供参考

结构体

1、结构体的声明

1.1 结构

  • 结构是一些值的集合,
  • 这些值称为 成员变量。
  • 结构的每个成员可以是 不同类型的变量。

1.1.2 结构的声明

  • struct关键字
  • 描述一个学生
  • 可以不用初始化
  • 在main外创建的结构体变量是全局变量
  • main里面创建的结构体变量是局部变量
struct Stu	没有Stu的话就是匿名结构体类型
{
	char name[20];	名字
	int age;		年龄 
	char sex[5];	性别
	char id[20];	学号
}s1,s2;	这个是struct Stu 结构体类型的变量
		最后一定要加分号

1.1.3 匿名结构

匿名结构体类型只能使用一次

struct 	没有给标签的话是匿名结构体类型
{
	char name[20];	名字
	int age;		年龄 
	char sex[5];	性别
	char id[20];	学号
}s1,s2;	

int main()
{
	return 0;
}

1.1.4 指针结构

struct 	
{
	char name[20];	名字
	int age;		年龄 
}a[20],* p;	

int main()
{	
	return 0;
}

1.1.5 结构的自引用

  • 结构体的字引用内不能自己包含自己 ❌
struct Node
{
	int data;
	struct Node;
};
  • 可以包含一个同类型的结构体指针 √
struct Node
{
	int data;
	struct Node* next;
};
  • 还有这样的重命名是可以的 √
第一种
typedef struct Node
{
 int data;
 struct Node* next;
}*linklist;

第二种
struct Node
{
 int data;
 struct Node* next;
};
typedef struct Node* linklist;

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

  • 定义并赋初值
struct point
{
	int x;
	int y;
}p1 = {2,3};	创建一个p1变量赋初值x = 2,y = 3,
  • 也可以在main里面定义变量赋初值
struct point
{
	int x;
	int y;
}p1 = {2,3};	创建一个p1变量赋初值x = 2,y = 3,
struct Stu
{
	char name[20];
	int age;
};

int main()
{
	struct Point p2 = {3,4}	;
	struct Stu s1 = {“zhangsan",20};	
	return 0;
}
  • 结构体嵌套并打印
struct score
{
	int n;
	char ch;
};
struct Stu
{
	char name[20];
	int age;
	struct score s;
};

int main()
{
	struct Point p2 = {3,4}	;
	struct Stu s1 = {“zhangsan",20,	{100,'q'}};
	嵌套初始化时要加上 {}	
	打印:
	printf("%s %d %d %s\n",s1.name, si.age, s1.s.n, s1.s.ch );	
	return 0;
}

1.3结构体内存对齐

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

  • S1的内存分布
  • 偏移量的分别是0 4 8
    在这里插入图片描述
struct S1
{

	char c1;
	int i;
	char c2;
};
struct S2
{

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


  • offsetof
  • 一个结构体成员在这个类型创建的变量中的偏移量
  • 要引用<stddef.h>头文件
  • S2的内存分布
  • 偏移量是 0 1 8
    在这里插入图片描述
struct S1
{

	char c1;
	int i;
	char c2;
};
struct S2
{

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

在这里插入图片描述


嵌套结构体的大小

  • 嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,
    结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

在这里插入图片描述

#include <stddef.h> 

struct S1
{
	double d;
	char c;
	int i;
};
struct S2
{
	char c1;
	struct S1 s1;
	double d;
	
};
int main()
{
	printf("%d\n", offsetof(struct S2, c1));
	printf("%d\n", offsetof(struct S2, s1));
	printf("%d\n", offsetof(struct S2, d));
	printf("%d\n", sizeof(struct S2));
	return 0;
}
  • 总的来说: 结构体的内存对齐是拿空间来换取时间的做法。
  • 所以我们在设计结构体时,我们既要满足对齐,又要节省空间。
  • 让占用空间小的成员尽量集中在一起。
  • 例子:
  • 这两个结构体定义的成员类型一样但是所占内存空间就不同
    在这里插入图片描述
struct S1
{
	char c1;
	int i;
	char c2;
};
struct S2
{
	char c1;
	char c2;
	int i;
};
int main()
{
	printf("%d\n", sizeof(struct S1));
	printf("%d\n", sizeof(struct S2));
	return 0;
}

1.3.1 修改默认对齐数

  • pragma pack(1)设置默认对齐数为1
  • pragma pack()取消设置的默认对齐数,还原为默认

在这里插入图片描述

#pragma pack(1)//设置默认对齐数为1
struct S1
{
 char c1;
 int i;
 char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
int main()
{
	printf("%d\n",sizeof(struct S1));
	return 0;
}

1.4 结构体传参

  • 如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,会导致性能的下降。
  • 首选传址 用指针接收
  • 怕误操作修改加const
struct S
{
	int data[1000];
	int num;

};
void print1(struct S ss)
{
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("%d ",ss.data[i]);
	}
	printf("%d\n", ss.num);
}


void print2(const struct S* ps)
{
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("%d ", ps->data[i]);
	}
	printf("%d\n", ps->num);
}
int main()
{
	struct S s = { {1,2,3},100 };
	print1(s);
	首选传址
	print2(&s);
	return 0;
}

位段

2 什么是位段

  • 位段的声明和结构是类似的,有两个不同
  • 位段的成员必须是int、unsigned int、signed int
  • 位段的成员后边有一个冒号和一个数字

这就是一个位段
后面数字是bit ( 位 )

struct A
{
	int _a:2;
	int _b:5;
	int _c:10;
	int _d:30;
} ;

2.1位段的内存分配

  1. 位段的成员可以是int.unsigned int signed int char(整形家族)类型。
  2. 位段的孔家你上是按照类型4个(int)或者1个(char)字节的方式来开辟的。
  3. 位段是不跨平台的,注重可移植的程序应该避免使用位段。
  • 位段也有内存对齐,对齐是以类型为准
  • 比如一个
    char _a : 5
    char _b : 4
  • 这个需要2个字节来存储
  • 因为char类型只能放下8位,_a和_b有九位
  • 所以第一个字节存放完_a后剩下的位数会空出来再开辟一个存放_b
    在这里插入图片描述
    见上图⬆
struct A
{
	4byte == 32bit
	int _a : 2;
	int _b : 5;
	int _c : 10;
	用掉17bit
	剩下15bit
	重新开辟
	
	4byte == 32bit
	int _d : 30;
};
//47
int main()
{
	printf("%d\n", sizeof(struct A));
	return 0;
}

第二个例子
在这里插入图片描述

  • 00000000 —— s.a = 10,_a : 3; s.b = 12, _b :4;
  • 00000000 —— 存放s.c = 3 _c : 5
  • 00000000 —— 存放s.d = 4 _d : 4
  • 三个字节每个字节从右往左存放
  • 第一个字节01100010、第二个字节00000010、第三个字节00000100计算的结果和上图一样(十六进制)
  • 解析(配合代码看):
  • 这四个要存放的数字的二进制为1010 — 10、1100 — 12、0011 — 3、0100 — 4
  • 1010只能放3位所以取010,1100能放四位所以结果为01100010
  • 这是第一个字节
  • 0011能放5位 放不够所以补满 00011 剩下三位放不满后一个所以结果是00000011
  • 这是第二个字节
  • 0100要放4位上面一个放不进去所以在开辟一个 00000100
  • 这是第三个字节
struct S
{
	char a : 3;
	char b : 4;
	char c : 5;
	char d : 4;
};
int main()
{
	struct S s = { 0 };
	s.a = 10;	//二进制为1010
	s.b = 12;	//1100
	s.c = 3;	//0011
	s.d = 4;	//0100
	 
	return 0;
}

2.3 位段的跨平台问题

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

枚举

枚举顾名思义就是一 一列举

3 枚举类型的定义

enum
默认起始位置为0
可以设置为其他

在这里插入图片描述

enum Day
{
	Mon,
	//MOn = 1,
	Tues,
	Wed,
	Thur,
	Fri,
	Sat,
	Sun
};
int main()
{
	printf("%d\n", Mon);
	printf("%d\n", Tues);
	printf("%d\n", Wed);
	printf("%d\n", Thur);
	printf("%d\n", Fri);
	printf("%d\n", Sat);
	printf("%d\n", Sun);

	return 0;
}

3.1 枚举的使用

enum A
{
	EXit,
	ADD,
};
int main()
{
	switch ()
		{
			case EXit:
				break;
			case ADD:
				break;
			default:			
				break;
		}
}

联合(共用体)

联合也是一种特殊的自定义类型
关键字union

4.1 联合类型的定义

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

    在这里插入图片描述
union Un
{
	int a;
	char c;
};
int main()
{
	union Un n;
	printf("%d\n", sizeof(n));
	printf("%p\n", &n);
	printf("%p\n", &(n.a));
	printf("%p\n", &(n.c));
	return 0;
}

判断计算机大小端 ( 普通方法 )

//判断当前计算的大小端存储
int check_sys()
{
	int a = 1;
	return* (char*)&a;
}
int main()
{
	int ret = check_sys();
	if (ret == 1)
		printf("小端\n");
	else
		printf("大端\n");
	return 0;
}

判断计算机大小端( 联合体方法 )

int check_sys()
{
	union	//匿名类型:只能用一次
	{
		char c;
		int i;
	}u;
	u.i = 1;
	return u.c;
}
int main()
{
	int ret = check_sys();
	if (ret == 1)
		printf("小端\n");
	else
		printf("大端\n");

	return 0;
}

联合体大小的计算

在这里插入图片描述

  • 为什么会有八个字节呢?
  • 因为联合体也有对齐。
  • 但是char数组用了5个字节剩三个,
  • int类型也用不了,
  • 所以最后是和char共用那五个字节。
union Un
{
	char arr[5];
	int i;
};
int main()
{
	printf("%d\n", sizeof(union Un));
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值