Buscar

exercicios resolvidos Linguagens de Programação - Conceitos e Técnicas

Esta é uma pré-visualização de arquivo. Entre para ver o arquivo original

ExerciciosCap8.pdf
Resolução dos Exercícios do Capítulo VIII
1. Explique as vantagens de se possuir um mecanismo de exceções incorporado a LP.
Ilustre essas vantagens apresentando exemplos de código com funcionalidade
equivalente em C e JAVA.
As vantagens de se ter um mecanismo de exceções incorporado à LP são: a melhoria
da legibilidade dos programas, pois separam o código com a funcionalidade principal
do programa do código responsável pelo tratamento de exceções; o aumento da
confiabilidade dos programas, uma vez que normalmente requerem o tratamento
obrigatório das exceções ocorridas e porque promovem a idéia de recuperação dos
programas mesmo em situações anômalas; e o incentivo a reutilização, redigibilidade
e a modularidade do código responsável pelo tratamento (em particular, em
linguagens orientadas a objetos, exceções são instâncias de classes, o que faz com que
essa parte do programa herde todas as propriedades relativas à reutilização e à
modularidade fornecidas pela programação orientada a objetos).
Exemplo:
// Em C
int trata_formatacão_errada (void) {
printf (“Formatacao Errada”);
return -99;
}
int lenum () {
char s[30];
gets(s);
if (isNumber(s))
return atoi(s);
else
return trata_formatacao_errada();
}
int trata_divisão_zero (void) {
printf (“Divisão por zero.”);
return -98;
}
int divideInteiros (int numerador, int denominador) {
if (denominador == 0) 
return tratar_divisão_zero ();
else
return numerador / denominador;
}
main() {
//...
int num, den, resultado;
num = lenum();
if (num != -99) {
den = lenum();
if (den != -99) {
1
resultado = divideInteiros (num, den);
if (resultado != -98) {
//..
}
// Em JAVA
// ...
int num, den, resultado;
try {
int num = Integer.valueOf(n).intValue();
int den = Integer.valueOf(d).intValue();
int resultado = num / den;
} catch (NumberFormatException e) {
System.out.println (e);
 } catch (ArithmeticException e) {
System.out.println (e);
 }
 // ...
Os trechos de código em C e JAVA, mostrados no exemplo acima, cumprem
basicamente a mesma funcionalidade. Ambos lêem duas strings, tentam convertê-las
em números, caso tenham sucesso, dividem a primeira pela segunda. Caso não tenham
sucesso nas operações de conversão de string para números e de divisão, são
mostradas mensagens correspondentes e o programa prossegue a partir do fim do
trecho.
A abordagem de JAVA é mais legível do que a de C pois existe uma separação do
código do programa que implementa a funcionalidade (trecho escrito dentro do boloco
try) do código de tratamento de erros. Isso não ocorre em C, uma vez que após a
chamada das operações de leitura dos número e divisão é necessário incluir um teste
para verificar se ocorreu a condição excepcional.
A abordagem de JAVA também é mais confiável porque não requer que o
programador lembre, identifique e teste todas as possíveis condições causadoras de
exceções. Isso já é feito pelas funções e operações utilizadas (observe em particular
que não é necessário testar se o denominador é zero antes da operação de divisão).
Além disso, caso o programador se esqueça de tratar as exceções que possam ocorrer
em um determinado trecho, o compilador JAVA pode lembrá-lo (em caso de exceções
que não sejam RuntimeException ou promover um tipo particular de tratamento,
encerrando o programa e imprimindo dados que facilitem a identificação de onde e
porque ocorreu a situação excepcional).
Por fim, pode-se observar que a abordagem de JAVA é muito mais redigível que a de
C. Precisa-se escrever muito menos para se conseguir a mesma funcionalidade. Em
parte, isso ocorre pela existência de funções pré-definidas na biblioteca de classes. No
entanto, grande parte desta melhor redigibilidade é obtida pela não necessidade de
testar as operações antes ou depois de executá-las para verificar a ocorrência de
exceções (por exemplo, não é preciso testar duas vezes se a operação de conversão foi
bem sucedida, como ocorre no bloco principal em C). Adicionalmente, pode-se ainda
se beneficiar da orientação a objetos para reutilizar tratadores de exceções (por
exemplo, a função toString das classes NumberFormatException e
2
ArithmeticException é reutilizada no momento de imprimir as mensagens nos
tratadores de exceção do exemplo em JAVA).
2. Erros ordinários podem ser tratados no mesmo ambiente no qual foram
identificados. Cite vantagens no uso de um mecanismo de exceções como o de
JAVA para o tratamento desse tipo de erro.
Em JAVA, a ocorrência de erros é sinalizada através de exceções, isto é, objetos
especiais que carregam informação sobre o tipo de erro detectado. As vantagens de se
utilizar um mecanismo como o de JAVA para tratar erros ordinários são a melhoria da
legibilidade dos programas, pois separa o código com a funcionalidade principal do
programa do código responsável pelo tratamento de exceções; o aumento da
confiabilidade e robustez dos programas, uma vez que normalmente requer o
tratamento obrigatório das exceções ocorridas e porque promove a idéia de
recuperação dos programas mesmo em situações anômalas; e o incentivo a
reutilização e a modularidade do código responsável pelo tratamento (em particular,
em linguagens orientadas a objetos, exceções são instâncias de classes, o que faz com
que essa parte do programa herde todas as propriedades relativas à reutilização e a
modularidade fornecidas pela programação orientada a objetos). 
3. Analise o seguinte trecho de programa em C:
3
int leArquivo ( int v [] ) {
 char *n;
 int cod;
 FILE *p;
 cod = leNomeArq (n);
 if (cod == 0) {
 printf ("nome invalido");
 return -1;
 }
 cod = abreArq (n, p);
 if (cod == 0) {
 printf("arquivo inexistente");
 return -1;
 }
 cod = carregaArq (p, v, 100);
 if (cod == 0) return -2;
 cod = fechaArq (p);
 if (cod == 0) return -3;
 return 0;
}
int tentaLer (int v [ ] ) {
 int cod;
 do {
 cod = leArquivo (v);
 if (cod == -1) {
 if (!continua ( ))
 return cod;
 } else {
 return cod;
 }
 } while (1);
}
Considere que as funções leNomeArq, abreArq, carregaArq e fechaArq retornam
0 (zero) se não forem bem sucedidas e 1 (um), caso contrário. Considere também
que a função continua pergunta ao usuário se ele deseja tentar novamente e retorna
1 (um) em caso afirmativo e 0 (zero) em caso negativo. Refaça esse programa
usando o mecanismo de tratamento de exceções de C++. Na versão em C++, as
funções leNomeArq, abreArq, carregaArq e fechaArq retornam void mas disparam
respectivamente as seguintes exceções nomeExc, arqExc, cargaExc e fechaExc.
Compare as duas soluções em termos de redigibilidade e legibilidade, justificando.
Programa em C++:
void tentaLer () throw (nomeExc, arqExc) {
do { 
try {
leNomeArq(n);
abreArq(n, p);
} catch (nomeExc e) {
cout << “nome invalido\n”;
if (!continua()) throw e;
} catch (arqExc e) {
cout << “arquivo inexistente\n”;
if (!continua()) throw e;
}
while (1);
}
main () {
int vet [100];
char *n;
FILE *p;
try {
4
main ( ) {
 int cod;
 int vet [100];
 cod = tentaLer (vet);
 switch (cod) {
 case 0: break;
 case -1: 
 printf ("erro de nome");
 break;
 case -2:
 printf ("erro de carga");
 break;
 case -3:
 printf("erro de fechamento");
 };
 ordena (vet);
 imprime (vet);
}
tentaLer();
carregaArq (p, v, 100);
fechaArq (p);
} catch (nomeExc) {
cout << “erro de nome\n”;
} catch (arqExc) {
cout << “erro de nome\n”;
} catch (cargaExc) {
cout << “erro de carga\n”;
} catch (fechaExc) {
cout << “erro de fechamento\n”;
}
ordena (vet);
imprime (vet);
}
No programa escrito em C++ a legibilidade é melhorada, pois o código com a
funcionalidade principal do programa é separado do código responsável pelo
tratamento de exceções. A redigibilidade é melhorada porque não se necessita utilizar
códigos numéricos e testá-los em vários pontos do programa. 
4. Considere o seguinte esqueleto de programa em C++:
class B {
 int k;
 float f;
public:
 void f1() {
 …
 try { …
 throw k;
 …
 throw f;
 …
 }catch (float){
 … 
 }
 …
 }
}
class A {
 int j;
 float g;
 B b;
public:
 void f2() {
 …
 try { …
 try { …
 b.f1();
5
 …
 throw j;
 …
 throw g;
 …
 }catch(int){ 
 … 
 }
 …
 }catch (float){
 … 
 }
 …
 }
}
main ( ) {
 A a;
 …
 a.f2 ( );
 …
}
Indique, para cada possível exceção disparada, o local onde ela será tratada.
• Exceção k → O mecanismo de exceções tentará casar essa exceção com a do
tratador do bloco definido na função f1 da classe B. Nesse caso, não haverá
casamento e a exceção será propagada para o bloco mais interno definido na
função f2 da classe A. O tratador desse bloco será executado.
• Exceção f → O mecanismo de exceções tentará casar essa exceção com a do
tratador do bloco definido na função f1 da classe B. Nesse caso, haverá casamento
e esse tratador será executado.
• Exceção j → O mecanismo de exceções tentará casar essa exceção com a do
tratador do bloco definido na função f2 da classe A. Nesse caso, haverá casamento
e esse tratador será executado.
• Exceção g → O mecanismo de exceções tentará casar essa exceção com a do
tratador do bloco definido na função f2 da classe A. Nesse caso, não haverá
casamento e a exceção será propagada para o bloco mais externo definido na
função f2 da classe A. O tratador desse bloco será executado.
5. Explique os mecanismos oferecidos por C, C++ e JAVA para o tratamento de
exceções. Enfoque sua explicação na comparação dos seguintes aspectos: a)
obrigatoriedade ou não do tratamento de exceções por um usuário de uma função
que dispara exceções; b) existência de exceções disparadas pelo próprio
mecanismo de exceções da linguagem (tal como quando ocorre divisão por zero).
A linguagem de programação C não oferece qualquer mecanismo específico para o
tratamento de exceções, ficando a critério do programador implementá-lo ou não. As
linguagens C++ e JAVA possuem mecanismo de tratamento de exceções. Enquanto o
mecanismo de C++ não obriga o programador a tratar as exceções e não possui
exceções disparadas pelo próprio mecanismo de exceções da linguagem, o mecanismo
6
de JAVA oferece vários tipos de exceções detectadas automaticamente e força o
programador a tratar grande parte delas.
6. Considere o seguinte trecho de código em JAVA.
class InfracaoTransito extends Exception {}
class ExcessoVelocidade extends InfracaoTransito {}
class AltaVelocidade extends ExcessoVelocidade {}
class Acidente extends Exception {}
class Defeito extends Exception {}
abstract class Dirigir {
 Dirigir() throws InfracaoTransito {}
 void irTrabalhar () throws InfracaoTransito {}
 abstract void viajar() throws 
 ExcessoVelocidade, Defeito;
 void caminhar() {} 
}
public class DirecaoPerigosa extends Dirigir {
 DirecaoPerigosa() throws Acidente {}
 void caminhar() throws AltaVelocidade {} 
 public void irTrabalhar() {}
 void viajar() throws AltaVelocidade {}
 public static void main(String[] args) {
 try {
 DirecaoPerigosa dp = new DirecaoPerigosa ();
 dp.viajar ();
 } catch(AltaVelocidade e) {
 } catch(Acidente e) {
 } catch(InfracaoTransito e) {
 }
 try {
 Dirigir d = new DirecaoPerigosa();
 d.viajar ();
 } catch(Defeito e) {
 } catch(ExcessoVelocidade e) {
 } catch(Acidente e) {
 } catch(InfracaoTransito e) {}
 }
}
O trecho de código acima apresenta dois erros identificáveis em tempo de
compilação. Que erros são esses? Justifique sua resposta.
JAVA estabelece a seguinte regra para garantir o uso apropriado do mecanismo de
exceções: os construtores devem necessariamente propagar as exceções declaradas no
construtor da superclasse. Se o construtor da superclasse pode propagar exceções, o da
subclasse também deverá propagá-las pois o último necessariamente chama o
primeiro. Assim, o primeiro erro é que o construtor de DirecaoPerigosa não propaga a
exceção InfracaoTransito, quando deveria fazê-lo, pois DirecaoPerigosa é subclasse
de Dirigir e nesta classe o construtor propaga a exceção InfracaoTransito.
7
O outro erro identificável em tempo de compilação é a implementação do método
caminhar na classe DirecaoPerigosa, pois sua implementação dispara a exceção
AltaVelocidade, que não está listada na especificação desse método na superclasse
Dirigir. O compilador de JAVA impede que isso possa ser feito porque no caso de se
chamar o método caminhar de DirecaoPerigosa através de uma referência a
superclasse Dirigir, isso poderia ocasionar o disparo da exceção AltaVelocidade, sem
que ela fosse devidamente tratada.
7. Apresente as abordagens que linguagens como C podem usar para lidar com erros
em situações nas quais não há conhecimento suficiente para tratar o erro no local
onde ele ocorre. Essas abordagens devem passar as informações do erro para um
contexto mais externo para que ele possa ser tratado. Enumere e explique os
problemas com cada uma delas.
As abordagens utilizadas são: o retorno do código de erro indicando a exceção
ocorrida em uma variável global, no resultado da função ou em um parâmetro
específico.
A opção de utilizar uma variável global não é muito boa porque o usuário da função
pode não ter ciência de que essa variável existe (uma vez que isso não fica explícito
na sua chamada) e também porque uma outra exceção pode ocorrer antes do
tratamento da anterior, sobrescrevendo o código de retorno da primeira exceção antes
dessa ser tratada efetivamente.
A opção de usar o resultado da função como código de retorno nem sempre é possível
porque pode haver incompatibilidade de valores e de tipo com o resultado normal da
função (afinal, o retorno da função normalmente é usado para retornar o resultado da
função e não um código).
Já a opção de usar um parâmetro para retornar o código de exceção é melhor do que o
retorno em variável global ou no resultado da função. Não obstante, ela exige a
inclusão de um novo parâmetro nas chamadas dos subprogramas e requer a
propagação desse parâmetro até o ponto de tratamento da exceção, diminuindo a
redigibilidade do código. Contudo, o grande problema relacionado com essa solução é
o fato de a experiência ter mostrado que, na maioria das vezes, o programador que
chama a função não testa todos os códigos de retorno possíveis, uma vez que não é
obrigatório fazê-lo.
8. Embora o esqueleto de programa JAVA seguinte seja válido sintaticamente, ele
não se comporta apropriadamente em uma situação específica (considere que uma
operação só é completada se realizada sem ocorrência de exceção). Identifique que
situação é essa. Justifique sua resposta. Reformule o programa para que esse
problema seja corrigido. 
public class DefeitoCarro {
 class
SemArranque extends Exception {} 
 class SuperAquecim extends Exception {}
 public void ligar() throws SemArranque {
 … 
 if (…) throws SemArranque(); 
 …
 }
 public void mover() throws SuperAquecim {
 … 
8
 if (…) throws SuperAquecim(); 
 …
 }
 public void desligar() {}
 public static void main(String[] args) {
 DefeitoCarro c = new DefeitoCarro ();
 try {
 c.ligar();
 c.mover();
 } catch(SemArranque e) {
 System.out.println("tem de empurrar!!!");
 } catch(SuperAquecim e) {
 System.out.println("vai fundir!!!");
 } finally {
 c.desligar();
 }
 }
}
A situação ocorre quando a exceção SemArranque é disparada na função ligar. Se
essa exceção ocorre, a função ligar não é executada, pois o fluxo do programa é
direcionado para o tratador de exceções e, depois para o bloco finally. Nesse caso, a
função desligar será executada, embora o carro não esteja ligado.
Programa reformulado:
public class DefeitoCarro {
class SemArranque extends Exception {}
class SuperAquecim extends Exception {}
public void ligar () throws SemArranque {
…
if (…) throws SemArranque ();
…
}
public void mover () throws SuperAquecim {
…
if (…) throws SuperAquecim ();
…
}
public void desligar () {}
public static void main (String [] args) {
DefeitoCarro c = new DefeitoCarro ();
try {
c.ligar ();
try {
c.mover();
} catch (SuperAquecim e) {
System.out.println (“vai fundir!!!”);
} finally {
c.desligar ();
}
} catch (SemArranque e) {
9
System.out.println (“tem de empurrar!!!”);
} 
}
}
9. Ao se compilar o seguinte programa JAVA ocorre um erro de compilação
relacionado ao uso de exceções. Identifique qual é esse erro, atentando para o fato
que nenhuma exceção disparável no programa é herdeira de RuntimeException, e
diga como você o corrigiria. Considerando que o problema foi corrigido tal como
você propôs, mostre o que será impresso durante a execução do programa. Mostre
o que seria impresso caso o valor atribuído a variável i do método main fosse 2.
Mostre, por fim, o que seria impresso se o valor de i fosse 3.
class testaExcecoes {
public static void main(String[] args) {
 int i = 1;
 try {
 primeiro(i);
 System.out.println("depois de primeiro");
 } catch (NullPointerException e){
 System.out.println("trata no primeiro bloco");
 }
 System.out.println("saiu do primeiro bloco");
}
public static void primeiro(int i) throws NullPointerException { 
 try {
 segundo(i);
 System.out.println("depois de segundo");
 } catch (IOException e) {
 System.out.println("trata no segundo bloco");
 }
 System.out.println("saiu do segundo bloco");
}
public static void segundo(int i) throws NullPointerException { 
 try {
 switch(i) {
 default: 
 case 1: throw new IOException();
 case 2: throw new EOFException();
 case 3: throw new NullPointerException();
 }
 System.out.println("depois do switch");
 } catch (EOFException e) {
 System.out.println("trata no terceiro bloco");
 }
 System.out.println("saiu do terceiro bloco");
}
}
Java exige a especificação das exceções não tratadas nos cabeçalhos dos métodos, as
quais não sejam RuntimeException, utilizando a cláusula throws.
10
O erro neste programa é a não identificação da exceção IOException no cabeçalho
da função segundo. Ela não é tratada nessa função e também não é uma
RuntimeException, portanto, deve ser especificada para que possa ser propagada para
o código no qual o método segundo foi chamado.
Correção do código:
public static void segundo (int i) throws NullPointerException, IOException { ...
• Execução para i = 1:
trata no segundo bloco
saiu do segundo bloco
depois de primeiro
saiu do primeiro bloco
• Execução para i = 2:
trata no terceiro bloco
saiu do terceiro bloco
depois de segundo
saiu do segundo bloco
depois de primeiro
saiu do primeiro bloco
• Execução para i = 3:
trata no primeiro bloco
saiu do primeiro bloco
10. Existem posições controversas com relação a incorporação de um mecanismo
rigoroso de tratamento de exceções em LPs. Enquanto alguns defendem o rigor,
outros preferem um mecanismo mais flexível. Por exemplo, alguns programadores
JAVA são defensores do uso exclusivo das exceções da classe RuntimeException
ou de suas subclasses. Indique a característica dessas classes de exceção que
justifica essa postura. Apresente argumentos favoráveis e contrários a posição
adotada por esses programadores.
A classe RuntimeException apresenta um comportamento diferenciado. As exceções
dessa classe não necessitam ser tratadas obrigatoriamente pelo programador (o
compilador não indica erro quando elas não são tratadas ou propagadas).
Se, por um lado, a existência desse tipo de exceções em JAVA poupa o programador
de uma grande dose de trabalho (uma vez que ele não necessita fornecer tratadores
para essas exceções ou especificar sua propagação), por outro lado, isso torna os
programas um pouco menos confiáveis (uma vez que não se exige o tratamento dessas
exceções, o programador pode se esquecer de fazê-lo).
11
ExerciciosCap9.pdf
Resolução dos Exercícios do Capítulo IX
1. Se a programação concorrente traz dificuldades para a programação, quais
vantagens se têm com a sua utilização?
Nos dias atuais a programação concorrente está muito presente, seja quando
imprimimos um documento e ao mesmo tempo o editamos, seja quando há duas
instâncias do mesmo programa em execução. A programação concorrente permite que
atividades sejam feitas em menor espaço de tempo e produz uma melhor
interatividade entre o sistema e os usuários por conseqüência da multiprogramação.
2. Quais são as principais diferenças entre threads e processos? Cite as respectivas
vantagens e desvantagens de sua utilização.
Processos são programas em execução, enquanto threads são fluxos de execução em
um determinado processo. Processos apresentam estados (novo, executável, em
espera, em execução e encerrado), enquanto o estado do thread é definido pelo estado
do processo em que ele se encontra.
Utilizar apenas processos concorrentes pode levar a um uso exagerado da memória,
visto que o estado atual dos registradores e demais atributos devem ser persistidos
quando um processo sai do estado "em execução" e passa para o estado "em espera".
Além disso, o controle dos processos produz um grande overhead no sistema,
reduzindo a performance geral. Threads, por outro lado, possibilitam uma melhor
performance e economia de memória. Note que não existem threads sem processos.
3. Quais são as principais diferenças entre memória compartilhada e de troca de
mensagens? Cite vantagens e desvantagens.
Como o nome já diz, "memória compartilhada" permite um compartilhamento da
memória física entre sistemas concorrentes. "Troca de mensagens" faz uso de
passagem de mensagem para comunicação entre os sistemas.
Trabalhar com programação concorrente fazendo uso de memória compartilhada é
mais eficiente que utilizar troca de mensagens. Entretanto, para utilizar memória
compartilhada se deve implementar mecanismos que garantam a exclusão mútua no
acesso à memória, o que não é trivial. Uma outra vantagem de utilizar troca de
mensagens é a possibilidade de utilizar memórias físicas em locais diferentes, ou seja,
não é necessário que a memória utilizada fique em apenas um computador.
4. Mostre como é possível utilizar semáforos junto aos laços while dos códigos do
produtor e do
consumidor no problema mostrado no exemplo 9.2, reduzindo assim
o overhead do sistema.
Inicialização:
1 fim = 0;
2 ini = 0;
3 n = 0;
4
5 semaforo Cheio;
1
6 Cheio.valor = 1;
7 semaforo Vazio;
8 Vazio.valor = 1;
Código do produtor:
1 for (i=0; i<1000; i++) {
2 // while (n == capacidade) ;
3 while (n == capacidade) P(Cheio)
4 buf[fim] = produzir(i);
5 fim = (fim + 1) % capacidade;
6 n++;
7 V(Vazio)
8 }
Código do consumidor:
1 for (i=0; i<1000; i++) {
2 // while (n == 0) ;
3 while (n == 0) P(Vazio)
4 consumir(buf[ini]);
5 ini = (ini + 1) % capacidade;
6 n--;
7 V(Cheio)
8 }
Com a inclusão de dois semáforos (“Cheio” e “Vazio”) é possível reduzir o overhead
do sistema causado pelo uso exclusivo dos laços while nos códigos do produtor e do
consumidor. Na linha 3 do código do produtor, caso o buffer esteja cheio, o processo
produtor é colocado em espera até que seja consumido algo. Caso contrário, um novo
elemento é produzido e o semáforo “Vazio” é liberado (linha 7). Analogamente, no
código do consumidor, o processo é colocado em espera caso o buffer esteja vazio.
Em caso contrário, um elemento é consumido e o semáforo “Cheio” é liberado.
Observe que os laços while substituídos estão comentados na linha 2, tanto do código
do produtor quanto do consumidor.
5. Faça uma classe Semaforo em JAVA que implemente as operações P e V de um
semáforo. Utilize para isso os métodos wait() e notify(). A classe deve possuir
métodos P e V em exclusão mútua (synchronized).
class Semaforo {
 private static int valor = 1;
 public synchronized void P() throws InterruptedException {
 valor -= 1;
 if (valor < 0) {
 wait();
 }
 }
2
 public synchronized void V() throws InterruptedException {
 valor += 1;
 if (valor <= 0) {
 notify();
 }
 }
}
6. Implemente uma tarefa Semaforo em ADA utilizando entradas P e V.
task Semaforo is
 entry P;
 entry V;
end Semaforo;
task body Semaforo is
begin
 loop
 accept P;
 accept V;
 end loop;
end;
7. Suponha que sejam retiradas as chamadas às entradas iniciar de carro1 e carro2
no exemplo 9.13. Indique a opção abaixo com o resultado correto da execução.
a) Aparecerá na tela as seguintes mensagens: 
O carro 1 esta na posicao 0 
O carro 2 esta na posicao 0
b) Aparecerá na tela as seguintes mensagens: 
O carro 1 esta na posicao 0 
O carro 1 esta na posicao 1 
O carro 2 esta na posicao 0
O carro 2 esta na posicao –1
c) Não aparecerá nada na tela.
d) O programa dará erro em tempo de compilação.
Letra C. Não aparecerá nada na tela, pois a tarefa Carro espera pela chamada à
entrada "iniciar" para prosseguir a execução. Somente após a chamada à entrada
"iniciar" é feita a impressão de algo na tela.
8. Implemente o programa do exemplo 9.9 retirando o semáforo, descreva o que
acontece e justifique.
Ao retirar todos os semáforos começam a aparecer algumas inconsistências. Um
exemplo é que o algoritmo pode fazer com que os ponteiros de início e fim do buffer
fiquem com indicação incorreta. Isso pode fazer com que as impressões na tela não
reflitam as operações reais, podendo indicar que um elemento está sendo produzido
mais de uma vez, quando na realidade não está.
3
9. Quais são as principais características das linguagens C, JAVA e ADA
relacionadas à programação concorrente?
A linguagem C não oferece mecanismos próprios para programação concorrente. É
necessário utilizar bibliotecas de funções ou utilizar recurso de chamada de sistema
para trabalhar com processos concorrentes.
A linguagem JAVA disponibiliza recursos para implementação de threads e oferece
mecanismos de sincronização de métodos.
A linguagem ADA utiliza-se da definição de módulos concorrentes permitindo a
construção de sistemas concorrentes. ADA oferece ainda recursos para sincronização
de tarefas através de objetos protegidos ou troca de mensagens.
4
ExerciciosCap7.pdf
Resolução dos Exercícios do Capítulo VII
1. Segundo a classificação de Cardelli e Wegner, existem quatro tipos de
polimorfismo. Quais desses tipos de polimorfismo existem em C, C++ e JAVA?
Mostre exemplos desses tipos de polimorfismo com trechos de código em C, C++
ou JAVA. Identifique o tipo de polimorfismo que ocorre em cada exemplo e
explique porque cada um dos trechos de código é polimórfico. Indique ainda se o
polimorfismo é ad-hoc ou universal e justifique.
Os quatro tipos de polimorfismo, segundo a classificação de Cardelli e Wegner, são:
polimorfismo de coerção, polimorfismo de sobrecarga, polimorfismo paramétrico e
polimorfismo por inclusão. 
Os tipos de polimorfismo existentes em C são o de coerção, o de sobrecarga (C
embute sobrecarga em seus operadores, mas os programadores não podem
implementar novas sobrecargas, além disso, não existe qualquer sobrecarga de
programas) e o paramétrico.
Os existentes em C++ são o de coerção, o de sobrecarga (adota postura ampla e
ortogonal, realizando e permitindo que programadores realizem sobrecarga de
subprogramas e operadores), o paramétrico e o por inclusão.
Os existentes em JAVA são o de coerção (só admite a realização de coerções para
tipos mais amplos), o de sobrecarga (embute sobrecarga em operadores e em
subprogramas de suas bibliotecas, mas somente subprogramas podem ser
sobrecarregados pelo programador) e o por inclusão.
Exemplos:
1.1) Expressão com coerção em C:
#include <stdio.h>
main () {
 int a = 1;
 float b = 2.5, c = 3.5;
 c = c + b; //c = somafloat (c, b)
 printf("%.1f", c); //imprime 6.0
 c = c + a; //c = somafloat (c, intToFloat (a))
 printf("\n%.1f", c); //imprime 7.0
}
O trecho acima é polimórfico, porque a instrução c = c + a (na penúltima linha deste
exemplo) sugere que o operador + corresponde a uma função capaz de realizar tanto a
operação de somar dois valores do tipo float, quanto a operação de somar um float e
um int, quando ela só realiza realmente a primeira dessas operações. Antes da
operação soma ser chamada, ocorre um chamada implícita a uma função capaz de
converter valores do tipo int em valores do tipo float.
1.2) Sobrecarga do operador + em C:
#include <stdio.h>
1
main () {
 int a = 1, b = 2;
 float c = 1.0, d = 2.0;
 a = a + b; //a = somaint (a, b)
 printf("%d", a); //imprime 3
 c = c + d; //c = somafloat (c, d)
 printf("\n%.1f", c); //imprime 3.0
}
O trecho acima é polimórfico porque sugere que o operador + representa uma função
capaz de realizar tanto a operação de somar dois valores do tipo int, quanto a operação
de somar dois valores do tipo float, sendo que, na realidade, cada ocorrência de +
invoca operações específicas para cada uma dessas operações.
1.3) Função genérica em C++:
template <class T>
T identidade (T x) {
return x;
}
class Horario {
int h, m, s;
}
main () {
int a;
float b;
Horario h1, h2;
a = identidade (10);
b = identidade (10.5);
h2 = identidade (h1);
}
O trecho acima é polimórfico porque parametriza o subprograma identidade com
relação ao tipo do elemento sobre o qual ele opera. A função identidade pode ser
aplicada a valores de qualquer tipo. Esse tipo é determinado pela combinação do tipo
do valor do argumento com o tipo declarado no cabeçalho da função.
1.4) Herança simples em JAVA:
public class Liquidificador {
protected int velocidade;
protected int velocidadeMaxima;
public Liquidificador () {
velocidade = 0;
velocidadeMaxima = 2;
}
public Liquidificador (int v) {
this ();
ajustarVelocidadeMaxima (v);
2
}
protected void ajustarVelocidadeMaxima (int v) {
if (v > 0)
velocidadeMaxima = v;
}
protected void ajustarVelocidade (int v){
if (v >= 0 && v <= velocidadeMaxima)
velocidade = v;
}
public int obterVelocidadeMaxima () {
return velocidadeMaxima;
}
public int obterVelocidade () {
return velocidade;
}
}
public class LiquidificadorAnalogico extends Liquidificador {
public LiquidificadorAnalogico () {
velocidade = 0;
}
public void aumentarVelocidade () {
ajustarVelocidade (velocidade + 1);
}
public void diminuirVelocidade () {
diminuirVelocidade (velocidade – 1);
}
}
public class LiquidificadorDigital extends Liquidificador {
public LiquidificadorDigital () {
velocidade = 0;
}
public void trocarvelocidade (int v) {
ajustarVelocidade (v);
}
}
public class LiquidificadorInfo {
public void velocidadeAtual (Liquidificador l) {
System.out.println (“Velocidade Atual: “+ l.obterVelocidade ());
}
}
Esse trecho é polimórfico porque a utilização da classe Liquidificador permite o
tratamento generalizado de todas as suas subclasses. O tratamento generalizado de
classes permite escrever programas que podem ser mais facilmente extensíveis, isto é,
que podem acompanhar a evolução de uma hierarquia de classe sem necessariamente
serem modificados. Por exemplo, na classe LiquidificadorInfo, existe um método
capaz de imprimir a velocidade atual de objetos liquidificador os quais podem ser
tanto do tipo digital como analógico.
Nesta situação, o polimorfismo permite que um simples trecho de código seja
utilizado para tratar objetos diferentes relacionados através de seu ancestral comum,
3
simplificando a implementação, melhorando a legibilidade do programa e também
aumentando sua flexibilidade.
Os polimorfismos dos exemplos 1.1 e 1.2 são de tipo adhoc. O operador + é
associado a diferentes trechos de código que atuam sobre diferentes tipos. Para quem
lê o código, pode parecer que esse operador denota um único trecho de código
polimórfico, atuando sobre elementos de tipos diferentes. Contudo, isso é apenas
aparente, uma vez que para utilizar operandos de tipos diferentes numa mesma
operação, o operando que não é do tipo esperado deve ser convertido para o mesmo, e
para realizar a operação de adição sobre diferentes tipos, diferentes operações,
específicas para cada tipo, são chamadas.
Os polimorfismos dos exemplos 1.3 e 1.4 são de tipo universal, pois as estruturas de
dados incorporam elementos de tipos diversos e um mesmo código pode ser
executado e atuar sobre elementos de diferentes tipos.
2. O que são classes abstratas? Quando devem ser usadas e quais as suas vantagens?
Quais diferenças existem na definição de classes abstratas em C++ e JAVA?
As classes abstratas são classes que não possuem instâncias, mas que possuem
membros (as instâncias de suas subclasses não abstratas) e que, portanto, devem ser
necessariamente estendidas, ou seja, devem ser herdadas por outras, mais específicas,
contendo os detalhes nela não incluídos. Elas normalmente possuem um ou mais
métodos abstratos, isto é, métodos declarados, mas não implementados (a
implementação dos mesmos é deixada para as suas subclasses) e também podem ter
métodos definidos e atributos próprios. De fato, elas podem possuir até construtores,
embora eles nunca possam ser chamados para criar instâncias dessa classe (só podem
ser chamados no momento da construção das instâncias das subclasses da classe
abstrata).
As classes abstratas são especialmente úteis quando uma classe, ancestral comum para
um conjunto de classes, se torna tão geral a ponto de não ser possível ou razoável ter
instâncias dela. As principais vantagens da sua utilização são: a melhoria da
organização hierárquica de classes, pelo encapsulamento de atributos e métodos na
raiz da estrutura; a promoção de uma maior disciplina na programação, visto que força
o comportamento necessário nas suas subclasses (para cada método abstrato em uma
classe abstrata, todas as suas subclasses devem implementar esse método ou também
devem ser abstratas); e o incentivo ao uso de amarração tardia, permitindo um
comportamento mais abstrato e genérico para os objetos.
Diferenças entre C++ e JAVA: Em JAVA, é possível, mas não é comum, criar classes
abstratas nas quais nenhum método é abstrato. Já em C++, para uma classe ser
abstrata, é obrigatório ter pelo menos um método abstrato. Em JAVA, para tornar uma
classe abstrata, basta incluir a palavra abstract como prefixo de definição. Em C++, os
métodos abstratos devem ter a terminação =0 e devem ser declarados como virtuais,
uma vez que seu comportamento terá que ser definido nas subclasses da classe
abstrata.
3. Enquanto em C++ somente os métodos precedidos pela palavra virtual utilizam o
mecanismo de amarração tardia de tipos, em JAVA todos os métodos empregam
este mecanismo. Justifique esta decisão dos criadores dessas linguagens.
O mecanismo de amarração tardia de tipos oferece maior flexibilidade para a escrita
de código reutilizável, já que possibilita a criação de código usuário com
polimorfismo universal, isto é, código usuário capaz de operar uniformemente sobre
4
objetos de tipos diferentes. Por darem preferência à versatilidade, os criadores de
JAVA optaram por sempre adotar esse mecanismo. Contudo, a amarração tardia de
tipos reduz a eficiência computacional quando comparada com a amarração estática,
pois na amarração tardia é necessário manter sempre a cadeia de ponteiros e segui-la
em todas as chamadas de métodos, enquanto na estática nada disso é necessário já que
o subprograma a ser chamado é definido em tempo de compilação. Assim, os
criadores de C++ decidiram adotar uma postura diferente, em que o programador pode
decidir se deseja o uso da amarração tardia, identificada pela declaração do método
precedida da palavra virtual, ou da amarração estática, identificada pela omissão da
palavra virtual, possibilitando ao programador escolher entre versatilidade e
eficiência, respectivamente.
4. Linguagens de programação orientadas a objetos podem adotar herança simples ou
múltipla. C++, por exemplo, adota herança múltipla. Quais os dois problemas que
podem ocorrer quando se adota herança múltipla? Explique-os usando exemplos
em C++. Mostre de que forma C++ permite contornar esses problemas. Apresente
um exemplo de situação na qual os mecanismos de C++ são inadequados para
tratá-los.
Os dois problemas que podem ocorrer quando se adota herança múltipla são o conflito
entre nomes de classes bases diferentes e a herança repetida. 
Os conflitos entre nomes ocorrem quando duas ou mais classes bases possuem nomes
idênticos (homônimos) de atributos ou métodos. Por exemplo: 
class Desempenho {
 int n;
 float t;
 public:
 int numero () { return n; }
float tempo() { return t; }
};
class Corrida: public Desempenho {
 float quilometragem;
 public:
 void imprime ();
};
class Natacao: public Desempenho {
float metragem;
public:
void imprime ()
};
class Duatlo : public Corrida, public Natação {
char prova;
}
main () {
 Duatlo atleta;
 //atleta.imprime;
}
5
Caso a linha comentada fosse compilada, seria detectado um erro de ambigüidade,
pois não foi especificado de qual classe herdada éo método referenciado pelo objeto
da classe herdeira. Em C++ esse problema pode ser resolvido pela sobrescrição da
operação de impressão na classe Duatlo e pelo uso do operador de resolução de
escopo ::, como mostrado abaixo, para identificar qual operação de impressão está
sendo chamada.
class Duatlo: public Corrida,
public Natação {
char prova;
public:
void imprime ();
};
void Duatlo :: imprime () {
if (prova == ‘C’)
Corrida :: imprime ();
else
Natacao :: imprime ();
}
main () {
 Duatlo atleta;
 atleta.imprime;
}
O problema da herança repetida ocorre quando uma classe faz herança múltipla de
classes descendentes de uma mesma classe. Os atributos dessa classe comum são
repetidos na classe na qual é feita herança múltipla. C++ fornece um mecanismo
especial, utilizando a palavra virtual para resolver esse problema. Se a especificação
da classe herdada é precedida por virtual, somente um objeto daquela classe comporá
o objeto das classes herdeiras, independentemente de a classe ser herdada múltiplas
vezes ou não.
No exemplo anterior, a palavra virtual não foi utilizada na especificação das classes
Corrida e Natação, o que faz com que os atributos numero (n) e tempo (t) de
Desempenho sejam repetidos na classe Duatlo. Isso não ocorre na implementação
mostrada a seguir:
class Desempenho {
 int n;
 float t;
 public:
 int numero () { return n; }
float tempo() { return t; }
};
class Corrida: virtual public Desempenho {
 float quilometragem;
 public:
 void imprime ();
};
class Natacao: virtual public Desempenho {
float metragem;
public:
6
void imprime ()
};
Contudo, esses mecanismos adotados por C++ para solucionar esses problemas
apresentam dois inconvenientes. Primeiro, a especificação da palavra virtual não é
feita na classe na qual ocorre a herança repetida. Inicialmente, ao se criar as classes
Corrida e Natacao, não se sabe se elas serão herdadas futuramente por uma mesma
subclasse e muito menos se esta subclasse precisará ou não compartilhar os atributos
comuns.
O segundo problema é que não é possível especificar que um atributo deva ser
repetido e outro não. No exemplo acima, o número do atleta é o mesmo, mas os
tempos para as provas de corrida e natação são tempos distintos.
5. C++ oferece três mecanismos distintos para permitir a realização de estreitamento.
Mostre exemplos do uso desses três mecanismos e os compare em termos de
confiabilidade e eficiência.
As três maneiras distintas oferecidas por C++ para permitir a realização de
estreitamento são: o mecanismo usual de conversão explícita (cast), o mecanismo de
conversão explícita estática (static_cast) e o mecanismo de conversão explícita
dinâmica (dynamic_cast). Por exemplo:
class UmaClasse {
public:
virtual void temVirtual () {};
}
class UmaSubclasse: public UmaClasse {}
class OutraSubclasse: public UmaClasse {}
class OutraClasse {}
main () {
//primeira parte do exemplo
UmaClasse *pc = new UmaSubclasse;
OutraSubclasse *pos = dynamic_cast <OutraSubclasse *> (pc);
UmaSubclasse *ps = dynamic_cast <UmaSubclasse *> (pc);
//segunda parte do exemplo
UmaSubclasse us;
pc = &us;
pc = static_cast <UmaClasse *> (&us);
OutraClasse *poc = (OutraClasse *) pc;
}
Na primeira parte do exemplo acima é mostrado o uso do mecanismo de
dynamic_cast. Essa é uma forma de fazer estreitamento de modo seguro. Ao usar
dynamic_cast, o que se está tentando fazer é um estreitamento para um tipo particular.
O valor de retorno dessa operação será um ponteiro para o tipo desejado, no caso de o
estreitamento ser apropriado. De outra forma, o valor retornado será zero (null) para
indicar que o tipo não era o esperado.
Somente podemos usar o dynamic_cast em classes com funções virtuais. Isso ocorre
porque o dynamic_cast usa a informação armazenada em uma tabela de métodos
virtuais para determinar o tipo atual. No exemplo, o ponteiro pos receberá zero, pois o
7
estreitamento para OutraSubclasse * é incorreto. É responsabilidade do programador,
verificar se o resultado do estreitamento por dynamic_cast é diferente de zero. 
A operação de dynamic_cast sobrecarrega um pouco a execução do programa,
portanto, se um programa usa muito dynamic_cast, isso poderá diminuir a eficiência
de execução.
O mecanismo de static_cast deve ser utilizado quando é possível saber durante a
própria redação do programa com qual tipo estamos lidando no local do estreitamento,
pois assim a verificação é feita em tempo de compilação. Usar static_cast para
realizar estreitamento é melhor do que o mecanismo de conversão explícita formal
(cast), pois o primeiro não permite fazer conversões fora da hierarquia de classes, o
que é permitido pelo segundo. Como a eficiência é a mesma para códigos gerados
usando ambos os mecanismos, static_cast deve ser preferido, pois é mais seguro.
A segunda parte do exemplo mostra o uso desse mecanismo. Nessa parte, um objeto
(us) de UmaSubclasse é criado e é feita uma ampliação a um ponteiro para
UmaClasse. Essa mesma operação é repetida usando static_cast. Como se trata de
uma ampliação, não existe obrigatoriedade de usar static_cast, mas seu uso pode ser
conveniente para tornar mais explícita a ampliação e para evitar a realização
equivocada de conversões fora da hierarquia de classes.
Após as operações de ampliação, o exemplo mostra a diferença entre se fazer
estreitamento com static_cast e o mecanismo tradicional. Com o mecanismo
tradicional, é possível fazer a conversão fora da hierarquia de classes entre os
ponteiros para UmaClasse e para OutraClasse. Isso não é permitido no caso do
static_cast. Caso a linha de código na qual essa operação é feita não estivesse
comentada, ocorreria um erro de compilação.
Resumindo, é boa prática usar preferencialmente os mecanismos do dynamic_cast,
pois embora seja mais rápido fazer estreitamento estaticamente, a conversão dinâmica
é mais segura, pois os mecanismos de conversão estática podem produzir conversões
inapropriadas. 
6. Amarração tardia de tipos é o processo de identificação em tempo de execução do
tipo real de um objeto. Esse processo pode ser utilizado para a identificação
dinâmica do método a ser executado (quando ele é sobrescrito) e para a
verificação das operações de estreitamento. Explique como pode ser
implementado o mecanismo de amarração tardia de tipos em linguagens como
C++ e JAVA e como ele é usado para realizar as operações mencionadas na frase
anterior.
Em JAVA, na utilização desse mecanismo, quando um método é invocado por uma
variável (essa variável aponta para um objeto), uma cadeia de ponteiros é seguida.
Essa cadeia começa pela variável que chamou o método e que está na base da pilha de
registros de ativação e que aponta para um objeto. Esse objeto apontado pela variável
possui, além dos atributos de sua classe, uma referência à tabela de métodos de sua
classe. Essa tabela, por sua vez, também possui uma referência à tabela de métodos de
sua superclasse. Assim, essa cadeia é seguida até chegar à primeira tabela de métodos
da classe na qual esse método foi definido e o método a ser utilizado é identificado.
Com relação à verificação das operações de estreitamento, a cadeia de ponteiros citada
acima é seguida e, caso a classe para a qual se deve fazer a conversão seja encontrada
em algum momento, a operação é validada. 
8
7. Tanto C++ quanto JAVA oferecem bibliotecas de classes que disponibilizam
estruturas de dados genéricas, tais como listas, árvores e tabelas hash. Ambas
utilizam polimorfismo para a implementação dessas estruturas, embora sejam
formas diferentes de polimorfismo. Explique como essas linguagens usam o
polimorfismo para a implementação dessas estruturas. Discuta as vantagens e
desvantagens de cada abordagem. Justifique também porque os criadores dessas
linguagens adotaram essa postura diferenciada.
Estruturas de
dados genéricas são capazes de armazenar e operar sobre elementos de
tipos diferentes. Estruturas genéricas podem ser preenchidas com elementos de um
mesmo tipo (nesse caso são chamadas de estruturas homogêneas) ou de tipos
diferentes (nesse caso são chamadas de estruturas heterogêneas).
C++ utiliza o polimorfismo paramétrico proporcionado pelo mecanismo de template
para a criação de estruturas de dados genéricas homogêneas, e o mecanismo de
polimorfismo por inclusão para a criação de estruturas de dados heterogêneas. É
possível ainda combinar o mecanismo de template com o polimorfismo de inclusão
para criar estruturas de dados genéricas heterogêneas.
JAVA utiliza o polimorfismo por inclusão para permitir a criação de estruturas de
dados genéricas heterogêneas. Para isso, JAVA considera todas as classes existentes
como subclasses (diretas ou indiretas) da classe Object.
Assim, estruturas de dados cujos elementos são do tipo Object podem abrigar
elementos de qualquer classe em JAVA. Para ter uma estrutura de dados homogênea,
o programador deve garantir que os elementos inseridos sejam sempre de um mesmo
tipo. Isso é pior do que a solução de C++, na qual o compilador garante a
homogeneidade da estrutura. Além disso, a solução de JAVA obriga a realização de
estreitamento sempre que um elemento deve ser acessado a partir da lista. Por outro
lado, a solução de JAVA simplifica a linguagem, pois não é necessário incluir o
polimorfismo paramétrico, imprescindível em C++.
8. Implemente o tipo abstrato de dados lista genérica em C, C++ e JAVA. É
suficiente apresentar a definição do tipo e o cabeçalho dos métodos de construção,
ordenação, destruição, verificação de lista vazia, inclusão e exclusão de elemento
(não é preciso codificar os métodos da lista). Atente para o fato de que a operação
de ordenação na lista deve ser única, mas deve permitir que a lista seja ordenada
por critérios distintos. Justifique a sua implementação, enfocando o modo como se
obtém a generalidade da lista e o funcionamento da operação de ordenação.
//C
typedef struct no {
void *info;
struct no *prox;
}No;
typedef struct Lista {
No *prim, *ult;
int tam;
}Lista;
Lista InicLista ();
Lista OrdenaLista (Lista lst, int (*compara) (void*, void*));
void DestroiLista (Lista lst);
int VaziaLista (Lista lst);
9
Lista InsereLista (Lista lst, int pos, void *elem);
Lista ElimLista (Lista lst, int pos);
//C++
template <class T>
class Lista {
struct no {
T info;
struct no *prox;
}No;
No *prim, *ult;
int tam;
public:
Lista ();
Lista OrdenaLista (int (*compara) (T, T));
int VaziaLista ();
Lista InsereLista (int pos, T elem);
Lista ElimLista (int pos);
void DesalocaInfos();
~Lista ();
}
//JAVA
class Lista {
private Object [] lst;
private int tam = 0;
private int marcador;
Lista ();
public int VaziaLista ();
public void OrdenaLista (Comparator c, int ordem);
public void InsereLista (int pos, Object o);
public void ElimLista (int pos);
public void finaliza ();
}
A generalidade da lista é obtida pela utilização de void* em C, template em C++ e
Object em JAVA, que permitem generalizar os elementos que compõem a lista. No
caso de C, o tipo void* permite que o elemento da lista seja de qualquer tipo ponteiro,
o que confere generalidade a lista. No caso de C++, o mecanismo template possibilita
que o tipo do elemento da lista seja definido no momento de criação das variáveis
lista. No caso de JAVA, como toda classe em JAVA é subclasse de Object, é possível
colocar qualquer tipo de objeto como elemento da lista.
A operação de ordenação pode ser realizada em diferentes contextos, isto é, diferentes
características dos objetos podem ser comparadas ou pode ser escolhido se o desejado
é a ordenação crescente ou decrescente, por exemplo. Isso é feito em C e C++ pela
utilização do ponteiro para função compara passado como parâmetro para a função
OrdenaLista e pelo parâmetro c da interface Comparator passado para a função
OrdenaLista em JAVA. No caso de C e C++, o que se precisa fazer é criar uma
função de comparação que compare dois elementos do tipo da lista e passar seu
endereço como argumento para o parâmetro formal compara da função OrdenaLista.
10
No caso de JAVA é preciso criar uma classe que implemente a interface Comparator
e que defina como deve ser feita a comparação em seu único método. Depois, basta
passar um objeto dessa classe para o método OrdenaLista.
9. Em alguns problemas pode ser conveniente permitir a um mesmo objeto participar
de duas ou mais listas cujos elementos são de tipos diferentes. Por exemplo, em
um problema no qual é necessário armazenar em listas os diversos tipos de
dependências de um apartamento haveria uma lista para quartos e outra para salas.
Contudo, é possível haver uma mesma dependência usada como quarto e como
sala. Nesse caso, essa dependência participaria tanto da lista de quartos quanto da
lista de salas.
a) Como você resolveria esse problema em uma linguagem que não possui
mecanismos para a realização de subtipagem múltipla, ou seja, que não permita a
um mesmo objeto fazer parte de listas de dados cujos elementos sejam de tipos
distintos. Existe alguma desvantagem na sua solução?
b) Sabendo que C também não permite a realização de subtipagem múltipla,
responda se é possível resolver esse mesmo problema de outra maneira? Se a
resposta for positiva, explique como seria essa solução e faça uma análise de suas
vantagens e desvantagens.
c) Mostre através da implementação desse exemplo como C++ e JAVA permitem
a realização de subtipagem múltipla. 
Compare ainda os mecanismos oferecidos por C++ e JAVA para realização de
subtipagem múltipla apresentando vantagens e desvantagens de cada um.
a) Uma forma seria através da existência de um objeto do tipo sala e outro do tipo
quarto referindo-se ao mesmo cômodo. O problema com essa abordagem é que
qualquer alteração feita em um atributo comum dos objetos deve implicar na
atualização de um ou outro objeto, sob pena de se gerar uma inconsistência de dados
caso isso não seja feito apropriadamente. 
b) Em C isso poderia ser feito através da criação de uma nova estrutura representando
objetos do tipo sala e quarto juntamente com a especificação do elemento das duas
listas como ponteiro para void. Um problema dessa abordagem é que se torna
necessário manter junto com o elemento uma indicação de seu tipo para que toda vez
que for utilizado se possa verificar o tipo real do elemento. Outro problema é que as
listas passam a ser genéricas, podendo assim receber elementos de outros tipos e não
apenas salas e quartos.
c)
C++:
class Quarto {
public:
Quarto() {}
int numeroCamas () {}
};
class Sala {
public:
Sala() {}
int numeroMesas () {}
};
11
class QuartoSala: public Quarto, public Sala { };
JAVA:
interface Quarto {
 int numeroCamas ();
}
interface Sala {
int numeroMesas ();
}
class QuartoSala implements Quarto, Sala {
public int numeroCamas() {}
public int numeroMesas() {}
}
C++ permite herança múltipla de classes, o que torna muito natural o
desenvolvimento de heterarquia de classes e de programas que as utilizam. Por outro
lado, o mecanismo de herança de C++ admite a ocorrência dos problemas de conflito
de nomes e herança repetida.
JAVA só permite herança múltipla de interfaces (isto é, classes abstratas puras).
Enquanto isso garante a inexistência de problemas de conflito de nomes e herança
repetida, a reutilização de código por herança não é possível (uma vez que interfaces
não implementam métodos nem possuem atributos) ou se torna mais complexa (há
uma forma de reutilizar
código por composição).
10. Considere as seguintes definições de funções e classes em C++:
template <class T >
T xpto (T x, T y) {
 return y;
}
template <class T, class U>
U ypto (T x, U y) {
 return y;
}
template <class T, class U>
T zpto (T x, U y) {
 return ((T) y);
}
class tdata {
 int d, m, a;
};
class thorario {
 int h, m, s;
};
class tdimensao {
 int h, l, w;
};
Indique quais das linhas de código de main são legais e explique as que não são.
main () {
 tdata a;
12
 thorario b;
 tdimensao c;
 a = xpto (a, a);
 b = xpto (a, a);
 c = xpto (a, b);
 a = ypto (a, a);
 b = ypto (a, a);
 b = ypto (a, b); 
 c = ypto (a, b);
 a = zpto (a, a);
 b = zpto (a, a);
 a = zpto (a, b);
 c = zpto (a, b);
}
Explique ainda como o compilador C++ implementa o mecanismo de
polimorfismo paramétrico. Discuta essa solução em termos de reusabilidade de
código.
Linhas legais: 
a = xpto (a, a); 
a = ypto (a, a);
b = ypto (a, b);
a = zpto (a, a); 
Linhas que não são legais:
b = xpto (a, a); → Uma vez que não foi feita uma sobrecarga do operador de
atribuição da classe thorario que receba um parâmetro do
tipo tdata, a chamada de xpto geraria erro de compilação
porque ela retorna um valor do tipo tdata, mas a atribuição
demanda um thorario.
c = xpto (a, b); → Uma vez que não foi feita uma sobrecarga do operador de
atribuição da classe tdimensao que receba um parâmetro do
tipo thorario, a chamada de xpto geraria erro de compilação
porque ela retorna um valor do tipo thorario, mas a
atribuição demanda um tdimensao.
b = ypto (a, a); → Uma vez que não foi feita uma sobrecarga do operador de
atribuição da classe thorario que receba um parâmetro do
tipo tdata, a chamada da função ypto geraria erro de
compilação porque ela retorna um valor do tipo tdata, mas a
atribuição demanda um thorario.
c = ypto (a, b); → Uma vez que não foi feita uma sobrecarga do operador de
atribuição da classe tdimensao que receba um parâmetro do
tipo thorario, a chamada da função ypto geraria erro de
compilação porque ela retorna um valor do tipo thorario,
mas a atribuição demanda um tdimensao.
b = zpto (a, a); → Uma vez que não foi feita uma sobrecarga do operador de
atribuição da classe thorario que receba um parâmetro do
tipo tdata, a chamada da função zpto geraria erro de
13
compilação porque ela retorna um valor do tipo tdata, mas a
atribuição espera um thorario.
a = zpto (a, b); → A chamada da função zpto geraria erro de compilação
porque não existe um construtor definido na classe tdata
que receba como parâmetro um objeto thorario.
c = zpto (a, b) → A chamada da função zpto geraria erro de compilação porque
não existe um construtor definido na classe tdata que receba
como parâmetro um objeto thorario e porque ela retorna um
valor do tipo tdata, mas a atribuição espera um tdimensao.
C++ usa o mecanismo de template para incorporar o polimorfismo paramétrico. A
forma de implementação do mecanismo template é curiosa. Ao contrário do que seria
mais desejado (a reutilização de código-fonte e objeto), esse mecanismo só possibilita
a reutilização de código-fonte. Isso significa que não é possível compilar o código
usuário das funções ou classes definidas com polimorfismo paramétrico
separadamente do código de implementação dessas funções ou classes. De fato, para
compilar funções ou classes paramétricas, o compilador C++ necessita saber quais
tipos serão associados a elas. A partir de uma varredura do código usuário, o
compilador identifica os tipos associados a essas funções e classes e replica todo o
código de implementação para cada tipo utilizado, criando assim um código objeto
específico para cada tipo diferente utilizado.
14
11. Cada um dos programas seguintes, escritos em C++, utiliza um tipo de
polimorfismo visto nesse capítulo. Defina o que é polimorfismo. Descreva as
características desses tipos de polimorfismo, indicando o tipo empregado por cada
programa. Execute os programas passo a passo, mostrando o resultado
apresentado, indicando onde ocorre polimorfismo e explicando sua execução.
O polimorfismo em LPs se refere à possibilidade de criar código capaz de operar (ou,
pelo menos, aparentar operar) sobre valores de tipos distintos.
Programa 1
15
// programa 1
#include <iostream>
class base {
 public:
 virtual void mostra1() {
 cout << "base 1\n";
 } 
 void mostra2 () {
 cout << "base 2 \n";
 }
};
class derivada1: public base {
 public:
 void mostra1() {
 cout << "derivada 1\n";
 }
};
class derivada2: public base {
 public:
 void mostra2 () {
 cout << "derivada 2 \n";
 }
};
void prt(base *q) {
 q->mostra1();
 q->mostra2();
}
void main( ) {
 base b;
 base *p;
 derivada1 dv1;
 derivada2 dv2;
 p = &b;
 prt(p);
 dv1.mostra1();
 p = &dv1;
 prt(p); 
 dv2.mostra2();
 p = &dv2;
 prt(p);
}
// programa 2
#include <iostream>
class teste {
 int d;
 public:
 teste () {
 d = 0;
 cout << "default \n";
 }
 teste (int p, int q = 0) {
 d = p + q;
 cout << "soma \n"; 
 }
 teste (teste & p) {
 d = p.d;
 cout << "copia \n"; 
 }
 teste & operator = (teste & p)
 {
 cout << "atribuicao 1\n"; 
 d = p.d; return *this;
 }
 teste & operator = (int i) {
 cout << "atribuicao 2\n"; 
 d = i; return *this;
 }
 void mostra ( ) {
 cout << d << " \n";
 }
};
void main ( ) {
 teste e1 (2, 6); e1.mostra ( );
 teste e2; e2.mostra ( );
 teste e3 (73); e3.mostra ( );
 teste e4; 
 e4 = e1; e4.mostra();
 teste e5 (e2); e5.mostra ( );
 e5 = 55; e5.mostra ( );
 teste e6 = e3; e6.mostra ( );
 teste e7 (21,3); e7.mostra
( );
 e1 = e3 = e5 = e7;
 e1.mostra(); e5.mostra();
}
// programa 3
#include <iostream>
template <class T>
class pilha {
 T* v;
 T* p;
public:
 pilha (int i) { 
 cout << "cria "<< i << "\n";
 v = p = new T[i];
 }
 ~pilha () { 
 delete[ ] v; 
 cout << "tchau \n";
 }
 void empilha (T a) {
 cout << "emp "<< a << "\n";
 *p++ = a;
 }
 T desempilha () {
 return *--p;
 }
 int vazia() {
 if (v == p) return 1;
 else return 0;
 }
};
void main ( ) {
 pilha<int> p(40); 
 pilha<char> q(30);
 p.empilha(11);
 q.empilha('x');
 p.empilha(22); 
 q.empilha('y');
 p.empilha(33);
 do {
 cout << p.desempilha() << 
 "\n";
 } while (!p.vazia()); 
 do {
 cout << q.desempilha() << 
 "\n";
 } while (!q.vazia());
} 
Tipo de polimorfismo: Por inclusão. Baseia-se na noção de hierarquia de classes para
tratar objetos de um determinado tipo (da superclasse) como sendo de outro (da
subclasse) através da amarração dinâmica de tipos. Os subprogramas mostra1 e
mostra2 definidos inicialmente na classe base foram reescritos nas suas subclasses
derivada1 e derivada2, respectivamente.
Resultado da execução:
base 1
base 2 
derivada 1
derivada 1
base 2
derivada 2
base 1
base 2
Em main são criados inicialmente um objeto do tipo base (b), um ponteiro
para objeto do tipo base (*p), um objeto do tipo derivada1 (dv1) e um objeto do tipo
derivada2 (dv2). Com a execução da instrução p = &b;, p passa apontar para b.
Assim, quando prt (p); é chamada, são executadas as funções mostra1 e mostra2
definidas na classe base, imprimindo, respectivamente, base 1 e base 2. Com a
execução
de dv1.mostra1();, a função mostra1 definida na classe derivada1 é
executada, imprimindo derivada 1. Com a execução de p = &dv1;, p passa a apontar
para dv1. Na próxima linha, a execução de prt (p); chama o método mostra1 de
derivada1 porque o método mostra1 é amarrado dinamicamente em base (uso da
palavra virtual) e, em seguida, chama o método mostra2 de base uma vez que esse
método é amarrado estaticamente (não é precedido pela palavra virtual). Assim, são
impressos derivada 1 e base 2. A instrução seguinte dv2.mostra2 (); imprime
derivada 2. A execução de p = &dv2; faz com que p passe a apontar para dv2. A
execução de prt (p); chama o método mostra1 herdado de base por derivada2 e depois
chama o método mostra2 de base, uma vez que ele é amarrado estaticamente. Por
conseguinte, são impressos base1 e base2.
Programa 2
Tipo de polimorfismo: Sobrecarga. Permite várias definições de funções referidas por
um mesmo símbolo ou identificador. Dá a aparência de usar na chamada um mesmo
trecho de código para diferentes tipos de dados dos parâmetros. Esse programa utiliza
o polimorfismo de sobrecarga para implementar diferentes construtores para a classe
teste, além de sobrecarregar o operador =.
Resultado da execução:
soma
8
default
0
soma
16
73
default
atribuicao 1
8
copia
0
atribuicao 2
55
copia
73
soma
24
atribuicao 1
atribuicao 1
atribuicao 1
24
24
Em main, a execução de teste e1 (2,6); imprime soma, pois o construtor
definido com dois parâmetros na classe teste é chamado. A execução de e1.mostra ();
imprime 8. A execução de teste e2; chama o construtor default (sem parâmetros)
definido em teste, imprimindo default. A execução de e2.mostra (); imprime 0. A
execução de teste e3 (73); imprime novamente soma pois um dos parâmetros do
construtor com dois parâmetros assume o valor default 0. A execução de e3.mostra ();
imprime 73. A execução de teste e4; imprime novamente default e a execução de e4
= e1 chama o método de atribuição que recebe um teste como parâmetro, imprimindo
atribuicao 1. A execução de e4.mostra (); imprime 8. A execução de teste e5 (e2);
chama o construtor que recebe um teste como parâmetro e imprime copia, pois o
construtor de cópia é chamado. A execução de e5.mostra (); imprime 0. A execução
de e5 = 55; chama o operador de atribuição que recebe um inteiro como parâmetro,
imprimindo atribuicao 2 e a execução de e5.mostra (); imprime 55. A execução de
teste e6 = e3; chama novamente o construtor de cópia, imprimindo copia, já que esta
é uma operação de inicialização. A execução de e6.mostra (); imprime 73. A execução
de teste e7 (21, 3); imprime novamente soma ie a execução de e7.mostra (); imprime
24, a execução de e1 = e3 = e5 = e7; chama sucessivamente o operador de atribuição
que recebe um teste como parâmetro e imprime três vezes atribuicao 1. A execução
de e1.mostra (); imprime 24 e a execução de e5.mostra (); também imprime 24.
Programa 3
Tipo de polimorfismo: Paramétrico. Permite a definição de estruturas de dados ou
funções que contenham um ou mais parâmetros indicando o(s) tipo(s) do(s) elemento
(s) que serão manipulados. O mecanismo de template é utilizado para permitir a
implementação do tipo pilha genérica. Na execução do programa serão criadas a pilha
do tipo inteiro e a pilha do tipo caractere, a partir da mesma classe pilha.
Resultado da execução:
cria 40
17
cria 30
emp 11
emp x
emp 22
emp y
emp 33
33
22
11
y
x
tchau
tchau
Em main, a instrução pilha<int> p(40); chama o construtor de pilha e imprime
cria 40, a instrução pilha<char> q(30); chama o construtor de pilha e imprime cria
30, a instrução p.empilha(11); imprime emp 11, a instrução q.empilha(‘x’); imprime
emp x, a instrução p.empilha(22) imprime emp 22, a instrução q.empilha(‘y’);
imprime emp y e a instrução p.empilha(33) imprime emp 33. O do-while seguinte
imprime inicialmente o número 33 porque é o valor retornado por p.desempilha(), na
segunda execução imprime 22 e na terceira imprime 11. O próximo do-while imprime
inicialmente a letra y porque é o elemento retornado por q.desempilha() e na segunda
execução imprime x. Antes de terminar o programa, o destrutor de p e q são chamados
imprimindo tchau duas vezes.
12. Qual a diferença entre as posturas adotadas por JAVA e C++ em relação ao
polimorfismo de sobrecarga? Qual dessas posturas você acha melhor? Apresente
argumentos justificando sua posição.
A diferença é que JAVA embute sobrecarga em operadores e em subprogramas de
suas bibliotecas, mas somente subprogramas podem ser sobrecarregados pelo
programador, enquanto C++ realiza e permite que programadores realizem sobrecarga
tanto de programas quanto de operadores.
Os criadores de JAVA resolveram não incluir a sobrecarga de operadores por
considerá-la capaz de gerar confusões e aumentar a complexidade da LP. A postura
adotada por C++ é mais ampla e ortogonal, facilitando a leitura e redação dos
programas, se utilizada corretamente. Em minha opinião pessoal, considero a postura
de C++ mais adequada em termos de elegância. Em termos práticos, acho que o ganho
conferido é pequeno se comparado a complexidade associada ao uso de sobrecarga de
operadores.
13. Uma loja especializada em produtos de arte vende livros, discos e fitas de vídeo.
Ela necessita montar um catálogo com as informações sobre cada produto. Todo
produto possui um número único de registro, um preço e uma quantidade em
estoque. Além disso, deve-se saber o número de páginas dos livros, o número de
músicas dos discos e a duração da fita de vídeo. Outro aspecto importante a ser
considerado é a existência de um tipo de produto de venda combinada (uma fita de
vídeo combinada com um disco). Utilize C, C++ e JAVA para:
a) Especificar tipos abstratos de dados para cada um dos produtos da loja (basta
apresentar a definição do tipo e os cabeçalhos de suas operações de criação,
leitura, obtenção de dados, escrita e destruição).
18
b) Supondo que os produtos da loja estão armazenados em uma lista genérica,
fazer uma função/método para receber a lista dos produtos e uma quantidade
mínima de produtos a serem mantidos em estoque, e listar quais produtos
necessitam de reposição.
c) Fazer uma função/método para receber a lista dos produtos, uma quantidade de
músicas e uma duração mínima de filme, e listar quais discos possuem menos
músicas que a quantidade especificada e quais filmes possuem maior duração que
a especificada.
AINDA A SER FEITA.
14. O Ministério da Defesa te contratou para desenvolver um protótipo de um sistema
de informação em C++ que cadastre os militares existentes nas Forças Armadas
Brasileiras e gere duas listagens.
a. Crie uma classe abstrata Militar com um atributo inteiro representando sua
matrícula e outro atributo representando sua patente. Garanta que todas subclasses
concretas de Militar implementem obrigatoriamente os métodos de leitura de
dados de um militar, impressão de dados de um militar e verificação se o militar
está habilitado para progredir na carreira.
Utilize a classe seguinte para representar a patente do militar.
class Patente {
 private:
 string titulo;
 int tempo; // na patente
 public:
 le() {
 cin >> titulo;
 cin >> tempo;
 }
 string retornaPatente() {
 return titulo;
 }
 int retornaTempo() {
 return tempo;
 }
 void incrementa (int t) {
 tempo+=t;
 }
 void imprime() {
 cout << titulo << “ – “
 << tempo;
 }
}
b. Implemente uma subclasse concreta de Militar, denominada MilitarAeronautica,
que será usada para representar
os militares dessa divisão das forças armadas. Essa
classe possui como atributo adicional o número de horas de vôo efetuadas pelo
militar. Note que um militar da aeronáutica está em condições de progredir se tem
mais de 2 anos naquela patente e se acumulou mais de 100 horas de vôo nesse
período.
c. Considerando a existência de duas outras subclasses de Militar semelhantes a
MilitarAeronáutica, denominadas MilitarExercito e MilitarMarinha, utilize uma
classe ListaMilitares (consistindo de uma lista de ponteiros para militares) para
implementar um programa que:
• solicite ao usuário o número total de militares das Forças Armadas;
19
• solicite ao usuário a corporação e os dados de cada militar das Forças
Armadas;
• apresente os dados de todos os militares em condições de progredir na carreira;
• apresente os dados de todos os militares da Aeronáutica.
Observação: Você não deve implementar a classe ListaMilitares. Considere que,
além de possuir o método construtor default e o destrutor, ela também possui os
seguintes métodos:
void incluir(Militar* m); // inclui um militar ao final da lista
Militar* retornar(int i); // retorna o militar na i-ésima posição
a) 
class Militar {
int matricula;
Patente p;
public:
virtual void leitura () = 0;
virtual void impressao () = 0;
virtual int verificacao () = 0;
}
b)
class MilitarAeronautica: public Militar {
float horas;
public:
void leitura () {
cin >> matricula;
p.le();
cin >> horas;
}
void imprime () {
cout << matricula;
p.imprime();
cout << horas;
}
int verifica () {
if (p.retornaTempo() > 2 && horas > 100) 
return 1;
else
return 0;
}
}
c) AINDA A SER FEITA.
20
ExerciciosCap6.pdf
Resolução dos Exercícios do Capítulo VI
1. Implemente uma função sem parâmetros em C na qual se efetue a troca de dois
valores. Utilize-a em um programa executor de trocas de valores entre diversos
pares de variáveis. Explique porque os problemas de redigibilidade, legibilidade e
confiabilidade seriam ainda mais graves nesse caso do que no exemplo 6.3.
int a, b; // variaveis globais
void troca(){
int aux;
aux = a;
a = b;
b = aux;
}
main(){
int c, d, e, f;
c = 0; d = 1; e = 2; f = 3;
a = c;
b = d;
troca();
c = a;
d = b;
a = e;
b = f;
troca();
e = a;
f = b;
}
Como no exemplo 6.3, a função troca não possui parâmetros, sendo necessário
utilizar as variáveis globais a e b para lhe conferir generalidade e possibilitar o seu
reuso em main. 
Os problemas de redigibilidade, legibilidade e confiabilidade são ainda mais graves
dos que os do exemplo 6.3 por causa da necessidade de modificação dos valores das
variáveis globais c, d, e e f após a execução da função troca. Com isso, torna-se
necessário escrever mais, a funcionalidade do programa fica ainda mais obscurecida
por envolver uma maior quantidade de atribuições e aumenta-se a possibilidade do
programador realizar alguma atribuição indevida ou esquecer alguma atribuição.
2. É possível implementar, para cada tipo primitivo, funções em JAVA nas quais
sejam trocados os valores dos seus parâmetros formais? Caso sua resposta seja
afirmativa, implemente uma dessas funções e explique como funciona, destacando
como a troca é feita. Em caso de resposta negativa, justifique. Existiria alguma
diferença na sua resposta caso a troca fosse realizada entre parâmetros de um
mesmo tipo objeto? Justifique.
Não. Java optou por fazer a passagem de tipos primitivos sempre por cópia para o
parâmetro formal, o que impede que uma troca entre os parâmetros formais se reflita
nos parâmetros reais. Não haveria diferença significativa na resposta se a troca fosse
1
realizada entre parâmetros de um mesmo tipo objeto. Embora JAVA faça a passagem
de parâmetros dos tipos objeto (não primitivos) por cópia de referência, os efeitos da
troca continuariam sendo válidos apenas internamente a função, não se reflitindo nos
parâmetros reais.
3. Um TAD (tipo abstrato de dados) é definido pelo comportamento uniforme de um
conjunto de valores. Embora a linguagem C não suporte a implementação do
conceito de TADs, o programador pode simular o seu uso. Explique como isto
pode ser feito. Descreva os problemas com essa aproximação.
Para simular o uso de TADs em C, é necessário definir um tipo de dados simples e um
conjunto de operações (subprogramas) que se aplicam sobre valores desse tipo, isto é,
subprogramas que têm parâmetros desse tipo.
Normalmente, a implementação das operações do TAD e quaisquer outras entidades
de computação necessárias para a implementação dessas operações são definidas num
arquivo de implementação (.c). No arquivo de interface (.h), o tipo da estrutura de
dados é definido e os protótipos dos subprogramas correspondentes às operações do
TAD são declarados, para que sejam disponibilizados para os programadores usuários.
Assim, o programador usuário não necessita mais implementar o código das
operações do TAD, o que torna o código mais legível e redigível, além do código
usuário não precisar ser alterado caso seja necessário realizar uma alteração na
implementação do tipo ou nas suas operações (desde que os cabeçalhos das operações
não sejam modificados).
Os problemas com essa aproximação são que, além dela não promover o
encapsulamento de dados e operações em uma única unidade sintática, ela não impede
o uso indisciplinado do TAD. A operação de inicialização do TAD, por exemplo, deve
ser chamada explicitamente pelo programador. Caso o programador usuário esqueça
ou não chame essa operação antes de qualquer outra, o uso correto do TAD ficará
comprometido. Além disso, é importante observar que ele pode realizar operações
adicionais sobre o TAD além das especificadas pelos subprogramas. Por exemplo, o
programador pode acessar diretamente a estrutura interna do TAD, quebrando o
ocultamento de informações.
4. Considere uma função em JAVA recebendo um objeto como único parâmetro e
simplesmente realizando a atribuição de null ao seu parâmetro formal. Qual o
efeito dessa atribuição no parâmetro real? Justifique.
Essa atribuição não produz qualquer efeito sobre o parâmetro real correspondente, só
produzindo efeitos internos, uma vez que, em JAVA, as atribuições de valores
completos do tipo não-primitivo ao parâmetro formal não produzem efeito no
parâmetro real, sendo a passagem para tipos não-primitivos considerada, nesse caso,
unidirecional de entrada.
5. JAVA não permite a criação de funções com lista de parâmetros variável, isto é,
funções nas quais o número e o tipo dos parâmetros possam variar, tal como a
função printf de C. Como JAVA faz para possibilitar a criação da função
System.out.println com funcionalidade similar à função printf de C? Como o
problema da falta de lista de parâmetros variável pode ser contornado de maneira
geral pelo programador JAVA em situações nas quais esse tipo de característica
2
pode ser útil? Compare essa abordagem geral de JAVA com a adotada por C e
C++ em termos de redigibilidade e legibilidade.
A função System.out.println de Java recebe sempre uma string como argumento. Ela
funciona de forma similar a printf por conta do uso combinado da operação de
concatenação (+) de strings e da realização de conversão implícita de tipos antes da
operação de concatenação. Tipos primitivos e objetos são convertidos implicitamente
para strings antes da realização da concatenação (quando o outro argumento é uma
string). Por exemplo, na chamada de System.out.println seguinte
int i =3;
System.out.println (“teste: “ + i + “: “+ new Float (4.3) + “: “+ true); 
o valor 3 de i é convertido na string “3” antes de ser concatenado à string

Teste o Premium para desbloquear

Aproveite todos os benefícios por 3 dias sem pagar! 😉
Já tem cadastro?

Outros materiais

Materiais relacionados

Perguntas relacionadas

Perguntas Recentes