深度解析结构体、联合体与枚举

目录

 

结构体

结构体声明

声明

特殊声明(匿名结构体声明)

结构体的类型

结构类型命名的意义

结构标记

类型定义

结构体的作用域

结构体操作

变量名以及成员

.操作符

->操作符

=操作符

结构体的作用域

结构体初始化

结构体传参与返回值

传参

指针传参

返回值

结构体与数组

包含数组的结构体

结构体数组

结构体在内存中的存储

存储

对齐

结构体的自引用

结构体位段

联合体

枚举


结构体

定义:不同元素的集合。

结构体声明

声明

struct family//声明family结构体
{
	char mom[20];
	char dad[20];
};

struct stu//声明stu结构体
{
	char name[20];
	int age;
	int tel[11];
	struct family fa;
};

特殊声明(匿名结构体声明)

struct 
{
	char mom[20];
	char dad[20];
}family;//声明family 变量

struct 
{
	char name[20];
	int age;
	int tel[11];
}stu ;//声明stu 变量

问题:

1、匿名声明能否实现结构体嵌套?

2、能否在程序中对结构变量赋值?

结构体的类型

struct {member-list};

结构类型命名的意义

情况1
struct
{
	char name[20];
	int age;
}stu1 = {"zhangsan",18,},
stu2 = {"lisi",20},
stu3 = {"wangwu",25};


int main()
{
	stu1 = stu2;
	printf("%s\n", stu1.name);
	SYS;
	R0;
}
输出结果为lisi

情况2
int main()
{
	struct
	{
		char name[20];
		int age;
	}stu1 = { "zhangsan", 18, };
	struct
	{
		char name[20];
		int age;
	}stu2 = { "lisi", 20 };

	stu1 = stu2;
	printf("%s\n", stu1.name);
	SYS;
	R0;
}

 匿名申明的变量必须像情况1一样需要一次性全部声明完,才能属于同一个结构类型,否则即使成员完全相同,也会像情况2一样被判断为不兼容的类型,结构变量之间不能赋值。

所以需要结构体类型命名。

结构体类型命名分为两种:

结构标记

structure tag。

struct stu//声明
{
	char name[20];
	int age;
};


int main()
{
	struct stu  s1= { "zhangsan", 18 };//初始化结构体变量s1
	struct stu  s2 = { "lisi", 20 };//初始化结构体变量s2
	SYS;
	R0;
}

stu 就是结构体标记,但是使用时前面一定要加上struct。

类型定义

利用typedef定义结构体类型

typedef struct 
{
	char name[20];
	int age;
}stu;


int main()
{
	stu  s1= { "zhangsan", 18 };
	stu  s2 = { "lisi", 20 };
	printf("%s\n",s1.name);
	SYS;
	R0;
}

 利用typedef定义的结构体类型,使用时前面可以不加struct,但是链表不支持,所以还是使用结构标记。

结构体的作用域

每个结构代表一种新的作用域。任何声明在此作用域内的名字都不会和程序中的其他名字冲突

struct stu
{
	char name[20];
	int age;
};

struct f
{
	char name[20];
	int age;
};


int main()
{
	struct stu  s1 = { "zhangsan", 18 };
	struct f f1 = { "lisi", 20 };
	char name[20] = "wangwu";
	printf("%s\n",s1.name);
	printf("%s\n", f1.name);
	printf("%s\n", name);
	SYS;
	R0;
}

 在上面的代码中name[20]数组出现在不同的结构体以及变量,但相互之间并不冲突。

结构体操作

变量名以及成员

上面的例子中,s1与f2就是结构体的变量名,结f构体中的name与age就是结构的成员,使用时2者是同时使用的。

.操作符

关联使用结构体成员的运算符,(struct tag name).(member)。

	printf("%s\n",s1.name);
	printf("%s\n", f1.name);

->操作符

关联使用结构体指针与成员的运算符,(struct tag *ps)->(member)。

->操作符等价于(*(struct tag *ps)).(member)。.操作符的优先级比较高,需要将解引用括起来

=操作符

结构体中兼容的结构体可以直接赋值(兼容的意思是拥有相同结构类型的结构体,结构体类型不同,成员全部一样也不是兼容的结构体,创建兼容结构体的方法就是使用结构标记,或者声明结构变量时一起声明)。

结构体可以用=进行赋值(数组不能用=赋值,但是结构体中包含的数组就可以)

//兼容结构体1
struct stu
{
	char name[20];
	int age;
};


int main()
{
	struct stu  s1 = { "zhangsan", 18 };
	struct stu s2 = s1;
	printf("%s\n", s1.name);
	printf("%s\n", s2.name);
	SYS;
	R0;
}

//兼容结构体2
struct 
{
	char name[20];
	int age;
}s1 = { "zhangsan", 18 }, s2;


int main()
{
	s2 = s1;
	printf("%s\n", s1.name);
	printf("%s\n", s2.name);
	SYS;
	R0;
}
//不兼容的类型
struct
{
	char name[20];
	int age;
}s1 = { "zhangsan", 18 }, s2;

struct
{
	char name[20];
	int age;
} s2;

int main()
{
	s2 = s1;
	printf("%s\n", s1.name);
	printf("%s\n", s2.name);
	SYS;
	R0;
}

不兼容类型的运行结果

结构体初始化

struct
{
	char name[20];
	int age;
}s1 = { "zhangsan", 18 }, s2;

结构体初始化也可以进行不完全初始化,在初始化时,没有被赋值的部分默认为0。

结构体传参与返回值

传参与返回值

传值传参
struct stu
{
	char name[20];
	int age;
};

struct stu change_stu(struct stu s1)
{
	strcpy(s1.name, "zhangsan");
	s1.age = 25;
	return s1;
}

int main()
{
	struct stu s1 = {"lisi",20};
	struct stu s2;
	s2 = change_stu(s1);
	printf("%s\n", s1.name);
	printf("%s\n", s2.name);
	SYS;
	R0;
}

 程序输出的结果是

lisi

zhangsan

结构体在传值传参时,形参是实参的复制体,对形参的改变不影响对实参的改变,结构体也可以作为参数进行返回,但这样做,对空间的消耗很大。

指针传参

struct stu
{
	char name[20];
	int age;
};

struct stu change_stu(struct stu *s1)
{
	strcpy(s1->name, "zhangsan");
	s1->age = 25;
	return *s1;
}

int main()
{
	struct stu s1 = {"lisi",20};
	struct stu s2;
	s2 = change_stu(&s1);
	printf("%s\n", s1.name);
	printf("%s\n", s2.name);
	SYS;
	R0;
}

结构体与数组

结构体数组

结构体可作为元素存储在数组中,结构体中的数组为了节省空间可以使用指针,该方法只能在后台使用,读取时仍然需要使用name数组

struct city
{
	char *name;
	int code;
};

int main()
{
	struct city c[3] = { {"beijing",01}, {"shanghai",02}, {"tianjing",03} };
	int i = 0;
	i = 0;
	while (i < 3)
	{
		printf("name=%s   code=%d\n", c[i].name, c[i].code);
		i++;
	}
	SYS;
	R0;
}

结构体在内存中的存储

存储

结构体的存储时按照结构体成员的顺序进行存储的

struct city
{
	char name[4];
	int code;
}s1;

对齐

结构体的对齐规则:

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

其他成员的存储地址为有效对齐值的最小整数倍地址处。

注:

有效对齐值:自身对齐值与指定对齐值中较小的一个(min{自身对齐值,指定对齐值})。

自身对齐值:是成本本身的字节数。

指定对齐值:有宏 #pragma pack(N) 指定的值,这里面的 N一定是2的幂次方.如1,2,4,8,16等.如果没有通过宏那么在32位Linux主机上默认指定对齐值为4,64位的默认对齐值为8,AMR CPU默认指定对齐值为8,VS默认为8;

总对齐时字节大小:min{结构体变量最大成员的字节数,指定对齐值}的整数倍。

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

S1中,第一个成员c1的地址在结构体变量偏移量为0的地址,i的对齐值为min{4,8}=4,依次查看4的整数倍地址能否储存4个字节,0*4=0//该地址存储c1,1*4=4,该地址为空,所以i的地址为4,5,6,7,c2的对齐值为min{1,8}=1,1*8=8//该地址为空,所以i的地址为8

总对齐时min{4,8}=4,但结构体占用的9个字节不是4的整数倍,所以需要扩充,离9最近的4的整数倍为12,多以结构体s1的字节长度是12。

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

S2中 ,第一个成员c1在结构体变量偏移量为0的位置,c2的对齐值为min{1,8}=1,一次查看1的整数倍地址能否储存1个字节,0*1=0//该地址有变量c1,1*1=1//该地址为空可以存储,所以c2的地址是1,i的对齐值为min{4,8}=4,依次查看4的整数倍地址0*4=0//该地址有变量c1在,1*4=4//该地址为空可以存储

总对齐时min{4,8}=4,结构体占用8个字节,是4的整数倍,所以结构体s2的长度是8。

struct S5
{
	char c1;
	double d;
	char c2;
	char c3;
};

struct S7
{
	char c1;
	struct S5 s;
	char c2;
	char c3[20];
};

int main()
{
	struct S7 s;
	printf("%d\n", sizeof(s));
	SYS;
	R0;
}

 首先看S5结构体需要多少内存,c1需要8个,d需要8个,c2,c3需要2个,总对齐时min{8,8}=8,超过18个字节的最小整数倍时24,所以S5结构体占用的字节时24给,套入到S7中,c1的结构体变量的偏移量是0,结构体变量s的对齐值是min{24,8}=8,1*8=8,所以8至32是结构体变量的空间,c2的对齐值是1*33=33,所以第33个位置存放c2的变量,之后20个字节存放C3数组

总对齐时,min{24,8}=8,比53个字节大的整数倍是56,所以结构体变量s所占据的字节数是56。

struct S5
{
	char c1;
	double d;
	char c2;
	char c3;
};


struct S7
{
	char c1;
	struct S5 s;
	char c2;
	int c3[20];
};

int main()
{
	struct S7 s = { 'a', 'b', 1.0, 'c', 'd', 'e', {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20} };
	printf("%d\n", sizeof(s));
	SYS;
	R0;
}

 c1的偏移量为0,向后7个字节后,放入S5 s所需要的24个字节,之后放入c2,向后3个字节后,放入c3数组,共80个字节

总对齐时,min{24,8}=8,结构体便变量s所占据的字节为116,能整大于116又能整除8的最小整数是120,所以结构体变量s的大小是120

结构体的自引用

结构体可以进行自引用,也就是自己引用自己,但是不能引用自己的变量,因为结构体变量b是一个完整的结构,其中还包含了自己,也是一个完整的结构,该结构下又包含了一个结构变量b,无限循环下去,所以无法创建。

错误
struct stu
{
	int a;
	struct stu b;
};

int main()
{
	struct stu b = { 10, { 2 } };
	SYS;
	R0;
}
//指针
struct stu
{
	int a;
	struct stu *b;
};

int main()
{
	struct stu b = { 10, NULL };
	struct stu a = { 10, &b };
	printf("%p",a.b );
	SYS;
	R0;
}

使用指针能创建,是因为指针的空间大小是确定的,所以声明结构的时候,已经确定了结构体的大小。 

结构体位段

位段是节省空间的一种方法,但不适合移植。

位段的内存分配:

位段的成员可以是int,unsigned int, signde int,char(属于整型家族)类型。

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

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

struct s
{
	char a : 3;//数字表示比特位
	char b : 4;
	char c : 5;
	char d : 4;
};
struct S s = { 0 };
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;

VS中

a设置为3个比特位,b设置4个比特位,c设置5个比特位,d设置4个比特位

首先开辟第一个char变量空间:

a=10,二进制为1010,因为a只有三个位,所以前面的1舍去,010放入内存

b=12,二进制为1100,b有四个位,可以放入。

此时,红色字节只剩下一个位,将其弃置,放入0,并重新开辟绿色字节

c=3,二进制为0011,因为c占据5个位,再补上一个0,00011放入。

剩余三个位,但成员d需要四个位,所以弃置放入0,重新开辟一个蓝色字节

d=4,占据四个位,二进制放入0100,剩余的位置补0

所以在内存中应为,三个字节,内存显示62 03 04

以上仅为VS编译器下的情况,不同编译器情况不同,主要体现在三个方面:

1、数据从哪边开始进行存储。

2、空间不够,再次开辟时是否会占用原空间。

3、数据超过时,是否丢弃。

联合体

公用一块空间的自定义类型

联合体的特点:共用一块空间

union un
{
	int i;
	char c;
};

int main()
{
	union un u = { 0 };
	printf("%p\n", &u);
	printf("%p\n", &u.i);
	printf("%p\n", &u.c);
	SYS;
	R0;
}

 

联合体在不同的时间可以存放不同类型的变量,那么大小至少是最大成员的大小。

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

struct stu
{

	char c[57];
};

union un
{
	int i;//4
	short c[7];//14
	struct stu s;//57
};

int main()
{
	struct stu s = { 0 };
	union un u = { 0 };
	printf("%d", sizeof(u));
	SYS;
	R0;
}

 代码中最大成员是结构体变量s,s=57,不能被4整除,所以u的大小,最终是60。

联合体的特别应用

判断大小端字节序
union un
{
	char c;
	int i;
};

int main()
{
	union un u = { 0 };
	u.i = 1;
	printf("%d", u.c);
	SYS;
	R0;
}

联合体做不同类型成员的数组 

union un
{
	int i;
	float f;
};

struct st
{
	char flag;
	union un u;
};

int main()
{
	struct st s[4] = { 0 };
	s[0].flag = 'i';
	s[0].u.i = 1;
	s[1].flag = 'f';
	s[1].u.f = 2.13f;
	s[2].flag = 'f';
	s[2].u.f =3.14f;
	s[3].flag = 'i';
	s[3].u.i = 4;
	int i = 0;
	for (i = 0; i < 4; i++)
	{
		if (s[i].flag == 'i')
		{
			printf("%d ", s[i].u.i);
		}
		else if (s[i].flag == 'f')
		{
			printf("%f ", s[i].u.f);
		}
	}
	SYS;
	R0;
}

 

union un
{
	float f;
	int i;
};

int main()
{
	union un u[4] = { 0 };
	u[0].i= 1;
	u[1].f= 2.13;
	u[2].f = 3.14;
	u[3].i = 4;
	SYS;
	R0;
}

 

实际结果在内存中的排列与数组并无太大的区别,只是使用的时候不算方便                                                                          

枚举

枚举是一种自定义的类型,成员本身可以初始化值,可以将成员赋值给变量,成员名相当于整型常量数值的另一个名字。

enum color
{
	blue,
	red=6,
	yellow
};

int main()
{
	int a = blue;
	printf("%d\n", a);
	printf("%d\n", blue);
	printf("%d\n", red);
	printf("%d\n", yellow);
	SYS;
	R0;
}

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值