+
binário e ++
prefixo.*this
.
Rever todo o programa da aula passada. Aproveitar para explicar
que com
class
os membros são privados salvo indicação
em contrário, e com
struct
são públicos salvo indicação
em contrário.
Atenção! Esta revisão é profunda (deve incluir um traçado do programa)! Daí a dimensão reduzida do guião. Mencionar cuidadosamente a CIC e acrescentar noções sobre passagem de argumentos por referência constante!
#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);
/**
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
somaCom
=*this
+r2
.*/
Racional somaCom(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ê();
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 denominador_)
{
assert(denominador_ != 0);
if(denominador_ < 0) {
numerador = -numerador_;
denominador = -denominador_;
} else {
numerador = numerador_;
denominador = denominador_;
}
reduz();
assert(cumpreInvariante());
assert(numerador * denomindor_ == numerador_ * denominador);
}
void Racional::escreve()
{
assert(cumpreInvariante());
cout << numerador;
if(denominador
!= 1)
cout << '/' << denominador;
assert(cumpreInvariante());
}
Racional Racional::somaCom(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ê()
{
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());
}
void Racional::reduz()
{
assert(denominador != 0);
int k = mdc(numerador, denominador);
numerador /= k;
denominador /= k;
assert(denominador != 0 and mdc(numerador, denominador) == 1);
}
bool Racional::cumpreInvariante()
{
return 0 < denominador and mdc(numerador, denominador) == 1;
}
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.somaCom(r2);
//
Escrever resultado:
cout << "A soma de ";
r1.escreve();
cout << " com ";
r2.escreve();
cout << " é ";
r.escreve();
cout << '.' << endl;
}
Na aula passada disse-vos que a notação a que a utilização de operações do TAD definido obrigava era algo estranha, pelo menos para o principiante, especialmente quando usada para somar dois racionais. Hoje, entre outros assuntos, vamos ver como se pode melhorar essa notação.
Mostrar, discutir e explicar objectivo.
O C++ proporciona-nos a possibilidade de sobrecarregar os operadores (+
,
-
, *
, /
, ==
, etc.) de modo a poderem ser utilizados com novos tipos
definidos por nós. Essa sobrecarga pode ser feita por rotinas normais ou por
rotinas membro de uma classe C++, ou operações.
Para que a função membro somaCom()
possa ser invocada através
do operador +
é necessário modificar o seu nome:
Falta agora fazer o mesmo para todos os operadores de interesse para os racionais. Quais são eles? São:...
class Racional {
public:
...
Racional operator+(Racional const& r2);
...
};
...
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;
}
...
int main()
{
...
//
Calcular racional soma:
Racional r = r1.soma(r2) + r2;
//
Escrever resultado:
cout << "A soma de ";
r1.escreve();
cout << " com ";
r2.escreve();
cout << " é ";
r.escreve();
cout << '.' << endl;
}
Uma questão importante é o que é que devolvem os operadores
++
,--
sufixo++
,--
prefixo+
,-
unários*
,/
+
,-
binários<
,<=
,>
,>=
==
!=
*=
/=
+=
-=
op=
e os operadores ++
e --
prefixo. Suponha-se o código:
int i = 0;
++(++i);
cout << i << endl;
Que resulta? 2? Claro! Mas então, o operador ++
para
além de incrementar i
devolve o próprio i
e não uma sua cópia!
Atenção! Como sempre deve-se usar a notação UML estendida para mostrar o efeito dos troços de código. Atenção à notação estendida para as referências!
Escreva-se um procedimento incrementa com o mesmo
efeito. Como deve aumentar i
, deve receber o argumento por referência:
Impossível!
void incrementa(int& v)
{v = v + 1;
}
...
int i = 0;
++(++i)
incrementa(incrementa(i));
cout << i << endl;
incrementa()
não devolve nada! Tentemos:
voidint incrementa(int& v)
{v = v + 1;
return v;
}
...
int i = 0;
incrementa(incrementa(i));
cout << i << endl;
Não funciona! Valor incrementado é uma cópia temporária
de i
. Se compilasse (e não compila), só incrementava
i
uma vez.
É necessário devolver o próprio i
!
int& incrementa(int& v)
{v = v + 1;
return v; //
ou simplesmentereturn v = v + 1;
}
...
int i = 0;
incrementa(incrementa(i));
cout << i << endl;
ou seja
Agora sim!
Vamos implementar o operador ++
prefixo para os racionais.
Como precisa de alterar as variáveis membro privadas, deve ser um operador membro:
Que devolver? Devia ser a própria variável implícita, à qual pertencem os atributos...
class Racional {
public:
...
/**
Incrementa o racional.@pre
*this
= r.
@post
*this
= r + 1. */
Racional& operator++();
...
};
...
Racional& Racional::operator++()
{
assert(cumpreInvariante());
Discutir este código! Porque não é necessário reduzir?
numerador += denominador;
assert(cumpreInvariante());
return
?Que devolver?}
...
numerador
e denominador
! É possível referirmo-nos
à variável implícita explicitamente através
de *this
(significa eu). Logo:
...
class Racional {
public:
...
/**
Incrementa 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;
}
...
Chamaremos a isto a explicitação da variável implícita.
Explicar o que significa idêntico! Diferença entre igualdade e identidade.Se houver tempo, falar da diferença entre
int cópia(int const& v)
{
return v;
}
e
int& mesmo(int& v)
{
return v;
}
Discutir utilizações. Mencionar conceito de lvalue (left value).
Falar um pouco de testes de unidade e da sua enorme importância.
Se houver tempo continuar com +=
, passando +
a não membro.