超详细的C进阶教程!(四)深度解剖C语言自定义类型

作者的码云地址:https://gitee.com/dongtiao-xiewei
后续作者会更新力扣的每日一题系列,原代码会全部上传码云,推荐关注哦,笔芯~

还像更深入地了解c语言?快来订阅作者的c语言进阶专栏!作者承诺本系列不会TJ!预计更新:指针,字符串处理,内存管理,结构体,预处理等等

自定义变量

  • 引言
  • 结构体
    • 结构体声明
      • 匿名结构体类型
    • 结构体自引用
    • 结构体的定义与初始化
    • 结构体内存对齐
    • 结构体传参
    • 位段
  • 枚举
    • 枚举的定义
    • 枚举的取值
    • 枚举的应用
  • 联合体
    • 联合体定义
    • 联合的特点
    • 联合体大小的计算

引言

嗨喽小伙伴们我又来啦!冲冲冲!

超详细的C进阶教程!(四)深度解剖C语言自定义类型_第1张图片

在我们刚开始学C语言,还是个C语言萌新时候,想必已经对结构类型这四个字已经耳朵都快听出茧子了吧!

为了让计算机能够控制各种各样的数据,C语言中引入了像char,short,int,float,等基本数据类型。使我们能够控制一些简单的数据。

但是,有没有想过,有些复杂的数据类型,不能单纯的由一个基本数据类型描述的,该怎么办?

比如,我们要描述一个学生

变量名 基本数据类型
姓名 char
年龄 int
性别 char
成绩 float

亦或者,我们要描述一本书的信息

变量名 基本数据类型
书名 char
价格 float
作者 char
出版社 char

我们可以观察到,这些信息只用一个基本数据类型是远远不能描述的,所以,为了解决这个问题,我们引入了自定义类型

而解决以上的问题,用到结构体就行了

结构体

结构体是一堆值的集合,这些值被称为成员变量

我们可以在结构体类型中封装一系列变量,方便描述我们在引例中给出的复杂类型

结构体声明

它的声明通用方式是

struct tag//结构体类型名
{
     
	//member
	//这里定义成员变量
};//分号千万不能丢

可以紧跟在分号后面创建变量,创建的为全局变量,也可以直接在此进行初始化

{
     
	...
}s1,s2;//创建方式

例如,我们将引例中的例子来用结构体说明

//学生类型
struct Stu
{
     
	char name[20];
	int age;
	char sex[5];
	float score;
};
//书信息类型
struct Book
{
     
	float price;
	char name[20];
	char author[20];
};

等等。

但是下面有一种不寻常的声明方式

匿名结构体类型

声明的时候,可以省略结构体的名字

struct//这里的tag被省略了
{
     
	int a;
	int b;
}x;

因为省略了结构体类型名,所以我们不能在主函数中定义结构体变量了

在声明后面定义也是唯一的方式

结构体自引用

我们应该都接触过链表这种数据结构,它在内存中不是连续存放的,但它们之间有一种逻辑关系,可以把它们联系在一起

超详细的C进阶教程!(四)深度解剖C语言自定义类型_第2张图片

有没有想过,它们是如何联系起来的呢?

这样定义链表可以吗?

struct ListNode
{
     
	int data;
	struct ListNode next;
};

萌新可能比较容易理解为,一个链表先是一个数据,而为了存放下一个链表,在结构体中再定义一个结点结构体,方便查找下一个结点

如果这样可行的话,那这个你怎么算?

sizeof(struct ListNode)

很显然算不出来,因为在一个结构体中再定义一个自己,自己套自己,将会无限套娃下去

超详细的C进阶教程!(四)深度解剖C语言自定义类型_第3张图片

所以这个方法显然不可行

那么,如何实现正确的自引用呢?

正确的自引用方式

可以考虑使用地址,还是拿链表举例

struct ListNode
{
     
	int data;
	struct ListNode* next;
};

它现在在内存中的存储就是这样的了

超详细的C进阶教程!(四)深度解剖C语言自定义类型_第4张图片

结构体的定义与初始化

定义:全局定义,局部定义

定义的类型名:struct tag

例如定义一个学生变量

struct Stu s1;

初始化:用大括号初始化,成员变量用逗号隔开

还是上面的学生类型

s1={"zhangsan",18,"man",99,5};

初始化也可以嵌套初始化

结构体内存对齐

这里我们主要讨论一个问题:如何计算结构体类型的大小

struct S1
{
     
	char a;
	char b;
	int c;
};

struct S2
{
     
	char a;
	int b;
	char c;
};

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

可能大家就猜测了,一个char1字节,一个int4字节,大小是不是就是6字节啊?

遗憾的是,并不是,不仅不是,这两个结构体大小都不一样

超详细的C进阶教程!(四)深度解剖C语言自定义类型_第5张图片

那怎么计算结构体变量大小的呢?

这里就需要用到结构体对齐了,直接先上结论再解释

  1. 第一个成员在与结构体变量偏移量为0的地址处。
  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
    对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。

VS中默认的值为8
Linux中的默认值为4

  1. 结构体总大小为最大对齐数的整数倍

这里画图为大家详细的解释一下

超详细的C进阶教程!(四)深度解剖C语言自定义类型_第6张图片
现在大家应该就会算结构体类型大小的吧?

那为什么会存在内存对齐现象呢?

大家可以重点记住,这是一种用空间换取时间的做法

为了节省空间,我们可以把小的成员变量定义在一起

默认对齐数也是可以修改的

#pragma pack()//括号内是默认对齐数,建议修改为2^n

结构体传参

结论:传参的时候尽量使用指针,因为传值调用需要压栈,会重新复制一份
结构体,导致性能的下降,而在函数内修改结构体的内容也必须用到指针

使用方法

void func(struct S2* ps)
{	
	ps->a=0//注意这里使用箭头,结构体为指针时的成员访问方式
}

位段

这里好多教材上都没有哦!

我们需要先明白几个概念:

  1. 这里的位,指的是二进制的位
  2. 位段可以同样使用结构体实现
  3. 位段的成员必须是int,unsigned int(标准的定义没有char类型,但经过实验,char类型也可实现位段)

位段的声明如下

struct Test
{
     
	char a:3;
	char b:4;
	char c:5;
	char d:4;
};

位段名后面必须跟一个冒号和数字

那么,位段是如何开辟的空间?

重点:位段后面的数字,表示这个变量需要占用的比特位

例如test结构体位段

超详细的C进阶教程!(四)深度解剖C语言自定义类型_第7张图片
如果我们尝试修改呢?

注意,这里同样会发生截断现象!

struct Test
{
     
	char a : 3;
	char b : 4;
	char c : 5;
	char d : 4;
}s;

int main()
{
     
	s.a = 10;
	s.b = 12;
	s.c = 3;
	s.d = 4;
	return 0;
}

它在内存中的情况就应该是以下这样

超详细的C进阶教程!(四)深度解剖C语言自定义类型_第8张图片
通过调试,验证了我们的结论

超详细的C进阶教程!(四)深度解剖C语言自定义类型_第9张图片
位段设计出来,有什么作用呢?

可以节省空间
比如,我们的学生类型中的分数变量,我们发现分数永远不会超过100分,所以我们只需要7个比特位存储,而不需要占用4字节,也就是32比特位了

枚举

注意,枚举是一种常量类型!

它与define定义的宏,和const都属于常量

顾名思义,枚举类型就是把可能的取值,一一列举出来

枚举的定义

枚举的通用定义如下

//enum枚举关键字

enum name//枚举类型名
{
     	
	//member list枚举成员名
};

比如我们要定义一个星期

//enum为枚举类型关键字

enum Day
{
     
	MON,
	TUES,
	WED,
	THUR,
	FRI,
	SAT,
	SUN
};

枚举的取值

默认第一个变量为0,一次递增1,当然可以在定义中赋予初值,后面的变量会继续以被赋予的值继续加一

enum Day
{
     
	MON=1,
	TUES,
	WED,
	THUR,
	FRI,
	SAT,
	SUN
};

枚举的应用

基本使用:

可以定义枚举类型的变量,注意!定义了便不能被修改!

enum Day day1=MON;//定义了一个day1的枚举类型常用,初始化为MON

应用

比如我们要实现一个计算器

写一个计算器的菜单

  1. add 2. sub
  2. mul 4. div

我们在代码的选项中可以这样写

switch (option)
case 1:
case 2:
case 3:
case 4:

用数字来代替选项,会带来许多不必要的麻烦,如菜单对照等

我们考虑可以使用枚举变量

enum Option
{
     
	ADD=1,
	SUB,
	MUL,
	DIV
};

将菜单改为以下这样,就方便我们进行对照了

switch (option)
case ADD:
case SUB:
case MUL:
case DIV:

联合体

它的特征是成员变量共用一块存储空间

联合体定义

与结构体定义类似

union Test
{
     	
	char c;
	int i;
}

联合的特点

就是共用一块成员变量,我们可以写个简单的代码验证一下

union U
{
     
	int i;
	char c;
}u;

int main()
{
     
	u.i = 0x11223344;
	u.c = 0x55;
	printf("%x\n",u.i);
	return 0;
}

输出结果为

超详细的C进阶教程!(四)深度解剖C语言自定义类型_第10张图片

联合体大小的计算

也存在内存对齐现象

  1. 至少为成员最大变量的大小,因为要确保能容纳下最大的变量
  2. 当最大成员大小不是最大对齐数整数倍的时候,要对齐到最大对齐数的整数倍

例如

union U
{
     
	char c[5];
	int i;
}

因为最大对齐数是4(int),并不是最大变量5的整数倍(char[5])

所以对齐前是5字节,对齐后就是8字节

你可能感兴趣的:(C语言进阶系列,c语言,c#,c++)