边学边记——自定义类型(结构体,联合体,枚举)

发布于:2023-01-20 ⋅ 阅读:(177) ⋅ 点赞:(0)

目录

一.结构体

1.定义

2.初始化

(1).先声明,后初始化

(2).使用 typedef 关键字声明,再初始化

 (3)声明的同时定义

3.结构体的自引用

(1).不使用 typedef 时

(2).使用 typedef 时

4.结构体的内存对齐(计算结构体的大小)

(1).为什么存在内存对齐?

(2).结构体的对齐规则

(3).修改默认对齐数

(4).举例

5.位段

(1).什么是位段

(2).举例说明

(3).总结

 二.联合体

1.定义

 2.联合体的特点

3.联合大小的计算

4.联合体的应用场景

三.枚举

1.定义

2.优点(原因)


一.结构体

1.定义

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

我们可以先来回顾一下基础的数据类型有:

1、整型

  • int:基本整型,存储整数,占4个字节。
  • short:短整型,占2个字节。
  • long:长整型,占4个字节。
  • long long:双长整型,占8个字节。

2、浮点型

  • float:单精度浮点型,占4个字节。
  • double:双精度浮点型,占8个字节。

3、字符型

  • char:字符型,存储单字符,占1个字节。

         当我们要描述一个复杂的对象时,他可能有很多特征需要来描述,但是这些特征又不全是一个类型的,我们用基础数据类型就没有办法很好的展示这个对象,这时我们就可以用结构体类型,简单来说,结构体类型就是将多个基础数据类型可以结合到一起,将其组成一个整体的复杂数据类型。

2.初始化

eg:猫类型:包括姓名,年龄,体重,叫声的特征描述。

(1).先声明,后初始化

struct cat{//猫类型
	char name[30];//姓名
	int age;//年龄
	int weight;//体重
	char voice[30];//叫声
};//注意分号

 实例化: 

void main{
    struct cat c1={"小白",2,10,"喵喵喵"};//定义结构体变量c1时同时初始化
    struct cat *p1 = NULL;//也可以定义结构体指针变量p1
}

(2).使用 typedef 关键字声明,再初始化

typedef 为C语言的关键字,作用是为一种数据类型定义一个新名字。这里的数据类型包括内部数据类型(int,char等)和自定义的数据类型(struct等)。

在编程中使用typedef目的一般有两个,一个是给变量一个易记且意义明确的新名字,另一个是简化一些比较复杂的类型声明 。

typedef struct cat{
	char name[30];
	int age;
	int wegiht;
	char voice[30];
};

实例化:

void main{
    cat c2={"小白",2,10,"喵喵喵"};//定义结构体变量c2时同时初始化
    cat *p2 = NULL;//结构体指针变量p2
}

 (3)声明的同时定义

struct cat{
	char name[30];
	int age;
	int wegiht;
	char voice[30];
}c3,*p3;//声明的同时定义结构体变量c3和结构体指针变量p3

实例化: 

void main(){
	c3 = { "小白", 2, 10, "喵喵喵", NULL };
	p3 = NULL;
}

也可以在声明定义时直接初始化: 

struct cat{
	char name[30];
	int age;
	int wegiht;
	char voice[30];
	struct cat *next;
}c3={"小白",2,10,"喵喵喵"};

注意:结构体变量中的成员变量,只有在初始化的时候可以通过赋值进行初始化,定义完毕后就不能使用赋值进行整体的成员变量初始化了(此时的操作就是赋值操作,不能称之为初始化)。 

比如说如下操作,就是对c3的赋值操作,这时已经不能再叫初始化了。

struct cat{
	char name[30];
	int age;
	int wegiht;
	char voice[30];
	struct cat *next;
}c3,*p3;

void main(){
	c3 = { "小白", 2, 10, "喵喵喵" };
	p3 = NULL;
}

3.结构体的自引用

结构体的自引用(self reference),就是在结构体内部,包含指向自身类型结构体的指针。

(1).不使用 typedef 时

错误用法:

typedef struct node{
	int data;
    struct node next;//错误
};

这种方式是错误的,因为这种声明实际上是一个无限循环,成员 node 是一个结构体,node 的内部还会有成员 next 是结构体,同理next内部还有成员是结构体,我们也不知道结构体node的大小,其实是无法分配内存的,这种方式是非法的。

正确用法:

那我们该如何实现自引用呢?这时我们就可以利用指针,指针的大小是固定的,比如在32位下是4个字节,这时就不会存在内存长度未知的情况了。

typedef struct node{
	int data;
    struct node *next;//正确
};

(2).使用 typedef 时

错误用法:

typedef struct {  
    int data;  
    node *next;//虽然使用了指针 但这里的node尚未被定义
} node; 

正确用法:

//方法一  
typedef struct node_1{  
    int data;  
    struct node_1 *p;    
}NODE;  

//方法二  
struct node_2;  
typedef struct node_2 NODE;  
struct node_2{  
    int data;  
    NODE *p;    
};  
  
//方法三 
struct node_3{  
    int daata;  
    struct node_3 *p;    
};  
typedef struct node_3 NODE;

4.结构体的内存对齐(计算结构体的大小)

(1).为什么存在内存对齐?

1. 平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2. 性能原因: 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
总体来说:结构体的内存对齐是拿空间来换取时间的做法。
那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:
让占用空间小的成员尽量集中在一起。

(2).结构体的对齐规则

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

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

3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

(3).修改默认对齐数

#pragma 这个预处理指令,可以改变我们的默认对齐数。

#pragma pack(n)
//设置默认对齐数为n

#pragma pack()
//还原默认对齐数

为什么要修改默认对齐数? 

        结构在对齐方式不合适的时候,我们可以自己更改默认对齐数。比如说在进行网络通信的时候,将一个结构体变量(一块连续的内存数据)发送给了另一个主机,如果两个平台对齐数不同,在进行内存中数据解释的时候就会出现问题,因此两个主机都必须使用指定大小的对齐数。

eg

#include <stdio.h>

#pragma pack(8)
//设置默认对齐数为8

struct S1{
	char c1;//1
	int i;//4
	char c2;//1
}; //16

#pragma pack()
//取消设置的默认对齐数,还原为默认

(4).举例

:该结构体默认对齐数为8,则结构体总大小为多少?

struct student{
	int sno;
	char num;
	int age;
	short score;
};

画图理解: 

  最后答案为:16。 

5.位段

(1).什么是位段

位段,C语言允许在一个结构体中以位为单位来指定其成员所占内存长度,这种以位为单位的成员称为“位段”或称“位域”( bit field) 。利用位段能够用较少的位数存储数据。

(2).举例说明

        由以下结构体举例,我们可以看出该结构体的总大小为12,但是我们可以发现,sex和pos属性其实根本占用不了1个字节,sex属性只需1个比特位,pos属性只需两个比特位,所以这样定义还是会造成一些空间的浪费。

struct student{
	int sno;//4
	char sex;//1
    //0-男 1-女
	char pos;//1
    //0-普通学生 1-班长 2-学习委员 3-生活委员
	int score;//4
};//12

那么我们是否可以采取一些措施,使该结构体的大小做到“物尽其用”呢?

这时就出现了位段,通过 “member:n(member为成员变量,n为占据的比特位)” 的形式指定空间中n个比特位存储数据,并且多个位段可以合并占用同一个存储空间,比如sex和pos成员宫廷使用一个字节的空间就够了,从而实现减少空间浪费的目的。

struct student{
	int sno;
	char sex : 1;//0-男 1-女
	char pos : 2;//0-普通学生 1-班长 2-学习委员
	int score;
};

sex和pos会合并使用一个字节,在vs平台下从低位开始赋值。

画图理解

注意:在vs平台下,多个位段,使用同一块内存空间,从低位开始向高位存储数据,如果比特位不够了则申请一个新的空间存储。

(3).总结

  • 为什么使用位段:可以节省空间。
  • 位段的成员必须是 int、unsigned int 或signed int 。
  • 位段的成员名后边有一个冒号和一个数字。
  • 位段存储的数据过大会产生截断。
  • 第一个位段剩余的空间不够存储第二个位段,则会为第二个位段新开空间存储。
  • 使用位段后不存在字节对齐了。
  • 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。

 二.联合体

1.定义

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

//联合类型的声明
union Un{
    char c;
    int i;
};

void main(){
	//联合变量的定义
	union Un un;
	//计算连个变量的大小
	printf("%d\n", sizeof(un));
}

c 成员和 i 成员都是从起始地址处,共用同一块空间,只不过 c 成员只可以访问1个字节,a 成员可以访问4个字节。

 运行结果如下:

 2.联合体的特点

联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)。

3.联合大小的计算

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

4.联合体的应用场景

(1).大小端的检测。

关于大小端的介绍可以访问以下链接查看:

C语言数据存储

union Un{
    char c;
    int i;
};

void main(){
	//联合变量的定义
	union Un un;
    //大小端判断
	un.i = 1;
	if (un.c == 1){
		printf("小端\n");
	}
}

(2).在不同的场景下适用不同的数据。

三.枚举

1.定义

枚举顾名思义就是一一列举。
把可能的取值一一列举。

比如我们现实生活中:
一周的星期一到星期日是有限的7天,可以一一列举。

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

{ } 中的内容是枚举类型的可能取值,也叫 枚举常量 。这些可能取值都是有值的,默认从0开始,依次递增1。当然在定义的时候也可以赋初值。

eg

enum Color//颜色
{
RED=1,
GREEN=2,
BLUE=4
};
enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值

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

2.优点(原因)

我们也可以使用 #define 定义常量,为什么非要使用枚举?

有以下几点:

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

 


网站公告

今日签到

点亮在社区的每一天
去签到