Vamos considerar um exemplo mais complicado Suponhamos que pretendemos construir um novo tipo a que chamaremos Buffer. Este pode ser usado para ajustar diferentes velocidades na transmissão de dados. A especificação do Buffer pode ser definida da seguinte forma: O tipo Buffer definido pelo utilizador possui dois atributos (dados) que são: 1) size, o tamanho do buffer e 2) storage, que é o buffer propriamente dito Possui três métodos (funções) que são: 1) push, para colocar elementos/dados no buffer, 2) pop, para extrair dados do buffer e 3) shift para deslocar os dados do buffer uma posição Podem ver como cada uma destas funções opera com a ajuda de uma apresentação animada em PowerPoint 1) push(e1) 2) push(e2) 4) 5) 3) shift() shift() 14) pop() 15) shift() Armazenamento em memória e2 e1 e1 e2 e2 e1 e2 e1 e2 e1 e2 e1 e2 e1 e1 e2 e2 e1 e2 e1 e2 e1 e2 e1 Storage nae2e1 e2e1memória size Os dados de Buffer são os seguintes e as funções são chamadas métodos e1 Como geralmente os dados são usados dentro do corpo dos métodos (funções), podemos torná-los inacessíveis do exterior do tipo abstracto Buffer. Assumimos também que a função shift pode ser invocada apenas dentro dos outros métodos do Buffer. Por isso, pode ser inacessível do exterior do Buffer int int int int int *storage unsigned size void shift(); Buffer int int As funções Buffer, push e pop têm de ser acessíveis do exterior int int Buffer(unsigned); int Á junção de dados void push(int); e métodos num novo tipo, como o Buffer, int pop(); chama-se encapsulamento O encapsulamento baseia-se em duas propriedades fundamentais que são a modularidade e a abstracção O encapsulamento em C++ pode ser especificado usando a noção de classe Geralmente qualquer classe em C++ contém dados e funções A classe é apenas uma especificaçãoclass (umaBuffer descrição do que Nome um tipo abstracto possui e o que pode fazer). Mas, por outro lado, { a classe é apenas uma abstracção // especificação }; Uma classe é uma especificação abstracta para um dado número de objectos que possuem a mesma estrutura e partilham o mesmo comportamento comum A estrutura é definida por dados. O comportamento é fornecido pelas funções Se uma classe é apenas uma abstracção (uma especificação ou descrição) então um objecto é uma entidade que existe na memória do computador Um objecto é uma instância de uma classe, isto é, corresponde à implementação física descrita pela respectiva classe Buffer buf1, buf2, buf3; Basicamente, as classes e os objectos são as principais inovações da POO. Relacionam-se mutuamente e não podem ser considerados de forma independente Memória buf1 buf2 buf3 Consideremos uma declaração simples em linguagem C: int a,b,c; Aqui int é o nome de uma classe pré-definida na linguagem que pode representar todos os inteiros entre –32768 e 32767 a, b e c são variáveis (ou instâncias) da classe int Assim, int é a classe e a, b, c são instâncias (ou objectos) Voltemos ao nosso exemplo com o Buffer Por analogia podemos escrever Buffer buf_a, buf_b, buf_c; Onde Buffer é o nome da nossa classe e buf_a, buf_b, buf_c são instâncias (ou objectos) da classe Buffer A seguinte figura animada mostra a diferença entre classes e objectos class Buffer { public: int pop(); void push(int); Buffer(unsigned); ~Buffer(); private: void shift(); unsigned size; int *storage; }; Buffer buf1, buf2, buf3; A classe descreve o modo de construção de cada objecto em memória Memória buf1 buf2 buf3 Com a ajuda de gráficos animados podemos ver como um objecto da classe Buffer opera push(int); buf1 int pop(); A respectiva mensagemcom pode ser palavra vista como uma por ordem para Um objecto comunica uma externa intermédio o objecto, como por exemplo “insere-me”, onde “me” de mensagens corresponde ao novo valor De facto, enviar uma mensagem é o equivalente a invocar o respectivo método, como push e pop A outra mensagem poderia ser “extrai do lado direito”. Isto é Puderam comoaofunção métodopop push para a direita o o mesmo quever invocar quedesloca devolve o valor extraído conteúdo do buffer (atributo storage) e insere um novo valor na célula mais à esquerda do storage. Este novo valor é passado no argumento da função push De notar que a nossa especificação da classe Buffer (do tipo abstracto Buffer) ainda não está completa. Não conhecemos o tamanho real do atributo storage e não temos espaço físico para ele Memória Por outras palavras, geralmente cada objecto tem de ser inicializado Constructor Constructor Constructor Inicialização significa atribuir valores iniciais aos atributos do objecto buf1 Este trabalho é efectuado por uma função especial que é chamada construtor da classe buf2 O construtor da classe possui o mesmo nome que a classe e não devolve nenhum valor, nem do tipo void. buf3 Todas as outras Buffer buf1, buf2, buf3; características de um construtor de uma classe são semelhantes às de um método normal Para o nosso exemplo o construtor aloca memória para o atributo storage, sendo o tamanho passado no único argumento do construtor O construtor é invocado todas as vezes que o objecto está a ser criado De facto, uma classe possui outra função especial para limpar a memória que foi alocada pelo construtor quando o objecto for destruído Memória Esta função é chamada destructor da classe. Destructor Destructor Destructor buf1 buf2 Buffer buf1, buf2, buf3; buf3 O destrutor é invocado todas as vezes que o objecto está a ser destruído O seguinte código C++ mostra a especificação completa da classe Buffer. class Buffer { public: int pop(); void push(int); Buffer(unsigned); ~Buffer(); private: void shift(); unsigned size; int *storage; }; Buffer a, b, c,...; Este código vai ser organizado da seguinte forma: os dados e as funções (ou métodos). Existem duas funções específicas que são: O construtor, que efectua a inicialização; O destrutor, que destrói objectos da classe. É permitido criar qualquer número de objectos da classe Buffer, isto é, da classe que descreve o novo tipo definido pelo utilizador, que é um tipo abstracto. Os membros (os dados e as funções) de uma classe podem declarar-se como públicos (acessíveis a entidades externas) ou privados (só acessíveis às suas próprias funções) ou protegidos que iremos considerar posteriormente. class Buffer { public: De notar que vocês não Buffersabem a, b, c; como construir int pop(); asvoid funções (os métodos) a.push(5); //de Okuma classe. push(int); Buffer(unsigned); a.shift(); // Erro Agora vamos abordar este tópico ~Buffer(); private: void shift(); unsigned size; int *storage; }; void Buffer::push(int push_me) { shift(); // Ok storage[0] = push_me; } Uma função declarada como membro de uma classe é designada como método da classe e é invocada usando a sintaxe de membro da classe A declaração de um membro é considerada estar dentro do alcance (scope) da sua classe. Isto significa que se pode usar directamente na definição de um método, nomes dos membros da sua classe int Buffer::pop() { int temp=storage[size-1]; shift(); return temp; } void void Buffer:: Buffer::push(int push_me) { shift(); storage[0] = push_me; } A definição poderá ser feita fora do alcance da classe; neste caso, o seu nome terá que ser qualificado pelo nome da classe, usando o operador “::” (qualificador de alcance scope resolution operator). Buffer::Buffer(unsigned O seguinte código C++ SIZE) mostra avoid implementação completa da Buffer::push(int push_me) { classe Buffer. De notar que este código é realista no sentido em { storage = new que int[size=SIZE]; pode ser usado em aplicações práticas shift(); for(unsigned i=0; i<size; i++) storage[0] = push_me; storage[i] = 0; class Buffer } } { public: int Buffer::pop() Buffer::~Buffer() int pop(); { { void push(int);int temp=storage[size-1]; delete[] storage; Buffer(unsigned); shift(); } ~Buffer(); return temp; private: } void Buffer::shift() void shift(); { unsigned size; for(int i=size-1; i>0; i--) int *storage; storage[i]=storage[i-1]; }; storage[i]=0; } #define 5 É permitido criarSIZE qualquer número de objectos da classe Buffer main(int argc, char* argv[]) isto é, da int classe que descreve o novo tipo definido pelo utilizador, { que é um tipo abstracto Buffer Our_buffer(SIZE); Our_buffer.push(1); Our_buffer.push(2); Our_buffer.push(3); for(int i=0; i<SIZE; i++) cout << Our_buffer.pop() << endl; return 0; } 0 Buffer::Buffer(unsigned SIZE) 0 { 1 2 = new int[size=SIZE]; storage 3 for(unsigned i=0; i<size; i++) storage[i] = 0; } Interface e Redefinição De notar que estas funções são muito simples e que servem essencialmente para mostrar como uma classe pode ser organizada. Por outro lado, os algoritmos que foram implementados são ineficientes e poderão ser melhorados. Uma possibilidade de melhoria é manter o acesso ao Buffer através dos ponteiros. Neste caso o código pode, por exemplo, ser modificado da seguinte maneira: Neste caso o Buffer vai ser organizado da seguinte maneira. Poderemos chamar-lhe Buffer cíclico. Vamos usar um nome novo, por exemplo, CBuffer. push tail tail CBuffer head pop head Posteriormente, mostraremos como construir CBuffer a partir de Buffer com a ajuda de herança, uma propriedade nova de POO. class Buffer { // ... private: unsigned count; int* tail; int* head; }; Buffer::Buffer(unsigned SIZE) { storage = new int[size=SIZE]; for(unsigned i=0; i<size; i++) storage[i] = 0; head = tail= storage+size-1; count=0; } void Buffer::push(int add) { if (count < size) { *(tail--)=add; count++; else { cerr << "no space" << endl; return; } if(tail<storage) tail=storage+size-1; } int Buffer::pop() { if (count==0) { cout << "Buffer is empty" << endl; return 0; count--; if (head==storage) { head=storage+size-1; return storage[0]; } return *(head--); } } } O próximo conjunto de figuras animadas ajuda a sumariar o que discutimos e aquilo que devem compreender Primeiro: um tipo abstracto é um tipo (como int, float e char) que é usado para representar um novo tipo de abstracção do ponto de vista do utilizador Segundo: a POO introduz a noção fundamental de encapsulamento, Exemplos tipos podem que é umde meio deabstractos representar tipos ser: abstractos 1.Terceiro: A classe Encapsulamento COMPLEX que descreve os números complexos é um conceito abstrato genérico. 2. AC++, classe SET (conjunto). Em uma classe representa um conjunto de regras formais 3. A classe GRAPH. que permitem implementar encapsulamento 4. A classe STUDENT (aluno). Quarto: 5. A classe se uma GRAPHICAL classe é usada SHAPE para descrever (forma gráfica). novos tipos abstractos e6.um A classe objectoPROFESSOR. é uma instância de uma classe, então para um dado 7. Aobjecto classe DRIVER, podemos encontrar etc. na memória do computador os seus dados e executar os seus métodos Qualquer objecto tem três propriedades importantes: 1. Pode ser identificado de forma única, isto é, possui um nome único. De notar que se uma no classe não possuir um construtor 2. Possui um estado, sentido de que os seus atributose um destrutor pelovalores utilizador, estes serão criados podemdefinidos armazenar físicos. automaticamente pelo compilador. Neste caso, sãoosutilizados 3. Possui um comportamento, no sentido de que seus métodos somentepodem para reservar memória para os atributos do variar objecto afectar os seus estados, podendo estes quandocom esteo étempo. criadoAssim, e para qualquer libertar esta memória objecto pode quando ser o objecto é destruído.como Não um é fornecida considerado sistema qualquer dinâmicoinicialização. que varia com o tempo. Quinto: Existem duas funções específicas em cada classe que são 1. Um construtor, que efectua a inicialização, isto é, atribui valores (na maioria das vezes definidos pelo utilizador) aos respectivos dados. 2. Um destrutor, que destrói objectos de uma classe. Normalmente o destrutor liberta a memória reservada pelo objecto. O que é importante: 1. Perceber o que é um tipo abstracto. 2. Perceber o que é encapsulamento. 3. Perceber o que é uma classe. 4. Perceber a diferença entre classes e objectos. 5. Perceber as regras da linguagem C++ que são aplicadas para declarar classes e definir objectos dessas classes 6. Estar apto a construir programas triviais que utilizem classes. O seguinte fragmento apresenta as regras formais para a declaração de uma classe palavra chave de C++ que é (class ou struct ou union) class Buffer { public: int pop(); void push(int); alcance da classe Buffer(unsigned); (scope da classe) ~Buffer(); private: void shift(); unsigned size; int *storage; }; a diferença vai ser considerada posteriormente nome, por exemplo Buffer Lembram-se ; O seguinte fragmento apresenta as regras formais para a declaração de uma classe Os atributos que são usados para controlo de acesso aos membros (os dados e as funções) class Buffer { public: int pop(); void push(int); Buffer(unsigned); ~Buffer(); private: void shift(); unsigned size; int *storage; }; Estes fragmentos podem ser usados por qualquer ordem e qualquer número de vezes class Buffer { public: int pop(); void push(int); Buffer(unsigned); ~Buffer(); private: void shift(); unsigned size; int *storage; }; class Buffer { private: void shift(); public: int pop(); void push(int); private: unsigned size; int *storage; public: Buffer(unsigned); ~Buffer(); }; O código seguinte mostra a definição de objectos: Buffer buf_a, buf_b, buf_c; O código seguinte mostra a definição de ponteiros para objectos: Buffer *p_a, *p _b, *p _c; . Agora podemos ter acesso aos membros através dos nomes de objectos, por exemplo: buf_a.push(125); cout<<a.pop(); 125 125 ou através dos ponteiros para objectos, por exemplo: -> p_a->push(125); cout<<a->pop();