自定义类型:结构体,位段,枚举和联合

C语言有内置类型

如  char , short , int,  long,long long, double,  float

也支持自定义类型

结构体   枚举    联合体

 

结构体

关于结构体的声明和变量的创建

数组是相同类型的元素的集合

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

dd3434f3eeff413aa09057ce3e54d262.png

 

结构体类型  struct Student

结构体成员变量   name,  age,    sex,

结构变量    s1,s2,s3,s4,s5,其中s1和s2是结构体全局变量,s3,s4和s5是结构体局部变量

结构体的特殊声明

05e1e80f7f9f459597c5f3c86b7355ae.png  这里省略了结构体的名字Student,也被称之为匿名结构体类型

这种结构体不能创建结构体局部变量,只能在结构体后面创建全局变量,如s1,s2

 

结构体的自引用

在结构中包含一个类型为该结构本身的成员是否可以呢?

结构体中包含在自己这个结构体创建的指针,就是结构体的自引用36ad00caae89474a9759e8bee82f32ac.png

 

结构体的定义和初始化

fa1749bea6f1469a9b25a02431fc7447.png

 

结构体内存对齐

首先得掌握结构体的对齐规则:

1. 第一个成员在与结构体变量偏移量为0的地址处。

2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 VS中默认的值为8

3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。

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

内存对齐的主要目的是为了效率

例题一:

创建两个结构体,结构体的成员变量相同,但是排列顺序不同,问占用内存是否相同

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

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

int main()
{
	//结构体的成员变量类型都相同但占用字节大小不同
	printf("%d\n", sizeof(struct S1));
	printf("%d\n", sizeof(struct S2));
return 0;
}

 

229398442df54b858dda882d097ad597.png913d09dba4f84d88bf32327c101c4e65.png

e17bb3575559491d8ac1c8e99b038fcd.png

 

例题二

对于结构体的嵌套,求结构体占用内存

struct S3
{
	double d;
	char c;
	int i;
};

//结构体的嵌套
struct S4
{
	char c1;
	struct S3 s3;
	double d;
};

int main()
{
	////结构体的成员变量类型都相同但占用字节大小不同
	//printf("%d\n", sizeof(struct S1));
	//printf("%d\n", sizeof(struct S2));


	printf("%d\n", sizeof(struct S3));

	printf("%d\n", sizeof(struct S4));

	return 0;
}

这里最大对齐数是8,那么结构体字节数必须大于等于16且是8的倍数

2a28fc8f7df648648912aca12af94d2c.png

e89ffda1a372426681f49c32698d5c5a.png

 

9a9a57505d204696a43bdb819e7c88b0.png

修改默认对齐数

默认对齐数的修改一般都是2的次方

#pragma pack(1)//设置默认对齐数为1
struct S1
{
	char c1;
	int i;
	char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认


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

	return 0;
}

 

dddeb9f31a5c4d1e828ca280b76aad9a.png结构体传参

结构体传参一般会使用传址

函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。

如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的降低

struct S
{
	char arr[1000];
	int num;
};

void print1(struct S s1)
{
	printf("传值调用%d  %d  %d  %d\n", s1.arr[0], s1.arr[1], s1.arr[2], s1.num);
}

void print2(const struct S* p)
{
	printf("传址调用%d  %d  %d  %d\n", p->arr[0], p->arr[1], p->arr[2], p->num);
}


int main()
{
	struct S s1 = { {1,2,3},100 };

	print1(s1);//这里传递的是s1变量,函数在打印时会重新开辟空间,设置变量

	print2(&s1);//这里是传址调用,不需要开辟空间

	return 0;
}

41d97202a18046fa971b4a436cf1966d.png

2.位段

位段与结构体相比可以很好的节省空间,但是存在着明显缺陷(难以跨平台)

 

2.1什么是位段

 

位段和结构体类似

主要区别有两个

1.位段的成员必须是  int,  unsigned int,  或者signed  int(在C99之后也有其他类型,但主要是 int和char)

2.位段的成员后面有一个冒号和一个数字

//struct A 是位段,而struct B 是结构体
struct A
{
	int _a : 2;//这里的:2表示_a 变量占用2个比特位
	int _b : 5;//5个比特位
	int _c : 10;//10个比特位
	int _d : 30;//30个比特位
};

struct B
{
	int _a;
	int _b;
	int _c;
	int _d;
};

int main()
{
	printf("位段占用的空间%d\n", sizeof(struct A));
	printf("结构体占用的空间%d\n", sizeof(struct B));

	return 0;
}

7f3455d8b0754082bcd64ec194fb51d9.png

 

2.2位段的内存分配(在VS编译器中)

1. 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型

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

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

struct S
{
	char a : 3;
	char b : 4;
	char c : 5;
	char d : 4;
};

int main()
{
	struct S s = { 0 };
	s.a = 10;
	s.b = 12;
	s.c = 3;
	s.d = 4;

	return 0;
}

874d88fc0ab74e24a10c792885eeb7ad.png

 

3.枚举

也就是枚举类型

枚举就是把可能的取值列举出来

enum day
{
	//里面是可能的取值,也叫枚举常量

	Mon,//枚举常量
	Tues,
	Wed,
	Thur,
	Fri,
	Sat,
	Sun
};

int main()
{
	//枚举常量的值第一个值默认从0开始依次递增,常量不可修改
	printf("%d ", Mon);
	printf("%d ", Tues);
	printf("%d ", Wed);
	printf("%d ", Thur);
	printf("%d ", Fri);
	printf("%d ", Sat);
	printf("%d ", Sun);
	return 0;
}

515c3100bc8946349533bc1af0836c4b.png

 

enum day
{
	//里面是可能的取值,也叫枚举常量

	Mon=100,//枚举常量在main函数中不可修改,但是在设定是可以改变
	Tues,
	Wed,
	Thur=200,
	Fri,
	Sat,
	Sun
};

int main()
{
	//枚举常量的值第一个值默认从0开始依次递增,常量不可修改
	printf("%d ", Mon);
	printf("%d ", Tues);
	printf("%d ", Wed);
	printf("%d ", Thur);
	printf("%d ", Fri);
	printf("%d ", Sat);
	printf("%d ", Sun);
	return 0;
}

a7574d4ad2d741e89748f47193d4c7d6.png

 

枚举的使用

枚举可以用来定义一组数据,与#define类似,但是比它跟安全

enum Color
{
	RED=10,
	GREEN,
	BLUE
};


int main()
{


	enum Color cla = GREEN;//enum Color是枚举类型,用来定义枚举变量,
							//然后把枚举常量red的值赋给这个变量
							//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异

	printf("%d\n", cla);

	return 0;
}

 

4.联合

联合类型的定义

联合也是一种特殊的自定义类型

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

union Un
{
	int a;
	char ch;
};

int main()
{
	union Un un;
	printf("%p\n", &un);//联合体变量un中的成员a和成员ch共用同一块空间,
	printf("%p\n", &un.a); //所以成员变量一般不能同时使用
	printf("%p\n", &un.ch);
	printf("%d\n", sizeof(un));		
	

	return 0;
}

5793a369278d4730b8b53a4f93a261c8.png

 

联合体大小的计算

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

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

union Un1
{
	char c[5];//5个字节,默认对齐数是8,数组相当于5个char类型变量对齐数按1来算
	int i;//4 个字节  对齐数按4来算,所以最大对齐数是4,要大于等于5且是4的倍数,所以占8个字节
};

union Un2
{
	short c[7];//每个元素两个字节,对齐数是2
	int i;//四个字节,对齐数是4,要大于等于14且是4的倍数,所以是16
};



int main()
{
	printf("%d\n", sizeof(union Un1));
	printf("%d\n", sizeof(union Un2));
	return 0;
}

db7f883696ea428f96f38feaf6a023e9.png

联合体的使用

要求:1.联合体的成员不在同一时间使用

可以用来判断编译器是大端存储还是小端存储

//int fun1()
//{
//	int a = 1;
//	return *(char*)&a;
//}


int fun1()
{
	union un
	{
		int a;
		char i;
	}u;
	u.a = 1;//利用占用同一块空间,
	return u.i;
}

int main()
{
	int rst = fun1();
	if (rst == 1)
	{
		printf("小端\n");
	}
	else
	{
		printf("大端\n");
	}
	return 0;
}

abfca8aeb69c4162adf5c3a31eabe84e.png

 

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值