将数据添加到没有原始循环的 STL 容器

adding data to stl container without raw loops

本文关键字:循环 STL 容器 原始 数据 添加      更新时间:2023-10-16

我经常看到你可以用stl算法替换所有手写/原始循环。只是为了提高我的C++知识,我一直在尝试。

为了用数据填充 std::vector,我使用 for 循环和循环索引。

unsigned int buffer_size = (format.getBytesPerSecond() * playlen) / 1000;
    // pcm data stored in a 'short type' vector
    vector<short> pcm_data;
    for (unsigned int i = 0; i < buffer_size; ++i)
    {
        pcm_data.push_back( static_cast<short>(amplitude * sin((2 * M_PI * i * frequency) / format.SampleRate)) );
    }

上面的代码工作正常,如您所见,我使用 for 循环索引"i"表示算法正确。

有人怎么能用标准中的东西替换它?

见过的唯一允许我这样做的函数是 std::transform 和 std::generate,但这两个都不起作用,因为我需要一个索引值来递增代码。

例如:

generate_n(begin(pcm_data), buffer_size, [] ()
    {
        return static_cast<short>(amplitude * sin((2 * M_PI * i * frequency) / format.SampleRate)); //what is i??
    });
    transform(begin(pcm_data), end(pcm_data), begin(pcm_data) [] (???)
    {
        return static_cast<short>(amplitude * sin((2 * M_PI * i * frequency) / format.SampleRate)); //what is i??
    });

还是我只是太过分地了解"没有原始循环"的想法?

这里真正的解决方案是定义一个适当的迭代器,如下所示:

class PcmIter : public std::iterator<std::forward_iterator_tag, short>
{
    int myIndex;
    double myAmplitude;
    double myFrequency;
    short myValue;
    void calculate()
    {
        myValue = myAmplitude * std::sin( 2 * M_PI * myIndex * frequency );
    }
public:
    PcmIter( int index, amplitude = 0.0, frequency = 0.0 )
        : myIndex( index )
        , myAmplitude( amplitude )
        , myFrequency( frequency )
    {
        calculate();
    }
    bool operator==( PcmIter const& other ) const
    {
        return myIndex == other.myIndex;
    }
    bool operator!=( PcmIter const& other ) const
    {
        return myIndex != other.myIndex;
    }
    const short& operator*() const
    {
        return myValue;
    }
    PcmIter& operator++()
    {
        ++ myIndex;
        calculate();
    }
    PcmIter operator++( int )
    {
        PcmIter results( *this );
        operator++();
        return results;
    }
};

在实践中,我怀疑你可以通过拥有 operator*返回一个值,该值是在此时计算的,并且没有myValue成员。

要使用:

std::vector<short> pcmData(
    PcmIter( 0, amplitude, frequency),
    PcmIter( buffer_size ) );

(振幅和频率与结束无关迭代器,因为它永远不会被取消引用。

理想情况下,这将是一个random_access_iterator,以便向量的构造函数将计算元素的数量,并且预先分配它们。 这涉及实施更多但是。

如果你很勇敢,并且必须经常做类似的事情,你可以考虑将迭代器制作为模板,以在你感兴趣的函数上实例化。

虽然我最近没有机会和他们一起玩,但如果您正在使用 Boost,您可以考虑链接一个transform_iterator和一个counting_iterator. 它仍然是有点罗嗦,但是在Boost做迭代器的人确实做到了考虑到 STL 的设计有些破碎,他们尽其所能迭代器。

您可以简单地在"generate_n"范围内使用变量来声明变量。

unsigned int i = 0;
generate_n(begin(pcm_data), buffer_size, [&] ()
    {
        return static_cast<short>(amplitude * sin((2 * M_PI * (i++) * frequency) / format.SampleRate)); //what is i??
    });

我建议在Boost Library中counting_iterator。一对计数迭代器为您提供一个整数范围。显然,没有底层容器。它"懒惰地"提供整数。该库提供用于创建它的工厂函数make_counting_iterator

标准库(标头 iterator(中的back_insert_iterator(使用工厂函数 back_inserter(有效地调用容器的成员push_back

有了这些成分,您可以将transform与"索引"一起使用。

#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
using namespace std;
#include <boost/iterator/counting_iterator.hpp>
int main(int argc, char* argv[])
{
    // Create a pair of counting iterators
    auto first = boost::make_counting_iterator(0);
    auto last = boost::make_counting_iterator(10);
    vector<int> vi;
    // Construct a vector of a few even number, as an example.
    transform(first, last, back_inserter(vi), [](int i){ return 2 * i; });
    // Print the result for check
    copy(vi.begin(), vi.end(), ostream_iterator<int>{cout, "  "});
    return 0;
}

打印输出:

0  2  4  6  8  10  12  14  16  18

不一定更好,而是使用 STL 的解决方案:

struct generate_value {
short operator() () const {return amplitude * sin((2 * M_PI * i++ * frequency) / format.SampleRate);}
private:
unsigned i = 0;
};
generate_n(back_inserter(pcm_data), buffer_size, generate_value{});  

我看到了一些我还没有看到的可能性。一个是从一系列数字的迭代器开始的:

template <class T>
class xrange_t {
    T start;
    T stop;
public:
    xrange_t(T start, T stop) : start(start), stop(stop) {}
    class iterator : public std::iterator<std::forward_iterator_tag, T> {
        T current;
    public:
        iterator(T t) : current(t) {}
        T operator *() { return current; }
        iterator &operator++() { ++current; return *this; }
        bool operator!=(iterator const &other) const { return current != other.current; }
        bool operator==(iterator const &other) const { return current == other.current; }
    };
    iterator begin() { return iterator(start); }
    iterator end() { return iterator(stop); }
};
template <class T>
xrange_t<T> xrange(T start, T stop) {
    return xrange_t<T>(start, stop);
}

然后,您将将其与 ranged-for 循环一起使用来完成实际工作:

#include "xrange"
for (auto i : xrange(0, buffer_size))
    pcm_data.push_back( static_cast<short>(amplitude * sin((2 * M_PI * i * frequency) / format.SampleRate)) );

另一种可能性是通过几个步骤执行工作:

std::vector<short> pcm_data(buffer_size);
std::iota(pcm_data.begin(), pcm_data.end(), 0);
std::transform(pcm_data.begin(), pcm_data.end(), pcm_data.begin(), 
    [](short i) { 
        return static_cast<short>(amplitude * sin((2 * M_PI * i * frequency) / format.SampleRate)));
    }
);

首先用连续的i值(即函数的输入(填充数组,然后将每个输入转换为匹配的输出值。

不过,这有两个潜在的缺点:

  1. 如果 i 的值可能超过可以存储在 short 中的值,则可能会在初始存储阶段截断输入值。目前尚不清楚您对i使用 int 是否反映了它可能具有更大量级的可能性,或者默认情况下只是使用 int
  2. 它遍历结果向量两次。如果矢量很大(特别是如果它太大而无法放入缓存(,则可能会慢得多。