paulo1205
(usa Ubuntu)
Enviado em 04/06/2018 - 17:41h
O tamanho do objeto tem de ser determinado em tempo de compilação. Assim sendo,
arrays tradicionais exigem que você tenha dimensões fixas.
No seu programa, você tem tamanhos constantes nas declarações que aparecem em
main (). Sendo fixos, você tem mais opções a usar.
Algumas alternativas são:
• Usar
containers (provavelmente
std::vector da biblioteca padrão, ou algum equivalente do Qt, Boost ou do seu
toolkit favorito) para criar
arrays dinâmicos. Entre suas vantagens estão ter um único tipo capaz de representar matrizes de quaisquer tamanhos e permitir que as matrizes sejam redimensionadas. Por outro lado, o diagnóstico de operações irregulares (por exemplo: tentativas de operar com matrizes com tamanhos incompatíveis com a operação solicitada) só tem como acontecer em tempo de execução.
template <class T=double>
class matriz {
vector<vector<T>> _mat;
public:
matriz(size_t lin, size_t col){
if(lin==0 || col==0)
throw invalid_argument("tentativa de criar matriz com dimensão nula");
_mat.resize(lin, vector<T>(col));
}
matriz(size_t n): matriz(n, n) { } // Constroi matriz quadrada reaproveitando construtor retangular.
size_t linhas() const { return _mat.size(); }
size_t colunas() const { return _mat[0].size(); }
// Acesso a elementos usando parênteses (e.g. “mat(lin, col)”).
T &operator()(size_t l, size_t c){ return _mat.at(l).at(c); } // Permite modificar valor (e.g. “m(1,2)=5.”).
T operator()(size_t l, size_t c) const { return _mat.at(l).at(c); } // Para consultar valor de uma matriz constante.
T determinante() const {
if(linhas()!=colunas())
throw logic_error("tentativa de calcular determinante de matriz não-quadrada");
T det;
/* Calcula o determinante e o armazena de ‘det’. */
return det;
}
matriz inversa(){
if(linhas()!=colunas())
throw logic_error("tentativa de inverter matriz não-quadrada");
if(determinante()==0)
throw runtime_error("tentativa de inverter matriz singular");
matriz inv(linhas());
/* Calcula a inversa da matriz, cujos elementos são postos em inv. */
return inv;
}
/* Outras funções-membro. */
};
// Multiplicação de matrizes.
template <class T, class U> matriz<decltype(T(0)*U(0))> operator*(const matriz<T> &m1, const matriz<U> &m2){
if(m1.colunas()!=m2.linhas())
throw invalid_argument("dimensões erradas para multiplicação");
matriz<decltype(T(0)*U(0))> produto(m1.linhas(), m2.colunas());
for(size_t l1=0; l1<m1.linhas(); l1++)
for(size_t c2=0; c2<m2.colunas(); c2++){
produto(l1, c2)=m1(l1, 0)*m2(0, c2);
for(size_t c1l2=1; c1l2<m1.colunas(); c1l2++)
produto(l1, c2)+=m1(l1, c1l2)*m2(c1l2, c2);
}
return produto;
}
int main(){
matriz<> m1(4, 3), m2(3), m3(3, 4);
/* Popula m1, m2 e m3. */
// Operações válidas.
auto m1_m2=m1*m2; // Colunas de m1 == linhas de m2.
auto det_m2=m2.determinante(); // m2 é quadrada, logo tem determinante.
auto m1_m3=m1*m3; // m1_m3 terá dimensão 4×4.
auto m3_m1=m3*m1; // m3_m1 terá dimensão 3×3.
// Operações inválidas (vão compilar, mas garantidamente darão erro de execução).
matriz<> m0(0); // ERRO: matriz com dimensão nula.
auto m2_m1=m2*m1; // ERRO: colunas de m2 != linhas de m1.
auto det_m1=m1.determinante(); // ERRO: m1 não é quadrada.
// Operações que realmente só podem ser avaliadas em tempo de execução.
auto inv_m2=m2.inversa(); // Pode falhar se o determinante for nulo.
}
• Usar
templates com parâmetros numéricos que indicam as dimensões, criando efetivamente tipos distintos para matrizes com dimensões diferentes. É vantajoso para fazer com que o compilador verifique a adequação de certas operações já em tempo de compilação, evitando que alguns erros lógicos apareçam apenas durante a execução. Por outro lado, todas as matrizes teriam de ter dimensões estáticas, e corre-se o risco de que o tamanho de código gerado a partir de
templates ocupe muito espaço, já que cada especialização produz sua própria classe e suas próprias funções. Além disso, embora o compilador possa interceptar erros lógicos, as mensagens mostradas podem não ser muito descritivas ou indicativas de como os corrigir, e ainda haverá casos de erros que só podem ser percebidos em tempo de execução.
template <size_t L, size_t C=L, class T=double>
class matriz {
static_assert(L>0 && C>0, "matriz não pode ter dimensão nula");
T _mat[L][C];
public:
/* Não precisa de construtores para os casos triviais. */
constexpr size_t linhas(){ return L; }
constexpr size_t colunas(){ return C; }
// Acesso a elementos usando parênteses (e.g. “mat(lin, col)”).
T &operator()(size_t l, size_t c){ // Permite modificar valor (e.g. “m(1,2)=5.”).
if(l>=L || c>=C)
throw range_error("índices inválidos");
return _mat[l][c];
}
T operator()(size_t l, size_t c) const { // Para consultar valor de uma matriz constante.
if(l>=L || c>=C)
throw range_error("índices inválidos");
return _mat[l][c];
}
T determinante() const {
static_assert(L==C, "matriz deve ser quadrada para calcular determinante");
T det;
/* Calcula o determinante e o armazena de ‘det’. */
return det;
}
matriz<L, L, T> inversa(){
static_assert(L==C, "matriz deve ser quadrada para poder ser invertida");
if(determinante()==0)
throw runtime_error("tentativa de inverter matriz singular");
matriz<L, L, T> inv;
/* Calcula a inversa da matriz, cujos elementos são postos em inv. */
return inv;
}
/* Outras funções-membro. */
};
// Multiplicação de matrizes.
template <size_t L1, size_t C1L2, size_t C2, class T, class U>
matriz<L1, C2, decltype(T(0)*U(0))> operator*(const matriz<L1, C1L2, T> &m1, const matriz<C1L2, C2, U> &m2){
matriz<L1, C2, decltype(T(0)*U(0))> produto;
for(size_t l1=0; l1<L1; l1++)
for(size_t c2=0; c2<C2; c2++){
produto(l1, c2)=m1(l1, 0)*m2(0, c2);
for(size_t c1l2=1; c1l2<C1L2; c1l2++)
produto(l1, c2)+=m1(l1, c1l2)*m2(c1l2, c2);
}
return produto;
}
int main(){
matriz<4, 3> m1; // 4×3
matriz<3> m2; // 3×3
matriz<3,4> m3; // 3×4
/* Popula m1, m2 e m3. */
// Operações válidas.
auto m1_m2=m1*m2;
auto det_m2=m2.determinante();
auto m1_m3=m1*m3; // m1_m3 terá tipo matriz<4, 4, double>.
auto m3_m1=m3*m1; // m3_m1 terá tipo matriz<3, 3, double>.
// Operações inválidas (vão disparar erros durante a compilação).
matriz<0> m0; // vai violar static_assert da declaração da classe.
auto m2_m1=m2*m1; // não vai encontrar operator*() compatível.
auto det_m1=m1.determinante(); // vai violar static_assert em matriz<4, 3>::determinante().
// Operações que realmente só podem ser avaliadas em tempo de execução.
auto inv_m2=m2.inversa(); // Pode falhar se o determinante for nulo.
}
Entretanto, se você tivesse algo completamente variável, apenas a primeira das alternativas acima seria válida.
int main(){
size_t x, y;
cin >> x >> y;
matriz<> m(x, y); // Versão do primeiro dos meus exemplos. OK.
// Se se tentasse usar o segundo exemplo, com algo como “matriz<x, y>”, o compilador não permitiria,
// pois x e y não são constantes conhecidas em tempo de compilação.
}