【问题标题】:Is there a way to do a circular stack?有没有办法做一个循环堆栈?
【发布时间】:2019-03-22 15:50:15
【问题描述】:

下午好!

我正在尝试制作某种圆形堆栈。它应该像一个普通的 LIFO 堆栈,但没有明显的限制。它应该消除或跳过当时引入的第一个元素,而不是达到其最大容量!

例如:

假设我们有一个包含 3 个元素的堆栈:stack[3]

我们通过“推入”其中的 3 个元素来填充它:push[a], push[b], push[c]

但随后我们将要添加第 4 个和第 5 个元素:push[d], push[e]

标准堆栈会说堆栈达到了它的限制,它不能再添加任何元素。

但我想要一个循环堆栈,它将消除或跳过ab,记住cde 并输出edc

该项目是在 ESP32 上的 PlatformIO 中完成的,所以我无法访问 C++ STL,即使我有,我认为只为 1 个堆栈编译这么大的库是没有意义的。 即使有一段时间我认为我应该编译一个类似的库来让我访问stackdeque,但那个时间已经一去不复返了,因为现在我觉得自己像个白痴,无法弄清楚数学问题。这已经困扰我一个多星期了。

我在网上找到的只是以下 FIFO 循环缓冲区:

class circular_buffer {
public:
    explicit circular_buffer(size_t size) :
        buf_(std::unique_ptr<T[]>(new T[size])),
        max_size_(size)
    {

    }

    void put(T item)
    {
        std::lock_guard<std::mutex> lock(mutex_);

        buf_[head_] = item;

        if(full_) {
            tail_ = (tail_ + 1) % max_size_;
        }

        head_ = (head_ + 1) % max_size_;

        full_ = head_ == tail_;
    }

    T get()
    {
        std::lock_guard<std::mutex> lock(mutex_);

        if(empty())
        {
            return T();
        }

        //Read data and advance the tail (we now have a free space)
        auto val = buf_[tail_];
        full_ = false;      
        tail_ = (tail_ + 1) % max_size_;

        return val;
    }

    void reset()
    {
        std::lock_guard<std::mutex> lock(mutex_);
        head_ = tail_;
        full_ = false;
    }

    bool empty() const
    {
        //if head and tail are equal, we are empty
        return (!full_ && (head_ == tail_));
    }

    bool full() const
    {
        //If tail is ahead the head by 1, we are full
        return full_;
    }

    size_t capacity() const
    {
        return max_size_;
    }

    size_t size() const
    {
        size_t size = max_size_;

        if(!full_)
        {
            if(head_ >= tail_)
            {
                size = head_ - tail_;
            }
            else
            {
                size = max_size_ + head_ - tail_;
            }
        }

        return size;
    }

private:
    std::mutex mutex_;
    std::unique_ptr<T[]> buf_;
    size_t head_ = 0;
    size_t tail_ = 0;
    const size_t max_size_;
    bool full_ = 0;
};

过去 3 天我一直在修改它,但我无法让它按我想要的方式工作。 它是一个先进先出结构,将打印abccde

在这种情况下,我希望它从上到下,从头到尾打印,但我无法弄清楚。

【问题讨论】:

  • 从技术上讲,“循环堆栈”不再是堆栈,而是循环缓冲区或环形缓冲区。

标签: c++ stack circular-buffer lifo


【解决方案1】:

如果我理解正确,那么您正在寻找的只是一个大小固定的缓冲区,该缓冲区有一个指向“堆栈”“顶部”的指针,该指针会递增/递减以使其环绕结束的缓冲区。这将自动导致最新的条目总是覆盖最旧的条目,从而有效地为您提供最后 N 个值的 LIFO 存储,其中 N 是缓冲区大小。例如:

#include <cstddef>
#include <memory>
#include <iostream>

template <typename T>
class ForgetfulStack
{
    std::unique_ptr<T[]> buffer;
    std::size_t head = 0;
    std::size_t size = 0;

public:
    ForgetfulStack(std::size_t size)
        : buffer(std::make_unique<T[]>(size)), size(size)
    {
    }

    void push(const T& value)
    {
        buffer[head] = value;
        head = (head + 1) % size;
    }

    T pop()
    {
        head = (head - 1 + size) % size;
        return buffer[head];
    }
};

int main()
{
    ForgetfulStack<int> blub(3);

    blub.push(1);
    blub.push(2);
    blub.push(3);
    blub.push(4);
    blub.push(5);

    std::cout << blub.pop() << ' ' << blub.pop() << ' ' << blub.pop() << std::endl;
}

请注意,这个简单的实现不是线程安全的……

【讨论】:

  • 感觉(head - 1 + size)如果head==0会下溢,应该改写成(head + size - 1)
  • @Useless, std::size_t 是无符号的,所以这不会下溢。至少根据我对无符号整数算术的理解,这里执行加法和减法的顺序应该无关紧要!?当然,我可能错了……
  • 它确实是无符号的,并且它下溢 - 但这实际上对于无符号整数类型是完美定义的。你仍然会得到正确的结果 (0u - 1 + size == UINT_MAX + size == size - 1),只是看到潜在的下溢让我觉得有点痒。
  • @Useless 啊,我想我们只是对那里的术语有误解。我假设您的意思是有符号整数超出范围的意义上的下溢,导致未定义的行为。如果您只是意味着它将在 0 处环绕,那么可以肯定,它会这样做。但正如你所说,结果最终还是正确的。将其更改为(head + size - 1) 只会将潜在环绕在 0 处换成潜在环绕在最大值处……当然,可以说,后者的发生频率要低得多……
  • 如果堆栈为空或应该为空时我不断弹出值,您的示例将返回先前存储在堆栈中的随机值。
猜你喜欢
  • 2021-04-22
  • 2011-06-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-18
  • 2018-02-04
  • 2013-07-19
  • 1970-01-01
相关资源
最近更新 更多