结构体、枚举、联合知识点

结构体: 具有相同或者不同类型元素的集合。

结构体也是一种类型。
结构体只能在初始化时定义内容,而不能以赋值的形式进行,若想操作,可用·操作符。
数组也是同样,若想赋值可以用循环。

struct stu {
	char name[20];
	char sex[5];
	int age;
	char number[10];
};

不允许结构体内为空(C标准要求)。为空的话没有任何实际意义。

结构体的成员可以是标量、数组、指针、还可以是其它结构体。

1.结构体中的成员访问:

  1. 结构体变量 访问成员,结构体变量的成员通过·操作符访问。如:
struct stu {
	char name[20];
	char sex[5];
	int age;
	char number[10];
};

int main()
{
	struct stu s;
	s.age = 20;
	return 0;
}
  1. 结构体指针访问指向变量的成员,指向结构体的指针。
struct stu {
	char name[20];
	char sex[5];
	int age;
	char number[10];
};

int main()
{
	struct stu s = {"小花","女",18,"192001"};
	struct stu *p = &s;
	printf("姓名:%s\n性别:%s\n年龄:%d\n学号:%s\n", p->name, p->sex, p->age, p->number);
	return 0;
}

->操作符进行访问。
 
 
 
2.结构体传参

结构体传参的时候,参数需要压栈(形参实例化),结构体没有“退化,降维”之类的问题,要发生对应的硬拷贝问题。故而导致了效率很低(系统开销大,性能下降)。一般建议用结构体指针。

即使结构体中有数组,也不会发生降维,而全是硬拷贝。

 
结构体的类型是否一致,取决于是否是同一个结构体变量。哪怕是两个结构体成员变量完全一致,也是两个结构体。
例如:

struct {
	char name[20];
	char sex[5];
	int age;
	char number[10];
}x;
struct {
	char name[20];
	char sex[5];
	int age;
	char number[10];
}*p;

int main()
{
	p = &x;
	return 0;
}

上述代码,在编译的时候是可以运行的,但会有告警。
 
 
 
3.结构体自引用:结构体中包含一个类型为该结构本身的成员。

struct Node
{
	int data;
	//struct Node next;//这是一种错误的自引用
	struct Node *next;
};

再看另一个代码:

typedef struct Node  //①
{
	int data;
	struct Node *next;//②
}node;  //③

struct Node进行重定义。其中①和②的Node意思是相同的,③和另外两个是不一样的。

比如这样的重定义就是有错误的。

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

程序会报错:
在这里插入图片描述
因为Node还没有完成重定义,故在结构体内不能先使用。

 
 
 
4.结构体内存对齐

结构体内部是存在内存对齐情况的,内存对齐会影响结构体整体的大小。
计算机访问内存的基本单位是字节,故计算机可以任意读取内存中的任意一个字节。但是,计算机因为硬件设计的原因,计算机读取数据的时候,必须从特定要求的起始位置开始读取。

 
 
①为什么要内存对齐?
因为硬件设计的原因导致效率变低。若访问未进行内存对齐的内存,处理器就会进行多次访存。
②内存对齐是什么?
牺牲内存空间,来满足cpu访存硬件的限制,提高效率的方式,就叫做内存对齐。
内存对齐的本质:空间换取时间。
③如何内存对齐:
内存对齐的规则:

1.第一个成员与结构体变量偏移量为0的地址处。
2.其它成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
3.结构体总大小为最大对齐数(每一个成员变量都有对齐数)的整数倍。
4.嵌套了结构体的情况,嵌套的结构体对齐到字节的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
举例:

struct stu{  //42 +2   4 
	char name[20];//20
	char sex[5];//5
	int age;//  3  +  4
	char number[10];//10
};

int main()
{
	printf("%d", sizeof(struct stu));
	return 0;
}

分析:

char name[20];  大小为20 不需要对齐
char sex[5];    每一个元素大小为1字节,对齐数为1,可以整除  故大小为5
int age;  age为int型,对齐数为4,前面的字节数为25,不能整除,故有偏移量,偏移量为3
char number[10];每一个元素大小为1字节,对齐数为1,可以整除  故大小为10
上述总大小为:42。最大对齐数为:4.
不能整除4,故需进行扩大。为44

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

struct list  //32  最大对齐数:8  32
{
	char num[5];//5
	int add;// 3 +  4
	char *p;//4
	double prt;//8
	char opp;//1
	int div;// 3 + 4
};

int main()
{
	printf("%d\n", sizeof(struct list));
	return 0;
}

在这里插入图片描述
嵌套了结构体的情况:

struct stu{  //50  最大对齐数:8   =  56
	char name[20];//20
	char sex[5];//5
	int age;//3 +  4
	double address;//8
	char number[10];//10
};

struct list  //81   最大对齐数:8   =  88  //这里的最大对齐数为什么是8?
{                                      //因为嵌套的结构体,最大对齐数为8,故,外部结构体最大对齐数也为8
	char num[5];//5
	int add;//  3  +  4
	char p;//1
	struct stu s;//3  +  56
	char opp;//1
	int div;// 3  +  4
	char mul;//1
};
int main()
{
	printf("%d\n", sizeof(struct stu));
	printf("%d\n", sizeof(struct list));
	return 0;
}

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

当然对齐数也可以进行修改。

#pragma pack()

可以对默认对齐数进行修改。
注意:()内的数值,必须为1,2,4,8,16。
如上述代码:我们将对齐数修改为2:

#pragma pack(2)
struct list  
{
	char num[5];
	int add;
	char *p;
	double prt;
	char opp;
	int div;
};
int main()
{
	printf("%d\n", sizeof(struct list));
	return 0;
}

字节数就发生了改变:28。
我们将对齐数修改为1:

#pragma pack(1)

字节数就发生了改变:26。
 
 
 
5.位段: 位段的声明和结构体比较类似,但有不同。

1.位段的成员必须是int、unsigned int 和signed int
2.位段的成员名后面有一个冒号和一个数字

struct A
{
	unsigned char a : 2;
	unsigned char b : 3;
	unsigned char c: 6;
	unsigned char d;
	unsigned char e : 4;
};
int main()
{
	printf("%d\n", sizeof(struct A));
	return 0;
}

在这里插入图片描述
所以这个代码的字节大小为4个字节。
我们对这个进行赋值试试。

int main()
{
	struct A s;
	s.a = 4;
	s.b = 5;
	s.c = 6;
	s.d = 7;
	s.e = 8;
	return 0;
}

分析:
在这里插入图片描述
当为有符号数时,一定要注意最高位的符号位是什么。

struct A
{
	char a : 2;
	char b : 3;
	char c: 6;
	char d;
	char e : 4;
};
int main()
{
	struct A s;
	s.a = 4;//100 发生截断00
	s.b = 5;//101 最高位为1,最后会原反补转换  结果为-3
	s.c = 6;//1100 
	s.d = 7;//1101
	s.e = 8;//1000  原反补转换  结果为-8
	printf("%d\n%d\n%d\n%d\n%d\n", s.a, s.b, s.c, s.d, s.e);
	return 0;
}

在这里插入图片描述
位段可以达到很好的节省空间的效果,但是跨平台存在问题。
位域整体可以取地址,但单独无法进行取地址。

 
 
 
 
枚举:枚举类型本质是int,而且默认情况(可以改,正负皆可)是从0开始,依次递增的。

枚举是一个类型,在编译的时候,会进行类型检查。宏是基于替换原则的,在这个过程中,并不进行任何检查工作。

enum Day
{
	MON,
	TUES,
	WED,
	THUR,
	FRI,
	SAT,
	SUN
};//一般都习惯于大写。(默认为常量)

枚举的优点:

  1. 增加代码的可读性和可维护性
  2. 和宏定义的标识符相比,枚举有类型检查,更加严谨。

 
 
 
 
联合:联合也是一种特殊的自定义类型,该类型中也包含一系列的成员,但是这些成员共用一块空间。

联合体的大小并不是把所有类型加起来的大小。
一般而言,联合体的大小是由联合体最大元素的大小决定的。决定之后,所有元素共享空间。
在使用union的时候,一次只会访问一个元素。
联合体也要考虑内存对齐问题,联合体的最终大小,要能整除联合体内的最大对齐数。

union un
{
	int i;
	char c[6];
};//该联合体字节大小为8

联合的成员是公用一块内存空间的,改变一个成员的值,就会影响后续的值。
有一个比较实用的例子:判断当前计算机的大小端?

union un
{
	int i;
	char c;
};

union un temp;
temp.i = 1;
printf("%d\n", temp.c);

输出0,大端存储,输出1,小端存储。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值