> 文档中心 > 结构体,枚举,联合果宝三剑客详解

结构体,枚举,联合果宝三剑客详解

文章目录

  • 一、乱臣贼子---结构
    • 1.1结构体类型的声明
      • 1.1.1特殊的声明
    • 1.2结构体的自引用
    • 1.3结构体变量的定义和初始化
    • 1.4结构体大小计算(内存对齐知识)
      • 1.4.1修改默认对齐数
    • 1.5结构体传参
    • 1.6结构体实现位段
      • 1.6.1什么是位段
      • 1.6.2位段的内存分配
      • 1.6.3位段的跨平台问题
  • 二、认贼作父---枚举
    • 2.1枚举类型的定义
    • 2.2枚举定义变量
    • 2.3枚举的优点
  • 三、贼眉鼠眼---联合
    • 3.1联合类型的定义
    • 3.2联合特点
    • 3.3联合大小的计算

一、乱臣贼子—结构体

首先是最难对付的乱臣贼子—结构体,他的体量最大,是各种值的集合,这些值称为成员变量。每个成员可以是不同类型的变量

1.1结构体类型的声明

struct tag(结构体标签){member list;}variable-list;

例如想描述一个学生:

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

这里的struct 和名称标签Stu两者合起来整体算一个类型名

1.1.1特殊的声明

在声明结构体时,我们可以不完全的声明
如:

//匿名结构体类型struct{ int a; char b; float c;}x;struct{ int a; char b; float c;}a[20], *p;

可以看到,上述两种结构体类型的声明都没有给出名称标签(tag)
那么问题来了?
在上述两个匿名结构体类型成员变量都相同的这种情况下

p=&x

是合法的吗?即编译器会将他们俩识别为同一个吗
答案是不会的,所以是非法的
同时要注意,因为这个结构体类型是匿名的,所以我们后续是无法调用的,只能在刚创建时定义变量。

1.2结构体的自引用

当然的,我们也可以在结构体中引用自身

struct node{     int data;     struct node NEXT;};//这个可行吗?

这个是不行的,因为如果这样嵌套自身的话,会无限套娃,那么sizeof(struct node)的结果就无法计算了
正确如下:

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

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

//1)定义struct Point{   int x;   int y;}p1;     //声明类型的同时定义变量struct Point P2 //后面再用该类型定义变量//2)初始化:定义变量同时赋初值struct Point p3={1,3};//定义变量的同时赋初值struct Stu  //声明一个学生类型{   char name[15]; //存放姓名信息   int age;//存放年龄信息}s;    //声明同时定义变量s={"zhangsan",15};  //赋初值//3)嵌套初始化struct Node{   int a;   struct Point p;   //嵌套前面的结构体类型   struct Node*NEXT; //引用自身 }n1={5,{2,4},NULL};  //结构体嵌套初始化struct Node n2={2{3,0}NULL}//结构体嵌套初始化

1.4结构体大小计算(内存对齐知识)

这块儿知识十分重要噢
我们先来看下面这一段代码

struct Stu{  char name[15];  int age;};printf("%d\n",sizeof(struct Stu));

大家觉得这个代码结果会是什么呢?
实际结果如下:
结构体,枚举,联合果宝三剑客详解
有同学或许会纳闷,name数组占15个字节空间,age整型占4字节空间,15+4不是19个字节吗?

这就涉及到结构体内存对齐的规则了

1.结构体第一个成员,存放在结构体变量初始0偏移的位置

2.从第二个成员开始,存放的位置是其对齐数的整数倍处(对齐数为:最大对齐数和当前成员变量大小之间的较小值,其中不同编译器默认对齐数不同,vs默认为8),如果成员是数组,则看元素类型大小

3.结构体的总大小应为最大对齐数的整数倍(最大对齐数:所有成员变量对齐数中最大的)

4.如果嵌套了结构体,对于该结构体成员来说应对齐到自身内部成员中的最大对齐数的整数倍,此时结构体的大小也为最大对齐数(嵌套结构体内部成员的对齐数也要参与比较)的整数倍

看到这里很多同学可能会比较疑惑,这种内存对齐的操作不是会浪费内存空间?降低内存利用率吗?为什么要有这样的规定?

查阅了大部分资料,总结为以下两点:

  1. 平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特 定类型的数据,否则抛出硬件异常。
  2. 性能原因: 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访 问。(因为在32位的系统中,内存每次只能读取4个字节,而如果内存数据不是对齐放着的话,很多时候需要我们对内存进行多次访问才能读完所需的数据)

总结:

其实这种内存对齐的操作,本质上就是以空间换取时间

所以如果我们要使结构体既满足结构对齐,又不那么浪费空间。就要在以后设计的时候,尽量让所占用字节小的成员变量集中在一起。

1.4.1修改默认对齐数

这就要用到预处理指令#pragma pack()
具体如下:

#pragma pack(1)    //将默认对齐数修改为1struct S1{   char c1;   int age;};#pragma pack()     //恢复默认对齐数

注意在修改时最好是2的倍数,因为内存一次是读取4个字节,要方便读取

1.5结构体传参

这里有两种传参的方式,
一是传结构体变量自身过去
二是结构体变量的地址过去

如下

struct Stu {    char name[15];    int age;}s;void Print1(struct Stu a)   //结构体传参{   printf("%d\n",a.age);}void Print2(struct Stu *p)  //结构体地址传参{   printf("%d\n",p->age);}Print1(s);   Print2(&s);

两种方法都是可行的,但是哪个函数的实现好些呢?

答案是:Print2
因为函数传参时,参数是需要压栈的。如果传递一个结构体对象时,结构体过大,参数压栈开销就会比较大,所以会使性能下降。而传址调用,地址大小是固定的只有4/8字节大小,(32位系统地址大小都是4字节,64位为8)压栈开销就很小。

所以结构体传参时,要传地址

1.6结构体实现位段

1.6.1什么是位段

位段的声明和结构是类似的,有两个不同:

1.位段的成员必须是 int、unsigned int 、signed int或者char(属于整型家族)类型
2.位段的成员名后边有一个冒号和一个数字。

如下:

struct s{  int a: 2;char b: 1;  unsigned int c:4;};//A就是一个位段类型

1.6.2位段的内存分配

1.位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
2.位段冒号后面的数字表示分配给这个变量几个比特的空间。
3.位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
4.在开辟的空间分配完当前变量还有剩余且满足下一个成员变量所需时,会使用剩余空间;但是当剩余内存不满足下一次需要时,是否仍会使用剩余变量的行为是未定义的(在不同的系统下可能会使用也可能不会,这也就涉及了下面要讲的跨平台性的问题)

1.6.3位段的跨平台问题

  1. int 位段被当成有符号数还是无符号数是不确定的。
  2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机 器会出问题。)
  3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
  4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。

总结:

跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。

二、认贼作父—枚举

枚举就是把可能的值一一列举
比如我们现实生活中:

一周的星期一到星期日是有限的7天,可以一一列举。
性别有:男、女、保密,也可以一一列举。
月份有12个月,也可以一一列举

这里就可以使用枚举了。

2.1枚举类型的定义

enum Day //星期{     Mon,     Tues,     Wed,     Thur,     Fri,     Sat,     Sun};enum Sex //性别{ MALE, FEMALE, SECRET};

enum Day、enum Sex就是枚举类型
{}中的内容是枚举类型的可能取值,也叫枚举常量 。

这些可能取值都是有值的,默认从0开始,一次递增1,当然在定义的时候也可以赋初值。

enum Color{  BLUE,  RED,  YELLOW=24//赋初值(从赋初值的位置往下值递增1,前面的值照常从0开始递增1)  GREEN};//BLUE对应值为0//RED对应值为1//YELLOW对应值为24//GREEN对应值为25

2.2枚举定义变量

只能拿枚举常量给枚举变量赋值,这样才不会产生类型差异

2.3枚举的优点

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

枚举的优点:

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

三、贼眉鼠眼—联合

联合也是一种特殊的自定义类型
这种类型的定义也包含一系列成员,不过特殊的是这些成员公用同一块内存空间(因此联合体也叫共用体)

3.1联合类型的定义

union Un  //联合类型的声明{  char a[5];  int b;};// 联合类型变量的定义union Un un;

3.2联合特点

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

union Un{  char a[5];  char b[2];};//下面这两个输出结果一样吗?printf("%p\n",a);printf("%p\n",b);

这两个输出结果应该是一样的
联合体变量中的成员char a[5] 和 char b[2]是用同一块空间的,所以它们的初始地址也是相等的

接着来观察下面这一串代码

union Un{  int a;  int b;};union Un un;//下面这两个输出结果是什么?un.a=2;un.b=3;printf("%s\n",un.a);printf("%s\n",un.b);

结果为3
还是因为a和b共用空间,所以当一个值被修改时,另一个值也可能会被修改

这也意味着当使用联合体时,一次只能使用一个变量

3.3联合大小的计算

1.联合的大小至少是最大成员的大小。
2.当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。(联想结构体内存对齐)

好了,今天的三兄弟也就给大家介绍到这里了,欢迎大家在评论区留言指正噢

China香烟网