【C语言】自定义类型详解:结构体、枚举、联合体

目录

一、结构体详解

1、结构体类型的声明

1.1.结构体是什么?

1.2.结构体声明

1.3.特殊的结构体声明(匿名结构体)

2、结构体的自引用

3、结构体变量的定义和初始化

4、结构体的内存对齐

内存对齐的规则是什么呢?

列1:

列2:

列3:

列4.内嵌结构体:

宏offsetof

#pragram pack()

 为什么会出现内存对齐?

5、结构体传参

6、结构体实现位段(填充&移植)

1.什么是位段?

2.位段的内存分配

3.位段跨平台的具体问题

4.位段的运用

二、枚举详解

1)、枚举类型的定义

2)、枚举的优点

3)、枚举的使用

三、联合体详解

1)、联合体类型的定义

2)、联合体类型的特点

3)、联合体的大小计算


一、结构体详解


1、结构体类型的声明


1.1.结构体是什么?

结构体是用来定义一个复杂对象,其关键字:struct

1.2.结构体声明

如果我们需要来描述一本书:书名、价格、序号

struct Book
{
	char name[20];// 书名
	int price;// 价格
	int list;// 序号
};//这就是我们声明的一个书的类型

同时我们可以在声明时定义一个变量

struct Book
{
	char name[20];// 书名
	int price;// 价格
	int list;// 序号
}bk1;// 我们定义了一个结构体变量 变量名是bk1

1.3.特殊的结构体声明(匿名结构体)

如果我们在声明时没有给这个类型附加类型名,那么这就是一个匿名结构体。

注意:如果我们定义的是一个匿名结构体,那么我们只能在声明时定义变量,以后就不能在使用这个匿名结构体去定义变量了。

struct
{
	char a;
	int b;
	short c;
}x;// 这就是一个匿名结构体,注意匿名结构体只能被使用一次

为了区分一下特殊情况看看如下代码:

struct
{
	char a;
	int b;
	short c;
}x;

struct sct
{
	char a;
	int b;
	short c;
}*p;定义了一个结构体指针

p = &x;//正确吗??可以这样用吗??

答案是不正确的,这两个结构体虽然成员的类型是一样的,但是编译器会将其区分为两个结构体类型,而两给个不同的类型的指针是不能赋值的。


2、结构体的自引用


定义:自引用就是在结构体的内部,有一个成员类型是这个结构体的指针。

struct Node
{
	int val;
	struct Node* next;
};// 这个结构体里面有一个自己的指针

那么可不可以将其成员写成自己这个结构体类型呢?不行,编译器不知道到底应该分配多少空间给这个结构体。

struct Node
{
	int val;
	struct Node next;
};// 这样是不行的,结构体里面有结构体,然后这个结构体里面又有结构体………………这样
  //编译器就不知道到底该为这个结构体分配多少空间了。

技巧:我们定义一个结构体变量时,总是会反复的写重复的关键字,我们可以typedef这个类型,方便我们以后定义变量时,编写迅速

typede fstruct Node
{
	int val;
	struct Node* next;
}Node;// 将这个结构体类型名重定义为 Node

那么我们在自引用时可不可以直接写重定义的类型名呢?

typedef struct Node
{
	int val;
	Node* next;
}Node;// 这样时不行的,这时会有一个经典的问题:是先有鸡,还是现有蛋。
     // 这里也是,是先有这个结构体,还是先重定义。

3、结构体变量的定义和初始化


在声明时定义&在普通定义

struct Book
{
	char name[20];// 书名
	int price;// 价格
	int list;// 序号
}bk1;

struct Book bk2;

初始化

struct Book
{
	char name[20];// 书名
	int price;// 价格
	int list;// 序号
}bk1;

struct Book p = { "mane", 75, 1 };

嵌套初始化

struct Book
{
	char name[20];// 书名
	int price;// 价格
	int list;// 序号
}bk1 = { "name", 75, 2};

struct Book
{
	char name[20];// 书名
	int price;// 价格
    struct Book p;
	int list;// 序号
}bk1;

bk1 = { "name", 75, {"n", 60, 1}, 2 };

4、结构体的内存对齐


每一种类型都会有自己的大小,比如int占4个字节,char占1一个字节,结构体类型也是,但是结构体的成员的不固定的,其大小也是不固定的,并且有自己的一套大小计算方法(内存对齐)。

// vs结构体默认对齐数是8
struct S
{
	char ch;// 8 -> 1
	int i;// 8 -> 4
	char c;// 8 -> 1
};// 内存:12字节

内存对齐的规则是什么呢?

1、第一个成员在与结构体偏移量为0的地址处。

2、其他成员要对齐到这个成员的对齐数的整数倍偏移量地址处。

(成员的对齐数:默认对齐数 与 这个成员的大小 相比较的较小值

 (首先:我们需要知道默认对齐数。(vs编译器中是8))

3、结构体的总大小为 成员最大对其数的整数倍

4、如果嵌套了结构体成员p,那么这个嵌套结构体成员p的对齐数就等于这个结构体p的成员的最大对齐数。

列1:

// vs结构体默认对齐数是8
struct S
{
	char ch;// 8 -> 1
	int i;// 8 -> 4
	char c;// 8 -> 1
};// 内存:12字节

图解1:

 

列2:

struct S1
{
	int i;// 8 -> 4
	char ch;// 8 -> 1
	char c;// 8 -> 1
};// 内存:8字节

图解2:

 

列3:

struct S2
{
	char ch;// 8 -> 1
	int i;// 8 -> 4
	double b;// 8 -> 8
};// 内存:16字节

图解3:

列4.内嵌结构体:

struct S2
{
	char ch;// 8 -> 1
	int i;// 8 -> 4
	double b;// 8 -> 8
};// 内存:16字节

struct S3
{
	char ch;// 8 -> 1
	struct S2;// 8 -> 8
	double b;// 8 -> 8
};// 内存:32字节

 图解4:

 

 

宏offsetof

如果我们不确定自己所求的成员偏移量,我们可以利用offsetof这个宏,将一个结构体的成员偏移量打印出来:

其参数是这个结构体类型,其成员名

struct S3
{
	char ch;// 8 -> 1
	struct S2 s;// 8 -> 8
	double b;// 8 -> 8
}c;// 内存:32字节
int main()
{
	printf("%u\n",offsetof(struct S3, s));


    return 0;
}

#pragram pack()

vs中默认对其数是8,如果我们需要可以将其修改为我们所需要的对齐数


struct S2
{
	char ch;// 8 -> 1
	int i;// 8 -> 4
	double b;// 8 -> 8
};// 内存:16字节

#pragma pack(1) // 将默认对齐数修改为1
struct S3
{
	char ch;// 8 -> 1
	struct S2 s;// 8 -> 1
	double b;// 8 -> 1
}c;// 内存:1 + 16 + 8 = 25字节

 为什么会出现内存对齐?

1、平台(移植)原因:并不是所有的硬件都可以在任何地址上存放内容的,一些硬件只能在某一些地址处取地址,否则硬件就会报出错误

2、性能原因:在访问内存的时候,由于处理器是32位的,一次处理32个位,如果内存不对齐,就有可能一个数据需要在内存中去取两次的情况

总结:拿空间换时间

5、结构体传参


1、值传递

直接将这个结构体变量实参传给函数

这样做是不好的,调用函数时会发生压栈的操作,而值传递一个结构体,这个结构体占用内存较大,就会过多的耗损空间和时间,增大系统开销,所以我们尽量采用指针传递。

2、指针传递

将这个结构体的指针传给函数,不管什么指针,只要是指针就只会占用四个字节


6、结构体实现位段(填充&移植)


1.什么是位段?

位段和结构体相识有些许的不同

i.位段的成员只能是 int, unsigned int, char

ii.位段成员后面有一个冒号(:)

struct A
{
	int _a : 2;
	int _b : 5;
	int _c : 10;
	int _d : 30;
};

2.位段的内存分配

i.位段的成员是int /unsigned int/char

ii.开辟的空间是按照需要以四字节或者一字节来开辟的

iii.位段空间分配会涉及到很多内容,所以位段是不跨平台的

struct A
{
	int _a : 2;// 2 个bit位
	int _b : 5;// 5 个bit位
	int _c : 10;// 10 个bit位
	int _d : 30;// 30 个bit位
};// 8字节

成员冒号后面的值表示这个成员占用多少bit位,最大不得超过这个成员的最大值,这里不能超过32,在vs环境下,当当前开辟的空间剩余的bit不能容纳下一个成员时,会开辟一个新的4字节空间。

 

3.位段跨平台的具体问题

i.int 被当作有符号还是无符号是不确定的

ii.位段中最大位数不能确定。(16位机器下int十六位,32位机器下int三十二位)

iii.位段成员是由左到右还是从右到左分配没有规定

iiii.当结构不能容纳下一个成员时,是舍弃当前剩余bit位,还是不舍弃,不确定

4.位段的运用

比如网络ip地址就是运用的位段

 


二、枚举详解

枚举就是一一列举


1)、枚举类型的定义


关键字:enum

enum Day//星期
{
 Mon,// 0
 Tues,// 1
 Wed,// 2
 Thur,// 3
 Fri,// 4
 Sat,// 5
 Sun //8
};// 这个成员的值实际上就是 由0开始依次递增的值
enum Sex//性别
{
 MALE,
 FEMALE,
 SECRET
};
enum Color//颜色
{
 RED,
 GREEN,
 BLUE
};

2)、枚举的优点


1. 增加代码的可读性和可维护性

2. 和#define定义的标识符比较枚举有类型检查,更加严谨。

3. 防止了命名污染(封装) 4. 便于调试

5. 使用方便,一次可以定义多个常量


3)、枚举的使用


enum Day//星期
{
	Mon,
	Tues,
	Wed,
	Thur,
	Fri,
	Sat,
	Sun
};
int main()
{
	enum Day d = Wed;

	return 0;
}

我们可以将枚举成员设置初始值

enum Day//星期
{
	Mon = 5,
	Tues,
	Wed,
	Thur,
	Fri,
	Sat,
	Sun
};

但我们不可以对这些成员再赋值

enum Day//星期
{
	Mon = 5,
	Tues,
	Wed,
	Thur,
	Fri,
	Sat,
	Sun
};


Wed = 4;//这是错误的

三、联合体详解

联合体成员公用一块空间


1)、联合体类型的定义

关键字:union

//联合类型的声明
union Un
{
 char c;
 int i;
};
//联合变量的定义
union Un un;
//计算连个变量的大小
printf("%d\n", sizeof(un));// 4


2)、联合体类型的特点


联合体成员共用一块空间

union Un
{
 int i;
 char c;
};
union Un un;
// 下面输出的结果是一样的吗?
printf("%d\n", &(un.i));
printf("%d\n", &(un.c));
//下面输出的结果是什么?
un.i = 0x11223344;
un.c = 0x55;
printf("%x\n", un.i);

前面两行表示,成员共用一块内存

第三行表示,成员之间会相互影响

所以联合体一般一次只使用一个成员


 

3)、联合体的大小计算

1.联合体的大小至少是最大成员的大小

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


union Un1
{
 char c[5];// 8 -> 1
 int i;// 8 -> 4
};// 8 字节
union Un2
{
 short c[7];// 8 -> 2
 int i;// 8 -> 4
};// 16字节
//下面输出的结果是什么?
printf("%d\n", sizeof(union Un1));
printf("%d\n", sizeof(union Un2));

 


 

  • 9
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

绅士·永

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

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

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

打赏作者

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

抵扣说明:

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

余额充值