c语言之自定义类型

一.结构体


1.概念

结构体是一些值的集合,结构体的每个成员的类型可以不同

struct Book
{
	char name[20];//书名
	int price;//价格
	char id[12];//书号
};

2.变量

struct Book
{
	char name[20];//书名
	int price;//价格
	char id[12];//书号
};b3, b4;//b3,b4是全局变量

int main()
{
	struct Book b1;//b1,b2是局部变量
	struct Book b2;
	return 0;
}
b1,b2,b3,b4基本一样,只是b1,b2是局部变量,b3,b4是全局变量

3.匿名结构体类型

在声明结构体的时候,可以不完全的声明

//匿名结构体类型
struct
{
	char c;
	int i;
	char ch;
	double d;
}s;

注意,结构体后面加*号表示匿名结构体类型的指针,与原结构体不同

struct
{
	char c;
	int i;
	char ch;
	double d;
}* ps;

在编译器看来,这两个结构体的类型不同,使用后面的结构体指针无法存储前一个结构体的地址

匿名结构体类型创建好了之后只能使用一次,因为所创建的结构体无名字无法再次引用


4.结构体的自引用

struct A
{
	int i;
	char c;
};

struct B
{
	char c;
	struct A sa;
	double d;
};

结构体可以引用其他结构体
但无法在结构体中引用自己
因为这样会造成无限嵌套,所占内存无法计算

struct A
{
	int i;
	struct A m;
};
int main()
{
	struct A n;
	return 0;
}

正确的自引用方式:(所引用的结构体非匿名)

struct Node
{
	int data;
	struct Node* next;
};

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

类比数组初始化,使用大括号进行初始化
要对应顺序

struct S
{
	char c;
	int i;
}s1, s2;
struct B
{
	double d;
	struct S s;
	char c;
};

void main()
{
	struct B b = { 3.14,{'c',100},'o' };
	//引用方法
	//.
	//->
	printf("%lf %c %d %c", b.d, b.s.c, b.s.i, b.c);
	//->只能用于指针指向变量的时候
	printf("%lf %c %d %c\n", ps->d, ps->s.c, ps->s.i, ps->c);
}

‘->’ 只能用于指针指向变量的时候


6.结构体传参

传参有两种方式:传值调用和传址调用

void print1(struct B t)
{
	printf("%lf %c %d %c\n", t.d, t.s.c, t.s.i, t.c);
}
void print2(struct B* ps)
{
	printf("%lf %c %d %c\n", ps->d, ps->s.c, ps->s.i, ps->c);
}
void main()
{
	struct B b = { 3.14,{'c',100},'o' };
	struct B* ps = &b;
	print1(b);     //传值调用
	print2(&b);	   //传址调用
}

传值调用和传址调用优缺点

这两种方法各有各的优缺点,但一般优先使用传址调用

(1)传值调用

优点:数据安全,不会被所接收的函数改变
缺点:形参和实参都需要占空间,需要双倍空间,浪费空间,参数一个一个传递,当参数较多时所需要时间长,容易造成参数压栈,系统性能下降

(2)传址调用

优点:无论几个参数,只需要占一个指针所需要的内存,传输时间短,只要传输一个地址
缺点:参数会被函数改变,数据不安全
当然如果用const可以保证参数不被改变


7.结构体所占空间的大小(结构体内存对齐)

(1)空间计算

你可能会认为只是简单的将结构体中的各个参数所占的空间加起来,可真的是这样吗?
让我们来验证一下。
在这里插入图片描述
认为结果:5
实际结果:8
为什么这个结构体所占空间与我们想象的不同?
实际上,因为结构体有特殊的性质——结构体内存对齐

结构体的对齐规则:
1.第一个成员在与结构体变量偏移量为0的地址处。
2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处
对齐数:编译器默认的一个对齐数与该成员大小的较小值
VS的默认的数为8

3.结构体的大小为所有成员的对齐数中最大的那个对齐数的整数倍
4.如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小计算==就是所有最大对齐数(含嵌套结构的对齐数)的整数倍

实例计算:
在这里插入图片描述

(1)如实例图1,图结构体大小理应为 9 ,但结构体的大小为所有成员的对齐数中最大的那个对齐数的整数倍,最大对齐数为4,总大小应为4 的整数倍,所以该结构体大小为 12
在这里插入图片描述

(2)如图2,计算嵌套结构体s1的大小(顺序法)
先按照顺序将char c1储放,再以正常规则找到下一个位置储放点,令为嵌套结构体的新基准线再按照正常规则正常计算
在这里插入图片描述
不要忘记了,因为开头为0,所以总大小为排序+1

(2)为什么存在内存对齐

1.平台原因(移植原因):不是所有的硬件平台都能访问任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常
2.性能原因:数据结构(尤其是栈)应该尽可能的在自然边界上对齐,原因在于,为了访问未对齐的内存处理器要作两次内存访问;而对齐的内存只需要一次访问。
换句话说,内存对齐就是牺牲空间换取时间。
那在设计程序的时候为了节省空间和缩短时间,我们要尽量让占用空间小的成员尽量集中在一起

例如:
在这里插入图片描述


(3)修改默认对齐数

格式:#pragma pack(对齐数);

//把默认对齐数改为2
#pragma pack (2)
struct S1
{
	char c1;
	char c2;
	int i;

};
//把默认对齐数改回原值
//#pragma pack ()

更改默认对齐数可以改变内存储放方式,改变结构体大小
在这里插入图片描述
但设置默认对齐数时应该考虑是否有意义,如设置为1是就失去了意义。所以平常设置时一般都设置为2的n次方

(4)计算结构体中某个参数相较于0的偏移量

引用头文件#include<stdddef.h>

函数格式:offsetof(结构体名字,结构体中参数的名字)

在这里插入图片描述

二.位段

1.什么是位段?

位段的声明和结构体是类似的,有两个不同:
1.位段的成员必须是int , nsighed int , sighed int , char。
2.位段的成员后边有一个冒号和一个数字。

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

位段后面的数字表示该变量所分配的空间(bit位)
同时,设置分配的空间不能超过数据类型原来的空间,例如一个int只有32bit位,不能设置比32大的数。

2.位段的内存分配:

1.位段成员必须是int , nsighed int , sighed int , char。
2.位段空间上是按照以4个字节(int) 或者1个字节(char)的方式来开辟空间的。
3.位段涉及很多不确定因数,位段是不跨平台的,注重可移植性的程序应该避免使用位段。

使用位段的意义:
当使用的参数不需要参数类型这么大的空间时,使用位段可以节省大量的空间。
例如:性别
1.男———用00来存放
2.女———用01来存放
只要两个bit位就可以存放,但int为四个字节(32个bit位),这时使用位段就可以节省26个bit位。
但使用位段限制了分配的bit位,同时也限制了能存储的数据的大小

struct A
{
	//1.开辟4个字节--32bit
	int a : 2;//占用2个bit位
	int b : 5//占用5个bit位
	int c : 10;//占用10个bit位
	//剩余15个bit位,不足,再次开辟4个字节--32bit
	int d : 30;//占用30个bit位
};

如图第一个开辟的空间还剩余空间,那是继续使用剩余空间还是直接废弃来使用新开辟的空间呢?
这个问题是不确定的,不同编译器有不同的处理办法。所以位段是不跨平台的,注重可移植性的程序应该避免使用位段。
vs中的内存分配:
1.一个空间的bit位分配先从低地址处(从右向左)开始分配。
2.当一个空间剩余bit位不够下一个参数使用时会开辟下一个空间并舍弃剩余空间,直接从新开辟的空间存放。

位段的跨平台问题:
1.int位段被当成有符号数还是无符号数是不确定的。
2.位段中最大位的数目不确定。
例如:
16位机器——int——2字节——16bit
32位机器——int——4字节——32bit
3.位段中的成员在内存中从左向右分配还是从右向左分配不确定。
4.新开辟空间后,老空间剩余的空间是否利用不确定。

总结
和结构体相比位段可以在达到同样的效果下,节省空间,但有跨平台的问题存在。
应用
节省封装的数据大小,减少传输数据的损耗,缩短传输时长。

三.枚举

当定义某些有限的有规律的可以被一一列举的变量时就可以使用枚举。
例如:星期一到星期天;

1.枚举的定义

//声明枚举类型
enum Color  //枚举关键字   枚举类型
{		//枚举类型的可能取值(皆为常量)
	red,	//默认为0
	green,	//1
	blue	//2
};

枚举就是逐个+1,即使前面的数被更改。
初始更改

enum Color  //枚举关键字   枚举类型
{		
	red=5,	//5     
	green,	//6
	blue	//7
};
enum Color  //枚举关键字   枚举类型
{		
	red=5,		//5     
	green=8,	//8
	blue		//9
};

枚举格式为enum+枚举类型(!!不是名字!!)
例如:enum Color
枚举类型为Color,而非int
enum Color c=5;
这是错误定义,原因5与Color类型不同

2.枚举相较于宏定义的优点

1.增加代码的可读性和可维护性。
2.和宏定义定义的标识符比较枚举有类型检查,更严谨。
3.防止命名污染。

//#define定义的是全局常量,所有地方都能引用
4.便于调试。
//在调试时使用宏定义的地方会直接显示被定义的量,不会显示名字,难以调试
5.使用方便,可以一口气定义多个常量。

四.联合(共同体)

1.联合的定义

一系列成员共同使用同一块空间的类型
在这里插入图片描述
所有成员地址相同代表着联合体的所有成员都存放在同一块空间内。

2. 联合的特点

1.联合体的成员共用同一块空间,这样联合体的大小至少是最大成员的大小(因为联合体要存放最大的那个成员)。
2.在同一时间只能使用其中一个值,因为存放在一起,所有改变一个值时会改变另一个值

	union Un u = { 10 };
	//i和c中都存放10
	u.i = 1000;
	//i放1000,同时改变c
	u.c = 100;
	//c放100,同时改变i

3.联合的应用

例如:大小端存储模式鉴别
在这里插入图片描述
十六进制时,前面的是高位字节,后面的是低位字节
大端存储模式:高位字节存放在低位,低位字节存放在高位上
小端存储模式:高位字节存放在高位,低位字节存放在低位上

int main()
{
	int a = 1;
	if ((*(char*)&a) == 1)
		//取a的四个字节的第一个字节,可用联合体代替
		printf("小端\n");
	else
		printf("大端\n");
	return 0;
}

用联合代替

union Un
{
	char c;
	int i;
};
int main()
{
	union Un a = { 1 };
	if (a.c == 1)
		//取a的四个字节的第一个字节
		printf("小端\n");
	else
		printf("大端\n");
	return 0;
}

4.联合的大小计算

1.联合的大小至少为最大成员的大小
2.当最大成员不是最大对齐数的整数倍时,就要对齐到最大对齐数的整数倍。

union Un
{
	char a[5];//1——占五个字节
	char c;//1
};
结果为:5
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值