POO em C++ e Java
POO em C++ e Java
Sistemas de Informação
Introdução à programação
orientada a objetos
com C++ e Java
Introdução à programação
orientada a objetos com
C++ e Java
Reitor
Targino de Araújo Filho
Vice-Reitor
Pedro Manoel Galetti Junior
Pró-Reitora de Graduação
Emília Freitas de Lima
Secretária de Educação a Distância - SEaD
Aline Maria de Medeiros Rodrigues Reali
Secretária Executiva
Adriana Silva
Coordenadora do Curso de Sistemas de Informação
Sandra Abib
UAB-UFSCar EdUFSCar
Universidade Federal de São Carlos Universidade Federal de São Carlos
Rodovia Washington Luís, km 235 Rodovia Washington Luís, km 235
13565-905 - São Carlos, SP, Brasil 13565-905 - São Carlos, SP, Brasil
Telefax (16) 3351-8420 Telefax (16) 3351-8137
www.uab.ufscar.br www.editora.ufscar.br
uab@ufscar.br edufscar@ufscar.br
Ednaldo Brigante Pizzolato
Introdução à programação
orientada a objetos com
C++ e Java
2011
© 2010, Ednaldo Brigante Pizzolato
Concepção Pedagógica
Daniel Mill
Supervisão
Douglas Henrique Perez Pino
Equipe de Ilustração
Jorge Luís Alves de Oliveira
Thaisa Assami Guimarães Makino
ISBN – 978-85-7600-204-8
Todos os direitos reservados. Nenhuma parte desta obra pode ser reproduzida ou transmitida por qualquer
forma e/ou quaisquer meios (eletrônicos ou mecânicos, incluindo fotocópia e gravação) ou arquivada em qual-
quer sistema de banco de dados sem permissão escrita do titular do direito autoral.
........... Sumário
Apresentação. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Unidade 1: Introdução
1.3.1 Encapsulamento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
1.3.2 Visibilidade . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
1.3.3 Encapsulamento e visibilidade. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
3.8 Exemplos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
5.3 Revisão. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
6.3 Composição . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
6.4 Herança . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
Apêndice B. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
Referências. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
Apresentação
• Programação Concorrente;
• Programação Lógica;
• Programação Funcional.
Espera-se que o leitor já tenha tido contato com alguma linguagem repre-
sentante do paradigma imperativo ou procedural (por incluir procedimentos e
funções) e tenha tido oportunidade de implementar programas usando tal lin-
guagem. Exemplos de linguagens do paradigma imperativo ou procedural são
C, Pascal, Fortran e PL/I. Também se espera que o leitor saiba o que seja pro-
gramação estruturada.
Pedirei permissão ao(a) leitor(a) para tratá-lo(a) por você daqui em diante.
12
Unidade 1
Introdução
1.1 Primeiras palavras
Esta unidade objetiva apresentar uma visão geral sobre o paradigma impe-
rativo e o orientado a objetos. Mais detalhes podem ser obtidos em livros sobre
paradigmas e linguagens de programação.
Por que não utilizamos nossa linguagem para transmitir para a máquina
(computador ou robô) nossas ideias ou intenções? Na verdade, é preciso enten-
der que o computador atual trabalha com a linguagem binária (zeros e uns) e que
tudo o que queremos que ele faça deve ser especificado em forma de algoritmo e,
posteriormente, traduzido para a linguagem da máquina. Um algoritmo especifica
quais ações devem ser feitas/tomadas e em qual ordem. As linguagens de pro-
gramação devem representar de forma clara e objetiva o que está especificado
no algoritmo. Queremos que as intenções ou ideias sejam transmitidas de forma
a não causar dúvidas ao computador ou robô. Não podemos conceber que um
computador pare de executar uma ação e tente adivinhar o que o programador
gostaria, na realidade, que fosse feito. Por exemplo: “robô, por favor, tome seu
lubrificante agora” e “robô, por favor, tome o ônibus das 3 para ir ao centro da
cidade” são frases que utilizam o mesmo verbo (tomar) para situações completa-
mente diferentes. O robô deve entender que a primeira ação significa engolir e a
segunda ação significa utilizar o meio de transporte coletivo para se deslocar até
o centro da cidade.
Também temos uma ideia natural de que as ações devem respeitar uma de-
terminada ordem. Por exemplo, para a troca de um pneu furado é necessário des-
parafusar o pneu e também é necessário erguer o carro para que outro pneu seja
colocado. Entretanto, se dispusermos apenas de equipamento mecânico (chave
de roda) para desparafusar a roda, não é aconselhável que o carro seja erguido
antes que as porcas sejam afrouxadas. O contato do pneu com o solo impede
que a roda gire e facilita o processo. Também não devemos retirar as porcas inteira-
mente com o carro no solo, sob o risco de que ele tombe e cause algum acidente.
Se houver passageiros, também é importante que eles saiam do carro antes de
ele ser erguido. Não se pode ordenar que os passageiros saiam se eles não
existirem. É preciso que existam comandos que possibilitem a mudança de fluxo
15
(execução de um conjunto de ações se uma determinada condição for satisfeita).
Dessa forma, sequência de comandos, estruturas de seleção e de repetição fazem
parte das chamadas linguagens imperativas.
• Abstração procedural;
• Bibliotecas de apoio.
16
Figura 1 Representação de código de repetição estruturado e não estruturado.
1.3.1 Encapsulamento
O conceito pode ser mais abrangente. Em uma busca pela World Wide Web
podemos encontrar a seguinte definição de cápsula fonocaptora:
De uma forma bem simples podemos verificar que uma cápsula tem a função
de reunir um conjunto de elementos em um único dispositivo e também de prote-
ger o material interno de ações indevidas do ambiente externo. A proteção está
associada à visibilidade, abordada a seguir. A Unidade 3 apresentará exemplos de
classes nas quais os conceitos de encapsulamento e visibilidade serão utilizados.
1.3.2 Visibilidade
Um objeto pode tanto estar ao alcance da visão (e, portanto, ser visível) como
pode estar escondido. O conceito de visibilidade, em programação orientada a
O(a) leitor(a) que desejar obter mais informações sobre paradigmas de pro-
gramação poderá consultar o livro:
TUCKER, A. B.; NOONAN, R. E. Programming Languages: Principles and Paradigms.
New York: McGraw-Hill, 2007.
20
Unidade 2
Nesta unidade você terá contato com os principais comandos das lingua-
gens C++ e Java. A apresentação dos comandos não é detalhada, visto que se
supõe que o leitor já tenha tido contato com a linguagem de programação C.
Os conceitos de orientação a objetos não serão apresentados ainda. O objetivo
principal da unidade é fazer com que o leitor tenha familiaridade com as duas
linguagens, para que possa entender os conceitos de orientação a objetos que
serão apresentados na próxima unidade.
Apesar de ser possível, é pouco provável que uma pessoa que esteja
aprendendo a programar computadores consiga se tornar um(a) exímio(a)
programador(a) apenas com as informações teóricas. Em geral, sugere-se que
o(a) aprendiz(a) coloque os conceitos em prática. Esta unidade apresentará
conceitos seguidos de pequenos exemplos que facilitarão o aprendizado das
linguagens C++ e Java.
Ah, você notará que as duas linguagens são muito parecidas. Por isso, antes
de apresentarmos as estruturas básicas das duas linguagens, cabe apresentar
um pouco da história das linguagens de programação que influenciaram ambas.
23
2.3 A História das linguagens
Java, por outro lado, originou-se de OAK, que teve influência de várias outras
linguagens de programação: ADA, OBJECTIVE-C, C++, CEDAR, SMALLTALK e
SCHEME. Note que Java sofre influência de C++ (e, portanto, de C também!).
O C++ foi desenvolvido por Bjarne Stroustrup, dos Bell Labs, durante a dé-
cada de 1980 com o objetivo de implementar uma versão distribuída do núcleo
(kernel) do sistema operacional Unix. Stroustrup percebeu que a linguagem
Simula possuía características bastante interessantes para o desenvolvimento
de softwares, mas era muito lenta para uso prático. Resolveu combinar suas
características com as da linguagem C (rápida e portável para diversas plata-
formas) de forma a construir uma nova linguagem mais poderosa. Inicialmente
denominou-a C with classes, que em português significa C com classes. De-
pois, mudou seu nome para C++.
24 2 Plankalkul é uma combinação de duas palavras: plan e kalkul. Kalkul significa cálculo e plan
significa plano. Mais informações sobre essa linguagem podem ser obtidas em Sebesta (2009).
// meu primeiro programa em C++
#include <iostream>
using namespace std;
O comando de saída cout << “Olá pessoal!” << endl; é uma simplificação do
comando printf da linguagem C. O comando em questão imprimirá a mensagem
“Olá pessoal!” e em seguida mudará de linha. O comando endl significa final de
linha. É equivalente ao “\n” da linguagem C. Na verdade, também é possível utilizar
o \n em C++. O comando equivalente ao apresentado anteriormente com \n seria:
Assim como a saída de dados ficou mais simples, a entrada de dados tam-
bém. O código a seguir calcula a média de 3 notas de um aluno. As notas são
digitadas e a média calculada é exibida na tela.
// declaração do namespace
using namespace std;
// programa principal
int main (){
float media, nota, soma;
int i;
soma = 0;
Note que cin apenas faz leitura de dados. Se houver a necessidade de que
uma informação seja apresentada para o usuário (geralmente isso acontece), então
é preciso utilizar o comando cout. Não é possível exibir uma mensagem para o
usuário utilizando o cin.
Para representar variáveis lógicas, o C não tinha um tipo definido. Era preci-
so utilizar variáveis inteiras e interpretar seus valores como sendo valores lógicos.
Em C o 0 (zero) representa o valor booleano falso, enquanto o 1 (um) representa
o valor verdadeiro. Em C++ é possível deixar o código mais limpo nesse sentido,
pelo uso do tipo de dado bool. Com ele é possível criar uma variável que aceite
os valores verdadeiro ou falso.
#define PI 3.14159
Mas antes, vamos contar um pouco sobre a linguagem Java. Ela foi de-
senvolvida nos laboratórios da Sun Microsystems no início dos anos 1990, com
o objetivo de ser uma linguagem-base de projetos de software para produtos
eletrônicos. Sim, o objetivo era colocar código em torradeiras e geladeiras! Mas
o programa desenvolvido em linguagem C não era tão genérico, dependia muito
da plataforma alvo. Na melhor das hipóteses, tinha que ser recompilado. No início
dos anos de 1990, Patrick Naughton, James Gosling e Mike Sheridan, três cientis-
tas da Sun, começaram a definir as bases para o projeto de uma nova linguagem
de programação que não contivesse os conhecidos problemas das linguagens
tradicionais, como C e C++. Especificaram a linguagem e a denominaram OAK.
Entretanto, por problemas de Copyright, tiveram que a renomear para Java, em
homenagem ao café que consumiam na Sun. A linguagem criada é mais simples
que C++, pois não tem sobrecarga de operadores, structs, unions, aritmética de
ponteiros e herança múltipla. Além disso, existe o gerenciamento de memória
(garbage collection), que evita o vazamento de memória. Conquistou atenção
devido a sua aplicação na World Wide Web, em que programas Java possibilitam
a criação de animações (por meio de applets). Mas isso veremos mais tarde.
class teste
{
// classe teste sem nada
// declaração de main()
public static void main(String args[])
{
System.out.println(“Olá pessoal“);
}
}
Esse exemplo simples mostra que não precisamos incluir arquivo algum. Há
uma função principal chamada main (em C++ também há!) que não retorna valor
algum. A saída de dados é realizada pelo comando println (escrita com mudança
de linha), acionado pela linha de código:
29
System.out.println(“...“);
import javax.swing.JOptionPane;
class teste {
public static void main(String args[]) {
String x;
x = JOptionPane.showInputDialog(null, “Digite uma msg: “);
System.out.println(x);
}
}
a) import javax.swing.*;
b) import javax.swing.nome_da_classe;
No primeiro caso (a), todas as classes do pacote swing são visíveis. No se-
gundo caso (b), apenas a classe especificada está visível. Alternativamente, po-
deríamos ter omitido o comando import e ter escrito o comando de leitura assim:
x = javax.swing.JOptionPane.showInputDialog (...);
import javax.swing.JOptionPane;
import java.lang.*;
class teste
{
public static void main(String args[])
{
float nota, media, soma = 0;
int i;
for (i=0;i<3;i++)
{
nota = Float.parseFloat(JOptionPane.showInputDialog(
null, “Digite uma nota: “));
soma = soma + nota;
}
media = soma/3;
System.out.println(media);
}
}
int dias_do_mes[ ];
dias_do_mes = new int[12];
Nesta unidade vimos um pouco da história das linguagens C++ e Java. Am-
bas são muito parecidas e os códigos aqui apresentados comprovam tais se-
melhanças. Para o leitor familiarizado com a linguagem C, aprender ambas as
linguagens não será um grande problema.
32
Unidade 3
A chamada “crise do software” foi um termo utilizado nos anos 1970 para
expressar as dificuldades relacionadas com o desenvolvimento de softwares
em comparação com a crescente demanda. Um paralelo em economia seria, de
um lado, um aumento considerável da demanda (feita pelos consumidores) por um
produto (por exemplo, televisor) e, de outro lado, a incapacidade da indústria de
fornecer a quantidade solicitada do referido produto. Geralmente isso gera um
efeito indesejável, que é o aumento da inflação. O problema com o desenvolvi-
mento dos softwares era ainda maior, pois não existiam técnicas eficientes de
desenvolvimento de software. Utilizando ainda o paralelo com a economia, seria
o equivalente a ter no mercado produtos (televisores) não muito confiáveis!
Para que o problema fique claro, vamos imaginar que temos um carrinho de
controle remoto. Do ponto de vista da programação, imaginemos que o controle
remoto seja uma função. Imaginemos, agora, que a indústria responsável pela
confecção do carrinho deseje lançar um novo brinquedo: um avião. Se a indústria
houvesse planejado um controle remoto mais geral (prevendo as funcionalidades
35
para carrinhos, helicópteros, aviões, barcos e submarinos), seria bem mais fácil
fazer adaptações no projeto do controle remoto para o lançamento do novo pro-
duto. Entretanto, sem um projeto geral de controle remoto como base, a indústria
teria 3 caminhos:
Adaptar o controle remoto do carrinho para o avião poderia ser uma solução
interessante. Entretanto, limitações projetadas para o carrinho (como velocidade)
poderiam não ser aplicáveis ao avião. Além disso, do ponto de vista de progra-
mação, teríamos algumas funcionalidades semelhantes (ir para frente) que apre-
sentariam implementações diferentes (uma para o controle remoto do carrinho e
outra para o controle remoto do avião), apesar de serem idênticas. Mais ainda,
uma alteração na funcionalidade “ir para frente” em carrinhos implicaria em alterar
o código do programa em 2 partes distintas. Imaginemos que a empresa tivesse
optado por tal estratégia para todos os produtos lançados e podemos entender o
grau de complexidade da tarefa de manutenção.
Por fim, a construção de uma nova solução. Seria a mais conveniente, mas
certamente teria maior custo e atrasaria o lançamento do produto (o que pode
significar perda considerável da fatia do mercado para um concorrente que esteja
acompanhando suas tendências).
3.3 O paradigma OO
Uma analogia com a clonagem pode ser explorada para transmitir o concei-
to de herança em computação. É importante, entretanto, definir o que é clonagem
do ponto de vista biológico. A palavra clone (do grego klon, significa “broto”) é 37
utilizada para designar a forma de geração de indivíduos por reprodução assexua-
da. Poderia ser definida como o processo em que são produzidas cópias fiéis de
outro indivíduo, ou seja, um clone.
Note que são cadeiras, mas que são diferentes do modelo apresentado
anteriormente. Na verdade, nossa mente tem uma representação complexa de
38 cadeira que aceita todos esses exemplos. Uma classe deve prever um conjunto
de características para o objeto a ser criado a partir do molde, bem como um
conjunto de ações que o objeto poderá executar. No exemplo da cadeira, isso
se aplica ao tipo de pé, ao tipo de encosto, se é uma cadeira com rodinhas, se é
uma cadeira de balanço, qual o material de sua estrutura (madeira, aço, metal ou
plástico), seu revestimento (pano, couro, verniz), etc. Como você pode perceber,
especificar um molde para uma cadeira é algo complexo.
Outro exemplo poderia ser uma lâmpada. Ela pode ser incandescente ou
fluorescente, pode ser de bastão ou ter o formato clássico, ter várias voltagens,
potências, cores, marcas, etc.
Mais um exemplo: uma empilhadeira. Ela tem uma cor, uma altura, uma
capacidade de carga, um ângulo máximo de giro, tem uma velocidade máxima de
movimentação, etc. Tudo isso se configura no conjunto de características do
futuro objeto (ainda estamos falando de classe!). Tal empilhadeira pode realizar as
seguintes (entre várias) funções: 1) movimentar-se para frente; 2) movimentar-se
para trás; 3) girar Z graus para a direita; 4) girar Z graus para a esquerda;
5) pegar a carga; etc. Tais funções se configuram em ações que o futuro objeto
poderá realizar.
Ah, um objeto pode interagir com outro e mudar suas características! Po-
deríamos utilizar um objeto “spray” para pintar (uma ação de “spray”) a empi-
lhadeira e, assim, mudar um valor de seus atributos (o atributo cor). Quando
pensamos em objetos, as coisas ficam mais claras.
39
3.5 Elementos de uma classe
Isso nos faz pensar sobre o uso do controle remoto do ar-condicionado (ou
do videocassete, do aparelho de DVD, do televisor, etc.) para realizar algumas
funções de nosso interesse. Um controle remoto de um aparelho de ar-condicio-
nado tem teclas para ligar e desligar, controlar a velocidade do ventilador, da pá
de distribuição do ar e também para controlar a temperatura (e principalmente
para controlar a temperatura!). Mais ainda, o controle remoto não permite que di-
minuamos a temperatura para menos de um valor pré-determinado (em torno de
15ºC) ou aumentemos para um valor superior a outro valor pré-determinado (em
torno de 27ºC). Isso permite que o aparelho funcione corretamente e não congele
ou derreta. O fabricante do aparelho projetou um controle remoto que protege o ob-
jeto que industrializa e comercializa. Ninguém que utilize o controle remoto poderá
modificar a temperatura para um nível inferior a 15ºC ou superior a 27ºC. Assim,
40
temos com classes mais que simplesmente um “empacotamento”. Temos um
empacotamento de dados e código que permite a consistência das informações
presentes. Mantendo a analogia com o exemplo do ar-condicionado, os dados só
poderão ser modificados segundo as “funções” das teclas do controle remoto.
A classe esconderá seus dados (visibilidade) e permitirá que apenas alguns mé-
todos sejam disponibilizados para alteração deles. Se criássemos a classe data,
precisaríamos de métodos (não chamamos mais de funções) que mantivessem
a consistência destes. Um método poderia ser atribuir_dia(int x). O valor x deve,
nesse exemplo, ser verificado para saber se é um valor válido. Ele depende do
mês e do ano da data. Se o mês for fevereiro e o ano for bissexto, então o dia
poderá assumir valores entre 1 e 29. Assim, se x estiver dentro desses limites,
dia poderá receber o valor em x. Caso contrário, dia poderá receber um valor pa-
drão (1, por exemplo). Se o mês for agosto, x poderá estar entre 1 e 31. Se for
setembro, entre 1 e 30.
41
Normalmente você verá os atributos tendo visibilidade privada (-). Isso sig-
nifica que somente os métodos da classe podem acessá-los. Visibilidade privada
impede que os valores sejam acessados diretamente. Para se alterar um atributo
privado é preciso que exista um método que todos possam acionar (acesso pú-
blico) relacionado a ele. Tal método controlará os valores que o atributo poderá
assumir. Assim, acesso público é um acesso irrestrito e acesso privado é um
acesso restrito. O modo de acesso protegido está relacionado com herança e
será discutido oportunamente.
Vale ressaltar que structs em C seriam equivalentes a uma classe sem mé-
todos com atributos de acesso irrestrito. Ou seja, sem proteção alguma. Agora
já temos isso bem claro.
Bom, voltando aos atributos, estes têm tipo de dado e podem ainda apre-
sentar um valor padrão. Note que na classe a seguir, o atributo Nome é do tipo
String e o valor padrão, representado pelo símbolo de igual, é Ednaldo:
Pessoa
+Nome : String = “Ednaldo“
+Idade : int = 43
...
...
+fazer_aniversario()
+Imprimir()
+Trocar_suprimento()
+Ligar()
+Desligar()
-Alarme()
Existem vários relacionamentos entre classes. Nosso foco será apenas nas
relações de herança e composição. A relação de herança é aquela em que uma
classe herda características de uma classe mais genérica. Normalmente é re-
conhecida por meio da relação é-um. “Um cachorro é um mamífero” indica que
cachorro herda características de uma classe mais genérica, que é a classe ma-
mífero. Na verdade, a classe mamífero é mais genérica, pois permite que vários
animais herdem características dela (tigre, gato, cavalo, etc.).
3.8 Exemplos
#include <stdlib.h>
#include <iostream.h>
using namespace std;
class lâmpada {
private:
int voltagem;
bool estado;
public:
lampada(); // construtor sem parâmetro
lampada(int v); // construtor com parâmetro
void ligar_desligar(); // simula o interruptor de ligar/
// desligar
bool get_estado(); // obtém o estado da lâmpada
int get_voltagem(); // obtém a voltagem da lâmpada
void imprime(); // imprime os atributos da lâmpada
};
Note que em C++ a definição da classe deve terminar com ponto e vírgula
(;). Os atributos normalmente estão na área private da classe e os métodos na
área public. Isso acontece porque desejamos proteger o acesso aos atributos, de
forma a evitar que o objeto tenha valores indevidos. Imagine que a lâmpada possa
ser somente 110 ou 220V. E se alguém desejasse que a lâmpada assumisse o
valor 145V para a voltagem? Em C, você se lembra, isso era possível, visto que
44 struct apenas agrupava valores, mas não os protegia. Agora, com a definição do
tipo de acesso private para voltagem e estado, se o programador criar um objeto
chamado lâmpada_sala, então ele poderá acionar apenas os métodos ligar_des-
ligar( ), get_estado( ), get_voltagem( ) e imprime( ). Note que os construtores não
estão na lista. Isso significa que não podem ser acionados pelo programador. Um
construtor é acionado apenas quando há a declaração de um objeto e tal aciona-
mento acontece de forma automática. No caso anterior, pode-se observar a exis-
tência de dois construtores. Um construtor tem o mesmo nome da classe, mas
não tem tipo de retorno. Assim, quando houver uma classe ABC, deverá existir
um construtor – cujo nome será ABC – que será o responsável pela inicialização
do objeto. Se houver dois ou mais construtores, então, o compilador identificará
qual será chamado a partir do número e tipo dos parâmetros que estão na lista
de parâmetros. A combinação nome do método e lista de parâmetros é conhecida
como assinatura. Métodos com o mesmo nome, mas com assinaturas distintas,
são diferentes. Em orientação a objetos isso significa sobrecarregar o método.
No caso em tela, ocorre uma sobrecarga de construtor. Veremos mais a respeito de
sobrecarga na próxima unidade.
Se no programa principal o programador criar o objeto L sem parâmetro,
então o construtor sem parâmetros é chamado. Caso contrário, se o programador
definir o objeto L(220), então a lâmpada L terá 220V.
lampada::lampada(){
voltagem = 110;
estado = false;
}
lampada::lampada(int v){
if (v == 110 || v == 220)
voltagem = v;
else
voltagem = 110;
estado = false;
}
Os métodos get (do inglês pegar, obter) são responsáveis pela obtenção dos
valores armazenados nos atributos de acesso restrito (private). Note que não é
possível acessar o atributo de forma direta simplesmente pelo fato de que tal ato
poderia quebrar toda a segurança do objeto. Lembre-se que, se alguém pode
acessar o atributo estado para imprimir seu valor na tela, também pode acessá-lo
para modificá-lo.
Note, também, que o compilador sabe a qual classe o método pertence por
intermédio do operador de escopo ::. Isso significa que, na verdade, o método
tem um nome e um sobrenome. Como exemplo, utilizemos o método imprime
do último código. É um método cujo nome é imprime e o sobrenome é lâmpada
(pertence à classe lâmpada). Seu tipo de retorno é void, ou seja, não há tipo de
retorno. Por isso, em seu código, não há o comando return.
46
int main(int argc, char** argv) {
lampada sala(9), cozinha(220), escritorio;
escritorio.imprime();
cout << “estado das lampadas \n“;
cout << “sala “ << sala.get_estado() << endl;
cout << “cozinha “ << cozinha.get_estado() << endl;
cout << “escritorio “ << escritorio.get_estado() << endl;
sala.ligar_desligar();
...
return (EXIT_SUCCESS);
}
Outro fato digno de nota no código é que não há um método set para alterar
a voltagem da lâmpada, por exemplo. Se ela é criada com uma voltagem X, per-
manecerá com tal voltagem até que se queime e seja inutilizada.
47
class horario //nome da classe
{ //início da declaração da classe
private: //especificador de acesso.
int hora; //atributo hora
int minuto; //atributo minuto
int segundo; //atributo segundo
public: //especificador de acesso.
horario(); //construtor
horario(int,int,int); //construtor com 3 parâmetros
acerta_hora(int); //método acerta_hora
acerta_minuto(int); //método acerta_minuto
acerta_segundo(int); //método acerta_segundo
~horario(); //destruidor
}; //fim da classe.
//atenção: não esqueça o ;
class horario {
private:
int hora;
int minuto;
int segundo;
public:
horario() { hora = 10; minuto = 5; Segundo = 2;}
horario(int,int,int);
48 acerta_hora(int);
acerta_minuto(int);
acerta_segundo(int);
~horario();
};
horario::horario(int a, int b, int c) {
hora = minuto = segundo = 0;
if (a >=0 && a < 24)
hora = a;
...
}
Já vimos pelo exemplo da classe lâmpada que para definir o método fora
da classe devemos especificar a qual classe ele pertence. Assim, o construtor
com parâmetros deverá ter nome e sobrenome, como no exemplo de código
apresentado.
horario::~horario() { }
Sim, o código é vazio. Para casos em que não exista alocação dinâmica de
memória, os destruidores são simples e não precisam nem de declaração. Para
os casos de alocação dinâmica de memória, o destrutor é necessário, pois é ele
que devolve a memória emprestada pelo construtor no momento da alocação
dinâmica.
Vale relembrar que tanto o construtor como o destrutor não são acionados
pelo programador. O construtor é acionado no momento da declaração do objeto
e o destruidor é acionado quando termina o escopo do objeto.
Como já foi indicado neste livro, você poderá comprovar que C++ e Java são
linguagens muito parecidas.
A classe Horário deverá ser delimitada pelo abre chaves ({) e o fecha chaves
(}). Mas note que, diferentemente de C++, em Java não há ponto e vírgula após o
} que indica final da classe.
Bom, a classe Horário está pronta e pode ser compilada. Na linha de co-
mando, digite:
javac Horário.java
51
class jogo {
private:
char tab[3][3]; // armazena jogadas
int jogadas; // controla total de jogadas
char jogador; // controla de quem é a vez
public:
jogo( ); // inicializa o jogo
bool terminou( ); // jogo terminou?
char verifica_vencedor( ); // quem venceu?
bool posicionar_peca(int, int); // posicionar peça
void jogar( ); // inicia o jogo
void limpa_tela( ); // limpa a tela
void troca_jogador( ); // troca jogador
void desenhar_tabuleiro( ); // desenha tabuleiro
};
jogo::jogo( ) {
int i, j;
for (i=0;i<3;i++)
for (j=0;j<3;j++)
tab[i][j] = ’*’;
jogadas = 0;
srand(time(NULL)); // inicializa semente para sorteio
if (rand( ) % 2 == 0) jogador = ’X’;
else jogador = ’O’;
}
bool jogo::terminou( ) {
if (jogadas < 9 && verifica_vencedor( ) == ’*’)
return false;
else
return true;
}
54
Unidade 4
Introdução à sobrecarga
4.1 Primeiras palavras
Muitas vezes, nos deparamos com a seguinte situação: temos uma função
em C++ que é responsável pelo cálculo do quadrado de um número. E ela funciona
para valores inteiros. Mas e se desejássemos que ela funcionasse também para
valores decimais? Na verdade, deveríamos criar outra função com o mesmo có-
digo, mas tipos de parâmetros diferentes! Replicar o código apenas por causa do
tipo de parâmetro não faz muito sentido, faz? Mas a sobrecarga de métodos é
mais que isso, e você verá detalhes em breve.
C = A + B;
tenha significado para inteiros, floats e doubles, mas também para vetores,
matrizes, números complexos, frações, etc. Se A, B e C fossem objetos da classe
fração, então o comando apresentado seria a atribuição para o objeto C do resul-
tado da soma de duas frações. Lindo, não? É o que também será abordado nesta
unidade.
57
4.3 Sobrecarga de métodos
class vetor
{
private:
float v[10];
public:
vetor(); // construtor não tem tipo de retorno!
void adiciona(float); // adiciona um float a
// todos os elementos
void adiciona(vetor); // adiciona um outro vetor ao atual
...
};
Vetor A;
for (i=0;i<10;i++)
A.atribui(i,i+1);
Agora, vamos supor que alguém deseje transformar a sequência {1, 2, 3,...,10}
58 em {2, 3, 4,...,11} por meio da chamada do método adiciona:
A.adiciona(1);
C.zera();
C.adiciona(A);
C.adiciona(B);
C.imprime();
Supondo B = {_1, _2, _3,..., _10} o resultado apresentado pelo método im-
prime para o objeto C seria:
{0,0,0,0,0,0,0,0,0,0,0}
Note que o método adiciona, nesse caso, passa um vetor e não um único
número. Os nomes são iguais, mas as operações não! Ah, e o programa saberá
qual método chamar apenas pela assinatura!
C = A + B;
Isso significa que a linguagem C++ permite que alguns operadores possam
ter sua funcionalidade estendida para outros tipos de dados. E com a “inteligên-
cia” embutida na linguagem, é possível fazer a seguinte operação:
C = A + 1;
Vamos ver como fazer a soma de dois vetores (com o operador +) em C++.
A declaração de operador de adição em C++ tem a seguinte sintaxe:
59
Tipo_retorno operator+ (parâmetros);
1. Vetor Vetor::adicao(vetor x)
2. {
3. vetor z;
4. int i;
5. for (i=0;i<x.tam;i++)
6. z.elemento[i] = elemento[i] + x.elemento[i];
7. z.tam = tam;
8. return z;
9. }
O que acontece é que quando uma chamada de método é feita, ela é feita
por um objeto. Provavelmente, em algum lugar do código (que pode ser no pro-
grama principal, por exemplo), haveria, em nosso exemplo, a seguinte linha de
comando:
a.atribui(b.adicao(c));
a.operator=(b.operator+(c));
a = b + c;
Bom, temos a atribuição! Ela tem algumas explicações extras! Existem duas
coisas novas: & (na primeira linha) e o return *this (antes do fecha chaves).
O & significa que o método retornará uma referência do próprio objeto que
chamou. Isso permite alterar o valor atual do objeto. Você deve ter muito cuidado
com o uso de referências. Sempre que houver TIPO & na declaração significa
que haverá o retorno de uma referência para um objeto do tipo TIPO. E sempre que
você fizer uma declaração de um tipo dentro de um método (como o objeto z do
método de adição), ele automaticamente desaparecerá após o fim do método.
Seu escopo (sua vida) terminará após o encerramento do método. Assim, você
não poderá utilizar TIPO & e fazer o retorno de um objeto z criado dentro de um
método. Claro?!
Bom, o *this significa que o código está retornando o objeto atual. Se você
quer atualizar o valor do elemento que está invocando o método de atribuição,
então deve utilizar & e *this.
Um fato importante é que o método é acionado por um objeto que está à sua
esquerda. E isso causa algum problema quando fazemos a seguinte operação:
a = 1 + b;
61
Quem aciona o método de adição? Não pode ser o número 1, pois os inteiros
trabalham com tipos pré-definidos e o vetor que estamos trabalhando não era
conhecido dos inteiros até agora!
Existe um truque aqui! Você pode utilizar a declaração friend. Friend indica
amizade e permite que um objeto acesse os dados de outro. Se dentro da classe
vetor houver uma declaração explícita de que um método X externo à classe é
friend dela, então X poderá acessar os dados daquela classe.
Exemplo:
classe vetor {
friend vetor operator+(int, vetor);
… // demais declarações da classe
};
a = operator+(1,b);
Sim, o número 1 e o objeto b são passados por parâmetro. Note que, nesse
caso, não há um objeto que invoca o método (porque ele não pertence a nenhu-
ma classe!).
class vetor {
friend ostream &operator<< (ostream &, vetor);
friend istream &operator>>(istream &, vetor &);
…
};
ou
+ - * / % ^ & |
~ ! = < > += -= *=
/= %= ^= &= |= << >> >>=
<<= == != <= >= && || ++
-- ->* , -> [] () new Delete
63
Ainda existem dois operadores (pré e o pós incremento) que precisam ser
explicados. O pré-incremento ocorre quando fazemos a operação ++x. Já o pós-
incremento ocorre quando o operador aparece após o objeto. Assim, poderíamos
ter os valores internos de nosso objeto vetor sendo atualizados tanto por um
como por outro operador. A tarefa do pré-incremento é atualizar o objeto antes
de utilizá-lo, enquanto a tarefa do pós-incremento é exatamente oposta a esta, ou
seja, utilizá-lo antes de incrementá-lo. Para simplificar, vamos supor que estejamos
trabalhando com inteiros (vamos voltar aos objetos em breve), e que o valor de
x seja 2. A linha de código z = x++ + 2; faz com que z seja 4 e que x se torne 3.
Se, entretanto, a linha de código fosse z = ++x + 2; o x também seria 3 após a
execução da linha, mas z seria 5!
64
vetor vetor::operator++(int z) {
vetor aux = *this;
int i;
for (i=0;i<tam;i++) // poderia ser, também, i<thistam
elemento[i] = elemento[i]+1; // ou thiselemento[i]...
return aux;
}
class caixote {
// atributos
private double largura, altura, profundidade;
// construtor sem parâmetros
// cria um caixote padrão de tamanho 1
caixote( ) { largura = 1; altura = 1; profundidade = 1;}
// cria um caixote com tamanho L (se L for positivo)
caixote( double L) {
if (L > 0) largura = altura = profundidade = L;
else largura = altura = profundidade = 1;
}
// cria um caixote com dimensões L x A x P desde que
// cada uma das dimensões seja positiva
caixote(double L, double A, double P) {
if (L > 0) largura = L;
else largura = 1;
if (A > 0) altura = A;
else altura = 1;
if (P > 0)
profundidade = P;
else
profundidade = 1;
}
65
// método de cálculo do volume do caixote
double volume( ) {
return largura * altura * profundidade;
}
}
caixote c1(5,6,7);
class caixoteDemo {
public static void main(String args[]) {
caixote c1 = new caixote(5,10,15);
caixote c2 = new caixote( );
caixote c3 = new caixote(15);
double vol1, vol2, vol3;
vol1 = c1.volume();
...
System.out.println(“ vol 1 = “ + vol1);
}
}
66
Além disso, foi apresentada a sobrecarga de operadores, que torna o código
C++ mais legível e intuitivo. Com ele é possível que operações como A = B + C;
ocorram também para classes definidas pelo programador (tais como fração, nú-
mero complexo, coordenadas polares, matrizes, etc.). Tal facilidade não existe
em Java.
Alguns livros como os da série How to Program (DEITEL & DEITEL, 2009a,
2009b) abordam o assunto sobrecarga. Em Deitel & Deitel (2009a) há um capítulo
inteiramente dedicado à sobrecarga de operadores. Um comando pouco explorado
nesta unidade foi o friend, que é explorado com um pouco mais de detalhes em
Deitel & Deitel (2009a).
67
Unidade 5
Sempre que você optar pela escolha de um tamanho fixo antes da execução
do programa, o compilador providenciará a alocação prévia da memória a ser
utilizada e isso não poderá ser modificado durante a execução de seu programa.
Isso se chama alocação estática de memória e se contrapõe ao conceito de
alocação dinâmica de memória.
A alocação dinâmica, por outro lado, permite que seu programa aguarde até
o momento da execução e consulte o usuário para saber qual a quantidade de me-
mória que deverá ser alocada para executar a tarefa. Como cada usuário fornecerá
uma resposta e como a decisão da quantidade de memória utilizada ficará adiada
para quando o usuário executar o programa, isso se chama alocação dinâmica de
memória. Percebe-se que com a alocação dinâmica de memória, o programa se
adequará à necessidade do usuário.
5.3 Revisão
72
Figura 3 Representação aérea de um campo de futebol.
E para saber quem está com a bola agora? Pergunte ao operador da câmera,
ou seja, utilize o operador *:
Em C, podemos utilizar ponteiros para apontar para uma variável única, para
um vetor, para uma matriz bidimensional, etc. Uma aplicação simples seria criar um
programa (ou função) para informar se uma cadeia de caracteres é palíndroma ou
não. Algo é palíndroma se a leitura feita da direita para a esquerda for igual à leitura
feita da esquerda para a direita. “ELE” é uma cadeia de caracteres que satisfaz tal
propriedade.
p = (int *) malloc(sizeof(int));
if (p == NULL) {
printf(“Solicitação não atendida! \n“);
exit(1);
}
A partir daí a coisa é simples! Basta utilizar o ponteiro da mesma forma que
76 foi utilizado anteriormente. É importante observar que quando uma alocação de
memória é feita, um espaço da área de dados é reservada para seu uso. De-
pois de utilizá-lo, você deve devolvê-lo ao dono para que, caso alguém deseje,
possa utilizá-lo. Se você só realizar solicitações de alocação e não fizer nenhuma
devolução será equivalente a um aluno que vai à biblioteca e só faz empréstimos
sem devolver. Os livros emprestados não estarão disponíveis para que outros pos-
sam utilizá-los. Em C a devolução é feita com o comando free. Lembre-se de
utilizá-lo!
...
int *x;
/* vetor com 10 posições */
x = (int *) malloc(10 * sizeof(int));
if (x != NULL)
{
for (i=0;i<10;i++)
x[i] = i;
... /* outras operações */
free(x); /* devolver a memória para o sistema */
x = NULL; /* por questões de segurança */
}
...
77
Utilizamos data por ser uma estrutura a ser explorada também em orienta-
ção a objetos e por servir como elemento de comparação. A estrutura em ques-
tão tem 3 elementos (dia, mês e ano). Se houvéssemos declarado uma variável
dia_independencia do tipo data, poderíamos fazer a atribuição do dia assim:
dia_independencia.dia = 7;
Para inserir uma pessoa na fila deve-se utilizar o método inserir (int), em que
o parâmetro a ser passado é a identificação da pessoa. Se a fila já estiver cheia, o
método deverá retornar o valor booleano falso, que indicará que a pessoa não pôde
ser inserida na fila.
Toda vez que um método retorna à cópia de um objeto e toda vez que cria-
mos um objeto utilizando outro como exemplo (lembra-se do exemplo da ove-
lha?), o construtor de cópia entra em ação.
X::X(X&) { ... }
79
class vetor {
private:
int elementos[10];
int total;
public:
vetor( ); // construtor sem parâmetros
vetor(vetor &); // construtor de cópia
...
};
vetor::vetor( ) {
int i;
for (i=0;i<10;i++)
elementos[i] = -1;
total = 0;
}
vetor::vetor(vetor &x) {
int i;
for (i=0;i<10; i++)
elementos[i] = x.elementos[i];
total = x.total;
}
vetor::vetor(vetor &x) {
int i;
elementos = new int [10]; // alocação do novo vetor
for (i=0;i<10; i++)
elementos[i] = x.elementos[i];
total = x.total;
}
Assim, o novo construtor de cópia vai ter uma cópia de todos os elementos
do vetor original, mas não terá a fragilidade de apontar para o mesmo local do
original.
*C = new circulo;
Como exemplo, vamos utilizar a classe fila. Queremos ter uma fila de ele-
mentos (vamos utilizar letras) que tenha um tamanho máximo definido pelo
programador. Se ele não definir nada, então será criada uma fila padrão de
tamanho 10. Na fila, o procedimento é atender aquele que está em primeiro
lugar. Quando o primeiro da fila for atendido, todos os outros devem mudar de
posição. Ou seja, quem estava em segundo passa a ficar em primeiro, quem
estava em terceiro passa para segundo e assim por diante. Resumindo, o fun-
cionamento é igual ao de uma fila de padaria. A seguir serão apresentados os
principais trechos do código C++.
class fila {
private:
char *elemento;
int tammax, tam_atual;
public:
fila();
fila(int);
~fila();
bool insere(char);
83
char retira();
void imprime();
};
fila::fila()
{
cout << “ construtor sem parâmetro“ << endl;
elemento = new char[10];
if (elemento != NULL) {
tammax=10;
tam_atual=0;
} else
exit (1);
}
84
fila::~fila()
{
cout << “destrutor“ << endl;
delete [] elemento;
elemento = 0;
}
bool fila::insere(char a)
{
if (isalpha(a) && tam_atual < tammax)
{
elemento[tam_atual++] = a;
return true;
}
else
return false;
}
O método retira deve retornar o valor que está sendo retirado da fila para quem
fez a chamada do método. Muitos confundem o conceito e acreditam que imprimir
o número retirado da fila já seja a solução. Não é! O valor deve ser devolvido e não
impresso. O valor impresso seria útil para o usuário que estivesse acompanhando
a execução do programa (caso a impressão fizesse algum sentido). Números sal-
tando na tela não fazem sentido. O valor retornado é útil para o programador, que
poderá utilizar o elemento retirado da fila para fazer testes, etc.
char fila::retira()
{
int i; 85
char letra;
if (tam_atual > 0)
{
letra = elemento[0];
for (i=0;i<tam_atual;i++)
elemento[i] = elemento[i+1];
tam_atual--;
return letra;
}
else
return ‘0’;
}
O método imprime é responsável por imprimir a fila. Se ela estiver vazia, não
imprime nada (só muda de linha).
void fila::imprime()
{
int i;
for (i=0;i<tam_atual;i++)
cout << elemento[i] << “ “;
cout << endl;
}
87
5.8 Considerações finais
88
Unidade 6
Composição e herança
6.1 Primeiras palavras
Apesar de a operação “copy & paste” ser muito utilizada, ela traz incon-
venientes, do ponto de vista da engenharia de software. A replicação do código
sem um adequado gerenciamento provocará efeitos colaterais sempre que hou-
ver uma manutenção no sistema.
6.3 Composição
No mundo real, um objeto é composto por outros. Uma porta é composta por
maçaneta, fechadura, madeira (se for porta de madeira), etc. A própria fechadura
é composta por diversos outros componentes. Um monitor de vídeo é composto
por placas de circuito integrado, botões (de ligar/desligar, contraste, brilho, etc.),
tela, etc. Enfim, o mundo real contém objetos que são obtidos por meio da com-
binação com outros objetos. Quando dizemos que um carro tem uma direção,
estamos indicando que “direção” faz parte de “carro”; quando um empregado tem
uma data de admissão, estamos indicando que a data de admissão faz parte da
classe “empregado”. 91
A relação tem-um é uma relação importante em orientação a objetos, pois
permite que o processo de criação de uma classe mais complexa seja feito de
forma similar a um jogo Lego.
6.4 Herança
94
Podemos dizer que circulo é um ponto com um raio. A classe circulo indica
que está herdando características da classe ponto por meio da declaração extends.
Dessa forma, os atributos x e y também fazem parte da classe circulo, assim
como os métodos set e get daquela classe.
Deve-se observar que em uma classe derivada o comando super deve ser
sempre o primeiro a ser executado. Se isso não acontecer, o construtor default da
classe base será acionado automaticamente. É importante que ele exista.
c.imprimir(); 95
Como mencionado, o método imprimir acionado é o da classe circulo. O mé-
todo imprimir da classe base está escondido. Na verdade, ele é acionado dentro
do método imprimir da classe circulo por meio da chamada super.imprimir().
Se uma classe é definida como final, então tal classe não poderá ser derivada.
bool bissexto(int a) {
if (a % 4 == 0 && a % 100 != 0) return true;
else
if (a % 4 == 0 && a % 100 == 0 && a % 400 == 0)
return true;
else
return false;
}
class data {
private:
int dia, mes, ano;
public:
data();
data(int, int, int);
void setDia(int);
void setMes(int);
96
void setAno(int);
int getDia();
int getMes();
int getAno();
void imprime();
};
data::data() {
dia = 1;
mes = 1;
ano = 2010;
}
data::data(int d, int m, int a) {
if (a > 1900 && a < 2011)
ano = a;
else
ano = 2010;
if (m >=1 && m <=12)
mes = m;
else
{
mes = 1;
if (d > 0 && d < 29)
dia = d;
else
if (d > 28 && d < 32)
{
dia = 30;
if (d == 29 && mes == 2 && bissexto(ano))
dia = d;
else
if (d == 31 && (mes == 1 || mes == 3 ||
mes == 5 || mes == 7 || mes == 8 ||
mes == 10 || mes == 12))
dia = d;
}
else
dia = 1;
}
}
void data::setAno(int a) {
if (a > 1900 && a < 2010) 97
ano = a;
else
ano = 2010;
}
void data::setMes(int m) {
if (m < 13 && m > 0)
mes = m;
else
mes = 1;
}
void data::setDia(int d) {
int x;
if (d > 0 && d < 29)
dia = d;
else
if (d > 28 && d < 32)
{
dia = 30;
if (d == 29 && getMes() == 2 && bissexto(getAno()))
dia = d;
else
{
x = getMes();
if (d == 31 && (x == 1 || x == 3 || x == 5 || x == 7 ||
x == 8 || x == 10 || x == 12))
dia = d;
}
}
else
dia = 1;
}
int data::getAno() {
return ano;
}
int data::getMes() {
return mes;
}
98
int data::getDia() {
return dia;
}
void data::imprime() {
cout << getDia() << “/“ << getMes() << “/“ << getAno() << endl;
}
Como informado, a classe pessoa utiliza a classe data (uma pessoa tem
uma data de nascimento), ou seja, uma composição.
class pessoa {
private:
char nome[20];
char sobrenome[20];
data nascimento;
public:
pessoa();
pessoa(char *, char*);
void setNome(char *);
void setSobrenome(char *);
char *getNome();
char *getSobrenome();
void imprime();
void setData(int, int, int);
};
pessoa::pessoa() {
strcpy(nome,“Ana“);
strcpy(sobrenome,“Silva“);
nascimento.setAno(2010);
nascimento.setDia(1);
nascimento.setMes(1);
}
pessoa::pessoa(char *n, char *s) {
strcpy(nome,n);
strcpy(sobrenome,s);
nascimento.setAno(2010);
nascimento.setDia(1);
nascimento.setMes(1);
}
void pessoa::setNome(char *x){
strcpy(nome,x); 99
}
void pessoa::setSobrenome(char *x) {
strcpy(sobrenome,x);
}
char * pessoa::getNome() {
return nome;
}
char * pessoa::getSobrenome() {
return sobrenome;
}
void pessoa::imprime() {
cout << getNome() << “,“ << getSobrenome() << endl;
nascimento.imprime();
}
void pessoa::setData(int d, int m, int a) {
nascimento.setDia(d);
nascimento.setMes(m);
nascimento.setAno(a);
}
100
public:
estudante();
estudante(char *, char *, char *);
estudante(char *, char *, char *, char *, char *);
void setCurso(char *);
void setRA(char *);
void setUniversidade(char *);
char *getCurso();
char *getRA();
char *getUniversidade();
void imprime();
};
estudante::estudante() {
strcpy(curso,“Computacao“);
strcpy(RA,“123456“);
strcpy(universidade,“UFSCar“);
}
estudante::estudante(char *c, char *r, char *u) {
strcpy(curso,c);
strcpy(RA, r);
strcpy(universidade,u);
}
estudante::estudante(char *c, char *r, char *u, char *n, char
*s): pessoa(n,s)
{
strcpy(curso,c);
strcpy(RA, r);
strcpy(universidade,u);
}
101
void estudante::setCurso(char *c) {
strcpy(curso,c);
}
void estudante::setRA(char *r) {
strcpy(RA, r);
}
void estudante::setUniversidade(char *u) {
strcpy(universidade,u);
}
char * estudante::getCurso() {
return curso;
}
char *estudante::getRA() {
return RA;
}
char *estudante::getUniversidade() {
return universidade;
}
void estudante::imprime() {
cout << “ curso : “ << curso << endl;
cout << “ RA : “ << RA << endl;
cout << “ Univ. : “ << universidade << endl;
pessoa::imprime();
}
A tarefa de copiar, colar e adaptar normalmente gera erros. Erros são mui-
to caros para as fábricas de software. Recriar do zero também é caro (afinal,
todo o esforço de planejar, implementar e testar já foi feito por alguém). A melhor
solução é poder reutilizar um código pronto. Algumas formas foram vistas nesta
unidade, como herança e composição. Herança está associada à relação é-um,
enquanto composição está associada à relação tem-um.
102
Unidade 7
Polimorfismo
7.1 Primeiras palavras
Você já viu algum tipo de polimorfismo neste livro, mas ele ainda não foi
formalmente definido. Nesta unidade você verá vários tipos de polimorfismo exis-
tentes em orientação a objetos com exemplos em C++ e em Java.
template class<T>
void troca(T & a, T & b) {
T temp(a);
a = b;
b = temp;
}
meu_vetor <int> v;
O tipo que está dentro dos limites < > é que será utilizado para fazer a aloca-
ção de memória. No construtor, por exemplo, você faria a alocação new T[x], em
que x seria o tamanho do vetor. Note que, no exemplo anterior, o método at está
dentro da classe. Se ele estivesse fora, você teria que especificar a qual classe
pertence. Bom, isso você já sabe! O que você provavelmente não sabe é que se
estivesse fora, e se você estivesse utilizando template, então deveria colocar a
declaração de template antes do método! Na verdade, o template deve ser decla-
rado antes de cada método.
Com o uso de templates passa a ser possível criar objetos da classe meu_vetor
do tipo inteiro, float, double, etc. Muito simples!
108
7.3.2 Generics em Java
Generics em Java tem o mesmo objetivo que template em C++. Vamos criar
uma classe simples que apenas armazena um valor.
Mas é fácil notar que tal programa não conseguiria trabalhar com Strings.
Para trabalhar com Strings ou qualquer outro tipo precisaríamos que Obj armaze-
nasse um tipo de dado genérico, conforme o exemplo a seguir.
Vamos supor que você seja gerente de uma equipe em uma fábrica de
software. Você, junto com a equipe de projeto de software, estabeleceu as carac-
terísticas gerais da classe funcionário_empresa.
classe funcionario_empresa {
private:
char primeiro_nome[20];
char sobrenome[20];
int codigo_funcao;
110
int codigo_departamento;
float salario_base;
public:
funcionario_empresa(char * , char *);
~funcionario_empresa();
void atribui_salario(float);
void promocao();
void imprime_dados();
void imprime_vencimentos();
};
classe funcionario_empresa {
private:
char primeiro_nome[20];
char sobrenome[20];
int codigo_funcao;
int codigo_departamento;
float salario_base;
111
public:
funcionario_empresa(char * , char *);
~funcionario_empresa();
void atribui_salario(float);
void promocao();
virtual void imprime_dados();
virtual void imprime_vencimentos();
};
funcionario_empresa *genérico;
genericoimprime_dados();
classe mamífero {
private:
...
public:
virtual emitir_som();
};
mamífero *animal[10];
animal[0]emitir_som();
113
Não é difícil imaginar que o seguinte trecho de código do programa poderia
emitir sons distintos, como au au au, miau, ihhhhh, etc.:
for (i=0;i<10;i++)
animal[i]emitir_som();
115
7.5 Considerações finais
116
Unidade 8
Classes abstratas
8.1 Primeiras palavras
Temos uma ideia do que seja abstrato. Uma forma simples de se definir seria
“algo intangível, que não pode ser pego”. Uma ideia, por exemplo, é algo intangí-
vel. Mas o que seria classe abstrata em programação? Nesta unidade tal conceito
será explorado e alguns exemplos em C++ e em Java serão apresentados.
Pode acontecer que você, como gerente, queira forçar sua equipe a criar mé-
todos específicos para as classes derivadas. Você pode fazer isso de duas formas:
É claro que você vai preferir a segunda opção! É mais fácil, rápida e segura.
Assim, você pode criar uma classe abstrata, que não pode ser utilizada direta-
mente (daí vem a justificação do uso da palavra abstrato), mas que irá servir como
referência para a criação de outras classes. Imagine que você deseje que todas as
figuras geométricas em 2 dimensões tenham os métodos calcArea( ), calcPerime-
tro( ) e desenhar( ). É preciso sinalizar para o compilador que ele deverá controlar o
trabalho de seus programadores. O compilador, na verdade deverá:
2. Acusar erro se algum dos métodos não tiver sido criado em uma classe
derivada que será utilizada.
Você deve estar achando que classes abstratas e interfaces (de Java) são
conceitos parecidos e que podem ser usados com objetivos semelhantes. Cuida-
do! Uma classe pode estender uma única classe (que pode ser abstrata ou não),
mas pode implementar várias interfaces. Além disso, interfaces Java não permi-
tem declaração de atributos, enquanto classes abstratas permitem.
public abstract class Eletrodomestico {
private boolean ligado;
private int voltagem;
// métodos abstratos
public abstract void ligar();
public abstract void desligar();
// construtor
public Eletrodomestico(boolean l, int volt) {
this.ligado = l;
this.voltagem = volt;
}
// métodos concretos
public void setVoltagem(int voltagem) {
this.voltagem = voltagem;
}
public int getVoltagem() {
return this.voltagem;
}
public void setLigado(boolean ligado) {
this.ligado = ligado;
}
public boolean isLigado() {
return ligado;
120 }
}
A classe eletrodoméstico, no exemplo anterior, cria um “modelo” que deve
ser compartilhado entre outras classes que herdam as características de eletro-
domésticos. Nota-se que ter uma voltagem e estar ligado ou não são caracterís-
ticas de aparelhos elétricos. Assim, pode-se criar uma classe televisão (televisão
é um eletrodoméstico) que implemente os métodos abstratos da superclasse. Por
questões de espaço serão apresentados apenas alguns dos principais métodos
da classe televisão:
public class TV extends Eletrodomestico {
private int tamanho;
private int canal;
private int volume;
public TV(int tam, int volt) {
super (false, volt);
this.tamanho = tam;
this.canal = 0;
this.volume = 0;
}
121
8.4.2 Exemplos em C++
virtual nome_método() = 0;
Assim, quando alguém cria um método xxx() virtual e o iguala a zero, na ver-
dade o que está sendo desejado é que a implementação de tal método ocorra em
cada uma das classes derivadas daquela classe à qual xxx pertence. Um método
puramente virtual (são assim chamados os métodos igualados a zero) não pode
ser instanciado em qualquer parte do programa (você só poderá utilizar ponteiros
para a classe). Ou seja, se você criou um método virtual xxx() = 0; na classe Ani-
mal, não poderá fazer a seguinte declaração:
Animal x;
Classes abstratas trazem segurança para quem planeja uma classe base e
deseja que todos a utilizem como referência. Como ela não pode ser instanciada,
o programador será obrigado a criar classes com implementações corretas de
todos os métodos virtuais puros (C++) ou abstratos.
123
Figura 12 Caixa de diálogo do novo projeto.
125
Figura 15 Ambiente de desenvolvimento do NetBeans.
127
Apêndice B
Classe simples
• Volume;
• Indicador de ligado/desligado;
• Estações.
129
class radio
{
private:
int volume; // volume (0 a 10)
int sintonia_AM; // estação AM atual da memória
int sintonia_FM; // estação FM atual da memória
int memoria[2][5]; // memórias de estações preferidas
bool estacao_FM; // indica se está no modo AM ou FM
bool ligado; // indica se está ligado ou não
float estacoes[2][20]; // total de estações AM e FM
public:
radio(); // construtor
float busca_estacao(); // “botão“ de buscar estação
void grava_memoria(int); // “botão“ de gravar memória
void sintoniza_memoria(int); // “botão“ de sintonizar
// estação gravada em
// memória
void muda_tipo_estacao(); // troca de AM para FM ou
// de FM para AM
void liga_desliga(); // “botão“ de ligar e desligar
void aumenta_volume(); // “botão“ de aumentar
// volume
void diminui_volume(); // “botão“ de diminuir volume
void display(); // display do rádio
};
radio::radio()
{
estacao_FM = true;
sintonia_AM = 0;
sintonia_FM = 0;
ligado = false;
estacoes[0][0] = 650;
estacoes[0][1] = 685;
estacoes[0][2] = 710;
130
estacoes[0][3] = 730;
estacoes[0][4] = 745;
estacoes[0][5] = 770;
estacoes[0][6] = 810;
estacoes[0][7] = 830;
estacoes[0][8] = 850;
estacoes[0][9] = 870;
estacoes[1][0] = 88.1;
estacoes[1][1] = 90.2;
estacoes[1][2] = 91.6;
estacoes[1][3] = 92.1;
estacoes[1][4] = 94.0;
estacoes[1][5] = 94.7;
estacoes[1][6] = 95.1;
estacoes[1][7] = 98.3;
estacoes[1][8] = 99.1;
estacoes[1][9] = 99.8;
volume = 5;
for (int i=0;i<5;i++)
{
memoria[0][i] = 0;
memoria[1][i] = 0;
}
}
O botão de buscar estação é responsável por sintonizar uma nova estação.
Ou seja, se atualmente o rádio está sintonizado na estação 90.2 FM, então o
busca estação vai para a próxima estação, ou seja, 91.6.
float radio::busca_estacao() {
if (ligado) {
if (estacao_FM) {
sintonia_FM++;
if (sintonia_FM == 10)
sintonia_FM = 0;
}
else {
sintonia_AM++;
if (sintonia_AM == 10)
131
sintonia_AM = 0;
}
}
}
void radio::grava_memoria(int m) {
if (ligado) {
if (estacao_FM) {
if (m >= 0 && m <= 4)
memoria[1][m] = sintonia_FM;
} else
if (m >= 0 && m <= 4)
memoria[0][m] = sintonia_AM;
}
}
void radio::sintoniza_memoria(int m) {
if (ligado) {
if (estacao_FM) {
if (m >= 0 && m <= 4)
sintonia_FM = memoria[1][m];
} else
if (m >= 0 && m <= 4)
sintonia_AM = memoria[0][m];
}
}
132
void radio::muda_tipo_estacao() {
if (ligado)
estacao_FM = !estacao_FM;
}
void radio::liga_desliga() {
ligado = !ligado;
}
void radio::aumenta_volume() {
if (ligado)
volume++;
if (volume > 25) volume = 25;
}
void radio::diminui_volume() {
if (ligado)
volume--;
if (volume < 0) volume = 0;
}
void radio::display() {
if (ligado) {
cout << “volume = “ << volume << endl;
if (estacao_FM)
cout << “FM = “ << estacoes[1][sintonia_FM] << endl;
else
cout << “AM = “ << estacoes[0][sintonia_AM] << endl;
}
else
cout << “radio desligado “ << endl;
}
O programa principal deverá contar com uma função menu que disponibi-
lizará as funcionalidades do rádio. Poderia ser parte da classe, como se fosse a
parte externa do rádio com seus botões.
int menu() {
int opcao;
opcao = 0; 133
while (opcao <= 0 || opcao > 8) {
cout << “ 1. Botao liga/desliga“ << endl;
cout << “ 2. Botao AM/FM “ << endl;
cout << “ 3. Botao busca estacao “ << endl;
cout << “ 4. Botao grava estacao “ << endl;
cout << “ 5. Botao memoria “ << endl;
cout << “ 6. Botao aumenta volume “ << endl;
cout << “ 7. Botao diminui volume “ << endl;
cout << “ 8. finalizar “ << endl;
cin >> opcao;
}
return opcao;
}
Por fim, o programa principal. Nele declaramos um objeto X e, por meio das
escolhas do usuário, fazemos o acionamento dos métodos de X.
3 O exemplo do jogo da forca foi elaborado por alunos de graduação da UFSCar. Os nomes 135
dos autores foram preservados no código.
/*
* JOGO DA FORCA
* Autores:
Alan Cesar Laine
Bruno Fernando Rodrigues
Guilherme Cuppi Jerônimo
Guilherme Rigo Recio
*/
//Construtor
jogo_da_forca::jogo_da_forca(int nivel)
{
int i; //Contador
numero_erros = 0;
posicao_erros = 0;
140 posicao_acertos = 0;
// Atribuição da dica com relação a sorteio_parcial
if(sorteio_parcial >= 0 && sorteio_parcial <= 5)
strcpy(dica, “Animal“);
else
if(sorteio_parcial >= 6 && sorteio_parcial <= 11)
strcpy(dica, “Novela ou Mini-Serie“);
else
if(sorteio_parcial >= 12 && sorteio_parcial <= 17)
strcpy(dica, “Fruta“);
else
if(sorteio_parcial >= 18 && sorteio_parcial <= 23)
strcpy(dica, “Filme“);
else
strcpy(dica, “Pais“);
}
void jogo_da_forca::imprimir()
{
int i, j; //Contadores
// Comando condicional utilizado para imprimir
// o jogo sendo acionado pelo número de erros
// 0 indica que a forca está vazia
// 1 indica que a forca já tem a cabeça
// 2 indica cabeça e pescoço
// etc.
switch(numero_erros)
{
case 0:
cout << “ _____ “;
cout << “ “ << “Dica: “ << dica << endl;
cout << “| |“ << endl;
cout << “|“ << endl;
cout << “|“ << endl;
cout << “|“ << endl;
cout << “|“ << endl;
cout << “|“;
break;
case 1:
cout << “ _____ “;
cout << “ “ << “Dica: “ << dica << endl; 141
cout << “| |“;
cout << “ Letras Erradas: “;
for(i = 0; i < 1; i++)
cout << letras_erradas[i] << ‘ ‘;
cout << endl;
cout << “| O“ << endl;
cout << “|“ << endl;
cout << “|“ << endl;
cout << “|“ << endl;
cout << “|“;
break;
// assim por diante…
}
void jogo_da_forca::monta_palavra()
{
int i, j; //Contadores
while(matriz[sorteio][i] != letras_corretas[j]
&& j < strlen(letras_corretas))
j++; 143
if(matriz[sorteio][i] == letras_corretas[j])
palavra_digitada[i] = letras_corretas[j];
else
palavra_digitada[i] = ‘_‘;
}
}
bool jogo_da_forca::ganhar()
{
// Faz a comparação entre a palavra_digitada
// e a palavra sorteada
if(strcmp(palavra_digitada, matriz[sorteio]) == 0)
return true;
else
return false;
}
bool jogo_da_forca::perder()
{
// Verifica se o jogador atingiu o
// limite de erros
if(numero_erros == 7)
return true;
else
return false;
}
144
for(i = 0; i < strlen(letras_erradas); i++)
if (letras_erradas[i] == letra)
{
cout << endl << “Esta Letra Ja Foi Digitada!!!“ <<
endl << endl;
return true;
}
return false;
}
int jogo_da_forca::jogar()
{
char letra; //Letra que o jogador insere
monta_palavra();
imprimir();
145
letra_correta(letra);
letra_errada(letra);
monta_palavra();
}
while(!ganhar() && !perder());
146
// se é um inteiro
do
{
cout << “Escolha o nivel:“ << endl << endl;
cout << “ 1 - Facil“ << endl;
cout << “ 2 - Intermediario“ << endl;
cout << “ 3 - Dificil“ << endl << endl;
cout << “Nivel: “;
cin >> caracter;
cout << endl;
if(isdigit(caracter[0]) == 0)
cout<< “Nivel Invalido!!!“ << endl << endl;
}
while(isdigit(caracter[0]) == 0);
class VM {
friend ostream& operator<< (ostream &, VM);
friend istream& operator>> (istream &, VM);
friend VM operator+ (int, VM);
private:
int unidade, centavos;
char simbolo;
public:
VM( );
VM(char, int, int);
VM& operator++();
VM operator++(int);
VM& operator=(VM);
VM& operator=(int);
VM operator+(int);
VM operator+(VM);
};
VM::VM() {
unidade = 0;
centavos = 0;
simbolo = ‘R‘;
}
// operadores membros
VM VM::operator++ (int) {
VM temp(*this);
this->centavos++;
if (this->centavos == 100) {
this->centavos = 0;
this->unidade++; 149
}
return temp; // retorna o valor anterior
// às modificações
}
return *this;
}
// operador de adição
// realiza a operação se os símbolos forem iguais
// mantém o cuidado de não ferir a regra
// dos centavos
VM VM::operator+(VM val) {
VM temp;
if (simbolo == temp.simbolo){
temp.unidade = val.unidade + unidade;
temp.centavos = val.centavos + centavos;
if (temp.centavos > 99){
temp.centavos -= 100;
temp.unidade++;
}
150 }
return temp;
}
// operador de adição
// realiza a operação se os símbolos forem iguais
// Não há a preocupação com os centavos, pois
// o valor alterado é apenas referente à unidade.
VM VM::operator+(int val) {
VM temp;
return temp;
}
z = x + y;
cout << z;
return 0;
}
152
Referências
153
Sobre O Autor