710 likes | 835 Views
Aula 10. Tipos Abstractos de Dados I. Flashback. Lembram-se da Aula 4?. 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 ). */
E N D
Aula 10 Tipos Abstractos de Dados I
Flashback Lembram-se da Aula 4? Introdução à Programação
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); … } (continua) PC relaxada para aceitar inteiros negativos e nulos (ver folhas teóricas) Introdução à Programação
Soma de fracções (II) /** Reduz a fracção recebida como argumento. @pre denominador ≠ 0 numerador = ndenominador = 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) Introdução à Programação
Soma de fracções (III) 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()); /** Lê do teclado uma fracção, na forma de dois inteiros sucessivos. @pre numerador = ndenominador = d. @post 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 = ndenominador = dcin.fail(). */ void lêFracção(int& numerador, int& denominador) { … } (continua) Não existia na Aula 4! Garante-se denominador positivo e representação em termos mínimos. Introdução à Programação
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); 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! Introdução à Programação
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) Introdução à Programação
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) Introdução à Programação
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; } Introdução à Programação
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 Introdução à Programação
Objectivo • Escrever programa para somar fracções tão simples como para somar inteiros • Ou seja… Introdução à Programação
O nosso objectivo #include <iostream> 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… Introdução à Programação
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) Introdução à Programação
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 Introdução à Programação
Variáveis membro ou atributos Variáveis membro ou atributos TAD Racional /** Representa números racionais. */ class Racional { public: int numerador; int denominador; }; Atenção ao ; final! Introdução à Programação
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; }; … Introdução à Programação
Racional numerador: int denominador: int Representação gráfica do TAD Nome Atributos: instâncias membro Operações: rotinas membro Introdução à Programação
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! Introdução à Programação
r1: Racional r1: Racional r2: Racional r2: Racional numerador = ? denominador = ? numerador = 6 denominador = 9 numerador = ? denominador = ? numerador = 7 denominador = 3 Objectos Instâncias do TAD Representações gráficas (I) Há quem lhes chame objectos, mas reservaremos esse nome para as classes propriamente ditas. Introdução à Programação
r1: Racional r2: Racional numerador: int denominador: int numerador: int denominador: int 6 9 7 3 Representações gráficas (II) Introdução à Programação
Acesso a membros de instâncias de um TAD • Operador de selecção de membro: . instância.membro Introdução à Programação
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); 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; } Nome sem sufixo Fracção: redundante dado tipo dos parâmetros. A fazer. Introdução à Programação
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; assert(r.denominador != 0); assert(mdc(r.numerador, r.denominador) == 1); } Nome sem sufixo Fracção: redundante dado tipo dos parâmetros. Introdução à Programação
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/dcin.fail() 0 < r.denominador mdc(r.numerador, r.denominador) = 1, senão r = rcin.fail(). */ void lêPara(Racional& r) { … } Introdução à Programação
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()); Introdução à Programação
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; } Introdução à Programação
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) Introdução à Programação
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; } Introdução à Programação
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? Introdução à Programação
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 Introdução à Programação
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 Introdução à Programação
Construtores: declaração Construtor invocável sem argumentos: constrói racional 0/1 /** 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); int numerador; int denominador; }; 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
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 Introdução à Programação
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); } Acesso directo a atributos da instância impícita Variável, ou melhor, instância implícita, ou seja, a instância que está em construção Introdução à Programação
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ê? Introdução à Programação
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 Introdução à Programação
O que já podemos fazer Construtores invocados automaticamente Racional r1; Racional r2(6, 9); escreve(r1); escreve(r2); Aparece 0! TAD nunca têm lixo! Aparece 2/3 Introdução à Programação
O que ainda podemos fazer... Racional r(6, 9); r.denominador = 0; O denominador tem de ser diferente de zero. Como impedir acesso indevidos? Introdução à Programação
Categorias de acesso • Os membros podem ser • públicos (public) • protegidos (protected) • privados (private) Introdução à Programação
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 Introdução à Programação
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 Introdução à Programação
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; }; Introdução à Programação
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! Introdução à Programação
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; }; Operação pública. Porquê? Introdução à Programação
Método Racional::escreve() void Racional::escreve() { cout << numerador; if(denominador != 1) cout << '/' << denominador; } Introdução à Programação
Invocação de operações • Operador de selecção de membro: . Racional r1(); Racional r2(6, 9); r1.escreve(); r2.escreve(); void Racional::escreve() { cout << numerador; if(denominador != 1) cout << '/' << denominador; } Numerador de quem? Introdução à Programação
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; }; Introdução à Programação
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. Introdução à Programação
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/dcin.fail() 0 < denominador mdc(numerador, denominador) = 1, senão *this = rcin.fail(). */ void lê(); private: int numerador; int denominador; }; Introdução à Programação
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()); Introdução à Programação