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();
Download

ppt1