【C/C++】纯C实现变长数组

【C/C++】纯C实现变长数组

编写一个动态数组库可以帮助我们深入理解数据结构和内存管理,特别是在没有 C++ STL 的情况下使用 C 语言创建动态数组。我们将逐步搭建这个动态数组库(称为 Vector),并逐行解释其实现方式,让读者理解每一行代码的作用和实现细节。

基础结构设计

在 C 语言中,我们常使用固定大小的数组,但动态数组则能灵活扩展。这里的目标是设计一个宏生成的动态数组结构体,确保我们能在编译时创建适用于各种数据类型的动态数组。

#define Vector(type)     \struct {             \size_t size;     \size_t capacity; \type *data;      \}

解释

  • Vector(type) 是一个宏,它使用传入的 type 数据类型定义了一个动态数组的结构体。
  • size_t size:用于跟踪数组中元素的数量。
  • size_t capacity:记录当前分配的内存空间允许存储的最大元素数。
  • type *data:存储实际数据的指针。

注意:我们没有使用命名空间封装,因为这是 C 语言而非 C++。这个宏将在使用时扩展为结构体定义,使用时需实例化变量并指定 type 类型。

初始化动态数组

为了使用动态数组,我们需要一个初始化宏,用于设置 sizecapacity 的初始值。

#define VecInit(vec)        \do {                    \(vec).size = 0;     \(vec).capacity = 0; \(vec).data = NULL;  \} while (0)

解释

  • VecInit 是初始化宏,用于将动态数组的 sizecapacity 设置为 0,并将 data 指针初始化为 NULL
  • 使用 do...while(0) 包裹是 C 中的一种安全宏写法,可以避免宏在使用过程中出现错误。

销毁动态数组

为了避免内存泄漏,在不再需要动态数组时,我们应当释放它所占用的内存。

#define VecDestroy(vec) free((vec).data)

解释

  • free((vec).data) 用于释放 data 指针所指向的内存。注意我们只释放 data 的内存,因为 sizecapacity 是栈上分配的,不需要显式释放。

访问动态数组的元素

创建一个简单的宏,用于直接访问数组的元素:

#define VecAt(vec, i) ((vec).data[(i)])

解释

  • VecAt 宏用于访问索引 i 处的元素,相当于数组的下标运算。
  • 需要注意访问的 i 索引应在 size 范围内,否则可能引发越界错误。

弹出元素

我们实现一个弹出数组最后一个元素的功能,并减少数组的 size

#define VecPop(vec) ((vec).data[--(vec).size])

解释

  • VecPopsize 减一,并返回该位置的元素。因为我们并未真正删除内存中的值,只是标记最后一个元素不可用。

获取数组的大小和容量

我们提供两个简单的宏来获取数组的大小和容量,便于在使用过程中查看动态数组的状态。

#define VecSize(vec) ((vec).size)
#define VecCapacity(vec) ((vec).capacity)

解释

  • VecSize 返回动态数组的当前元素个数,而 VecCapacity 则返回已分配的内存容量。

调整数组容量

动态数组的核心是可以在容量不够时自动扩展,我们通过 VecResize 宏来实现这一点。

#define VecResize(vec, new_size)                                                                    \do {                                                                                            \(vec).capacity = (new_size);                                                                \(vec).data = (typeof((vec).data))realloc((vec).data, sizeof(*(vec).data) * (vec).capacity); \} while (0)

解释

  • VecResizecapacity 设置为 new_size,并使用 realloc 重新分配 data 指针的内存空间。
  • (typeof((vec).data)) 是 GNU C 扩展 typeof 的用法,用于确保 realloc 返回的 void* 被转换为正确的指针类型。
  • realloc 将现有数据复制到新分配的内存空间中。如果 new_size 小于当前容量,则会截断数据。

复制动态数组

在某些情况下,我们可能需要将一个动态数组的内容复制到另一个数组中,这可以通过 VecCopy 宏来实现。

#define VecCopy(dest, src)                                                 \do {                                                                   \if ((dest).capacity < (src).size)                                  \VecResize(dest, (src).size);                                   \(dest).size = (src).size;                                          \memcpy((dest).data, (src).data, sizeof(*(src).data) * (src).size); \} while (0)

解释

  • VecCopy 首先检查 dest 的容量是否大于 src 的大小,如果不是则调整 dest 的大小。
  • 然后使用 memcpy 复制数据内容。

添加元素到数组

当数组满时自动扩展容量,将新元素添加到数组尾部。

#define VecPush(vec, value)                                                                             \do {                                                                                                \if ((vec).size == (vec).capacity) {                                                             \(vec).capacity = (vec).capacity ? (vec).capacity * 2 : 2;                                   \(vec).data = (typeof((vec).data))realloc((vec).data, sizeof(*(vec).data) * (vec).capacity); \}                                                                                               \(vec).data[(vec).size++] = (value);                                                             \} while (0)

解释

  • VecPush 首先检查是否已达当前容量限制,若已满则将容量扩大一倍(或最小值 2)。
  • realloc 调整 data 指针的内存空间,并将新元素 value 放入数组的尾部。

排序数组

我们可以使用 C 标准库中的 qsort 函数对数组进行排序。

#define VecSort(vec, cmp)                                            \do {                                                             \if ((vec).size > 1) {                                        \qsort((vec).data, (vec).size, sizeof(*(vec).data), cmp); \}                                                            \} while (0)

解释

  • VecSort 使用 qsort 对数组进行排序,需要传入比较函数 cmp
  • 这里的 qsortdata 中的元素按指定的比较规则排序。
  • cmp 是用户自定义的比较函数,用于指定排序方式。

非GCC实现

typeof 是 GCC 提供的扩展,不在 C 标准中,因此在其他编译器(如 MSVC、Clang 在标准模式下)中可能不可用。为了确保跨平台兼容性,typeof 可以用更通用的写法替代,比如通过传入数据类型参数来避免依赖非标准的特性。

例如,可以重构如下:

#define Vector(type)     \struct {             \size_t size;     \size_t capacity; \type *data;      \}#define VecResize(vec, type, new_size)                                               \do {                                                                             \(vec).capacity = (new_size);                                                 \(vec).data = (type *)realloc((vec).data, sizeof(type) * (vec).capacity);     \} while (0)#define VecPush(vec, type, value)                                                    \do {                                                                             \if ((vec).size == (vec).capacity) {                                          \(vec).capacity = (vec).capacity ? (vec).capacity * 2 : 2;                \(vec).data = (type *)realloc((vec).data, sizeof(type) * (vec).capacity); \}                                                                            \(vec).data[(vec).size++] = (value);                                          \} while (0)

在这里:

  • 我们在 VecResizeVecPush 宏中加入了 type 参数,避免使用 typeof 关键字。
  • 调用 VecPushVecResize 时,需要显式指定数据类型,如 VecPush(my_vector, int, 5);,从而确保在任何编译器上都能正确识别并处理指针类型转换。

这种写法稍显冗长,但可以更好地兼容不同的编译器。

总结

通过以上宏,我们实现了一个基础的 C 语言动态数组库,包括初始化、销毁、添加、访问、弹出、复制和排序等操作。这个库使用 C 的动态内存管理来支持数组自动扩展,适用于任意类型的数据。这种宏定义的写法避免了复杂的函数模板,便于理解和扩展。下面展示了完整代码:

#ifndef VECTOR_H
#define VECTOR_H#include <stdlib.h>  // 引入标准库,便于使用内存管理函数// 定义动态数组结构体
#define Vector(type)     \struct {             \size_t size;     \size_t capacity; \type *data;      \}// 初始化动态数组
#define VecInit(vec)        \do {                    \(vec).size = 0;     \(vec).capacity = 0; \(vec).data = NULL;  \} while (0)// 销毁动态数组,释放内存
#define VecDestroy(vec) free((vec).data)// 获取索引 i 处的元素
#define VecAt(vec, i) ((vec).data[(i)])// 弹出最后一个元素
#define VecPop(vec) ((vec).data[--(vec).size])// 获取当前元素个数
#define VecSize(vec) ((vec).size)// 获取当前容量
#define VecCapacity(vec) ((vec).capacity)// 调整动态数组容量
#define VecResize(vec, new_size)                                                                    \do {                                                                                            \(vec).capacity = (new_size);                                                                \(vec).data = (typeof((vec).data))realloc((vec).data, sizeof(*(vec).data) * (vec).capacity); \} while (0)// 复制另一个动态数组的内容
#define VecCopy(dest, src)                                                 \do {                                                                   \if ((dest).capacity < (src).size)                                  \VecResize(dest, (src).size);                                   \(dest).size = (src).size;                                          \memcpy((dest).data, (src).data, sizeof(*(src).data) * (src).size); \} while (0)// 向动态数组末尾添加元素,如果需要,自动扩展数组容量
#define VecPush(vec, value)                                                                             \do {                                                                                                \if ((vec).size == (vec).capacity) {                                                             \(vec).capacity = (vec).capacity ? (vec).capacity * 2 : 2;                                   \(vec).data = (typeof((vec).data))realloc((vec).data, sizeof(*(vec).data) * (vec).capacity); \}                                                                                               \(vec).data[(vec).size++] = (value);                                                             \} while (0)// 对动态数组进行排序
#define VecSort(vec, cmp)                                            \do {                                                             \if ((vec).size > 1) {                                        \qsort((vec).data, (vec).size, sizeof(*(vec).data), cmp); \}                                                            \} while (0)#endif  // VECTOR_H

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【C++系列】-----------内存管理

c内存管理&#xff08;涉及&#xff1a;数据在内存中的分布、new和delete使用、动态内存管理等&#xff09; 文章目录 c内存管理&#xff08;涉及&#xff1a;数据在内存中的分布、new和delete使用、动态内存管理等&#xff09;前言一、C/C内存分布二、C中动态内存管理2.1、 ne…

SpringBoot框架:作业管理系统构建之道

摘 要 使用旧方法对作业管理信息进行系统化管理已经不再让人们信赖了&#xff0c;把现在的网络信息技术运用在作业管理信息的管理上面可以解决许多信息管理上面的难题&#xff0c;比如处理数据时间很长&#xff0c;数据存在错误不能及时纠正等问题。 这次开发的作业管理系统有管…

Linux字体更新 使用中文字体

问题描述&#xff0c;处理之前&#xff0c;中文乱码 处理后的结果 压缩需要上传的字体&#xff1a; 上传到LInux的字体目录&#xff0c;上传后解压出来 刷新字体&#xff1a; fc-cache -fv 测试是否正常 fc-list | grep "FontName"如果还不行 可以在代码里面指定字…

【书生.浦语实战营】——入门岛

【书生.浦语实战营】——入门岛_第一关_Linux基础 任务分布1. 本地vscode远程连接并进行端口映射端口映射What——何为端口映射How——怎么进行端口映射 2. Linux基础命令touch &#xff1a;创建文件mkdir &#xff1a;创建目录cd:进入 退出 目录pwd :确定当前所在目录cat:可以…

VirtualBox 解决虚拟机Cable Unplugged 无法上网问题

问题描述 VirtualBox 中的虚拟机无法上网&#xff0c;在虚拟机中查看网络设置显示 Cable Unplugged。 解决方案 选择VirtualBox 上方任务栏的控制->设置->网络&#xff0c;勾选接入网线即可解决。

win10下MMSegmentation自定义数据集

下载1.2.1版本: Releases open-mmlab/mmsegmentation GitHub 安装环境 本地torch环境为1.9.1 pip install -U openmim mim install mmengine mim install "mmcv>=2.0.0" 报mmcv版本不匹配的问题,形如:MMCV==X.X.X is used but incompatible. Please inst…

CSS网格布局

前言 希望元素按照网格的方式进行布局&#xff0c;最简单的方式就是利用网格布局&#xff0c;如图所示&#xff1a; 网格布局 设置网格布局的核心属性&#xff1a; ① display: grid 设置容器为网格布局容器&#xff08;如果希望设置行内的网格容器&#xff0c;可以设置disp…

童年的玩具:燕麦时钟

也不知道是谁传下来的&#xff0c;燕麦时钟。 燕麦是野生的&#xff0c;通常在麦地里面的都被拔掉&#xff0c;但是土埂上面的还幸存下来。 这个燕麦成熟后&#xff0c;上面有个麦芒由直的变弯&#xff0c;越是90度&#xff0c;越成熟。 选一根90度的成熟麦芒。把下部插入一团…

如何在BSV区块链上实现可验证AI

​​发表时间&#xff1a;2024年10月2日 nChain的顶尖专家们已经找到并成功测试了一种方法&#xff1a;通过区块链技术来验证AI&#xff08;人工智能&#xff09;系统的输出结果。这种方法可以确保AI模型既按照规范运行&#xff0c;避免严重错误&#xff0c;遵守诸如公平、透明…

2024年还有多少人在使用PHP?

根据W3Techs的最新数据&#xff0c;2024年PHP仍然支持76.5%的网站&#xff0c;这一数字在一年内下降不到1%&#xff08;截至2023年为77.3%&#xff09;。 尽管这一数字有所下降&#xff0c;但这表明PHP仍然是Web开发中非常流行的语言。 根据JetBrains的调查&#xff0c;在过去…

在这里游玩和创造,见证实时互动和 AI 的融合爆发丨年末场 RTE Open Day@RTE2024 回顾

RTE2024 第十届实时互联网大会上周末在北京圆满结束了&#xff0c;不知道大家体验交流得如何&#xff1f;可能是因为本来入秋的北京悄然升温&#xff0c;又或者是那两天的观众都很热情&#xff0c;25-26 号的活动现场特别像是一场夏天的聚会。 RTE Open Day 马不停蹄来到了第五…

智能体联手微信,打造24小时在线的全能AI机器人,除了聊天,还能接商单

最近在我们的智能体学习群里&#xff0c;微信AI机器人成了小明星&#xff0c;它功能丰富&#xff0c;机智幽默&#xff0c;成为了大家的心头好&#x1f617; 比如&#xff0c;它会非常热情的欢迎新入群的小伙伴&#xff0c;并且能够很机智的将小伙伴的名字巧妙地融入到欢迎词中…

采购退料单集成方案:从旺店通到金蝶云的API实现

14-采购退料单集成方案&#xff1a;旺店通旗舰奇门数据集成到金蝶云星空 在企业的供应链管理中&#xff0c;采购退料单的高效处理至关重要。为了实现这一目标&#xff0c;我们采用了轻易云数据集成平台&#xff0c;将旺店通旗舰奇门的数据无缝对接到金蝶云星空。本次分享的案例…

MyBatis的高级映射及延迟加载

多对一&#xff1a; 多种方式&#xff0c;常见的包括三种&#xff1a; 第一种方式&#xff1a;一条SQL语句&#xff0c;级联属性映射。 第二种方式&#xff1a;一条SQL语句&#xff0c;association。 第三种方式&#xff1a;两条SQL语句&#xff0c;分步查询。&#xff08;这…

bugku中web题-source

Web安全解题基础三件套思路 信息收集与目录扫描 御剑扫描&#xff1a;探测网站目录结构&#xff0c;找潜在可访问路径&#xff0c;如管理员后台等目录。dirsearch扫描后台目录&#xff1a;配置参数后扫描&#xff0c;分析返回有意义状态码的目录&#xff0c;可能包含后台管理界…

Java中String的length与Oracle数据库中VARCHAR2实际存储长度不一致的问题

目录 一、根本原因 二、解决方案 一、根本原因 Oracle数据库新增数据的时候报如下错误&#xff1a; 先给大家看个小案例&#xff0c;这样更好去理解&#xff0c;下面是一段测试代码&#xff1a; 这里面我分别列举了三种字符串&#xff0c;中文&#xff0c;英文和数字以及两种…

shodan6-7---清风

shodan6-7 1.shodan网页版 以cve-2019-0708漏洞指纹特征为例 "\x03\x00\x00\x0b\x06\xd0\x00\x00\x124\x00"在这里插入图片描述 搜索命令参考 https://www.shodan.io/search/filters这个网页中有搜索关键词 对指定网址进行监控&#xff0c;这里可以对ip进行扫描&…

Spring5学习记录(四)声明式事务管理

Spring5学习记录&#xff08;四&#xff09;声明式事务管理 一、事务管理1、事务四个特性ACID2、事务的两种方式 二、基于注解实现声明式事务管理1、配置xml文件2、添加事务注解 Transactional 三、声明式事务管理的参数配置1、propagation&#xff1a;事务传播行为2、isolat…

Linux上python离线安装教程

一. 安装Python 1. 下载python离线包 安装包下载地址&#xff1a;https://www.python.org/downloads/source/ 我下载的是Python 3.10.14 下面是linux服务器上的部署过程 2. 系统更新 sudo yum update -y 3. 安装必要的依赖项 sudo yum groupinstall “Development Tools” -y…

快速入门CSS

欢迎关注个人主页&#xff1a;逸狼 创造不易&#xff0c;可以点点赞吗 如有错误&#xff0c;欢迎指出~ 目录 CSS css的三种引入方式 css书写规范 选择器分类 标签选择器 class选择器 id选择器 复合选择器 通配符选择器 color颜色设置 border边框设置 width/heigth 内/外边距 C…