Я бы рекомендовал использовать одномерный массив, а 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 или нет. Одна из возможностей состоит в том, чтобы иметь для этого случая другой класс. –
Вам понадобится создать второй класс, представляющий массив 1D. Еще лучше, используйте 'std :: vector> 'для представления 2D-массива - не вмешиваться в управление размерами массива, распределением памяти, или пытается перегрузить (как вы) 'operator []()' странным образом. –
Peter
Нужно ли вам динамически изменять размер вашей матрицы? Если нет, вы можете взять строки и столбцы в качестве параметров шаблона, что позволит вам специализироваться в случае, если либо '1'. –