Aula 12 Tipos Abstractos de Dados III Operadores a sobrecarregar Feito. Prefixo: ++ e -Sufixo: ++ e -- Especiais de atribuição: 2 == e != Incrementação: <, <=, > e >= Igualdade e diferença: Binários: *, /, + e Unários: + e - Relacionais: Feito, mas precisa ser refeito. Aritméticos: *=, /=, += e -= Introdução à Programação 2003/2004 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? 3 Introdução à Programação 2003/2004 Operadores com efeitos laterais Prefixo: ++ e -Sufixo: ++ e -- Especiais de atribuição: Feito. Incrementação: *=, /=, += e -= De atribuição: = Fica para POO. (Aliás, aqui é desnecessário.) 4 Introdução à Programação 2003/2004 Operação Racional::operator++() /** … */ class Racional { public: … /** Incrementa o racional. @pre *this = r. @post operator++ ≡ *this *this = r + 1. */ Racional& operator++(); … private: … }; 5 Introdução à Programação 2003/2004 Operação Racional::operator--() /** … */ class Racional { public: … /** Decrementa o racional. @pre *this = r. @post operator-- ≡ *this *this = r - 1. */ Racional& operator--(); … private: … }; 6 Introdução à Programação 2003/2004 Método Racional::operator++() Racional& Racional::operator++() { assert(cumpreInvariante()); numerador += denominador; assert(cumpreInvariante()); return *this; } 7 Introdução à Programação 2003/2004 Método Racional::operator--() Racional& Racional::operator--() { assert(cumpreInvariante()); numerador -= denominador; assert(cumpreInvariante()); return *this; } 8 Introdução à Programação 2003/2004 Operadores especiais de atribuição *=, /=, += e -= Como membros da classe (porquê?) Se é possível com tipos básicos, também o deve ser com os instância nossos implícita TAD. Primeiro operando é Isso não significa que este Devolvem primeiro operando por referência: código seja recomendável! Racional a(3), b(1, 2); (a *= b) *= b; 9 Introdução à Programação 2003/2004 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: … }; 10 Introdução à Programação 2003/2004 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; } 11 Introdução à Programação 2003/2004 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: … }; 12 Introdução à Programação 2003/2004 Método Racional::operator/=() Racional& Racional::operator/=(Racional const& r2) { assert(cumpreInvariante()); assert(r2.cumpreInvariante()); assert(r2 != 0); numerador *= r2.denominador; denominador *= r2.numerador; Não se pode dividir por zero. reduz(); assert(cumpreInvariante()); Asserção possível apenas quando se definir operador !=. return *this; } 13 Introdução à Programação 2003/2004 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: … }; 14 Introdução à Programação 2003/2004 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()); Pode-se trocar a ordem destas atribuições? return *this; } 15 Introdução à Programação 2003/2004 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: … }; 16 Introdução à Programação 2003/2004 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; } 17 Introdução à Programação 2003/2004 Operadores aritméticos binários *, /, + e – Podem-se definir à custa dos operadores especiais de atribuição! Aliás, devem-se! Como? 18 Introdução à Programação 2003/2004 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 Devolução como valor! constante?? Alteração de r1 é alteração de cópia do Torna impossível argumento. Racional r1(1, 2), r2(3, 2); ++(r1 * r2); tal como acontece para os tipos básicos. 19 Introdução à Programação 2003/2004 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; } 20 Introdução à Programação 2003/2004 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; } 21 Introdução à Programação 2003/2004 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; } 22 Operadores ou rotinas nãomembro, são externas à classe C++, mas fazem parte da definição do TAD! Introdução à Programação 2003/2004 Discussão Quase regra: Métodos devem recorrer a operações existentes 23 Se rotina pode não ser membro, não o deve ser *= à custa de * ou * à custa de *=? Introdução à Programação Obriga a cópia desnecessária 2003/2004 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; 24 Introdução à Programação 2003/2004 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 Conversão Permitem: implícita, Racional r(1, 3); colocada pelo compilador. Racional s = r + Racional(3); 25 Introdução à Programação 2003/2004 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 = 3 + r; 26 Introdução à Programação Erro! Conversão impossível! 2003/2004 Conversões explícitas class Racional { public: … explicit Racional(int const valor = 0); … }; … Racional Racional Racional Racional Racional 27 Neste caso conversões implícitas são desejáveis. Não se deve usar explicit. r(1, 3); s = 4; t(4); u = r + 3; v = r + Racional(3); Introdução à Programação 2003/2004 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) … 28 Introdução à Programação 2003/2004 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 29 Introdução à Programação 2003/2004 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: … }; 30 int numerador_; int denominador_; Não pode haver operações com mesmo nome de atributos. Optou-se por alterar nome de atributos. Porquê? Introdução à Programação 2003/2004 Dois inspectores úteis int Racional::numerador() { assert(cumpreInvariante()); assert(cumpreInvariante()); return numerador_; } int Racional::denominador() { assert(cumpreInvariante()); assert(cumpreInvariante()); return denominador_; } 31 Introdução à Programação 2003/2004 Predicado operator==() /** Indica se dois racionais são iguais. @pre V. Sempre? De certeza? @post operator== = (r1 = r2). */ bool operator==(Racional const& r1, Racional const& r2) { As instruções de asserção return r1.numerador() == r2.numerador()não ando garantem? r1.denominador() == r2.denominador(); } Sim, mas… Funciona porque os inspectores se limitam a devolver os atributos e estes cumprem sempre a CIC, ou seja, 0 < denominador_ mdc(numerador_, denominador_) = 1 32 Introdução à Programação 2003/2004 Bronca… Racional& Racional::operator/=(Racional const& r2) { assert(cumpreInvariante()); assert(r2.cumpreInvariante()); assert(r2 != 0); numerador_ *= r2.denominador(); denominador_ *= r2.numerador(); reduz(); assert(cumpreInvariante()); } 33 return *this; Introdução à Programação Pois é… O numerador de r2 pode ser negativo! 2003/2004 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; 34 Introdução à Programação 2003/2004 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()); } 35 return *this; Introdução à Programação 2003/2004 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); } 36 Introdução à Programação 2003/2004 Em falta Aritméticos: Relacionais: <, <=, > e >= Incrementação: 37 Unários: + e - Sufixo: ++ e -- Introdução à Programação 2003/2004 Coisas várias Uma trabalheira destas vale a pena? O código está errado! 38 Introdução à Programação 2003/2004 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: 39 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 Introdução à Programação 2003/2004