CES-11 ALGORITMOS E ESTRUTURAS DE DADOS Capítulo IX Programação Orientada a Objetos Capítulo IX – Programação Orientada a Objetos 9.1 – Classes, objetos e métodos 9.2 – Herança 9.3 – Polimorfismo 9.1 – Classes, Objetos e Métodos 9.1.1 – Revendo tipos abstratos de dados Programação Orientada a Objetos (POO) é um paradigma de programação originado do conceito de tipos abstratos de dados (TAD’s) Tal paradigma é usado em linguagens como C++, Java, Delphy, C#, Python, Visual Basic (a partir da versão 4), etc. O MATLAB, a partir da versão 7.6, lançada em 03/2008, tem utilizado princípios de orientação a objetos Este capítulo será ilustrado com a Linguagem C++ TAD é um modelo de armazenamento de informações para o qual é definida uma estrutura e uma relação de operadores Por questões de disciplina, operadores fora da relação não podem ser aplicados a esses tipos Tais operadores são implementados em C por funções Numa região do programa, declara-se a estrutura de dados e define-se as funções para os operadores do TAD No resto do programa podem ser declaradas variáveis desse TAD Essas variáveis só podem figurar em chamadas dessas funções-operadoras Exemplo: programa com o TAD lista linear struct lista { int ultimo, elementos[51]; }; Funções com os operadores de listas lineares lista L, L1, L2; Região de definição do TAD lista Operadores previstos para o TAD lista: Inserir (x, p, L): insere elemento x na posição p da lista L Deletar (p, L): deleta elemento da posição p da lista L Conteudo (p, L): retorna elemento da posição p da lista L Alterar (x, p, L): altera para x o conteúdo da posição p da lista L Exemplo: programa com o TAD lista linear Operadores: struct lista { int ultimo, elementos[51]; }; Inserir (x, p, L) Deletar (p, L) Conteúdo (p, L) Alterar (x, p, L) Funções com os operadores de listas lineares lista L, L1, L2; Nesta região, a estrutura interna de uma lista deve ser considerada desconhecida Exemplo: programa com o TAD lista linear Operadores: struct lista { int ultimo, elementos[51]; }; Inserir (x, p, L) Deletar (p, L) Conteúdo (p, L) Alterar (x, p, L) Funções com os operadores de listas lineares Já que (L.elementos[i] (L.ultimo (x = L.elementos[i];) = --= - - x;) - - ;) Proibido: é proibido, usar lista L, L1, L2; L.ultimo = muda -que - - -atribui -o- ; a x = Conteúdo Inserir Alterar (x, i, L)(i, –que que L) provoca acréscimo xconteúdo o conteúdo de unitário L.elementos[i] de L.elementos[i] em L.ultimo, ou L.elementos[i] = x; Deletar (p, L) – que provoca x = unitário L.elementos[i]; decréscimo 9.1.2 – Correspondência entre TAD’s e POO Em Programação Orientada a Objetos: Um TAD corresponde a uma classe Uma variável de tal TAD corresponde a um objeto de tal classe Os campos da estrutura de dados de tal TAD correspondem aos atributos dos objetos de tal classe Uma função-operadora de tal TAD corresponde a um método de tal classe Em vários programas escritos e elaborados em CES-11, foi usado o conceito de TAD’s O professor exigiu dos alunos a disciplina de impedir que variáveis de certos TAD’s figurassem em operações não definidas para os mesmos Em POO essa disciplina pode ser exigida pelo compilador O acesso à estrutura de dados de um TAD ficou restrito às funções-operadoras do mesmo O programador fica impedido de desobedecê-la Esconder a estrutura de um TAD ou classe é chamado de encapsular 9.1.3 – Definições Em POO, combina-se numa única entidade sua estrutura de dados e as funções que operam sobre essa estrutura Tal entidade é denominada objeto Classe é uma categoria de objetos É na declaração de uma classe que se especifica a estrutura de dados e as funções que operam sobre os objetos da mesma Classes correspondem a tipos e objetos correspondem a variáveis Os elementos que compõem a estrutura de dados de uma classe são denominados atributos dos objetos da mesma As funções que operam nos objetos de uma classe são denominadas métodos dessa classe Exemplo: uma classe com Apenas um atributo Um método (função) para atribuir valor ao atributo Um método (função) para mostrar o atributo no vídeo #include <iostream> Contém os objetos cout e cin #include <conio.h> using namespace std; Indica que o atributo x é visível class cl { apenas dentro da class cl private: int x; Indica que os métodos AtribValor e MostraValor são public: void AtribValor (int d) {x = d;} visíveis em todo o programa void MostraValor () { Comando de cout << "Valor = " << x << "\n"; saída de C++ } }; Mensagem para o objeto obj1 executar o método AtribValor int main () { cl obj1, obj2; obj1.AtribValor (11); obj2.AtribValor (22); obj1.MostraValor (); obj2.MostraValor (); cout << "\n\n"; system("pause"); return 0; } #include <iostream> Resultado #include <conio.h> Valor = 11 using namespace std; Valor = 22 class cl { Pressione ... private: int x; public: void AtribValor (int d) {x = d;} void MostraValor () { cout << "Valor = " << x << "\n"; } Tal como para struct’s, usa-se o }; ponto ‘.’ para acessar os atributos e os métodos de um objeto, de fora de sua classe int main () { cl obj1, obj2; obj1.AtribValor (11); obj2.AtribValor (22); Dentro da classe não se obj1.MostraValor (); obj2.MostraValor (); cout << "\n\n"; system("pause"); return 0; usa o ponto ‘.’ } #include <iostream> Por default, atributos e métodos numa classe #include <conio.h> são private using namespace std; class cl { Para encapsular objetos, os atributos de sua classe devem estar em private e os métodos private: devem estar em public int x; public: void AtribValor (int d) {x = d;} void MostraValor () { cout << "Valor = " << x << "\n"; Aqui o comando obj1.x = 40; } }; seria rejeitado pelo compilador Mas não mais seria, se private fosse int main () { trocado por public cl obj1, obj2; obj1.AtribValor (11); obj2.AtribValor (22); obj1.MostraValor (); obj2.MostraValor (); cout << "\n\n"; system("pause"); return 0; } #include <iostream> Alternativa para declarar uma classe: #include <conio.h> Dentro da classe ficam os atributos e os using namespace std; protótipos dos métodos class cl { As definições dos métodos podem ficar fora private: dos limitantes da classe int x; public: void AtribValor (int); void MostraValor (void); }; :: é um operador de void cl :: AtribValor (int d) { escopo x = d; } AtribValor e MostraValor void cl :: MostraValor () { são métodos da classe cl cout << "Valor = " << x << "\n"; } void main () {- - - - -} 9.1.4 – Programação estruturada versus POO Programação estruturada: um programa é dividido em funções que se comunicam e trocam dados entre si Dados Globais Dados Controle Função main Função f1 Função f2 Programação orientada a objetos: um programa é dividido em objetos que se comunicam entre si através de chamadas de métodos atributos Método 1 Método 2 atributos Objeto 2 Objeto 1 Chamar um método de um objeto pode ser entendido como enviar uma mensagem para esse objeto atributos Método 1 Método 1 Método 2 Método 2 Objeto 3 Na programação estruturada, é difícil estabelecer um elo de ligação entre funções e estruturas de dados com os objetos do mundo real Isso se torna crítico quando os programas são muito grandes Como o mundo real é composto de objetos e não de funções, a POO tem se mostrado melhor modeladora da realidade do que a programação estruturada 9.1.5 – Entrada e saída em C++ Além das funções de entrada e saída da linguagem C, a linguagem C++ tem ainda os objetos cin e cout, da biblioteca iostream cin faz entrada pelo teclado e cout faz saída pelo vídeo O operador endl (end line) substitui o caractere ‘\n’ O operador setw faz formatação de saída (espaço de caracteres em que um item será escrito) Exemplo: seja o seguinte programa Contém o operador setw #include <iostream> Vai duas vezes para o início #include <iomanip> da linha seguinte Escreve a cadeia #include <conio.h> using namespace std; “x^2” num espaço de 10 caracteres, int main () { justaposta à direita int x, m, n; cout << endl << "TABELA DE POTENCIAS" << endl << endl ; cout << "\tDigite o intervalo da tabela: "; cin >> m >> n; cout << endl << endl; cout << setw(5) << "x" << setw(10) << "x^2" << setw(15) << "x^3" << endl; cout << "------------------------------" << endl; for (x = m; x <= n; x++) cout << setw(5) << x << setw(10) << x*x << setw(15) << x*x*x << endl; cout << endl << endl; system("pause"); return 0; } Resultado: TABELA DE POTENCIAS #include <iostream> Digite o intervalo da tabela: 7 12 #include <iomanip> x x^2 x^3 #include <conio.h> -----------------------------using namespace std; 7 49 343 8 64 512 int main () { 9 81 729 int x, m, n; 10 100 1000 11 POTENCIAS" 121 1331 << endl cout << endl << "TABELA DE << endl ; 12 144 1728 cout << "\tDigite o intervalo da tabela: "; cin >> m >> n; Pressione ... cout << endl << endl; cout << setw(5) << "x" << setw(10) << "x^2" << setw(15) << "x^3" << endl; cout << "------------------------------" << endl; for (x = m; x <= n; x++) cout << setw(5) << x << setw(10) << x*x << setw(15) << x*x*x << endl; cout << endl << endl; system("pause"); return 0; } Exemplo: soma de dois números complexos lidos #include <iostream> using namespace std; class complexo { private: double real, imag; public: void Ler (void); void Escrever (void); void Soma (complexo, complexo); }; void complexo :: Ler () { cin >> real >> imag; } void complexo :: Escrever () { cout << "[(" << real << ")+j(" << imag << ")]"; } void complexo :: Soma (complexo a, complexo b) { real = a.real + b.real; imag = a.imag + b.imag; } int main () { complexo a, b, r; char c; cout << "Adicao de complexos? (s/n): "; cin >> c; while (c == 's' || c == 'S') { cout << endl << "\tDigite os dois operandos: "; a.Ler(); b.Ler(); Adicao de complexos? (s/n): s r.Soma(a, b); cout << endl << "\t"; Digite os dois operandos: 2.3 -7.1 -1.2 4.3 a.Escrever(); [(2.3)+j(-7.1)] + [(-1.2)+j(4.3)] = [(1.1)+j(-2.8)] cout << " +Adicao "; de complexos? (s/n): n b.Escrever(); Process returned 0 (0x0) execution time : 48.740 s cout << " =Press "; any key to continue. r.Escrever(); cout << endl << endl; cout << "Adicao de complexos? (s/n): "; cin >> c; } Exercício: estender este programa para fazer return 0; subtração, multiplicação e divisão de complexos } Exemplo: leitura das coordenadas de um ponto #include <iostream> #include <conio.h> using namespace std; typedef char logic; const logic TRUE = 1, FALSE = 0; class ponto { private: int abscissa, ordenada; public: void Setar (int, int); int PegarAbscissa (void); int PegarOrdenada (void); }; void ponto :: Setar (int abs, int ord) { logic ok; ok = abs >= 0 && abs <= 100 && ord >= 0 && ord <= 100; abscissa = ok ? abs : 0; ordenada = ok ? ord : 0; } int ponto :: PegarAbscissa () { return abscissa; } int ponto :: PegarOrdenada () { return ordenada; } int main () { ponto a; int abs, ord; cout << "Digite as coordenadas de um ponto: "; cin >> abs >> ord; a.Setar(abs, ord); cout << endl << "\tPonto digitado: "; cout << "[" << a.PegarAbscissa() << ", " << a.PegarOrdenada() << "]"; cout << endl << endl; system("pause"); return 0; } Digite as coordenadas de um ponto: 3 8 Resultado: Ponto digitado: [3, 8] Pressione ... 9.1.6 – Construtores e destrutores A declaração de um objeto de uma determinada classe aloca tal objeto na memória Isso é feito por um método invisível de tal classe: Tem o mesmo nome da classe, não tem tipo nem parâmetros O método que aloca um objeto é chamado de construtor de sua classe Construtores podem ser programados Exemplo: contas bancárias #include <iostream> #include <conio.h> using namespace std; conta :: conta () { saldo = 0; } class conta { conta :: conta (int inic) { saldo = inic; } private: int saldo; public: conta (void); conta (int); void Creditar (int); int PegaSaldo (void); }; void conta :: Creditar (int quant) { saldo += quant; } int conta :: PegaSaldo () { return saldo; } Quando um construtor alternativo é programado, o construtor padrão também deve ser c1 é inicializada pelo construtor int main () { padrão conta c1, c2 = conta(200); c2 é pelo outro construtor cout << "Saldos iniciais:"; cout << endl << endl << "\tSaldo da conta c1: " << c1.PegaSaldo(); cout << endl << endl << "\tSaldo da conta c2: " << c2.PegaSaldo(); c1.Creditar (500); c2.Creditar (800); Saldos iniciais: cout << endl << endl << "Saldos finais:"; Saldo dada conta c1:c1: 0 " << cout << endl << endl << "\tSaldo conta c1.PegaSaldo(); Saldo da conta c2: 200 cout << endl << endl << "\tSaldo da conta c2: " << c2.PegaSaldo(); Saldos finais: cout << endl << endl; system("pause"); return 0; Saldo da conta c1: 500 } Saldo da conta c2: 1000 Resultado: Pressione ... A desalocação de um objeto é feita por outro método invisível de sua classe Tem o mesmo nome da classe, precedido do caractere ‘~’, não tem tipo nem parâmetros O método que desaloca um objeto é chamado de destrutor de sua classe Destrutores podem ser programados e são usados quando se deseja desalocar alocações dinâmicas class Exemplo { private: int dado; Exemplo: public: Exemplo () {dado = 0;} ~Exemplo () {} }; 9.1.7 – Atributos estáticos Até agora, os atributos vistos pertencem ao conjunto particular de cada objeto de uma classe No entanto pode haver atributos pertencentes à classe como um todo Esses atributos devem ser declarados com a palavra reservada static Eles existem independentemente de haver objetos declarados de tal classe Exemplo: atributos estáticos #include <iostream> #include <conio.h> using namespace std; class ccc { private: static int atr; public: ccc (void); void SomarAtr (void); int PegaAtr (void); ccc :: ccc () { atr = 0; } void ccc :: SomarAtr () { atr++; } int ccc :: PegaAtr () { return atr; } int ccc :: atr; }; O atributo atr deve ser definido globalmente int main () { ccc c1, c2, c3; cout << endl << cout << endl << cout << endl << c1.SomarAtr (); cout << endl << c2.SomarAtr (); cout << endl << c3.SomarAtr (); cout << endl << cout << endl << } "atr = " << c1.PegaAtr(); "atr = " << c2.PegaAtr(); "atr = " << c3.PegaAtr(); "atr = " << c1.PegaAtr(); "atr = " << c1.PegaAtr(); "atr = " << c1.PegaAtr(); endl; system("pause"); return 0; atr atr atr atr Resultado: atr atr = = = = = = 0 0 0 1 2 3 Pressione ... 9.1.8 – Classe pilha para cadeias espelhadas Seja o seguinte conjunto infinito de cadeias: “k”, “aka”, “bkb”, “abkba”, “bakab”, “abbkbba”, “abakaba”, “aabkbaa”, ...... Especificação de cadeia típica: wkwr, onde w contém uma sequência qualquer de ‘a’s e ‘b’s, que pode até ser vazia, e wr é a sequência inversa a w Exemplo: se w = “abb”, wr = “bba” Fazer um algoritmo que leia uma cadeia qualquer x e, usando uma pilha, determine se x é do tipo wkwr Método: Percorrer a cadeia empilhando os ‘a’s e ‘b’s iniciais Se o primeiro caractere diferente de ‘a’ e ‘b’ for um ‘k’, avançar; se não for, a cadeia não é wkwr Comparar cada caractere da cadeia com o topo da pilha: Se forem iguais, desempilhar Senão, a cadeia não é wkwr A seguir, o programa /* Diretivas de pre-processamento */ #include <iostream> #include <stdlib.h> #include <conio.h> using namespace std; /* Definicao do tipo logic e suas constantes typedef char logic; const logic TRUE = 1, FALSE = 0; /* Definicao do tipo noh typedef struct noh noh; struct noh { char elem; noh *prox; }; */ */ /* Classe Pilha */ class pilha { private: noh *topo; public: void Inicializar (void); void Empilhar (char); void Desempilhar (void); char Topo (void); logic Vazia (void); }; void pilha :: Inicializar () {topo = NULL;} void pilha :: Empilhar (char x) { noh *temp; temp = topo; topo = (noh *) malloc (sizeof (noh)); topo->elem = x; topo->prox = temp; } void pilha :: Desempilhar () { noh *temp; if (! Vazia()) { temp = topo; topo = topo->prox; free (temp); } } char pilha :: Topo () { if (! Vazia()) return topo->elem; else return '\0';} logic pilha :: Vazia () { if (topo == NULL) return TRUE; else return FALSE; } /* Classe cadeia */ class cadeia { private: char str[200]; int cursor; public: void Ler (void); void Inicializar (void); char GetNext (void); void Escrever (void); }; void cadeia :: Inicializar () { cursor = -1; } char cadeia :: GetNext () { cursor++; return str[cursor]; } void cadeia :: Ler () { cin >> str; } void cadeia :: Escrever () { cout << str; } /* Funcao main: Verifica se uma cadeia eh espelhada */ int main ( ) { pilha P; cadeia cad; char wkwr, c; cout << "Digite a cadeia: "; cad.Ler(); P.Inicializar(); wkwr = TRUE; /* Percorre a primeira parte da cadeia cad.Inicializar(); c = cad.GetNext(); while (c == 'a' || c == 'b') { P.Empilhar(c); c = cad.GetNext(); } */ /* Verifica se a segunda parte eh espelho da primeira if (c != 'k') wkwr = FALSE; else { while (P.Vazia() == FALSE && wkwr == TRUE) { c = cad.GetNext (); if (c != P.Topo()) wkwr = FALSE; else P.Desempilhar(); } if (wkwr == TRUE) { c = cad.GetNext(); if (c != '\0') wkwr = FALSE; } } */ /* Emite o resultado da verificacao */ cout << endl << "\t"; cad.Escrever(); cout << ": cadeia "; if (wkwr) cout << "aceita"; else cout << "rejeitada"; cout << endl << endl; system("pause"); return 0; } 9.1.9 – Sobrecarga de métodos Sobrecarga é a possibilidade de vários métodos de uma mesma classe terem o mesmo nome, porém com protótipos ligeiramente diferentes Variações na quantidade ou no tipo dos argumentos e no tipo de retorno Exemplo: #include <math> class Logaritmo { public: double logaritmo(double x); double logaritmo(double x, double b); }; double Logaritmo::logaritmo(double x) { return log(x); } double Logaritmo::logaritmo(double x, double b) { return (log(x)/log(b)); } Capítulo IX – Programação Orientada a Objetos 9.1 – Classes, objetos e métodos 9.2 – Herança 9.3 – Polimorfismo 9.2 – Herança Herança permite criar novas classes a partir de classes existentes A classe existente se chama classe base A nova classe se chama classe derivada Classe base (pai) Classe derivada (filha) Classe derivada (filha) Um exemplo: class Pessoa { private: char nome[50]; int idade; class Funcionario: public Pessoa { int depto; public: public: Funcionario(char *nome, int idade, int depto); bool ehResponsavel(); Pessoa(char *nome, int idade); }; bool Pessoa::ehResponsavel() { if(idade >= 18 )return true; }; Funcionario::Funcionario(char *nome, int idade, int depto) :Pessoa(nome,idade) { this->depto = depto; else return false; } } Pessoa::Pessoa(char *nome, int idade){ strcpy(this->nome,nome); this->idade=idade; } 9.3 – Polimorfismo Na herança, os objetos da classe derivada herdam os atributos e os métodos da classe base Além disso, uma classe derivada pode sobrescrever os métodos da sua classe base, isto é, ter uma versão particular dessas operações Consequentemente, a execução de um mesmo método, se realizada por objetos de classes distintas (base e derivada, por exemplo), pode gerar diferentes ações Exemplo: class Pessoa { private: char nome[50]; int idade; public: virtual bool ehResponsavel(); Pessoa(char *nome, int idade); char *getNome(); }; char *Pessoa::getNome() { return nome; } bool Pessoa::ehResponsavel() { if (idade >= 18) return true; else return false; } Pessoa::Pessoa(char *nome, int idade) { strcpy(this->nome,nome); this->idade=idade; } Possibilita sobrescrita Uma classe derivada: class Casado: public Pessoa { public: bool ehResponsavel(); Casado(char *nome,int idade): Pessoa(nome,idade) { } }; bool Casado::ehResponsavel() { return true; }