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;
}
Download

Programação Orientada a Objetos