链表的替代品--Vector组件

news2024/11/23 15:32:06

概述

  • 在之前的一篇文章中,作者写了一个事件组件-- 超精简的订阅发布事件组件–SPEvent,这个组件是采用链表建立所有事件节点的关系的。
  • 链表的优缺点:
    1. 优点:①链表上的元素在空间存储上内存地址不连续;②在插入和删除操作时,只需要修改被删节点上一节点的链接地址,不需要移动元素;
    2. 缺点:①没有解决连续存储分配带来的表长难以确定的问题;②失去了顺序存储结构随机存取的特性;③不能通过数学表达式计算被查找元素的内存地址,每一次查找都是从头节点开始遍历,直到找到为止。
  • SPEvent实际不会存在删改的动作,显然链表的优点在这个组件中无法体现优势。而实际顺利存储更能满足SPEvent的业务及能力,那么有什么方式能做到这个操作了?答案肯定是有的,有一个好组件(Vector)正好可以解决掉这个问题。
  • Vector组件–向量;这个名称一点也不陌生,比如我们单片机开发中常常听到中断向量表,它是通过地址查找对应中断服务函数;而Vector组件也有点类似这个概念,它可以通过名称、类型查找对象。
  • Vector组件的优势可以应用像SPEvent这类组件中,如:SPEvent就可以通过Event类型去查找事件节点。那么Vector是怎么实现的??

Vector组件

Vector组件–它是类似于链表拥有的能力,是一种动态数组存储组件,Vector组件拥有的能力如下:

  1. 提供了顺序存储的能力,并且能够动态增大顺序存储空间;
  2. 提供了增加对象能力,查找对象能力。
  3. 提供获取顺序存储空间能力,获取对象个数能力。
  4. 采用KEY-VALUE的特性开查找对象。

Vector接口说明:

接口描述
Vector VECTOR_Make(VECTOR_Key key, VECTOR_Compare compare)创建Vector列表对象,用户根据业务注册VECTOR_Key方法和VECTOR_Compare方法
void VECTOR_Clear(Vector *vector)清空Vector列表对象,并释放存储数据空间
int16_t VECTOR_Add(Vector *vector, void *element)添加元素到Vector列表对象
int16_t VECTOR_Size(Vector *vector)获取Vector列表对象的元素个数
int16_t VECTOR_Num(Vector *vector)获取Vector列表对象的元素记录数目
void *VECTOR_At(Vector *vector, int16_t index)根据下标获取Vector列表对象的元素
void *VECTOR_Swap(Vector *vector, int16_t index, void *element)替换指定下标的Vector列表对象的元素
int16_t VECTOR_Find(Vector *vector, const void *element)通过元素从Vector列表对象中查找对应下标
int16_t VECTOR_FindByKey(Vector *vector, const void *key)通过键从Vector列表对象中查找对应下标

Vector实现:

  • 数据结构:每一个存储列表都需要构造一个Vector结构体对象,用于存储元素对象。
// vector.h
#define GROW_STEP 4

#define INVALID_INDEX (-1)
typedef void *(*VECTOR_Key)(const void *);                      // 应用层提供KEY-VALUE获取方法,泛类型
typedef int (*VECTOR_Compare)(const void *, const void *);      // 应用层提供比较函数,泛类型

typedef struct SimpleVector {
    int16_t max;                // vector所能存储的最大数据记录数目
    int16_t top;                // vector当前已经存储的数据的峰值数目
    int16_t free;               // vector已经被释放的数据记录数目
    void **data;                // vector存储数据指针
    VECTOR_Key key;             // 将数据元素转换为用于比较的键。方法由用户提供
    VECTOR_Compare compare;     // 将用于比较键值。方法由用户提供
} Vector;
  • Vector列表对象构造方法:其中max,top,free初始状态都为0。
Vector VECTOR_Make(VECTOR_Key key, VECTOR_Compare compare)
{
    Vector vector = {0, 0, 0, NULL, key, compare};
    return vector;
}
  • Vector列表对象清除方法:将Vector列表对象的数据元素空间释放,并将max,top,free清0。
void VECTOR_Clear(Vector *vector)
{
    if (vector == NULL) {
        return;
    }
    if (vector->data == NULL) {
        return;
    }
    free(vector->data);
    vector->max = 0;
    vector->top = 0;
    vector->free = 0;
    vector->data = NULL;
}
  • Vector列表对象增加元素方法:
    • 存储方式:采用顺序存储方式
    • 存储空间扩展策略:通过GROW_STEP的来决定没存储多少个元素来动态扩展空间;描述:如GROW_STEP的值为4,每次申请4个空间进行存储,如果存储元素个数小于4个,不会重新申请空间;如果元素个数个数超过4个,那么将重新申请4个空间。以此类推。优点:减少每次增加元素都要重新申请空间,提高了效率。
int16_t VECTOR_Add(Vector *vector, void *element)
{
    if (vector == NULL || element == NULL) {
        return INVALID_INDEX;
    }

    if (vector->top >= vector->max) {
        int16_t i;
        for (i = vector->top - (int16_t)1; i >= 0; --i) {
            if (vector->data[i] == NULL) {
                vector->data[i] = element;
                vector->free--;
                return i;
            }
        }

        if (vector->max + GROW_STEP < 0) {
            return INVALID_INDEX;
        }

        void **data = (void **)malloc(sizeof(void *) * (vector->max + GROW_STEP));
        if (data == NULL) {
            return INVALID_INDEX;
        }

        if (vector->data != NULL) {
            (void)memcpy(data, vector->data, sizeof(void *) * vector->max);
            free(vector->data);
        }
        vector->data = data;
        vector->max += GROW_STEP;
    }

    vector->data[vector->top] = element;
    return vector->top++;
}
  • Vector列表对象根据下标过去对象方法:Vector可以直接通过顺序表的策略,直接通过下标获取元素;相对于链表来说,效率更加有优势。
void *VECTOR_At(Vector *vector, int16_t index)
{
    if (vector == NULL || vector->top <= index || index < 0) {
        return NULL;
    }

    return vector->data[index];
}
  • Vector列表对象根据下标替换对象方法:Vector可以直接通过顺序表的策略,直接通过下标修改元素;相对于链表来说,效率更加有优势。
void *VECTOR_Swap(Vector *vector, int16_t index, void *element)
{
    if (vector == NULL || vector->top <= index || index < 0) {
        return NULL;
    }
    if (element == NULL) {
        vector->free++;
    }
    void *oldElement = vector->data[index];
    vector->data[index] = element;
    return oldElement;
}
  • Vector列表对象根据元素查找对应下标方法:最终也是调用VECTOR_FindByKey方法。
int16_t VECTOR_Find(Vector *vector, const void *element)
{
    if (vector == NULL || element == NULL) {
        return INVALID_INDEX;
    }
    return VECTOR_FindByKey(vector, (vector->key == NULL) ? element : vector->key(element));
}
  • Vector列表对象根据键查找对应下标方法:遍历整个Vector列表,查询对应的key值,并返回对应下边。
int16_t VECTOR_FindByKey(Vector *vector, const void *key)
{
    if (vector == NULL || key == NULL) {
        return INVALID_INDEX;
    }

    int16_t i;
    for (i = 0; i < vector->top; ++i) {
        if (vector->data[i] == NULL) {
            continue;
        }

        void *first = (vector->key != NULL) ? vector->key(vector->data[i]) : vector->data[i];
        if (first == key) {
            return i;
        }

        if (vector->compare == NULL || first == NULL) {
            continue;
        }

        if (vector->compare(first, key) == 0) {
            return i;
        }
    }
    return INVALID_INDEX;
}
  • Vector列表对象中元素个数获取方法:
int16_t VECTOR_Size(Vector *vector)
{
    if (vector == NULL) {
        return INVALID_INDEX;
    }
    return vector->top;
}
  • Vector列表对象中元素记录数目获取方法:
int16_t VECTOR_Num(Vector *vector)
{
    if (vector == NULL) {
        return INVALID_INDEX;
    }
    return vector->top - vector->free;
}

Vector使用:

  1. 定义一个元素结构体(vector_test),包含两个字段:name和data,其中name可以作为元素对象的唯一标识。

  2. 定义两个vector_test变量,test1和test2。

  3. 我们这个demo是采用name作为唯一标识,需要顶一个函数用于获取vector_test变量的name字段成员的值,作为VECTOR_Key指向函数。

  4. 通过VECTOR_Make构造一个vector对象。其中VECTOR_Key指向vector_name_get函数作为key获取,VECTOR_Compare指向strcmp函数用于key(name字符串)的比较。

  5. 通过VECTOR_Add向vector对象增加元素test1和test2。

  6. 通过VECTOR_FindByKey从vector对象查找元素对象下标。如:key为"rice"的元素对象下标。

  7. 通过VECTOR_FindByKey获取的pos,调用VECTOR_At获取元素对象。

  8. 验证:根据获取元素对象调用其成员,确定是否成功。

#include "vector.h"

Vector vector;

typedef struct {
    char *name;
    int data;
}vector_test;

vector_test test1 = {"rice", 100};
vector_test test2 = {"chen", 100};

const char *vector_name_get(vector_test *test)
{
    return test->name;
}

int main(void)
{
    vector = VECTOR_Make(vector_name_get, strcmp);

    VECTOR_Add(&vector, &test1);
    VECTOR_Add(&vector, &test2);

    int16_t pos = VECTOR_FindByKey(&vector, "rice");

    printf("pos: %d\r\n", pos);

    vector_test *temp = VECTOR_At(&vector, pos);

    printf("name: %s\r\n", temp->name);

    return RT_EOK;
}
  • 结果:


关注微信公众号『Rice嵌入式开发技术分享』,后台回复“微信”添加作者微信,备注”入群“,便可邀请进入技术交流群。

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

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

相关文章

注解开发定义bean

注解开发定义bean 使用Component定义bean在核心配置文件中通过组件扫描加载bean&#xff0c;需要指定扫描包的范围 当然也可以使用Component的衍生注解&#xff0c;可以更加形象的表示 纯注解的开发模式 使用java类来代替了以前的 配置文件&#xff0c;在java类中&#xff…

渗透测试之巧用工具搞定sharepoint

背景 在一次实战演练中 goby扫描到一个sharepoint的getshell漏洞 &#xff0c;漏洞cve编号为CVE-2019-0604&#xff0c;本想着一把梭&#xff0c;直接渗透内网&#xff0c;没想到有waf之类的防护&#xff0c;最后还是想办法解决了。 现在网络上各类漏洞利用工具很多&#xff…

项目中用到的知识点回顾---JWT(JSON Web Token)

1.JWT原理&#xff1a; JWT 的原理是&#xff0c;服务器认证以后&#xff0c;生成一个 JSON 对象&#xff0c;发回给用户&#xff0c;如下&#xff1b; {"姓名": "张三","角色": "管理员","到期时间": "2018年7月1日…

【调试】ftrace(一)基本使用方法

简介 Ftrace是Linux Kernel的官方tracing系统&#xff0c;支持Function trace、静态tracepoint、动态Tracepoint的跟踪&#xff0c;还提供各种Tracer&#xff0c;用于统计最大irq延迟、最大函数调用栈大小、调度事件等。 Ftrace还提供了强大的过滤、快照snapshot、实例&#…

数据结构的一些基础概念

一 基本术语 数据&#xff1a;是描述客观事物的符号&#xff0c;是计算机中可以操作的对象&#xff0c;是能被计算机识别&#xff0c;并输入给计算机处理的符号集合。 数据元素&#xff1a;是组成数据的&#xff0c;有一定意义的基本单位&#xff0c;在计算机中通常作为整体处…

【Docker】docker | 迁移docker目录

一、场景说明1、物理机磁盘空间不够用了2、docker的镜像、容器、卷等资料的默认路径为&#xff1a; /var/lib/docker3、增加了数据盘挂在&#xff0c;需要将docker的全部资料更换个目录二、操作确认是否满足切换条件1&#xff09;服务是否能够暂停&#xff0c;如果可以就OK2&am…

新一代骨传导机皇重磅发布:南卡Neo骨传导运动耳机,性能全面提升

近日&#xff0c;中国最强骨传导品牌NANK南卡发布了最新一代骨传导耳机——南卡Neo骨传导耳机&#xff01;该款耳机与运动专业性更强的南卡runner Pro4略微不同&#xff0c;其主要定位于轻运动风格&#xff0c;所以这款耳机的音质和佩戴舒适度达到了令人咂舌的地步&#xff01;…

KDZD地埋电缆故障测试仪

一、产品特性 ★电缆故障测试仪&#xff08;闪测仪&#xff09; &#xff08;1&#xff09;使用范围广&#xff1a;用于测量各种不同截面、不同介质的各种电力电缆、高频同轴电缆&#xff0c;市话电缆及两根以上均匀铺设的地埋电线等电缆高低阻、短路、开路、断线以及高阻泄漏…

localStorage线上问题的思考

一、背景&#xff1a; localStorage作为HTML5 Web Storage的API之一&#xff0c;使用标准的键值对&#xff08;Key-Value,简称KV&#xff09;数据类型主要作用是本地存储。本地存储是指将数据按照键值对的方式保存在客户端计算机中&#xff0c;直到用户或者脚本主动清除数据&a…

公司新招了个人,一副毛头小子的样儿,哪想到是新一代卷王····

内卷&#xff0c;是现在热度非常高的一个词汇&#xff0c;随着热度不断攀升&#xff0c;隐隐到了“万物皆可卷”的程度。 在程序员职场上&#xff0c;什么样的人最让人反感呢? 是技术不好的人吗?并不是。技术不好的同事&#xff0c;我们可以帮他。 是技术太强的人吗?也不是…

如何对web系统开展无障碍测试

Accessibility test&#xff08;无障碍测试&#xff09;是一种测试方法&#xff0c;旨在评估软件、网站或其他数字产品的可访问性&#xff0c;以确保它们能够被身体残障或其他特殊需求的用户使用。这些测试通常包括使用辅助技术&#xff0c;如屏幕阅读器和放大器&#xff0c;以…

mysql集群简介

集群的好处 高可用性&#xff1a;故障检测及迁移&#xff0c;多节点备份。 可伸缩性&#xff1a;新增数据库节点便利&#xff0c;方便扩容。 负载均衡&#xff1a;切换某服务访问某节点&#xff0c;分摊单个节点的数据库压力。 集群要考虑的风险 网络分裂&#xff1a;群集还…

学生专用台灯怎么选?2023最新对学生眼睛好的台灯盘点

学生是现在用台灯非常多的群体&#xff0c;因为学习压力大&#xff0c;免不了晚上加班&#xff0c;但是也要注意使用保护眼睛的台灯&#xff0c;否则容易出现近视。 那么学生专用台灯该怎么选呢&#xff1f;选择学生专用台灯需要考虑以下几个方面&#xff1a; Ⅰ、亮度&#x…

Ajax ie缓存问题 请求超时与网络异常处理

IE缓存问题解决 什么是ie缓存&#xff1a; ie浏览器会对AJAX的请求结果做一个缓存 这样子就会导致一个问题 :下一次再次发送这个请求时用的是本地的缓存而并不是服务器返回的最新数据。 前端代码&#xff1a; btn.addEventListener(click, function () {const xhr new XMLH…

关于UE4多人局域网联机游戏中联网和回放的设置细节问题

背景 需要搭建局域网游戏。还需要把多人协作玩耍的过程记录下来&#xff0c;可以回放。于是开发了联网和回放功能。但出了问题。报了错。“客户端漫游失败&#xff0c;待定网络游戏创建失败” 怎么解决&#xff1f; 问题 创建UE4工程后&#xff0c;使用蓝图创建多人局域网蓝…

知识图谱的介绍

知识图谱的由来 谷歌在2012年提出了知识图谱的概念&#xff0c;当时目的在于优化搜索引擎的返回结构&#xff0c;为用户提供更精确的结果。 知识图谱的定义 为了理解知识图谱&#xff0c;我们首先要明白信息与知识的概念。首先&#xff0c;信息表示的是外部的客观事实&#…

binlog找回误删数据

1、检查当前是否开启binlog存储 输入命令show variables like %log_bin%;&#xff0c;结果如下 可以看到log_bin的值是ON&#xff0c;说明binlog开启了。 2、查找binlog的存储位置 这个去到数据库的my.cnf配置文件中寻找&#xff0c;有一个log_bin的配置 切换到log_bin的目…

Saleen 系列来袭!

由 Ghostopunch 创作&#x1f47b;&#x1f94a; Ghostpunch 将 Saleen Automotive 带入 The Sandbox 元宇宙&#xff01; 是 Saleen Automotive 于 1984 年由汽车界的梦想家 Steve Saleen 创立&#xff0c;目标是将经过比赛验证的性能带入大街小巷和元宇宙……&#x1f609; 5…

TSDF学习记录

【唐宇迪】三维重建-TSDF通俗解读 人工智能入门教程 水泡动画模拟&#xff08;Marching Cubes&#xff09; - 算法小丑 - 博客园 (cnblogs.com) TSDF 流程分析 首先需要构建一大块空区域采用体素网格来存储该区域需要计算每个体素的TSDF值及其权重 原理简述 SDF值&#x…

【开源库学习】从OkHttp到Retrofit(其一 OkHttp)

从OkHttp到Retrofit主要流程dispatcherInterceptorsRetryAndFollowUpInterceptorBridgeInterceptorCacheInterceptorConnectInterceptorCallServerInterceptor缓存连接池主要流程 okHttp的使用比较简单&#xff0c;通常需要首先初始化一个HttpClient&#xff0c;然后在每次发送…