Il concetto alla base di std::vector
è non quello di un vettore matematico, ma quello di una matrice di dimensioni dinamiche.
Aggiungendo un'operazione aritmetica ad esso, rende quelle operazioni disponibili a qualunque codice nel tuo sviluppo può usare std :: vector per fare altro che le operazioni di algebra lineare.
Per questo motivo, se hai bisogno di un vettore matematico, è meglio definire un nuovo tipo indipendente.
Ovviamente niente può impedirti di usare std::vector
al suo interno per contenere i valori.
Se le operazioni devono essere membri o la funzione libera applicata a quel tipo ... è più una questione di gusti che altro: a.add(b)
e add(a,b)
hanno esattamente la stessa potenza semantica.
Personalmente preferisco il secondo, poiché mette sia a
che b
allo stesso livello, o anche operator+(a,b)
(in modo che tu possa scrivere a+b
)
Quando implementi la matrice, tuttavia, pensa un po 'prima di implementarli come vector of vectors
per vari motivi:
- se i vettori sono implementati a "dinamicamente considerevole" un vetore di vettori avrà tutte le righe indipendentemente considerevoli e memorizzate a vicenda separatamente con tutta la struttura (di solito tre puntatori) per seguirle. Per le matrici piccole, i vettori (come lo spazio 3d-proiettivo di 4 componenti) sono più sovradimensionati dei dati.
- Un vettore dinamico di vettori dinamici privilegia l'accesso per righe (o per colonne), ma una matrice deve essere in grado di invertire facilmente il proprio ruolo (cosa al prodotto di matrice
Cij = Aik*Bkj
)
- Le matrici sono esse stesse "spazi vettoriali" contro + - e scalare * e /.
È molto meglio pensare alle matrici come tipi indipendenti, implementato come un vettore semplice, accessibile esternamente con due indici, ma le cui operazioni di base sono implementate in termini di vettore interno.
Probabilmente arriverò a qualcosa del genere:
template<class T>
class vect
{
std::vector<T> m;
public:
explcit vect(size_t n) :m(n) {}
size_t size() const { return n; }
T& operator[] (size_t i) { return m(i); }
const T& operator[] (size_t i) const { return m[i]; }
};
template<class T>
vect<T> operator+(const vect<T>& a, const vect<T>& b)
{
vect<T> z(a.size());
for(size_t i=0; i<a.size(); ++i)
z[i] = a[i]+b[i];
return z;
}
template<class T>
class matrix
{
vect<T> m;
size_t R;
public:
matrix(size_t r, size_t c) :m(r*c), R(r) {}
matrix(const vect<T>& s, size_t r) :m(s), R(r) {}
const vect<T>& as_vect() const { return m; }
size_t rows() const { return R; }
size_t cols() const { return m.size()/R; }
T& operator()(size_t r, size_t c) { return m[r*cols()+c]; }
const T& operator()(size_t r, size_t c) const { return m[r*cols()+c]; }
};
template<class T>
matrix<T> operator+(const matrix<T>& a, const matrix<T>& b)
{ return matrix<T>(a.as_vect()+b.as_vect(),a.rows()); }
Lo stesso può essere fatto sia per matrix che per vect su operator-
, e unary +
e -
, nonché per operator*(vect<T>, T)
e operator*(matrix<T>, T)
(prodotto per scalare) e le controparti commutative operator*(T, vect<T>)
e operator*(T, matrix<T>)
.
Deve quindi essere implementato operator*
int le varianti matrix*matrix
, matrix*vect
, vect*matrix
.
Esistono altre tecniche più sofisticate per evitare la ripetizione di loop e le variabili locali all'interno delle funzioni (come la costruzione tramite lambda e i proxy vettoriali che formano espressioni di template da utilizzare per ottenere sezioni vettoriali come viste trasposte matrice e così via. .. ma potrebbe essere troppo lontano per quello che stai facendo)