C语言-第六章-加餐:其他自定义类型

传送门:C语言-第六章:结构体

目录

第一节:位段

1-1.位段是什么

1-2.位段的大小

第二节:联合体

2-1.联合体是什么

2-2联合体的大小

第三节:枚举类型

3-1.枚举是什么

第四节:结构体中的柔性数组

4-1.柔性数组是什么

4-2.为什么要有柔性数组

[4-3.malloc 函数](#4-3.malloc 函数)

第五节:利用成员计算结构体的地址

下期预告:


第一节:位段

1-1.位段是什么

位段是一种特殊的结构体,合理使用可以节约内存空间,它的声明与定义和结构体有两个不同:

(1)位段的成员必须是整型家族:int、unsigned int、short、long、long long、char

(2)位段的成员名后有冒号和数字,表示这个成员占用的内存大小(比特)

cpp 复制代码
// 位段
struct A
{
    int a : 2;
    int b : 1;
};
struct A z;

上述代码表示成员 a 占用2个比特位,成员 b 占用1个比特位,那么 a 只有00、01、10、11,四种二进制,分别表示十进制的0、1、2、3;同理,b 只能表示0、1。

1-2.位段的大小

按照之前的规则这个结构体的大小应该是8字节,但是它实际上的大小是4字节:

cpp 复制代码
#include <stdio.h>

// 位段
struct A
{
    int a : 2;
    int b : 1;
};

int main()
{
    printf("它的大小是:%d\n",sizeof A);
    return 0;
}

这是因为它的两个成员只占用了1个字节(可能是2个字节,具体看平台差异)的空间,但是int类型的对齐数是4,所以这个结构体的最大对齐数是4,结构体的大小至少是最大对齐数的整数倍,即4字节。

因此在表示的数字不太大时,合理使用位段可以节约空间。

第二节:联合体

2-1.联合体是什么

联合体也可以节约空间,它的所有成员共用内存空间,同一时刻只能使用其中的一个成员。

它的声明和定义如下:

cpp 复制代码
// 联合体
union U
{
    char c;
    int a;
};
union U u;

2-2联合体的大小

联合体的大小有两个规则:

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

(2)当最大成员的大小不是最大对齐数的整数倍时(比如数组),联合体的大小也至少是最大对齐数的整数倍

第一条规则如下:

cpp 复制代码
#include <stdio.h>

// 联合体
union U
{
    char c; // 4字节
    int a;  // 1字节
};
union U u;

int main()
{
    printf("联合体的大小:%d\n", sizeof U);
    return 0;
}

第二条规则如下:

cpp 复制代码
#include <stdio.h>

// 联合体
union U
{
    char c[5]; // 大小为5,但是对齐数是1
    int a;     // 对齐数是4
};
union U u;

int main()
{
    printf("联合体的大小:%d\n", sizeof U);
    return 0;
}

根据规则1,联合体的大小比最大成员 c 的大小5还要大;根据规则2,联合体的大小也至少是最大对齐数的整数倍,最大对齐数是4,所以它的大小是4X2等于8。

第三节:枚举类型

3-1.枚举是什么

枚举是一种数据类型,它用于定义一组命名的整型常量。它使代码更具可读性和可维护性。

它的声明与定义如下:

cpp 复制代码
// 枚举
enum color
{
    RED,   
    GREEN,
    BIUE
};
color e;

其中的变量 e 的类型是 color,它可以用整型或者 enum color 中的 RED, GREEN, BIUE赋值:

cpp 复制代码
#include <stdio.h>

// 枚举
enum color
{
    RED,   
    GREEN,
    BIUE
};

int main()
{
    color e1 = RED;
    color e2 = GREEN;
    color e3 = BIUE;
    printf("%d\n", e1);
    printf("%d\n", e2);
    printf("%d\n", e3);
    return 0;
}

可见,枚举类型中的整型常量默认从0开始赋值,我们也可以在任意位置改变它的数值:

cpp 复制代码
#include <stdio.h>

// 枚举
enum color
{
    RED=1,  // 赋值
    GREEN,
    BIUE
};

int main()
{
    color e1 = RED;
    color e2 = GREEN;
    color e3 = BIUE;
    printf("%d\n", e1);
    printf("%d\n", e2);
    printf("%d\n", e3);
    return 0;
}

总之,如果不改变中间的整型常量的大小,枚举类型的整型常量就是依次递增的。

第四节:结构体中的柔性数组

4-1.柔性数组是什么

柔性数组是定义在结构体中的数组,它在定义时不能写出数组大小,否则就是普通数组而不是柔性数组了:

cpp 复制代码
// 含有柔性数组的结构体
struct A
{
    int i;
    int arr[]; // []中不能写入整数
};

在计算含有柔性数组的结构体的大小时,不考虑柔性数组,上述结构体的大小仍然是4字节:

cpp 复制代码
#include <stdio.h>

// 含有柔性数组的结构体
struct A
{
    int i;
    int arr[]; // []中不能写入整数
};
struct A a;
int main()
{
    printf("含有柔性数组的结构体大小:%d",sizeof A);
    return 0;
}

4-2.为什么要有柔性数组

在C语言中,数组在定义时它的大小就必须确定了,即它的 [ ] 中只能写常量或者常量表达式:

cpp 复制代码
    int arr1[b];     // 错误写法,b是个变量
    int arr1[2];     // 正确写法,2是个常量
    int arr1[2*3];   // 正确写法,2*3是个常量表达式

这就意味着数组的大小不能改变,而柔性数组就可以改变大小,柔性数组利用 malloc 函数来改变自身的大小。

4-3.malloc 函数

之前我们说过,局部变量的空间在内存的栈区,全局变量的空间在内存的静态区,这个两个区由系统自己回收和管理空间;而堆区空间可以由用户自己申请。在程序运行过程中,申请多少堆区空间,什么时候回收,由用户自己决定。

malloc 函数的作用是在内存的堆区开辟一块大小确定空间,然后返回这块空间的首地址,供用户使用,它开辟柔性数组的使用方法如下:

cpp 复制代码
struct A* ptr = (struct A*)malloc(sizeof A+20);

上述代码表示在堆区开辟一个 结构体大小+20 字节 的空间,其中的20就是你要开辟的柔性数组的大小。

它的示意图如下:

然后我们就可以用 ptr 来访问这个柔性数组了:

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>

// 含有柔性数组的结构体
struct A
{
    int i;
    int arr[]; // []中不能写入整数
};
int main()
{
    struct A* ptr = (struct A*)malloc(sizeof A+20);
    (ptr->arr)[0] = 0;
    (ptr->arr)[1] = 1;
    (ptr->arr)[2] = 2;
    (ptr->arr)[3] = 3;
    printf("%d ", (ptr->arr)[0]);
    printf("%d ", (ptr->arr)[1]);
    printf("%d ", (ptr->arr)[2]);
    printf("%d\n",(ptr->arr)[3]);
    return 0;
}

如果我们要再次改变柔性数组的大小,可以使用 realloc 函数(因为 malloc 不能保留原来空间的数据):

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>

// 含有柔性数组的结构体
struct A
{
    int i;
    int arr[]; // []中不能写入整数
};
int main()
{
    struct A* ptr = (struct A*)malloc(sizeof A+20);
    struct A* ptr1 = (struct A*)realloc(ptr,sizeof A + 24); // 再次申请

    (ptr1->arr)[0] = 0;
    (ptr1->arr)[1] = 1;
    (ptr1->arr)[2] = 2;
    (ptr1->arr)[3] = 3;
    (ptr1->arr)[4] = 4;
    printf("%d ", (ptr1->arr)[0]);
    printf("%d ", (ptr1->arr)[1]);
    printf("%d ", (ptr1->arr)[2]);
    printf("%d ", (ptr1->arr)[3]);
    printf("%d\n",(ptr1->arr)[4]);
    return 0;
}

realloc 的第一个参数需要原来空间的指针,因为它需要拷贝原来空间中的数据到新空间中。这种灵活的内存分配方式叫做 动态内存分配。

关于更多动态内存分配相关的知识将在第七章讲述。

第五节:利用成员计算结构体的地址

计算结构体的地址需要以下1个已知条件:

(1)已知某个成员变量的地址

它的计算方式如下:

看上图,我们要得到结构体的地址,就需要得到红线的长度(也就是相对于地址0的差值),我们假设有一个相同类型的结构体变量对齐到地址为0的地方 ,那么黄线长度和红线长度是一样的。

我们用 已知的成员地址-虚拟结构体的成员地址 就可以得到结构体的地址,虚拟结构体的成员地址可以用 &((结构体类型*)0->成员变量名) 得到:

cpp 复制代码
#include <stdio.h>

struct A
{
    int i;
    int b;
};

int main()
{
    struct A a;
    printf("      原来的结构体地址:%p\n", &a);
    printf("计算得到的的结构体地址:%p\n", (long long) & (a.b) - (long long) & ((struct A*)0)->b);
    return 0;
}

用 (long long)强转是因为 指针-指针 的操作得到的不是地址的差值,而是获得偏移量。

用成员得到结构体地址在操作系统的内核数据结构中非常有用。

下期预告:

下一次我们将进入动态内存分配、字符和字符串函数的学习,具体要认识以下函数:

memcpy 内存拷贝函数

memmove 内存拷贝函数

memset 内存设置函数

memcmp 内存比较函数

strtok 字符串切割函数

strerror 错误码翻译函数

malloc calloc realloc 堆区申请函数

free 堆区释放函数

传送门:C语言-第七章:字符和字符串函数、动态内存分配

相关推荐
.普通人43 分钟前
c语言--力扣简单题目(回文链表)讲解
c语言·leetcode·链表
星迹日44 分钟前
C语言:联合和枚举
c语言·开发语言·经验分享·笔记
Huazzi.1 小时前
算法题解:斐波那契数列(C语言)
c语言·开发语言·算法
DdddJMs__1351 小时前
C语言 | Leetcode C语言题解之题409题最长回文串
c语言·leetcode·题解
元气代码鼠1 小时前
C语言程序设计(进阶)
c语言·开发语言·算法
做完作业了2 小时前
【C语言】预处理详解
c语言·预处理
aqymnkstkw3 小时前
2024年【电气试验】考试题库及电气试验模拟试题
大数据·c语言·人工智能·嵌入式硬件·安全
Happy鱿鱼6 小时前
C语言-数据结构 有向图拓扑排序TopologicalSort(邻接表存储)
c语言·开发语言·数据结构
KBDYD10106 小时前
C语言--结构体变量和数组的定义、初始化、赋值
c语言·开发语言·数据结构·算法
LWDlwd05256 小时前
shell指令及笔试题
c语言