Racional
.explicit
.
Na última aula fizemos alguns aperfeiçoamentos no TAD Racional
: acrescentámos o operador
++
prefixo e transformámos
a função membro somaCom()
numa sobrecarga do operador +
:
#include <iostream>
#include <cassert>
using namespace std;
/**
Devolve o máximo divisor comum dos inteiros passados como argumento.
@pre
m
<> 0 oun
<> 0.
@post
mdc
= mdc(m
,n
).*/
int mdc(int const m, int const n)
...
{
}
/**
Representa números racionais.@invariant 0 <
denominador
e mdc(numerador
,denominador
) = 1.*/
class Racional {
public:
/**
Constrói racional com valor inteiro.@pre V.
@post
*this
=valor
.*/
Racional(int const valor = 0);
/**
Constrói racional correspondente an
/d
.@pre
d
<> 0.@post
*this
=n
/d
.*/
Racional(int const numerador, int const denominador);
/**
Escreve um racional no ecrã no formato de uma fracção.
@pre V.
@post ¬
cout
.good
() oucout
contémn/d
(ou simplesmente
n
, sed
= 1) em quen
ed
são os valores denumerador
edenominador
.*/
void escreve();
/**
Devolve a soma com o racional recebido como argumento.
@pre V.
@post
operator+
=*this
+r2
.*/
Racional operator+(Racional const& r2);
/**
Lê do teclado o racional, na forma de dois inteiros sucessivos.
@pre
*this
= r.
@post Se
cin
.good
() ecin
tem dois inteiros n' e d' disponíveis para
leitura, com d' <> 0,
então
*this
= n'/d',
senão
*this
= r e¬cin
.good
().*/
void lê();
/**
Incrementa o racional.@pre
*this
= r.
@post
operator++
idêntico a*this
e*this
= r + 1. */
Racional& operator++();
private:
int numerador;
int denominador;
/**
Reduz a fracção que representa o racional.
@pre
denominador
<> 0 e*this
= r.
@post
denominador
<> 0 e mdc(numerador
,denominador
) = 1 e
*this
= r.*/
void reduz();
/**
Indica se a CIC se verifica.@pre V.
@post
cumpreInvariante
= 0 <denominador
e mdc(numerador
,denominador
) = 1.*/
bool cumpreInvariante();
};
Racional::Racional(int const valor)
: numerador(valor), denominador(1)
...
{
}
Racional::Racional(int const numerador, int const denominador)
...
{
}
void Racional::escreve()
{
...
}
Racional Racional::operator+(Racional const& r2)
{
assert(cumpreInvariante() and r2.cumpreInvariante());
Racional r;
r.numerador = numerador * r2.denominador +
r2.numerador * denominador;
r.denominador = denominador * r2.denominador;
r.reduz();
assert(cumpreInvariante() and r.cumpreInvariante());
return r;
}
void Racional::lê()
{
...
}
Racional& Racional::operator++()
{
assert(cumpreInvariante());
numerador += denominador;
assert(cumpreInvariante());
return *this;
}
void Racional::reduz()
{
...
}
bool Racional::cumpreInvariante()
{
...
}
int main()
{
//
Ler fracções:
cout << "Introduza duas fracções (numerador denominador): ";
Racional r1, r2;
r1.lê();
r2.lê();
if(not cin) {
cout << "Opps! A leitura dos racionais falhou!" << endl;
return 1;
}
//
Calcular racional soma:
Racional r = r1 + r2;
//
Escrever resultado:
cout << "A soma de ";
r1.escreve();
cout << " com ";
r2.escreve();
cout << " é ";
r.escreve();
cout << '.' << endl;
}
Já agora podíamos definir também o operador
--
prefixo:
Quais os operadores interessantes para o TAD? Na aula anterior vimos que eram:...
class Racional {
public:
...
/**
Decrementa o racional.@pre
*this
= r.
@post
operator--
idêntico a*this
e*this
= r - 1. */
Racional& operator--();...
};
...
Racional& Racional::operator--()
{
assert(cumpreInvariante());
numerador -= denominador;
assert(cumpreInvariante());
return *this;
}
Destes, os primeiros candidatos a implementar são os que alteram um dos seus operandos. Seja:
++
,--
sufixo
++
,--
prefixo
*=
/=
+=
-=
,
+-
unários
*
,/
+
,-
binários
<
,<=
,>
,>=
==
!=
Discutir e concluir que os que alteram são:
Racional a(1,2), b(3,5);
a + b; //
alteraa
? eb
?a++; //
alteraa
?
++
, --
(sufixo e
prefixo) e os op=
. Dizer que o operador +
devia ter sido deixado
para o fim. Deixar claro que estes operadores são mal comportados.
São definidos à custas de "coisos", mistos de funções e
procedimentos, ou funções com efeitos laterais.
Agora podemos passar ao operador *=
que, como tem dois operandos, vai
ter um parâmetro (o primeiro operando é a instância
implícita!). Estes operadores também devolvem uma referência
para o primeiro operando, de modo a se poder escrever:
Discutir resultado do código! Explicar que se está a tentar reproduzir a semântica que o operador
Racional a(3), b(1, 2);
(a *= b) *= b;
*=
tem para os tipos
básicos do C++. Não se está a defender que este tipo de código deva
ser escrito!
Passando ao código:
De igual forma para...
class Racional {
public:
...
/**
Multiplica por um racional.@pre
*this
= r.
@post
operator*=
idêntico a*this
e*this
= r ×r2
.*/
...
Racional& operator*=(Racional const& r2);
};
...
Racional& Racional::operator*=(Racional const& r2)
{
assert(cumpreInvariante() and r2.cumpreInvariante());
Discutir código!
numerador *= r2.numerador;
denominador *= r2.denominador;
reduz();
assert(cumpreInvariante());
return *this;
}
...
/=
(com cuidado para não dividir por zero e
com numerador do divisor negativo!), para +=
e para -=
:
...
class Racional {
public:
...
/**
Multiplica por um racional.@pre
*this
= r.
@post
operator*=
idêntico a*this
e*this
= r ×r2
.*/
Racional& operator*=(Racional const& r2);
/**
Divide por um racional.@pre
*this
= r er2
<> 0.
@post
operator/=
idêntico a*this
e*this
= r /r2
.*/
Racional& operator/=(Racional const& r2);
/**
Adiciona de um racional.@pre
*this
= r.
@post
operator+=
idêntico a*this
e*this
= r +r2
.*/
Racional& operator +=(Racional const& r2);
/**
Subtrai de um racional.@pre
*this
= r.
@post
operator-=
idêntico a*this
e*this
= r -r2
.*/
Racional& operator-=(Racional const& r2);...
};
...
Racional& Racional::operator*=(Racional const& r2)
{
assert(cumpreInvariante() and r2.cumpreInvariante());
numerador *= r2.numerador;
denominador *= r2.denominador;
reduz();
assert(cumpreInvariante());
return *this;
}
Racional& Racional::operator/=(Racional const& r2)
{
assert(cumpreInvariante() and r2.cumpreInvariante());
assert(r2 != 0);
Discutir asserção! Só depois se fará o operador
!=
!
Discutir código!
numerador *= r2.denominador;
denominador *= r2.numerador;
reduz();
assert(cumpreInvariante());
return *this;
}
Racional& Racional::operator +=(Racional const& r2)
{
assert(cumpreInvariante() and r2.cumpreInvariante());
numerador = numerador * r2.denominador +
r2.numerador * denominador;denominador *= r2.denominador;
Pode-se trocar a ordem destas atribuições?
reduz();
assert(cumpreInvariante());
return *this;
}
Racional& Racional::operator-=(Racional const& r2)
{
assert(cumpreInvariante() and r2.cumpreInvariante());
numerador = numerador * r2.denominador -
r2.numerador * denominador;denominador *= r2.denominador;
reduz();
assert(cumpreInvariante());
return *this;
}
...
Agora podemos passar a *
, /
, +
e -
.
Podemos escrevê-los à
custa de *=
, /=
, +=
e -=
! Como?
Discutir para
*
. Concluir:
Discutir que/**
Produto de dois racionais.@pre V.
@post
operator*
=r1
×r2
. */
Racional const operator*(Racional r1, Racional const& r2){
r1 *= r2;
return r2;
}
r1
é cópia. Pode-se alterar localmente!
Explicar vantagem de devolver constante (não se pode escrever ++(r1
* r2)
...!
Pense-se no seguinte código:
Racional r1(1, 2), r2(3, 2);
++(r1 + r2);
Faz algum sentido? O que acontece? É coisa que seja admissível se em vez de racionais tivéssemos inteiros?
Discutir.
A solução para este problema é simples: basta alterar as rotinas (membro ou não) que devolvem um racional por valor para devolverem um racional constante por valor. Isso levaria imediatamente a um erro de compilação, pois não se pode incrementar um racional constante!
E da mesma forma para os outros operadores! Estes operadores binários sem efeitos laterais não precisam de ser membros!
Discutir vantagens de
implementar uns métodos em termos de outros, e a vantagem de ser +
implementado à custa de +=
, e não o contrário.
Fazer /
, +
e -
!
/**
Divisão de dois racionais.@pre
r2
<> 0.
@post
operator/
=r1
/r2
. */
Racional const operator/(Racional r1, Racional const& r2){
assert(r2 != 0);
r1 /= r2;
return r1;
}
/**
Soma de dois racionais.@pre V.
@post
operator+
=r1
+r2
. */
Racional const operator+(Racional r1, Racional const& r2){
r1 += r2;
return r1;
}
/**
Subtracção de dois racionais.@pre V.
@post
operator-
=r1
-r2
. */
Racional const operator-(Racional r1, Racional r2){
r1 -= r2;
return r1;
}
Regra aproximativa: Se uma rotina não precisa de ser membro, não deve ser membro.
Que vantagem é que tem o operador +
, por exemplo, não
ser membro? É que, se for membro, o seguinte código
é válido:
pois o C++ converte o 3 implicitamente para
Racional r(1, 3);
Racional s = r + 3;
Racional
(usando a primeira
versão do construtor), e usa o operador +
definido.Mas o código seguinte é inválido:
pois o C++ nunca converte a instância através da qual se invoca um método.
Racional r(1, 3);
Racional s = 3 + r;
Se a rotina (neste caso um operador) for normal, todos os seus argumentos pode sofrer conversões implícitas, o que resolve o problema.
Repare-se que a existência de um construtor que pode ser invocado
com apenas um argumento estabelece uma conversão implícita,
neste caso de int
para Racional
. Se não for o pretendido pode-se
usar a palavra chave explicit
:
Note-se que...
class Racional {
public:
...
explicit Racional(int const valor = 0);
...
};
...
Racional r(1, 3);
Racional s = 4;
Racional t(4);
Racional u = r + 3;
Racional v = r + Racional(3);
Racional(3)
é muitas vezes interpretado como se fosse
um valor literal para racionais!
Podemos agora passar aos operadores ==
e !=
. Precisam de ser membros?
Qual o seu código?
Discutir: têm de ser membros porque se tem de conhecer numerador e denominador. Mas não precisam se existirem funções membro para devolver os o numerador e o denominador da fracção representativa do racional!
A estas operações de TAD que se limitam a devolver propriedades das variáveis chama-se inspectores. É como num peep show: permitem olhar para o que é privado mas não se lhe pode tocar...
Discutir também questão dos nomes: dizer que é muito importante manter a interface, pelo que o melhor é mudar o nome dos atributos. A forma usual e recomendada é acrescentar-lhes o sublinhado.
Funciona? De certeza? Porquê? Porque 0 <...
class Racional {
public:
...
/**
Devolve numerador da fracção mínima correspondente ao racional.@pre V.
@post
numerador
/denominador
() =*this
.*/
int numerador();
/**
Devolve denominador da fracção mínima correspondente ao racional.@pre V.
@post (E n : V : n/
denominador
=*this
e 0 <denominador
e
mdc(n,
denominador
) = 1).*/
int denominador();
...
private:
...
int numerador_;
int denominador_;
};
...
int Racional::numerador()
{
assert(cumpreInvariante());
assert(cumpreInvariante());
return numerador_;
}
int Racional:: denominador()
{
assert(cumpreInvariante());
assert(cumpreInvariante());
return denominador_;
}
...
/**
Devolve verdadeiro se dois racionais forem iguais.@pre V.
@post
operator==
= (r1
=r2
).*/
bool operator==(Racional const& r1, Racional const& r2){
return r1.numerador() == r2.numerador() and
r1.denominador() == r2.denominador();
}
denominador_
e mdc(numerador_
, denominador_
)
= 1sempre?
De certeza? Vejam /=
! Que se passa?
Chamar a atenção para que as instruções de asserção para verificar o cumprimento da CIC levariam naturalmente à detecção deste erro.
É preciso corrigir:
Racional& Racional::operator/=(Racional const& r)
{
assert(cumpreInvariante() and r2.cumpreInvariante());
assert(r2 != 0);
if(r2.numerador_ < 0) {
numerador_ *= -r2.denominador_;denominador_ *= -r2.numerador_;
} else {
numerador_ *= r2.denominador_;denominador_ *= r2.numerador_;
}
reduz();
assert(cumpreInvariante());
return *this;
}
E o operador !=
?
Discutir solução!
/**
Devolve verdadeiro se dois racionais forem iguais.@pre V.
@post
operator!=
= (r1
<>r2
).*/
bool operator!=(Racional const& r1, Racional const& r2)
{
return not (r1 == r2);
}
Versão final:
Explicar alterações em /=
!
Devem-se à possibilidade de escrever r /= r
!
#include <iostream>
#include <cassert>
using namespace std;
/**
Devolve o máximo divisor comum dos inteiros passados como argumento.
@pre
m
<> 0 oun
<> 0.
@post
mdc
= mdc(m
,n
).*/
int mdc(int const m, int const n)
{
assert(m != 0 or n != 0);
...
}
/**
Representa números racionais.@invariant 0 <
denominador_
e mdc(numerador_
,denominador_
) = 1.*/
class Racional {
public:
/**
Constrói racional com valor inteiro.@pre V.
@post
*this
=valor
.*/
Racional(int const valor = 0);
/**
Constrói racional correspondente an
/d
.@pre
d
<> 0.@post
*this
=numerador
/denominador
.*/
Racional(int const numerador, int const denominador);
/**
Devolve numerador da fracção mínima correspondente ao racional.@pre V.
@post
numerador
/denominador
() =*this
.*/
int numerador();
/**
Devolve denominador da fracção mínima correspondente ao racional.@pre V.
@post (E n : : n/
denominador
=*this
e 0 <denominador
e
mdc(n,
denominador
) = 1).*/
int denominador();
/**
Escreve um racional no ecrã no formato de uma fracção.
@pre V.
@post ¬
cout
.good
() oucout
contémn/d
(ou simplesmente
n
, sed
= 1) em quen
ed
são os valores denumerador
() edenominador
().*/
void escreve();
/**
Lê do teclado o racional, na forma de dois inteiros sucessivos.
@pre
*this
= r.
@post Se
cin
.good
() ecin
tem dois inteiros n' e d' disponíveis para
leitura, com d' <> 0,
então
*this
= n'/d',
senão
*this
= r e¬cin
.good
().*/
void lê();
/**
Incrementa o racional.@pre
*this
= r.
@post
operator++
idêntico a*this
e*this
= r + 1. */
Racional& operator++();
/**
Decrementa o racional.@pre
*this
= r.
@post
operator--
idêntico a*this
e*this
= r - 1. */
Racional& operator--();
/**
Multiplica por um racional.@pre
*this
= r.
@post
operator*=
idêntico a*this
e*this
= r ×r2
. */
Racional& operator*=(Racional const& r2);
/**
Divide por um racional.@pre
*this
= r er2
<> 0.
@post
operator/=
idêntico a*this
e*this
= r /r2
. */
Racional& operator/=(Racional const& r2);
/**
Adiciona de um racional.@pre
*this
= r.
@post
operator+=
idêntico a*this
e*this
= r +r2
. */
Racional& operator +=(Racional const& r2);
/**
Subtrai de um racional.@pre
*this
= r.
@post
operator-=
idêntico a*this
e*this
= r -r2
. */
Racional& operator-=(Racional const& r2);
private:
int numerador_;
int denominador_;
/**
Reduz a fracção que representa o racional.
@pre
denominador_
<> 0 e*this
= r.
@post
denominador_
<> 0 e mdc(numerador_
,denominador_
) = 1 e
*this
= r.*/
void reduz();
/**
Indica se a CIC se verifica.@pre V.
@post
cumpreInvariante
= 0 <denominador_
e mdc(numerador_
,denominador_
) = 1.*/
bool cumpreInvariante();
};
Racional::Racional(int const valor)
: numerador_(valor), denominador_(1)
{
assert(cumpreInvariante());
assert(numerador() == valor * denominador());
}
Racional::Racional(int const numerador, int const d
enominador
)
{
assert(denominador
!= 0);
if(denominador < 0) {
numerador_ = -numerador;
denominador_ = -denominador;
} else {
numerador_ = numerador;
denominador_ = denominador;
}
reduz();
assert(cumpreInvariante());
assert(numerador_() * denominador == numerador * denominador());
}
int Racional::numerador()
{
assert(cumpreInvariante());
assert(cumpreInvariante());
return numerador_;
}
int Racional::denominador()
{
assert(cumpreInvariante());
assert(cumpreInvariante());
return denominador_;
}
void Racional::escreve()
{
assert(cumpreInvariante());
cout << numerador_;
if(denominador()
!= 1)
cout << '/' << denominador();
assert(cumpreInvariante());
}
void Racional::lê()
{
assert(cumpreInvariante());
int n, d;
cin >> n >> d;
if(cin.good())
if(d == 0)
cin.setstate(ios_base::failbit);
else {
if(d < 0) {
numerador_ = -n;
denominador_ = -d;
} else {
numerador_ = n;
denominador_ = d;
}
reduz();
assert(cumpreInvariante());
assert(numerador_() * d == n * denominador() and cin.good());
return;
}
assert(cumpreInvariante());
assert(not cin.good());
}
Racional& Racional::operator++()
{
assert(cumpreInvariante());
numerador_ += denominador_;
assert(cumpreInvariante());
return *this;
}
Racional& Racional::operator--()
{
assert(cumpreInvariante());
numerador_ -= denominador_;
assert(cumpreInvariante());
return *this;
}
Racional& Racional::operator*=(Racional const& r2)
{
assert(cumpreInvariante() and r2.cumpreInvariante());
numerador_ *= r2.numerador_;
denominador_ *= r2.denominador_;
reduz();
assert(cumpreInvariante());
return *this;
}
Racional& Racional::operator/=(Racional const& r2)
{
assert(cumpreInvariante() and r2.cumpreInvariante());
assert(r2 != 0);
//
Necessário para evitar problemas comr /= r
!
int numerador = r2.numerador_;
if(r2.numerador_ < 0) {
numerador_ *= -r2.denominador_;denominador_ *= -r2.numerador_numerador;
} else {
numerador_ *= r2.denominador_;denominador_ *= r2.numerador_numerador;
}
reduz();
assert(cumpreInvariante());
return *this;
}
Racional& Racional::operator+=(Racional const& r2)
{
assert(cumpreInvariante() and r2.cumpreInvariante());
numerador_ = numerador_ * r2.denominador_ +
r2.numerador_ * denominador_;denominador_ *= r2.denominador_;
reduz();
assert(cumpreInvariante());
return *this;
}
Racional& Racional::operator-=(Racional const& r2)
{
assert(cumpreInvariante() and r2.cumpreInvariante());
numerador_ = numerador_ * r2.denominador_ -
r2.numerador_ * denominador_;denominador_ *= r2.denominador_;
reduz();
assert(cumpreInvariante());
return *this;
}
void Racional::reduz()
{
assert(denominador_ != 0);
int divisor = mdc(numerador_, denominador_);
numerador_ /= divisor;
denominador_ /= divisor;
assert(denominador_ != 0 and mdc(numerador_, denominador_) == 1);
}
bool Racional::cumpreInvariante()
{
return 0 < denominador_ and mdc(numerador_, denominador_) == 1;
}
/**
Produto de dois racionais.@pre V.
@post
operator*
=r1
×r2
. */
Racional const operator*(Racional r1, Racional const& r2){
r1 *= r2;
return r1;
}
/**
Divisão de dois racionais.@pre
r2
<> 0.
@post
operator/
=r1
/r2
. */
Racional const operator/(Racional r1, Racional const& r2){
assert(r2 != 0);
r1 /= r2;
return r1;
}
/**
Soma de dois racionais.@pre V.
@post
operator+
=r1
+r2
. */
Racional const operator+(Racional r1, Racional const& r2){
return r1 += r2;
}
/**
Subtracção de dois racionais.@pre V.
@post
operator-
=r1
-r2
. */
Racional const operator-(Racional r1, Racional const& r2){
r1 -= r2;
return r1;
}
/**
Devolve verdadeiro se dois racionais forem iguais.@pre V.
@post
operator==
= (r1
=r2
).*/
bool operator==(Racional const& r1, Racional const& r2){
return r1.numerador() == r2.numerador() and
r1.denominador() == r2.denominador();
}
/**
Devolve verdadeiro se dois racionais forem iguais.@pre V.
@post
operator
!=
= (r1
<>r2
).*/
bool operator!=(Racional const& r1, Racional const& r2){
return not (r1 == r2);
}
int main()
{
//
Ler fracções:
cout << "Introduza duas fracções (numerador denominador): ";
Racional r1, r2;
r1.lê();
r2.lê();
if(not cin) {
cout << "Opps! A leitura dos racionais falhou!" << endl;
return 1;
}
//
Calcular racional soma:
Racional r = r1 + r2;
//
Escrever resultado:
cout << "A soma de ";
r1.escreve();
cout << " com ";
r2.escreve();
cout << " é ";
r.escreve();
cout << '.' << endl;
}
Curiosamente este código tem um erro: não se pode invocar os métodos numerador()
e denominador()
através de instâncias constantes, como é o caso
nos operadores ==
e !=
! A ver na próxima
aula...
Chamar a atenção para o trabalho todo que estamos a ter...
Mais trabalho do produtor, menos do consumidor. Menos trabalho do produtor, mais trabalho do consumidor.
Ficaram a faltar...
++
,--
sufixo
+
,-
unários
<
,<=
,>
,>=
e ainda
<<
e>>
para inserção e extracção em e de canais
A ver mais tarde...