首页 > 其他分享 >C语言-第六章-加餐:其他自定义类型

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

时间:2024-09-06 18:22:13浏览次数:10  
标签:arr struct 自定义 int C语言 加餐 数组 printf 柔性

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

目录

第一节:位段

        1-1.位段是什么

        1-2.位段的大小

第二节:联合体

        2-1.联合体是什么

        2-2联合体的大小

第三节:枚举类型

        3-1.枚举是什么

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

        4-1.柔性数组是什么

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

        4-3.malloc 函数

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

下期预告:


第一节:位段

        1-1.位段是什么

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

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

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

// 位段
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字节:

#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.联合体是什么

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

它的声明和定义如下:

// 联合体
union U
{
    char c;
    int a;
};
union U u;

        2-2联合体的大小

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

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

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

        第一条规则如下:

#include <stdio.h>

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

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

        第二条规则如下:

#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.枚举是什么

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

它的声明与定义如下:

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

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

#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开始赋值,我们也可以在任意位置改变它的数值:

#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.柔性数组是什么

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

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

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

#include <stdio.h>

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

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

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

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

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

        4-3.malloc 函数

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

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

struct A* ptr = (struct A*)malloc(sizeof A+20);

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

        它的示意图如下:

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

#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 不能保留原来空间的数据):

#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->成员变量名) 得到:

#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语言-第七章:字符和字符串函数、动态内存分配

        

        

标签:arr,struct,自定义,int,C语言,加餐,数组,printf,柔性
From: https://blog.csdn.net/2303_78095330/article/details/141826986

相关文章

  • 功能发布-自定义SQL查询
    引言本期主要为大家介绍ClkLog九月上线的新功能-自定义SQL查询。什么是自定义SQL查询?自定义SQL查询是指根据具体的应用场景和需求,由开发者或数据库管理员自行编写的SQL(StructuredQueryLanguage,结构化查询语言)语句。自定义SQL查询有什么作用?以精确的方式从数据库中检索数据。在Cl......
  • C语言面向对象
    我们在编写程序时,通常采用以下步骤:将问题的解法分解成若干步骤使用函数分别实现这些步骤依次调用这些函数这种编程风格的被称作面向过程。除了面向过程之外,还有一种被称作面向对象的编程风格被广泛使用。面向对象采用基于对象的概念建立模型,对现实世界进行模拟,从而完......
  • C语言学习——sprintf函数详细解释及其用法
    文章目录函数功能:把格式化的数据写入某个字符串参数说明及应用举例解释:连接字符串打印地址信息利用sprintf的返回值使用sprintf的常见问题函数功能:把格式化的数据写入某个字符串头文件:stdio.h函数原型:intsprintf(char*buffer,constchar*format,[arg......
  • 学习C语言结构体(结构体的前世今生)
    1、首先我将使用DevC++这个软件(其实随意一个C++软件都可以)来演示一下结构体的使用方法。这里已经写了一个最简单的HelloWorld!程序。2、对于C语言的数据来说最重要的就是两个功能,一个是定义数据,一个是引用数据。既然结构体也是数据类型,那么他就和其他的数据类型差不多。也分......
  • 20240906_142048 c语言 认识c语言
    C语言是一种广泛使用的编程语言,它以其高效、灵活和接近硬件的特性而闻名。对于零基础的学生来说,学习C语言是一个很好的起点,因为它不仅能帮助你理解计算机程序的基本结构和概念,还能为学习更高级的编程语言(如C++、Java、Python等)打下坚实的基础。下面我将简要介绍C语言的一些基本概念......
  • C语言之动态内存分配与释放
    C语言之动态内存分配与释放通用指针类型void通用类型指针具有以下特点:类型无关,赋值灵活:由于指针本质上是一个存储内存地址的变量,而内存地址是没有类型的,所以void指针可以存储任意类型数据的地址,指向任意类型对象。无论是整数、浮点数、字符或数组、结构体等类型都可以用void指......
  • vue自定义指令
    现象:可使用自定义指令v-per实现:index.js文件importperfrom"./per"importper2from"./per2"//批量注册指令(现在就一个permission)constdirectives={per,per2}//注册的一般写法,循环遍历directives,通过vue.directive注册exportdefault{inst......
  • 新手c语言讲解及题目分享(十九)--数据类型专项练习
    本文主要讲解c语言的基础部分,常见的c语言基础数据类型,这个也非常重要。参考书目和推荐学习书目:通过网盘分享的文件:C语言程序设计电子教材(1).pdf链接:https://pan.baidu.com/s/1JFqSaCKZ0A2Lr944e72NUA?pwd=p648提取码:p648目录前言一.常量与变量1.常量2.变量二.......
  • 新手c语言讲解及题目分享(十八)--基本输入输出函数专项练习
    本文主要讲解c语言的基础部分,基本的输入与输出,通过手动的输入从而得到自己想要的预期值。参考书目和推荐学习书目:通过网盘分享的文件:C语言程序设计电子教材(1).pdf链接:https://pan.baidu.com/s/1JFqSaCKZ0A2Lr944e72NUA?pwd=p648提取码:p648目录前言一.格式输出......
  • C语言 10 数组
    简单来说,数组就是存放数据的一个组,所有的数据都统一存放在这一个组中,一个数组可以同时存放多个数据。一维数组比如现在想保存12个月的天数,那么只需要创建一个int类型的数组就可以了,它可以保存很多个int类型的数据,这些保存在数组中的数据,称为元素://12个月的数据全部保存......