Aula 10
Tipos Abstractos de Dados I
2003/2004Introdução à Programação2
Flashback
Lembram-se da Aula 4?
2003/2004Introdução à Programação3
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)
2003/2004Introdução à Programação4
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)
2003/2004Introdução à Programação5
Soma de fracções (III)
/** Lê do teclado uma fracção, na forma de dois inteiros sucessivos. @pre numerador = n denominador = 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 = 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/2004Introdução à Programação6
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!
2003/2004Introdução à Programação7
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)
2003/2004Introdução à Programação8
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/2004Introdução à Programação9
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/2004Introdução à Programação10
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/2004Introdução à Programação11
Objectivo
Escrever programa para somar fracções tão simples como para somar inteiros
Ou seja…
2003/2004Introdução à Programação12
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…
2003/2004Introdução à Programação13
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/2004Introdução à Programação14
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/2004Introdução à Programação15
TAD Racional
/** Representa números racionais. */class Racional { public: int numerador; int denominador;};
Variáveis membro ou atributos
Variáveis membro ou atributos
Atenção ao ; final!
2003/2004Introdução à Programação16
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;};
…
2003/2004Introdução à Programação17
Representação gráfica do TAD
Racional
numerador: intdenominador: int
Atributos: instânciasmembro
Operações: rotinasmembro
Nome
2003/2004Introdução à Programação18
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/2004Introdução à Programação19
Representações gráficas (I)
r1: Racional
numerador = ?denominador = ?
r2: Racional
numerador = ?denominador = ?
ObjectosInstâncias do TAD
Há quem lhes chame objectos, mas reservaremos esse nome para as classes propriamente ditas.
r1: Racional
numerador = 6denominador = 9
r2: Racional
numerador = 7denominador = 3
2003/2004Introdução à Programação20
Representações gráficas (II)
r1: Racional
numerador: int
6
denominador: int
9
r2: Racional
numerador: int
7
denominador: int
3
2003/2004Introdução à Programação21
Acesso a membros de instâncias de um TAD
Operador de selecção de membro: .
instância.membro
2003/2004Introdução à Programação22
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; }
A fazer.
Nome sem sufixo Fracção: redundante dado tipo dos parâmetros.
2003/2004Introdução à Programação23
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.
2003/2004Introdução à Programação24
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) { …}
2003/2004Introdução à Programação25
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/2004Introdução à Programação26
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; }
2003/2004Introdução à Programação27
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/2004Introdução à Programação28
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/2004Introdução à Programação29
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/2004Introdução à Programação30
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/2004Introdução à Programação31
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/2004Introdução à Programação32
Construtores: declaração
/** 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 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/2004Introdução à Programação33
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/2004Introdução à Programação34
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/2004Introdução à Programação35
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/2004Introdução à Programação36
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/2004Introdução à Programação37
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/2004Introdução à Programação38
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/2004Introdução à Programação39
Categorias de acesso
Os membros podem ser públicos (public) protegidos (protected) privados (private)
2003/2004Introdução à Programação40
Categorias de acesso
Os membros podem ser públicos (public) protegidos (protegidos (protectedprotected)) privados (private)
POO!
Acessíveis por todos
Acessíveis apenas pelos membros da classe
2003/2004Introdução à Programação41
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/2004Introdução à Programação42
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; };
2003/2004Introdução à Programação43
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/2004Introdução à Programação44
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ê?
2003/2004Introdução à Programação45
Método Racional::escreve()
void Racional::escreve() { cout << numerador; if(denominador != 1) cout << '/' << denominador; }
2003/2004Introdução à Programação46
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/2004Introdução à Programação47
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; };
2003/2004Introdução à Programação48
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/2004Introdução à Programação49
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; };
2003/2004Introdução à Programação50
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/2004Introdução à Programação51
Operação Racional::reduz()
/** Representa números racionais. */class Racional { public: …
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; };
Operação privada. Porquê?
2003/2004Introdução à Programação52
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/2004Introdução à Programação53
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/2004Introdução à Programação54
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/2004Introdução à Programação55
E mdc()?
Deveria passar a membro?
Porquê?
2003/2004Introdução à Programação56
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/2004Introdução à Programação57
Desenho de TAD
Começar sempre:
pela interface e
pelo contrato.
2003/2004Introdução à Programação58
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/2004Introdução à Programação59
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; };
2003/2004Introdução à Programação60
Método Racional::cumpreInvariante()
bool Racional::cumpreInvariante() { return 0 < denominador and mdc(numerador, denominador) == 1;}
2003/2004Introdução à Programação61
Operação Racional::Racional()/** … */class Racional { public: /** Constrói racional com valor inteiro. @pre V. @post *this = n. */ Racional(int const n = 0);
…
private: …};
2003/2004Introdução à Programação62
Método Racional::Racional()Racional::Racional(int const n) : numerador(n), denominador(1){
assert(cumpreInvariante()); }
2003/2004Introdução à Programação63
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: …};
2003/2004Introdução à Programação64
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/2004Introdução à Programação65
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: …};
2003/2004Introdução à Programação66
Método Racional::escreve()
void Racional::escreve() { assert(cumpreInvariante());
cout << numerador; if(denominador != 1) cout << '/' << denominador;
assert(cumpreInvariante());}
2003/2004Introdução à Programação67
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: …};
2003/2004Introdução à Programação68
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/2004Introdução à Programação69
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: …};
2003/2004Introdução à Programação70
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/2004Introdução à Programação71
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