Um tipo abstrato de dado é um tipo definido pelo programador com um
conjunto de valores e uma coleção de operações
sobre estes valores. As operações devem ser consistentes com os
tipos dos valores.
Para a implementação de um tipo abstrato de
dado, o programador deve escolher uma representação de dados de
um dado abstrato usando os tipos de dados existentes e, implementar as
operações permitidas sobre estes dados com as
instruções da linguagem de programação desejada.
A idéia fundamental de linguagens orientadas a objetos é a possibilidade de combinar num único registro campos que conterão dados e campos que são funções para operar os campos de dados do registro. Um tipo abstrato de dado que suporta este conceito é a classe.
Uma classe é ponto de partida inicial para se desenvolver um
programa em linguagens orientadas à objetos.
Uma classe é um de
tipo abstrato de dados. Abaixo, relacionamos os componentes de uma classe
(não é obrigatório conter todos na definição
de uma classe) :
class emp { private : int empno; char ename[10 + 1]; char job[9 + 1]; int mgr; long int hiredate; long int sal; long int comm; int deptno; public : emp(); // construtor emp(int emp, char nome[10 + 1], char j[9 + 1], int m, long int h, long int s, long int c, int d); ~emp(); // destrutor , executa a exclusão int inclusão(); void alteração(); void consulta(); void tela(); void menu(); int le_disco(fstream *fio); int grava_disco(fstream *fio); int abre_arq(fstream *fio); long num_reg(fstream *fio); };
Os dados privados são acessados somente pelas
funções-membro da classe. Dizemos que os dados privados
são ocultos para os dados não pertencentes a classe.
Exemplo : Na
classe emp declarada abaixo, todas as variáveis foram declaradas como
dados privados. Isto é feito para garantir maior integridade e
segurança aos dados manipulados de um objeto.
class emp { private : int empno; char ename[10 + 1]; char job[9 + 1]; int mgr; long int hiredate; long int sal; long int comm; int deptno; };
Dados públicos podem ser acessados por qualquer objeto de uma
classe.
Abaixo é descrito um exemplo de dados públicos.
Dados públicos, normalmente, são as funções-membro de uma
classe.
class emp { public : emp(); // construtor emp(int emp, char nome[10 + 1], char j[9 + 1], int m, long int h, long int s, long int c, int d); ~emp(); // destrutor , executa a exclusão int inclusão(); void alteração(); void consulta(); void tela(); void menu(); int le_disco(fstream *fio); int grava_disco(fstream *fio); int abre_arq(fstream *fio); long num_reg(fstream *fio); };
Ao invés de declararmos os dados de uma classe como privados podemos
declará-los como protegidos. A vantagem de utilizar isto é que os
dados declarados como protegidos podem ser acessados em caso de herança
pela classe derivada e se forem declarados como privados a classe derivada
não terá acesso a eles.
class BASE { protected : Int secreto; private : int ultra_secreto; public : int publico; }; class DERIV1 : public BASE { public : int a = secreto; // ok int b = ultra_secreto; // erro : não-acessível int c = publico // ok }; class DERIV2 : private BASE { public : int a = secreto; // ok int b = ultra_secreto; // erro : não-acessível int c = publico // ok } ;
É importante salientar que uma classe é um tipo abstrato de
dado e não um objeto. Quando criamos uma variável do tipo da
classe declarada, criamos um objeto desta classe.
Exemplo : emp obj1; ( a
variável obj1 é um objeto da classe emp)
Nós definimos as funções-membro dentro de uma classe
e, somente os objetos destas classe poderão ter acesso as mesmas.
São o único meio de acesso aos campos de dados ou também
chamados dados privados. Abaixo é descrito um exemplo da
função-membro tela declarada na classe emp.
Funções-membro também são chamadas de
métodos.
Exemplo :
void emp::tela() { clrscr(); gotoxy(30,6); cout << "Cadastro de Empresas"; gotoxy(10,8); cout << "Código Empresa : "; gotoxy(10,10); cout << "Nome Empresa : "; gotoxy(10,12); cout << "Cargo : "; gotoxy(10,14); cout << "Código Gerente : "; gotoxy(10,16); cout << "Data : "; gotoxy(10,18); cout << "Salário : "; gotoxy(10,20); cout << "Comissão : "; gotoxy(10,22); cout << "Departamento : "; };
As funções-membro só podem ser chamadas quando
associadas ao objeto específico pelo operador ponto. Uma
função-membro age sobre um objeto em particular e não
sobre a classe como um todo. Um exemplo : Se quisermos chamar a
função-membro inclusão da classe emp, primeiro temos que
criar um objeto desta classe e depois chamar a função-membro,
como segue :
A instrução de chamada a
uma função-membro é denominada MENSAGEM.
As funções-membro podem ser definidas dentro da
própria declaração da classe quanto fora dela. Se for
feita fora da definição da classe, devemos declarar dentro da
classe o protótipo da função-membro. Podemos então,
declarar a função-membro em qualquer parte do programa. Quando a
declararmos, deveremos colocar antes do nome da função-membro
nome da classe seguido do operador "::" para identificar a qual classe ela
pertence.
Exemplo : Se quiséssemos declarar a
função-membro inclusão da classe emp em qualquer lugar do
programa devemos declarar como segue :
Construtor é uma função-membro que é executada
quando é criado uma instância de classe. Isto garante a
inicialização de uma instância de classe. O nome do
construtor é o mesmo que o nome da classe. Podemos declarar um
construtor de duas maneiras:
O próprio
compilador define qual construtor será chamado (se os dois forem
declarados) no momento em que uma instância de classe está sendo
criada. Um construtor nunca é chamado por um objeto da classe, mas
sempre quando é criada uma instância dessa classe. O construtor
não deve retornar nenhum valor. O motivo é que ele é
chamado diretamente pelo sistema e não temos como recuperar um valor de
retorno.
Exemplo do construtor da classe emp sem parâmetros :
emp::emp() // construtor { empno = 0; ename[0] = '\0'; job[0] = '\0'; mgr = 0; hiredate = 0; sal = 0; comm = 0; deptno = 0; }Exemplo do construtor da classe emp com parâmetros :
emp::emp( int emp, char nome[10 + 1], char j[9 + 1], int m, long int h, long int s, long int c, int d) { empno = emp; strcpy(ename,nome); strcpy(job,j); mgr = m; hiredate = h; sal = s; comm = c; deptno = d; };
Destrutor é uma função-membro que é executada
quando é destruída uma instância de classe. O nome do
destrutor é o mesmo que o nome da classe precedido do caracter `~'.
Abaixo é descrito um exemplo da classe emp que diminui o valor do
salário em um cada vez que um objeto é destruído.
emp::~emp() // destrutor , executa a exclusão { sal - -; }
Uma classe invariante é uma declaração sobre o
corrente estado de uma instância de uma classe. Tipos invariantes
referem-se tipicamente somente para os dados membros de uma classe. Todas as
funções-membro da classe devem cooperar para preservar o estado
da classe invariante. Uma classe invariante é uma
pós-condição implícita para cada
função-membro. E, é também uma
pré-condição implícita para a
função-membro exceto os construtores. Todas classes devem
providenciar um jeito de inicializar suas classes invariantes. A estrutura de
um construtor é o local mais apropriado. Um construtor é chamado
quando uma classe é criada, antes que o usuário possa chamar
qualquer outra função-membro da classe. Entretanto, os comandos
do construtor devem inicializar os dados de uma instância de classe
mantendo o resultado da classe invariante sempre verdadeiro quando o construtor
retornar o controle do programa.
O encapsulamento de dados em uma classe são os dados que são
escondidos, ou seja, os dados declarados como private. Com isto, somente
funções-membro podem acessar os dados declarados como private e
não qualquer variável declarada como instância de uma
classe. Um exemplo do encapsulamento de dados é descrito abaixo
através da declaração de variáveis da classe emp
como dados privados.
class emp { private : int empno; char ename[10 + 1]; char job[9 + 1]; int mgr; long int hiredate; long int sal; long int comm; int deptno; };
A palavra polimorfismo em Programação orientada a objetos
assume o sentido de que com um único nome para uma
função-membro podemos definir várias funções
distintas. Duas ou mais funções-membro podem ter o mesmo nome,
mas um código independente. A situação anterior é
bastante utilizada em classes derivadas à partir de herança
simples ou múltipla.
A herança é um recurso muito poderoso da
programação orientada a objetos. Herança é o
processo pelo qual criamos novas classes à partir de classes existentes.
A herança nos permite relacionar classes que contém parte ou
totalmente dos dados relacionados entre si. Um exemplo clássico de
herança é representado na figura abaixo:
class janela { protected :int linicio, cinicio, lfim, cfim; public : janela(); janela(int li, int ci, int lf, int cf); void box(char modo[] = "[]|-=+|"); }; class window : public janela // classe derivada (herança)
{ public : window(): janela() {}; window(int li, int ci, int lf, int cf) : janela(li,cf,lf,cf){}; void cls(); void cursor(char lin, char col); void centra(char lin, char s[]) ; };
Class conta { private : char nome[40]; // nome do cliente int nconta; // número da conta float saldo; // saldo da conta public : void getdata(); void putdata(); float Saldo(); }; class ContaSimples : public conta {}; class ContaEspecial : public conta { private :float limite; public : void getdata(); void putdata(); }; class Poupanca : public conta { private : float taxa; public : void getdata() { conta::getdata(); cout << " Taxa:"; cin >> taxa; } void putdata() { conta::putdata(); cout << "\n Taxa:" << taxa; cout << "\nSaldo Total: " << (Saldo() * taxa);} };No exemplo acima, declaramos uma classe conta (classe base) e declaramos as classes conta_especial, conta_simples e poupança como classes derivadas da classe conta. Quando declaramos uma classe como derivada de outra, a classe derivada herda todas as características da classe-base (este processo recebe o nome de reutilização do código). A classe derivada pode acessar todos os dados públicos e privados da classe-base. Além do que, pode alterar estes dados, isto é, pode acrescentar novos campos de dados, pode reescrever um método da classe-base com o mesmo nome e alterar o seu conteúdo.
Quando criamos uma classe derivada com a clausula "public", estamos dizendo
que os dados públicos da classe base serão dados públicos
na classe derivada e, os dados protegidos serão os dados protegidos da
classe derivada. A classe derivada não terá acesso aos dados
privados. Podemos criar uma classe com a clausula "private", com isso tanto os
dados públicos como protegidos da classe base serão dados
privados da classe derivada. Com isso, a classe derivada não terá
acesso a nenhum dado da classe base.
class BASE { protected : Int secreto; private : int ultra_secreto; public : int publico; }; class DERIV1 : public BASE { public : int a = secreto; // ok int b = ultra_secreto; // erro :não-acessível int c = publico // ok }; class DERIV2 : private BASE { public : int a = secreto; // ok int b = ultra_secreto; // erro :não-acessível int c = publico // ok } ;
É possível a criação de
funções-membro na classe derivada que tenha o mesmo nome de uma
função-membro da classe base. Com isto, podemos chamar a
função-membro da classe base através do operador de escopo
"::" e, construirmos este procedimento com o que queremos.
Exemplo :
const TAM = 80; class BasAg { protected : char nome[TAM]; char numag[4]; public : BasAg() { nome[0] = '\0'; numag[0] = '\0'; } BasAg(char n[],char ng[]) {} void print(){} };No exemplo acima, o método "print()" da classe derivada agente é reescrita com o mesmo nome do método da classe base e, ainda, chama o método "print()" da classe base BasAg através do operador de escopo "::". Quando for executado o comando "agente.print()" do exemplo acima, o compilador executará o método print() da classe agente. Mas, o primeiro comando deste método é chamar o método de mesmo nome da classe base, o compilador executa este método e, após os comandos do método da classe derivada.
class Agente : public BasAg // herança ou classe derivada { protected : float altura; int idade; public : Agente() : BasAg() { altura = 0; idade = 0; } Agente(char n[], char ng[], float a, int i) : BasAg(n,ng) { altura = a; idade = i; } void print();
};
void main() { Agente agente("Daniel","1",1.78,21); agente.print(); }
Quando uma classe herda as características de mais de uma
classe-base. Abaixo, temos o exemplo de herança múltipla.
Declaramos as classes simples Cadastro, Imóvel e Tipo. Declaramos a
classe Venda como herança múltipla das três
anteriores.
class Cadastro { private : char nome[30], fone[20]; public : Cadastro(); void getdata(); void putdata(); }; class Imóvel { private : char end[30], bairro[20]; float AreaUtil, AreaTotal; int quartos; public : Imóvel(); void getdata(); void putdata(); }; class Tipo { private : char tipo[20]; // Residencial, Loja, Galpão... public : Tipo(); void getdata(); void putdata(); }; class Venda : private Cadastro,Imovel,Tipo { private : float valor; public : Venda() : Cadastro(), Imóvel(), Tipo(); void getdata(); void putdata(); };
São utilizadas quando queremos fazer referência através
de uma matriz de ponteiros para uma classe base que contém várias
classes derivadas com o objetivo de acessar um mesmo método de cada
classe derivada. Para utilizarmos este recurso da POO, declaramos os
métodos que quisermos como funções virtuais da classe
base. A sintaxe é a seguinte:
Exemplo:O resultado produzido é : DERIV0 DERIV1 DERIV2
class base { public : virtual void print() { cout << "\nBASE"; }}; class deriv0 : public base { public : void print() { cout <<"\nDERIV0"; } }; class deriv1 : public base { public : void print() { cout <<"\nDERIV1"; } }; class deriv2 : public base { public : void print() { cout <<"\nDERIV2"; } }; void main() { base *p[3]; // matriz de ponteiro para a classe base deriv0 dv0; // objeto da classe deriv0 deriv1 dv1; // objeto da classe deriv1 deriv2 dv2; // objeto da classe deriv2 p[0] = &dv0; p[1] = &dv1; p[2] = &dv2; for (int i = 0; i < 3; i++) p[i]-> print(); } O resultado produzido é : DERIV0 DERIV1 DERIV2 Se não colocarmos a palavra virtual na declaração do método print() da classe base o resultado será: BASE BASE BASEFUNÇÕES VIRTUAIS PURAS
Criamos funções virtuais puras para o uso de herança de uma classe base que nunca terá um objeto definido. Esta função não possui código. Na declaração do protótipo da função usamos o operador de atribuição seguido de um zero após o seu protótipo. A função serve somente para prover uma interface polimórfica para as classes derivadas. Exemplo :
class base { public : virtual void print() = 0; };class deriv0 : public base { public : void print() { cout <<"\nDERIV0"; } }; class deriv1 : public base { public : void print() { cout <<"\nDERIV1"; } }; class deriv2 : public base { public : void print() { cout <<"\nDERIV2"; } }; void main() { base *p[3]; // matriz de ponteiro para a classe base deriv0 dv0; // objeto da classe deriv0 deriv1 dv1; // objeto da classe deriv1 deriv2 dv2; // objeto da classe deriv2 p[0] = &dv0; p[1] = &dv1; p[2] = &dv2; for (int i = 0; i < 3; i++) p[i]-> print(); }
Por default, os membros privados de uma classe somente podem ser acessados
por outros membros da mesma classe. Contudo, uma classe pode dar
permissão para uma função não membro desta classe
acessar os seus membros privados, mas não estará associada a um
objeto da classe. Isto é feito com a declaração friend.
Uma função amiga pode agir em duas ou mais classes diferentes,
fazendo o papel de elo de ligação entre as classes.
Exemplo :
Além de ser possível declarar funções
independentes como amigas, podemos declarar uma classe toda como amiga de
outra. Os métodos da classe serão todas amigas da outra classe. A
diferença é que estas funções-membro tem
também acesso a parte privada ou protegida de sua própria
classe.
void main()
{
tempo tm(12,18,30);
tempo tm1 (13,22,10);
data dt(18,12,55);
dt.prndt(tm);
dt.prndt(tm1);
}
Sobrecarga de operadores é uma das opções mais poderosas
da orientação a objeto. Sobrecarregar um operador
significa alterar o domínio de um operador pré-definido de uma
linguagem de operação. Por exemplo, o domínio do operador
`+' é os números reais. Se desejarmos que o operador `+' ao
invés de somar dois números inteiros e passe a somar duas
matrizes de dimensões nxn, devemos declarar uma
função-membro `operator +' dentro da classe matriz.
À partir de agora, cada vez que o operador `+' for executado entre
dois objetos da classe matriz ele irá somá-las e atribuir
o resultado ´a uma terceira matriz.
Sobrecarregar um operador significa redefinir o seu
símbolo, de modo que ele se adeque aos tipos abstratos de dados
definidos pelo usuário, tais como, estruturas e classes. A
implementação de sobrecargas de operadores é definido por
meio de funções chamadas operadoras. Estas funções
podem ser criadas como membros de classe ou como funções globais.
A sobrecarga de operadores deve respeitar a definição original do
operador. Por exemplo, o operador soma necessita de dois argumentos, não
podemos modificá-lo para três argumentos. Para realizar a
sobrecarga devemos usar os operadores definidos, ou seja, não podemos
criar operadores novos. A sobrecarga deve obedecer à precedência
original do operador sobrecarregado. Não é possível
modificar a precedência dos operadores sobrecarregados.
Os operadores ".", "::" e "?:" não podem ser sobrecarregados.
Operadores unários ("++", "--" e "-"), definidos como métodos
de classes, não recebem nenhum argumento, enquanto que os operadores
binários recebem um único argumento. Podemos sobrecarregar
além dos operadores unários e binários, strings.
Podemos usar os operadores "=", "+"e "+=" para concatenar e comparar
strings.
A sobrecarga do operador de incremento pré-fixado tem a seguinte
sintaxe : void operator ++() { comandos } void operator --() { comandos } A
função não recebe nenhum argumento e não retorna
nada neste caso. Esta declaração indica ao compilador que este
método deve ser executado cada vez que for usado este operador com
objetos desta classe. A chamada pode ser duas maneiras : ++p1; ou
p1.operator++(); A sobrecarga do operador de incremento pós-fixado tem a
seguinte sintaxe : void operator ++(int) { comandos } void operator --(int) {
comandos } O parâmetro "int" indica para o compilador que a
função sobrecarregada é pós-fixada. Exemplo de um
operador sobrecarregado pré-fixado.
#include <iostream.h> class ponto { private : int x,y; public : ponto(int x1=0,int y1=0) // construtor { x = x1; y = y1;} void operator ++() // função operadora prefixada { ++x; ++y; } void printpt() const // imprime ponto { cout << "(" << x << "," << y << ")"; } }; void main() { ponto p1, p2(2,3), p3; // declara e inicializa cout << "\n p1 = "; p1.printpt(); cout << "\n p2 = "; p2.printpt(); ++p1; //incrementa p1 ++p2; // incrementa p2 cout << "\n ++p1 = "; p1.printpt(); cout << "\n ++p2 = "; p2.printpt(); p3 = p1; cout << "\n p3 = "; p3.printpt(); } Resultado : p1 = (0,0) p2 = (2,3) ++p1 = (1,1) ++p2 = (3,4) p3 = (1,1)
Quando operadores binários são sobrecarregados, a
função operadora terá um argumento. A função
operadora, quando declarada como método de uma classe sempre precisa de
um argumento a menos que o número de operandos daquele operador.
Exemplo:
#include <iostream.h> #include <iomanip.h> class venda { private : int npecas; float preco; public : venda() {} venda(int np, float p) // construtor com argumentos { npecas= np; preco = p; } void getvenda() { cout << "Insira Numero de Pecas: "; cin >> npecas; cout << "Insira Preco : "; cin >> preco; } venda operator + (venda v) const; // funçãooperador void printvenda() const; }; venda venda::operator + (venda v) const // soma duas vendas { int pec = npecas + v.npecas; float pre = preco + v.preco; return venda(pec,pre); } void venda::printvenda() const { cout << setiosflags(ios::fixed) // nao notaçãocientifica << setiosflags(ios::showpoint) // ponto decimal <<setprecision(2) // duas casas decimais << setw(10) << npecas; // tamanho do campo 10 cout << setw(10) << preco << "\n"; } void main() { venda A(58,12734.53),B,C(30,6000.3),T,Total; B.getvenda(); T = A + B; Total = A + B + C; //somas múltiplas cout << "Venda A ........"; A.printvenda(); cout << "Venda B ........"; B.printvenda(); cout << "Venda:A + B ...."; T.printvenda(); cout << "Totais:A + B + C"; Total.printvenda(); return; } Resultado: Insira No.Peças: 45 Insira Preço: 10987.85 Venda: A.... 58 12734.53 Venda: B.... 45 10987.85 Venda A + B: ..... 103 23722.38 Totais A + B + C: 133 29722.68
[HEA94] HEADINGTON, M. RILEY, D. Data Abstraction and Structures Using C++,
D. C. Heath and Company, Toronto, 1994.
[MIZ94a] MIZRAHI, V. Treinamento em Linguagem C++, Módulo 1, Makron
Books, SP, 1994.
[MIZ94b] MIZRAHI, V. Treinamento em Linguagem C++, Módulo 2, Makron
Books, SP, 1994.