Массивы переменной длины: как создать буфер с переменным размером в C ++ - PullRequest
0 голосов
/ 29 мая 2018

Я сейчас пишу класс скользящей средней.

Цель состоит в том, чтобы иметь возможность указать размер буфера как часть конструктора при создании нового объекта класса 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, говоря:

*Выражение 1013 *

должно иметь постоянный размер.

Я хочу, чтобы пользователь указал размер буфера, с которым он хочет работать при создании нового объекта, передав его значение в constructor.

Как я могу заставить эту работу работать без ошибки?

Спасибо за помощь!

РЕДАКТИРОВАТЬ: РЕШЕНО!Спасибо всем за вашу помощь!Мне удалось заставить функцию работать, используя std :: vector для определения массива переменного размера.

Ответы [ 5 ]

0 голосов
/ 29 мая 2018

Массивы переменной длины допустимы в 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;
}

Изменения отОснованное на векторе решение довольно незначительно:

  • Конструктор больше не имеет вектора (очевидно).Вместо этого он имеет массив фиксированного размера для использования в качестве кольцевого буфера, а также переменные count и next для управления им.
  • Теперь вам нужен деструктор для очистки буфера (перед векторомуправлял собой).
  • при добавлении элементов теперь используются count и next (а не вектор), чтобы выяснить, как настроить сумму и вести учет соответствующих данных.
  • В вычислениях среднего теперь используется count, а не векторный размер.

Кроме этого, он на самом деле очень похож на приведенный выше векторный код.

0 голосов
/ 29 мая 2018

Исходя из того, как вы используете 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();
}

Я бы также удалил переменные-члены sum, average и i и вместо этого объявил бы их гдеони используются (при необходимости).

0 голосов
/ 29 мая 2018

Есть много способов сделать это.Те, которые приходят на ум от лучшего к худшему:

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));

Обратите внимание, что во всех этих случаях вы можете индексировать буфер точно так же, как массив.

0 голосов
/ 29 мая 2018

выражение должно иметь постоянный размер
double buffer[buffersize] = { 0 };

Сначала , buffersize не является constexpr.Это переменная, которая изменяется во время выполнения.

При указании размера для массива в соответствии со стандартным определением array:

constant expression задает границу (числоэлементы в) массив.Если значение constant expression равно N, массив имеет N элементов с номерами от 0 до N-1,

Пример объявления массива с 5 элементами типа double должен выглядеть следующим образом:

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

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

Second , buffer - массив переменной длины (VLA).
VLA частично поддерживаются в некоторых компиляторах как расширение.

Как я могу заставить эту работу работать без ошибок?

Если вам нужна переменная длина, используйте std::vector и инициализируйте ее в конструкторе:

class Running_Average {
    Running_Average(int size): buffer(size, 0) {}
    std::vector<double> buffer;
}
0 голосов
/ 29 мая 2018

Стандартный C ++ не имеет массивов переменной длины.( Почему? ) Размер массива должен быть константным выражением.Некоторые компиляторы имеют нестандартные расширения, которые допускают VLA, но вы не должны полагаться на них.Используйте std::vector, когда вам нужен массив, который может иметь переменную длину и может быть изменен.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...