【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~

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【jQuery】 jQuery基础及选择器介绍(基本选择器 层次选择器 属性选择器 过滤选择器)

文章目录 jQuery基础1. 优势2. 版本3. 基本语法4. 选择器基本选择器层次选择器属性选择器过滤选择器基本过滤选择器可见性过滤选择器 注意事项 jQuery基础 jQuery 是一个功能强大且易于使用的 JavaScript 库&#xff0c;它极大地简化了前端开发的工作。无论是 DOM 操作、事件处…

健康推荐系统:SpringBoot技术实现

3系统分析 3.1可行性分析 通过对本基于智能推荐的卫生健康系统实行的目的初步调查和分析&#xff0c;提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本基于智能推荐的卫生健康系统采用SSM框架&#…

在Java程序中监听mysql的binlog

文章目录 1、背景2、mysql-binlog-connector-java简介3、准备工作1、验证数据库是否开启binlog2、开启数据库的binlog3、创建具有REPLICATION SLAVE权限的用户4、事件类型 eventType 解释1、TABLE_MAP 的注意事项2、获取操作的列名 5、监听binlog的position1、从最新的binlog位…

HCIP-HarmonyOS Application Developer 习题(十)

1、HarmonyOS设备A上的应用通过调用分布式任务调度的能力continuesbility&#xff0c;向设备B的应用发起跨端迁移&#xff0c;此过程属于跨端迁移中的哪个流程? A、流转准备 B、流转进行 C、流转结束 D、流转完成 答案&#xff1a;D 分析&#xff1a; 2、为了帮助用户通过全局…

软件测试工程师面试整理 —— 操作系统与网络基础!

在软件测试中&#xff0c;了解操作系统和网络基础知识对于有效地进行测试工作至关重要。无论是在配置测试环境、调试网络问题&#xff0c;还是在进行性能测试和安全测试时&#xff0c;这些知识都是不可或缺的。 1. 操作系统基础 操作系统&#xff08;Operating System, OS&am…

Node.js管理工具NVM

nvm&#xff08;Node Version Manager&#xff09;是一个用于管理多个 Node.js 版本的工具。以下是 nvm 的使用方法和一些常见命令&#xff1a; 一、安装 nvm 下载 nvm&#xff1a; 地址&#xff1a;https://github.com/coreybutler/nvm-windows/releases访问 nvm 的 GitHub 仓…

Python | Leetcode Python题解之第474题一和零

题目&#xff1a; 题解&#xff1a; class Solution:def findMaxForm(self, strs: List[str], m: int, n: int) -> int:count10 []for s in strs:count10.append([0,0])for c in s:if c 0: count10[-1][0]1else: count10[-1][1]1dp [[0]*(n1) for _ in range(m1)]for i …

贪吃蛇游戏(代码篇)

我们并不是为了满足别人的期待而活着。 前言 这是我自己做的第五个小项目---贪吃蛇游戏&#xff08;代码篇&#xff09;。后期我会继续制作其他小项目并开源至博客上。 上一小项目是贪吃蛇游戏&#xff08;必备知识篇&#xff09;&#xff0c;没看过的同学可以去看看&#xf…

文件完整性监控:如何提高企业的数据安全性

企业网络庞大而复杂&#xff0c;需要处理大量关键业务数据&#xff0c;这些敏感文件在企业网络中不断传输&#xff0c;并由多个用户和实体存储、共享和访问。FIM 工具或具有 FIM 功能的 SIEM 解决方案使企业能够跟踪未经授权的文件更改、对敏感信息的恶意访问、数据篡改尝试和内…

ubuntu下实时查看CPU,内存(Mem)和GPU的利用率

一、实时查看CPU和内存&#xff08;Mem&#xff09;利用率 htop官网&#xff1a;htop - an interactive process viewer sudo apt-get install htop htop ①. 顶部状态栏&#xff08;System Metrics Overview&#xff09; 这个区域显示系统的全局资源使用情况&#xff0c;包括…

JavaSE——集合12:Map接口实现类—Properties

目录 一、Properties基本介绍 二、Properties常用方法 一、Properties基本介绍 Properties类继承自HashTable类并且实现了Map接口&#xff0c;也是使用一种键值对的形式&#xff0c;来保存数据。Properties的使用特点和HashTable类似Properties还可以用于从xxx.properties文件…

【实践】快速学会使用阿里云消息队列RabbitMQ版

文章目录 1、场景简介2、实验架构和流程2.1、实验架构2.2、实验流程 3、创建实验资源4、创建阿里云AccessKey5、创建静态用户名密码6、创建Vhost、Exchange、Queue并绑定关系6.1、Vhost 的作用6.2、创建Vhost6.3、Exchange 的作用6.4、创建Exchange6.5、Queue 的作用6.6、创建Q…

基于Python flask的豆瓣电影可视化系统,豆瓣电影爬虫系统

博主介绍&#xff1a;✌Java徐师兄、7年大厂程序员经历。全网粉丝13w、csdn博客专家、掘金/华为云等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;&#x1f3fb; 不…

Mysql(七) --- 索引

文章目录 前言1.简介1.1.索引是什么&#xff1f;1.2.为什么使用索引? 2.索引应该使用什么数据结构&#xff1f;2.1.Hash2.2.二叉搜索树2.3.N叉树2.4.B树2.4.1. 简介2.4.2. B树的特点2.4.3. B树和B树的对比 3.Mysql中的页3.1.为什么要使用页3.2.页文件头和页文件尾3.3.页主体3.…

【Linux】解锁线程基本概念和线程控制,步入多线程学习的大门

目录 1、线程初识 1.1线程的概念 1.2.关于线程和进程的进一步理解 1.3.线程的设计理念 1.4.进程vs线程&#xff08;图解&#xff09; 1.5地址空间的第四谈 2.线程的控制&#xff1a; 2.1.关于线程控制的前置知识 2.2创建线程的系统调用&#xff1a; 这个几号手册具体…

JavaScript | 定时器(setInterval和clearInterval)的使用

效果图如下&#xff1a; 当用户第一次看到这个页面时&#xff0c;按钮是不可点击的&#xff0c;并显示一个5秒的倒计时。倒计时结束后&#xff0c;按钮变为可点击状态&#xff0c;并显示“同意协议”。这样做的目的是确保用户有足够的时间阅读用户协议。 <!DOCTYPE html>…

机器学习:知识蒸馏(Knowledge Distillation,KD)

知识蒸馏&#xff08;Knowledge Distillation&#xff0c;KD&#xff09;作为深度学习领域中的一种模型压缩技术&#xff0c;主要用于将大规模、复杂的神经网络模型&#xff08;即教师模型&#xff09;压缩为较小的、轻量化的模型&#xff08;即学生模型&#xff09;。在实际应…

Vue(3) 组件

文章目录 对组件的理解单文件组件非单文件组件基本使用几个注意点组件的嵌套VueComponent构造函数一个重要的内置关系 组件的自定义事件全局事件总线安装全局事件总线使用事件总线解绑事件消息订阅与发布简介使用步骤范例 $nextTick插槽1.默认插槽2.具名插槽作用域插槽 对组件的…

[linux 驱动]网络设备驱动详解

目录 1 描述 2 结构体 2.1 net_device 2.2 sk_buff 2.3 net_device_ops 2.4 ethtool_ops 3 相关函数 3.1 网络协议接口层 3.1.1 dev_queue_xmit 3.1.2 netif_rx 3.1.3 alloc_skb 3.1.4 kfree_skb 3.1.5 skb_put 3.1.6 skb_push 3.1.7 skb_reserve 3.2 网络设备驱…