详解C语言结构体,枚举,联合体的使用

目录
  • 一、匿名结构体
  • 二、结构体的自引用
    • 1、声明时不要自己引用自己
    • 2、结构体重命名时不能使用重命名
  • 三、结构体内存对齐规则
    • 1、结构体内存计算
    • 2、结构体嵌套
    • 3、通过调整结构体成员顺序,压缩内存
  • 四、存在内存对齐的原因
  • 五、修改默认对齐数
  • 六、结构体传参
  • 七、位段
    • 1、位段在内存中的存储
    • 2、位段的跨平台问题
  • 八、枚举
    • 1、枚举的定义
    • 2、枚举的优点
  • 九、联合体(共用体)
    • 1、联合体大小的计算
    • 2、使用联合体判断计算机的大小端字节序

一、匿名结构体

struct
{
    char name[20];
    int age;
}s1;

匿名结构体对象s1过了这一行即销毁。

二、结构体的自引用

1、声明时不要自己引用自己

struct Node
{
 int data;
 struct Node next;//错误的,严禁自己引用自己
};

struct Node
{
 int data;
 struct Node* next;//正确的引用方式
};

2、结构体重命名时不能使用重命名

typedef struct
{
 int data;
 Node* next;//错误的,不要再重命名中使用重命名
}Node;

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

博主在学数据结构的时候踩过这个坑,在结构体重命名的时候成员变量的类型就使用了重命名,导致整个程序不认识这个成员变量的类型(但是vs在typedef这里不报错,而是在每个使用这个类型的地方报错!!!)。后来把这个成员变量的类型修改为重命名之前的类型,整个程序就可以运行了。(如上图的正确写法)

三、结构体内存对齐规则

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

后续成员变量要放到各自的对齐数的倍数上。对齐数 = 编译器默认对齐数与该成员类型大小的较小值。(vs中默认对齐数是8,gcc没有默认对齐数)

结构体最终大小为最大对齐数的整数倍。

如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

1、结构体内存计算

struct S1
{
    char c1;
    int i;
    char c2;
};
int main()
{
    printf("%d\n", sizeof(struct S1));
    return 0;
}

char c1在结构体变量的零偏移量处分配内存

int i的对齐数为4,所以跳过3个字节,在4的整数倍地址处分配内存

char c2的对齐数为1,使用下一个字节空间即可

目前已使用9字节

由于该结构体中所有成员变量中最大的成员类型大小为4字节,所以最大内存对齐数为4字节,结构体总大小为最大对齐数的整数倍。所以该结构体内存为12字节。

可以使用宏offsetof来观察结构体成员在内存中的偏移量:

2、结构体嵌套

struct S3//16
{
 double d;
 char c;
 int i;
};
struct S4//32
{
 char c1;
 struct S3 s3;
 double d;
};

char c1在结构体变量的零偏移量处分配内存

struct S3 s3按照其最大内存对齐数(此处为8)进行对齐

double d按照其最大内存对齐数(8)进行对齐

S4的最大内存对齐数为8,所以结构体的最终大小为32

3、通过调整结构体成员顺序,压缩内存

通过上述例子可以发现,结构体成员之间有很大的空间浪费,哪怕是拥有相同结构体成员的两个结构体类型,其在内存中所占据的空间也不相同,所以为了空间的节省,在不影响数据结构的情况下,有目的的把字节占用小的成员变量放在一起,达到节省空间的目的。

四、存在内存对齐的原因

1. 平台原因(移植原因)

不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

2. 性能原因(空间换时间)

数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

五、修改默认对齐数

#pragma pack(2)//把默认对齐数改成2
struct S
{
    char c1;
    int i;
    short c2;
};
#pragma pack()//恢复默认对齐数为8
int main()
{
    printf("%d\n", sizeof(struct S));
    return 0;
}
#pragma pack(num)修改默认对齐数,该结构体的内存大小由12字节降低为8字节。

默认对齐数尽量为2的次方。

六、结构体传参

结构体传参要传地址。

传址调用优于传值调用的原因是地址占4/8个字节。

但是传值调用参数需要压栈,当结构体过大时,参数压栈的系统开销较大。

七、位段

位段是在结构体中实现的。

位段的成员可以是 int、unsigned int、signed int或者是char类型

位段的空间增长方式为每次增长4个字节(int)或1个字节(char)

位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。

1、位段在内存中的存储

1.1位段中char类型的存储方式(vs中舍弃剩余空间)

struct S//占用3个字节
{
    char _a : 3;
    char _b : 4;
    char _c : 5;
    char _d : 4;
};
int main()
{
    printf("%d\n", sizeof(struct S));
    struct S s= { 0 };
    s._a = 10;//1010,截断为010
    s._b = 12;//1100
    s._c = 3;//0011
    s._d = 4;//0100
    return 0;
}

通过调用内存发现,&s中存储的16进制数字为62 03 04,那么可以发现s在内存中的存储方式如下图:

在vs环境中,char成员变量在单个字节中是倒着存储的(有截断先发生截断),当该字节中剩余的比特位不足以存放下一个完整的成员变量时,会将剩余的比特位舍弃。

1.2位段中int类型的存储方式(vs中利用剩余空间)

struct A//占4个字节
{
    int a : 2;
    int b : 3;
    int c : 4;
};
int main()
{
    struct A s = { 0 };
    s.a = 12;//1100,截断为00
    s.b = 13;//1101,截断为101
    s.c = 14;//1110
    return 0;
}

通过调用内存发现,&s中存储的16进制数字为d4 01 00 00,那么可以发现s在内存中的存储方式如下图:

在vs环境中,int成员变量在单个字节中是倒着存储的(有截断先发生截断),当该字节中剩余的比特位不足以存放下一个完整的成员变量时,会将继续存储,存不下的二进制位将存放至下一个字节的右侧。

注意:位段冒号后面的数字只能小于等于类型大小(例如char a:9是错误的)

2、位段的跨平台问题

1.int 位段被当成有符号数还是无符号数是不确定的。

2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。

3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。

4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。

八、枚举

1、枚举的定义

enum color
{
    RED,//枚举常量
    BLUE,
    YELLOW
};

不赋值那么默认从0开始,后续枚举成员的值递增1

enum color
{
    RED=1,
    BLUE,
    YELLOW
};

只需要对第一个成员进行赋值,后续枚举成员的值递增1

在写枚举成员的时候建议全大写,博主在写通讯录枚举了exit,使用时vs提示该命名和exit函数冲突。

2、枚举的优点

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

枚举使用时有类型检查,#define定义的标识符没有

防止了命名污染(封装)

便于调试(#define定义宏在预处理时是直接替换)

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

九、联合体(共用体)

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

1、联合体大小的计算

#include <stdio.h>
union un
{
    char arr[5];
    int a;
}u;
int main()
{
    printf("%d", sizeof(u));//8
    return 0;
}

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

最大内存对齐数的整数倍要大于等于最大成员的大小。

(这里最大成员是arr,占5个字节,最大内存对齐数是4,所以需要为祖国联合体开辟8个字节空间)

2、使用联合体判断计算机的大小端字节序

#include <stdio.h>
union un
{
    int m;
    char n;
}u;
int check_sys()
{
    u.m = 1;
    return u.n;
}

int main()
{
    int a = check_sys();
    if (a == 1)
        printf("小端存储\n");
    else
        printf("大端存储\n");
    return 0;
}

以上就是详解C语言结构体,枚举,联合体的使用的详细内容,更多关于C语言 结构体 枚举 联合体的资料请关注我们其它相关文章!

(0)

相关推荐

  • 全面了解结构体、联合体和枚举类型

    一. 结构体: 1. 定义结构体类型: struct 结构体    {        任意类型 任意变量:        任意类型 任意变量:        -- }: 注意:这不是定义变量,而是自定义一种类型而已. 如 struct student    {     char name[10];//学生姓名             int height;//学生身高             bool sex;//学生性别 假设0表示女,1表示男.           }://此处分号不能少.

  • C语言自定义类型超详细梳理之结构体 枚举 联合体

    目录 一.什么是结构体 1.结构体实现 2.匿名结构体类型 3.结构体自引用 4.结构体的内存对齐 5.结构体位段  二.什么是枚举 1.枚举类型的定义 2.枚举的优点 三.联合(共用体) 1.什么是联合(共用体) 2.联合(共用体)的定义 3.联合(共用体)的初始化 总结 一.什么是结构体 结构是一些值的集合,这些值称为成员变量.结构的每个成员可以是不同类型的变量. //结构体声明 struct tag //struct:结构体关键字,tag:标签名,合起来是结构体类型(类型名) { memb

  • C语言自定义类型详解(结构体、枚举、联合体和位段)

    目录 前言 一.结构体 1.结构体类型的声明 2.结构体的自引用 3.结构体变量的定义和初始化 4.结构体内存对齐 5.结构体传参 二.位段 1.位段的定义 2.位段的内存分配 3.位段的应用 三.枚举 1.枚举类型的定义 2.枚举的优点 3.枚举的使用 四.联合体(共用体) 1.联合体的定义 2.联合体的特点 3.联合体的大小计算 总结 前言 一.结构体 1.结构体类型的声明 当我们想要描述一个复杂变量--学生,可以这样声明. ✒️代码展示: struct Stu { char name[20

  • C语言结构体,枚举,联合体详解

    目录 1.什么是结构体.枚举.联合体 2.定义结构体 2.1 包含结构体成员变量.variable 2.2 tag.结构体成员变量 2.3 用结构体声名变量 2.4 用typedef 创建新类型 2.5 两个结构体相互包含 2.6 结构体变量初始化 2.7 结构体指针 3.枚举 3.1 定义方式 3.2 为什么用枚举 3.3 枚举变量的定义 3.4 实例 3.5 枚举实际用途 4.联合体 4.1 与结构体区别 4.2 定义 总结 1.什么是结构体.枚举.联合体 结构体(struct)是由一系列具

  • 详解C语言结构体,枚举,联合体的使用

    目录 一.匿名结构体 二.结构体的自引用 1.声明时不要自己引用自己 2.结构体重命名时不能使用重命名 三.结构体内存对齐规则 1.结构体内存计算 2.结构体嵌套 3.通过调整结构体成员顺序,压缩内存 四.存在内存对齐的原因 五.修改默认对齐数 六.结构体传参 七.位段 1.位段在内存中的存储 2.位段的跨平台问题 八.枚举 1.枚举的定义 2.枚举的优点 九.联合体(共用体) 1.联合体大小的计算 2.使用联合体判断计算机的大小端字节序 一.匿名结构体 struct { char name[2

  • 详解C语言结构体中的char数组如何赋值

    目录 前景提示 一.char数组类型的处理 1.结构体初始化 2.结构体内数据赋值(简单法) 二.char数组指针类型的处理 1.结构体初始化 2.结构体内数据赋值 3.结构体内输出数据 三.全部代码 1.char数组 2.char数组指针 总结 前景提示 定义一个结构体,结构体中有两个变量,其中一个是char类型的数组,那么,怎么向这个数组中插入数据,打印数据呢? typedef struct SequenceList { // 数组的元素 char element[20]; // 数组的长度

  • 详解C语言结构体中的函数指针

    结构体是由一系列具有相同类型或不同类型的数据构成的数据集合.所以,标准C中的结构体是不允许包含成员函数的,当然C++中的结构体对此进行了扩展.那么,我们在C语言的结构体中,只能通过定义函数指针的方式,用函数指针指向相应函数,以此达到调用函数的目的. 函数指针 函数类型 (*指针变量名)(形参列表):第一个括号一定不能少. "函数类型"说明函数的返回类型,由于"()"的优先级高于"*",所以指针变量名外的括号必不可少.  注意指针函数与函数指针表示

  • 详解C语言结构体的定义和使用

    目录 1.1: 结构体用来干嘛? 1.2:结构体变量的基本定义格式 1.3:结构体变量的定义 1.4结构体变量的三种引用方法 2.结构体变量的使用(直接使用结构体变量) 2.1输出结果 3.使用结构体指针操作,返回总成绩最低的学生信息 3.1运行结果 总结: 1.1: 结构体用来干嘛? 生活中我们会遇到很多的表格,就比如你的学习成绩表,有姓名 ,学号,各科的成绩,总的成绩等等,这是一些不同的数据类型,我们要是在c语言中想同时使用这些不同的数据怎么办呢? 可以使用结构体变量,结构体变量在c语言中是

  • C语言超详细讲解结构体与联合体的使用

    目录 结构体 offsetof-宏 位段 枚举 联合体(共用体) 结构体 结构体内存对齐问题: 当我们在计算结构体的大小时,我们便需要清楚的知道结构体内存对齐是什么. 存在内存对齐的原因可细分为两个: 平台原因: 不是所有的硬件平台都能方位任意地址上的任意数据:某些硬件平台只能在某些地址处取某些特定类型的数据,否则会抛出硬件异常. 性能原因: 首先内存对齐可以提高程序的性能,当访问未对其的内存空间时,有时候处理器需要进行两次访问,而当访问对齐的内存时,只需要一次就够了.这同时也被叫做 用空间换取

  • C语言中结构体、联合体的成员内存对齐情况

    前言 最近项目进行中,遇到一个小问题,在数据协议传输过程中,我为了方便解析,就定义了一个结构体,在数据的指针传入函数的时候,我用定义好的结构体进行强制转化,没想到一直解析失败,调试很久,终于反应过来,在用结构体指针对数据强制转换时,定义结构体我没有注意到数据对齐,因为在底层实现中,我传入的数据buffer是排列整齐的,而强制转化的结构体格式中,我定义的时候没有使用__attribute__((__packed__))或者__packed强制数据对齐,导致结构体成员真实排列会按照成员中最大的变量的

  • 详解C语言内核中的链表与结构体

    Windows内核中是无法使用vector容器等数据结构的,当我们需要保存一个结构体数组时,就需要使用内核中提供的专用链表结构LIST_ENTRY通过一些列链表操作函数对结构体进行装入弹出等操作,如下代码是本人总结的内核中使用链表存储多个结构体的通用案例. 首先实现一个枚举用户进程功能,将枚举到的进程存储到链表结构体内. #include <ntifs.h> #include <windef.h> extern PVOID PsGetProcessPeb(_In_ PEPROCES

  • C语言结构体指针引用详解

    目录 指向结构体变量的指针 指向结构体数组的指针 结构体指针,可细分为指向结构体变量的指针和指向结构体数组的指针. 指向结构体变量的指针 前面我们通过"结构体变量名.成员名"的方式引用结构体变量中的成员,除了这种方法之外还可以使用指针. 前面讲过,&student1 表示结构体变量 student1 的首地址,即 student1 第一个项的地址.如果定义一个指针变量 p 指向这个地址的话,p 就可以指向结构体变量 student1 中的任意一个成员. 那么,这个指针变量定义成

随机推荐