ABSTRAÇÃO • processo de representar um grupo de entidades através de seus atributos comuns • feita a abstração, cada entidade particular (instância) do grupo é considerada somente pelos seus atributos particulares • os atributos comuns às entidades do grupo são desconsiderados (ficam "ocultos" ou "abstraídos") ABSTRAÇÃO • de processos • de dados ABSTRAÇÃO DE PROCESSOS • o conceito de abstração de processos é um dos mais antigos no projeto de linguagens • absolutamente crucial para a programação • historicamente anterior à abstração de dados • todos os subprogramas são abstrações de processo • exemplo: chamadas sort(array1, len1), sort(array2, len2), ... MÓDULOS • módulos são "containers" sintáticos contendo subprogramas e grupos de dados relacionados logicamente • modularização: processo de projetar os módulos de um programa • a compreensão do programa pelos mantenedores seria impossível sem organização modularizada • além disso, há um ponto crítico: quando o projeto de um programa estende-se por milhares de linhas, recompilá-lo totalmente a cada atualização do código é absolutamente inviável - daí a necessidade de modularizá-lo ENCAPSULAMENTO • um agrupamento de subprogramas+dados que é compilado separada/independentemente chama-se uma unidade de compilação ou um encapsulamento • um encapsulamento é portanto um sistema abstraído • muitas vezes os encapsulamentos são colocados em bibliotecas • exemplo: encapsulamentos C (não são seguros porque não há verificação de tipos de dados em diferentes arquivos de encapsulamento) OBJETOS • um tipo abstrato de dados é um encapsulamento que inclui somente um tipo específico de dado e os subprogramas que fornecem as operações para este tipo • detalhes de implementação do tipo ficam ocultos das unidades fora do encapsulamento que o contém • um objeto é uma variável (instância) de um tipo abstrato de dados, declarada por alguma unidade • programação orientada a objetos consiste no uso de objetos no desenvolvimento do software EXEMPLO: O PONTO-FLUTUANTE COMO TIPO ABSTRATO DE DADOS • embora o tipo ponto-flutuante esteja presente desde o início da programação, raramente nos referimos a ele como tipo abstrato de dados • praticamente todas as linguagens permitem que se criem "objetos" do tipo ponto-flutuante • observe que existe um conjunto de operações que são válidas para o tipo ponto-flutuante, exatamente como os “métodos” definidos para uma “classe” • além disso, a ocultação da informação está presente: o formato real dos dados é inacessível ao programador - isto é exatamente o que se espera de uma abstração • isto é o que permite a portabilidade de um programa entre as implementações da linguagem para plataformas particulares TIPOS DE DADOS ABSTRATOS DEFINIDOS PELO USUÁRIO • a definição do tipo e as operações sobre objetos do tipo estão contidas numa única unidade sintática • outras unidades de programa podem ter permissão para criar variáveis do tipo definido • a implementação do tipo não é visível pelas unidades de programa que usam o tipo • as únicas operações possíveis sobre objetos do tipo são aquelas oferecidas na definição do tipo CLIENTES • unidades de programa que utilizam um tipo abstrato chamam-se clientes daquele tipo • a ocultação da representação do tipo abstrato é vantajoso para seus clientes: o código no cliente não depende desta representação, e mudanças na representação não exigem mudanças nos clientes (mas se o protocolo de alguma operação for modificado, então é claro que os clientes precisam ser alterados) • a ocultação aumenta a confiabilidade: nenhum cliente pode interferir intencional ou acidentalmente na representação EXEMPLO: UMA PILHA E SUAS OPERAÇÕES ABSTRATAS • • • • • • • create(stack) destroy(stack) empty(stack) push(stack, elem) pop(stack) top(stack) create(STK1); % STK1 é um objeto ou uma instância do tipo stack • create(STK2); % outra instância do tipo stack TIPOS DE DADOS ABSTRATOS EM C++ • os tipos abstratos de dados em C++ são as chamadas classes • as variáveis são declaradas como instâncias de classes • classes do C++ são baseadas nas da SIMULA67 e no struct do C • os dados definidos numa classe são os membros de dados FUNÇÕES-MEMBRO • as funções definidas em uma classe são as funções-membro • as funções-membro são compartilhadas por todas as instâncias de classe • mas: cada instância tem seu próprio conjunto de membros de dados • uma função membro pode ter sua definição completa dentro da classe ("inlined") ou apenas seu cabeçalho TEMPO DE VIDA DAS CLASSES • as instâncias de classe podem ser estáticas, stack-dinâmicas ou heap-dinâmicas, exatamente como variáveis do C • as classes podem incluir membros de dados heap-dinâmicos, não obstante elas próprias não serem heap-dinâmicas OCULTAÇÃO DA INFORMAÇÃO EM C++ • cláusula private: para entidades ocultas na classe • cláusula public: para as entidades visíveis aos clientes. Descreve a interface com objetos da classe • cláusula protected: relacionada com herança Construtores • Funções-membro usadas para inicializar os membros de dados de um objeto recémcriado • Também alocam membros de dados heapdinâmicos • Têm o mesmo nome da classe • Pode-se sobrecarregar construtores • Não têm tipo de retorno, não usam return Destrutores • Implicitamente chamados quando se encerra o tempo de vida de um objeto • Se um objeto é heap-dinâmico, será explicitamente desalocado com delete • O destrutor pode conter chamadas delete para desalocar membros de dados heapdinâmicos • Nome do destrutor: ~ <nome_da_classe> • Não têm tipo de retorno, não usam return Exemplo #include <iostream.h> class pilha { private: int *ptr_pilha; int tam_max; int top_ptr ; public: pilha( ){ //** um construtor ptr_pilha = new int [100]; tam_max = 99; top_ptr = -1; } Exemplo (continuação) ... ~pilha( ){ //** um destrutor delete [ ] ptr_pilha; } void push ( int elem) { if (top_ptr = = tam_max) cout << “Erro - pilha cheia\n”; else ptr_pilha[ + + top_ptr ] = elem; } void pop ( ) { if (top_ptr = = -1) cout << “Erro - pilha vazia\n”; else top_ptr -- ; } Exemplo (continuação) ... int top { return ( ptr_pilha[top_ptr] ); } int empty { return ( top_ptr = = -1 ); } } \\** fim da classe pilha Código no cliente: void main ( ) { int top_one; pilha stk; stk.push(42); stk.push(17); top_one = stk.top( ); stk.pop( ); ... } Avaliação das classes C++ • As classes são tipos • Não há construções de encapsulamento generalizadas • Exemplo: temos uma classe “matriz” e uma classe “vetor”, e precisamos multiplicar um objeto “matriz” por um objeto “vetor”. • Em qual classe essa operação deve ser definida? Solução para “matriz vetor” class Matriz { friend Vetor mult(const Matriz&, const Vetor&); ...} class Vetor { friend Vetor mult(const Matriz&, const Vetor&); ...} Vetor mult(const Matriz& m1, const Vetor& v1){ ..} Se Matriz e Vetor pudessem ser definidas num único pacote, evitaríamos esta construção pouco natural. Java • Suporte para tipos abstratos similar a C++ • Todos os tipos de dados definidos pelo usuário são classes • Todos os objetos são heap-dinâmicos e acessados por variáveis de referência • Todos os subprogramas (métodos) em Java somente podem ser definidos em classes • public e private são modificadores anexados às definições de métodos/variáveis Java • Suporte para tipos abstratos similar a C++ • Todos os tipos de dados definidos pelo usuário são classes • Todos os objetos são heap-dinâmicos e acessados por variáveis de referência • Todos os subprogramas (métodos) em Java somente podem ser definidos em classes • public e private são modificadores anexados às definições de métodos/variáveis Pacotes Java • Em C++ as classes são a única construção de encapsulamento • Java inclui uma construção adicional: os pacotes • Pacotes podem conter mais de uma classe • public e private são os chamados modificadores • Os membros sem modificador (e os membros public) de uma classe são visíveis a todas as classes do mesmo pacote (escopo de pacote) • Não há, portanto, necessidade de declarações friend explícitas em Java Exemplo import java.io.* class Pilha { private int [ ] ref_pilha; private int tam_max, top_index ; public Pilha( ){ // um construtor ref_pilha = new int [100]; tam_max = 99; top_index = -1; } Exemplo (continuação) ... public void push ( int elem) { if (top_index = = tam_max) System.out.println(“Erro”); else ref_pilha[ + + top_index ] = elem; } public void pop ( ) { if (top_index = = -1) System.out.println(“Erro”); else --top_index ; } public int top { return ( ref_pilha[top_index] ); } public boolean empty { return ( top_index = = -1 ); } } \\** fim da classe Pilha Exemplo (continuação) - uso da classe Pilha public class Testa_Pilha { public static void main (String [ ] args) { Pilha p1 = new Pilha( ); p1.push(42); p1.push(29); p1.pop( ); p1.pop( ); p1.pop( ); // Produz msg de erro ... } • Não há destrutor (eliminado pela coleta de lixo implícita em Java) • Observe o uso de variável de referência em vez de ponteiro Classes parametrizadas em C++ Exemplo: suponha que o método construtor para a classe pilha fosse: pilha (int size ){ ptr_pilha = new int [size]; tam_max = size-1; top_ptr = -1; } No cliente: ... pilha(150) p1; Classes genéricas em C++ #include <iostream.h> template < class TIPO > class pilha { private: TIPO *ptr_pilha; int tam_max; int top_ptr ; public: pilha( ){ //** um construtor ptr_pilha = new TIPO [100]; tam_max = 99; top_ptr = -1; } Classes genéricas em C++ (continuação) pilha (int size ){ // outro construtor sobrecarregado ptr_pilha = new TIPO [size]; tam_max = size-1; top_ptr = -1; } ~pilha( ){ delete ptr_pilha; } void push ( TIPO elem) { if (top_ptr = = tam_max) cout << “Erro - pilha cheia\n”; else ptr_pilha[ + + top_ptr ] = elem; } void pop ( ) {...} Classes genéricas em C++ (continuação) ... TIPO top { return ( ptr_pilha[top_ptr] ); } int empty { return ( top_ptr = = -1 ); } } \\** fim da classe pilha