【C语言 |结构体 联合体 枚举】结构体、联合体、枚举详解

目录

一、结构体

1.结构的声明

 第一种:先定义结构,再定义结构变量

第二种:定义结构体类,同时说明变量

第三种:匿名结构体

2.结构体变量的初始化  

3. typedef与struct

4.结构体成员的访问 

 5.结构体传参

 6.结构体内存对⻬

-偏移量 

-举几个例子

eg1.

 eg2.

 eg3.

eg4. 

- 修改编译器的默认对⻬数

 7.结构体实现位段

-位段的内存分配

二、联合体 

1.结构的声明

2.联合体特点

 3.联合大小的计算

三、枚举

1.枚举类型的定义

 2.枚举的优点

 3.枚举的使用


一、结构体

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

1.结构的声明

 第一种:先定义结构,再定义结构变量

struct Stu
{
 int age;
 char name[20];
  float height;
};   //这个;不能丢

struct stu s1,s2;  //定义了局部变量s1,s2
  • 关键字struct,它表示接下来是一个结构体
  • Stu是结构名
  • {}中的是成员列表,可以是不同数据类型的变量

第二种:定义结构体类,同时说明变量

struct Stu
{
 int age;
 char name[20];
  float height;
}s4,s3;         //这个;不能丢

定义了全局变量s3,s4

第三种:匿名结构体

struct 
{
 int age;
 char name[20];
  float height;
}s5;        

省略的结构体名,就无法组合结构体类型了,而无这种结构体类型,就不能再定义这种结构体类型的变量

struct s5 a;  //像这种的就是错误的

没有结构体名。匿名结构体通常作为结构体成员的一个变量去使用。如下:

struct stu{

  int age; //成员1

  struct {    //成员2
    int month;
  }birthday;
    
  char name[20];  //成员3
  float height;    //成员4

}stu1;
 
sut1.birthday.month = 10; //用法

2.结构体变量的初始化  

struct Stu
{
 int age;
 char name[20];
  float height;
}s1 = {19,"lisi", 1.75};        //顺序初始化



struct Stu s2 = {18,"zhangsan", 1.85};    //顺序初始化

struct Stu s3 = {.name = "wangwu",.height = 1.85,.age = 12};    //不按顺序初始化
  • s1在创建结构体同时创建变量的时候同时顺序初始化了
  • s2在后面创建变量同时顺序初始化了
  • s3 在后面创建变量同时不按顺序初始化了

在初始化结构体成员时如果不想按照顺序初始化,只需要 加一个.点来找那个成员 然后进行初始化


3. typedef与struct

 常规定义结构体类型需要用struct 结构名的方式,比较繁琐。所以结构体定义往往与typedef相结合使用。

struct Stu_sch
{
 int age;
 char name[20];
  float height;
}STU;  


struct Stu_sch s1;//定义变量


typedef struct Stu_sch
{
 int age;
 char name[20];
  float height;
}STU;  


STU s1;//定义变量

此时Stu_sch 等价于STU ,只不过换了一个名字,更简洁方便一点


4.结构体成员的访问 

结构体变量 . 结构体成员

结构体指针 -> 结构体成员

typedef struct Stu
{
	int age;
	char name[20];

};

int main()
{
	struct Stu s1 = {18,"lisi"};
	struct Stu s2 = { 19,"zhangsan" };
	struct Stu* p = &s2;

	printf("%d,%s",s1.age,s1.name );

	prinft("%d,%s",p->age ,p->name );


	return 0;
}

 

上述s1为结构体变量所以用 s1. 结构体成员,就能访问到各个成员

而*p是一个指针变量 存储的s2地址 所以要用结构体指针  p -> 结构体成员,才能访问到各个成员


 5.结构体传参

函数传参的时候,参数是需要压栈的。
如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的 下降
所以一般优先传结构体的地址

struct S
{
 int data[1000];
 int num;
};

struct S s = {{1,2,3,4}, 1000};

void print2(struct S* ps)
{
 printf("%d\n", ps->num);
}

 6.结构体内存对⻬

⾸先得掌握结构体的对⻬规则:

1. 结构体的第⼀个成员对⻬到和结构体变量起始位置偏移量为0的地址处

2. 其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处

对⻬数 = 编译器默认的⼀个对⻬数与该成员变量⼤⼩的较⼩值

VS 中默认的值为 8

- Linux中 gcc 没有默认对⻬数,对⻬数就是成员⾃⾝的⼤⼩
3. 结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的整数倍
4. 如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构 体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数的整数倍
结构体的内存对⻬是拿空间来换取时间的做法
-偏移量 

首先什么是偏移量呢

原因其实也很简单,作为计算机,他所能识别的语言只有“0”、"1",而它存放的数据也是连续的,这和他的构造也有一定的关系。

如果我们把一个结构体看作是一个装满数据的小盒子的话,即便是我们知道要找的信息在盒子里,我们也只能是通过从头到尾遍历查找的方式确认信息到底在什么地方,首先确定数据头的位置,才能够确定出信息具体的位置所在


-举几个例子
eg1.
struct S2
{
	char c1;  //1
	char c2;  //1
	int i;    //4
}s1;
int main()
{
	printf("%d",sizeof(s1));

}

对于这个结构体内存是如何分配的呢

c1自身大小为1,默认对齐数为8,所以对齐数为1 

c2自身大小为1,默认对齐数为8,所以对齐数为1

i自身大小为4,默认对齐数为8,所以对齐数为4

所以该结构体最大对齐数为4,

起始位置偏移量为0的地址处开始,分别放入c1,c2,

因为i对齐数为4,所以偏移量为4的地方放入i

此时内存大小为8,因为要是最大对齐数的整数倍所以这个结构体内存为8

所以结果为8


 eg2.
struct S2
{
	char c1;  //1
    int i;    //4
	char c2;  //1
	
}s1;
int main()
{
	printf("%d",sizeof(s1));

}

对于这个结构体内存是如何分配的呢

c1自身大小为1,默认对齐数为8,所以对齐数为1 

c2自身大小为1,默认对齐数为8,所以对齐数为1

i自身大小为4,默认对齐数为8,所以对齐数为4

所以该结构体最大对齐数为4,

起始位置偏移量为0的地址处开始,放入c1,

因为i对齐数为4,所以偏移量为4的地方放入i,再放入c2

此时内存大小为9,因为要是最大对齐数的整数倍所以这个结构体内存为,12


 eg3.
struct S3
{
 double d;
 char c;
 int i;
}s1;

int main()
{
	printf("%d",sizeof(s1));

}

 对于这个结构体内存是如何分配的呢

d自身大小为8,默认对齐数为8,所以对齐数1

c自身大小为1,默认对齐数为8,所以对齐数为1

i自身大小为4,默认对齐数为8,所以对齐数为4

所以该结构体最大对齐数为8

起始位置偏移量为0的地址处开始,放入d,

再放入c2,因为i对齐数为4,所以偏移量为12的地方放入i

此时内存大小为9,因为要是最大对齐数的整数倍所以这个结构体内存为16

 


eg4. 
struct S4
{
 char c1;
 struct S3 s3;
 double d;
}s1;

int main()
{
	printf("%d",sizeof(s1));

}

对于这个结构体内存是如何分配的呢

 

c1自身大小为1,默认对齐数为8,所以对齐数1

S3自身大小为16,默认对齐数为8,所以对齐数为8

i自身大小为4,默认对齐数为8,所以对齐数为4

所以该结构体最大对齐数为8

起始位置偏移量为0的地址处开始,放入c1

再放入S3,因为S3对齐数为8,所以偏移量为8的地方放入S3

再放i,因为i对齐数为4,所以偏移量为28的地方放入i 

此时内存大小为32,因为要是最大对齐数的整数倍所以这个结构体内存为32


 这四个例子很就很有代表了,结构体分配空间时,

 那在设计结构体的时候,我们既要满⾜对⻬,⼜要节省空间

让占⽤空间⼩的成员尽量集中在⼀起

- 修改编译器的默认对⻬数

 #pragma 这个预处理指令,可以改变编译器的默认对⻬数

#pragma pack(1)//设置默认对⻬数为1
struct S
{
	char c1;
	int i;
	char c2;
}s1;

int main()
{
	printf("%d", sizeof(s1));

}

对于这个结构体内存是如何分配的呢 

c1自身大小为1,默认对齐数为改为了1,所以对齐数为1 

c2自身大小为1,默认对齐数为改为了1,所以对齐数为1

i自身大小为4,默认对齐数为改为了1,所以对齐数为1

所以该结构体最大对齐数为1

起始位置偏移量为0的地址处开始,放入c1,

因为i对齐数为1,所以偏移量为1的地方放入i,再放入c2

此时内存大小为6,因为要是最大对齐数的整数倍所以这个结构体内存为6

 结构体在对⻬⽅式不合适的时候,我们可以⾃⼰更改默认对⻬数


 7.结构体实现位段

位段的声明和结构是类似的,有两个不同:
  • 位段的成员必须是 intunsigned int signed int ,在C99中位段成员的类型也可以 选择其他类型。
  • 位段的成员名后边有⼀个冒号和⼀个数字。
struct A
{
 int _a:2;
 int _b:5;
 int _c:10;
 int _d:30;
};
A就是⼀个位段类型

-位段的内存分配

VS里面空间从低址到高址,内存内部从右向左开辟,不够用浪费掉开辟新的空间

a的位段是3,分配了3个空间
b的位段是4,分配了4个空间
c的位段是5,分配了5个空间
d的位段是4,分配了4个空间
a的赋值为10,二进制为01010,又因为a分配了3个空间,所以发生截断010放入
b的赋值为12,二进制为01100,又因为b分配了4个空间,所以发生截断1100放入
c的赋值为3,二进制为00011,又因为c分配了5个空间,所以发生截断00011放入
d的赋值为4,二进制为00100,又因为d分配了4个空间,所以发生截断0100放入
  • int 位段被当成有符号数还是⽆符号数是不确定的
  • 位段中最⼤位的数⽬不能确定。(16位机器最⼤16,32位机器最⼤32,写成27,在16位机器会 出问题
  • 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
  • 当⼀个结构包含两个位段,第⼆个位段成员⽐较⼤,⽆法容纳于第⼀个位段剩余的位时,是舍弃 剩余的位还是利⽤,这是不确定的
位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使⽤位段
位段可以很好的节省空间,但是有跨平台的问题存在

位段的⼏个成员共有同⼀个字节,这样有些成员的起始位置并不是某个字节的起始位置,那么这些位置处是没有地址的,内存中每个字节分配⼀个地址,⼀个字节内部的bit位是没有地址的

所以不能对位段的成员使⽤&操作符,这样就不能使⽤scanf直接给位段的成员输⼊值,只能是先输⼊放在⼀个变量中,然后赋值给位段的成员。
struct A
{
 int _a : 2;
 int _b : 5;
 int _c : 10;
 int _d : 30;
};

int main()
{
 struct A sa = {0};
 scanf("%d", &sa._b);//这是错误的
 
 //正确的⽰范
 int b = 0;
 scanf("%d", &b);
 sa._b = b;
 return 0;
}

二、联合体 

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

1.结构的声明

union Un
{
 char c;
 int i;
}u1;

unoin Un u2;
  • 关键字union,它表示接下来是一个联合体
  • Stu是联合名
  • {}中的是成员列表,可以是不同数据类型的变量

2.联合体特点

联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联 合至少得有能力保存最大的那个成员)
当最大成员不是最大对齐数的整数倍时,要对齐到最大对齐数的整数倍
union Un
{
	int i;
	char c;
};
int main()
{
	union Un un;

	printf("%p\n", &(un.i));
	printf("%p\n", &(un.c));
	printf("%d\n",sizeof(un));
}

我们打印出联合体成员的地址发现,这里我们可以发现 i 和 c的地址是相同的 

并且这个联合体的大小为最大成员大小int  ,4字节

竟然共用一块空间那么下面输出的结果是什么?

union Un
{
	int i;
	char c;
};
int main()
{
	union Un un;
	un.i = 0x11223344;
	un.c = 0x55;
	printf("%x\n", un.i);
}

为什么会这样呢,我来解释一下

 首先是i被赋值

但是c也被赋值了呀,但是是共用的一块内存,所以安装vs编译器从右向左分配内存所以会占掉1个字节 

 

所以打印出来为11223355 

所以,对于联合体而言,改一个成员,其他成员值都会改变

 3.联合大小的计算

  • 联合的大小至少是最大成员的大小。
  • 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍
union Un1
{
	char c[5];
	int i;
};
union Un2
{
	short c[7];
	int i;
};
int main()
{
	printf("%d\n", sizeof(union Un1));
	printf("%d\n", sizeof(union Un2));
	return 0;
}

un1,un2最大对齐数都为4

un1中最大成员大小为5(1 * 5),对齐到最大对齐数的整数倍,所以为8

un2中最大成员大小为14(2 * 7),对齐到最大对齐数的整数倍,所以为16


三、枚举

1.枚举类型的定义

enum Day//星期
{
 Mon,   //0
 Tues,  //1
 Wed,   //2
 Thur,  //...
 Fri,
 Sat,
 Sun
};
  • 关键字enum,它表示接下来是一个枚举
  • {}中的是枚举常量,默认为第一个成员为0 第二个1  依次累增

当然也可以赋值

enum Color//颜色
{
 RED=1,
 GREEN=2,
 BLUE=4
};

 2.枚举的优点

enum Color
{
 RED=1,
 GREEN=2,
 BLUE=4
};


#define RED 1
#define GREEN 2
#define BLUE 4

 这两种都可以定义常量,有时候枚举显得更加简洁一点

  1.  增加代码的可读性和可维护性
  2. #define定义的标识符比较枚举有类型检查,更加严谨。
  3. 防止了命名污染(封装)
  4. 便于调试
  5. 使用方便,一次可以定义多个常量

 3.枚举的使用

只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。

enum Color//颜色
{
 RED=1,
 GREEN=2,
 BLUE=4
};
enum Color clr = GREEN;

clr = 5;               //这样是错误的

 希望对你有用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值