aula 12 tipos abstractos de dados iii. 2003/2004 introdução à programação 2 operadores a...
TRANSCRIPT
Aula 12
Tipos Abstractos de Dados III
2003/2004Introdução à Programação2
Operadores a sobrecarregar
Aritméticos: Binários: *, /, + e - Unários: + e -
Relacionais: <, <=, > e >=
Igualdade e diferença: == e !=
Incrementação: Prefixo: ++ e -- Sufixo: ++ e --
Especiais de atribuição: *=, /=, += e -=
Feito.
Feito, mas precisa ser refeito.
2003/2004Introdução à Programação3
Por onde começar?
Devemos começar pelos operadores com efeitos laterais
Quais são?
Racional a(1,2), b(3,5); a + b; // altera a? e b?
a++; // altera a?
2003/2004Introdução à Programação4
Operadores com efeitos laterais
Incrementação: Prefixo: ++ e -- Sufixo: ++ e --
Especiais de atribuição: *=, /=, += e -=
De atribuição: =
Feito.
Fica para POO. (Aliás, aqui é desnecessário.)
2003/2004Introdução à Programação5
Operação Racional::operator++()/** … */class Racional { public: …
/** Incrementa o racional.
@pre *this = r.
@post operator++ ≡ *this *this = r + 1. */ Racional& operator++();
…
private: …
};
2003/2004Introdução à Programação6
Operação Racional::operator--()/** … */class Racional { public: …
/** Decrementa o racional.
@pre *this = r.
@post operator-- ≡ *this *this = r - 1. */ Racional& operator--();
…
private: …
};
2003/2004Introdução à Programação7
Método Racional::operator++()Racional& Racional::operator++() { assert(cumpreInvariante());
numerador += denominador;
assert(cumpreInvariante());
return *this; }
2003/2004Introdução à Programação8
Método Racional::operator--()Racional& Racional::operator--() { assert(cumpreInvariante());
numerador -= denominador;
assert(cumpreInvariante());
return *this; }
2003/2004Introdução à Programação9
Operadores especiais de atribuição
*=, /=, += e -=
Como membros da classe (porquê?)
Primeiro operando é instância implícita
Devolvem primeiro operando por referência:
Racional a(3), b(1, 2); (a *= b) *= b;
Se é possível com tipos básicos, também o deve ser com os nossos TAD.
Isso não significa que este código seja recomendável!
2003/2004Introdução à Programação10
Operação Racional::operator*=()/** … */class Racional { public: …
/** Multiplica por um racional. @pre *this = r. @post operator*= ≡ *this *this = r × r2. */ Racional& operator*=(Racional const& r2); …
private: …};
2003/2004Introdução à Programação11
Método Racional::operator*=()Racional& Racional::operator*=(Racional const& r2) { assert(cumpreInvariante()); assert(r2.cumpreInvariante());
numerador *= r2.numerador; denominador *= r2.denominador;
reduz();
assert(cumpreInvariante());
return *this; }
2003/2004Introdução à Programação12
Operação Racional::operator/=()/** … */class Racional { public: …
/** Divide por um racional. @pre *this = r r2 ≠ 0. @post operator/= ≡ *this *this = r / r2. */ Racional& operator/=(Racional const& r2); …
private: …};
2003/2004Introdução à Programação13
Método Racional::operator/=()Racional& Racional::operator/=(Racional const& r2) { assert(cumpreInvariante()); assert(r2.cumpreInvariante());
assert(r2 != 0);
numerador *= r2.denominador; denominador *= r2.numerador;
reduz();
assert(cumpreInvariante());
return *this; }
Não se pode dividir por zero.
Asserção possível apenas quando se definir operador !=.
2003/2004Introdução à Programação14
Operação Racional::operator+=()/** … */class Racional { public: …
/** Adiciona de um racional. @pre *this = r. @post operator+= ≡ *this *this = r + r2. */ Racional& operator+=(Racional const& r2); …
private: …};
2003/2004Introdução à Programação15
Método Racional::operator+=()Racional& Racional::operator+=(Racional const& r2) { assert(cumpreInvariante()); assert(r2.cumpreInvariante());
numerador = numerador * r2.denominador + r2.numerador * denominador; denominador *= r2.denominador;
reduz();
assert(cumpreInvariante());
return *this; }
Pode-se trocar a ordem destas atribuições?
2003/2004Introdução à Programação16
Operação Racional::operator-=()/** … */class Racional { public: …
/** Subtrai de um racional. @pre *this = r. @post operator-= ≡ *this *this = r - r2. */ Racional& operator-=(Racional const& r2); …
private: …};
2003/2004Introdução à Programação17
Método Racional::operator-=()Racional& Racional::operator-=(Racional const& r2) { assert(cumpreInvariante()); assert(r2.cumpreInvariante());
numerador = numerador * r2.denominador - r2.numerador * denominador; denominador *= r2.denominador;
reduz();
assert(cumpreInvariante());
return *this; }
2003/2004Introdução à Programação18
Operadores aritméticos binários
*, /, + e –
Podem-se definir à custa dos operadores especiais de atribuição!
Aliás, devem-se!
Como?
2003/2004Introdução à Programação19
Devolução como constante??
Torna impossívelRacional r1(1, 2), r2(3, 2);
++(r1 * r2);
tal como acontece para os tipos básicos.
Função operator*()
/** Produto de dois racionais.
@pre V.
@post operator* = r1 × r2. */Racional const operator*(Racional r1, Racional const& r2) { r1 *= r2;
return r1; }
Passagem por valor! Alteração de r1 é alteração de cópia do argumento.
2003/2004Introdução à Programação20
Função operator/()
/** Quociente 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; }
2003/2004Introdução à Programação21
Função operator+()
/** Soma de dois racionais.
@pre V.
@post operator+ = r1 + r2. */Racional const operator+(Racional r1, Racional const& r2) { r1 += r2;
return r1; }
2003/2004Introdução à Programação22
Função operator-()
/** Subtracção de dois racionais.
@pre V.
@post operator- = r1 - r2. */Racional const operator-(Racional r1, Racional const& r2) { r1 -= r2;
return r1; }
Operadores ou rotinas não-membro, são externas à classe C++, mas fazem parte da definição do TAD!
2003/2004Introdução à Programação23
Discussão
Quase regra: Se rotina pode não ser membro, não o deve ser
Métodos devem recorrer a operações existentes *= à custa de * ou * à custa de *=?
Obriga a cópia desnecessária
2003/2004Introdução à Programação24
Conversões implícitas
Definidas por construtores invocáveis com apenas um argumento
Convertem entre o tipo do parâmetro desse construtor e a classe
Permitem:Racional r(1, 3);
Racional s = r + 3;
2003/2004Introdução à Programação25
Conversões implícitas
Definidas por construtores invocáveis com apenas um argumento
Convertem entre o tipo do parâmetro desse construtor e a classe
Permitem:Racional r(1, 3);
Racional s = r + Racional(3);
Conversão implícita, colocada pelo compilador.
2003/2004Introdução à Programação26
Membro ou não membro, eis a questão
Conversões implícitas não ocorrem para instâncias através das quais se
invocam operações
(que são implícitas na execução do respectivo método)
Se operator+() fosse membro de Racional:
Racional r(1, 3);
Racional s = 33 + r;
Erro! Conversão impossível!
2003/2004Introdução à Programação27
Conversões explícitas
class Racional { public: …
explicit Racional(int const valor = 0);
… };
…
Racional r(1, 3); Racional s = 4;Racional s = 4; Racional t(4); Racional u = r + 3;Racional u = r + 3; Racional v = r + Racional(3);
Neste caso conversões implícitas são desejáveis. Não se deve usar explicit.
2003/2004Introdução à Programação28
Operadores de igualdade e diferença
Membros ou não membros?
Precisam de aceder aos atributos: membros
Convinha que tivessem comportamento comutativo no que diz respeito às conversões: não membros
Racional r(1, 2);
if(r == 1) …if(1 == r) …
2003/2004Introdução à Programação29
Inspectores
Operações que permitem obter informações acerca de uma instância de um TAD
Muitas vezes permitem saber o valor de atributos
Revelam informação sem violar encapsulamento
2003/2004Introdução à Programação30
Dois inspectores úteis
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 0 < denominador (E n : V : n/denominador = *this mdc(n, denominador) = 1). */ int denominador();
…
private: …
int numerador_; int denominador_; };
Não pode haver operações com mesmo nome de atributos.
Optou-se por alterar nome de atributos. Porquê?
2003/2004Introdução à Programação31
Dois inspectores úteis
int Racional::numerador() { assert(cumpreInvariante());
assert(cumpreInvariante());
return numerador_; }
int Racional::denominador() { assert(cumpreInvariante());
assert(cumpreInvariante());
return denominador_; }
2003/2004Introdução à Programação32
Predicado operator==()
/** Indica se dois racionais são 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(); }
Funciona porque os inspectores se limitam a devolver os atributos e estes cumprem sempre a CIC, ou seja,
0 < denominador_ mdc(numerador_, denominador_) = 1
Sempre? De certeza?
As instruções de asserção não o garantem?
Sim, mas…
2003/2004Introdução à Programação33
Bronca…
Racional& Racional::operator/=(Racional const& r2) { assert(cumpreInvariante()); assert(r2.cumpreInvariante());
assert(r2 != 0);
numerador_ *= r2.denominador(); denominador_ *= r2.numerador();
reduz();
assert(cumpreInvariante());
return *this; }
Pois é… O numerador de r2 pode ser negativo!
2003/2004Introdução à Programação34
Correcção e… nova bronca!
Racional& Racional::operator/=(Racional const& r2) { assert(cumpreInvariante()); assert(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 se r2 for o mesmo que *this? Ou seja, se:
Racional r(1, 2);
r /= r;
2003/2004Introdução à Programação35
Correcção e… nova bronca!
Racional& Racional::operator/=(Racional const& r2) { assert(cumpreInvariante()); assert(r2.cumpreInvariante());
assert(r2 != 0);
int numerador = r2.numerador();
if(numerador < 0) { numerador_ *= -r2.denominador(); denominador_ *= -numerador; } else { numerador_ *= r2.denominador(); denominador_ *= numerador; }
reduz();
assert(cumpreInvariante());
return *this; }
2003/2004Introdução à Programação36
E o operador !=?
/** Indica se dois racionais são diferentes.
@pre V.
@post operator!= = (r1 ≠ r2). */bool operator!=(Racional const& r1, Racional const& r2) { return not (r1 == r2); }
2003/2004Introdução à Programação37
Em falta
Aritméticos: Unários: + e -
Relacionais: <, <=, > e >=
Incrementação: Sufixo: ++ e --
2003/2004Introdução à Programação38
Coisas várias
Uma trabalheira destas vale a pena?
O código está errado!
2003/2004Introdução à Programação39
Aula 12: Sumário
Sobrecarga de operadores para o TAD Racional Operadores com efeitos laterais Regra: uma rotina só deve ser membro se precisar de o ser Conversões implícitas:
Construtores invocáveis com um único argumento Excepções
Evitando as conversões implícitas com explicit Comutatividade quanto a conversões Devolução de constantes Implementação de operações à custa de outras Inspectores e sua utilidade Vantagens e desvantagens de definir um TAD completo:
trabalho do produtor vs. trabalho do consumidor