Aula 10 Tipos Abstractos de Dados I Flashback Lembram-se da Aula 4? 2 Introdução à Programação 2003/2004 Soma de fracções (I) #include <iostream> #include <cassert> using namespace std; /** Devolve o máximo divisor comum dos inteiros passados como argumento. @pre m ≠ 0 ou n ≠ 0. @post mdc = mdc(m, n). */ int mdc(int const m, int const n) { assert(m != 0 or n != 0); } PC relaxada para aceitar inteiros negativos e nulos (ver folhas teóricas) … (continua) 3 Introdução à Programação 2003/2004 Soma de fracções (II) /** Reduz a fracção recebida como argumento. @pre denominador ≠ 0 numerador = n denominador = d. @post denominador ≠ 0 mdc(numerador, denominador ) = 1 numerador/denominador = n/d. */ void reduzFracção(int& numerador, int& denominador) { assert(denominador != 0); int const divisor = mdc(numerador, denominador); numerador /= divisor; denominador /= divisor; assert(denominador != 0); assert(mdc(numerador, denominador) == 1); } (continua) 4 Introdução à Programação 2003/2004 Soma de fracções (III) int n, d; /** Lê do teclado uma fracção, na forma de dois inteiros sucessivos. cin >> n >> d;= n denominador = d. @pre numerador if(cin.good()) @post Se cin.good() cin tem dois inteiros n' e d' disponíveis para if(d == 0) leitura, com d' ≠ 0, então cin.setstate(ios_base::failbit); else { 0 < denominador mdc(numerador, denominador) = 1 if(d < 0) { numerador/denominador numerador = -n;= n'/d' cin.fail(), senão numerador = n =denominador = d cin.fail(). */ denominador -d; } else { void lêFracção(int& numerador, int& denominador) numerador = n; { denominador = d; } … reduzFracção(numerador, denominador); } Não existia na Aula 4! assert(0 < denominador); assert(mdc(numerador, denominador) == 1); assert(numerador * d == n * denominador); Garante-se assert(not cin.fail()); (continua) } denominador positivo e representação em termos mínimos. return; assert(cin.fail()); 5 Introdução à Programação 2003/2004 Soma de fracções (IV) /** Soma duas fracções. @pre denominador1 ≠ 0 denominador2 ≠ 0. @post numerador/ denominador = numerador1/denominador1 + numerador2/denominador2 denominador ≠ 0 mdc(numerador, denominador) = 1. */ void somaFracção(int& numerador, int& denominador, int const numerador1, int const denominador1, int const numerador2, int const denominador2) { assert(denominador1 != 0); assert(denominador2 != 0); Não existia na Aula 4! numerador = numerador1 * denominador2 + numerador2 * denominador1; denominador = denominador1 * denominador2; reduzFracção(numerador, denominador); assert(denominador != 0); assert(mdc(numerador, denominador) == 1); } (continua) 6 Introdução à Programação 2003/2004 Soma de fracções (V) /** Escreve uma fracção no ecrã no formato usual. @pre V. @post cout.fail() cout contém n/d (ou simplesmente n, se d = 1) em que n e d são os valores de numerador e denominador. */ void escreveFracção(int const numerador, int const denominador) { cout << numerador; if(denominador != 1) cout << '/' << denominador; } (continua) 7 Introdução à Programação 2003/2004 Soma de fracções (VI) int main() { // Ler fracções: cout << "Introduza duas fracções (numerador denominador): "; int n1, d1, n2, d2; lêFracção(n1, d1); lêFracção(n2, d2); if(cin.fail()) { cout << "Opps! return 1; } A leitura das fracções falhou!" << endl; (continua) 8 Introdução à Programação 2003/2004 Soma de fracções (VII) // Calcular fracção soma reduzida: int n, d; somaFracção(n, d, n1, d1, n2, d2); // Escrever resultado: cout << "A soma de "; escreveFracção(n1, d1); cout << " com "; escreveFracção(n2, d2); cout << " é "; escreveFracção(n, d); cout << '.' << endl; } 9 Introdução à Programação 2003/2004 Problemas Dois inteiros para cada fracção Não é possível desenvolver funções para somar fracções: 10 funções só devolvem um valor Código complexo e difícil de perceber Introdução à Programação 2003/2004 Objectivo 11 Escrever programa para somar fracções tão simples como para somar inteiros Ou seja… Introdução à Programação 2003/2004 O nosso objectivo #include <iostream> using namespace std; … int main() { Lá chegaremos, lá chegaremos… cout << "Introduza duas fracções (numerador denominador): "; Racional r1, r2; cin >> r1 >> r2; if(cin.fail()) { cout << "Opps! A leitura dos racionais falhou!" << endl; return 1; } Racional r = r1 + r2; cout << "A soma de " << r1 << " com " << r2 << " é " << r << '.' << endl; } 12 Introdução à Programação 2003/2004 Solução 13 Criar um novo tipo de dados que permita representar um número racional (fracção) com uma só instância Ou seja, criar um Tipo Abstracto de Dados (TAD) Introdução à Programação 2003/2004 Tipos Abstractos de Dados (TAD) Ou Tipos de Primeira Categoria Características: Tipo definido pelo programador Comporta-se como os tipos básicos Serve para definir variáveis e constantes com que se pode operar Representado pelas classes C++ Não confundir “classe C++” com “classe” (propriamente dita)… Introdução à Programação 14 Pormenores só em POO 2003/2004 TAD Racional /** Representa números racionais. */ class Racional { Variáveis public: membro ou atributos int numerador; int denominador; }; Atenção ao ; final! 15 Introdução à Programação 2003/2004 TAD Racional #include <iostream> #include <cassert> using namespace std; int mdc(int const m, int const n) { … } /** Representa números racionais. */ class Racional { public: int numerador; int denominador; }; … 16 Introdução à Programação 2003/2004 Representação gráfica do TAD Nome Racional numerador: int denominador: int Atributos: instâncias membro Operações: rotinas membro 17 Introdução à Programação 2003/2004 Utilização do TAD Racional r1; Racional r2; r1.numerador = 6; r1.denominador = 9; r2.numerador = 7; r2.denominador = 3; 18 Cada instância de Racional tem os seus próprios atributos! Introdução à Programação 2003/2004 Representações gráficas (I) r1: Racional numerador = 6 ? denominador = 9 ? Instâncias do TAD Objectos Há quem lhes chame objectos, mas reservaremos esse nome para as classes propriamente ditas. 19 r2: Racional numerador = 7 ? denominador = 3 ? Introdução à Programação 2003/2004 Representações gráficas (II) 20 r1: Racional r2: Racional numerador: int numerador: int 6 7 denominador: int denominador: int 9 3 Introdução à Programação 2003/2004 Acesso a membros de instâncias de um TAD Operador de selecção de membro: . instância.membro 21 Introdução à Programação 2003/2004 Função somaDe() /** Devolve a soma de dois racionais. @pre r1.denominador ≠ 0 r2.denominador ≠ 0. @post somaDe = r1 + r2 somaDe.denominador ≠ 0 mdc(somaDe.numerador, somaDe.denominador) = 1. */ Racional somaDe(Racional const r1, Racional const r2) { assert(r1.denominador != 0); Nome sem sufixo assert(r2.denominador != 0); Fracção: redundante dado tipo dos parâmetros. Racional r; r.numerador = r1.numerador * r2.denominador + r2.numerador * r1.denominador; r.denominador = r1.denominador * r2.denominador; reduz(r); assert(r.denominador != 0); assert(mdc(r.numerador, r.denominador) == 1); A fazer. return r; } 22 Introdução à Programação 2003/2004 Procedimento reduz() /** Reduz a fracção que representa o racional recebido como argumento. @pre r.denominador ≠ 0 r = r. @post r.denominador ≠ 0 mdc(r.numerador, r.denominador) = 1 r = r. */ void reduz(Racional const r) { assert(r.denominador != 0); int const divisor = mdc(r.numerador, r.denominador); r.numerador /= divisor; r.denominador /= divisor; Nome sem sufixo Fracção: redundante dado tipo dos parâmetros. assert(r.denominador != 0); assert(mdc(r.numerador, r.denominador) == 1); } 23 Introdução à Programação 2003/2004 Procedimento lêPara() /** Lê do teclado um racional, na forma de dois inteiros sucessivos. @pre r = r. @post Se cin.good() cin tem dois inteiros n e d disponíveis para leitura, com d <> 0, então r = n/d cin.fail() 0 < r.denominador mdc(r.numerador, r.denominador) = 1, senão r = r cin.fail(). */ void lêPara(Racional& r) { … } 24 Introdução à Programação 2003/2004 Procedimento lêPara() int n, d; cin >> n >> d; if(not cin.fail()) if(d == 0) cin.setstate(ios_base::failbit); else { if(d < 0) { r.numerador = -n; r.denominador = -d; } else { r.numerador = n; r.denominador = d; } reduz(r); assert(0 < r.denominador); assert(mdc(r.numerador, r. denominador) == 1); assert(r.numerador * d == n * r.denominador); assert(not cin.fail()); } return; assert(cin.fail()); 25 Introdução à Programação 2003/2004 Procedimento escreve() /** Escreve um racional no ecrã no formato de uma fracção. @pre V. @post cout.fail() cout contém n/d (ou simplesmente n, se d = 1) em que n e d são os valores de r.numerador e r.denominador. */ void escreve(Racional const r) { cout << r.numerador; if(r.denominador != 1) cout << '/' << r.denominador; } 26 Introdução à Programação 2003/2004 Programa principal (I) int main() { // Ler fracções: cout << "Introduza duas fracções (numerador denominador): "; Racional r1, r2; lêPara(r1); lêPara(r2); if(cin.fail()) { cout << "Opps! return 1; } A leitura dos racionais falhou!" << endl; (continua) 27 Introdução à Programação 2003/2004 Programa principal (II) // Calcular racional soma: Racional r = somaDe(r1, r2); // Escrever resultado: cout << "A soma de "; escreve(r1); cout << " com "; escreve(r2); cout << " é "; escreve(r); cout << '.' << endl; } 28 Introdução à Programação 2003/2004 Inicialização Para inicializar um racional: Racional a; a.numerador = 10; a.denominador = 0; Para inicializar um inteiro: Mas como inicializar um racional tão simplesmente como um inteiro? Como evitar inicializações inválidas? int a = 10; int a(10); 29 Introdução à Programação 2003/2004 Rotinas membro? 30 Sim! Classes C++ podem ter rotinas membro! Operação: declaração de rotina membro Método: definição de rotina membro Diz-se que as classes C++ têm operações que são implementadas por métodos Introdução à Programação 2003/2004 Construtores (I) 31 Construir uma instância de um TAD é instanciá-lo Durante a construção é invocada uma operação especial: um construtor Como não definimos um construtor, o compilador forneceu um que não faz nada Introdução à Programação 2003/2004 Construtores: declaração /** Representa números racionais. */ class Racional { public: /** Constrói racional com valor inteiro. Construtor invocável sem argumentos: constrói racional 0/1 @pre V. @post *this = n 0 < denominador mdc(numerador, denominador) = 1. */ Racional(int const n = 0); /** Constrói racional correspondente a n/d. @pre d ≠ 0. @post *this = n/d 0 < denominador mdc(numerador, denominador) = 1. */ Racional(int const n, int const d); int numerador; int denominador; }; 32 Construtor que recebe como argumento o numerador: constrói racional n/1 Construtor que recebe como argumentos o numerador e o denominador: constrói racional n/d Introdução à Programação 2003/2004 Construtores: implementação (I) class Racional { … }; Racional::Racional(int const n) : numerador(n), denominador(1) { Lista de inicializadores assert(0 < denominador); assert(mdc(numerador, denominador) == 1); } (continua) Prefixo identifica classe a que o método pertence 33 Introdução à Programação 2003/2004 Construtores: implementação (II) Racional::Racional(int const n, int const d) { assert(d != 0); Acesso directo if(d < 0) { numerador = denominador } else { numerador = denominador } a atributos da instância impícita -n; = -d; n; = d; Variável, ou melhor, instância implícita, ou seja, a instância que está em construção reduz(*this); assert(0 < denominador); assert(mdc(numerador, denominador) == 1); assert(numerador * d == n * denominador); } 34 Introdução à Programação 2003/2004 Construtores: implementação (III) if(d < 0) { numerador = denominador } else { numerador = denominador } -n; = -d; n; = d; reduz(*this); Garante-se denominador positivo e representação em termos mínimos. Para quê? 35 Introdução à Programação 2003/2004 A reter... *this: explicitação da instância implícita Construtores: Se não forem definidos construtores: 36 operações com mesmo nome da classe não têm tipo de devolução sobrecarregáveis C++ fornece um sem parâmetros e que não faz nada Atributos da instância implícita directamente acessíveis dentro de métodos Operações declaradas dentro da classe Métodos definidos fora da classe Introdução à Programação 2003/2004 O que já podemos fazer Construtores invocados automaticamente Racional r1; Racional r2(6, 9); escreve(r1); escreve(r2); 37 Aparece 0! TAD nunca têm lixo! Aparece 2/3 Introdução à Programação 2003/2004 O que ainda podemos fazer... Racional r(6, 9); r.denominador = 0; O denominador tem de ser diferente de zero. Como impedir acesso indevidos? 38 Introdução à Programação 2003/2004 Categorias de acesso 39 Os membros podem ser públicos (public) protegidos (protected) privados (private) Introdução à Programação 2003/2004 Categorias de acesso Os membros podem ser públicos (public) protegidos (protected) privados (private) Acessíveis por todos POO! Acessíveis apenas pelos membros da classe 40 Introdução à Programação 2003/2004 Princípio do encapsulamento Tudo o que pode ser privado, deve ser privado! Regra: Todos os atributos das classes devem ser privados Excepção: constantes podem ocasionalmente ser públicas 41 Os construtores da classe foram feitos públicos: porquê? Introdução à Programação 2003/2004 Atributos privados /** Representa números racionais. */ class Racional { public: /** Constrói racional com valor inteiro. @pre V. @post *this = n 0 < denominador mdc(numerador, denominador) = 1. */ Racional(int const n = 0); /** Constrói racional correspondente a n/d. @pre d ≠ 0. @post *this = n/d 0 < denominador mdc(numerador, denominador) = 1. */ Racional(int const n, int const d); private: int numerador; int denominador; }; 42 Introdução à Programação 2003/2004 Continua tudo a funcionar? Racional r(6, 9); escreve(r); Não tem acesso aos atributos por serem privados. Faça-se o procedimento membro! 43 Introdução à Programação 2003/2004 Operação Racional::escreve() /** Representa números racionais. */ class Racional { public: … /** Escreve um racional no ecrã no formato de uma fracção. @pre V. @post cout.fail() ou cout contém n/d (ou simplesmente n, se d = 1) em que n e d são os valores de numerador e denominador. */ void escreve(); private: int numerador; int denominador; }; 44 Operação pública. Porquê? Introdução à Programação 2003/2004 Método Racional::escreve() void Racional::escreve() { cout << numerador; if(denominador != 1) cout << '/' << denominador; } 45 Introdução à Programação 2003/2004 Invocação de operações Operador de selecção de membro: . Racional r1(); Racional r2(6, 9); r1.escreve(); r2.escreve(); Numerador de quem? 46 void Racional::escreve() { cout << numerador; if(denominador != 1) cout << '/' << denominador; } Introdução à Programação 2003/2004 Operação Racional::somaCom() /** Representa números racionais. */ class Racional { public: … /** Devolve a soma de dois racionais. @pre denominador ≠ 0 r2.denominador ≠ 0. @post somaDe = *this + r2 denominador ≠ 0 somaDe.denominador ≠ 0 mdc(somaDe.numerador, somaDe.denominador) = 1. */ Racional somaCom(Racional const r2); private: int numerador; int denominador; }; 47 Introdução à Programação 2003/2004 Método Racional::somaCom() Racional Racional::somaCom(Racional const r2) { assert(denominador != 0); assert(r2.denominador != 0); Soma da instância implícita com r2. Racional r; r.numerador = numerador * r2.denominador + r2.numerador * denominador; r.denominador = denominador * r2.denominador; r.reduz(); assert(denominador != 0); assert(r.denominador != 0); assert(mdc(r.numerador, r.denominador) == 1); return r; } 48 Introdução à Programação 2003/2004 Operação Racional::lê() /** Representa números racionais. */ class Racional { public: … /** Lê do teclado um racional, na forma de dois inteiros sucessivos. @pre *this = r. @post Se cin.good() cin tem dois inteiros n e d disponíveis para leitura, com d <> 0, então *this = n/d cin.fail() 0 < denominador mdc(numerador, denominador) = 1, senão *this = r cin.fail(). */ void lê(); private: int numerador; int denominador; }; 49 Introdução à Programação 2003/2004 Método Racional::lê() n, d; voidint Racional::lê() { cin >> n >> d; … if(not cin.fail()) } if(d == 0) cin.setstate(ios_base::failbit); else { if(d < 0) { numerador = -n; denominador = -d; } else { numerador = n; denominador = d; } reduz(); assert(0 < denominador); assert(mdc(numerador, denominador) == 1); assert(numerador * d == n * denominador); assert(not cin.fail()); } return; assert(cin.fail()); 50 Introdução à Programação 2003/2004 Operação Racional::reduz() /** Representa números racionais. */ class Racional { public: … Operação privada. Porquê? private: /** Reduz a fracção que representa o racional recebido como argumento. @pre denominador ≠ 0 r = r. @post denominador ≠ 0 mdc(numerador, denominador) = 1 *this = r. */ void reduz(); int numerador; int denominador; }; 51 Introdução à Programação 2003/2004 Método Racional::reduz() void Racional::reduz() { assert(denominador != 0); int const divisor = mdc(numerador, denominador); numerador /= divisor; denominador /= divisor; assert(denominador != 0); assert(mdc(numerador, denominador) == 1); } 52 Introdução à Programação 2003/2004 Programa principal (I) int main() { // Ler fracções: cout << "Introduza duas fracções (numerador denominador): "; Racional r1, r2; r1.lê(); r2.lê(); if(cin.fail()) { cout << "Opps! return 1; } A leitura dos racionais falhou!" << endl; (continua) 53 Introdução à Programação 2003/2004 Programa principal (II) (continuação) // 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; Horrendo! } 54 Introdução à Programação 2003/2004 E mdc()? Deveria passar a membro? Porquê? 55 Introdução à Programação 2003/2004 Classe é módulo por excelência Interface 56 Parte pública Implementação Parte privada Métodos (implementação das operações) Manual de utilização (contrato) Comentário de documentação da classe Manual de utilização de cada operação pública Introdução à Programação 2003/2004 Desenho de TAD 57 Começar sempre: pela interface e pelo contrato. Introdução à Programação 2003/2004 Condição Invariante da Classe Condição Invariante da Classe (CIC) Racional: 0 < denominador mdc(numerador, denominador) = 1 Condição mais forte que se verifica sempre para todas as instâncias de uma classe Reflecte as assunções do produtor da classe acerca da sua implementação Objectivo: verificar erros do programador Deve verificar-se no início e no fim de cada método nãoprivado (no final dos construtores) 58 Introdução à Programação 2003/2004 Operação Racional::cumpreInvariante() /** Representa números racionais. @invariant 0 < denominador mdc(numerador, denominador) = 1. */ class Racional { public: … private: /** Indica se a CIC se verifica. @pre V. @post cumpreInvariante = 0 < denominador mdc(numerador, denominador) = 1. */ bool cumpreInvariante(); int numerador; int denominador; }; 59 Introdução à Programação 2003/2004 Método Racional::cumpreInvariante() bool Racional::cumpreInvariante() { return 0 < denominador and mdc(numerador, denominador) == 1; } 60 Introdução à Programação 2003/2004 Operação Racional::Racional() /** … */ class Racional { public: /** Constrói racional com valor inteiro. @pre V. @post *this = n. */ Racional(int const n = 0); … private: … }; 61 Introdução à Programação 2003/2004 Método Racional::Racional() Racional::Racional(int const n) : numerador(n), denominador(1) { assert(cumpreInvariante()); } 62 Introdução à Programação 2003/2004 Operação Racional::Racional() /** … */ class Racional { public: … /** Constrói racional correspondente a n/d. @pre d ≠ 0. @post *this = n/d. */ Racional(int const n, int const d); … private: … }; 63 Introdução à Programação 2003/2004 Método Racional::Racional() Racional::Racional(int const n, int const d) { assert(d != 0); if(d < 0) { numerador = denominador } else { numerador = denominador } -n; = -d; n; = d; reduz(); assert(cumpreInvariante()); assert(numerador * d == n * denominador); } 64 Introdução à Programação 2003/2004 Operação Racional::escreve() /** … */ class Racional { public: … /** Escreve um racional no ecrã no formato de uma fracção. @pre V. @post cout.fail() ou cout contém n/d (ou simplesmente n, se d = 1) em que n e d são os valores de numerador e denominador. */ void escreve(); … private: … }; 65 Introdução à Programação 2003/2004 Método Racional::escreve() void Racional::escreve() { assert(cumpreInvariante()); cout << numerador; if(denominador != 1) cout << '/' << denominador; assert(cumpreInvariante()); } 66 Introdução à Programação 2003/2004 Operação Racional::somaCom() /** … */ class Racional { public: … /** Devolve a soma de dois racionais. @pre V. @post somaDe = *this + r2. */ Racional somaCom(Racional const r2); … private: … }; 67 Introdução à Programação 2003/2004 Método Racional::somaCom() Racional Racional::somaCom(Racional const r2) { assert(cumpreInvariante()); assert(r2.cumpreInvariante()); Racional r; r.numerador = numerador * r2.denominador + r2.numerador * denominador; r.denominador = denominador * r2.denominador; r.reduz(); assert(cumpreInvariante()); assert(r.cumpreInvariante()); return r; } 68 Introdução à Programação 2003/2004 Operação Racional::lê() /** … */ class Racional { public: … /** Lê do teclado um racional, na forma de dois inteiros sucessivos. @pre *this = r. @post Se cin.good() cin tem dois inteiros n e d disponíveis para leitura, com d <> 0, então *this = n/d cin.fail(), senão *this = r cin.fail(). */ void lê(); private: … }; 69 Introdução à Programação 2003/2004 Método Racional::lê() voidassert(cumpreInvariante()); Racional::lê() { int n, d; … cin >> n >> d; } if(not cin.fail()) 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); assert(not cin.fail()); } return; assert(cumpreInvariante()); assert(cin.fail()); 70 Introdução à Programação 2003/2004 Aula 10: Sumário 71 Necessidade de TAD: acrescentando tipos ao C++ Sintaxe da definição de classes C++ Sintaxe da definição de variáveis e constantes de uma classe C++: as instâncias e a instanciação Variáveis e constantes membro: instâncias membro ou atributos Acesso a atributos membro de uma classe C++: operador de selecção de membro. Rotinas membro: Operações e métodos Declaração vs. definição. A construção TAD:: Acesso a membros de uma classe C++: a instância implícita Construtores: Sintaxe Utilização Parâmetros com argumentos por omissão de novo Categorias e políticas de acesso: Membros públicos Membros privados Princípio do encapsulamento: aplicação aos TAD Noção de condição invariante de classe (CIC): regras e vantagens Exemplos com o TAD Racional, para concretização do conceito de número racional Introdução à Programação 2003/2004