In the table, rows and columns, in theory, should not store information. They are just a navigation tool. The data must be stored in a different entity called a cell.
The table is a specialization of the matrix, which differs from the latter only by the presence of horizontal and vertical headers, so the implementation of the code I propose to start with the matrix.
template<class T> class Matrix { public: explicit Matrix() : _rows(0), _cols(0) {} explicit Matrix(int rows, int cols) : _rows(rows), _cols(cols), _cells(rows*cols) {} virtual ~Matrix() {} int rows() const {return _rows;} int cols() const {return _cols;} void insertRows(int row, int cnt) { if(_rows >= row && row >= 0 && cnt > 0) { if(_cols == 0) _cols = 1; _cells.insert(_cols * row, _cols * cnt, T()); _rows += cnt; } } void removeRows(int row, int cnt) { if(_rows >= (row+cnt) && row >= 0 && cnt > 0) { _cells.remove(_cols * row, _cols * cnt); _rows -= cnt; if(_rows == 0) _cols = 0; } } void insertCols(int col, int cnt) { if(_cols >= col && col >= 0 && cnt > 0) { if(_rows == 0) _rows = 1; for(int row = 0; row < _rows; ++row) _cells.insert(pos(row,col)+(row*cnt), cnt, T()); _cols += cnt; } } void removeCols(int col, int cnt) { if(_cols >= (col+cnt) && col >= 0 && cnt > 0) { for(int row = 0; row < _rows; ++row) _cells.remove(pos(row,col)-(row*cnt), cnt); _cols -= cnt; if(_cols == 0) _rows = 0; } } const T &data(int row, int col) const { return _cells[pos(row,col)]; } T &data(int row, int col) { return _cells[pos(row,col)]; } void clear() {_rows = 0; _cols = 0; _cells.clear();} private: int _rows, _cols; QVector<T> _cells; int pos(int row, int col) const { return _cols * row + col; } };
Someone prefers to store cells in a two-dimensional array, but I gave the option using a regular vector. Despite the greater difficulty in adding / deleting rows / columns compared to the two-dimensional option, this one is no less interesting.
Since the table, as already stated, is a specialization of the matrix, the corresponding class can be easily inherited from the previous one with the addition of functionality for headings.
template<class T> class Table : public Matrix<T> { public: explicit Table() : Matrix<T>(1,1) {} explicit Table(int rows, int cols) : Matrix<T>(rows+1,cols+1) {} virtual ~Table() {} int rows() const {return Matrix<T>::rows()-1;} int cols() const {return Matrix<T>::cols()-1;} void insertRows(int row, int cnt) { Matrix<T>::insertRows(row+1, cnt); } void removeRows(int row, int cnt) { Matrix<T>::removeRows(row+1, cnt); } void insertCols(int col, int cnt) { Matrix<T>::insertCols(col+1, cnt); } void removeCols(int col, int cnt) { Matrix<T>::removeCols(col+1, cnt); } const T &headerRowData(int row) const { return Matrix<T>::data(row+1, 0); } T &headerRowData(int row) { return Matrix<T>::data(row+1, 0); } const T &headerColData(int col) const { return Matrix<T>::data(0, col+1); } T &headerColData(int col) { return Matrix<T>::data(0, col+1); } const T &data(int row, int col) const { return AMatrix<T>::data(row+1, col+1); } T &data(int row, int col) { return Matrix<T>::data(row+1, col+1); } };
As is well seen, the class of the table is not engaged in anything other than adjusting the indexes of rows and columns when referring to the methods of the matrix in order to be able to store information about the headers in its first row and in its first column.