2016-12-28 6 views
2

У меня есть этот матричный класс, который имеет 2d двойной массив. В конструкторе вы можете указать ширину и высоту. Я хочу создать 1d-массив вместо 2d, когда ширина равна 1. Потому что я перегрузил оператор [] и вернул указатель. Если есть только 1 строка/столбец, я не хочу всегда писать [i] [0]. Вместо этого я хочу просто написать [i]. Кто-нибудь знает, как это решить?Изменение размеров массива во время выполнения

Редактировать: Чтобы уточнить это, мне нужен этот класс для матричных вычислений не просто как массив.

+1

Если размер вашей матрицы определяется во время выполнения, то компилятор не знает, имеет ли ширина 1 или нет. Одна из возможностей состоит в том, чтобы иметь для этого случая другой класс. –

+2

Вам понадобится создать второй класс, представляющий массив 1D. Еще лучше, используйте 'std :: vector > 'для представления 2D-массива - не вмешиваться в управление размерами массива, распределением памяти, или пытается перегрузить (как вы) 'operator []()' странным образом. – Peter

+0

Нужно ли вам динамически изменять размер вашей матрицы? Если нет, вы можете взять строки и столбцы в качестве параметров шаблона, что позволит вам специализироваться в случае, если либо '1'. –

ответ

0

Я бы рекомендовал использовать одномерный массив, а user2079303 suggested in their answer. Однако, если это нежелательно, вы также можете реализовать свою идею с помощью шаблонов или полиморфизма.


Если вам не нужно, чтобы быть в состоянии изменить его размеры на лету, вы могли бы сделать вашу матрицу шаблонного класса, используя свои размеры в качестве параметров шаблона. Это, в свою очередь, позволяет использовать размеры для логики времени компиляции с помощью SFINAE, что позволяет перегрузить operator[]() на основе размеров матрицы.

Обратите внимание, что это не на самом деле создать 1D массив, когда Rows == 1 или Cols == 1, он просто имитирует один; если вы явно нуждаетесь в этом поведении, вместо технически различного, но в основном эквивалентного поведения, вы захотите изучить специализацию класса, когда один или оба параметра равны 1. Это, скорее всего, будет дублировать хотя бы часть кода, так что это будет немного грязно.

// This example uses std::array for the actual array, since it's compile-time anyways. 
// This, in turn, lets it declare a lot of functions constexpr. 
// Const-correctness omitted for brevity. Remember to restore it for actual code. 
template<size_t Rows, size_t Cols> 
class Matrix { 
    std::array<std::array<double, Cols>, Rows> mat; 

    public: 
    // Default constructor. Clang _really_ likes braced initialiser lists. 
    constexpr Matrix() : mat{{{{0}}}} {} 

    // Array constructor. 
    constexpr Matrix(const decltype(mat)& arr) : mat(arr) {} 

    // ----- 

    // Subscript operators. 

    // Generic operator. Matrix<x, y>, where x != 1 && y != 1. 
    // Does what normal subscript operators do. 
    template<bool R = (Rows == 1), bool C = (Cols == 1)> 
    auto& operator[](std::enable_if_t<!R && !C, size_t> i) { 
     return mat[i]; 
    } 

    // Magic operator. Matrix<1, x>, where x != 1. 
    template<bool R = (Rows == 1), bool C = (Cols == 1)> 
    auto& operator[](std::enable_if_t<(R && !C), size_t> i) { 
     return mat[0][i]; 
    } 

    // Magic operator. Matrix<x, 1>, where x != 1. 
    template<bool R = (Rows == 1), bool C = (Cols == 1)> 
    auto& operator[](std::enable_if_t<C && !R, size_t> i) { 
     return mat[i][0]; 
    } 

    // Scalar matrix operator. Matrix<1, 1>. 
    // Just returns mat[0][0], for simplicity's sake. Might want to make it do something 
    // more complex in your actual class. 
    template<bool R = (Rows == 1), bool C = (Cols == 1)> 
    auto& operator[](std::enable_if_t<R && C, size_t> i) { 
     return mat[0][0]; 
    } 

    // ----- 

    // A few interface helpers. 

    // Simple begin() & end(), for example's sake. 
    // A better version would begin at mat[0][0] and end at mat[Rows - 1][Cols - 1]. 
    constexpr auto begin() const { return mat.begin(); } 
    constexpr auto end() const { return mat.end(); } 

    // Generic helpers. 
    constexpr size_t size() const { return mat.size() * mat[0].size(); } 
    constexpr size_t rows() const { return mat.size(); } 
    constexpr size_t cols() const { return mat[0].size(); } 

    // 1D Matrix helpers. 
    constexpr bool is_one_d() const { return (Rows == 1) || (Cols == 1); } 
    constexpr bool  one_row() const { return Rows == 1; } 
    constexpr size_t dimension() const { return (one_row() ? cols() : rows()); } 

    // ----- 

    // Output. 
    // Would need modification if better begin() & end() are implemented. 
    friend std::ostream& operator<<(std::ostream& str, const Matrix<Rows, Cols>& m) { 
     for (auto& row : m) { 
      for (auto& elem : row) { 
       str << std::setw(6) << elem << ' '; 
      } 
      str << '\n'; 
     } 
     str << std::endl; 
     return str; 
    } 
}; 

Он может быть использован в качестве ...

// Get rid of any "Waah, you didn't use that!" warnings. 
// See https://stackoverflow.com/a/31654792/5386374 
#define UNUSED(x) [&x]{}() 

// This should really use if constexpr, but online compilers don't really support it yet. 
// Instead, have an SFINAE dummy. 
template<size_t Rows, size_t Cols> 
void fill(Matrix<Rows, Cols>& m, std::enable_if_t<(Rows == 1) || (Cols == 1), int> dummy = 0) { 
    UNUSED(dummy); 

    //for (size_t i = 0; i < (m.one_row() ? m.cols() : m.rows()); i++) { 
    for (size_t i = 0; i < m.dimension(); i++) { 
     m[i] = (i ? i : 0.5) * (i ? i : 0.5); 
    } 
} 

template<size_t Rows, size_t Cols> 
void fill(Matrix<Rows, Cols>& m, std::enable_if_t<!((Rows == 1) || (Cols == 1)), int> dummy = 0) { 
    UNUSED(dummy); 

    for (size_t i = 0; i < m.rows(); i++) { 
     for (size_t j = 0; j < m.cols(); j++) { 
      m[i][j] = (i ? i : 0.5) * (j ? j : 0.5) + (i >= j ? 0.1 : -0.2); 
     } 
    } 
} 

в действии here.


Если вы сделать должны быть в состоянии изменить его размеры на лету, это становится все более сложным. Самое простое решение, вероятно, было бы использовать полиморфизм и вернуть operator[].

class Matrix { 
    protected: 
    // Out proxy class. 
    // Visible to children, for implementing. 
    struct SubscriptProxy { 
     virtual operator double&() = 0; 
     virtual operator double*() = 0; 
     virtual double& operator=(double) = 0; 
     virtual double& operator[](size_t) = 0; 
     virtual ~SubscriptProxy() = default; 
    }; 

    public: 
    virtual SubscriptProxy& operator[](size_t i) = 0; 
    virtual ~Matrix() = default; 

    virtual void out(std::ostream& str) const = 0; 
    friend std::ostream& operator<<(std::ostream& str, const Matrix& m) { 
     m.out(str); 
     return str; 
    } 
}; 
std::ostream& operator<<(std::ostream& str, const Matrix& m); 

Вы можете иметь каждый класс сделать членов прокси, которые необходимы public, и те, что не private; private получают нефункциональную фиктивную реализацию.

// Resizing omitted for brevity. 
class OneDMatrix : public Matrix { 
    double arr[5]; 

    // Proxy for single element. 
    class OneDProxy : public SubscriptProxy { 
     double& elem; 

     operator double*() override { return &elem; } 
     double& operator[](size_t) override { return elem; } 
     public: 
     OneDProxy(double& e) : elem(e) {} 

     operator double&() override { return elem; } 
     double& operator=(double d) override { 
      elem = d; 
      return elem; 
     } 
    }; 

    public: 
    OneDMatrix() : arr{0} {} 

    // operator[] maintains a static pointer, to keep the return value alive and guarantee 
    // proper cleanup. 
    SubscriptProxy& operator[](size_t i) override { 
     static OneDProxy* ret = nullptr; 

     if (ret) { delete ret; } 
     ret = new OneDProxy(arr[i]); 
     return *ret; 
    } 

    void out(std::ostream& str) const override { 
     for (size_t i = 0; i < 5; i++) { 
      str << std::setw(4) << arr[i] << ' '; 
     } 
     str << std::endl; 
    } 
}; 

// Resizing omitted for brevity. 
class TwoDMatrix : public Matrix { 
    double arr[3][4]; 

    // Proxy for array. 
    class TwoDProxy : public SubscriptProxy { 
     double* elem; 

     operator double&() override { return elem[0]; } 
     double& operator=(double) override { return elem[0]; } 
     public: 
     TwoDProxy(double* e) : elem(e) {} 
     operator double*() override { return elem; } 
     double& operator[](size_t i) override { return elem[i]; } 
    }; 

    public: 
    TwoDMatrix() : arr{{0}} {} 

    // operator[] maintains a static pointer, to keep the return value alive and guarantee 
    // proper cleanup. 
    SubscriptProxy& operator[](size_t i) override { 
     static TwoDProxy* ret = nullptr; 

     if (ret) { delete ret; } 
     ret = new TwoDProxy(arr[i]); 
     return *ret; 
    } 

    void out(std::ostream& str) const override { 
     for (size_t i = 0; i < 3; i++) { 
      for (size_t j = 0; j < 4; j++) { 
       str << std::setw(4) << arr[i][j] << ' '; 
      } 
      str << '\n'; 
     } 
    } 
}; 

Благодаря ссылка является пригодной для полиморфизма, Matrix может быть использован в качестве интерфейса, позволяя все, что конкретно не требует один из реальных классов принять Matrix&.

Просмотреть в действии here.

С такой установкой становится легко динамически создавать правильный тип матрицы, предоставляя вспомогательную функцию.

// Assume OneDMatrix is expanded for dynamic size specification. 
    // It now has constructor OneDMatrix(size_t sz), and owns a dynamic double[sz]. 
// Assume TwoDMatrix is expanded for dynamic size specification. 
    // It now has constructor TwoDMatrix(size_t r, size_t c), and owns a dynamic double[r][c]. 
Matrix* createMatrix(size_t rows, size_t cols) { 
    if (rows == 1)  { return new OneDMatrix(cols);  } 
    else if (cols == 1) { return new OneDMatrix(rows);  } 
    else    { return new TwoDMatrix(rows, cols); } 
} 

Как обычно, версия шаблона более многословным, но безопаснее (из-за не имея играть с указателями) и, вероятно, более эффективным (из-за не нуждаясь сделать столько, сколько динамическое выделение & Deallocation , Я не проверял это, однако).

-1

Чтобы создать 2D динамический массив, вам нужно использовать некоторые концепции структур данных, и я считаю, что вы не знакомы с ними. До этого позвольте мне показать вам, как сделать 1D динамический массив.

int main() 
{ 
    int size; 

    std::cin >> size; 

    int *array = new int[size]; 

    delete [] array; 

    return 0; 
} 

Не забудьте удалить каждый массив, который вы назначаете новым.

Вернемся к 2D динамическим массивам. Эти массивы можно суммировать как хеш-таблицу, которая является общим типом структуры данных. Теперь этот человек объяснил 2D-массивы намного лучше меня How to create dynamic 2D array перейдите по ссылке.

+1

Вы можете * хотя бы * использовать 'unique_ptr'. И это не отвечает на вопрос ОП. –

2

Вы можете обернуть два альтернативных типа в тип варианта (помеченный союз).

Однако вы не можете использовать operator[] для доступа к обоим вариантам, так как тип возврата будет отличаться для каждого. Один вернет ссылку на подмассиву, а другой вернет двойной. У вас не может быть двух перегрузок, которые отличаются только их возвращаемым типом.

Вместо этого вы можете использовать перегруженные функции. Например, double at(size_type x) и double at(size_type x, size_type y).


Однако, поскольку вы представляете матрицу, может быть проще использовать 1D массив представляют собой матрицу произвольного ранга, заложив более высокие размеры плоской последовательно, как многомерные массивы хранятся в памяти (в конце концов, память одномерна). Это позволяет указывать ширину каждого измерения во время выполнения и избегать сложностей типа варианта.

0

Хорошим решением было бы использовать std::variant, или boost::variant, если у вас нет компилятора C++ 17. Я бы создал контейнер, который бы облегчил его использование.

template<typename T> 
struct DynamicDimension { 
    std::variant<T, std::vector<T>> element; 

    // accessors, is_vector and is_element function. 
    // Maybe operator[] and push_back and a get function. 
    // Add begin and end to make your class useable with range for loops. 
}; 
0

Этот вопрос действительно интересует меня, и в моем оригинальный ответ я заявил, что требуемая операция не представлялось возможным, так как в C++ вы не можете перегружать функции, которые отличаются только их тип возвращаемого значения.

Но что, если бы вы могли?

Мне стало любопытно, поэтому я потратил немало времени на изучение и игру с C++, чтобы увидеть, могу ли я на самом деле заставить одну функцию возвращать несколько типов ... И ответ «да», но это способ, который обычно не используется, потому что он использует void *, что затрудняет жизнь.

У этого, однако, есть добавленная черта для написания меньшего количества строк кода, даже если это труднее понять.

Это сказало, что я узнал что-то из опыта, поэтому я решил поделиться с вами.

Итак, существует несколько способов решить эту проблему с меньшим количеством кода. В классе матрицы есть два указателя, один a T * matrix, а другой a T ** matrix2d, и используйте void * в качестве возвращаемого типа для вашего перегруженного оператора [].

#ifndef matrix_h 
#define matrix_h 

template <typename T> 
class Matrix { 
private: 
    int width; 
    int height; 
    int size; 
    T * matrix; 
    T ** matrix2d; 

public: 
    Matrix(const int w, const int h): width(w), height(h){ 
     if(w==1){ 
      matrix = new T[h]; 
      size = h; 
     } else if (h==1){ 
      matrix = new T[w]; 
      size = w; 
     } else { 
      matrix2d = new T*[h]; 
      for(int i=0;i<h;++i){ 
       matrix2d[i] = new T[w]; 
      } 
      size = w*h; 
     } 
    } 

    ~Matrix() { 
     if(width==1 || height==1){ 
      delete [] matrix; 
     } else { 
      for(int i=0;i<height;++i){ 
       T * _r = matrix2d[i]; 
       delete [] _r; 
      } 
      delete [] matrix2d; 
     } 
    } 

    void * operator[](const int i){ 
     if(width==1 || height==1){ 
      return & matrix[i]; 
     } else { 
      return & (*matrix2d[i]); 
     } 
    } 

    const int getSize(){ 
     return size; 
    } 
}; 

#endif /* matrix_h */ 

В main, сделайте это демо:

#include <iostream> 
#include "Matrix.h" 

int main() { 
    //Give the type so the correct size_t is allocated in memory. 
    Matrix <double> matrix1(1, 3); 
    Matrix <double> matrix2(2,2); 

    //Now have an array of void pointers. 
    std::cout << "1 dimensional" << std::endl; 
    for(int i=0;i<matrix1.getSize();++i){ 
     std::cout << matrix1[i] << std::endl; 
    } 

    //Cast the void *, then dereference it to store values. 
    *((double*)matrix1[0]) = 77; 
    *((double*)matrix1[1]) = 31; 
    *((double*)matrix1[2]) = 24.1; 

    for(int i=0;i<matrix1.getSize();++i){ 
     std::cout << *((double *)matrix1[i]) << std::endl; 
    } 

    std::cout << "2 dimensional addresses." << std::endl; 
    for(int i=0;i<2;++i){ 
     double * _row = (double*)matrix2[i]; 
     for(int j=0;j<2;++j){ 
      std::cout << &_row[j] << " "; 
     } 
     std::cout << std::endl; 
    } 

    std::cout << "2 dimensional assignment and display." << std::endl; 
    double num = 13.1; 
    for(int i=0;i<2;++i){ 
     double * _row = (double*)matrix2[i]; 
     for(int j=0;j<2;++j){ 
      _row[j] = num; 
      num += 0.13; 
      std::cout << _row[j] << " "; 
     } 
     std::cout << std::endl; 
    } 

    return 0; 
} 
Смежные вопросы