Aula 10 Tipos Abstractos de Dados I
2003/2004 Introdução à Programação 2 Flashback Lembram-se da Aula 4?
2003/2004 Introdução à Programação 3 Soma de fracções (I) #include using namespace std; /** Devolve o máximo divisor comum dos inteiros passados como m 0 ou n mdc = mdc( m, n ). */ int mdc(int const m, int const n) { assert(m != 0 or n != 0); … } (continua) PC relaxada para aceitar inteiros negativos e nulos (ver folhas teóricas)
2003/2004 Introdução à Programação 4 Soma de fracções (II) /** Reduz a fracção recebida como denominador 0 numerador = n denominador = 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)
2003/2004 Introdução à Programação 5 Soma de fracções (III) /** Lê do teclado uma fracção, na forma de dois inteiros numerador = n denominador = Se cin. good () cin tem dois inteiros n' e d' disponíveis para leitura, com d' 0, então 0 < denominador mdc( numerador, denominador ) = 1 numerador / denominador = n'/d' cin. fail (), senão numerador = n denominador = d cin. fail (). */ void lêFracção(int& numerador, int& denominador) { … } (continua) 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; } reduzFracção(numerador, denominador); assert(0 < denominador); assert(mdc(numerador, denominador) == 1); assert(numerador * d == n * denominador); assert(not cin.fail()); return; } assert(cin.fail()); Não existia na Aula 4! Garante-se denominador positivo e representação em termos mínimos.
2003/2004 Introdução à Programação 6 Soma de fracções (IV) /** Soma duas denominador1 0 denominador2 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); numerador = numerador1 * denominador2 + numerador2 * denominador1; denominador = denominador1 * denominador2; reduzFracção(numerador, denominador); assert(denominador != 0); assert(mdc(numerador, denominador) == 1); } (continua) Não existia na Aula 4!
2003/2004 Introdução à Programação 7 Soma de fracções (V) /** Escreve uma fracção no ecrã no formato 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)
2003/2004 Introdução à Programação 8 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! A leitura das fracções falhou!" << endl; return 1; } (continua)
2003/2004 Introdução à Programação 9 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; }
2003/2004 Introdução à Programação 10 Problemas Dois inteiros para cada fracção Não é possível desenvolver funções para somar fracções: funções só devolvem um valor Código complexo e difícil de perceber
2003/2004 Introdução à Programação 11 Objectivo Escrever programa para somar fracções tão simples como para somar inteiros Ou seja…
2003/2004 Introdução à Programação 12 O nosso objectivo #include using namespace std; … int main() { 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; } Lá chegaremos, lá chegaremos…
2003/2004 Introdução à Programação 13 Solução 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)
2003/2004 Introdução à Programação 14 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)… Pormenores só em POO
2003/2004 Introdução à Programação 15 TAD Racional /** Representa números racionais. */ class Racional { public: int numerador; int denominador; }; Variáveis membro ou atributos Atenção ao ; final!
2003/2004 Introdução à Programação 16 TAD Racional #include using namespace std; int mdc(int const m, int const n) { … } /** Representa números racionais. */ class Racional { public: int numerador; int denominador; }; …
2003/2004 Introdução à Programação 17 Representação gráfica do TAD Racional numerador : int denominador : int Atributos: instâncias membro Operações: rotinas membro Nome
2003/2004 Introdução à Programação 18 Utilização do TAD Racional r1; Racional r2; r1.numerador = 6; r1.denominador = 9; r2.numerador = 7; r2.denominador = 3; Cada instância de Racional tem os seus próprios atributos!
2003/2004 Introdução à Programação 19 Representações gráficas (I) r1 : Racional numerador = ? denominador = ? r2 : Racional numerador = ? denominador = ? Objectos Instâncias do TAD Há quem lhes chame objectos, mas reservaremos esse nome para as classes propriamente ditas. r1 : Racional numerador = 6 denominador = 9 r2 : Racional numerador = 7 denominador = 3
2003/2004 Introdução à Programação 20 Representações gráficas (II) r1 : Racionalnumerador : int 6 denominador : int 9 r2 : Racionalnumerador : int 7 denominador : int 3
2003/2004 Introdução à Programação 21 Acesso a membros de instâncias de um TAD Operador de selecção de membro:. instância.membro
2003/2004 Introdução à Programação 22 Função somaDe() /** Devolve a soma de dois r1.denominador 0 r2.denominador somaDe = r1 + r2 somaDe.denominador 0 mdc( somaDe.numerador, somaDe.denominador ) = 1. */ Racional somaDe(Racional const r1, Racional const r2) { assert(r1.denominador != 0); assert(r2.denominador != 0); 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); return r; } A fazer. Nome sem sufixo Fracção : redundante dado tipo dos parâmetros.
2003/2004 Introdução à Programação 23 Procedimento reduz() /** Reduz a fracção que representa o racional recebido como r.denominador 0 r = 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; assert(r.denominador != 0); assert(mdc(r.numerador, r.denominador) == 1); } Nome sem sufixo Fracção : redundante dado tipo dos parâmetros.
2003/2004 Introdução à Programação 24 Procedimento lêPara() /** Lê do teclado um racional, na forma de dois inteiros r = 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) { … }
2003/2004 Introdução à Programação 25 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());
2003/2004 Introdução à Programação 26 Procedimento escreve() /** Escreve um racional no ecrã no formato de uma 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; }
2003/2004 Introdução à Programação 27 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! A leitura dos racionais falhou!" << endl; return 1; } (continua)
2003/2004 Introdução à Programação 28 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; }
2003/2004 Introdução à Programação 29 Inicialização Para inicializar um racional: Racional a; a.numerador = 10; a.denominador = 0; Para inicializar um inteiro: int a = 10; int a(10); Mas como inicializar um racional tão simplesmente como um inteiro? Como evitar inicializações inválidas?
2003/2004 Introdução à Programação 30 Rotinas membro? 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
2003/2004 Introdução à Programação 31 Construtores (I) 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
2003/2004 Introdução à Programação 32 Construtores: declaração /** Representa números racionais. */ class Racional { public: /** Constrói racional com valor * this = n 0 < denominador mdc( numerador, denominador ) = 1. */ Racional(int const n = 0); /** Constrói racional correspondente a n / d * this = n / d 0 < denominador mdc( numerador, denominador ) = 1. */ Racional(int const n, int const d); int numerador; int denominador; }; Construtor invocável sem argumentos: constrói racional 0/1 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
2003/2004 Introdução à Programação 33 Construtores: implementação (I) class Racional { … }; Racional::Racional(int const n) : numerador(n), denominador(1) { assert(0 < denominador); assert(mdc(numerador, denominador) == 1); } (continua) Lista de inicializadores Prefixo identifica classe a que o método pertence
2003/2004 Introdução à Programação 34 Construtores: implementação (II) Racional::Racional(int const n, int const d) { assert(d != 0); if(d < 0) { numerador = -n; denominador = -d; } else { numerador = n; denominador = d; } reduz(*this); assert(0 < denominador); assert(mdc(numerador, denominador) == 1); assert(numerador * d == n * denominador); } Variável, ou melhor, instância implícita, ou seja, a instância que está em construção Acesso directo a atributos da instância impícita
2003/2004 Introdução à Programação 35 Construtores: implementação (III) if(d < 0) { numerador = -n; denominador = -d; } else { numerador = n; denominador = d; } reduz(*this); Garante-se denominador positivo e representação em termos mínimos. Para quê?
2003/2004 Introdução à Programação 36 A reter... *this : explicitação da instância implícita Construtores: operações com mesmo nome da classe não têm tipo de devolução sobrecarregáveis Se não forem definidos construtores: 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
2003/2004 Introdução à Programação 37 O que já podemos fazer Racional r1; Racional r2(6, 9); escreve(r1); escreve(r2); Aparece 0! TAD nunca têm lixo! Aparece 2/3 Construtores invocados automaticamente
2003/2004 Introdução à Programação 38 O que ainda podemos fazer... Racional r(6, 9); r.denominador = 0; O denominador tem de ser diferente de zero. Como impedir acesso indevidos?
2003/2004 Introdução à Programação 39 Categorias de acesso Os membros podem ser públicos ( public ) protegidos ( protected ) privados ( private )
2003/2004 Introdução à Programação 40 Categorias de acesso POO! Acessíveis por todos Acessíveis apenas pelos membros da classe
2003/2004 Introdução à Programação 41 Princípio do encapsulamento Tudo o que pode ser privado, deve ser privado! Regra: Todos os atributos das classes devem ser privados Os construtores da classe foram feitos públicos: porquê? Excepção: constantes podem ocasionalmente ser públicas
2003/2004 Introdução à Programação 42 Atributos privados /** Representa números racionais. */ class Racional { public: /** Constrói racional com valor * this = n 0 < denominador mdc( numerador, denominador ) = 1. */ Racional(int const n = 0); /** Constrói racional correspondente a n / d * this = n / d 0 < denominador mdc( numerador, denominador ) = 1. */ Racional(int const n, int const d); private: int numerador; int denominador; };
2003/2004 Introdução à Programação 43 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!
2003/2004 Introdução à Programação 44 Operação Racional::escreve() /** Representa números racionais. */ class Racional { public: … /** Escreve um racional no ecrã no formato de uma 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; }; Operação pública. Porquê?
2003/2004 Introdução à Programação 45 Método Racional::escreve() void Racional::escreve() { cout << numerador; if(denominador != 1) cout << '/' << denominador; }
2003/2004 Introdução à Programação 46 Invocação de operações Operador de selecção de membro:. Racional r1(); Racional r2(6, 9); r1.escreve(); r2.escreve(); Numerador de quem? void Racional::escreve() { cout << numerador; if(denominador != 1) cout << '/' << denominador; }
2003/2004 Introdução à Programação 47 Operação Racional::somaCom() /** Representa números racionais. */ class Racional { public: … /** Devolve a soma de dois denominador 0 r2.denominador somaDe = * this + r2 denominador 0 somaDe.denominador 0 mdc( somaDe.numerador, somaDe.denominador ) = 1. */ Racional somaCom(Racional const r2); private: int numerador; int denominador; };
2003/2004 Introdução à Programação 48 Método Racional::somaCom() Racional Racional::somaCom(Racional const r2) { assert(denominador != 0); assert(r2.denominador != 0); 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; } Soma da instância implícita com r2.
2003/2004 Introdução à Programação 49 Operação Racional::lê() /** Representa números racionais. */ class Racional { public: … /** Lê do teclado um racional, na forma de dois inteiros * this = 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; };
2003/2004 Introdução à Programação 50 Método Racional::lê() void 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(0 < denominador); assert(mdc(numerador, denominador) == 1); assert(numerador * d == n * denominador); assert(not cin.fail()); return; } assert(cin.fail());
2003/2004 Introdução à Programação 51 Operação Racional::reduz() /** Representa números racionais. */ class Racional { public: … private: /** Reduz a fracção que representa o racional recebido como denominador 0 r = denominador 0 mdc( numerador, denominador ) = 1 * this = r. */ void reduz(); int numerador; int denominador; }; Operação privada. Porquê?
2003/2004 Introdução à Programação 52 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); }
2003/2004 Introdução à Programação 53 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! A leitura dos racionais falhou!" << endl; return 1; } (continua)
2003/2004 Introdução à Programação 54 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!
2003/2004 Introdução à Programação 55 E mdc() ? Deveria passar a membro? Porquê?
2003/2004 Introdução à Programação 56 Classe é módulo por excelência Interface 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
2003/2004 Introdução à Programação 57 Desenho de TAD Começar sempre: pela interface e pelo contrato.
2003/2004 Introdução à Programação 58 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ão- privado (no final dos construtores)
2003/2004 Introdução à Programação 59 Operação Racional::cumpreInvariante() /** Representa números 0 < denominador mdc( numerador, denominador ) = 1. */ class Racional { public: … private: /** Indica se a CIC se cumpreInvariante = 0 < denominador mdc( numerador, denominador ) = 1. */ bool cumpreInvariante(); int numerador; int denominador; };
2003/2004 Introdução à Programação 60 Método Racional::cumpreInvariante() bool Racional::cumpreInvariante() { return 0 < denominador and mdc(numerador, denominador) == 1; }
2003/2004 Introdução à Programação 61 Operação Racional::Racional() /** … */ class Racional { public: /** Constrói racional com valor * this = n. */ Racional(int const n = 0); … private: … };
2003/2004 Introdução à Programação 62 Método Racional::Racional() Racional::Racional(int const n) : numerador(n), denominador(1) { assert(cumpreInvariante()); }
2003/2004 Introdução à Programação 63 Operação Racional::Racional() /** … */ class Racional { public: … /** Constrói racional correspondente a n / d * this = n / d. */ Racional(int const n, int const d); … private: … };
2003/2004 Introdução à Programação 64 Método Racional::Racional() Racional::Racional(int const n, int const d) { assert(d != 0); if(d < 0) { numerador = -n; denominador = -d; } else { numerador = n; denominador = d; } reduz(); assert(cumpreInvariante()); assert(numerador * d == n * denominador); }
2003/2004 Introdução à Programação 65 Operação Racional::escreve() /** … */ class Racional { public: … /** Escreve um racional no ecrã no formato de uma 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: … };
2003/2004 Introdução à Programação 66 Método Racional::escreve() void Racional::escreve() { assert(cumpreInvariante()); cout << numerador; if(denominador != 1) cout << '/' << denominador; assert(cumpreInvariante()); }
2003/2004 Introdução à Programação 67 Operação Racional::somaCom() /** … */ class Racional { public: … /** Devolve a soma de dois somaDe = * this + r2. */ Racional somaCom(Racional const r2); … private: … };
2003/2004 Introdução à Programação 68 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; }
2003/2004 Introdução à Programação 69 Operação Racional::lê() /** … */ class Racional { public: … /** Lê do teclado um racional, na forma de dois inteiros * this = 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: … };
2003/2004 Introdução à Programação 70 Método Racional::lê() void Racional::lê() { … } assert(cumpreInvariante()); 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());
2003/2004 Introdução à Programação 71 Aula 10: Sumário 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