详细讲解c语言结构体、联合体、枚举

目录

1、结构体类型声明

1.1结构体的定义

1.2 结构体变量的声明

1.3 结构体变量的创建和初始化

2、如何使创建的结构体所占内存最小

 2.1 结构体内存对齐-存储

 2.2 通过宏offsetof计算结构体内变量的偏移量

2.3 如何通过宏offsetof计算数组的偏移量

2.4 为什么存在内存对齐

 2.5 修改默认对齐数

3、结构体传参 

 3.1 值传递 - 传结构体

3.2 指针传递 - 传地址

4、联合体类型的声明

​ 4.1 联合体的特点

4.2 相同成员的结构体和联合体对比

4.3 联合体大小的计算

4.4 联合体举例​编辑

5、枚举类型

5.1 枚举类型的声明 

5.2 枚举类型的优点

5.3 枚举类型的使用


1、结构体类型声明

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

1.1结构体的定义

在C语言中,使用 struct 关键字定义结构体。结构体定义的一般语法是:

struct 结构体名称 
{
    数据类型1 成员1;
    数据类型2 成员2;
    // 更多成员声明
};
1.2 结构体变量的声明

(1)定义结构体后,可以使用结构体名称来声明结构体变量。声明结构体变量的一般语法是:

struct 结构体名称 变量名称;
struct Stu
{
	char name[20];//名字
	int age;//年龄
	char sex[5];//性别
	char id[20];//学号
}; //分号不能丢

struct Stu student1;

(2)也可以在定义结构体时直接声明结构体的变量:

struct 结构体名 
{
    数据类型 成员1;
    数据类型 成员2;
    // 其他成员变量
} 变量名;
struct Student 
{
    char name[20];
    int age;
    float score;
} stu1, stu2; // 在结构体定义时声明结构体变量

 这样,在声明结构体变量时,就可以直接使用结构体类型名和变量名来创建结构体实例。

// 修改结构体成员的值
strcpy(stu1.name, "Tom");
stu1.age = 20;
stu1.score = 85.5;

// 访问结构体成员的值
printf("Name: %s\n", stu1.name);
printf("Age: %d\n", stu1.age);
printf("Score: %.2f\n", stu1.score);

(3) 还可以同时定义结构体、声明结构体的变量并赋值。

1.3 结构体变量的创建和初始化

初始化方法有两种:①按照结构体成员的顺序初始化,②按照指定的顺序初始化

#include <stdio.h>
struct Stu
{
	char name[20];//名字
	int age;//年龄
	char sex[5];//性别
	char id[20];//学号
};
int main()
{
	//按照结构体成员的顺序初始化
	struct Stu s = { "张三", 20, "男", "20230818001" };
	printf("name: %s\n", s.name);
	printf("age : %d\n", s.age);
	printf("sex : %s\n", s.sex);
	printf("id : %s\n", s.id);
	//按照指定的顺序初始化
	struct Stu s2 = { .age = 18, .name = "lisi", .id = "20230818002", .sex = "⼥
	printf("name: %s\n", s2.name);
	printf("age : %d\n", s2.age);
	printf("sex : %s\n", s2.sex);
	printf("id : %s\n", s2.id);
	return 0;
}

2、如何使创建的结构体所占内存最小

 通过下面代码发现,具有相同成员变量的结构体所占内存大小并不相同。

 2.1 结构体内存对齐-存储

 如何理解上述对其规则?

(1)任何变量创建都需要在系统中开辟一块内存,且每个字节都有对应地址。当创建一个结构体时,同样需要为其开辟一块内存空间,结构体的首地址偏移量为0,之后每个字节为一个偏移量。  (2)分析上述第一个结构体,这个结构体第一个定义的是 char 类型的变量,占一个字节。根据对其规则第一条,从 0偏移量起到 1偏移量存放的是 char 类型变量 c1。                                              (3)根据对其规则第二条,结构体其他成员变量要对齐到 ‘对齐数’ 的整数倍的地址处,同时规定对齐数为该成员变量大小与 8 中的较小值。第一个结构体中第二个成员变量同样是 char 类型,占 1 个字节,1 和 8进行比较,1 < 8 ,所以此时对齐数为 1,所以结构体中第二个成员变量应放在对齐数的整数倍地址处,任何数都是1 的整数倍,所以将其继  c1之后放在偏移量为 1 处即可。          (4)第一个结构体中第三个成员变量为 int 类型,其大小为 4个字节,与 8进行比较,4<8,因此对齐数为 4,应该将该变量放在 4的整数倍的地址处,由于前四个字节处以及存放变量,所以要将该int 类型变量 i 放在偏移量为 4的地址处。                                                                                        (5)根据对其规则第 3条,结构体总大小为最大对齐数的整数倍,而第一个结构体中最大对齐数为 4,所以结构体总大小应该为 4的倍数,而根据前四点的分析,总共用了 8个字节,恰好是 4的倍数,因此第一个结构体的大小为 8。

        定义的第二个结构体 S2同理,第一个成员变量要对其到偏移量为 0的地址处,char 类型占一个字节。但是与第一个结构体不同的是,S2的第二个成员变量为 int类型,占四个字节,4 < 8,因此要将第二个成员变量 i 放在 4的整数倍地址处,然后再存放第三个char 类型成员变量,占一个字节。可以发现存放好三个变量后总共有 9个字节,但是结构体的总大小为最大对齐数的整数倍,该结构体最大对齐数为 4,9不是 4的倍数,因此还要继续占用空间,最终该结构体总共占用 12字节大小的内存,相比于第一个结构体,虽然包含了相同的变量,却使用了更多的内存空间。

那么对于如下情况,结构体内调用结构体的内存大小该如何计算?

#include<stdio.h>
struct S3
{
	double d;
	char c;
	int i;
};

struct S4
{
	char c1;
	struct S3 s3;
	double d;
};

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

       根据对齐规则第四条,如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,根据上面所学规则,可以计算出结构体 S3 的大小为 16个字节,其中最大的对齐数为 8,那么在结构体 S4中,创建的 S3结构体变量就应该对齐到第 8位的偏移量,然后 S4中的第三个变量为 double类型,占 8个字节,应该对齐到第 24位偏移量处,正好接到 3s后面。最后计算结构体 S4的总大小为最大对齐数的整数倍,最大对齐数为 s3的 16,因此,结构体 S4的总大小为32。

 2.2 通过宏offsetof计算结构体内变量的偏移量

  offsetof 是 C 语言标准库 <stddef.h> 中定义的宏。它用于计算结构体中成员的相较于起始位置的偏移量(offset)。offsetof 宏接受两个参数:第一个参数是一个结构体类型,第二个参数是结构体中的一个成员的名称。它返回指定成员在结构体中的字节偏移量。 

        以上面我们定义的 S3结构体中的 int i 变量以及 S4结构体中的 s3变量和 double d变量为例,可以发现正好与演示图中的位置对应。

2.3 如何通过宏offsetof计算数组的偏移量

       结构体内数组的偏移量是根据每个数组元素来计算的,对齐数为每个元素的大小,而不是整个数组的大小。

2.4 为什么存在内存对齐

 2.5 修改默认对齐数

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

#pragma pack(1)   设置默认对⻬数为1,一般为2的倍数
#pragma pack()   取消设置的对⻬数,还原为默认

3、结构体传参 

 3.1 值传递 - 传结构体

3.2 指针传递 - 传地址

4、联合体类型的声明

       联合体的定义、声明和结构体基本一样,使用的关键字是union。虽然结构上相同但是本质上却大不相同。如下代码所示,虽然联合体和结构体的变量相同,但是所占大小并不相同。

 
4.1 联合体的特点

       通过分析上述代码可以发现,联合体 un 的变量首地址相同,同时char类型变量 c 与 int 类型变量 i 共用第一个字节的空间。所以只有在使用 i 的时候不使用 c,在使用 c 的时候不使用 i 才能满足共存。也说明了联合体的大小至少是那个最大成员的大小。

        调试下面代码可以发现,首先为联合体 int 类型变量 i 赋值,再为 char 类型变量 c 赋值后就会将 int 类型变量 i 的第一个字节覆盖。

联合体的特点:

  1. 节省内存:由于联合体的成员共享同一块内存空间,因此节省了存储空间。

  2. 只能同时存储一个成员的值:联合体的各个成员共用一块内存,因此同一时间只能存储其中的一个成员的值。

  3. 最大成员决定了联合体的大小:联合体的大小取决于最大的成员的大小,不同于结构体,结构体的大小等于所有成员大小之和。

        联合体通常用于需要在不同数据类型之间进行转换或者需要节省内存空间的情况下,但需要注意在使用时确保正确地理解成员的含义和使用场景,避免出现错误。

4.2 相同成员的结构体和联合体对比

4.3 联合体大小的计算

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

4.4 联合体举例
struct gift_list
{
	//公共属性
	int stock_number;//库存量
	double price; //定价
	int item_type;//商品类型
	//特殊属性
	char title[20];//书名
	char author[20];//作者
	int num_pages;//⻚数
	char design[30];//设计
	int colors;//颜⾊
	int sizes;//尺⼨
};

struct gift_list
{
	int stock_number;//库存量
	double price; //定价
	int item_type;//商品类型
	union {
		struct
		{
			char title[20];//书名
			char author[20];//作者
			int num_pages;//⻚数
		}book;
		struct
		{
			char design[30];//设计
		}mug;
		struct
		{
			char design[30];//设计
			int colors;//颜⾊
			int sizes;//尺⼨
		}shirt;
	}item;
};

 如何访问上述结构体变量,仔细看下述代码即可:

(方法一)可以使用结构体变量名后跟成员访问运算符.,然后跟上成员变量的名称。对于包含在联合体中的成员变量,可以通过结构体变量名后跟成员访问运算符.,再跟上联合体的成员变量名,然后再跟上成员变量的名称。

struct gift_list myGift;

 访问 stock_number 变量
myGift.stock_number = 10;

 访问 price 变量
myGift.price = 29.99;

 访问 item_type 变量
myGift.item_type = 1;

 访问 book 结构体中的 title 变量
strcpy(myGift.item.book.title, "The Book Title");

 访问 book 结构体中的 author 变量
strcpy(myGift.item.book.author, "The Author");

 访问 book 结构体中的 num_pages 变量
myGift.item.book.num_pages = 200;

 访问 mug 结构体中的 design 变量
strcpy(myGift.item.mug.design, "The Mug Design");

 访问 shirt 结构体中的 design 变量
strcpy(myGift.item.shirt.design, "The Shirt Design");

 访问 shirt 结构体中的 colors 变量
myGift.item.shirt.colors = 3;

 访问 shirt 结构体中的 sizes 变量
myGift.item.shirt.sizes = 2;

(方法二) 除了使用.操作符来访问结构体中的成员变量之外,还可以使用指针和->操作符来访问结构体成员变量。

struct gift_list myGift;
struct gift_list *ptrGift = &myGift; // 定义一个指向结构体的指针,并指向结构体变量

 使用指针和 -> 操作符来访问结构体成员变量
ptrGift->stock_number = 10;
ptrGift->price = 29.99;
ptrGift->item_type = 1;

 访问 book 结构体中的 title 变量
strcpy(ptrGift->item.book.title, "The Book Title");

 访问 book 结构体中的 author 变量
strcpy(ptrGift->item.book.author, "The Author");

 访问 book 结构体中的 num_pages 变量
ptrGift->item.book.num_pages = 200;

 访问 mug 结构体中的 design 变量
strcpy(ptrGift->item.mug.design, "The Mug Design");

 访问 shirt 结构体中的 design 变量
strcpy(ptrGift->item.shirt.design, "The Shirt Design");

 访问 shirt 结构体中的 colors 变量
ptrGift->item.shirt.colors = 3;

 访问 shirt 结构体中的 sizes 变量
ptrGift->item.shirt.sizes = 2;

5、枚举类型

5.1 枚举类型的声明 

enum Day//星期
{
	Mon,
	Tues,
	Wed,
	Thur,
	Fri,
	Sat,
	Sun
};
enum Sex//性别
{
	MALE,
	FEMALE,
	SECRET
};
enum Color//颜⾊
{
	RED,
	GREEN,
	BLUE
};

enum Color//颜⾊
{
	RED = 2,
	GREEN = 4,
	BLUE = 8
};
5.2 枚举类型的优点

5.3 枚举类型的使用
enum Color//颜⾊
{
RED=1,
GREEN=2,
BLUE=4
};
enum Color clr = GREEN;//使⽤枚举常量给枚举变量赋值

在下面的示例中,我们定义了一个枚举类型Season,并包含了四个枚举常量:SPRINGSUMMERAUTUMN 和 WINTER。在main函数中,我们声明并初始化了一个枚举变量currentSeason,然后根据不同的季节常量输出相应的信息。

枚举类型的特点包括:

  1. 枚举常量默认从0开始递增,也可以手动指定初始值。
  2. 枚举常量在定义时不能重复命名。
  3. 可以将枚举类型用于变量声明、函数参数等地方,以提高代码的可读性。

需要注意的是,枚举类型虽然提高了代码的可读性,但会占用一定的内存空间,因此在需要大量枚举常量时,应慎重使用。

#include <stdio.h>

// 定义枚举类型 Season
enum Season {
    SPRING,
    SUMMER,
    AUTUMN,
    WINTER
};

int main() {
    // 声明并初始化一个枚举变量
    enum Season currentSeason = SPRING;

    // 使用枚举变量
    if (currentSeason == SPRING) {
        printf("It's Spring!\n");
    } else if (currentSeason == SUMMER) {
        printf("It's Summer!\n");
    } else if (currentSeason == AUTUMN) {
        printf("It's Autumn!\n");
    } else if (currentSeason == WINTER) {
        printf("It's Winter!\n");
    }

    return 0;
}

      那是否可以拿整数给枚举变量赋值呢?在C语⾔中是可以的,但是在C++是不⾏的,C++的类型检查比较严格。
 

  • 25
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

做完作业了

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值