1 Programação Orientada a Objetos A linguagem C, desde a sua primeira especificação, vem sendo desenvolvida com várias extensões. De uma dessas extensões foi criada a Linguagem C++ onde encontramos diversas construções voltadas à Programação Orientada a Objetos. Os fundamentos e algumas características da linguagem C++ voltados à Programação Orientada a Objetos são apresentados neste documento. Encapsulamento Um objeto é uma estrutura de dados que contém um número fixo de componentes. Os objetos encapsulam dados e funções. Os objetos são declarados a partir das palavras reservadas class (ou struct) onde são definidos campos com seus tipos e as funções completas o apenas com seus cabeçalhos. objeto funções dados Ex.: class C1 { tipo1 campo1; tipo2 campo2; void rot1(parâmetros) { comandos } tipo3 rot2(parâmetros); }; As sintaxes das declarações dos dados e das funções são normais como qualquer outra declaração, a única diferença é elas são membros de uma classe. Herança Uma classe pode herdar membros de outras classes. Classe2 Classe1 Herança da Classe1 2 Ex.: class C2 : public C1 { tipo4 campo3; void rot3(parâmetros); }; A classe C2, além do campo3 e da função rot3, tem também os campo1, campo2, rot1 e rot2. Funções Uma função membro de uma classe é normalmente utilizada para manipular os dados encapsulados dentro da mesma classe. A declaração de uma função membro pode ser completa ou apenas o protótipo. Caso apenas o protótipo tenha sido declarado, o corpo da função membro é definido fora da declaração da classe. Ex.: tipo3 C1::rot2(parâmetros) { . . . }; Observe que a classe C1 aparece no instante da declaração do corpo da função. Um método pode ser estático ou virtual. Instanciação Para que as definições nas classes possam ser utilizadas devemos instanciar estas classes. Instanciar significar criar uma variável a partir do tipo definido pela classe. Classe1 Objeto1 Objeto2 Instâncias da Classe1 Objeto3 Instanciação Ex.: C1 o1; C2 o2; A instância (variável) o1 tem: dois campos (campo1 e campo2) e duas funções (rot1, rot2). A instância o2 tem três campos (campo1, campo2 e campo3) e três funções (rot1, rot2, e rot3). De outra forma poderíamos também utilizar instâncias alocadas dinamicamente. Esta forma é a mais comum. class C3 : public C2 { tipo5 campo4; void rot4(); }; . . . C3 *o3; . . . void main() { o3 = new C3(); o3->rot4(); . . . 3 Termos utilizados em POO Nos exemplos: C1 e C2 são classes o1 e o2 são instâncias ou objetos de C1 e C2 campo1, campo2, campo3 e campo4 são dados-membro (ou campos, ou propriedades) rot1, rot2, rot3 e rot4 são funções membro (ou métodos) Programa exemplo Programa utilizando programação orientada a objetos para manter um vetor verificando se os índices são respeitados: #include <stdio.h> #define MAX 6 typedef float tipoDado; typedef tipoDado Vet[MAX]; struct VetUni { Vet v; bool verificaIndice(int i); void atrib(int i, tipoDado val); tipoDado consu(int i); }; bool VetUni::verificaIndice(int i) { if (i<0 || i>=MAX) { printf("Indice %d e' invalido\n", i); return 0; } else return 1; } void VetUni::atrib(int i, tipoDado val) { if (verificaIndice(i)) v[i]=val; } tipoDado VetUni::consu(int i) { if (verificaIndice(i)) return v[i]; } VetUni v1, v2; tipoDado x; int main() { v1.atrib(0, 7.2); v2.atrib(1, 3.5); v1.atrib(2, 0.4); v1.atrib(3,-1.0); v1.atrib(4, 2.3); v2.atrib(5,-9.2); x=v1.consu(0)+v2.consu(1); . . . return 0; } /* atribuicao */ /* consulta e operaçoes */ Os dados e funções membros podem ter a permissão de acesso alterada através dos modificadores private, protected e public. Estes modificadores criam seções dentro de uma classe restringindo ou ampliando a permissão de acesso, aos membros desta classe, vindo do exterior dela. O acesso aos membros declarados após um private é permitido apenas aos membros da classe, os declarados após um public não apresentam nenhuma restrição, e finalmente os declarados após um protected só podem receber acesso dos membros da sua classe e de suas classes descendentes. Teoricamente um objeto seguro deveria ter todos os seus dados membros private. Isso impediria que acessos "mal comportados" alterassem o funcionamento esperado do objeto. Todas as alterações aos dados membro deveriam ser realizadas através de chamadas às funções membro públicos do objeto. 4 A única diferença entre declarar uma classe utilizando class ou struct é a permissão de acesso padrão. Nas classes declaradas com class todos os membros são privates, nas classes declaradas com struct todos os membros são publics. Os modificadores private, protected e public podem alterar o acesso dos membros de qualquer classe. Os dois programas a seguir apresentam duas classes diferentes com a mesma interface e o mesmo comportamento, observa-se, quando comparando os dois programas, que a implementação fica oculta na utilização da classe no programa principal (função main()). A classe VetUni nos dois programas tem a mesma interface pública. #include <stdio.h> #define MAX 1000 typedef float tipoDado; typedef tipoDado Vet[MAX]; class VetUni { Vet v; bool verificaIndice(int i); public: void inicia(); void atrib(int i, tipoDado val); tipoDado consu(int i); }; void VetUni::inicia() { for (int i=0; i<MAX; i++) v[i]=0; } bool VetUni::verificaIndice(int i) { if (i<0 || i>=MAX) { printf("Indice %d e' invalido\n", i); return 0; } else return 1; } void VetUni::atrib(int i, tipoDado val) { if (verificaIndice(i)) v[i]=val; } tipoDado VetUni::consu(int i) { if (verificaIndice(i)) return v[i]; } VetUni v1, v2; tipoDado x; int main() { v1.inicia(); v2.inicia(); v1.atrib(0, 7.2); v2.atrib(1, 3.5); v1.atrib(2, 0.4); v1.atrib(3,-1.0); v1.atrib(4, 2.3); v2.atrib(5,-9.2); x=v1.consu(0)+v2.consu(1); . . . return 0; } /* atribuicao */ /* consulta e operaçoes */ O programa abaixo implementa uma forma econômica de espaço de memória para armazenar vetores esparsos. Apenas os valores diferentes de zero são armazenados. 5 #include <stdio.h> #define MAX 1000 #define MAXDIFZERO 10 /* maximo elementos diferentes de zero */ typedef float tipoDado; class VetUni { struct { tipoDado valor; int ind; } m[MAXDIFZERO]; int encontra(int ind); public: void inicia(); void atrib(int i, tipoDado val); tipoDado consu(int i); }; void VetUni::inicia() { for (int i=0; i<MAXDIFZERO; i++) m[i].ind=-1; } int VetUni::encontra(int ind) { int i; i=0; while (i<MAXDIFZERO && m[i].ind!=ind) i++; if (i<MAXDIFZERO) return i; else return -1; /* indicador de nao encontrado */ } void VetUni::atrib(int i, tipoDado val) { int ind; ind=encontra(i); if (val==0) { if (ind!=-1) m[ind].ind=-1; /* retira elemento da tabela */ } else if (ind!=-1) m[ind].valor=val; /* já existe o índice */ else { ind=encontra(-1); /* encontra índice vazio */ if (ind==-1) printf("Erro, nao cabe mais\n"); else { m[ind].ind=i; m[ind].valor=val; } } } tipoDado VetUni::consu(int i) { int ind; ind=encontra(i); if (ind!=-1) return m[ind].valor; else return 0; } VetUni v1, v2; tipoDado x; int main() { v1.inicia(); v2.inicia(); v1.atrib(0, 7.2); v2.atrib(1, 3.5); v1.atrib(2, 0.4); v1.atrib(3,-1.0); v1.atrib(4, 2.3); v2.atrib(5,-9.2); x=v1.consu(0)+v2.consu(1); . . . return 0; } /* atribuicao */ /* consulta e operaçoes */ 6 Mensagens Conceitualmente, na OOP, os objetos, em um programa, interagem através da troca de mensagens. No mínimo, enviar uma mensagem envolve especificar o nome de um objeto destino e o nome da mensagem a ser enviada. Constantemente haverá também argumentos que deverão ser especificados. Podemos imaginar, em um certo sentido, que cada objeto se comporta como um pequeno programa especializado, que se comunica através das mensagens com outros programas especializados. As trocas de mensagens em C++ são simples chamadas às funções membros dos objetos. Assim se na linguagem da OOP dizemos: o objeto o1 envia uma mensagem m ao objeto o2, em C++ a mensagem m para o2 seria: o2.m(), onde o2 é uma instância e m é uma função declarada na classe de o2. Funções Virtuais Uma função declarada como virtual pode ser substituída nas suas classes descendentes. Essas funções usam uma tabela adicional para identificar em qual classe está a função a ser chamada. Assim, a ligação dessas funções não ocorre no instante da ligação (link), ela ocorre no instante em que a função é executada. Esse tipo de ligação recebe o nome de vinculação tardia de funções no momento de execução (“runtime late binding of functions”). #include <stdio.h> struct A { void m1() { printf("A.m1()\n"); m2(); } virtual void m2() { printf("A.m2()\n"); } }; struct B : public A { virtual void m2() { printf("B.m2()\n"); } }; Classe A Classe B função m1 M1 chama M2 função m2 função m2 A objA; B objB; int main() { objA.m2(); objB.m2(); printf("------\n"); objA.m1(); objB.m1(); getchar(); } A figura acima mostra a classe A com duas funções, m1 e m2, tal que m1 chama m2; e a classe B que é subclasse de A. Considerando a criação de dois objetos a partir de A e B, teremos respectivamente objA e objB. Quando uma mensagem m1 é enviada para objA as funções m1 e m2 de A serão executadas. O que acontecerá se a mensagem m1 for para objB? 1 O sistema em execução pega a mensagem objB.m1 e tenta encontrar o método m1 na classe B. 2 A ação 1 fracassa e o sistema procura nos métodos das classes mães (classe A). A busca tem sucesso e o código do método A.m1 é usado. 3 Como o método A.m1 chama o método m2, o programa em execução precisa resolver a chamada: 7 1a Se m2 NÃO for virtual: A.m2 será imediatamente executado (a vinculação foi feita na ligação e, portanto, é estática); 2a Se m2 for virtual: o sistema primeiro volta aos métodos da classe B (a classe cuja instância recebeu a mensagem original) e tenta localizar um método m2. A busca tem sucesso e o sistema emprega o código de B.m2 para resolver a chamada de objA.m1 (a vinculação é feita no instante da execução). A linguagem C++ utiliza uma tabela (tabela de funções virtuais) para resolver a vinculação tardia. Essa tabela é alocada e iniciada por uma função especial chamada construtor no momento da instanciação e é liberada por outra função especial chamada destrutor. As funções construtor e destrutor são executadas automaticamente ao início e término da utilização da instância, mas podem ser explicitamente chamadas pelo programador. Construtores e Destrutores Construtores e destrutores são funções especiais que sempre existem nos objetos, mesmo quando não são declarados explicitamente. Os construtores têm sempre o nome da classe, podem ter parâmetros diversos e não podem retornar valores; eles são sempre as primeiras rotinas a serem executadas após a instanciação e servem para iniciar os objetos (iniciar variáveis, alocar memória dinâmica, abrir arquivos, etc.) no momento da instanciação. Os nomes dos destrutores são formados sempre pelo nome da classe precedido por um til “~”, eles não podem ter parâmetros e nem retornar valores; eles são sempre as últimas rotinas a serem executadas antes da liberação da instancia e servem para terminar os objetos (liberar memória dinâmica, fechar arquivos, etc). Exemplo: #include <stdio.h> #include <stdlib.h> typedef float tipoDado; typedef tipoDado *Vet; class VetUni { Vet v; int numElem; bool verificaIndice(int i); public: VetUni(int n); ~VetUni(); void atrib(int i, tipoDado val); tipoDado consu(int i); }; VetUni::VetUni(int n) { numElem=n; v=(Vet)malloc(numElem*sizeof(tipoDado)); printf("Memoria alocada, "); for (int i=0; i<numElem; i++) v[i]=0; printf("vetor iniciado\n"); } VetUni::~VetUni() { free(v); printf("Memoria liberada.\n"); } bool VetUni::verificaIndice(int i) { if (i<0 || i>=numElem) { printf("Indice %d e' invalido\n", i); return 0; } else return 1; } void VetUni::atrib(int i, tipoDado val) { if (verificaIndice(i)) v[i]=val; } tipoDado VetUni::consu(int i) { if (verificaIndice(i)) return v[i]; } void testaDestrutor() { tipoDado x; VetUni v1(6), v2(6); v1.atrib(0, 7.2); v2.atrib(1, 3.5); v1.atrib(2, 0.4); v1.atrib(3,-1.0); v1.atrib(4, 2.3); v2.atrib(5,-9.2); x=v1.consu(0)+v2.consu(1); printf("%f\n", x); } int main() { testaDestrutor(); getchar(); return 0; } 8 Exemplos de POO #include <stdio.h> #include <stdlib.h> void Pessoa::mostra() { titulo("Mostra"); printf("Nome: %s\n", nome); printf("Endereco: %s\n", endereco); printf("R.G.: %i\n", RG); } class Pessoa { char nome[30], endereco[30]; int RG; public: void leDados(); void mostra(); void titulo(char funcao[]); }; Pessoa pessoa; void Pessoa::titulo(char funcao[]) { printf("-- %s Pessoa: --\n", funcao); } void Pessoa::leDados() { char sRG[10]; titulo("Le Dados"); printf("Nome: "); gets(nome); printf("Endereco: "); gets(endereco); printf("R.G.: "); gets(sRG); RG=atoi(sRG); } int main() { pessoa.leDados(); pessoa.mostra(); getchar(); return 0; } No vídeo teremos: -- Le Dados Pessoa: -Nome: João Endereco: XV de novembro S/N R.G.: 12345678 -- Mostra Pessoa: -Nome: João Endereco: XV de novembro S/N R.G.: 12345678 O programa abaixo coloca uma nova classe descendente de Pessoa. Observe que no instante da impressão no vídeo o título é mostrado como: -- "funcao" Pessoa -- ou seja, o método Aluno.titulo NÃO foi usado. #include <stdio.h> #include <stdlib.h> class Pessoa { char nome[30], endereco[30]; int RG; public: void leDados(); void mostra(); void titulo(char funcao[]); }; void Pessoa::titulo(char funcao[]) { printf("-- %s Pessoa: --\n", funcao); } void Pessoa::leDados() { char sRG[10]; titulo("Le Dados"); printf("Nome: "); gets(nome); printf("Endereco: "); gets(endereco); printf("R.G.: "); gets(sRG); RG=atoi(sRG); } void Pessoa::mostra() { titulo("Mostra"); printf("Nome: %s\n", nome); printf("Endereco: %s\n", endereco); printf("R.G.: %i\n", RG); } class Aluno : public Pessoa { int RA; public: void leDados(); void mostra(); void titulo(char funcao[]); }; void Aluno::titulo(char funcao[]) { printf("-- %s Aluno: --\n", funcao); } void Aluno::leDados() { char sRA[10]; Pessoa::leDados(); printf("R.A.: "); gets(sRA); RA=atoi(sRA); } void Aluno::mostra() { Pessoa::mostra(); printf("R.A.: %i\n", RA); } Aluno aluno; int main() { aluno.leDados(); aluno.mostra(); getchar(); return 0; } 9 No vídeo teremos: -- Le Dados Pessoa: -Nome: João Endereco: XV de novembro S/N R.G.: 12345678 Numero: 111 -- Mostra Pessoa: -Nome: João Endereco: XV de novembro S/N R.G.: 12345678 Numero: 111 O programa abaixo corrige o anterior utilizando um método virtual #include <stdio.h> #include <stdlib.h> class Pessoa { char nome[30], endereco[30]; int RG; public: void leDados(); void mostra(); virtual void titulo(char funcao[]); }; void Pessoa::titulo(char funcao[]) { printf("-- %s Pessoa: --\n", funcao); } void Pessoa::leDados() { char sRG[10]; titulo("Le Dados"); printf("Nome: "); gets(nome); printf("Endereco: "); gets(endereco); printf("R.G.: "); gets(sRG); RG=atoi(sRG); } void Pessoa::mostra() { titulo("Mostra"); printf("Nome: %s\n", nome); printf("Endereco: %s\n", endereco); printf("R.G.: %i\n", RG); } class Aluno : public Pessoa { int RA; public: void leDados(); void mostra(); virtual void titulo(char funcao[]); }; void Aluno::titulo(char funcao[]) { printf("-- %s Aluno: --\n", funcao); } void Aluno::leDados() { char sRA[10]; Pessoa::leDados(); printf("R.A.: "); gets(sRA); RA=atoi(sRA); } void Aluno::mostra() { Pessoa::mostra(); printf("R.A.: %i\n", RA); } Pessoa pessoa; Aluno aluno; int main() { aluno.leDados(); aluno.mostra(); getchar(); return 0; } No vídeo teremos: -- Le Dados Aluno: -Nome: João Endereco: XV de novembro S/N R.G.: 12345678 Numero: 111 -- Mostra Aluno: -Nome: João Endereco: XV de novembro S/N R.G.: 12345678 Numero: 111 Note no programa acima que as rotinas Aluno.mostra e Aluno.leDados chamam as mesmas rotinas declaradas na classe mãe, que agora estão sendo substituídas. Esta facilidade é muito útil em classes descendentes de classes complexas, quando toda a complexidade não precisa ser repetida. 10 Criando Instancias em C++ Utilizaremos o exemplo anterior: class Pessoa { char nome[30], endereco[30]; int RG; public: void leDados(); void mostra(); virtual void titulo(char funcao[]); }; . . . class Aluno : public Pessoa { int RA; public: void leDados(); void mostra(); virtual void titulo(char funcao[]); }; . . . A instanciação utiliza até agora foi: Aluno aluno; int main() { aluno.leDados(); aluno.mostra(); getchar(); return 0; } Existe ainda uma forma mais utilizada que cria uma instância a partir de memória alocada dinamicamente: Aluno *aluno; int main() { aluno = new Aluno(); aluno->leDados(); aluno->mostra(); delete aluno; getchar(); return 0; }