Robot Operating System——深度解析单线程执行器(SingleThreadedExecutor)执行逻辑

news2024/11/14 15:05:36

大纲

  • 创建SingleThreadedExecutor
  • 新增Node
    • add_node
    • trigger_entity_recollect
      • collect_entities
  • 自旋等待
    • get_next_executable
      • wait_for_work
      • get_next_ready_executable
        • Timer
        • Subscription
        • Service
        • Client
        • Waitable
        • AnyExecutable
    • execute_any_executable
  • 参考资料

在ROS2中,我们设置的各种回调都是在执行器(Executor)中执行的,所以它是整个系统非常核心的组件。
在这里插入图片描述
目前,rclcpp 提供了三种 Executor 类型,它们派生自同一个父类Executor。
在这里插入图片描述

本文我们将借用《Robot Operating System——单线程中启动多个Node》中的例子,将单线程执行器在最上层的使用方法总结如下

  // Create an executor that will be responsible for execution of callbacks for a set of nodes.
  // With this version, all callbacks will be called from within this thread (the main one).
  rclcpp::executors::SingleThreadedExecutor exec;
  rclcpp::NodeOptions options;

  // Add some nodes to the executor which provide work for the executor during its "spin" function.
  // An example of available work is executing a subscription callback, or a timer callback.
  auto server = std::make_shared<composition::Server>(options);
  exec.add_node(server);

  // spin will block until work comes in, execute work as it becomes available, and keep blocking.
  // It will only be interrupted by Ctrl-C.
  exec.spin();

即:

  1. 创建SingleThreadedExecutor ;
  2. 新增Node;
  3. 自旋等待。

创建SingleThreadedExecutor

SingleThreadedExecutor的构造函数基本就是交给基类rclcpp::Executor的构造函数来实现。

// https://github.com/ros2/rclcpp/blob/jazzy/rclcpp/src/rclcpp/executors/single_threaded_executor.cpp
SingleThreadedExecutor::SingleThreadedExecutor(const rclcpp::ExecutorOptions & options)
: rclcpp::Executor(options) {}

在Executor的构造函数中,我们着重关注成员变量collector_。

Executor::Executor(const rclcpp::ExecutorOptions & options)
: spinning(false),
  interrupt_guard_condition_(std::make_shared<rclcpp::GuardCondition>(options.context)),
  shutdown_guard_condition_(std::make_shared<rclcpp::GuardCondition>(options.context)),
  context_(options.context),
  notify_waitable_(std::make_shared<rclcpp::executors::ExecutorNotifyWaitable>(
      [this]() {
        this->entities_need_rebuild_.store(true);
      })),
  entities_need_rebuild_(true),
  collector_(notify_waitable_),
  wait_set_({}, {}, {}, {}, {}, {}, options.context),
  current_notify_waitable_(notify_waitable_),
  impl_(std::make_unique<rclcpp::ExecutorImplementation>())
{
  shutdown_callback_handle_ = context_->add_on_shutdown_callback(
    [weak_gc = std::weak_ptr<rclcpp::GuardCondition>{shutdown_guard_condition_}]() {
      auto strong_gc = weak_gc.lock();
      if (strong_gc) {
        strong_gc->trigger();
      }
    });

  notify_waitable_->add_guard_condition(interrupt_guard_condition_);
  notify_waitable_->add_guard_condition(shutdown_guard_condition_);

  wait_set_.add_waitable(notify_waitable_);
}

collector_是一个集合,它保存了后续要执行的各个Node指针。

/// Collector used to associate executable entities from nodes and guard conditions
  rclcpp::executors::ExecutorEntitiesCollector collector_;

新增Node

add_node

业务逻辑的Node会被添加到上面介绍的成员变量collector_中。

// https://github.com/ros2/rclcpp/blob/jazzy/rclcpp/src/rclcpp/executor.cpp
void
Executor::add_node(rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_ptr, bool notify)
{
  this->collector_.add_node(node_ptr);

  try {
    this->trigger_entity_recollect(notify);
  } catch (const rclcpp::exceptions::RCLError & ex) {
    throw std::runtime_error(
            std::string(
              "Failed to trigger guard condition on node add: ") + ex.what());
  }
}

然后会调用trigger_entity_recollect方法。

trigger_entity_recollect

这个方法会做两件事:

  1. 修改std::atomic_bool类型变量entities_need_rebuild_的值为true,进而让collect_entities()被执行。
  2. 如果notify为true,则会通过interrupt_guard_condition_->trigger()唤醒一个处于等待状态的执行器。
// https://github.com/ros2/rclcpp/blob/jazzy/rclcpp/src/rclcpp/executor.cpp
void
Executor::trigger_entity_recollect(bool notify)
{
  this->entities_need_rebuild_.store(true);

  if (!spinning.load() && entities_need_rebuild_.exchange(false)) {
    std::lock_guard<std::mutex> guard(mutex_);
    this->collect_entities();
  }

  if (notify) {
    interrupt_guard_condition_->trigger();
  }
}

collect_entities

collect_entities主要做两件事:

  1. 过滤过期的Node以及相关回调函数。
// https://github.com/ros2/rclcpp/blob/jazzy/rclcpp/src/rclcpp/executor.cpp
void
Executor::collect_entities()
{
  // Updating the entity collection and waitset expires any active result
  this->wait_result_.reset();

  // Get the current list of available waitables from the collector.
  rclcpp::executors::ExecutorEntitiesCollection collection;
  this->collector_.update_collections();
  auto callback_groups = this->collector_.get_all_callback_groups();
  rclcpp::executors::build_entities_collection(callback_groups, collection);

  // We must remove expired entities here, so that we don't continue to use older entities.
  // See https://github.com/ros2/rclcpp/issues/2180 for more information.
  current_collection_.remove_expired_entities();
  1. 更新current_collection_和wait_set_。
  // Update each of the groups of entities in the current collection, adding or removing
  // from the wait set as necessary.
  current_collection_.timers.update(
    collection.timers,
    [this](auto timer) {wait_set_.add_timer(timer);},
    [this](auto timer) {wait_set_.remove_timer(timer);});

  current_collection_.subscriptions.update(
    collection.subscriptions,
    [this](auto subscription) {
      wait_set_.add_subscription(subscription, kDefaultSubscriptionMask);
    },
    [this](auto subscription) {
      wait_set_.remove_subscription(subscription, kDefaultSubscriptionMask);
    });

  current_collection_.clients.update(
    collection.clients,
    [this](auto client) {wait_set_.add_client(client);},
    [this](auto client) {wait_set_.remove_client(client);});

  current_collection_.services.update(
    collection.services,
    [this](auto service) {wait_set_.add_service(service);},
    [this](auto service) {wait_set_.remove_service(service);});

  current_collection_.guard_conditions.update(
    collection.guard_conditions,
    [this](auto guard_condition) {wait_set_.add_guard_condition(guard_condition);},
    [this](auto guard_condition) {wait_set_.remove_guard_condition(guard_condition);});

  current_collection_.waitables.update(
    collection.waitables,
    [this](auto waitable) {wait_set_.add_waitable(waitable);},
    [this](auto waitable) {wait_set_.remove_waitable(waitable);});

  // In the case that an entity already has an expired weak pointer
  // before being removed from the waitset, additionally prune the waitset.
  this->wait_set_.prune_deleted_entities();
}

自旋等待

spin()内部核心是一个while循环。它会不停使用get_next_executable取出可以运行的Node的回调,然后让execute_any_executable将其执行。

// https://github.com/ros2/rclcpp/blob/jazzy/rclcpp/src/rclcpp/executors/single_threaded_executor.cpp
void
SingleThreadedExecutor::spin()
{
  if (spinning.exchange(true)) {
    throw std::runtime_error("spin() called while already spinning");
  }
  RCPPUTILS_SCOPE_EXIT(wait_result_.reset();this->spinning.store(false););

  // Clear any previous result and rebuild the waitset
  this->wait_result_.reset();
  this->entities_need_rebuild_ = true;

  while (rclcpp::ok(this->context_) && spinning.load()) {
    rclcpp::AnyExecutable any_executable;
    if (get_next_executable(any_executable)) {
      execute_any_executable(any_executable);
    }
  }
}

那么这个while循环会不会导致CPU一直空转呢?答案是:不是。我们可以看get_next_executable的实现。

get_next_executable

// https://github.com/ros2/rclcpp/blob/jazzy/rclcpp/src/rclcpp/executor.cpp
bool
Executor::get_next_executable(AnyExecutable & any_executable, std::chrono::nanoseconds timeout)
{
  bool success = false;
  // Check to see if there are any subscriptions or timers needing service
  // TODO(wjwwood): improve run to run efficiency of this function
  success = get_next_ready_executable(any_executable);
  // If there are none
  if (!success) {
    // Wait for subscriptions or timers to work on
    wait_for_work(timeout);
    if (!spinning.load()) {
      return false;
    }
    // Try again
    success = get_next_ready_executable(any_executable);
  }
  return success;
}

它会在底层调用wait_for_work。

wait_for_work

这个方法会一直阻塞到时间超时,或者有回调函数可以被调用。

// https://github.com/ros2/rclcpp/blob/jazzy/rclcpp/src/rclcpp/executor.cpp
void
Executor::wait_for_work(std::chrono::nanoseconds timeout)
{
  TRACETOOLS_TRACEPOINT(rclcpp_executor_wait_for_work, timeout.count());

  // Clear any previous wait result
  this->wait_result_.reset();

  {
    std::lock_guard<std::mutex> guard(mutex_);
    if (this->entities_need_rebuild_.exchange(false) || current_collection_.empty()) {
      this->collect_entities();
    }
  }
  this->wait_result_.emplace(wait_set_.wait(timeout));
  if (!this->wait_result_ || this->wait_result_->kind() == WaitResultKind::Empty) {
    RCUTILS_LOG_WARN_NAMED(
      "rclcpp",
      "empty wait set received in wait(). This should never happen.");
  } else {
    if (this->wait_result_->kind() == WaitResultKind::Ready && current_notify_waitable_) {
      auto & rcl_wait_set = this->wait_result_->get_wait_set().get_rcl_wait_set();
      if (current_notify_waitable_->is_ready(rcl_wait_set)) {
        current_notify_waitable_->execute(current_notify_waitable_->take_data());
      }
    }
  }
}

其中主要负责等待的是这句

wait_set_.wait(timeout)

在SingleThreadedExecutor中,由于调用get_next_executable没有传递时间,便采用了默认时间。这样get_next_executable会一直等到有回调可以被执行。这样就避免了CPU空转的问题。

  /// Wait for executable in ready state and populate union structure.
  /**
   * If an executable is ready, it will return immediately, otherwise
   * block based on the timeout for work to become ready.
   *
   * \param[out] any_executable populated union structure of ready executable
   * \param[in] timeout duration of time to wait for work, a negative value
   *   (the defualt behavior), will make this function block indefinitely
   * \return true if an executable was ready and any_executable was populated,
   *   otherwise false
   */
  RCLCPP_PUBLIC
  bool
  get_next_executable(
    AnyExecutable & any_executable,
    std::chrono::nanoseconds timeout = std::chrono::nanoseconds(-1));

get_next_ready_executable

get_next_ready_executable会按顺序寻找Timer、Subscription、Service 、Client和Waitable中第一个处于可被回调状态的Node。

Timer

  bool valid_executable = false;

  if (!wait_result_.has_value() || wait_result_->kind() != rclcpp::WaitResultKind::Ready) {
    return false;
  }

  if (!valid_executable) {
    size_t current_timer_index = 0;
    while (true) {
      auto [timer, timer_index] = wait_result_->peek_next_ready_timer(current_timer_index);
      if (nullptr == timer) {
        break;
      }
      current_timer_index = timer_index;
      auto entity_iter = current_collection_.timers.find(timer->get_timer_handle().get());
      if (entity_iter != current_collection_.timers.end()) {
        auto callback_group = entity_iter->second.callback_group.lock();
        if (!callback_group || !callback_group->can_be_taken_from()) {
          current_timer_index++;
          continue;
        }
        // At this point the timer is either ready for execution or was perhaps
        // it was canceled, based on the result of call(), but either way it
        // should not be checked again from peek_next_ready_timer(), so clear
        // it from the wait result.
        wait_result_->clear_timer_with_index(current_timer_index);
        // Check that the timer should be called still, i.e. it wasn't canceled.
        any_executable.data = timer->call();
        if (!any_executable.data) {
          current_timer_index++;
          continue;
        }
        any_executable.timer = timer;
        any_executable.callback_group = callback_group;
        valid_executable = true;
        break;
      }
      current_timer_index++;
    }
  }
Subscription
  if (!valid_executable) {
    while (auto subscription = wait_result_->next_ready_subscription()) {
      auto entity_iter = current_collection_.subscriptions.find(
        subscription->get_subscription_handle().get());
      if (entity_iter != current_collection_.subscriptions.end()) {
        auto callback_group = entity_iter->second.callback_group.lock();
        if (!callback_group || !callback_group->can_be_taken_from()) {
          continue;
        }
        any_executable.subscription = subscription;
        any_executable.callback_group = callback_group;
        valid_executable = true;
        break;
      }
    }
  }
Service
  if (!valid_executable) {
    while (auto service = wait_result_->next_ready_service()) {
      auto entity_iter = current_collection_.services.find(service->get_service_handle().get());
      if (entity_iter != current_collection_.services.end()) {
        auto callback_group = entity_iter->second.callback_group.lock();
        if (!callback_group || !callback_group->can_be_taken_from()) {
          continue;
        }
        any_executable.service = service;
        any_executable.callback_group = callback_group;
        valid_executable = true;
        break;
      }
    }
  }
Client
  if (!valid_executable) {
    while (auto client = wait_result_->next_ready_client()) {
      auto entity_iter = current_collection_.clients.find(client->get_client_handle().get());
      if (entity_iter != current_collection_.clients.end()) {
        auto callback_group = entity_iter->second.callback_group.lock();
        if (!callback_group || !callback_group->can_be_taken_from()) {
          continue;
        }
        any_executable.client = client;
        any_executable.callback_group = callback_group;
        valid_executable = true;
        break;
      }
    }
  }
Waitable
  if (!valid_executable) {
    while (auto waitable = wait_result_->next_ready_waitable()) {
      auto entity_iter = current_collection_.waitables.find(waitable.get());
      if (entity_iter != current_collection_.waitables.end()) {
        auto callback_group = entity_iter->second.callback_group.lock();
        if (!callback_group || !callback_group->can_be_taken_from()) {
          continue;
        }
        any_executable.waitable = waitable;
        any_executable.callback_group = callback_group;
        any_executable.data = waitable->take_data();
        valid_executable = true;
        break;
      }
    }
  }
AnyExecutable
// https://github.com/ros2/rclcpp/blob/jazzy/rclcpp/include/rclcpp/any_executable.hpp
struct AnyExecutable
{
  RCLCPP_PUBLIC
  AnyExecutable();

  RCLCPP_PUBLIC
  virtual ~AnyExecutable();

  // Only one of the following pointers will be set.
  rclcpp::SubscriptionBase::SharedPtr subscription;
  rclcpp::TimerBase::SharedPtr timer;
  rclcpp::ServiceBase::SharedPtr service;
  rclcpp::ClientBase::SharedPtr client;
  rclcpp::Waitable::SharedPtr waitable;
  // These are used to keep the scope on the containing items
  rclcpp::CallbackGroup::SharedPtr callback_group {nullptr};
  rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_base {nullptr};
  std::shared_ptr<void> data {nullptr};
};

execute_any_executable

找到可以执行的Node后,便可以调用execute_any_executable让其执行。

在execute_any_executable内部,我们看到它也是区分Timer、Subscription、Service 、Client和Waitable类型来执行的。

// https://github.com/ros2/rclcpp/blob/jazzy/rclcpp/src/rclcpp/executor.cpp
void
Executor::execute_any_executable(AnyExecutable & any_exec)
{
  if (!spinning.load()) {
    return;
  }

  assert(
    (void("cannot execute an AnyExecutable without a valid callback group"),
    any_exec.callback_group));

  if (any_exec.timer) {
    TRACETOOLS_TRACEPOINT(
      rclcpp_executor_execute,
      static_cast<const void *>(any_exec.timer->get_timer_handle().get()));
    execute_timer(any_exec.timer, any_exec.data);
  }
  if (any_exec.subscription) {
    TRACETOOLS_TRACEPOINT(
      rclcpp_executor_execute,
      static_cast<const void *>(any_exec.subscription->get_subscription_handle().get()));
    execute_subscription(any_exec.subscription);
  }
  if (any_exec.service) {
    execute_service(any_exec.service);
  }
  if (any_exec.client) {
    execute_client(any_exec.client);
  }
  if (any_exec.waitable) {
    const std::shared_ptr<void> & const_data = any_exec.data;
    any_exec.waitable->execute(const_data);
  }

  // Reset the callback_group, regardless of type
  any_exec.callback_group->can_be_taken_from().store(true);
}

参考资料

  • https://docs.ros.org/en/rolling/Concepts/Intermediate/About-Executors.html#executors

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

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

相关文章

ARM知识点二

一、指令 指令的生成过程 指令执行过程示例 if (a 0) {x 0; } else {x x 3; } //翻译为 cmp r0,#0 MOVEQ R1,#0 ADDGT R1,R1,#3指令获取&#xff1a;从Flash中读取 CMP R0, #0&#xff0c;控制器开始执行。 指令解码&#xff1a;解码器解析 CMP 指令&#xff0c;ALU比较R…

DAMA学习笔记(十)-数据仓库与商务智能

1.引言 数据仓库&#xff08;Data Warehouse&#xff0c;DW&#xff09;的概念始于20世纪80年代。该技术赋能组织将不同来源的数据整合到公共的数据模型中去&#xff0c;整合后的数据能为业务运营提供洞察&#xff0c;为企业决策支持和创造组织价值开辟新的可能性。与商务智能&…

浅谈线程组插件之jp@gc - Ultimate Thread Group

浅谈线程组插件之jpgc - Ultimate Thread Group jpgc - Ultimate Thread Group是JMeter的一个强大且灵活的扩展插件&#xff0c;由JMeter Plugins Project提供。它为性能测试提供了超越JMeter原生线程组的更精细的控制能力&#xff0c;允许用户根据复杂的场景设计自定义负载模…

【TFT电容屏】

TFT电容屏基础知识补课 前言一、入门知识1.1 引脚介绍1.1.1 显示部分片选指令选择写指令读操作复位并行数据接口 1.1.2 背光电源背光电源 1.1.3 触摸IIC接口外部中断接口复位NC 1.2 驱动介绍1.3 FSMC介绍 总结 前言 跟着阳桃电子的学习⇨逐个细讲触摸屏接口定义–STM32单片机…

科普文:JUC系列之ForkJoinPool源码解读ForkJoinWorkerThread

科普文&#xff1a;JUC系列之ForkJoinPool基本使用及原理解读-CSDN博客 科普文&#xff1a;JUC系列之ForkJoinPool源码解读概叙-CSDN博客 科普文&#xff1a;JUC系列之ForkJoinPool源码解读WorkQueue-CSDN博客 科普文&#xff1a;JUC系列之ForkJoinPool源码解读ForkJoinTask…

复现sql注入漏洞

Less-1 字符型注入 页面如下&#xff1a; 我们先输入“?id1”看看结果&#xff1a; 页面显示错误信息中显示提交到sql中的“1”在通过sql语句构造后形成“1" LIMIT 0, 1”&#xff0c;其中多了一个“”&#xff0c;那么&#xff0c;我们的任务就是——逃脱出单引号的控制…

petalinux安装成功后登录Linux出现密码账号不正确

安装完Linux系统后发现登陆开发板上的Linux系统登陆一直错误&#xff0c;但你输入的账号和密码确确实实是“root”&#xff0c;但仍然一直在重复登陆。 这个时候就会怀疑自己是不是把密码改了&#xff0c;导致错误&#xff0c;然后又重新创建petalinux工程。 其实这个时候不需…

2024年第二季度HDD出货量和容量分析

概述 根据Trendfocus, Inc.发布的《SDAS: HDD Information Service CQ2 24 Quarterly Update – Executive Summary》报告&#xff0c;2024年第二季度硬盘驱动器(HDD)出货量和容量均出现了显著增长。总体来看&#xff0c;HDD出货量较上一季度增长2%&#xff0c;达到3028万块&a…

MySQLDM笔记-查询库中是否存在列出的表名及查询库中列出的不存在的表名

如下表名&#xff1a; aaa,bb,cc,ccs,dds,csdf,csdfs,sdfa,werwe,csdfsd 在MySQL库中&#xff0c;查询哪些表名在数据库中 SELECT table_name FROM information_schema.tables WHERE table_schema your_database_name_here AND table_name IN (aaa, bb, cc, ccs, dds, csdf…

硬件电路学习记录(七)——全面概述MOS管

目录 1.NMOS&#xff1a; 工作原理 特性 应用 2.PMOS&#xff1a; PMOS的结构与工作原理 结构 工作原理 增强型PMOS与耗尽型PMOS 增强型PMOS&#xff08;Enhancement Mode PMOS&#xff09; 耗尽型PMOS&#xff08;Depletion Mode PMOS&#xff09; 应用 PMOS的工…

不同角色路由权限配置(六)

一、启用方式 配置开启config/config.ts。同时需要 src/access.ts 提供权限配置 export default {access: {},// access 插件依赖 initial State 所以需要同时开启initialState: {}, };这里以扩展的路由配置为例&#xff0c;配置只有admin权限才能查看的页面 1、在src/acces…

新华三H3CNE网络工程师认证—路由基础

我们的一个个网络其实是由不同的广播域构成的&#xff0c;而路由器的作用就是用来连接不同的广播域。那么不同广播域之间是如何通信的呢&#xff1f;比如有三个网段&#xff0c;1.0、2.0和3.0。网段1.0和网段2.0通信需要构造数据包&#xff0c;源是1.1&#xff0c;目标去往2.1。…

3.6 上下文菜单

上下文菜单 上下文菜单就是常见的右键菜单(弹出式菜单)。 显示上下文菜单&#xff0c;阻塞函数 BOOL TrackPopupMenu(HMENU hMenu, //菜单句柄UINT uFlags, //显示方式int x, //水平位置&#xff0c;屏幕坐标系int y, //垂直位置&#xff0c;屏幕坐标系UINT nReserved, //…

Cartopy简介和安装

Cartopy 是一个开源免费的第三方 Python 扩展包&#xff0c;由英国气象办公室的科学家们开发&#xff0c;支持 Python 2.7 和 Python 3&#xff0c;致力于使用最简单直观的方式生成地图&#xff0c;并提供对 matplotlib 友好的协作接口。初学Cartopy&#xff0c;欢迎指正&#…

Leetcode—186. 反转字符串中的单词 II【中等】Plus

2024每日刷题&#xff08;152&#xff09; Leetcode—186. 反转字符串中的单词 II 实现代码 class Solution { public:void reverseW(vector<char>& s, int n) {int i 0;int j 0;while(i < n) {while(i < j || i < n && s[i] ) {i;}while(j &…

Spring自动装配的局限

Spring自动装配的局限 1. 覆盖风险2. 类型限制3. 精确性挑战4. 维护难度 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; Spring的自动装配功能虽然为开发者带来了极大的便利&#xff0c;但在实际应用中也存在一些不容忽视的局限。 1. 覆盖…

大数据-65 Kafka 高级特性 分区 Broker自动再平衡 ISR 副本 宕机恢复再重平衡 实测

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

科普小课堂|LCD 问题排查思路解析

&#xff08;ELF 1开发板、ELF 1S开发板及显示屏&#xff09; 在嵌入式系统开发中&#xff0c;液晶显示器&#xff08;LCD&#xff09;作为人机交互的重要界面&#xff0c;其稳定性和可靠性至关重要。然而在实际应用中&#xff0c;LCD难免会遇到各种问题。今天和各位小伙伴分享…

Javascript常见算法(二)【学习】

动态规划 斐波那契数列&#xff1a; 经典的动态规划问题&#xff0c;每个数是前两个数的和。 斐波那契数列&#xff08;Fibonacci sequence&#xff09;是一个非常著名的数列&#xff0c;其中每个数是前两个数的和&#xff0c;序列以0和1开始。在JavaScript中&#xff0c;有多…

【数据结构】顺序表详解以及实现(C语言实现)

目录 前言&#xff1a; 顺序表的特点&#xff1a; 顺序表简介&#xff1a; 顺序表具体实现&#xff1a; 1.初始化 2.销毁 3.检查空间容量 4.头插和尾插 5.头删和尾删 6.打印 7.指定位置插入 8.指定位置删除 9. 查找是否有对应元素 顺序表是线性表的存储结构&#xff…