【C语言】你不知道的知识小盲区——柔性数组

在这里插入图片描述

文章目录

  • 一、什么是柔性数组
  • 二、柔性数组的特点
  • 三、柔性数组的使用
  • 四、柔性数组的优势

一、什么是柔性数组

   也许你从来没有听说过柔性数组(flexible array)这个概念,但是它确实是存在的。在C99标准中,如果结构体的最后一个成员是数组,那么这个数组可以不指定大小,它的大小是未知的,被称为柔性数组
   例如:

struct Stu
{int i;int arr[0];
};

   这种写法有些编译器可能会报错,可以使用这种形式:

struct Stu
{int i;int arr[];
};

   这两种写法都可以创建柔性数组,具体看编译器的选择

二、柔性数组的特点

  1. 结构中的柔性数组成员前⾯必须⾄少⼀个其他成员,比如我们上面的例子中,在柔性数组前都建立了另一个整型成员在前面
  2. sizeof返回的这种结构大小不包括柔性数组的内存,只包含其它成员的大小,我们可以来试一下计算上面的结构体Stu:
    在这里插入图片描述
       可以看到,sizeof(struct Stu)只包含了结构体中的整型成员i的大小,并没有包含柔性数组进去
  3. 包含柔性数组成员的结构体使用动态内存开辟函数进行内存的动态分配,并且分配的内存应该⼤于结构的大小,以适应柔性数组的预期大小(如果还没有学习过动态内存分配可以参考文章:【C语言】动态内存管理及相关笔试题)

   我们在给柔性数组申请空间时,一般会结合柔性数组第2和第3条特点来进行,第2点说使用sizeof计算结构体大小时不会计算柔性数组的大小,而第3点则说开辟空间时,需要大于计算出来的结构体的大小,所以我们开辟空间时的大小可以写成:sizeof(struct Stu) + 柔性数组的空间,如下:

//给结构体改名:
typedef struct Stu Stu
//为带有柔性数组的结构体Stu开辟空间
Stu* p = (Stu*)malloc(sizeof(Stu) + 10 * sizeof(int));

   以上操作就是为带有柔性数组的结构体Stu开辟空间,给除了柔性数组外的其它成员开辟空间我们采用的就是sizeof(Stu),然后再加上柔性数组的大小,上述代码中就为柔性数组开辟了10个整型的空间

三、柔性数组的使用

   刚刚我们学会了给包含柔性数组的结构体初始化,接着我们就举一个例子,就是给柔性数组开辟指定的空间,然后使用它,如下结构体:

typedef struct Stu
{int i;int arr[]; 
}Stu;

   上面的代码中,我们创建了一个带有柔性数组的结构体,将其重命名为了Stu,由于我们要指定空间内容,所以我们可以把其中的i用来表示柔性数组arr的元素个数
   但是我们要注意一点,不能直接来创建空间,因为我们的成员i需要用户输入,但是最开始的时候结构体的空间都还没有申请,成员i自然也就没有自己的空间,也就不能给i输入值
   本质上就是i不能输入,不知道柔性数组arr的元素个数,我们给结构体申请空间时就不能正常给柔性数组开辟空间,那么该怎么办呢?
   第一步就是给结构体申请不包含柔性数组的空间,因为柔性数组的元素个数还不确定,这时我们就给i申请到空间了,这时就可以使用scanf为i输入值了,如下:

typedef struct Stu
{int i;int arr[]; 
}Stu;int main()
{Stu* p = (Stu*)malloc(sizeof(Stu));scanf("%d", &p->i);return 0;
}

   这时我们就知道了用户输入的i了,也就是知道数组元素的个数,那么我们就可以为柔性数组arr开辟空间了,由于结构体Stu的空间已经被开辟了,只是没有开辟柔性数组的空间
   所以我们就可以使用realloc对原空间进行增容,再次提醒一下,不能直接用原来的指针p来接收,因为一旦realloc开辟空间失败返回了空指针,那么我们就找不到原数据了
   所以我们这里重新创建一个变量tmp来接收,然后判断它是否为空,不为空再传给我们的p,如果为空那么就直接打印错误信息,然后返回,如下:

#include <stdio.h>
#include <stdlib.h>typedef struct Stu
{int i;int arr[];
}Stu;int main()
{Stu* p = (Stu*)malloc(sizeof(Stu));scanf("%d", &p->i);Stu* tmp = (Stu*)realloc(p, sizeof(Stu) + p->i * sizeof(int));if (tmp == NULL){perror("realloc");return 1;}p = tmp;return 0;
}

   现在我们已经为柔性数组开辟了空间,现在就该使用这段空间了,我们遍历这个柔性数组,然后给它填充值,当然,使用完后也一定要记得将空间手动释放,然后将其置为空指针,以免造成内存泄漏和出现野指针,如下:

#include <stdio.h>
#include <stdlib.h>typedef struct Stu
{int i;int arr[];
}Stu;int main()
{Stu* p = (Stu*)malloc(sizeof(Stu));scanf("%d", &p->i);Stu* tmp = (Stu*)realloc(p, sizeof(Stu) + p->i * sizeof(int));if (tmp == NULL){perror("realloc");return 1;}p = tmp;for (int j = 0; j < p->i; j++){p->arr[j] = j;printf("%d ", p->arr[j]);}free(p);p = NULL;return 0;
}

   我们来看看运行结果:
在这里插入图片描述
   可以看到我们已经实现了我们的需求,根据输入的值来确定柔性数组元素个数,从而创建了一个柔性数组,随后进行使用,这就是我们柔性数组的基本用法
   我们扩散一下思维,这里的柔性数组是不是很像我们学过的一个东西,每错,就是变长数组,根据输入的值来确定数组的元素个数,这不就是变长数组吗?我们也说过VS默认不支持变成数组,那么我们就可以使用柔性数组,虽然麻烦一点,但是至少可以用了
   当然还有另一个办法,就是给VS加上clang组件,然后在项目菜单,选择属性,在常规中将平台工作集改成clang,流程图如下:
在这里插入图片描述

四、柔性数组的优势

   实际上上面的那些关于柔性数组的操作,也可以不使用柔性数组完成,那么我们为什么还要引入柔性数组呢?我们接下来不使用柔性数组来完成以上操作来和柔性数组做一个对比就知道了
   具体是什么方法呢?实际上也不陌生,就是我们上一篇在动态内存管理中讲过的malloc模拟实现数组的功能的办法,使用一个整型指针来当作一个数组的首元素,然后给它开辟空间,把这段连续空间当作数组使用,首先创建如下结构体:

typedef struct Stu
{int i;int* arr;
}Stu;

   接着我们就使用动态内存管理的malloc来为结构体指针Stu开辟空间,接着还是使用scanf让用户输入一个i,代表数组元素个数,然后根据i的大小来为这个数组开辟相应的空间,如下:

	Stu* p = (Stu*)malloc(sizeof(Stu));scanf("%d", &p->i);int* tmp = (int*)malloc(p->i * sizeof(int));if (tmp == NULL){perror("malloc");return 1;}p->arr = tmp;

   现在我们给这个数组申请了空间,就可以使用了,然后对它进行释放,如下:

#include <stdio.h>
#include <stdlib.h>typedef struct Stu
{int i;int* arr;
}Stu;int main()
{Stu* p = (Stu*)malloc(sizeof(Stu));scanf("%d", &p->i);int* tmp = (int*)malloc(p->i * sizeof(int));if (tmp == NULL){perror("malloc");return 1;}p->arr = tmp;for (int j = 0; j < p->i; j++){p->arr[j] = j;printf("%d ", p->arr[j]);}free(p);p = NULL;return 0;
}

   那么这段代码有没有问题?其实是有问题的,因为我们这里使用了两个malloc,一个给了结构体开辟空间,另一个为结构体中的arr开辟了空间,而两次malloc开辟的空间不一定是连续的,所以我们释放时只释放了结构体的空间就不够
   我们还需要先释放开辟的数组的空间,把它置为空指针,再释放掉p,将p置为空指针,所以 最后真正完整的代码如下:

#include <stdio.h>
#include <stdlib.h>typedef struct Stu
{int i;int* arr;
}Stu;int main()
{Stu* p = (Stu*)malloc(sizeof(Stu));scanf("%d", &p->i);int* tmp = (int*)malloc(p->i * sizeof(int));if (tmp == NULL){perror("malloc");return 1;}p->arr = tmp;for (int j = 0; j < p->i; j++){p->arr[j] = j;printf("%d ", p->arr[j]);}free(p->arr);p->arr = NULL;free(p);p = NULL;return 0;
}

我们来看看运行结果:
在这里插入图片描述

   可以我们就使用这种方式就模拟了柔性数组的用法,随后我们可以对比一下它们,其中柔性数组有以下两个优势:

  1. 第⼀个好处是:使用柔性数组方便内存释放
       如果我们的代码是在⼀个给别人用的函数中,你里面做了⼆次内存分配,并把整个结构体返回给用户,用户调⽤free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事
       所以,如果我们把结构体的内存以及其成员要的内存⼀次性分配好了,并返回给用户⼀个结构体指针,用户做⼀次free就可以把所有的内存也给释放掉,否则用户可以忘记释放数组的空间,造成内存泄漏
  2. 第⼆个好处是:使用柔性数组有利于访问速度
       柔性数组的空间是连续的,而我们上面的那种方式空间就不是连续的,而连续的内存有益于提⾼访问速度,也有益于减少内存碎⽚(其实,我个⼈觉得也没多⾼了,反正你跑不了要⽤做偏移量的加法来寻址,但是至少也算是好处之一)

   今天分享的柔性数组就到这里了,友友们之前有没有听说过柔性数组这个知识点呢?欢迎在评论区说出你们的想法
   那么我们下次再见,bye~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/56277.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring Integration + MQTT

1. 简介 Spring Integration&#xff1a; Spring Integration是一个开源的Java库&#xff0c;用于构建基于消息的应用程序。它提供了一套丰富的组件和工具&#xff0c;使得开发者可以轻松地开发出可靠、灵活和可扩展的集成解决方案。以下是Spring Integration的一些主要用途&…

电话聊天狂人

给定大量手机用户通话记录&#xff0c;找出其中通话次数最多的聊天狂人。 输入格式: 输入首先给出正整数N&#xff08;≤105&#xff09;&#xff0c;为通话记录条数。随后N行&#xff0c;每行给出一条通话记录。简单起见&#xff0c;这里只列出拨出方和接收方的11位数字构成…

jenkins 插件Publish Over SSH

一、安装插件 二、配置sshserver http://192.168.31.156:8080/manage/configure 三、添加自由风格&#xff1a;PublishOverSSHDemo 我们将工作目录&#xff1a;/var/lib/jenkins/workspace/PublishOverSSHDemo下的图片m3.jpeg 同步到目标143服务器目录&#xff1a;/root/imag…

Python脚本爬取目标网站上的所有链接

一、爬取后txt文件保存 需要先pip install requests和BeautifulSoup库 import requests from bs4 import BeautifulSoup# 定义要爬取的新闻网站URL url https://www.chinadaily.com.cn/ # China Daily 网站# 发送请求获取页面内容 response requests.get(url)# 检查请求是否…

Jgit的使用

Jgit的使用 文章目录 Jgit的使用一&#xff0c;git操作的对应代码1.1 查看操作1.1.1 打开仓库1.1.3 获取状态信息 1.2 添加操作1.2.1 初始化本地仓库1.2.2 创建一个新文件并写入内容1.2.3 添加指定&#xff08;所有&#xff09;文件到暂存区1.2.4 提交操作1.2.5 连接并推送到远…

Android中的三种数据存储方式

目录 1.文件存储 1&#xff09;内部存储 1--MODE_PRIVATE: 2--MODE_APPEND: 3--MODE_WORLD_READABLE: 4--MODE_WORLD_WRITEABLE: 5--简单使用 3&#xff09;外部存储 4&#xff09;内部读取 4&#xff09;外部读取 2.SharePreferences存储 1&#xff09;基本概念 2&#xff09…

设计模式 - 行为模式

行为模式 观察者模式&#xff0c;策略模式&#xff0c;命令模式&#xff0c;中介者模式&#xff0c;备忘录模式&#xff0c;模板方法模式&#xff0c;迭代器模式&#xff0c;状态模式&#xff0c;责任链模式&#xff0c;解释器模式&#xff0c;访问者模式 保存/封装 行为/请求…

MySQL表连接查询算法

前言 MySQL属于关系型数据库&#xff0c;我们建的表大多也都存在业务上的关联关系&#xff0c;同时我们又不可能将所有的数据都冗余一份&#xff0c;这不符合数据库的设计范式。因此&#xff0c;当我们需要把多张表的数据融合在一起的时候&#xff0c;就需要使用到「多表连接查…

CSS伪元素详解

CSS伪元素详解 一、引言 在CSS中&#xff0c;伪元素是一个非常强大的工具&#xff0c;它允许我们为元素的特定部分添加样式&#xff0c;而无需修改HTML结构。这不仅提高了样式的灵活性&#xff0c;还有助于保持代码的整洁和可维护性。本文将深入探讨CSS伪元素的使用方法和一些…

详解tar.gz, tar.xz, tar, gz后缀文件的区别

详解tar.gz, tar.xz,tar, gz后缀文件的区别 tar.gz、tar.xz、tar 和 gz 是常见的文件压缩与归档格式&#xff0c;它们的区别主要在于文件的归档和压缩方式。 1. tar 文件 全称&#xff1a;Tape Archive扩展名&#xff1a;.tar说明&#xff1a;tar 文件本身并没有压缩&#x…

SQL分类中的DDL

DDL&#xff08;Data Definition Language):数据定义语言&#xff0c;用来定义数据库对象&#xff08;数据库&#xff0c;表&#xff0c;字段&#xff09;。 一、DDL语句操作数据库 1、查询所有数据库&#xff1a;show databases&#xff1b;&#xff08;一般用大写&#xff…

C语言动态内存开辟

文章目录 malloc函数free函数calloc函数realloc函数二维数组的动态内存开辟 malloc函数 malloc函数包含再#include<stdlib.h>头文件中 void* malloc (size_t size);这个函数向内存申请⼀块连续可⽤的空间&#xff0c;并返回指向这块空间的指针。 如果开辟成功&#xff…

OpenCV-人脸检测

文章目录 一、人脸检测流程二、关键方法三、代码示例四、注意事项 OpenCV是一个开源的计算机视觉和机器学习软件库&#xff0c;它提供了多种人脸检测方法&#xff0c;以下是对OpenCV人脸检测的详细介绍&#xff1a; 一、人脸检测流程 人脸检测是识别图像中人脸位置的过程&…

【Docker系列】Docker查看镜像架构

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

模态与非模态的对话框

本文学习自&#xff1a; 《Qt Creato快速入门》 #include "widget.h" #include <QApplication>int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); }1. #include "widget.h" #include "ui_w…

MySQL数据的导入

【图书推荐】《MySQL 9从入门到性能优化&#xff08;视频教学版&#xff09;》-CSDN博客 《MySQL 9从入门到性能优化&#xff08;视频教学版&#xff09;&#xff08;数据库技术丛书&#xff09;》(王英英)【摘要 书评 试读】- 京东图书 (jd.com) MySQL9数据库技术_夏天又到了…

小白也能学会的预测新模型!ReliefF特征选择+XGBoost回归!

小白也能学会的预测新模型&#xff01;ReliefF特征选择XGBoost回归&#xff01; 目录 小白也能学会的预测新模型&#xff01;ReliefF特征选择XGBoost回归&#xff01;预测效果基本介绍程序设计参考资料 预测效果 基本介绍 Matlab实现ReliefF-XGBoost多变量回归预测 1.excel数据…

linux应用

检查Python程序未运行则重新运行 entity_program定时杀掉进程重新运行 match_program定时检查是否运行&#xff0c;未运行则启动 (注意echo时间时&#xff0c;date和中间要有空格) #!/bin/bash# 检测的Python程序名称 entity_program"entity.py" match_program"…

算法收敛的一些证明方法与案例

证明一个算法收敛通常涉及多个角度&#xff0c;以下是一些常用的方法和示例&#xff1a; 一、方法 1. 数学归纳法 通过数学归纳法证明算法在每一步的输出结果都在收敛范围内。 示例&#xff1a;考虑一个递归算法&#xff0c;假设我们要证明它在每一步中输出的值逐渐接近目标…

有问必答!zabbix“专家坐诊”第259期问答

问题一 Q&#xff1a;现在监控项4万多&#xff0c;调整到多少比较合理 zabbix7.03&#xff1f; A&#xff1a;慢慢往上调&#xff0c;没有标准。 问题二 Q&#xff1a;想问下大家&#xff0c;zabbix的监控项怎么不能自动清除&#xff0c;比如说这次监控是A监控项&#xff0c;下…