可变长度数组:如何在 C++ 中创建可变大小的缓冲区

Variable Length Arrays: How to create a buffer with variable size in C++

我目前正在写移动平均线class。

目标是在创建 class Running_Average 的新对象时能够将缓冲区大小指定为构造函数的一部分。

#include <iostream>
#include "Complex.h"
#include <cmath>
#include<stdio.h>
#include<stdlib.h>
#include <windows.h>

using namespace std;

class Running_Average
{
public:
    double sum = 0;
    double average = 0;
    int i;

    double Average(void); // Member functions declaration
    void AddSample(double);
    Running_Average(int);
};


Running_Average::Running_Average(int size) {
    int buffersize = size;
    double buffer[buffersize] = { 0 };
}

void Running_Average::AddSample(double val)  //Add new values to buffer
{
    for (i = 9; i>0; i--)
    {
        buffer[i] = buffer[i-1];
    }
    buffer[0] = val;
}

double Running_Average::Average(void) //Calculate Average of current values in buffer
{
    for (i = 0; i<buffersize; i++)
    {
        cout << buffer[i] << endl;
        sum += buffer[i];
    }
    average = sum / buffersize;
    sum = 0; 
    return average;
}

int main()
{
    double value;
    int i;
    int f = 0;
    Running_Average test;

    for (i = (40); i < (50); i++)
    {
        test.AddSample(i);
    }

    while (1) 
    {
        i = rand() % 100;
        test.AddSample(i);
        value = test.Average();
        cout << endl;
        cout << value << endl;
        cout << endl; 
        Sleep(1000);
    }

}

然而,构造函数让我很伤心:

Running_Average::Running_Average(int size) {
    int buffersize = size;
    double buffer[buffersize] = { 0 };
}

具体来说:

buffer[buffersize]

在 visual studio 中抛出一个错误说:

expression must have a constant size.

我希望用户通过将其值传递给 constructor.

来指定在创建新对象时要使用的缓冲区大小

如何在不抛出错误的情况下完成这项工作?

感谢您的帮助!

编辑: 已解决!谢谢大家的协助!我设法通过使用 std::vector 来定义可变大小的数组来使函数正常工作。

标准 C++ 没有可变长度数组。 (Why?) The size of an array must be a constant expression. Some compilers have non-standard extensions that allow VLAs, but you shouldn't rely on them. Use std::vector 当您需要一个长度可变且可以调整大小的数组时。

有很多方法可以做到这一点。从好到坏,我想到的是:

1 使用 std::vector

int buffersize = size;
std::vector<double> buffer(buffersize);

2 内置唯一指针或共享指针(取决于用途)

int buffersize = size;
auto buffer = make_unique<double[]>(buffersize) // C++14

int buffersize = size;
auto buffer = make_shared<double[]>(buffersize) // C++14

3 手动分配

int buffersize = size;
double *buffer = new double[buffersize];

// delete [] buffer, must be called later

4 在堆栈上分配 建议,并且取决于平台)

int buffersize = size;
double *buffer = alloca(buffersize * sizeof(*buffer));

请注意,在所有这些情况下,您都可以像数组一样索引缓冲区。

expression must have a constant size
double buffer[buffersize] = { 0 };

第一个buffersize 不是constexpr。它是一个在运行时改变的变量。

根据 array 的标准定义指定数组大小时:

The constant expression specifies the bound of (number of elements in) the array. If the value of the constant expression is N, the array has N elements numbered 0 to N-1,

包含 5 个 double 类型元素的数组声明示例应如下所示:

double buffer[5]; // 5 is a literal

constexpr int size = 5;
double buffer[size];    // size is constexpr

其次buffer是变长数组(VLA)。
VLA 作为扩展在某些编译器中得到部分支持。

How can I make this work without it throwing an error?

如果你需要长度可变,使用std::vector并在你的构造函数中初始化它:

class Running_Average {
    Running_Average(int size): buffer(size, 0) {}
    std::vector<double> buffer;
}

根据您使用 buffer 的方式,我建议使用 std::list<double>

将此添加到 Running_Average 的开头:

class Running_Average
{
    private:
        list<double> buffer;

        const size_t MaxBufferSize;

    public:
        ...

构造函数:

Running_Average::Running_Average(size_t size)
:   MaxBufferSize(size)
{
}

AddSample()Average() :

void Running_Average::AddSample(double val)
{
    if (buffer.size() == MaxBufferSize)
    {
        buffer.pop_back();
    }
    buffer.push_front(val);
}

double Running_Average::Average()
{
    double sum = 0;
    for (auto a : buffer)
    {
        cout << a << endl;
        sum += a;
    }
    return sum / buffer.size();
}

我还会删除 sumaveragei 成员变量,而是在使用它们的地方声明它们(如果需要)。

可变长度数组在 C 中有效,但在 C++ 中无效。在 C++ 中,您最好使用 vector 集合,因为它可以让您更好地表示意图,改变数组大小,而不必单独维护当前大小。

以下完整程序为您提供了工作基准,包括测试工具代码:

#include <iostream>
#include <vector>

class RunningValues {
public:
    RunningValues(size_t size = 50);
    void Add(double val);
    double Sum();
    double Average();

private:
    std::vector<double> dataBuffer;
    size_t sizeLimit;
    double sum;
};


// Constructor: store limit and zero sum (vector is already empty).

RunningValues::RunningValues(size_t size): sizeLimit(size), sum(0.0) {}

// Add a sample.

void RunningValues::Add(double val) {
    // Zero size, disallow adds.

    if (sizeLimit == 0) return;

    // If we would exceed limit, remove earliest.

    if (dataBuffer.size() == sizeLimit) {
        sum -= dataBuffer[0];
        dataBuffer.erase(dataBuffer.begin());
    }

    // Add value to end.

    sum += val;
    dataBuffer.push_back(val);
}

// Get the average (zero if nothing yet added) or sum.

double RunningValues::Average() {
    if (dataBuffer.size() == 0) return 0.0;
    return sum / dataBuffer.size();
}

double RunningValues::Sum() {
    return sum;
}

// Test harness.

int main() {
    RunningValues test(10);

    std::cout << "Ave = " << test.Average() << ", sum = " << test.Sum() << '\n';

    for (int i = 40; i < 50; ++i)
    {
        test.Add(i);
        std:: cout << "Add " << i << ", ave = " << test.Average() << ", sum=" << test.Sum() << '\n';
    }

    for (int i = 0; i < 20; ++i)
    {
        int val = rand() % 100;
        test.Add(val);
        std:: cout << "Add " << val << ", ave = " << test.Average() << ", sum=" << test.Sum() << '\n';
    }
}

示例 运行,显示了各个点的平均值和总和,如下所示:

Ave = 0, sum = 0
Add 40, ave = 40, sum=40
Add 41, ave = 40.5, sum=81
Add 42, ave = 41, sum=123
Add 43, ave = 41.5, sum=166
Add 44, ave = 42, sum=210
Add 45, ave = 42.5, sum=255
Add 46, ave = 43, sum=301
Add 47, ave = 43.5, sum=348
Add 48, ave = 44, sum=396
Add 49, ave = 44.5, sum=445
Add 83, ave = 48.8, sum=488
Add 86, ave = 53.3, sum=533
Add 77, ave = 56.8, sum=568
Add 15, ave = 54, sum=540
Add 93, ave = 58.9, sum=589
Add 35, ave = 57.9, sum=579
Add 86, ave = 61.9, sum=619
Add 92, ave = 66.4, sum=664
Add 49, ave = 66.5, sum=665
Add 21, ave = 63.7, sum=637
Add 62, ave = 61.6, sum=616
Add 27, ave = 55.7, sum=557
Add 90, ave = 57, sum=570
Add 59, ave = 61.4, sum=614
Add 63, ave = 58.4, sum=584
Add 26, ave = 57.5, sum=575
Add 40, ave = 52.9, sum=529
Add 26, ave = 46.3, sum=463
Add 72, ave = 48.6, sum=486
Add 36, ave = 50.1, sum=501

如果您希望有一个避免 vector 的解决方案(当向量大小从 0 到 N然后停留在那里),你可以只使用堆上的裸数组作为循环缓冲区。

该代码略有变化(没有 main,因为它没有改变):

#include <iostream>

class RunningValues {
public:
    RunningValues(size_t size = 50);
    ~RunningValues();
    void Add(double val);
    double Sum();
    double Average();

private:
    size_t count, next, limit;
    double sum, *data;
};

RunningValues::RunningValues(size_t size)
    : count(0), next(0), limit(size)
    , sum(0.0), data(new double[size]) {}

RunningValues::~RunningValues() {
    delete[] data;
}

void RunningValues::Add(double val) {
    // Zero size, disallow adds.

    if (limit == 0) return;

    // If we would exceed limit, remove earliest.

    if (count == limit) {
        sum -= data[next];
        --count;
    }

    // Add value to end.

    data[next] = val;
    sum += val;
    ++count;
    next = (next + 1) % limit;

}

// Get the average (zero if nothing yet added) or sum.

double RunningValues::Average() {
    if (count == 0) return 0.0;
    return sum / count;
}

double RunningValues::Sum() {
    return sum;
}

基于矢量的解决方案的变化相当小:

  • 构造函数不再有向量(很明显)。相反,它有一个固定大小的数组用作循环缓冲区,以及 countnext 变量来管理它。
  • 您现在需要一个析构函数来清理缓冲区(之前,vector 会自行管理)。
  • 项目的添加现在使用 countnext(而不是矢量)来计算如何调整总和并保持相关数据的统计。
  • 平均值的计算现在使用 count 而不是矢量大小。

除此之外,它实际上与上面基于向量的代码非常相似。