Você está na página 1de 369

1

Captulo I Introduo


O aspecto mais importante, mas tambm o mais elusio, de qualquer erramenta sua
inluncia nos habitos daqueles que se treinam no seu uso. Se a erramenta uma lingua-
gem de programaao essa inluncia , gostemos ou nao, uma inluncia em nosso habito
de pensar.`
Ldsger \. Dijkstra
Uma Linguagem de Programao (LP) um instrumento utilizado pelo
profissional de computao para escrever programas, isto , conjuntos de
instrues a serem seguidas pelo computador para realizar um determina-
do processo.
Em virtude das limitaes fsicas dos computadores e da pouca maturida-
de da cincia da computao na poca de surgimento dos primeiros com-
putadores, eles s podiam ser programados atravs de linguagens de pro-
gramao muito simples. Tais linguagens disponibilizavam um pequeno
conjunto de tipos de instrues capazes de realizar aes muito elementa-
res e se caracterizavam por serem de uso exclusivo de um computador
especfico. Por conta disso, hoje elas so conhecidas como linguagem de
mquina ou de baixo nvel.
medida que a computao avanava e se vislumbrava o potencial dessa
nova ferramenta, as aplicaes iam se tornando cada vez mais complexas.
Nessa poca, foi constatado que o uso de linguagens to simples e espec-
ficas reduzia significativamente a produtividade dos programadores e im-
pedia a ampla disseminao dos computadores. Para contornar esse pro-
blema, surgiram as linguagens de programao de alto nvel. Em contras-
te com as linguagens de mquina, essas linguagens se caracterizam por
no serem especficas para um computador e por terem um conjunto mais
amplo e expressivo de tipos de instruo.
O enfoque desse livro no estudo dessas linguagens. Dessa maneira,
sempre que for utilizado o termo linguagem de programao, estaremos
nos referindo a uma linguagem de programao de alto nvel.
Para que os programadores possam construir programas em uma determi-
nada linguagem necessrio definir os smbolos que podem ser utilizados
e a forma como devem ser combinados para produzir um programa vli-
do. Alm disso, os programadores tambm precisam entender como um
programa vlido ser executado pelo computador.
Embora existam inmeras linguagens de programao, cada qual com seu
prprio conjunto de smbolos e regras para formao e interpretao de
programas, possvel e interessante estud-las focando os principais con-
2
ceitos que lhes so comuns e esclarecendo como eles podem ser utiliza-
dos na programao e como podem ser implementados nas linguagens.
Conceitos relevantes e especficos existentes em uma linguagem particu-
lar tambm devem ser compreendidos pois podem ser um instrumento
valioso para melhor entendimento de uma tcnica de programao ou pa-
ra futuras evolues e novos projetos de linguagens.
Para tornar o aprendizado de um conceito mais efetivo, importante que
ele seja utilizado na construo de um programa em uma linguagem de
programao que oferea recursos para utilizao do conceito. As trs
linguagens (C, C++ e JAVA), que so usadas primordialmente nos exem-
plos deste texto, oferecem recursos para a programao da grande maioria
dos conceitos abordados aqui.
O estudo e a discusso sobre as caractersticas e os mecanismos existentes
em linguagens de programao requer entendimento sobre as proprieda-
des desejveis em uma linguagem de programao, os papis que podem
ser exercidos pela linguagem e o contexto no qual seu projeto foi realiza-
do.
Nesse captulo apresentada uma introduo ao estudo de Linguagens de
Programao. So discutidas as razes para se conhecer profundamente
esse tema, o papel das linguagens de programao no processo de desen-
volvimento de programas e as principais propriedades desejveis em lin-
guagens de programao. Em seguida, so apresentadas noes importan-
tes sobre a especificao, implementao e padronizao de uma lingua-
gem. Cada um dos paradigmas mais comuns de linguagens de programa-
o so brevemente abordados. Por fim, discutida a evoluo e histria
dessas linguagens.
1.1 Razes para Estudar Linguagens de Programao
Embora qualquer programador reconhea que linguagens de programao
so instrumentos fundamentais dentro de sua especialidade, importante
destacar quais so os benefcios que um estudo aprofundado de Lingua-
gens de Programao pode proporcionar ao estudante. Esses benefcios
so apresentados a seguir:
a) Maior capacidade de desenvolver solues computacionais para pro-
blemas. Uma maior compreenso sobre os conceitos de uma LP pode
aumentar nossa habilidade em como pensar e resolver problemas. Por
exemplo, o conhecimento do conceito de tipos abstratos de dados es-
timulam a utilizao desse mtodo de programao mesmo em LPs
que no possuem mecanismos especficos para a sua implementao.
3
b) Maior habilidade ao usar uma LP. O maior entendimento a respeito
das funcionalidades e implementao de uma LP possibilita ao pro-
gramador construir programas melhores e mais eficientes. Por exem-
plo, conhecendo como as LPs so implementadas pode-se entender
porque algoritmos recursivos so menos eficientes que os iterativos
correspondentes.
c) Maior capacidade para escolher LPs apropriadas. Conhecer os recur-
sos oferecidos por uma linguagem e saber como esses recursos so
implementados pode determinar uma boa escolha da linguagem de
programao a ser usada em um projeto. Por exemplo, saber que C
no realiza checagem dinmica dos ndices de acessos a posies de
vetores pode ser decisivo para escolher essa linguagem em aplicaes
de tempo real que fazem uso frequente de acessos vetoriais.
d) Maior habilidade para aprender novas LPs. Por exemplo, programado-
res que aprenderam os conceitos de orientao a objetos tem maior fa-
cilidade para aprender C++ e JAVA.
e) Maior habilidade para projetar novas LPs. Muito embora poucos pro-
fissionais de computao tenham a oportunidade, ao longo de suas car-
reiras, de participar da criao de uma linguagem de programao de
propsito geral, no raro se depararem com situaes nas quais ne-
cessrio projetar linguagens para um propsito especfico. Por exem-
plo, ao construir as interfaces com o usurio de sistemas pode ser ne-
cessrio projetar e implementar uma linguagem de comandos para
comunicao entre o usurio e o sistema.
1.2 O Papel das Linguagens de Programao no Processo de
Desenvolvimento de Software
Linguagens de programao foram criadas para tornar mais produtivo o
trabalho dos programadores. Logo, em ltima instncia, o objetivo das
linguagens de programao tornar mais efetivo o processo de desenvol-
vimento de software. importante enfatizar que esse processo existe tan-
to para tornar mais produtiva a gerao e manuteno de software quanto
para garantir que ele seja produzido atendendo a padres de qualidade.
Assim, uma maneira de saber como as linguagens de programao podem
apoiar esse processo envolve o conhecimento das propriedades requeridas
em um software de qualidade. As principais propriedades desejadas em
um software so confiabilidade, manutenibilidade e eficincia.
A confiabilidade diz respeito ao atendimento adequado da especificao
funcional, da garantia de segurana contra erros e da integridade dos da-
dos manipulados pelo software. Uma LP pode promover a confiabilidade
4
de programas facilitando a existncia de ferramentas computacionais que
verifiquem a ocorrncia de erros nos programas. Por exemplo, LPs que
requerem a declarao de variveis, tais como C, PASCAL, e MODULA-
2, facilitam a identificao de erros de digitao de nomes. Caso um usu-
rio digite um nome incorreto, um verificador de erros pode identific-lo
porque no foi declarado.
A manutenibilidade diz respeito a facilidade de alterao do software.
Necessidades de modificao so provenientes de erros de especificao,
projeto ou implementao, de alteraes no ambiente tecnolgico onde o
software executado e de novas demandas do usurio. Uma LP pode
promover a manutenibilidade de programas fornecendo mecanismos que
permitam a sua adaptao a diferentes contextos. Por exemplo, a declara-
o de constantes em Pascal e Modula-2 facilita a realizao de modifica-
es nos programas. Caso um programa utilize uma constante para definir
o tamanho mximo de um vetor, basta modificar essa constante para a-
daptar todo o programa a um aumento no tamanho mximo do vetor.
A eficincia diz respeito ao uso otimizado dos recursos computacionais
em termos de tempo de execuo, de espao de memria utilizado e de
uso de dispositivos perifricos. Uma LP pode promover a eficincia de
programas incentivando o uso de mecanismos computacionalmente efici-
entes. Por exemplo, FORTRAN (com exceo de FORTRAN 90) no
permite o uso de recurso para tornar mais eficiente o processamento e o
consumo de memria.
Outro modo de saber como as linguagens de programao podem apoiar o
processo de desenvolvimento de software conhecendo como ele reali-
zado. O processo de desenvolvimento de software normalmente com-
preendido em cinco etapas [PRESSMAN, 1997]:
1.2.1 Especificao de Requisitos
Nessa etapa se identifica o que o software dever realizar em termos de
funcionalidades. necessrio especificar o ambiente no qual o software
atuar, quais sero as suas atividades, quais os impactos que dever pro-
duzir e de que maneira ele dever interagir com os usurios. Em particu-
lar, dever-se- especificar os requisitos de desempenho do sistema em
termos de tempo de resposta desejado, espao de memria requerido e
tambm de interao com outros dispositivos e usurios.
Outra atividade importante nessa etapa a realizao de um estudo de
viabilidade e custo do software a ser desenvolvido. Esse estudo tem por
objetivo responder se a confeco e implantao do software vivel tc-
nica, cronolgica e socialmente, bem como determinar, atravs da estima-
tiva do custo de desenvolvimento do software, se a sua construo vi-
5
vel economicamente. A confeco de verses dos manuais do usurio do
software tambm uma atividade desta etapa.
O estudo de linguagens de programao influencia pouco a realizao
dessa etapa. Basicamente, o conhecimento sobre linguagens de progra-
mao pode ser usado no estudo de viabilidade para ajudar a responder se
possvel desenvolver o software no perodo de tempo desejado.
1.2.2 Projeto do Software
Tendo por base os documentos de especificao de requisitos, pode-se
projetar o sistema de programao. O projeto envolve a escolha de um
mtodo de projeto e sua aplicao na especificao da arquitetura do
software e seus procedimentos, das suas estruturas de dados e de suas in-
terfaces.
O resultado desta fase um documento de especificao do projeto do
sistema, identificando todos os mdulos que compem a arquitetura do
sistema, as estruturas de dados utilizadas por cada mdulo, as interfaces
de comunicao entre mdulos (interfaces internas), com outros sistemas
(interfaces externas) e com as pessoas que o utilizam (interface com o
usurio). Tambm faz parte desse documento as descries procedimen-
tais de cada mdulo da arquitetura.
O principal papel das LPs nessa etapa dar suporte ao mtodo de projeto.
Isto possibilita que a implementao de um sistema reflita o seu projeto,
evitando adaptaes e distores no projeto e perda de correspondncia.
Pode-se observar que algumas linguagens de programao so mais ade-
quadas quando se utilizam certos mtodos de projeto. Por exemplo, en-
quanto C mais adequada ao mtodo de projeto hierrquico-funcional,
JAVA mais adequada ao mtodo orientado a objetos.
Existem vrias ferramentas CASE que oferecem suporte s atividades
dessa etapa. Muitas dessas ferramentas j geram parte da codificao do
software em uma LP.
1.2.3 Implementao
A etapa de implementao onde ocorre a programao dos mdulos do
software. Obviamente, LPs so essenciais nessa etapa uma vez que os
programas devem ser escritos em uma linguagem. Essa etapa a mais
atendida por ferramentas, tais como editores de texto que destacam os
vocbulos da linguagem e identam automaticamente o texto, analisadores
lxicos, sintticos e semnticos de programas e bibliotecas de subprogra-
mas e mdulos.
6
1.2.4 Validao
O propsito dessa etapa verificar se o sistema satisfaz as exigncias das
especificaes de requisitos e de projeto. Geralmente, isto feito testan-
do-se o sistema contra as especificaes. Existem trs tipos de testes: tes-
te de mdulo, teste de integrao e teste de sistema. No teste de mdulo
verificado se ele cumpre o que lhe foi especificado. No teste de integra-
o verificado se os mdulos se integram apropriadamente, isto , se
eles interagem tal como estabelecido nas especificaes de suas interfa-
ces. O teste de sistema averigua se o software cumpre as funcionalidades
para o qual foi desenvolvido e atende a todos os demais requisitos de usa-
bilidade e eficincia.
LPs podem auxiliar a validao de vrios modos. Por exemplo, a natureza
de algumas linguagens facilita a construo de depuradores de erros e
ambientes nos quais fcil executar mdulos de programa independen-
temente da existncia de outros mdulos. Isso pode ser muito valioso para
realizar os testes de mdulo e integrao.
1.2.5 Manuteno
A ltima etapa do processo de desenvolvimento de software a manuten-
o e evoluo do sistema. Para que o ciclo de vida de um software possa
ser ampliado necessrio que ele seja capaz de facilitar a correo de er-
ros residuais (isto , erros descobertos aps a sua liberao para o usu-
rio), adaptar-se a mudanas no seu contexto de aplicao (tal como, um
novo ambiente computacional) e atender a demandas por melhoria e in-
cluso de servios.
LPs que oferecem recursos de modularizao tendem a gerar programas
mais fceis de serem mantidos uma vez que as alteraes em um mdulo
no interferem nos demais mdulos constituintes do software.
1.3 Propriedades Desejveis em uma Linguagem de Progra-
mao
A partir da chamada crise do software [PRESSMAN, 1997], o aproveita-
mento do tempo do profissional de programao se tornou um conceito
central no processo de desenvolvimento de software. Consequentemente,
as propriedades desejveis nas LPs devem enfatizar esse aspecto. Discu-
tem-se a seguir algumas das principais propriedades desejveis em uma
LP:
7
1.3.1 Legibilidade
Essa propriedade diz respeito facilidade para se ler e entender um pro-
grama. Quanto mais fcil for seguir as instrues de um programa, mais
fcil ser entender o que est sendo feito e tambm descobrir erros de
programao.
LPs que requerem o uso extensivo do comando goto normalmente redu-
zem a legibilidade dos programas porque permitem a ocorrncia da cha-
mada programao macarrnica ou no estruturada. Nesse tipo de pro-
gramao, os programas possuem fluxo de controle que no obedecem a
padres regulares. Isto torna difcil acompanhar e entender o que fazem
esses programas. Programas em verses antigas de FORTRAN e BASIC,
por exemplo, tendiam a ser mal estruturados porque estas verses reque-
riam o uso de goto para implementar as estruturas de seleo e repetio.
O uso de um mesmo vocbulo da LP para denotar diferentes comporta-
mentos dependendo do contexto onde usado tambm prejudicial le-
gibilidade. Por exemplo, o vocbulo this pode ser usado em JAVA tanto
para referenciar um objeto quanto para fazer uma chamada a uma funo
construtora de dentro de outra. Outro exemplo o operador * em C, que
tanto pode denotar a operao de multiplicao de nmeros quanto opera-
es de manipulao de ponteiros. Isso permite a criao de expresses
confusas. Por exemplo, a seguinte linha de cdigo C apresenta o uso do
operador * em trs diferentes contextos:
*p = (*p)*q;
Observe que a operao designada pelo * mais a direita nessa linha de
cdigo a operao de multiplicao. Por sua vez, a operao designada
pelo * do meio a de retorno do contedo da clula de memria apontada
pelo ponteiro p. Por fim, o * mais esquerda denota a operao de retor-
no do endereo da clula apontada por p.
Efeitos colaterais so mudanas adicionais promovidas no estado do pro-
grama (isto , nos valores das variveis do programa) durante a avaliao
de uma determinada de expresso ou a execuo de um comando ou sub-
programa. O termo adicionais se refere ao fato das mudanas provocadas
pelos efeitos colaterais no serem o objetivo principal da expresso, co-
mando ou subprograma realizado. Por exemplo, a operao x++ de C tem
como efeito principal retornar o valor da varivel x e como efeito colate-
ral incrementar o valor dessa varivel. Efeitos colaterais podem ser preju-
diciais a legibilidade quando seus resultados no ficam explcitos no tre-
cho de programa que utiliza a operao. Observe no exemplo 1.1, em C,
que a chamada da funo retornaCinco provoca alterao na varivel
global x.
8
int x = 1;
int retornaCinco() {
x = x + 3;
return 5;
}
main() {
int y;
y = retornaCinco ();
y = y + x;
}
Exemplo 1. 1- Problema de Legibilidade Causado por Efeito Colateral em C
Note que esse efeito no fica explcito no trecho de cdigo que chama
retornaCinco. Algum que fizesse uma rpida inspeo no cdigo do e-
xemplo 1.1 tentando identificar o que o programa faz e apenas olhasse a
funo main no conseguiria entender que o valor final de y nove e no
seis. Efeitos colaterais podem causar problemas ainda mais graves do que
o de legibilidade (por exemplo, podem causar indeterminismo na expres-
so x + retornaCinco())
1.1
.
Marcadores de blocos de comandos, tais como o BEGIN({)END(}) de
PASCAL e C, tambm podem causar confuses na leitura do programa
quando existem vrios comandos de repetio e seleo aninhados. A i-
nexistncia de um marcador especfico que indique onde o comando if de
C se encerra possibilita a escrita de comandos ifs aninhados difceis de
serem entendidos. No exemplo 1.2, embora o else pertena ao if mais in-
terno, tem-se a impresso que ele se refere ao if mais externo.
if (x>1)
if (x==2)
x=3;
else
x=4;
Exemplo 1. 2 Problema de Legibilidade Relacionado com Marcadores de Bloco
ADA reduz este problema usando beginendif e beginendloop.
Algumas LPs adotaram posturas altamente questionveis com relao
legibilidade. FORTRAN, por exemplo, permite que palavras reservadas
como DO, END, INTEGER e REAL sejam tambm nomes de variveis.

1.1
Esse problema ser discutido em maiores detalhes no captulo 5 desse livro.
9
1.3.2 Redigibilidade
Essa propriedade possibilita ao programador se concentrar nos algoritmos
centrais do programa, sem se preocupar com aspectos no relevantes para
a resoluo do problema. Esta caracterstica a que melhor diferencia as
linguagens de mquina (nas quais o programador deve se preocupar prin-
cipalmente com detalhes de implementao) e linguagens de programa-
o (nas quais o programador se concentra na descrio do algoritmo que
resolve o problema).
LPs com tipos de dados limitados requerem o uso de estruturas comple-
xas. Isto acaba dificultando a redao de programas. Por exemplo, como
FORTRAN no possui registros, armazenar dados de empregados de uma
firma requer a criao de vetores especficos para cada tipo de dado. Ao
redigir um subprograma para ordenar os dados seria necessrio usar v-
rias instrues para trocar os elementos correspondentes em cada vetor.
LPs que requerem muita programao de entrada e sada e que no dis-
pem de mecanismos para o tratamento de erros tendem a obscurecer os
algoritmos centrais nos programas.
A redigibilidade de programas pode conflitar com a legibilidade. C per-
mite a redao de comandos complexos, mas que podem no identificar
de maneira muito clara a sua funcionalidade. Observe o comando for do
exemplo 1.3 e tente identificar o que ele faz.
void f(char *q, char *p) {
for (;*q=*p; q++,p++);
}
Exemplo 1. 3 - Redigibilidade X Legibilidade
fcil perceber o quo concisa essa implementao. Contudo, o preo a
ser pago a falta de entendimento imediato sobre sua funcionalidade.
Programadores inexperientes encontraro dificuldades para entender o
que esse comando faz, enquanto programadores experientes podero se
confundir ao fazer uma rpida leitura.
1.3.3 Confiabilidade
Essa propriedade se relaciona aos mecanismos fornecidos pela LP para
incentivar a construo de programas confiveis.
LPs que requerem a declarao de dados permitem verificar automatica-
mente erros de tipos durante compilao ou execuo. Um compilador de
JAVA pode detectar durante a compilao um erro de digitao cometido
10
por um programador em situaes como a do exemplo 1.4, onde o v foi
inadvertidamente trocado por u no trecho v = u + 2; :
boolean u = true;
int v = 0;
while (u && v < 9) {
v = u + 2;
if (v == 6) u = false;
}
Exemplo 1. 4 Declarao de Tipos e Confiabilidade
LPs que possuem mecanismos para detectar eventos indesejveis e espe-
cificar respostas adequadas a tais eventos permitem a construo de pro-
gramas mais confiveis. No trecho de cdigo em JAVA, apresentado no
exemplo 1.5, poder haver um erro se o valor de i estiver fora dos limites
de ndices do vetor a. Caso isto ocorra, o programa interrompe seu fluxo
normal de execuo e passa para o trecho de cdigo responsvel pelo tra-
tamento desse erro.
try {
System.out.println(a[i]);
} catch (IndexOutofBoundsException) {
System.out.println(Erro de Indexao);
}
Exemplo 1. 5 - Tratamento de Excees e Confiabilidade
1.3.4 Eficincia
De acordo com as demandas por recursos de um tipo de aplicao, certas
LPs so mais recomendadas e outras no devem ser usadas. Aplicaes
de automao em tempo real, por exemplo, normalmente requerem o uso
de LPs que minimizem o tempo de execuo e de acesso aos dispositivos
perifricos, bem como o consumo de espao de memria.
Muito embora, hoje, boa parte da responsabilidade em gerar cdigo efici-
ente seja transferida para o compilador, atravs da otimizao automtica
de cdigo, as caractersticas de uma LP podem determinar se o programa
gerado naquela LP ser mais ou menos eficiente. Assim, LPs que reque-
rem a verificao de tipos durante a execuo so menos eficientes do
que aquelas que no fazem este tipo de requisio.
Por exemplo, o mecanismo de tratamento de excees existente em JA-
VA impem que os ndices de vetores sejam verificados em todos os a-
cessos durante a execuo dos programas. Isso implica na necessidade de
se fazer um teste antes de qualquer acesso aos vetores. Por outro lado,
11
como C no faz esse tipo de exigncia, o cdigo gerado economizar a
realizao desse teste e, portanto, ser mais veloz.
1.3.5 Facilidade de aprendizado
O programador deve ser capaz de aprender a linguagem com facilidade.
LPs com muitas caractersticas e mltiplas maneiras de realizar a mesma
funcionalidade, tal como C++, tendem a ser mais difceis de aprender.
Por exemplo, num determinado contexto, os seguintes comandos em C ou
C++, tm o mesmo efeito:
c = c + 1; c+=1; c++; ++c;

Alm disso, outro aspecto negativo causado pelo excesso de caractersti-
cas o fato de levar os programadores a conhecerem apenas uma parte da
linguagem, o que torna mais difcil a um programador entender o cdigo
produzido por outro.
1.3.6 Ortogonalidade
Ortogonalidade diz respeito capacidade da LP permitir ao programador
combinar seus conceitos bsicos sem que se produzam efeitos anmalos
nessa combinao. Assim, uma LP to mais ortogonal quanto menor for
o nmero de excees aos seus padres regulares.
LPs ortogonais so interessantes porque o programador pode prever, com
segurana, o comportamento de uma determinada combinao de concei-
tos. Isso pode ser feito sem que se tenha de implementar testes para ave-
riguao do uso combinado de dois ou mais conceitos, ou mesmo buscar
na especificao da LP se existe alguma restrio quela combinao.
A falta de ortogonalidade, por sua vez, dificulta o aprendizado da LP e
pode estimular a ocorrncia de erros de programao. No exemplo 1.6
mostrada a falta de ortogonalidade de um cdigo JAVA:
int x, y = 2, z = 3;
byte a, b = 2, c = 3;
x = y + z;
a = b + c;
Exemplo 1. 6 - Falta de Ortogonalidade em JAVA
Embora tanto o tipo int quanto o tipo byte sejam tipos inteiros, a linha de
cdigo onde ocorre a soma de tipos int legal enquanto a que soma tipos
byte ilegal (tente descobrir porqu). Ora, essa falta de ortogonalidade
claramente uma fonte potencial de erros, uma vez que a maioria dos
12
programadores pensaria que a mesma regra que se aplica ao tipo int se
aplicaria aos outros tipos de inteiros.
Outro exemplo clssico de falta de ortogonalidade ocorre em PASCAL.
Nessa LP, funes podem retornar qualquer tipo de dados com exceo
de registros e vetores.
1.3.7 Reusabilidade
Outra propriedade desejvel em LPs a reusabilidade de cdigo, isto , a
possibilidade de reutilizar o mesmo cdigo para diversas aplicaes.
Quanto mais reusvel for um cdigo, maior ser a produtividade de pro-
gramao, uma vez que, na construo de novos programas, bastar utili-
zar e, eventualmente, adaptar cdigos escritos anteriormente sem que se
faa necessrio reconstru-los novamente a partir do zero.
LPs podem incentivar a criao de cdigo reusvel de vrias maneiras. A
forma mais simples de facilitar a reusabilidade atravs da parametriza-
o de subprogramas. Por exemplo, o subprograma em C apresentado no
exemplo 1.7 pode ser utilizado em qualquer aplicao na qual se queira
trocar os valores de duas variveis inteiras quaisquer.
void troca (int *x, int *y) {
int z = *x;
*x = *y;
*y = z;
}
Exemplo 1. 7 - Reuso por Parametrizao de Subprogramas
Outro mecanismo muito til para permitir o reuso de cdigo a modula-
rizao atravs das bibliotecas de subprogramas. A linguagem C oferece
inmeras funes de entrada e sada (tais como, printf, scanf e fprintf)
como parte de sua biblioteca padro. Essas funes podem ser usadas em
qualquer programa sem que o programador necessite reescrev-las.
1.3.8 Modificabilidade
Essa propriedade se refere s facilidades oferecidas pelas LPs para possi-
bilitar ao programador alterar o programa em funo de novos requisitos
sem que tais modificaes impliquem em mudanas em outras partes do
programa.
Exemplos de mecanismos que proporcionam boa modificabilidade so o
uso de constantes simblicas e a separao entre interface e implementa-
o na construo de subprogramas e tipos abstratos de dados.
13
Em C, vrias constantes simblicas so usadas, tais como NULL e EOF.
O comando seguinte cria uma constante simblica em C denotando o n-
mero pi com preciso de duas casas decimais.
const float pi = 3.14;
Se em algum momento for constatada a necessidade de maior preciso na
definio do nmero pi, basta fazer a alterao nessa mesma linha (inclu-
indo mais casas decimais) e todas as ocorrncias da constante pi no pro-
grama sero ajustadas para o seu novo valor numrico, sem que seja ne-
cessrio realizar alteraes em outras partes do programa onde pi usado.
1.3.9 Portabilidade
altamente desejvel que programas escritos em uma LP se comportem
da mesma maneira independentemente da ferramenta utilizada para tra-
duzir os programas para a linguagem de mquina ou da arquitetura com-
putacional (hardware ou sistema operacional) sobre a qual esto sendo
executados.
Dessa maneira, um mesmo programa ou biblioteca pode ser utilizado em
vrios ambientes em diferentes situaes sem que seja necessrio dispen-
der tempo de programao para reescrev-los ou adapt-los ao novo am-
biente de traduo ou execuo.
LPs podem facilitar a obteno de programas portveis atravs da amar-
rao rigorosa do comportamento de seus elementos em tempo de projeto
da linguagem, no dando liberdade para que os implementadores definam
comportamentos distintos para um mesmo elemento.
Contudo, essa postura pode impor algumas restries implementao
das linguagens, em particular, no que diz respeito busca por eficincia
na execuo dos programas. Nesse caso, pode-se optar por sacrificar a
completa portabilidade dos programas na LP em benefcio da potenciali-
zao de outras propriedades. Mesmo assim, deve-se procurar maximizar
a portabilidade permitindo que os programas escritos na LP sejam trans-
portados para outros ambientes requerendo apenas poucas modificaes
em seu cdigo.
1.4 Especificao de LPs
Ao se criar uma LP necessrio definir como se faz para escrever pro-
gramas nessa linguagem e como os programas vlidos devem se compor-
tar. Essa definio deve ser feita atravs de documentos descritivos que
estabeleam de maneira precisa como essas duas atividades devem ser
realizadas. Tais documentos formam a especificao da LP. Sem uma es-
14
pecificao apropriada, implementaes das LPs no podem ter uniformi-
dade, fazendo com que programas construdos para uma implementao
tenham comportamento bem diferenciado ou mesmo no sejam vlidos
em outra implementao.
A especificao de uma LP requer a descrio de um lxico, de uma sin-
taxe e de uma semntica para a LP. O lxico da LP corresponde ao voca-
bulrio que pode ser utilizado para formar sentenas na linguagem.
A sintaxe da LP corresponde ao conjunto de regras que determinam quais
sentenas podem ser formadas a partir da combinao dos itens lxicos. O
lxico e sintaxe esto relacionados com a forma dos programas, isto ,
como expresses, comandos, declaraes e outros elementos da LP po-
dem ser combinados para formar programas vlidos.
A semntica da LP descreve como as construes sintaticamente corretas
so interpretadas ou executadas. A semntica est relacionada com o sig-
nificado dos programas, isto , como eles se comportam quando executa-
dos por computadores. Por exemplo, no comando seguinte, em C:
a = b;
O lxico da LP estabelece que a, =, b e ; fazem parte do vocabulrio
da LP.
A sintaxe da LP indica que a sentena formada pelo identificador a, o
smbolo =, o identificador b e o smbolo ; designa um comando vlido
de atribuio.
A semntica da LP indica que este comando deve ser executado de
modo a substituir o valor de a pelo valor atual de b.
A sintaxe de uma LP influencia como os programas so escritos pelo pro-
gramador, lidos por outros programadores e analisados pelo computador.
A semntica de uma LP influencia como os programas so criados pelo
programador, entendidos por outros programadores e interpretados pelo
computador.
A sintaxe de uma LP descrita por uma gramtica. Uma notao muito
utilizada para descrever gramticas de LPs a BNF (Backus-Naur Form).
O exemplo 1.8 apresenta uma gramtica para formao de expresses a-
ritmticas elementares, descrita em BNF:
<expresso> ::= <valor><valor><operador><expresso>
<valor> ::= <nmero><sinal><nmero>
<nmero> ::= <semsinal><semsinal>.<semsinal>
<semsinal> ::= <dgito><dgito><semsinal>
<dgito> ::= 0123456789
15
<sinal> ::= +
<operador> ::= +/*
Exemplo 1. 8 - Gramtica Simples
Recomenda-se descrever a semntica de uma LP formalmente. Contudo,
descries formais de semntica so freqentemente complexas para ler e
escrever. Como resultado, na maior parte das vezes, a semntica de LPs
descrita de maneira informal atravs de documentos que explicam em
linguagem natural qual o significado dos comandos da LP. Uma outra
abordagem muito adotada o enfoque operacional, que consiste em des-
crever o significado de um comando atravs da apresentao de um cdi-
go equivalente numa linguagem mais elementar.
Um problema que ocorre muito frequentemente com LPs a ausncia de
uma padronizao. Ao invs de se ter uma especificao nica adotada
por todos os implementadores de LPs, surgem vrias implementaes dis-
tintas com as suas prprias especificaes. Isso ocorre normalmente
quando a linguagem ainda no se encontra totalmente estabelecida. As-
sim, os implementadores discordam a respeito de quais elementos devem
fazer parte da linguagem e como eles devem se comportar. Muitas vezes
essa discordncia provocada pela necessidade de explorar as caracters-
ticas especficas do ambiente onde a implementao ser realizada.
Contudo, na medida que a linguagem vai se popularizando e amadure-
cendo, essa variao de comportamentos acaba por gerar problemas signi-
ficativos de portabilidade de programas.
Para resolver esses problemas, procura-se estabelecer uma especificao
padro nica que deve ser respeitada por todos os implementadores da
LP. Variaes podem at continuar a existir, mas um ncleo de elementos
comum necessita ser implementado para que a LP esteja em conformida-
dade com o padro estabelecido. Assim, os programadores podem escre-
ver programas garantidamente portveis, desde que s utilizem os ele-
mentos padronizados.
Normalmente, a padronizao de uma LP promovida por alguma insti-
tuio especializada nesse servio, tais como a ISO (International Stan-
dards Organization), o IEEE (Institute of Electrical and Electronics Engi-
neers), o ANSI (American National Standards Institute) e o NIST (Nati-
onal Institute for Standards and Technology). O processo de padronizao
envolve a formao de um grupo de voluntrios especialistas que traba-
lham para definir quais elementos devem fazer parte da padronizao.
Esse trabalho complexo e demorado, pois envolve a obteno de con-
senso entre os participantes do grupo de padronizao. Alm disso, o con-
senso geralmente obtido omitindo-se as caractersticas mais polmicas.
16
Outro problema a definio do momento de padronizao. Se muito ce-
do, a falta de experincia com a LP pode produzir um padro inadequado
que iniba o seu uso e disseminao. Se muito tarde, a existncia de muitas
verses incompatveis com um grande legado de cdigo pode dificultar
ou retardar a aceitao do padro pela comunidade.
1.5 Mtodos de Implementao de LPs
Todo e qualquer programa escrito em uma LP necessita ser traduzido para
a linguagem de mquina para ser executado. Para fazer isto necessrio
aplicar um programa (ou conjunto de programas) que receba como entra-
da o cdigo fonte do programa a ser traduzido e gere o cdigo traduzido
na linguagem de mquina. Esse programa tradutor quem determina co-
mo os programas na LP sero implementados, isto , como o cdigo fonte
traduzido se comportar efetivamente quando executado no computador.
Sebesta [SEBESTA, 1999] descreve trs mtodos gerais de implementa-
o de LPs (ilustrados na figura 1.1).












Figura 1. 1 - Mtodos de Implementao de LPs
1.5.1 Compilao
O processo de compilao efetua a traduo integral do programa fonte
para o cdigo de mquina. Uma vez traduzido, o programa em linguagem
de mquina pode ser executado diretamente.
A grande vantagem desse mtodo de traduo a otimizao da eficin-
cia na execuo dos programas. A execuo rpida porque no se ne-
Compilao Interpretao Hbrido
Fonte Fonte Fonte LP LP LP
Execuo
L.M.
L.M.
L.M.
Compilao
Compilao
Interpretao
Cdigo
Intermedirio
Interpretao
Execuo
Execuo
Dados
Dados
Dados
17
cessita fazer qualquer traduo durante a execuo e porque boa parte das
verificaes de erros j podem ser efetuadas durante a traduo. Alm
disso, o prprio tradutor tem mais liberdade para realizar otimizaes na
gerao do cdigo executvel, uma vez que pode considerar o cdigo fon-
te globalmente.
Uma outra vantagem do processo de compilao requerer apenas o c-
digo executvel para que o programa possa ser executado. Assim, no
necessrio possuir o cdigo fonte do programa para sua execuo.
A principal desvantagem do mtodo de compilao a no portabilidade
do cdigo executvel para mquinas com arquitetura diferenciada daquela
na qual ele foi compilado. Por exemplo, um programa em C compilado
sobre o ambiente Linux no executado sobre o ambiente Windows e
vice-versa.
Outro problema se refere depurao. Uma vez que o cdigo executvel
normalmente no guarda referncias ao texto do cdigo fonte, qualquer
indicao de erro de execuo no pode ser devidamente identificada com
a informao do nome da varivel envolvida no erro ou da linha corres-
pondente no cdigo fonte que ocasiona o erro. importante mencionar,
no entanto, que existem sistemas de desenvolvimento de programas que
permitem a depurao de programas compilados. Esses sistemas mantm
uma correspondncia entre o cdigo compilado e o cdigo fonte para po-
der realizar a execuo passo a passo do cdigo, inspeo de valores de
variveis durante a execuo e prestar informaes referentes ao cdigo
fonte sobre um eventual erro de execuo.
1.5.2 Interpretao pura
No processo de interpretao pura, um programa interpretador age como
um simulador de um computador virtual que entende as instrues da LP.
Basicamente, cada instruo do cdigo fonte traduzida para a linguagem
de mquina quando essa instruo necessita ser executada. Imediatamente
aps a traduo, o cdigo gerado executado. Deste modo, em contraste
com a compilao, a traduo e a execuo de programas interpretados
podem ser vistos como um processo nico, no existindo etapas separadas
de traduo e de execuo.
A interpretao pura apresenta como vantagens: a facilidade para prototi-
pao, visto que se pode executar comandos e partes do programa assim
que so construdos, verificando se atuam corretamente; a facilidade de
depurao, visto que as mensagens de erro podem se referir diretamente
ao cdigo fonte; e a facilidade de escrever programas mais flexveis, visto
que o interpretador da LP est presente durante a execuo (permitindo,
18
por exemplo, a execuo de trechos de programas criados, alterados ou
obtidos durante a prpria execuo).
A grande desvantagem da interpretao pura em relao compilao a
execuo muito mais lenta do programa. A razo para essa lentido de-
corre da necessidade do interpretador decodificar comandos complexos
da LP, verificar erros do programa e gerar cdigo em linguagem de m-
quina durante a prpria execuo do programa. Alm disso, enquanto na
compilao os comandos internos de uma repetio s necessitam ser tra-
duzidos e verificados uma nica vez, na interpretao pura, de modo ge-
ral, esse processo se repete para cada ciclo de execuo da repetio.
Outra desvantagem o maior consumo de espao de memria, pois de-
vem ser mantidos em memria uma tabela de smbolos, o cdigo fonte e
o prprio programa interpretador.
1.5.3 Hbrido
Processo que combina tanto a execuo eficiente quanto a portabilidade
de programas atravs da aplicao combinada dos dois mtodos anterio-
res. A base para o mtodo hbrido a existncia de um cdigo intermedi-
rio, mais fcil de ser interpretado e, ao mesmo tempo, no especfico de
uma plataforma computacional. O mtodo hbrido dividido, portanto,
em duas etapas: compilao para um cdigo intermedirio e interpretao
deste cdigo.
Embora ainda de execuo mais lenta que o cdigo compilado, a interpre-
tao do cdigo intermedirio muito mais rpida que a interpretao
pura do cdigo fonte porque as instrues do cdigo intermedirio so
muito mais simples que as do cdigo fonte e porque a maior parte das ve-
rificaes de erro realizada j na etapa de compilao.
Por sua vez, como o cdigo intermedirio no especfico para uma pla-
taforma, os programas j compilados para este cdigo podem ser portados
para as mais diferentes plataformas sem necessidade de adaptao ou
mesmo recompilao, bastando que exista um interpretador do cdigo
intermedirio instalado na plataforma onde se deseja executar o progra-
ma.
JAVA adota o mtodo hbrido. O cdigo intermedirio chamado de by-
tecode. O interpretador de bytecode a JVM (JAVA Virtual Machine).
Cada plataforma computacional necessita possuir a sua prpria JVM para
que o programa em bytecode possa ser executado.
19
1.6 Paradigmas de LPs
D-se o nome de paradigma a um conjunto de caractersticas que servem
para categorizar um grupo de linguagens. Existem diversas classificaes
de paradigmas de LPs, sendo a mais comum a que divide os paradigmas
de LPs nos paradigmas imperativo, orientado a objetos, funcional e lgi-
co. A classificao utilizada aqui adapta a proposta apresentada por Ap-
pleby [APPLEBY, 1991]. As nicas alteraes realizadas nessa classifi-
cao so a substituio do termo distribudo pelo termo, mais genrico,
concorrente e a remoo do paradigma de linguagens de bancos de dados.
A figura 1.2 ilustra a classificao adotada aqui:









Figura 1. 2 - Paradigmas de LPs
Nessa classificao os paradigmas so subdivididos em duas categorias
principais: imperativo e declarativo.
1.6.1 Paradigma Imperativo
O paradigma imperativo engloba os paradigmas baseados na idia de
computao como um processo que realiza mudanas de estados. Nesse
sentido, um estado representa uma configurao qualquer da memria do
computador. Programas de LPs que so includas nesse paradigma especi-
ficam como uma computao realizada atravs de uma sequncia de
alteraes no estado da memria do computador.
O foco dos programas no paradigma imperativo se encontra em especifi-
car como um processamento deve ser feito no computador. Os conceitos
fundamentais so de varivel, valor e atribuio. Variveis so vistas co-
mo sendo um conjunto de clulas de memria. Elas possuem um valor
associado em um determinado instante do processamento e podem ter seu
valor modificado atravs de operaes de atribuio.
Paradigmas
Imperativo Declarativo
Estruturado Orientado
a Objetos
Concorrente Funcional Lgico
20
O paradigma imperativo subdividido nos paradigmas estruturado, orien-
tado a objetos e concorrente.
1.6.1.1 O Paradigma Estruturado
As primeiras LPs foram fortemente influenciadas pela programao em
linguagem de mquina. Esse tipo de programao se caracterizava pela
existncia de uma sequncia monoltica de comandos e pelo uso de des-
vios condicionais e incondicionais para determinar o fluxo de controle da
execuo do programa.
Logo se percebeu que esse estilo de programao estimulava a ocorrncia
de erros e reduzia a produtividade do programador. Para contornar essa
dificuldade surgiu a programao estruturada. Esse tipo de programao
se baseia na idia de desenvolvimento de programas por refinamentos
sucessivos (top-down). A programao estruturada consegue organizar
o fluxo de controle de execuo dos programas desestimulando o uso de
comandos de desvio incondicional e incentivando a diviso dos progra-
mas em subprogramas e em blocos aninhados de comandos. PASCAL e
C so linguagens que adotam o paradigma estruturado.
1.6.1.2 O Paradigma Orientado a Objetos
Com o avano da computao, os sistemas de software tm se tornado
cada vez maiores e mais complexos. O paradigma orientado a objetos o-
ferece conceitos que objetivam tornar mais rpido e confivel o desen-
volvimento desses sistemas.
Enquanto as linguagens que adotam o paradigma estruturado enfocam as
abstraes de controle de execuo dos programas, as linguagens que a-
dotam o paradigma orientado a objetos enfocam as abstraes de dados
como elemento bsico de programao. Classes so abstraes que defi-
nem uma estrutura de dados e um conjunto de operaes que podem ser
realizadas sobre elas, chamadas mtodos. Objetos so instncias de clas-
ses. Outros conceitos importantes nesse paradigma so a herana e o po-
limorfismo.
Por utilizarem os conceitos do paradigma estruturado na especificao
dos mtodos, o paradigma orientado a objetos pode ser considerado uma
evoluo do paradigma estruturado. SMALLTALK, C++ e JAVA so
linguagens que adotam o paradigma orientado a objetos.
21
1.6.1.3 O Paradigma Concorrente
A programao concorrente ocorre quando vrios processos executam
simultaneamente e concorrem por recursos. Sistemas concorrentes tm se
tornado cada vez mais usados. Eles podem utilizar uma nica unidade de
processamento ou vrias unidades em paralelo. Nesse ltimo caso as uni-
dades de processamento podem estar localizadas em um mesmo compu-
tador ou distribudas entre vrios. Sistemas concorrentes tambm podem
compartilhar dados ou dispositivos perifricos.
O paradigma concorrente engloba linguagens que oferecem facilidades
para o desenvolvimento desses sistemas. ADA provavelmente a lingua-
gem mais conhecida que oferece suporte a concorrncia.
1.6.2 Paradigma Declarativo
Em contraste com o paradigma imperativo, no qual os programas so es-
pecificaes de como o computador deve realizar uma tarefa, no para-
digma declarativo os programas so especificaes sobre o que esta ta-
refa. No paradigma declarativo, o programador no precisa se preocupar
sobre como o computador implementado, nem sobre a maneira pela
qual ele melhor utilizado para realizar uma tarefa. A preocupao do
programador em descrever de forma abstrata a tarefa a ser resolvida.
Tipicamente, programas em linguagens declarativas so especificaes de
relaes ou funes. No existem atribuies a variveis dos programas
uma vez que as variveis declarativas so de fato incgnitas e no repre-
sentam clulas de memria.
Os interpretadores ou compiladores das LPs declarativas gerenciam a
memria do computador, tornando transparente para o programador a ne-
cessidade de alocao e desalocao de memria.
1.6.2.1 O Paradigma Funcional
Linguagens funcionais operam apenas sobre funes, as quais recebem
listas de valores e retornam um valor. O objetivo da programao funcio-
nal definir uma funo que retorne um valor como a resposta do pro-
blema. Um programa funcional uma chamada de funo que normal-
mente chama outras funes para gerar um valor de retorno. As principais
operaes nesse tipo de programao so a composio de funes e a
chamada recursiva de funes. Outra caracterstica importante que fun-
es so valores de primeira classe que podem ser passados para outras
22
funes. LISP, HASKELL e ML
1.2
so exemplos de linguagens funcio-
nais.
1.6.2.2 O Paradigma Lgico
Linguagens lgicas so normalmente baseadas em um subconjunto do
clculo de predicados. Um predicado define uma relao entre fatos ou
entre variveis. Um programa lgico composto por clusulas que defi-
nem predicados e relaes factuais. A caracterstica diferencial do para-
digma lgico que a execuo dos programas corresponde a um processo
de deduo automtica.
Assim, quando uma questo formulada, um mecanismo de inferncia
tenta deduzir novos fatos a partir dos existentes para verificar a veracida-
de da questo. PROLOG o exemplo mais conhecido de linguagem que
adota o paradigma lgico.
1.7 Evoluo das LPs
Antes do surgimento das LPs, a programao de computadores era feita
exclusivamente em linguagem de mquina. Programadores tinham de co-
nhecer profundamente a arquitetura da mquina onde o programa seria
executado, seu conjunto de instrues e sua forma de funcionamento.
Mesmo dominando todo esse conhecimento, a atividade de programao
era pouco produtiva porque as instrues das linguagens de mquina so
muito simples.
As primeiras LPs surgiram no final dos anos 50 e incio dos anos 60 para
facilitar o trabalho de programao. Por conta da cultura de programao
dessa poca e da limitao de recursos dos computadores, essas lingua-
gens foram fortemente influenciadas pelas linguagens de mquina e pela
arquitetura de Von Neumman dos computadores. A eficincia computa-
cional era o foco principal das LPs porque os recursos como memria e
processadores eram escassos. FORTRAN e COBOL so exemplos de lin-
guagens que surgiram nessa poca.
Na medida que os recursos computacionais se desenvolviam, os compu-
tadores iam se tornando mais poderosos e teis. Novamente, a atividade
de programao se tornava um gargalo para a disseminao dos sistemas
computacionais. No final dos anos 60, as LPs passaram a enfocar a efici-
ncia na produtividade dos programadores. Surgiram as LPs que enfati-
zavam a programao estruturada. PASCAL e C so exemplos de lingua-
gens que surgiram nessa poca.

1.2
ML no considerada uma linguagem puramente funcional. Ela tambm possui caractersticas de
uma linguagem imperativa.
23
Com o aumento da complexidade dos sistemas computacionais, uma nova
tcnica de programao passou a ser o foco das LPs no final dos anos 70
e incio dos anos 80 - a abstrao de dados. Essas LPs enfocavam a cons-
truo modularizada de programas e bibliotecas e o conceito de tipos abs-
tratos de dados. MODULA-2 e ADA so exemplos de linguagens que
surgiram nessa poca.
Durante os anos 80 e 90 houve uma vasta disseminao do uso de compu-
tadores pessoais e das estaes de trabalho. Surge a indstria do software
e com ela a necessidade de se produzir e atualizar software rapidamente.
O reuso passa a ser um conceito central para a produtividade no desen-
volvimento de software. Para atender esse requisito so desenvolvidas as
LPs orientadas a objetos. SMALLTALK, EIFFEL, C++ e JAVA so e-
xemplos de linguagens que surgiram nessa poca.
importante dizer que muitas linguagens foram incorporando novas ca-
ractersticas na medida que se constatava a sua necessidade. Assim, ver-
ses atuais de FORTRAN e COBOL, por exemplo, j incorporam os con-
ceitos de programao estruturada.
Cabe dizer ainda que as linguagens declarativas evoluram em paralelo
com as imperativas. LISP surgiu no final dos anos 50 e PROLOG no in-
cio dos anos 70. O maior interesse no desenvolvimento dessas linguagens
tem sido demonstrado no meio acadmico, em particular, nas reas de
pesquisa sobre Linguagens de Programao e Inteligncia Artificial.
Apresenta-se a seguir uma breve descrio da origem e principais caracte-
rsticas de algumas das LPs mais conhecidas.
1.7.1 Origem de LPs
FORTRAN (1957): Desenvolvida inicialmente por Backus para com-
putadores IBM. Destinou-se a aplicaes numrico-cientficas (carac-
terizadas por poucos dados e muita computao). Enfatizava eficincia
computacional (por exemplo, no havia alocao dinmica de mem-
ria). No enfocava eficincia dos programadores (por exemplo, as es-
truturas de controle eram todas baseadas no comando GOTO). Verses
atuais de FORTRAN incorporaram avanos das outras LPs.

LISP (1959): Criada por John McCarthy no MIT. Adota o paradigma
funcional. Apropriada para processamento simblico. Ainda hoje a
LP mais usada na Inteligncia Artificial. COMMON LISP e SCHEME
so dialetos.

24
ALGOL (1960): Criada por um comit de especialistas. Primeira LP
com sintaxe formalmente definida. Importncia terica enorme, tendo
influenciado todas as LPs imperativas subsequentes, embora ela pr-
pria no tenha sido muito usada (at hoje se usa o termo ALGOL-
like).

COBOL (1960): Criada por comit de especialistas. Primeira LP en-
comendada pelo Departamento de Defesa Americano (DoD). Destina-
da para aplicaes comerciais (caracterizada por muitos dados e pouca
computao). Tentou enfatizar legibilidade (LP mais prxima do in-
gls), mas acabou comprometendo redigibilidade.

BASIC (1964): Criada por Kemeny e Kurtz na Universidade de Dar-
mouth. Objetivava ser de fcil aprendizado para uso por estudantes de
artes e cincias humanas.

PASCAL (1971): Criada por Niklaus Wirth. Foi projetada para ser
usada no ensino de programao estruturada. Enfocou a simplicidade.

C (1972): Criada por Dennis Ritchie no Bell Labs. Projetada para ser
usada no desenvolvimento de sistemas de programao (em particular,
para a implementao do sistema operacional UNIX).

PROLOG (1972) - Criada por Comerauer e Roussel, na Universida-
de de Aix-Marseille, com o auxlio de Kowalski, da Universidade de
Edinburgo. Adota o paradigma lgico, sendo bastante usada em Inteli-
gncia Artificial.

SMALLTALK (1972): Criada por Alan Key e Adele Goldberg no
Xerox PARC. Primeira LP totalmente orientada a objetos. O ambiente
de programao de SMALLTALK introduziu o conceito de interfaces
grficas com o usurio que hoje amplamente utilizado.

ADA (1983): Criada pela empresa Cii-Honeywell Bull, liderada pelo
francs Jean Ichbiah, vencedora de licitao promovida pelo DoD para
atender demanda de uma linguagem de programao de alto-nvel
padronizada. Demandou o maior esforo para o desenvolvimento de
uma LP, envolvendo centenas de pessoas durante oito anos. LP muito
grande e complexa. Apropriada para programao concorrente e sis-
temas de tempo real.

25
C++ (1985): Criada por Bjarne Stroustrup no Bell Labs. Projetada pa-
ra ser uma extenso de C com orientao a objetos. Tinha como requi-
sito no implicar em perda de eficincia em relao ao cdigo em C.
Responsvel pela rpida aceitao da orientao a objetos. Se tornou
uma LP muito complexa.

JAVA (1995): Criada pela SUN para ser usada na construo de soft-
wares para sistemas de controle embutido (tais como eletrodomsti-
cos), mas acabou no sendo usada para este fim. Baseou-se fortemente
em C++, mas bem mais simples. uma LP orientada a objetos. No
utiliza explicitamente o conceito de ponteiros e foi projetada para en-
fatizar a portabilidade. Tem se tornado amplamente utilizada por causa
da sua confiabilidade e portabilidade, pelo advento da INTERNET e
porque os programadores de C e C++ a aprendem facilmente.
1.8 Consideraes Finais
Nesse captulo foram apresentados diversos temas importantes para o en-
tendimento dos conceitos discutidos no resto desse livro. Em particular,
importante ter compreendido como cada uma das propriedades apresenta-
das na seo 1.3 podem influenciar o projeto, implementao e uso das
LPs. Ter uma boa noo sobre como LPs podem ser especificadas e im-
plementadas tambm contribui para a compreenso de diversos tpicos
subseqentes.
Por fim, vale repetir que o foco desse livro ser na discusso das LPs que
se enquadram sobre o paradigma imperativo, isto , linguagens que ado-
tam o paradigma estruturado, orientado a objetos ou concorrente. Vale
ressaltar tambm que os exemplos sero dados primordialmente nas lin-
guagens C, C++ e JAVA.
1.9 Exerccios
1. Identifique problemas de legibilidade e redigibilidade nas LPs que co-
nhece. Verifique se existem casos nos quais essas propriedades so
conflitantes.

2. Identifique problemas de confiabilidade e eficincia nas LPs que
conhece. Verifique se existem casos nos quais essas propriedades so
conflitantes.

3. Identifique problemas de falta de ortogonalidade nas LPs que conhece.
Esses problemas comprometem a facilidade de aprendizado da LP?

26
4. Reusabilidade e modificabilidade muitas vezes contribuem para a me-
lhoria uma da outra. D exemplos de situaes nas quais isso ocorre.

5. Identifique situaes nas quais a busca por eficincia computacional
compromete a portabilidade de LPs e vice-versa.

6. Uma LP sempre pode ser implementada usando tanto o mtodo de
compilao quanto o de interpretao? Em caso positivo, discuta se
existem LPs que se ajustam melhor a um mtodo de implementao
do que a outro. Em caso negativo, apresente um exemplo de uma LP
na qual s se pode utilizar um mtodo de implementao e justifique.

7. Faa uma anlise lxica, sinttica e semntica das seguintes linhas de
cdigo C e descreva quais as concluses obtidas:
int a, i;
int b = 2, c =3;
a = (b + c) * 2;
i = 1 && 2 + 3 | 4;

8. Enumere e explique quais os principais fatores que influenciaram a
evoluo das LPs imperativas.

9. Induzir a legibilidade, confiabilidade e reuso de programas so algu-
mas das propriedades desejveis em Linguagens de Programao.
Mostre, atravs de exemplos (um para cada propriedade) retirados de
linguagens de programao conhecidas, como elas podem cumprir es-
tes papis e justifique os seus exemplos.

27

Captulo II Amarraes


... se antes de cada ato nosso nos pusssemos a preer todas as conseqncias dele, a
pensar nelas a srio, primeiro as imediatas, depois as proaeis, depois as imaginarias,
nao chegaramos sequer a moer-nos de onde o primeiro pensamento nos tiesse eito
parar.`
Jos Saramago
Amarrao (binding) um conceito amplamente utilizado no estudo de
LPs. Em termos gerais, uma amarrao uma associao entre entidades
de programao, tais como entre uma varivel e seu valor ou entre um
identificador e um tipo.
Nesse captulo se discute o conceito de amarrao enfocando especial-
mente as associaes feitas entre identificadores e smbolos da LP com
entidades de programao, tais como constantes, variveis, procedimen-
tos, funes e tipos.
Inicialmente apresentam-se os diversos momentos nos quais podem ocor-
rer amarraes. Em seguida, discutem-se as propriedades relacionadas
com identificadores. Abordam-se tambm os ambientes de amarrao e as
noes de escopo das entidades de programao. Por fim, discute-se co-
mo podem ser feitas definies e declaraes dessas entidades.
2.1 Tempos de Amarrao
Existem inmeros tipos de amarraes, as quais podem ocorrer em mo-
mentos distintos. O momento no qual uma amarrao realizada co-
nhecido como tempo de amarrao. A seguir, so apresentadas descries
de diferentes tempos de amarrao juntamente com exemplos.
Tempo de Projeto da LP: Ao se projetar uma LP necessrio de-
finir os smbolos e alguns identificadores que podero ser usados
para a construo de programas, bem como amarr-los s entidades
que representam. Por exemplo, a escolha do smbolo * para denotar
a operao de multiplicao em C foi feita durante o projeto da lin-
guagem.
Tempo de Implementao do Tradutor: Algumas amarraes
so efetuadas no momento em que se implementa o software res-
ponsvel por traduzir o cdigo da LP (em geral, o compilador). Por
exemplo, a definio do intervalo de inteiros associado ao tipo int
de C realizada durante a implementao do compilador. Isso sig-
28
nifica que diferentes compiladores podem adotar diferentes interva-
los para o tipo int.
Tempo de Compilao: Um grande nmero de amarraes ocorre
no momento em que o programa compilado. So exemplos desse
tipo de amarrao em C a associao de uma varivel a um tipo de
dados e a associao, em uma expresso do programa, do operador
* operao que denota.
Tempo de Ligao: Amarraes tambm ocorrem no momento em
que vrios mdulos previamente compilados necessitam ser inte-
grados (ou, no termo mais usado, ligados) para formar um progra-
ma executvel. Por exemplo, a amarrao entre a chamada de uma
funo da biblioteca padro de C (tal como, printf) e o cdigo
compilado correspondente a essa funo realizado em tempo de
ligao.
Tempo de Carga: Outro momento onde ocorrem amarraes du-
rante o carregamento do programa executvel na memria do com-
putador. Nesse momento, so associadas reas de memria s vari-
veis globais e constantes que sero usados pelo programa, assim
como so substitudas vrias referncias no cdigo executvel por
endereos absolutos de memria.
Tempo de Execuo: Outro grande nmero de amarraes ocorre
durante a prpria execuo do programa. Exemplos desse tipo de
amarrao so a associao de um valor a uma varivel ou a asso-
ciao de reas de memria s variveis locais de uma funo em
C.
Costuma-se afirmar tambm que uma amarrao esttica se ela ocorre
antes da execuo do programa e permanece inalterada ao longo de toda a
execuo. J se a amarrao ocorre ou alterada durante a execuo do
programa, ela chamada de amarrao dinmica.
O entendimento sobre amarraes e seus respectivos tempos colabora
muito para o entendimento da semntica de LPs. Como durante a pro-
gramao s se realizam amarraes entre identificadores e entidades de
computao, vamos nos concentrar nelas a partir de agora. Comearemos
discutindo identificadores, abordaremos ambientes de amarrao e con-
cluiremos estudando declaraes e definies.
2.2 Identificadores
Identificadores so cadeias de caracteres definidos pelos programadores
para servirem de referncia s entidades de computao. A escolha apro-
29
priada de identificadores e smbolos facilita o entendimento dos progra-
mas.
O uso de identificadores tambm possibilita definir uma entidade em um
ponto do programa e posteriormente utilizar aquele identificador para se
referir quela entidade em vrios outros lugares. Alm de aumentar a re-
digibilidade dos programas, isso faz com que o programa seja mais facil-
mente modificvel, uma vez que, se a implementao da entidade deve
ser alterada, a mudana afeta apenas a parte do programa na qual ela foi
amarrada ao identificador e no as partes nas quais o identificador foi u-
sado.
A sintaxe para formao de identificadores em LPs pode variar. Uma
forma comum apresentada a seguir (em BNF):
<identificador> ::= <letra> | <letra><sequencia>
<sequencia> ::= <caracterid> | <caracterid><sequencia>
<caracterid> ::= <letra> | <digito> | <sublinha>
Exemplo 2. 1 - Regras Sintticas para Formao de Identificadores
Algumas LPs limitam o nmero mximo de caracteres que podem ser uti-
lizados. Outras no impem limites ou permitem que se criem nomes com
tamanho ilimitado, mas fazem distines apenas at um nmero determi-
nado de caracteres. Algumas LPs definem o limite na sua definio, en-
quanto outras deixam para o implementador do compilador ou interpreta-
dor da LP definir o tamanho mximo.
LPs podem ser case sensitive, isto , podem fazer distines entre
identificadores escritos com letras maisculas e minsculas (C, MODU-
LA-2, C++ e JAVA) ou no (PASCAL). As LPs que adotam a abordagem
no sensitiva permitem que uma mesma entidade seja referenciada pelo
mesmo nome escrito de vrias maneiras distintas. Isto tende a provocar
programas menos legveis, visto que o programador pode abusar do uso
das variaes, dificultando o reconhecimento da amarrao entre o identi-
ficador e a entidade que referencia. Por outro lado, as LPs que adotam a
abordagem sensitiva permitem que um mesmo nome identifique vrias
entidades distintas, podendo gerar confuses no entendimento do progra-
ma, alm de forar o programador a lembrar como descreveu a entidade
em termos do formato de seu identificador.
Identificadores devem ser formados por nomes significativos, isto , de-
vem prover informao a respeito do significado das entidades que deno-
tam. Em particular, identificadores devem refletir o significado das enti-
dades do domnio do problema e no da forma como elas so implemen-
tadas. Por exemplo, sempre melhor definir um identificador chamado
30
palavra do que um chamado lista_de_caracteres, mesmo que o identifi-
cador esteja associado a uma entidade implementada dessa forma.
Identificadores que sejam visveis ao longo de partes substanciais do pro-
grama, e que no sejam muito usados, devem ter significado bvio e po-
dem ser relativamente longos. Identificadores visveis apenas em peque-
nos trechos do programa, mas muito usados, podem ser curtos. Tipica-
mente, nesses casos, deve-se usar abreviaes ou acrnimos ou nomes
convencionais, tais como, i, j e p.
Em geral, deve-se evitar formar identificadores que se diferem de forma
sutil, como por exemplo, identificadores que se diferem apenas pela escri-
ta em letra maiscula e minscula ou que se diferem apenas pelo uso da
letra o maiscula e o dgito zero. Alm disso, importante tentar manter
um estilo consistente na formao de identificadores, embora isso nem
sempre seja possvel, uma vez que programas so frequentemente com-
postos por fragmentos de diferentes origens.
2.2.1 Identificadores Especiais
Alguns identificadores podem ter significado especial para a LP [SE-
BESTA, 1998]. Alguns podem ser vocbulos reservados, isto , so
smbolos da LP que no podem ser usados pelo programador para a
criao de identificadores de entidades. Os identificadores int, char, float,
if, break de C so exemplos de vocbulos reservados.
Outros identificadores podem ser vocbulos chave, isto , s so smbolos
da LP quando usados em um determinado contexto. Esse tipo de identifi-
cador pode ser muito ruim para a legibilidade de programas. O vocbulo
INTEGER um vocbulo chave em FORTRAN (veja o exemplo 2.2).
INTEGER R
INTEGER = 7
Exemplo 2. 2 - Vocbulos Chave
Na primeira linha do exemplo 2.2, o identificador INTEGER usado para
declarar a varivel R como do tipo inteiro. J na segunda linha, esse
mesmo identificador denota uma varivel.
Por fim, identificadores podem ser vocbulos pr-definidos, isto , tem
significados pr-definidos, mas podem ser redefinidos pelo programador.
Por exemplo, as funes fopen e fclose so exemplos de identificadores
pr-definidos na biblioteca padro de C, mas que podem ser redefinidos
pelo programador.
31
2.3 Ambientes de Amarrao
A interpretao de comandos e expresses, tais como a = 5 ou g(a + 1),
depende do que denotam os identificadores utilizados nesses comandos e
expresses. A maioria das LPs permite que um mesmo identificador seja
declarado em vrias partes do programa denotando, presumivelmente,
diferentes entidades.
O conceito de ambiente (environment) utilizado para determinar o
que os identificadores denotam ao longo do programa. Um ambiente cor-
responde a um conjunto de amarraes. Cada expresso e comando in-
terpretado num determinado ambiente e todos os identificadores que o-
correm devem ter amarraes nesse ambiente.
Expresses e comandos idnticos em diferentes partes de um programa
podem ser interpretados diferentemente se seus ambientes so distintos.
Por outro lado, em geral, s permitida uma amarrao por identificador
dentro de um determinado ambiente. Uma exceo a essa ltima regra
ocorre em C++, como ilustrado no exemplo 2.3.
int a = 13;
void f() {
int b = a;
int a = 2;
b = b + a;
}
Exemplo 2. 3 - Amarrao de Identificador a Duas Entidades Distintas no Mesmo Ambiente
Enquanto o identificador a na primeira linha da funo f do exemplo 2.3
designa a varivel global, esse mesmo identificador nas segunda e terceira
linhas designa a varivel local.
Apenas em uma LP muito elementar todas amarraes afetam o ambiente
de todo o programa. Em geral, uma amarrao tem um determinado esco-
po de visibilidade, isto , a regio do programa onde a entidade amarrada
visvel. O escopo de visibilidade de uma LP pode ser esttico ou din-
mico.
No escopo esttico, o ambiente de amarrao determinado pela organi-
zao textual do programa. Assim, de maneira geral, as entidades de
computao so amarradas em tempo de compilao.
No escopo dinmico, o ambiente de amarrao determinado em funo
da sequncia de ativao (chamada) dos mdulos do programa, a qual s
determinada em tempo de execuo. Em outras palavras, o fluxo de
controle do programa que determina as amarraes s entidades de
computao.
32
2.3.1 Escopo Esttico
O conceito de bloco fundamental para o entendimento do escopo estti-
co. Um bloco delimita o escopo de qualquer amarrao que ele possa con-
ter. Normalmente, um bloco um subprograma ou um trecho de cdigo
delimitado atravs de marcadores, tais como, as chaves ({ e }) de C, C++
e JAVA ou os vocbulos begin e end introduzidos por ALGOL-60 e
adotados por PASCAL e ADA.
A estrutura de blocos de uma LP a relao textual entre blocos. A figura
2.1 ilustra uma classificao dos tipos de estruturas, tal como apresentada
por Watt [WATT, 1990]:










Figura 2. 1 - Estruturas de Blocos (adaptada de Watt [WATT, 1990])
Na estrutura monoltica todo o programa composto por um nico bloco.
Todas as amarraes tm como escopo de visibilidade o programa inteiro.
Essa estrutura de blocos a mais elementar possvel e no apropriada
para programas grandes, uma vez que todas as amarraes de identifica-
dores devem ser agrupadas num mesmo lugar. Isso faz com que o pro-
gramador tenha de interromper frequentemente a anlise de trechos do
programa e desviar sua ateno para o lugar onde pode consultar o signi-
ficado dos identificadores usados nesses trechos. Mais ainda, isso dificul-
ta o trabalho simultneo de vrios programadores em um mesmo progra-
ma visto que os identificadores criados por um deles devem ser necessari-
amente distintos dos identificadores criados pelos outros. Verses antigas
de BASIC e COBOL adotam essa estrutura.
A estrutura de blocos no aninhada considerada um avano em relao
estrutura de blocos monoltica, uma vez que o programa dividido em
vrios blocos. Nessa estrutura, o escopo de visibilidade dos identificado-
res o bloco onde foram criados. Esses identificadores so chamados de
locais. Os identificadores criados fora do bloco so chamados de globais,
uma vez que seu escopo de visibilidade todo o programa. Uma desvan-
tagem associada estrutura de blocos no aninhada que qualquer identi-
x x
y
z
w
x
y
z
w
x
Bloco Monoltico Blocos No Aninhados Blocos Aninhados
33
ficador que no pode ser local forado a ser global e ter todo o progra-
ma como escopo, mesmo que seja acessado por poucos blocos. Outra
desvantagem a exigncia de que todos os identificadores globais tenham
identificadores distintos. FORTRAN adota esse tipo de estrutura. Todos
subprogramas so separados e cada um atua como um bloco.
A estrutura aninhada considerada um avano ainda maior. LPs como
PASCAL, MODULA-2 e ADA adotam essa estrutura. So, por isso,
chamadas LPs ALGOL-like uma vez que foi ALGOL a primeira lin-
guagem a utiliz-la. Qualquer bloco pode ser aninhado dentro de outro
bloco e localizado em qualquer lugar que seja conveniente. Identificado-
res podem ser amarrados dentro de cada bloco. Normalmente, para des-
cobrir qual entidade est amarrada a um identificador, deve-se procurar a
declarao da entidade no bloco onde usada. Se no a encontrar, deve-
se procurar no bloco mais externo imediato e assim por diante.
Entidades podem se tornar inacessveis quando se usa o mesmo identifi-
cador para denotar diferentes entidades em blocos aninhados. Isso ocorre
em C, como mostra o exemplo 2.4.
main() {
int i = 0, x = 10;
while (i++ < 100) {
float x = 3.231;
printf(x = %f\n, x*i);
}
}
Exemplo 2. 4 - Ocultamento de Entidade em Blocos Aninhados
No exemplo 2.4, a varivel x inteira criada no bloco mais externo no
visvel dentro do bloco mais interno porque neste bloco foi criada uma
outra varivel de tipo ponto flutuante com o mesmo identificador x.
Com o intuito de evitar confuses, JAVA no permite que um mesmo
identificador seja utilizado para designar entidades distintas em blocos
aninhados. J em algumas LPs, como ADA, permitido usar o nome do
bloco para acessar a entidade que fica oculta quando outra entidade as-
sociada ao mesmo identificador em um bloco aninhado interno. Isso
chamado de referncia seletiva. O exemplo 2.5 ilustra essa situao.
procedure A is
x : integer;
procedure B is
y : integer;
procedure C is
x : integer;
34
begin
x := A.x;
end C;
begin
null;
end B;
begin
null;
end A;
Exemplo 2. 5 - Referncia Seletiva em ADA
No exemplo 2.5, a referncia seletiva A.x dentro do bloco C permite que a
varivel x do bloco A seja acessada. Observe que a referncia x dentro de
C designa a varivel x do prprio bloco C, como seria de se esperar.
Em certas situaes, contudo, a estrutura aninhada pode requerer que uma
varivel seja declarada globalmente, embora seja usada por poucos blo-
cos.







Figura 2. 2 - Aninhamento de Blocos
Na figura 2.2.a, o bloco D deve ser repetido dentro dos blocos A e C para
que ele possa ser usado nica e exclusivamente por esses dois blocos.
Uma alternativa para a no repetio do cdigo seria elevar o bloco D
para o mesmo nvel de amarrao de A e C (ver figura 2.2.b). Nesse caso,
D seria visvel por esses blocos. Contudo, D tambm passaria a ser visvel
por P e B, o que pode no ser interessante. Outra opo seria permitir de-
clarar que um bloco faz uso de outro bloco criado dentro de um terceiro.
Por exemplo, poder-se-ia declarar que o bloco A utiliza o bloco D criado
dentro do bloco C.
C utiliza uma abordagem mista na qual os blocos definidos por funes
adotam uma estrutura no aninhada e os blocos internos s funes ado-
tam uma estrutura aninhada. Nesse caso, o que pode ser reusado (as fun-
es) ou compartilhado (as variveis globais) se torna visvel para todos.
P
A B C
D E D
P
A B C
E
D
a b
35
J o que no pode ser reusado (os blocos internos) so aninhados. Obser-
ve como isso feito no exemplo 2.6.
int x = 10;
int y = 15;
void f() {
if (y x) {
int z = x + y;
}
}
void g() {
int w;
w = x;
}
main() {
f();
x = x + 3;
g();
}
Exemplo 2. 6 - Estrutura de Blocos de C
Observe no exemplo 2.6 que qualquer funo pode usar a varivel global
x e chamar qualquer outra funo. Observe tambm que dentro do bloco
definido na funo f existe um bloco interno aninhado associado ao co-
mando if. Observe que mesmo nessa abordagem, mais uma vez, as vari-
veis globais e funes se tornam visveis para blocos que no as utilizam.
Pelas regras de escopo de C, a funo g teria permisso para utilizar a va-
rivel global y e chamar a funo f.
2.3.2 Escopo Dinmico
No escopo dinmico as entidades so amarradas aos identificadores de
acordo com o fluxo de controle do programa. APL, SNOBOL4 e verses
iniciais de LISP adotam este tipo de escopo. Considere o exemplo 2.7,
escrito numa LP hipottica:
procedimento sub() {
inteiro x = 1;
procedimento sub1() {
escreva( x);
}
procedimento sub2() {
inteiro x = 3;
sub1();
}
36
sub2();
sub1();
}
Exemplo 2. 7 - Escopo Dinmico
Quando sub chama sub2 e este chama sub1, o valor escrito de x por sub1
3, isto , x uma referncia varivel criada em sub2. Quando sub
chama sub1 diretamente, o valor escrito de x por sub1 1, isto , x uma
referncia varivel criada em sub.
LPs que adotam o escopo dinmico apresentam os seguintes problemas:
1. perda de eficincia pois a checagem de tipos tem de ser feita du-
rante a execuo;
2. legibilidade do programa reduzida pois a sequncia de chama-
das de subprogramas deve ser conhecida para determinar o sig-
nificado das referncias a variveis no locais;
3. acesso menos eficiente s variveis porque necessrio seguir a
cadeia de chamadas de subprogramas para identificar as refe-
rncias no locais;
4. confiabilidade do programa reduzida pois variveis locais po-
dem ser acessadas por qualquer subprograma chamado subse-
quentemente no processo.
Como consequncia desses problemas, a maioria das LPs atuais no ado-
tam a abordagem de escopo dinmico.
2.4 Definies e Declaraes
Definies e declaraes so frases de programa elaboradas para produzir
amarraes. Definies produzem amarraes entre identificadores e en-
tidades criadas na prpria definio. Declaraes produzem amarraes
entre identificadores e entidades j criadas ou que ainda o sero.
Algumas LPs, tais como C, C++ e JAVA, permitem que sejam feitas de-
claraes e definies dentro de blocos. Enquanto C requer que elas se-
jam feitas imediatamente aps o marcador ( { ) de incio do bloco e antes
de qualquer comando executvel, C++ e JAVA permitem que elas sejam
feitas em qualquer ponto do bloco. J PASCAL no permite que sejam
feitas declaraes e definies dentro dos blocos internos, apenas na rea
destinada a amarraes no programa e nos subprogramas. O exemplo 2.8
mostra uma funo f em C++ na qual so definidas as variveis a e b.
void f() {
int a = 1;
a = a + 3;
int b = 0;
37
b = b + a;
}
Exemplo 2. 8 - Localizao de Definies de Variveis em C++
Observe que a varivel b definida aps a realizao do comando de atri-
buio varivel a. Note que um compilador de C acusaria erro ao com-
pilar essa funo, uma vez que em C todas as amarraes devem ocorrer
no incio do bloco.
2.4.1 Declaraes de Constantes
Uma declarao de constante amarra um identificador a um valor pr-
existente que no pode ser alterado ao longo da execuo do programa.
Isso pode ser feito em C++ tal como na seguinte linha de cdigo:
const float pi = 3.14;
Nessa linha criada uma constante de nome pi. Essa mesma declarao
vlida em C. Contudo, a definio de C s requer que os compiladores
avisem ao programador de tentativas de alteraes de constantes, permi-
tindo assim que eles ignorem a definio e aceitem alteraes de valores
das constantes!!! Por essa razo, programadores C continuam utilizando o
mecanismo tradicional de macros quando querem criar constantes em C:
#define pi 3.14
Com esse mecanismo todas as referncias a pi no cdigo sero substitu-
das por 3.14, antes do incio da compilao, tendo efeito equivalente a
definio de pi como constante.
No entanto, pode ser vantajoso usar const ao invs de define em C porque
esse ltimo mecanismo no reconhece regras de escopo (a constante ser
reconhecida do ponto de declarao at o final do programa). Em caso de
uso de const, a constante s ser reconhecida dentro do escopo de visibi-
lidade definido pelo seu ambiente de amarrao.
Algumas linguagens, como PASCAL e MODULA-2, requerem que cons-
tantes tenham seus valores definidos estaticamente (em tempo de compi-
lao). J ADA, C++ e JAVA permitem que sejam usados valores calcu-
lados dinamicamente (em tempo de execuo do programa). Essa uma
outra vantagem do uso de const em relao a define. O mecanismo de
macros somente permite a declarao de constantes estticas. JAVA utili-
za a palavra final para declarar constantes, tal como ilustrado no exemplo
2.9
2.1
.

2.1
A palavra static utilizada no exemplo 2.9 utilizada para indicar que a constante um atributo de classe (isto ,
um atributo comum para todos os objetos da classe). As declaraes sem static provocam a criao de atributos
constantes individuais para cada objeto da classe.
38
final int const1 = 9;
static final int const2 = 39;
final int const3 = (int)(Math.random()*20);
static final const4 = (int)(Math.random()*20);
Exemplo 2. 9 - Declarao de Constantes em JAVA
Enquanto as duas primeiras linhas do exemplo 2.9 ilustram a criao de
constantes estticas, as duas ltimas ilustram a criao de constantes di-
nmicas. Math.random uma funo calculada em tempo de execuo
que gera um nmero aleatrio.
JAVA tambm permite a inicializao de constantes em ponto distinto da
declarao. No exemplo 2.10, mostrada uma situao onde isso ocorre.
Nesse exemplo, a constante j inicializada no mtodo Construtor.
final int j;
Construtor () {
j = 1;
}
Exemplo 2. 10 - Inicializao de Constante em JAVA
2.4.2 Definies e Declaraes de Tipos
Uma definio de tipo amarra um identificador a um tipo criado na pr-
pria definio. As definies de struct, union, enum em C so definies
de tipo, tal como visto no exemplo 2.11.




Exemplo 2. 11 - Definies de Tipos em C
Uma declarao de tipo amarra um identificador a um tipo definido em
outro ponto do programa. A primeira linha do exemplo 2.12 (em C) indi-
ca que o identificador data est amarrado a um tipo estrutura definido em
outro ponto do programa. As duas linhas restantes mostram o uso de ty-
pedef na produo de declaraes de tipo, amarrando respectivamente os
identificadores curvatura e aniversario aos tipos union angulo e struct
data.
struct data;
typedef union angulo curvatura;
typedef struct data aniversario;
Exemplo 2. 12 - Declaraes de Tipos em C
struct data {
int d, m, a;
};
union angulo {
int graus;
float rad;
};
enum dia_util {
seg, ter, qua, qui, sex
};
39
Observe que, com o uso de typedef, no foi criado um novo tipo, ou seja,
produz-se simplesmente um novo identificador para designar tipos previ-
amente definidos. Por exemplo, possvel usar uma varivel do tipo uni-
on angulo onde se espera uma varivel do tipo curvatura e vice-versa.
2.4.3 Definies e Declaraes de Variveis
Definies de variveis so as mais comuns em LPs. Uma definio de
varivel um trecho de programa onde ocorre uma amarrao determi-
nando a criao de uma varivel, isto , a alocao de um conjunto de c-
lulas de memria e sua associao varivel definida naquele trecho. Ve-
ja alguns exemplos de definies em C:
int k; union angulo ang; struct data d;
Variveis podem ser definidas em uma mesma frase para economizar di-
gitao e espao do programa, aumentando a redigibilidade. A seguinte
linha em C mostra um exemplo de como isso pode ser feito:
int *p, i, j, k, v[10];
Observe nessa linha que so definidas trs variveis inteiras (i, j, k), uma
varivel ponteiro para inteiro (p) e um vetor de inteiros com 10 elementos
(v). Se, por um lado, construes como essa aumentam a redigibilidade
do programa, por outro lado, ao misturar a definio de variveis de dife-
rentes tipos, elas reduzem a legibilidade e podem provocar erros. Portan-
to, aconselhvel que s se faam definies de variveis de um nico
tipo em uma mesma frase do programa.
Uma vantagem de se definir variveis em frases separadas, mesmo que de
um nico tipo, permitir a insero de comentrios explicativos a respei-
to das variveis ao lado de cada definio, aumentando a legibilidade.
Uma varivel pode ser inicializada durante a sua definio. O exemplo
2.13 mostra isso sendo feito em C.
int i = 0;
char virgula = ', ';
float f, g = 3.59;
int j, k, l = 0, m=23;
Exemplo 2. 13 - Inicializao de Variveis em C Durante a Definio
As duas ltimas linhas do exemplo 2.13 mostram como combinar defini-
es em uma mesma frase nas quais algumas variveis so inicializadas
(no caso: g, l e m) e outras no (no caso: f, j e k).
Certas linguagens oferecem inicializao implcita (tambm conhecida
como inicializao default) de variveis. Em C e C++, variveis alo-
cadas estaticamente (isto , variveis globais) so inicializadas com zero
40
do tipo apropriado. Isso significa que, se as variveis definidas no exem-
plo 2.13 forem globais, as variveis j e k sero inicializadas automatica-
mente com o valor 0 e a varivel f ser inicializada com o valor 0.0. J
variveis alocadas dinamicamente (isto , variveis locais e variveis alo-
cadas explicitamente na memria livre) no so inicializadas, ou seja, tm
valores indefinidos (lixo). JAVA adota uma outra poltica. Variveis de-
finidas como atributos de classe podem ser inicializadas implicitamente e
variveis locais aos mtodos, no.
Tambm comum se permitir inicializar explicitamente variveis atravs
do uso de expresses. De modo similar ao mecanismo de inicializao
implcita, em C e C++, as variveis alocadas estaticamente s permitem a
inicializao com expresses estticas, isto , expresses que podem ter
seu valor calculado em tempo de compilao. J as variveis alocadas
dinamicamente podem ser inicializadas com expresses calculadas em
tempo de execuo. O nico requisito exigido que os valores necess-
rios para o clculo da expresso sejam conhecidos no momento da criao
da varivel. O exemplo 2.14 mostra a inicializao, em C++, de uma va-
rivel (k) atravs do uso de uma expresso dinmica.
void f(int x) {
int i;
int j = 3;
i = x + 2;
int k = i * j * x;
}
Exemplo 2. 14 - Inicializao com Uso de Expresso Dinmica
Variveis compostas tambm podem ser inicializadas em sua definio.
No caso de C e C++, os valores usados na inicializao devem ser lista-
dos em ordem para que haja correspondncia com os elementos da vari-
vel composta. A linha seguinte mostra a inicializao explcita de um ve-
tor de inteiros em C.
int v[3] = { 1, 2, 3 };
Uma declarao de varivel serve para indicar que a varivel correspon-
dente quele identificador definida em outro mdulo de cdigo ou para
amarrar um identificador a uma varivel existente. C utiliza declaraes
de variveis para indicar que uma varivel definida em um mdulo ex-
terno. Isso significa que o compilador no deve gerar cdigo para alocar
espao de memria para essa varivel (isso ser feito pelo mdulo exter-
no), mas poder utilizar a declarao para verificar o uso apropriado da
varivel externa nas operaes do programa.
extern int a;
41
C++ permite que declaraes de variveis sejam feitas amarrando um i-
dentificador a uma varivel j existente. Uma desvantagem dessa aborda-
gem permitir a produo de sinnimos (aliases), tornando mais dif-
cil entender os programas e podendo provocar erros de programao. O
exemplo 2.15 mostra uma situao na qual isso ocorre. Observe que o
incremento da varivel j implica em alterar implicitamente o valor da va-
rivel r para 11.
int r = 10;
int &j = r;
j++;
Exemplo 2. 15 - Declarao de Varivel
2.4.4 Definies e Declaraes de Subprogramas
Subprogramas se compem de cabealho e corpo. No cabealho so es-
pecificados o identificador do subprograma, sua lista de parmetros e o
tipo de retorno (se for o caso). No corpo definido o algoritmo que im-
plementa o subprograma e tambm especificado o valor de retorno (se
for o caso).
Uma definio de subprograma contm a especificao do cabealho e do
corpo do subprograma. O exemplo 2.16 ilustra um subprograma em C:
int soma (int a, int b) {
return a + b;
}
Exemplo 2. 16 - Definio de Subprograma em C
Uma declarao de subprograma contm apenas o cabealho do subpro-
grama. Ela indica que a definio do subprograma ocorre em outro trecho
do cdigo ou em outro mdulo. Declaraes de subprogramas so usadas
para permitir a verificao do uso apropriado do subprograma durante a
compilao. O exemplo 2.17 mostra o uso de uma declarao de subpro-
grama em C.
int incr (int);
void f(void) {
int k = incr(10);
}
int incr (int x) {
x++;
return x;
}
Exemplo 2. 17 - Declarao de Subprograma em C
42
A primeira linha de cdigo do exemplo 2.17 uma declarao do subpro-
grama incr. Em C, s necessrio dar nomes aos parmetros na definio
do subprograma, bastando especificar o seu tipo na declarao. Observe
que no corpo da funo f ocorre uma chamada a incr, que s definida
posteriormente. O compilador pode verificar se a chamada a incr est cor-
reta em f porque a declarao de incr foi antecipada
2.2
.
2.4.5 Composio de Definies
Definies podem ser compostas a partir de outras definies ou a partir
delas mesmas. Definies compostas podem ser sequenciais ou recursi-
vas.
2.4.5.1 Definies Sequenciais
Definies sequenciais se utilizam de outras definies estabelecidas an-
teriormente no programa. Elas permitem que as amarraes produzidas
em uma definio sejam usadas nas demais. O exemplo 2.18 mostra a o-
corrncia de definies sequenciais em um cdigo C.
struct funcionario {
char nome [30];
int matricula;
float salario;
};
struct empresa {
struct funcionario listafunc [1000];
int numfunc;
float faturamento;
};
int m = 3;
int n = m;
Exemplo 2. 18 - Definies Sequenciais em C
No exemplo 2.18, a definio do tipo struct empresa utiliza a definio
do tipo struct funcionario e a definio da varivel n utiliza a definio da
varivel m.
Definies sequenciais de subprogramas normalmente envolvem chama-
das a outros subprogramas. Contudo, algumas LPs fornecem uma forma

2.2
No caso especfico do exemplo 2.17, onde a definio do subprograma se encontra no mesmo arquivo onde o
subprograma usado, um compilador poderia dispensar a declarao de incr. Para tanto, o compilador deveria
varrer previamente o cdigo identificando todos os cabealhos de subprogramas para posteriormente realizar as
verificaes de uso.
43
menos comum de definio sequencial de subprogramas. Nessas LPs, o
lado direito da definio sequencial pode ser qualquer expresso que pro-
duza um valor subprograma. ML uma linguagem que oferece esse tipo
de recurso. O exemplo 2.19 mostra a definio sequencial das funes
impar e jogo em ML.
val par = fn (n: int) => (n mod 2 = 0)
val negacao = fn (t: bool) => if t then false else true
val impar = negacao o par
val jogo = if x < y then par else impar
Exemplo 2. 19 - Definies Sequenciais em ML
Observe que a funo impar definida a partir das funes par e nega-
cao, j definidas anteriormente. Observe tambm que a definio de im-
par no envolve a chamada dos subprogramas negacao e par. De fato, a
definio de impar dada atravs da especificao de uma expresso que
utiliza o operador de composio de funes (o) aplicado sobre os valores
par e negacao. Note, por fim, que a funo jogo definida de maneira
similar. Se no momento da criao de jogo, o valor associado a x for infe-
rior ao valor associado a y, jogo se referir funo par. Caso contrrio,
se referir funo impar.
Tais possibilidades so eliminadas em LPs onde definies de funes e
procedimentos so a nica forma de amarrao de um identificador a uma
funo ou procedimento.
2.4.5.2 Definies Recursivas
Definies recursivas so aquelas que utilizam as prprias amarraes
que produzem. Algumas verses de LPs antigas (tal como, FORTRAN e
COBOL) no suportam recurso e, como conseqncia, se enfraquecem.
As LPs mais modernas suportam recurso, em geral, restringindo-a a ti-
pos e definies de procedimentos e funes, que so, de fato, os modos
mais teis de recurso.
Idealmente, tipos recursivos devem ser oferecidos pelas LPs de maneira
indiscriminada. Contudo, em linguagens que utilizam explicitamente o
conceito de ponteiros (tal como C), a definio de tipos recursivos res-
trita, atravs de uma regra sinttica, a tipos que envolvem esses elemen-
tos. O exemplo 2.20 mostra um tipo recursivo struct lista sendo definido
em C:
struct lista {
int elemento;
struct lista * proxima;
};
44
Exemplo 2. 20 - Tipo Recursivo em C
Definies de funes tambm podem ser recursivas em C. O exemplo
2.21 mostra a definio recursiva da funo potencia em C.
float potencia (float x, int n) {
if (n == 0) then {
return 1.0;
} else if (n < 0) {
return 1.0/ potencia (x, -n);
} else {
return x * potencia (x, n - 1);
}
}
Exemplo 2. 21 - Definio Recursiva de Funo em C
Definies de funes em C tambm podem ser mutuamente recursivas.
O exemplo 2.22 mostra as definies mutuamente recursivas, em C, das
funes primeira e segunda.
void segunda (int);
void primeira (int n) {
if (n < 0) return;
segunda (n 1);
}
void segunda (int n) {
if (n < 0) return;
primeira (n 1);
}
Exemplo 2. 22 - Definies Mutuamente Recursivas em C
Existem desvantagens em tratar declaraes como automaticamente re-
cursivas. Suponha que se queira redefinir a funo strcmp de C. O pro-
gramador poderia tentar redefinir essa funo utilizando a verso original
de strcmp, tal como ilustrado no exemplo 2.23.
int strcmp (char *p, char *q) {
return !strcmp (p, q);
}
Exemplo 2. 23 - Erro em Definio de Funo strcmp em C
A funo definida no exemplo 2.23 no alcana o resultado esperado. A
chamada strcmp (p, q) ser recursiva, enquanto o desejo do nosso pro-
gramador era chamar a funo da biblioteca! Seria melhor se o programa-
dor pudesse escolher se a declarao recursiva ou no.
Em ML, possvel definir se uma declarao recursiva ou no. O e-
xemplo 2.24 ilustra a definio recursiva em ML da funo mdc. Observe
45
que a palavra rec colocada aps a palavra val indica que essa uma fun-
o recursiva, isto , referncias a mdc no corpo da funo significam
chamadas recursivas.
val rec mdc = fn ( m:int, n: int) = >
if m > n then mdc (m n, n)
else if m < n then mdc (m, n m)
else m
Exemplo 2. 24 - Explicitao de Recursividade em Funo ML
2.5 Consideraes Finais
Nesse captulo foi apresentado e discutido o conceito de amarrao. Mos-
trou-se que as amarraes podem ocorrer em diferentes momentos, desde
o instante de criao da linguagem ao tempo de execuo dos programas.
O enfoque do captulo se concentrou na amarrao de identificadores a
entidades dos programas e no estudo dos conceitos de ambientes de amar-
rao e escopo de visibilidade.
Mostrou-se ainda que as amarraes de identificadores s entidades dos
programas podem ser feitas atravs de definies ou declaraes. Nesse
contexto, abordou-se as amarraes de identificadores a constantes, tipos,
variveis e subprogramas.
Estudos mais aprofundados dos conceitos de tipos de dados, variveis e
constantes e subprogramas sero vistos, respectivamente, nos captulos 3,
4 e 6 desse livro.
2.6 Exerccios
1. Liste pelo menos cinco diferentes tipos de amarraes que ocorrem no
seguinte trecho de cdigo C.
float j = 3.2;
j = j 1.7;
2. Especifique as regras de formao de identificadores de C, C++ e JA-
VA. Responda ainda se existem limites no nmero mximo de caracte-
res que podem ser usados e quais tipos de identificadores especiais so
considerados.
3. Considere o seguinte trecho de cdigo em ADA:
procedure A is
u : integer;
procedure B is
v : integer;
procedure C is
x : integer;
46
procedure D is
u : integer;
begin
null;
end D;
procedure E is
v : integer;
begin
u := 7;
end E;
begin
null;
end C;
procedure F is
y : integer;
procedure G is
x : integer;
begin
null;
end G;
begin
u := 10;
end F;
begin
null;
end B;
begin
null;
end A;
Identifique quais variveis e subprogramas so visveis em cada um dos
subprogramas desse trecho de cdigo. Suponha que novos requisitos do
problema demandem que a varivel u de D possa ser acessada por G.
Quais modificaes necessitariam ser feitas no programa? Cite erros que
poderiam ocorrer em situaes como essa.

4. Indique qual valor ser escrito pelo trecho de cdigo seguinte no caso da
linguagem de programao utilizada adotar escopo esttico e no caso de-
la adotar escopo dinmico.
procedimento sub() {
inteiro x = 1;
inteiro y = 1;
procedimento sub1() {
se (x = 1 & y = 1) ento
sub2();
seno
47
sub3();
}
procedimento sub2() {
inteiro x = 2;
y = 0;
sub1();
}
procedimento sub3() {
escreva( x);
}
sub1();
}
Cite e explique os problemas de legibilidade do trecho de cdigo acima
quando se adota o escopo esttico e o escopo dinmico.
5. Compare, em termos de legibilidade, as opes de C e C++ relativas
localizao das definies e declaraes nos programas.
6. Identifique o problema que ocorre no seguinte trecho de cdigo C. Ex-
plique porque ele ocorre e indique como poderia ser resolvido.
void circulo () {
#define pi 3.14159
float raio = 3;
float area = pi * raio * raio;
float perimetro = 2 * pi * raio;
}
void pressao () {
float pi = 3.2, pf = 5.3;
float variacao;
variacao = pf pi;
}
7. Indique quais valores sero escritos pelo seguinte programa em C. Ex-
plique sua resposta e discuta a postura da linguagem em termos de orto-
gonalidade e de potencialidade para induo de erros de programao.
int i;
main () {
printf (%d\n, i);
f();
}
void f () {
int i;
printf (%d\n, i);
}
8. Uma declarao de funo um segmento de cdigo contendo apenas a
sua assinatura (isto , um segmento de cdigo com o cabealho da fun-
48
o, mas sem seu corpo). Apresente uma situao na qual a declarao
de funes til (ou necessria) em C. Justifique sua resposta explican-
do para que o compilador utiliza a declarao.
49

Captulo III Valores e Tipos de Dados


Nem todos os bits tem o mesmo alor.`
Carl Sagan
Juntamente com os programas, dados so a matria prima da computao.
De fato, para que haja computao necessrio que os programas mani-
pulem dados. A importncia dos dados to fundamental na computao
que, durante muito tempo, essa rea foi popularmente conhecida como a
rea de processamento de dados.
Grandes massas de dados, tais como, dados geogrficos, dados de catlo-
gos telefnicos de cidades, dados de censo demogrfico, imagens de sat-
lite e cadastro de consumidores so de grande interesse econmico, che-
gando a custar vrias vezes mais que o preo dos programas que os mani-
pulam.
Linguagens de Programao utilizam os conceitos de tipos de dados para
permitir a representao de valores em programas. O termo valor utili-
zado aqui como sinnimo de dado. Cada linguagem adota um conjunto
prprio de valores e tipos para permitir a representao de dados.
Um valor qualquer entidade que existe durante uma computao, isto ,
tudo que pode ser avaliado, armazenado, incorporado numa estrutura de
dados, passado como argumento para um procedimento ou funo, retor-
nado como resultado de funes, etc. So exemplos de valores em C:
3 2.5 'a' "Paulo" 0x1F 026
Um tipo de dado um conjunto cujos valores exibem comportamento u-
niforme nas operaes associadas com o tipo. Por exemplo:
{ true, 25, ' b', "azul" } no corresponde a um tipo
{ true, false } corresponde a um tipo
Dizer que um valor de um determinado tipo significa dizer que esse va-
lor pertence ao conjunto de valores definido por aquele tipo. De maneira
similar, dizer que uma expresso de um determinado tipo significa dizer
que o resultado dessa expresso um valor pertencente ao conjunto defi-
nido por aquele tipo.
Um importante conceito para o entendimento de tipos de dados a sua
cardinalidade (#), isto , o nmero de valores distintos que fazem parte do
tipo. Por exemplo, a cardinalidade do tipo boolean de JAVA 2. Ao lon-
50
go desse captulo, o conceito de cardinalidade ser usado na apresentao
dos tipos de dados.
Nesse captulo so discutidos os diversos tipos de dados que costumam
ser adotados em linguagens de programao. Esses tipos so divididos em
duas categorias principais (primitivos e compostos). Por sua vez, os tipos
compostos so subdivididos em outras seis categorias. Alm dos aspectos
sintticos e semnticos de cada tipo de dados, so apresentados modelos
de implementao desses tipos.
3.1 Tipos Primitivos
Tipos primitivos (ou atmicos) so aqueles cujos valores no podem ser
decompostos em outros valores de tipos mais simples. Os tipos primitivos
so a base de todo sistema de tipos de uma linguagem, pois a partir deles
que todos os demais tipos podem ser construdos.
Embora linguagens de propsito geral devam oferecer tipos que permitam
lidar com qualquer tipo de aplicao, a escolha dos tipos primitivos da LP
ajuda a revelar a rea de aplicao pretendida para a LP. Por exemplo,
COBOL, destinada para o processamento comercial, possua como tipos
primitivos strings de comprimento fixo e nmeros de ponto fixo. FOR-
TRAN, destinada para a computao numrica, possui como tipos primi-
tivos nmeros reais com preciso variada.
Tipos Primitivos costumam ser definidos na implementao da LP. Logo,
limitaes e variaes de hardware podem fazer com que diferentes im-
plementaes possuam conjunto de valores diferentes para um mesmo
tipo de uma LP. Isto prejudica a portabilidade dos programas nestas LPs.
Por exemplo, em C, o conjunto de valores do tipo int normalmente cor-
responde ao intervalo que pode ser representado com o nmero de bits da
palavra do computador. Numa mquina de 16 bits, o tipo int varia de
32.768 a +32.767. Numa mquina de 32 bits, o tipo int varia de
2.147.483.648 a +2.147.483.647.
3.1.1 Tipo Inteiro
Um tipo inteiro corresponde a um intervalo do conjunto dos nmeros in-
teiros. Em geral, existem vrios tipos inteiros numa mesma LP. Normal-
mente, existe pelo menos um dos tipos inteiros que reflete exatamente as
operaes de inteiros fornecidas por hardware. Por exemplo, C pode pos-
suir at 8 tipos inteiros resultantes da combinao dos tipos bsicos char
e int com os modificadores signed, unsigned, short e long. Geralmente, o
tipo int ocupar o tamanho da palavra do computador e refletir as opera-
es aritmticas embutidas em hardware. Um compilador C tpico para
51
uma mquina de 16 bits possuir os tipos de dados inteiros apresentados
na tabela 3.1:
Intervalo Tipo Tamanho
(bits) Incio Fim
char 8 -128 127
unsigned char 8 0 255
signed char 8 -128 127
int 16 -32768 32767
unsigned int 16 0 65535
signed int 16 -32768 32767
short int 16 -32768 32767
unsigned short int 16 0 65535
signed short int 16 -32768 32767
long int 32 -2147483648 2147483647
unsigned long int 32 0 4294967295
signed long int 32 -2147483648 2147483647
Tabela 3. 1 - Tipos Inteiros de um Compilador C
Ao analisar a tabela 3.1 pode se constatar que os tipos definidos com o
modificador short estabelecem intervalos de valores iguais aos definidos
pelo tipo de dado equivalente sem o uso desse modificador. De fato, a
existncia dos modificadores short e long revela a inteno de prover in-
tervalos diferentes de inteiro, onde isso for prtico; contudo, a especifica-
o de C [KERNIGHAN & RITCHIE, 1989] no obriga que seja sempre
assim. Normalmente, int ter o tamanho da palavra de uma determinada
mquina. Em geral, short ocupa 16 bits, long ocupa 32 bits e int, 16 ou 32
bits. Cada compilador livre para escolher tamanhos adequados ao pr-
prio hardware, com as nicas restries de que shorts e ints devem ocupar
pelo menos 16 bits, longs pelo menos 32 bits, e short no pode definir um
intervalo maior que int, que no pode ser maior do que long.
A mesma constatao pode ser feita em relao ao modificador signed.
Os tipos definidos na tabela 3.1 usando esse modificador tambm estabe-
lecem intervalos de valores iguais aos definidos pelo tipo de dado equiva-
lente sem o uso desse modificador. Isso tambm ocorre porque a especifi-
cao de C [KERNIGHAN & RITCHIE, 1989] no exige que o intervalo
do tipo bsico (por exemplo, o tipo char) englobe valores positivos e ne-
gativos. Essa deciso normalmente ser dependente da mquina e do
compilador. Assim, se o tipo char definir um intervalo com valores posi-
tivos e negativos, tal como na tabela 3.1, o modificador signed ser re-
dundante quando aplicado ao tipo char. Por outro lado, se o tipo char de-
finir um intervalo de valores no negativos, o modificador unsigned que
ser redundante.
52
interessante compreender porque os criadores de C adotaram a postura
de deixar para os implementadores dos compiladores a definio dos in-
tervalos dos tipos, uma vez que isso claramente traz problemas para a
portabilidade dos programas. A razo dessa escolha enfatizar a eficin-
cia. Tendo essa liberdade, os implementadores dos compiladores podem
selecionar os intervalos dos tipos de modo a utilizar da melhor maneira
possvel os recursos de hardware disponveis.
JAVA rompe com a tradio de deixar a definio do intervalo de inteiros
para a fase de implementao dos compiladores. Como JAVA prioriza a
portabilidade de programas, ela j define na prpria LP os intervalos de
valores que cada tipo inteiro deve representar. A tabela 3.2 mostra os ti-
pos de dados inteiros de JAVA:
Intervalo Tipo Tamanho
(bits) Incio Fim
byte 8 -128 127
short 16 -32768 32767
int 32 -2.147.483.648 2.147.483.647
long 64 -9223372036854775808 9223372036854775807
Tabela 3. 2- Tipos Inteiros de JAVA
Normalmente, o modelo de implementao dos tipos inteiros adota a no-
tao de complemento a dois quando o intervalo de valores inteiros inclui
nmeros positivos e negativos. J quando o intervalo inclui apenas nme-
ros no negativos, a notao binria normal adotada. A tabela 3.3 mos-
tra exemplos da correspondncia entre representaes binrias de nme-
ros e o seu valor decimal correspondente ao se adotar a notao de com-
plemento a dois e a prpria notao binria.
Inteiros Representao
Binria Notao de Comple-
mento a Dois
Notao Binria
0000 0101 5 5
0000 0100 4 4
0000 0011 3 3
0000 0010 2 2
0000 0001 1 1
0000 0000 0 0
1111 1111 -1 255
1111 1110 -2 254
1111 1101 -3 253
1111 1100 -4 252
1111 1011 -5 251
Tabela 3. 3 - Notao de Complemento a Dois e Binria
53
Para converter a representao binria de um nmero em seu decimal cor-
respondente segundo a notao binria, basta utilizar a regra de converso
de nmeros na base binria para a base decimal. Por exemplo:
11111101 = 1x2
7
+1x2
6
+1x2
5
+1x2
4
+1x2
3
+1x2
2
+0x2
1
+1x2
1
= 253
Para converter a representao binria de um nmero em seu decimal cor-
respondente segundo a notao de complemento a dois um pouco mais
trabalhoso. Primeiramente, necessrio verificar se o dgito mais es-
querda do nmero um ou zero. Se for zero, o nmero ser zero ou posi-
tivo. Se for um, o nmero decimal ser negativo. Nesse caso, deve-se in-
verter a representao binria do nmero e increment-lo de um. Assim,
obtm-se a representao do nmero na base binria. Basta, ento conver-
t-la para a base decimal levando em conta o seu sinal. Por exemplo:
11111101 (representao na notao de complemento a dois)
1 (dgito mais a esquerda > nmero negativo)
00000010 (representao com inverso binria)
00000001 (representao binria do nmero um)
00000011 (adio binria do nmero invertido e um)
00000011 = 0x2
7
+0x2
6
+0x2
5
+0x2
4
+0x2
3
+0x2
2
+1x2
1
+1x2
1
= 3
11111101 = -3 (considerando o sinal)
As principais vantagens da notao de complemento a dois so ter uma
representao nica para o nmero zero e tambm poder utilizar os ope-
radores aritmticos binrios para implementar suas prprias operaes.
A cardinalidade dos tipos inteiros corresponde ao nmero total de valores
que podem ser representados no intervalo definido pelo tipo. Esse nmero
limitado pelo total de combinaes de bits que podem ser formadas com
o nmero de bits utilizados para representar os nmeros do tipo. Assim,
se so usados n bits para a representao dos nmeros de um determinado
tipo inteiro, a cardinalidade desse tipo ser 2
n
.
3.1.2 Tipo Caracter
Valores caracteres so armazenados em computadores como cdigos nu-
mricos. Para permitir o processamento de caracteres, algumas LPs for-
necem um tipo primitivo cujos valores correspondem aos smbolos de
uma tabela padro de caracteres. Por exemplo, PASCAL e MODULA 2
oferecem o tipo char.
Existem vrias tabelas padro com cdigos numricos para caracteres,
tais como, EBCDIC, ASCII e UNICODE. A tabela mais utilizada para
codificao de caracteres a ASCII ("American Standard Code for In-
formation Interchange"), que usa os valores de 0..127, armazenados em 8
bits, para codificar 128 caracteres diferentes. A figura 3.1 mostra a tabela
54
ASCII padro. Observe que os 32 primeiros cdigos representam caracte-
res de controle. Note tambm que essa tabela no inclui caracteres acen-
tuados.

Figura 3. 1 - Tabela ASCII Padro
Por causa da globalizao da economia e da necessidade de computadores
se comunicarem com outros computadores ao redor do mundo, a tabela
ASCII tem se tornado inadequada. Muitas vezes, para capturar caracteres
internacionais no presentes na tabela ASCII, cria-se uma extenso espe-
cfica da tabela ASCII, chamada ASCII estendida, atravs da incluso de
mais 128 caracteres tabela padro. Contudo, essa soluo no geral. A
figura 3.2 apresenta uma extenso muito comum da tabela ASCII. Obser-
ve nessa tabela a incluso de caracteres latinos acentuados, caracteres
gregos e outros caracteres especiais.
55

Figura 3. 2 - Extenso de Tabela ASCII
Uma nova tabela padro, chamada UNICODE, foi desenvolvida recente-
mente como uma alternativa. Esta tabela utiliza 16 bits para armazenar
caracteres da maioria das linguagens naturais existentes no mundo. Por
exemplo, ela inclui caracteres usados na linguagem de pases como a Sr-
via, Japo, Tailndia e outros. Para manter a compatibilidade de progra-
mas baseados em ASCII, a tabela UNICODE engloba a tabela ASCII, isto
, ela utiliza os mesmos cdigos numricos para representar os caracteres
da tabela ASCII padro. JAVA adota a tabela padro UNICODE.
Finalmente, importante destacar que, embora C tenha um tipo primitivo
char, esse tipo classificado como um tipo inteiro, pois as operaes rea-
lizadas sobre este tipo so as mesmas que podem ser realizadas sobre os
demais tipos inteiros. De fato, os caracteres usados num programa C so
na realidade sinnimos usados para representar os nmeros corresponden-
tes ao seu cdigo na tabela padro (tipicamente, ASCII). Isso motivo de
frequente confuso para os programadores iniciantes. Por exemplo, o se-
guinte trecho de cdigo C atribui o valor inteiro 100 a varivel d (o valor
100 corresponde a soma de 3 com o cdigo numrico ASCII da letra mi-
nscula a).
char d;
d = 3 + 'a';
Por outro lado, a opo adotada por C permite criar cdigo mais facil-
mente redigvel e eficiente em algumas situaes. Em PASCAL e MO-
DULA-II, por exemplo, para obter o cdigo ASCII de um caracter, ne-
cessrio chamar uma funo. Isso no necessrio em C.
56
A cardinalidade do tipo caracter igual ao nmero de entradas na tabela
de caracteres adotada.
3.1.3 Tipo Booleano
O tipo primitivo booleano o tipo de dados mais simples que pode existir
numa LP. Ele possui apenas dois valores, um correspondente a verdadeiro
e outro a falso. Tipos booleanos so tipicamente usados como resultados
de expresses condicionais ou como variveis identificadoras de estado,
popularmente conhecidas como "flags".
Com exceo de C, o tipo booleano tem sido includo na maioria das LPs.
Em C, as expresses numricas podem ser usadas como condicionais.
Neste caso, todos os operandos com valores diferentes de zero so con-
siderados verdadeiro e todos os operandos com valor zero so conside-
rados falso.
Embora outros tipos, tais como os inteiros de C, possam ser usados para
atingir os mesmos propsitos para os quais o tipo booleano indicado, o
uso de tipos booleanos torna o programa mais legvel e impede a ocorrn-
cia de erros, tais como:
if (c += 1) x = 10;
Esse trecho de cdigo um comando legal em C, mas pode ser decorrente
de um erro de digitao (o programador poderia ter teclado + ao invs de
=). Muito embora JAVA seja uma linguagem fortemente baseada em C,
por causa do tipo de erro acima e da questo de legibilidade, ela inclui o
tipo de dado boolean.
Um valor booleano pode ser representado por um nico bit, contudo, co-
mo um nico bit de memria difcil de acessar eficientemente em mui-
tas mquinas, eles so frequentemente armazenados na menor clula efi-
cientemente enderevel de memria, tipicamente um byte.
Obviamente, a cardinalidade do tipo booleano dois.
3.1.4 Tipo Decimal
O tipo primitivo decimal armazena um nmero fixo de dgitos decimais.
A localizao do ponto decimal estabelecida arbitrariamente, pela LP,
pelo prprio hardware ou pelo programador, em alguma posio das clu-
las de memria que armazenam o seu valor.
O tipo decimal essencial para aplicaes comerciais por ser capaz de
armazenar precisamente valores decimais. Por conta disso, o tipo decimal
um tipo fundamental em COBOL e tambm oferecido por ADA. Em-
57
bora limitado a um intervalo restrito, o tipo decimal garante que as opera-
es sobre seus valores so sempre precisas. Isto no pode ser feito com
nmeros reais armazenados usando a notao de ponto flutuante.
Tipos decimais so representados como cadeia de caracteres, usando-se
cdigos binrios para os dgitos decimais. Estas representaes so cha-
madas BCD (binary coded decimal). Em alguns casos, elas so
armazenadas usando um byte por dgito, mas em outros podem empacotar
dois dgitos em cada byte, visto que para representar binariamente qual-
quer dgito decimal so necessrios apenas 4 bits. A figura 3.3 mostra um
exemplo de modelo de implementao para um tipo decimal:




Figura 3. 3 - Representao Binria de Nmero Decimal
A representao BCD de nmeros reais sempre ocupa mais memria do
que a representao binria correspondente. Ela ocupa no mnimo 4 bits
para codificar um dgito decimal. Por exemplo, para armazenar o nmero
18 na representao BCD necessrio utilizar 8 bits. Contudo, na repre-
sentao binria, s so necessrios 5 bits para armazenar o mesmo n-
mero.
Assim, as desvantagens do tipo decimal so o reduzido intervalo de valo-
res decimais que pode ser representado (pois expoentes no podem ser
utilizados) e o desperdcio da memria usada pela representao.
As operaes sobre valores decimais so realizadas por hardware, quando
a mquina j as tem embutidas, ou emuladas por software.
A cardinalidade do tipo decimal funo do nmero total n de dgitos
inteiros e fracionrios do tipo. Como cada casa decimal pode ser um dos
dez algarismos e como os nmeros decimais podem ser positivos ou ne-
gativos, a cardinalidade corresponde a 2 x 10
n
valores. Por exemplo, no
caso do tipo decimal da figura 3.3, a cardinalidade seria de 2 x 10
11
.
3.1.5 Tipo Ponto Flutuante
O tipo primitivo ponto flutuante modela os nmeros reais. Como a repre-
sentao em ponto flutuante finita, nmeros reais como s podem ser
representados aproximadamente.
Como nmeros reais no possuem correspondncia numa representao
binria direta, valores de ponto flutuante necessitam ser representados
1010 0011 1011 0000 1110 0110 1111 1000 0000 0010 1000 1011
2 bytes
4 casas decimais
4 bytes
7 casas inteiras
1 sinal
sinal
58
atravs de uma notao que combina representaes binrias de fraes e
expoentes.
Operaes de ponto flutuante so fornecidas pelo hardware. Mquinas
atuais usam o padro IEEE 754 para ponto flutuante. Como pode ser visto
na figura 3.4, esse padro define dois tipos de ponto flutuante: preciso
simples e preciso dupla. O tipo de preciso simples o de uso mais co-
mum e ocupa 32 bits. O tipo de preciso dupla s usado em situaes
onde maiores expoentes e partes fracionrias so necessrios e ocupa 64
bits.






Figura 3. 4 - Padro IEEE 754
Os implementadores de LPs normalmente usam a representao fornecida
pelo hardware. A maioria das LPs inclui dois tipos de ponto flutuante,
frequentemente chamados de float e double. JAVA possui esses dois tipos
e adota o padro IEEE para cada um deles. A tabela 3.4 ilustra as caracte-
rsticas dos tipos de dados float e double de JAVA.
Intervalo Tipo Nmero
de Bits Incio Fim
float 32 3.40282347 E +38 1.40239846 E 45
double 64 1.79769313486231570
E +308
4.94065645841246544
E 324
Tabela 3. 4 - Tipos de Dados Ponto Flutuante de JAVA (extrada de [FLANAGAN, 1997])
A cardinalidade dos tipos float e double limitada pelo nmero de bits
usado em cada uma das representaes. Para evitar que a cardinalidade do
tipo de ponto flutuante seja reduzida pelo fato de um mesmo nmero po-
der ser representado de formas diferentes (por exemplo, 0.01 x 10
1
e 1.0 x
10
-1
), a representao em ponto flutuante sempre normalizada, isto , o
dgito mais significativo do nmero nunca deve ser zero. Por outro lado,
algumas representaes binrias especficas so utilizadas para a repre-
sentao de valores especiais, tais como, zero, infinito e no nmeros (por
exemplo, a raiz quadrada de nmeros negativos). Assim, o nmero de va-
lores distintos dos tipos de ponto flutuante sempre inferior ao total de
expoente frao
expoente frao
Preciso Simples
bit de sinal 8 bits
23 bits
bit de sinal
11 bits
52 bits
Preciso Dupla
59
configuraes binrias distintas disponveis. Por exemplo, a cardinalidade
de float inferior a 2
32
.
Uma explicao detalhada a respeito da representao em ponto flutuante
e de como so implementadas suas operaes aritmticas se encontra a-
lm do escopo desse livro. Maiores informaes podem ser obtidas em
[GOLDBERG, 1991].
3.1.6 Tipo Enumerado
Em algumas LPs, tais como PASCAL, ADA, C e C++, permitido que o
programador defina novos tipos primitivos atravs da enumerao dos
identificadores que denotaro os valores do novo tipo. Por exemplo, em C
possvel definir o seguinte tipo:
enum mes_letivo {mar, abr, mai, jun, ago, set, out, nov};
Tipos enumerados possuem correspondncia direta (uma relao de 1 pa-
ra 1) com intervalos de tipos inteiros e podem ser usados para indexar
vetores e para contadores de repeties. De fato, em C e C++, os valores
enumerados so convertidos implicitamente para nmeros inteiros, sendo
aplicados sobre eles as mesmas operaes que regem os tipos inteiros.
Tipos enumerados so utilizados basicamente para aumentar a legibilida-
de e confiabilidade do cdigo. A legibilidade aumentada porque identi-
ficadores de valores so mais facilmente reconhecidos do que cdigos
numricos. Por exemplo, num programa de processamento bancrio
mais fcil identificar os bancos especficos se os descrevemos por seu
nome ao invs de usar seus cdigos numricos.
A confiabilidade aumentada porque valores fora da enumerao no so
vlidos. Alm disso, operaes aritmticas comuns no podem ser reali-
zadas sobre estes tipos, permitindo que o compilador identifique possveis
erros lgicos e tipogrficos. Como C e C++ tratam enumeraes como
inteiros, estas duas vantagens de confiabilidade no esto presentes.
Os projetistas de JAVA fizeram uma opo por simplificar a linguagem e
no incluram o tipo enumerado de C e C++.
A cardinalidade de um tipo enumerado corresponde ao nmero de identi-
ficadores usados na enumerao do tipo.
3.1.7 Tipo Intervalo de Inteiros
Em algumas LPs, tais como PASCAL e ADA, tambm possvel definir
tipos intervalo de inteiros. Por exemplo, em PASCAL, possvel definir:
type meses = 1 .. 12;
60
Tipos intervalos herdam as operaes dos inteiros. De fato, como as vari-
veis intervalo podem ser atribudas por variveis inteiras, e vice-versa,
analisando estritamente, elas podem ser consideradas como um subtipo
do tipo de dados inteiro.
As vantagens do tipo intervalo so praticamente as mesmas dos tipos e-
numerados. A legibilidade aumentada a partir do momento que fica
mais claro qual intervalo de valores que o tipo pode assumir. A confiabi-
lidade tambm aumentada porque a atribuio de valores fora do inter-
valo pode ser verificada esttica e dinamicamente.
3.2 Tipos Compostos

Tipos compostos so aqueles que podem ser criados a partir de tipos mais
simples. So exemplos os registros, os vetores, as listas, os arquivos, etc.
David Watt [WATT, 1990] utiliza os conceitos de produto cartesiano,
unies, mapeamentos, conjuntos potncia e tipos recursivos para classifi-
car e explicar os tipos compostos. Essa tambm a abordagem seguida
aqui.
3.2.1 Produto Cartesiano
Consiste na combinao de valores de tipos diferentes em tuplas. So
produtos cartesianos os registros de PASCAL, MODULA 2, ADA e CO-
BOL e as estruturas de C. O exemplo 3.1 ilustra produtos cartesianos a-
travs do uso de estruturas em C:






Exemplo 3. 1 - Produtos Cartesianos em C
Na struct empregado, os identificadores nfunc e salario so seletores uti-
lizados para acessar os componentes da tupla. Eles liberam o programa-
dor de ter que lembrar a ordem dos componentes.
Para referenciar diretamente os componentes da tupla, utilizam-se nor-
malmente referncias completas. Por exemplo, para acessar o campo meio
em C, necessrio usar:
emp.nfunc.meio
Algumas LPs possuem mecanismos para abreviar referncias. Por exem-
plo, em PASCAL, pode-se utilizar o comando with:
struct nome {
char primeiro [20];
char meio [10];
char sobrenome [20];
};

struct empregado {
struct nome nfunc;
float salario;
} emp;
61
WITH emp.nfunc DO BEGIN
WRITE (primeiro, meio, sobrenome);
END;
A operao de atribuio entre registros frequentemente permitida em
LPs, tais como, PASCAL, ADA e C, bem como as comparaes de i-
gualdade e desigualdade. Muitas vezes, tambm permitida a inicializa-
o do registro com um agregado de valores. Em C, por exemplo:
struct data { int d, m, a; };
struct data d = { 7, 9, 1999 };
A figura 3.5 ilustra os conjuntos de valores dos tipos S, T e do produto
cartesiano S x T:







Figura 3. 5 - Produto Cartesiano de Dois Tipos (extrada de [WATT, 1990])
A cardinalidade do produto cartesiano dada pelo produto da cardinali-
dade de seus componentes:
#(S x T) = #S x #T
Generalizando, a cardinalidade de um produto cartesiano de n componen-
tes dada por:
#(S
1
x S
2
x ... x S
n
) = #S
1
x #S
2
x ...x #S
n

No caso particular onde todos os componentes so do mesmo tipo (tupla
homognea), a cardinalidade dada por:
#(S
n
) = (#S)
n
Em LPs orientadas a objetos, produtos cartesianos so definidos a partir
do conceito de classe. Embora C++ contenha classes, ela mantm as s-
truct em decorrncia da necessidade de manter compatibilidade com C.
Contudo, isso introduz uma redundncia na LP. JAVA, por sua vez, s
possui o conceito de classe.
O modelo de implementao de produtos cartesianos consiste normal-
mente do armazenamento dos valores do produto em campos adjacentes
de memria. O acesso realizado atravs do par (endereo, deslocamen-
to).


x =
a b c d e
(b,c) (b,d) (b,e)

(a,c) (a,d) (a,e)
S T
S x T
62
3.2.2 Unies
Consiste na unio de valores de tipos distintos para formar um novo tipo
de dados. Um exemplo de uso de unies pode ocorrer na definio de
uma tabela de constantes de um compilador. Esta tabela pode ser compos-
ta com os diversos identificadores de constantes e os seus respectivos va-
lores. Como constantes podem ser de tipos diferenciados, o campo valor
da tabela pode ser implementado como uma unio dos possveis tipos de
constantes. Unies podem ser livres ou disjuntas.
3.2.2.1 Unies Livres
Nas unies livres pode haver interseo entre o conjunto de valores dos
tipos que formam a unio. No tipo unio resultante haver um nico valor
correspondente aos valores que so comuns aos diversos tipos. So e-
xemplos de unies livres os tipos resultantes do comando EQUIVALEN-
CE de FORTRAN e union de C e C++.
Um problema existente com o uso de unies livres a possibilidade de
violao do sistema de tipos da LP. O exemplo 3.2 ilustra uma unio livre
em C:
union medida {
int centimetros;
float metros;
};
union medida medicao;
float altura;
medicao.centimetros=180;
altura = medicao.metros; // erro
printf(\n altura : %f metros\n", f);
Exemplo 3. 2 - Unio em C
No exemplo 3.2, o programador atribuiu um valor de 180 centmetros a
varivel medicao e posteriormente utiliza o valor de medicao em metros,
o qual no havia sido atribudo, com consequncias imprevisveis para o
resultado do programa. Este tipo de uso de unies livres no pode ser ve-
rificado pelo compilador C. Por causa deste tipo de insegurana, e por
poder utilizar herana para agrupar valores de tipos distintos, JAVA no
possui unies.
O conjunto de valores de uma unio livre determinado pela composio
dos valores distintos de cada componente da unio. Portanto, quando os
componentes so disjuntos, a cardinalidade de uma unio livre dada pe-
la soma das cardinalidades de seus componentes. Por outro lado, quando
existe interseo entre o conjunto de valores dos componentes, a cardina-
63
lidade de uma unio livre dependente das possveis intersees existen-
tes entre os conjuntos de valores dos componentes. A figura 3.6 mostra a
unio livre de dois tipos de dados disjuntos.




Figura 3. 6 - Unies Livres sem Interseo
A cardinalidade da unio apresentada na figura 3.6 dada por:
#(S + T) = #S + #T
Generalizando, a cardinalidade de uma unio livre de n componentes
dada por:
#(S
1
+ S
2
+ ... + S
n
) = #S
1
+ #S
2
... + #S
n

Essas frmulas no se aplicam no caso de haver interseo, como na figu-
ra 3.7:




Figura 3. 7 - Unies Livres com Interseo
O modelo de implementao de unies livres frequentemente consiste em
reservar espao suficiente em memria para abrigar o componente da u-
nio que requer mais espao e compartilh-lo com os demais componen-
tes. A figura 3.8 ilustra como a unio livre medicao do exemplo 3.2 ar-
mazenada na memria. Enquanto todos os 32 bits so usados para arma-
zenar o componente metros, somente os 16 primeiros bits so usados para
armazenar o componente centimetros.







Figura 3. 8 - Implementao de Unio em C

+ =

a b c d e
a b c d e
S
T
S + T

+ =

a b c b c d
a b c d
S
T
S + T
metros
1010101010101011 101010101010101
centimetros
medicao
64
3.2.2.2 Unies disjuntas
Nas unies disjuntas no h possibilidade de haver interseo entre o con-
junto de valores dos tipos que formam a unio. Tipicamente se utiliza um
campo marcador, chamado de tag, para identificar qual o tipo originrio
do valor da unio disjunta. So exemplos de unies disjuntas os tipos as-
sociados aos registros variantes de PASCAL, MODULA 2 e ADA,
clusula REDEFINES de COBOL e s union de ALGOL 68.
A figura 3.9 mostra a forma sinttica dos registros variantes de PASCAL
e MODULA 2.








Figura 3. 9 - Sintaxe de Registros Variantes de PASCAL e MODULA-2
Na figura 3.9, os valores V
1
, ..., V
n
cobrem todos os possveis valores do
tipo primitivo discreto T do tag. O conjunto de valores desse tipo de re-
gistro variante dado pela unio dos valores pertencentes a T
1
, T
2,
... , T
n
.
Considere o exemplo 3.3 em PASCAL:
TYPE Representacao = (decimal, fracionaria);
Numero = RECORD CASE Tag: Representacao OF
decimal: (val: REAL);
fracionaria: (numerador, denominador: INTEGER);
END;
Exemplo 3. 3 - Unio Disjunta em PASCAL
O conjunto de valores do tipo Numero formado por:
{..., decimal (-0.33), ... , decimal (-1.5), ... , decimal (0.6), ...}

{..., fracionaria (-1,3), ... , fracionaria (-3,2), ... , fracionaria (3,5), ...}
Os valores decimal e fracionaria so as tags dos tipos da unio.
Em PASCAL, a tag do registro variante e os componentes da variante
podem ser acessados do mesmo modo como componentes de registros,
provocando uma notria insegurana na programao.
RECORD CASE C:T OF
V
1
: (C
1
: T
1
);
.
.
.
V
N
: (C
N
: T
N
);
END;
65
Considere que uma varivel Num do tipo Numero tem o valor decimal
1.5. Logo, Num.Tag possui o valor decimal e Num.val o valor 1.5. O pro-
grama pode tentar acessar Num.numerador, o qual no existe corrente-
mente. Isto provoca um tipo de erro de execuo muito desagradvel,
quando a verificao dinmica implementada pelo compilador. Outro
problema que uma atribuio de fracionaria para Num.Tag provoca o
efeito colateral de destruir Num.val e criar Num.numerador e
Num.denominador com um valor indefinido. Portanto, o valor de Num
pode ser mudado num nico passo de decimal (1.5) para fracionaria
(?,?).
ADA torna o uso de unies disjuntas mais seguro que em PASCAL e
MODULA-2. ADA exige que todos os registros variantes tenham tags.
Alm disso, o programador no pode criar unies inconsistentes porque o
tag no pode ser atribudo separadamente. Mais ainda, ADA requer que
qualquer referncia ao campo variante seja verificada com relao con-
sistncia do tag.
A cardinalidade de unies disjuntas obtida tal como no caso das unies
livres com inexistncia de interseo entre os conjuntos de valores dos
componentes, ou seja, atravs da soma da cardinalidade dos componen-
tes. A figura 3.10 ilustra a unio disjunta de dois tipos. Os sinais + e &
indicam os valores do campo tag.
Figura 3. 10 - Unio Disjunta (adaptada de [WATT, 1990])
O modelo de implementao das unies disjuntas o mesmo das unies
livres.
Em PASCAL, MODULA-2 E ADA, registros misturam os conceitos dis-
tintos de Produto Cartesiano e Unies. Considere o exemplo 3.4 em PAS-
CAL:
TYPE TipoProduto = (musica, livro, video);
Compra = RECORD
valor: REAL;
CASE produto: TipoProduto OF
musica: (numeromusicas: INTEGER );
livro: (numeropaginas: INTEGER);


+ =
+ + +
a b c
& & &
c d e
+ + + & & &
a b c c d e
S
T
S + T
66
video: (duracao: INTEGER, colorido: BOOLEAN);
END;
Exemplo 3. 4 Produto Cartesiano com Unio Disjunta em PASCAL
O cardinalidade do tipo Compra dado por:
#REAL x (#INTEGER + #INTEGER + (#INTEGER x #BOOLEAN) )
So exemplos de valores do tipo Compra:
(25.00, musica (16) )
(35.00, livro (257) )
(40.00, video (121, TRUE))
3.2.3 Mapeamentos
Mapeamentos so tipos de dados cujo conjunto de valores corresponde a
todos os possveis mapeamentos de um tipo de dados S em outro T (que
pode ser do mesmo tipo).
A notao S T simboliza o conjunto de todos os possveis mapeamen-
tos distintos de S para T. A figura 3.11 mostra dois mapeamentos distin-
tos de S para T.








Figura 3. 11 - Mapeamentos (extrada de [WATT, 1990])
O conjunto de valores do mapeamento S T
{ ((u,a),(v,a)), ((u,b), (v,b)), ((u,c),(v,c)), ((u,a),(v,b)), ((u,a),(v,c)),
((u,b),(v,a)), ((u,b), (v,c)), ((u,c),(v,a)), ((u,c),(v,b)) }
possvel entender um mapeamento como sendo um produto cartesiano
de #S elementos, no qual cada elemento pode assumir qualquer valor do
tipo T. Logo, a cardinalidade de S T expressa por:



u


v
a

b

c
u


v
a

b

c
S
T
S
T
#(S T) = #T x #T x x #T = (#T)
#S

#S vezes
67
3.2.3.1 Mapeamentos Finitos
So mapeamentos no qual o conjunto domnio finito. Os vetores podem
ser vistos como sendo um mapeamento finito do conjunto ndice para o
conjunto dos elementos do vetor. Considere as linhas de cdigo seguintes
em PASCAL:
ARRAY S OF T; [S T]
A: ARRAY [1..50] OF CHAR; A: ([1,50] CHAR)
Nesse trecho de cdigo qualquer valor possvel do vetor corresponde a
um mapeamento do conjunto ndice (intervalo inteiro de 1 a 50) para o
conjunto dos caracteres. A figura 3.12 ilustra um desses possveis mape-
amentos.


Figura 3. 12 - Vetor como Mapeamento Finito
Os elementos dos vetores so acessados atravs de indexao. As nota-
es mais comuns utilizadas para indexao so parnteses ( ) e colchetes
[ ]. O uso de parnteses pode provocar confuso porque normalmente
chamadas de funes tambm os usam.
O conjunto ndice deve ser finito (valores no podem ser strings de tama-
nho varivel) e discreto (no podem ser nmeros reais). A maior parte das
LPs restringe o conjunto ndice a um intervalo dos inteiros. Algumas fi-
xam o limite inferior. C, C++ e JAVA, por exemplo, estabelecem 0 (zero)
como o ndice inicial. Outras permitem a definio dos limites pelo pro-
gramador. PASCAL E ADA permitem que o conjunto ndice seja de
qualquer tipo primitivo discreto (inteiro, enumerado ou intervalo).
Em geral, o erro devido ao uso de ndice inexistente s pode ser verifica-
do em tempo de execuo. Algumas LPs, como PASCAL, MODULA-2,
ADA e JAVA fazem a verificao dinmica do ndice. Enquanto essa op-
o aumenta a confiabilidade dos programas escritos na LP, ela reduz a
eficincia de execuo porque qualquer acesso ao vetor requer o teste do
ndice. Isso se agrava bastante quando o acesso ocorre dentro de uma re-
petio. Por conta dessa perda de eficincia, LPs como C, C++ e FOR-
TRAN no fazem a verificao dos ndices e acabam comprometendo a
confiabilidade. Portanto, importante ter cuidado redobrado ao progra-
mar nessas LPs porque erros como o apresentado no cdigo C a seguir
no sero detectados.
int v[7]; // cria vetor de 7 posicoes com indices de 0 a 6
v[13] = 198; // atribuicao valida c/ consequencias imprevisiveis
a z d r s f h w o
1 2 3 4 5 47 48 49 50
68
Quatro categorias de vetores podem ser definidas de acordo com o tama-
nho e tempo de definio do vetor e do momento e local de sua alocao
na memria. A tabela 3.5 apresenta as caractersticas de cada uma dessas
categorias.

Categoria
de Vetor
Tamanho Tempo de
Definio
Alocao Local de
Alocao
Exemplos
Estticos Fixo Compilao Esttica Base FORTRAN 77
Semi-
Estticos
Fixo Compilao Dinmica Pilha PASCAL, C,
MODULA 2
Semi-
Dinmicos
Fixo Execuo Dinmica Pilha ALGOL 68,
ADA
Dinmicos Varivel Execuo Dinmica Monte APL, PERL
Tabela 3. 5 - Categorias de Vetores
A coluna Exemplos da tabela 3.5 indica que a LP citada possui essa cate-
goria de vetor. Embora se tenha classificado a LP naquela categoria que
lhe mais caracterstica, isso no significa que ela s possua essa catego-
ria de vetores. Por exemplo, C tambm permite a implementao de veto-
res estticos.
Vetores estticos so alocados no incio do programa numa posio fixa
da memria, chamada de base, e permanecem ali durante toda a execu-
o. Eles apresentam como principal vantagem a eficincia de execuo,
visto que no requerem a alocao e desalocao de memria. Por outro
lado, LPs que s possuem vetores estticos, como FORTRAN 77, con-
somem mais memria do que necessrio, visto que vetores usados ape-
nas em regies do programa tm de ficar alocados durante toda a execu-
o. Um exemplo de vetor esttico em C :
void f () {
static int x[10];
Vetores semi-estticos so alocados na pilha sempre que o bloco onde
esto declarados comea a ser executado. Eles so mais econmicos com
o uso da memria pois s alocam o vetor na regio do programa onde so
necessrios. Contudo, essa poltica implica numa reduo da eficincia de
execuo. Alm disso, programas recursivos podem multiplicar o consu-
mo de memria. Um exemplo de vetor semi-esttico em C :
void f () {
int x[10];
Vetores semi-dinmicos tambm so alocados na pilha sempre que o blo-
co onde esto declarados comea a ser executado. No entanto, o tamanho
do vetor s conhecido no momento da alocao. A grande vantagem do
vetor semi-dinmico sobre o vetor semi-esttico a flexibilidade que ele
69
proporciona. Nesta categoria, o programador no necessita definir um
tamanho mximo para o vetor. A seguir, um exemplo de vetor semi-
dinmico em ADA apresentado. Nele, a varivel inteira tam lida e, em
seguida, o vetor lista criado com tam elementos inteiros.
get (tam);
declare
lista: array (1..tam) of integer;
begin
Vetores dinmicos so alocados em qualquer ponto da execuo do
programa na regio de monte, tambm conhecida como heap. Alm
disso, o tamanho do vetor pode ser modificado durante a execuo, em
qualquer ponto onde uma atribuio feita ao vetor. A reduo e aumento
do vetor implementada atravs da alocao de um novo espao de
memria para o vetor, da cpia do contedo (se for o caso) e da
desalocao da memria anteriormente reservada para ele. Um exemplo
de vetores dinmicos em APL dado a seguir. Nesse exemplo, um vetor
criado inicialmente com trs elementos e posteriormente tem seu tamanho
aumentado para cinco elementos atravs de uma nova atribuio.
A (2 3 4)
A (2 3 4 15 20)
Se flexibilizarmos a exigncia que vetores semi-dinmicos sejam aloca-
dos na pilha, tambm possvel implement-los em C, C++ e JAVA. Em
C, usa-se ponteiros e as funes padro malloc e free para gerenciar a a-
locao de memria. Em C++, usa-se ponteiros e os operadores new e
delete para esta tarefa. Em JAVA, basta criar um objeto do tipo vetor. O
exemplo 3.5 mostra como vetores semi-dinmicos podem ser implemen-
tados em C, C++ e JAVA.







Exemplo 3. 5 Vetores Semi-Dinmicos em C, C++ e JAVA
Esse mesmo mecanismo pode ser usado para a implementao de vetores
dinmicos.
Em C:
void f (int a) {
int *p;
p = (int *)
malloc (a * sizeof(int));
p[0] = 10;
free (p);
}
Em C++:
void f (int a) {
int *p;
p =
new int[a];
p[0] = 10;
delete[ ] p;
}
Em JAVA:
void f (int a){
int p[];
p =
new int[a];
p[0] = 10;
}
70
O uso desse mecanismo em C e C++, para a implementao de vetores
semi-dinmicos e dinmicos, algo tortuoso. Essas LPs deixam ao encar-
go do programador a tarefa de gerenciar a alocao e desalocao de
memria, o que torna a programao desses vetores muito mais complexa
e suscetvel a erros.
Observe que em JAVA isso no ocorre. O programador no necessita u-
sar explicitamente o conceito de ponteiros para criar os vetores. Alm
disso, JAVA possui coletor de lixo, o que exime o programador de ter de
desalocar explicitamente a memria usada. Dessa forma, a abordagem de
JAVA mais redigvel e confivel.
A maioria das LPs tambm possui vetores multidimensionais, conhecidos
como matrizes. Um componente de uma matriz n-dimensional acessado
usando n valores do conjunto ndice. De fato, podem-se encarar matrizes
como tendo um nico ndice que uma tupla. Assim, matrizes continuam
sendo um mapeamento de um conjunto ndice para o conjunto dos ele-
mentos. Note que o conjunto ndice formado pelo produto cartesiano
dos conjuntos de valores de cada componente da tupla. Considere o se-
guinte exemplo em C:
int mat [5][4];
O conjunto de valores desse tipo :
{0, , 4} x {0, , 3} int
E sua cardinalidade :
(#int)
# ({0, , 4} x {0, .., 3})
= (#int)
(# {0, , 4} x #{0, .., 3})
= (#int)
5 x 4
= (#int)
20
Os elementos de vetores multidimensionais so acessados atravs da es-
pecificao de seus ndices. Por exemplo, vetores bidimensionais so a-
cessados atravs da especificao da linha e coluna do elemento. Uma
representao grfica para o vetor bidimensional mat dada na figura
3.13.






Figura 3. 13 - Representao Grfica de Vetor Bidimensional
0 1 2 3
0

1

2

3

4
71
Note que o primeiro elemento da tupla especifica a linha do elemento que
ser acessado. O segundo elemento especifica a coluna do elemento. As-
sim, o elemento mat [2] [3] indicado na figura 3.13 pela clula preen-
chida com cinza.
Os elementos de vetores unidimensionais so armazenados em posies
contguas de memria. Vetores multidimensionais so normalmente ar-
mazenados como um vetor unidimensional de tamanho igual ao nmero
de clulas do vetor multidimensional. Tipicamente, vetores bidimensio-
nais so armazenados por linha, isto , armazena-se primeiramente os e-
lementos da primeira linha, depois os da segunda, e assim por diante. Lo-
go, o elemento a ser acessado pode ser obtido atravs da seguinte frmu-
la:
posio mat [i] [j] = endereo de mat [0][0] + i tamanho da li-
nha + j tamanho do elemento = endereo de mat [0][0] +
(i nmero de colunas + j) tamanho do elemento
importante notar que o acesso a elemento via vetor multidimensional
sempre implica no clculo dinmico da posio do elemento na memria.
Isso menos eficiente do que acessar o elemento de um vetor unidimen-
sional. Em certas situaes, onde se quer ter o mximo de eficincia
computacional, pode-se considerar substituir a representao multidimen-
sional por uma unidimensional.
Na maioria das LPs um vetor multidimensional regular, isto , o nmero
de elementos de cada dimenso fixo. Por exemplo, numa matriz bidi-
mensional, cada linha possui o mesmo nmero de colunas. Em JAVA,
alm dos vetores multidimensionais regulares, possvel criar vetores
onde uma ou mais dimenses podem ter nmero de elementos variado.
Isto se torna possvel porque em JAVA vetores multidimensionais so na
realidade vetores unidimensionais cujos elementos so outros vetores.
Veja o exemplo 3.6 em JAVA:
int [] [] a = new int [5] [];
for (int i = 0; i < a.length; i++) {
a [i] = new int [i + 1];
}
Exemplo 3. 6 - Vetores Multidimensionais em Escada em JAVA
O exemplo 3.6 cria um vetor bidimensional em forma de escada (uma
matriz triangular inferior esparsa). A primeira linha tem um elemento, a
segunda tem dois elementos e assim por diante. Observe que LPs como
PASCAL e MODULA-2 no permitem esse tipo de construo, uma vez
que vetores devem possuir elementos de mesmo tipo e tamanho. C possi-
72
bilita a construo desse tipo de estrutura de dados atravs do uso de pon-
teiros.
Cada LP oferece um conjunto particular de operaes que podem ser rea-
lizadas sobre vetores. Operaes comuns so indexao, inicializao,
atribuio, comparao de igualdade e desigualdade. Alm da indexao,
C apenas fornece a operao de inicializao:
int lista [] = {4, 5, 7, 83};
char name [] = "frederico";
char * names [] = {"Leo", "Marcos", "Isa"};
Alm das operaes mencionadas acima, ADA oferece tambm a conca-
tenao de vetores.
Algumas LPs, como FORTRAN 90, permitem a manipulao de parte do
vetor (um subvetor) Este tipo de operao chamada de slicing. Nos
trechos de cdigo seguintes so criados em FORTRAN 90 um vetor VET
de 8 elementos inteiros e uma matriz MAT de 4 linhas e 4 colunas com
elementos inteiros. Mostra-se tambm como pode-se obter partes dessas
estruturas de dados.
INTEGER VET (1:8), MAT (1:4, 1:4)
VET (2:5) VET(2:10:2) VET((/5, 3, 2, 7/))
MAT (3, 1:3)
VET(2:5) produz um vetor composto do segundo, terceiro, quarto e quin-
to elementos de VET. VET(2:8:2) produz um vetor com o segundo, quar-
to, sexto e oitavo elemento de VET. VET((/5, 3, 2, 7/)) produz um vetor
com o quinto, terceiro, segundo e stimo elementos de VET, nessa ordem.
MAT(3, 1:3) produz um vetor com os elementos das trs primeiras colu-
nas da terceira linha de MAT.
APL a linguagem que fornece o conjunto mais amplo de operaes so-
bre vetores. Dentre as operaes pr-definidas esto a soma, subtrao,
multiplicao, transposio e inverso de matrizes.
3.2.3.2 Mapeamentos atravs Funes
Outra forma de mapeamento em LPs atravs de funes. Uma funo
implementa um mapeamento ST, atravs de um algoritmo, o qual toma
qualquer valor em S e computa sua imagem em T. O conjunto S no ne-
cessita ser finito.
O conjunto de valores do tipo mapeamento ST so todas as funes
que mapeiam o conjunto S no conjunto T. Considere o exemplo 3.7 em
JAVA:
73
boolean positivo (int n) {
return n > 0;
}
Exemplo 3. 7 - Mapeamento por Funes em JAVA
A funo do exemplo 3.7 um valor do tipo mapeamento do conjunto
dos inteiros para o conjunto dos valores booleanos [int boolean]. Outros
valores desse tipo poderiam ser funes que implementem as abstraes
par, mpar, primo, palndromo, mltiplo de 3, etc. O descritor de tipo do
mapeamento (como, por exemplo, [int boolean]), normalmente co-
nhecido pelo termo de assinatura da funo.
Numa LP, funes podem ser valores de primeira ou segunda classe. Elas
so valores de primeira classe quando se pode fazer com elas as mesmas
coisas que se pode fazer com valores de outros tipos, por exemplo, criar
variveis e estruturas de dados daquele tipo, atribuir valores, passar como
parmetro, retornar como resultado de uma funo, etc. Elas so valores
de segunda classe quando existem restries arbitrrias na LP que impe-
dem algum desses usos. Por exemplo, em PASCAL possvel passar fun-
es como parmetros, mas no se pode criar variveis do tipo funo.
C utiliza o conceito de ponteiros para manipular endereos de funes
como valores. O exemplo 3.8 utiliza uma funo conta, a qual possui um
parmetro funo, para calcular o nmero de mpares, negativos e mlti-
plos de 7 num vetor vet de 10 elementos:
int impar (int n){ return n%2; }
int negativo (int n) { return n < 0; }
int multiplo7 (int n) { return !(n%7); }
int conta (int x[], int n, int (*p) (int) ) {
int j, s = 0;
for (j = 0; j < n; j++)
if ( (*p) (x[j]) ) s++;
return s;
}
main() {
int vet [10];
printf ("%d\n", conta (vet, 10, impar));
printf ("%d\n", conta (vet, 10, negativo));
printf ("%d\n", conta (vet, 10, multiplo7));
}
Exemplo 3. 8 - Uso de Funes como Valores em C
Embora use um mecanismo complicado, C trata funes como valores de
primeira classe, pois tanto possvel criar estruturas de dados cujos ele-
74
mentos so do tipo ponteiro para funo, quanto passar parmetros para
subprogramas do tipo ponteiro para funo, tal como ilustrado no exem-
plo 3.8.
JAVA no trata funes (mtodos) como sendo valores. Mtodos no so
dados e no podem ser manipulados por programas. Contudo, mtodos
podem ser passados para outros mtodos atravs da passagem de uma ins-
tncia de uma classe que defina o mtodo.
Na maioria das LPs uma funo pode ter n parmetros. Quando chamada,
devem ser passados n valores. Pode-se entender esse caso como se a fun-
o recebesse um nico valor que uma tupla. Por exemplo, a funo C
que possui o seguinte prottipo
float potencia (float b, int n);
define um mapeamento de [float x int float].
Observe que a funo potencia no pode ser passada como parmetro pa-
ra a funo conta do exemplo 3.8 porque o seu tipo diferente do tipo do
parmetro.
importante lembrar ainda que se pode empregar algoritmos diferentes
para implementar um mesmo mapeamento. Alm disso, algumas vezes,
vetores e funes podem ser usados para implementar o mesmo mapea-
mento finito. A escolha de um algoritmo especfico ou a deciso sobre o
uso de vetores ou funes depender de consideraes sobre o tempo de
resposta desejado e sobre o espao de memria requerido.
3.2.4 Conjuntos Potncia
Conjuntos potncia so tipos de dados cujo conjunto de valores corres-
ponde a todos os possveis subconjuntos que podem ser definidos a partir
de um tipo base S. A figura 3.14 mostra todos os valores de um conjunto
potncia (S):
S = {s | s S}



Figura 3. 14 - Conjunto Potncia
Como cada valor em S pode ser membro ou no de um conjunto particu-
lar, a cardinalidade do conjunto potncia de S dada por
#S = 2
#S

a b c
S
{} {a} {b} {c} {a, b}
{a, c} {b, c} {a, b, c}
S
75
As operaes bsicas que normalmente podem ser feitas sobre um tipo
conjunto potncia so as mesmas que podem ser feitas usando a teoria
dos conjuntos: pertinncia, contm, est contido, unio, diferena, dife-
rena simtrica e interseo. Alm dessas, as operaes de igualdade, de-
sigualdade e atribuio tambm so vlidas.
Nem todas as LPs oferecem tipos conjunto potncia como um tipo pr-
definido. Mesmo quando fornecem esse tipo, eles so oferecidos de ma-
neira restrita. Em PASCAL, por exemplo, s permitido construir con-
junto de valores de tipos discretos primitivos pequenos. Considere o e-
xemplo 3.9 em PASCAL:
TYPE
Carros = (corsa, palio, gol);
ConjuntoCarros = SET OF Carros;
VAR
Carro: Carros;
CarrosPequenos: ConjuntoCarros;
BEGIN
Carro:= corsa;
CarrosPequenos:= [palio, gol]; /*atribuicao*/
CarrosPequenos:= CarrosPequenos + [corsa]; /*uniao*/
CarrosPequenos:= CarrosPequenos * [gol]; /*intersecao*/
if Carro in CarrosPequenos THEN /*pertinencia*/
if CarrosPequenos >= [verde, azul] THEN /*contem*/
Exemplo 3. 9 - Uso de Conjuntos em PASCAL
As restries de PASCAL visam permitir uma implementao eficiente
de conjuntos. As variveis conjunto so armazenadas em uma palavra de
memria e as operaes so implementadas sobre cadeias de bits usando
as instrues da mquina. Por exemplo, a figura 3.15 mostra como a uni-
o do cdigo do exemplo 3.10 pode ser implementada atravs da opera-
o de OR.
VAR S: SET OF [ 'a .. 'h' ];
BEGIN
S := ['a', 'c', 'h'] + ['d'];
END;
Exemplo 3.10 - Operao de Unio em PASCAL
Observe na figura 3.15 que a representao binria indica quais elementos
esto presentes no conjunto. Observe ainda que a operao ou binria
produz em S o resultado esperado da unio.
76


Figura 3. 15 - Implementao de Operao de Unio
LPs que no possuem tipos conjunto necessitam criar abstraes de dados
que os implemente. Isto torna a programao mais trabalhosa e frequen-
temente menos eficiente. Embora C no possua tipos conjunto, ela forne-
ce acesso a operaes bit a bit que facilitam a sua implementao atravs
de vetores.
Existe um grande nmero de problemas que poderiam ser resolvidos caso
fosse permitido definir conjuntos de valores compostos (conjuntos de s-
trings ou registros). Um exemplo de problema que seria facilmente resol-
vido o de identificar os vocbulos comuns presentes em dois arquivos
contendo texto. Bastaria inserir os vocbulos presentes em cada arquivo
em dois conjuntos distintos e realizar a sua interseco.
Contudo, a implementao de tipos conjunto com valores compostos no
pode ser feita to facilmente quanto em PASCAL porque esses valores
no so enumerveis, sendo difcil estabelecer uma relao um a um com
vetores de bits. Normalmente, a implementao dos tipos conjunto tem de
envolver listas ou tabelas hash, o que torna a implementao das opera-
es de conjunto muito menos eficientes. Talvez seja por isso que a maio-
ria das LPs opta por no incluir o tipo conjunto de valores compostos co-
mo tipo pr-definido da LP.
Uma abordagem que tem sido comum em LPs orientadas a objetos con-
siste em definir na biblioteca padro uma classe (normalmente chamada
de "set") que se comporte aproximadamente como um tipo conjunto po-
tncia. Nestas LPs os conjuntos podem envolver qualquer classe de obje-
tos. Esta a abordagem de SMALLTALK e JAVA. Contudo, nem todas
as operaes de conjunto mencionadas anteriormente costumam ser dis-
ponibilizadas.
3.2.5 Tipos Recursivos
Tipos recursivos so tipos de dados cujos valores so compostos por va-
lores do mesmo tipo. Um tipo recursivo definido em termos de si mes-
mo. De modo geral, o formato de definio de um tipo recursivo R :
R ::= < parte inicial > R < parte final >
Por exemplo, listas so seqncias que podem ter zero ou mais valores.
Formalmente, o tipo lista pode ser definido como sendo a unio disjunta
1 0 1 1 0 0 0 1 1 0 1 0 0 0 0 1 0 0 0 1 0 0 0 0 OR S
['a', 'c', 'h' ] [ 'd' ]
=
['a', 'c','d', 'h' ]
77
do tipo lista vazia com os valores do produto cartesiano do tipo do ele-
mento da lista com o tipo lista:
Tipo Lista ::= Tipo Lista Vazia | (Tipo Elemento x Tipo Lista)
Note que a definio do Tipo Lista usa o prprio Tipo Lista.
A cardinalidade de um tipo recursivo infinita. Isto verdade mesmo que
o tipo do elemento da lista seja finito. O conjunto de valores do tipo listas
infinitamente grande (no podendo ser enumerado) embora toda lista
individual seja finita.
Em PASCAL, C, C++ e ADA tipos recursivos devem ser definidos a par-
tir de ponteiros. O exemplo 3.11 mostra a definio do tipo n de uma
lista de inteiros em C e C++:
struct no { class no {
int elem; int elem;
struct no* prox; no* prox;
}; };
Exemplo 3. 11 - Definio do Tipo Recursivo N de Lista em C e C++
LPs puramente orientadas a objetos, tal como JAVA, permitem que tipos
recursivos sejam definidos diretamente. Veja o exemplo 3.12 em JAVA:
class no {
int elem;
no prox;
};
Exemplo 3. 12 - Definio do Tipo Recursivo N de Lista em JAVA
3.2.5.1 Tipos Ponteiros
O uso de tipos ponteiros (tambm chamados de apontadores) no se res-
tringe a estruturas de dados recursivas. Por exemplo, eles podem ser utili-
zados para simular vetores dinmicos em C. Mesmo assim, eles so inclu-
dos nesta subseo porque ocorrem mais frequentemente dentro dessas
estruturas.
Ponteiro um conceito de baixo nvel relacionado com a arquitetura dos
computadores. O conceito de ponteiros surgiu em decorrncia da
necessidade de se alocar memria de acordo com as demandas dinmicas
do programa e, desta forma, evitar o sub ou superdimensionamento do
espao de memria alocado.
bem verdade que LPs funcionais, lgicas e orientadas a objetos contor-
nam este problema sem que se tenha de incluir ponteiros como conceito
da LP. Contudo, como LPs imperativas so fortemente influenciadas pelo
78
arquitetura de computadores, o conceito de ponteiros foi naturalmente
herdado do conceito equivalente de endereamento indireto dos assem-
blers.
Outro fator determinante na incluso de ponteiros em LPs tem sido a efi-
cincia da execuo de programas. Implementaes de LPs que no pos-
suem ponteiros devem necessariamente incluir um sistema de gerencia-
mento de memria de modo a controlar o espao utilizado e liberar o es-
pao que no esteja mais sendo utilizado. Esse sistema tende a tornar o
processamento computacional do programa um pouco mais lento.
O conjunto de valores de um tipo ponteiro so os endereos de memria e
o valor nil. O valor nil indica que o ponteiro no est referenciando qual-
quer clula de memria.
Ponteiros so valores de primeira classe em muitas LPs, tais como PAS-
CAL, MODULA-2, C, ADA e C++. Nessas LPs pode-se criar variveis
do tipo ponteiro, passar ponteiros como parmetros, inclu-los em estrutu-
ras de dados, etc. Com o uso de ponteiros possvel criar-se estruturas de
dados complexas (um grafo, por exemplo) onde as conexes so
representadas por ponteiros armazenados nos ns.
Tais estruturas complexas podem ser atualizadas para adicionar, remover
um n ou modificar a ligao entre ns atravs da manipulao de pontei-
ros. Isto mais radical que a atualizao em registros e vetores, pois nes-
ses a atualizao afeta os contedos das variveis mas no a sua estrutura.
Considere o programa C do exemplo 3.13. Nesse exemplo so criadas
uma lista com nmero palndromos e outra com nmeros cuja soma dos
dgitos que os formam igual a dez. Observe que todos os nmeros pa-
lndromos fazem parte da outra lista. Note tambm que a atualizao do
ponteiro do n de valor 343 para apontar para o n de valor 181 provoca a
remoo do n 262 de ambas as listas.
#define nil 0
#include <stdlib.h>
#include <stdio.h>
typedef struct no* listaint;
struct no {
int cabeca;
listaint cauda;
};
listaint anexa (int cb, listaint cd) {
listaint l;
l = (listaint) malloc (sizeof (struct no));
l->cabeca = cb;
l->cauda = cd;
79
return l;
}
void imprime (listaint l) {
printf("\nlista: ");
while (l) {
printf("%d ",l->cabeca);
l = l->cauda;
}
}
main() {
listaint palindromos, soma10, aux;
palindromos = anexa(343, anexa(262, anexa(181, nil)));
soma10 = anexa(1234, palindromos);
imprime (palindromos);
imprime (soma10);
aux = palindromos ->cauda;
palindromos ->cauda = palindromos ->cauda->cauda;
free(aux);
imprime (palindromos);
imprime (soma10);
}
Exemplo 3. 13 - Uso de Ponteiros
As operaes mais comuns sobre ponteiros so atribuio, alocao,
desalocao e derreferenciamento. A atribuio pode ser feita entre
variveis do tipo ponteiro ou entre uma varivel ponteiro e um endereo
de memria. O exemplo 3.14 ilustra atribuies a ponteiros em C:
int *p, *q, r; // dois ponteiros para int e um int
q = &r; // atribui endereco onde variavel r esta alocada a q
p = q; // atribui endereco armazenado em q a p
Exemplo 3. 14 - Atribuio em Ponteiros
Uma operao de alocao armazena dinamicamente um espao de me-
mria que ser acessado via ponteiros e retorna o endereo da clula ini-
cial alocada.
int* p = (int*) malloc (sizeof(int));
Na maioria das LPs existe uma operao de desalocao que fora a
liberao de reas de memria que esto alocadas e apontadas pelo
ponteiro. Em C, chama-se a funo free da biblioteca padro passando o
ponteiro para a clula inicial da rea de memria a ser desalocada.
free (p);
80
A operao de derreferenciamento retorna o contedo do que apontado
pelo ponteiro. A operao de derreferenciamento pode ser implcita (tal
como em ALGOL 68 e FORTRAN 90) ou explcita (como em C ou
PASCAL). Compare os cdigos em FORTRAN 90 e C do exemplo 3.15:
INTEGER, POINTER :: PTR int *p;
PTR = 10 *p = 10;
PTR = PTR + 10 *p = *p + 10;
Exemplo 3. 15 - Derreferenciamento Implcito em FORTRAN 90 e Explcito em C
Uma operao caracterstica de C sobre ponteiros a aritmtica. O exem-
plo 3.16 mostra vrias operaes aritmticas sobre ponteiros.
p++;
++p;
p = p + 1;
p--;
--p;
p = p - 3;
Exemplo 3. 16 - Aritmtica de Ponteiros em C
Essas operaes deslocam o ponteiro de sua posio original. Por exem-
plo, se p aponta para uma varivel que ocupa dois bytes, a instruo p++
faz com que o ponteiro passe a apontar para uma posio dois bytes adi-
ante na memria. Outra operao tpica de C o uso de indexao em
ponteiros:
p[3]; // equivale a *(p + 3)
possvel entender a operao de indexao como um atalho sinttico
para a combinao da operao de adio a um ponteiro com a operao
de derreferenciamento subsequente.
3.2.5.1.1 Ponteiros Genricos
C inclui ainda uma categoria especial de ponteiros: aqueles que apontam
para void (o tipo nulo). O tipo void usado em C para indicar que uma
funo no retorna valor e para permitir a existncia de ponteiros genri-
cos, isto , ponteiros que podem apontar para valores de qualquer tipo.
importante destacar aqui que no permitido criar-se variveis do tipo
void, apenas ponteiros para void:
void* p; // ponteiro generico
Ponteiros genricos podem provocar problemas de erros de tipos se usa-
dos de maneira indiscriminada. Por exemplo, se um ponteiro genrico
estiver apontando para uma clula que armazena um float e tentssemos
acess-la como um int, no haveria como verificar (ou avisar, no caso de
81
C) que estamos violando o sistema de tipos da linguagem. C evita este
problema impedindo que os valores derreferenciados de ponteiros para
void possam ser utilizados. O exemplo 3.17 mostra um erro dessa nature-
za identificado em tempo de compilao em C:
int f, g;
void* p;
f = 10;
p = &f;
g = *p; // erro: ilegal derreferenciar ponteiro p/ void
Exemplo 3. 17 - Tentativa de Derreferenciamento de Ponteiro para Void em C
Ponteiros genricos servem para criar funes genricas que gerenciam a
memria (por exemplo, malloc). Outra aplicao interessante a criao
de listas com elementos de tipos heterogneos.
3.2.5.1.2 Problemas com Ponteiros
A programao com ponteiros requer ateno dos programadores para
no provocar erros na sua manipulao. Esses erros podem ser bastante
perigosos em determinadas situaes. Os problemas mais comuns rela-
cionados com a manipulao desse tipo de dados so:
a) Baixa Legibilidade: Ponteiros so conhecidos como o GOTO das
estruturas de dados. A menos que se tenha muita disciplina e cuidado,
a manipulao de ponteiros provoca muitos erros e obscura quanto a
seus efeitos. Por exemplo, numa atribuio de ponteiros
p->cauda = q;
no podemos saber, atravs de uma simples inspeo qual estrutura de
dados est sendo atualizada. Seu efeito pode ser at radical, mudando
toda a estrutura de dados atravs da introduo de um ciclo. Como vi-
mos no exemplo das listas de palndromos e de nmeros cujos dgitos
somam dez (exemplo 3.13), ponteiros permitem que duas ou mais es-
truturas de dados compartilhem os mesmos valores. Qualquer atuali-
zao de uma estrutura pode afetar diretamente as outras. Tais atuali-
zaes no ficam explcitas no cdigo do programa, dificultando a le-
gibilidade.
b) Erro de violao do sistema de tipos: Em LPs nas quais o tipo apon-
tado pelo ponteiro no restrito, expresses contendo ponteiros po-
dem ser avaliadas com valores de tipos diferentes do esperado origi-
nalmente, provocando erros na avaliao em tempo de execuo. A-
lm de obscurecer a programao, isso tambm inviabiliza a checagem
esttica de tipos. O exemplo 3.18 em C:
82
int i, j = 10;
p = &j; // p aponta para a variavel inteira j
p++; // p nao necessariamente aponta mais para um inteiro
i = *p + 5; // valor imprevisivel atribuido a i
Exemplo 3.18 - Violao de Sistemas de Tipo pelo Uso de Ponteiros
c) Objetos Pendentes: Nesse tipo de problema, clulas de memria alo-
cadas dinamicamente se tornam inacessveis. O exemplo 3.19 mostra
como objetos pendentes podem ser criados em C.
int* p = (int*) malloc (10*sizeof(int));
int *q = (int*) malloc (5*sizeof(int));
p = q; // celula que era apontada por p torna-se inacessivel
Exemplo 3.19 - Objetos Pendentes em C
A ocorrncia frequente de objetos pendentes provoca o problema de
vazamento de memria, isto , o espao disponvel para alocao vai
se reduzindo.
d) Referncias Pendentes: Nesse tipo de problema, um ponteiro possui
como valor um endereo de uma varivel dinmica desalocada. Os
exemplos a seguir mostram situaes em C onde ocorrem referncias
pendentes. No caso mais frequente, ilustrado no exemplo 3.20, uma a-
tribuio de ponteiros seguida de uma operao de desalocao deixa
um ponteiro com referncia pendente.
int* p = (int*) malloc(10*sizeof(int));
int* q = p;
free(p); // q aponta agora para area de memoria desalocada
Exemplo 3.20 - Referncia Pendente em C por Desalocao Explcita
Em outra situao, ilustrada no exemplo 3.21, uma referncia a uma vari-
vel local atribuda a uma varivel com tempo de vida mais longo:
main() {
int *p, x;
x = 10;
if (x) {
int i;
p = &i;
}
// i nao existe mais, mas p continua apontado para onde i estava
// alocado
}
Exemplo 3.21 Referncia Pendente por Fim de Tempo de Vida
83
Outra causa potencial das referncias pendentes a falta de inicializao
automtica de ponteiros estabelecida na definio da LP. Neste caso, o
programador pode esquecer de inicializar e acessar qualquer posio de
memria, tal como no exemplo 3.22.
int* p;
*p = 0; // p aponta para um lugar desconhecido da memoria
Exemplo 3.22 - Referncia Pendente por Falta de Alocao
3.2.5.2 Tipo Referncia
Em C++ possvel criar variveis do tipo referncia. O conjunto de valo-
res desse tipo o conjunto de endereos das clulas de memria. O e-
xemplo 3.23 mostra o uso de uma varivel do tipo referncia em C++:
int res = 0;
int& ref = res; // ref passa a referenciar res
ref = 100; // res passa a valer 100
Exemplo 3.23 - Tipo Referncia em C++
Todas as variveis que no so de tipos primitivos em JAVA so do tipo
referncia. Enquanto as variveis do tipo referncia em C++ no podem
ter seu valor alterado aps a inicializao, as variveis de JAVA podem
ser atribudas a diferentes instncias de objetos.
3.2.6 Strings
Strings correspondem a uma seqncia de caracteres. So tipicamente
usadas para a realizao de entrada e sada de dados e para armazenar da-
dos no numricos.
Strings so consideradas aqui como uma categoria a parte de tipos porque
no existe consenso sobre como devem ser tratadas. Algumas LPs (como
PERL, SNOBOL e ML) as tratam como tipos primitivos, fornecendo um
conjunto especfico e pr-definido de operaes que podem ser realizadas
sobre elas.
Outras LPs (tais como C e PASCAL) as tratam como mapeamentos fini-
tos (tipicamente vetores de caracteres). As operaes sobre strings so as
mesmas fornecidas para o tipo vetor. Normalmente, estas LPs fornecem
um conjunto de funes de biblioteca padro para a manipulao de s-
tring. C, por exemplo, fornece funes para cpia de strings (strcpy),
comparao (strcmp), etc.
JAVA considera strings como sendo um novo tipo, na verdade, uma clas-
se da biblioteca padro com as suas prprias operaes.
84
Uma outra abordagem consiste em considerar uma string como o tipo re-
cursivo lista. Esta abordagem normalmente adotada por LPs (MIRAN-
DA, PROLOG e LISP) que embutem o tipo lista como pr-definido na
prpria LP.
Operaes comuns sobre strings so atribuio e comparao (PASCAL),
concatenao e seleo de caracter ou substring (ADA). PERL oferece
um conjunto poderoso de operaes sobre string, como por exemplo, ca-
samento de padres atravs de expresso regular.
Existem trs formas de implementao de strings:
Esttica: o tamanho da string pr-definido ou definido em compila-
o e no modificado durante a execuo. Tipicamente, o resto da s-
tring preenchido com brancos. Exemplos so as strings de COBOL.
Dinmica Limitada: o tamanho mximo da string pr-definido ou
definido em compilao, mas pode variar durante a execuo at o
mximo especificado. Em C, o final da string indicado pelo caracter
nulo \0. Em alguns dialetos de PASCAL, as strings possuem no ndice
0, um caracter cujo cdigo ASCII corresponde ao tamanho corrente da
string.
Dinmica: o tamanho da string pode variar livremente durante a exe-
cuo. Exemplos so as strings de PERL, SNOBOL e APL. Neste tipo
de implementao sempre ocorre alocao e desalocao de memria
quando h necessidade de aumentar uma string.
3.3 Consideraes Finais
O passo inicial para aprender uma LP estudar os tipos de dados ofereci-
dos por ela. Esse estudo envolve saber qual o conjunto de valores de cada
tipo, como esses tipos so armazenados na memria, quais operaes po-
dem ser feitas sobre cada tipo, como elas funcionam e como so imple-
mentadas, bem como identificar as limitaes de cada tipo. Tambm
importante conhecer como os tipos podem ser combinados entre si para
formar novos tipos de dados.
Nesse captulo foi discutido em detalhe como os tipos de dados mais co-
muns se apresentam em LPs, como so implementados e quais operaes
so oferecidas sobre eles. Em especial, foi apresentada uma classificao
abstrata que engloba a maior parte dos tipos de dados oferecidos pelas
linguagens de programao. Essa classificao reapresentada na figura
3.16.
85















Figura 3. 16 - Classificao de Tipos de Dados
3.4 Exerccios
1. Ponteiros so causadores potenciais de erros em programao. D e-
xemplos, com trechos de cdigo em C, de erros causados por pontei-
ros que provocam violao dos sistemas de tipos da linguagem, ocor-
rncia de objetos pendentes e ocorrncia de referncias pendentes.

2. Uma diferena significativa entre a definio de tipos primitivos em
C++ e JAVA se refere ao intervalo de valores de cada tipo. Enquanto
em JAVA os intervalos foram fixados na definio da LP, em C++ a
implementao do compilador que define esses intervalos. Compare
estas duas abordagens, justificando a opo de cada uma dessas lin-
guagens.

3. Em geral, a verificao de uso de ndice fora dos limites do vetor s
pode ser verificado em tempo de execuo. Algumas LPs, como JA-
VA, PASCAL e MODULA-2 fazem a verificao dinmica dos ndi-
ces. Outras, como C, C++ e FORTRAN no fazem essa verificao.
Justifique porque algumas LPs adotaram uma postura e outras adota-
ram uma postura oposta. Uma terceira postura, intermediria, seria ge-
rar cdigo com verificao dinmica na fase de desenvolvimento e
sem verificao dinmica para a fase de uso. Discuta essa opo em
termos dos conceitos usados para justificar as opes das LPs mencio-
nadas acima.

Tipos
Primitivos Compostos
Booleanos
Inteiros
Caracteres
Decimais
Ponto Flutuantes
Enumerados
Intervalos
Unies
Strings Recursivos
Mapeamentos
Conjuntos Potncia
Produtos Cartesianos
Ponteiros
Livres Disjuntas
Finitos Funes
86
4. Arrays podem ser estticos, semi-estticos, semi-dinmicos e dinmi-
cos. Enquanto a criao de arrays estticos e semi-estticos pode ser
feita facilmente em C, a construo de arrays semi-dinmicos e din-
micos envolve um maior esforo de programao. Responda como os
mecanismos de C permitem a criao desses tipos de arrays. Ilustre
com exemplos.

5. Produtos cartesianos, unies, mapeamentos e tipos recursivos so ca-
tegorias de tipos compostos de dados. Ilustre, com exemplos em C,
cada um desses conceitos. Crie ainda um novo tipo de dados que com-
bine trs desses conceitos e diga qual a sua cardinalidade.

6. Determine a cardinalidade de cada um dos tipos abaixo, usando os
conceitos de produto cartesiano, unies e mapeamentos para explicar a
cardinalidade dos tipos compostos:

enum sexo {masculino, feminino};
enum estado_civil {solteiro, casado, divorciado};
enum classe {baixa, media, alta};
enum instrucao {primario, secundario, superior};
union cidadania {
enum classe c;
enum instrucao i;
}
struct pessoa {
enum sexo s;
enum estado_civil e;
union cidadania c;
};
struct amostra {
int n;
struct pessoa p[10];
}

7. Considere o seguinte programa escrito em C++:

#include <iostream>
int& xpto (int sinal) {
int p = 4;
if (!sinal) {
p*=sinal;
} else {
p++;
}
return p;
87
}

void ypto () {
int c[1000];
int aux;
for (aux = 0; aux < 1000; aux++) {
c[aux] = aux;
}
}

main() {
int a = 1;
int& b = xpto(a);
ypto();
cout << b;
}

Determine quais sero as sadas possveis do programa acima. Expli-
que sua resposta.

8. Considere o seguinte programa escrito em C:

#include <stdio.h>
int* calcula(int a){
int p;
p = a;
if (a) {
p*=3;
} else {
p++;
};
return &p;
}
main() {
int x = 1;
int* b = calcula(x);
int* c = calcula (0);
printf("%d\n", *b);
}

Descreva o que ocorre nesse programa. Justifique sua resposta.

9. Listas heterogneas so estruturas de dados capazes de armazenar no
seu campo de informao valores de tipos distintos. Uma forma de
implementar listas heterogneas em C atravs do uso de unies. Ou-
88
tra forma atravs do uso de ponteiros para void. Mostre, atravs de
exemplos de cdigo em C, como se pode fazer para definir listas hete-
rogneas usando essas duas abordagens (no preciso implementar as
operaes de lista, apenas a definio da estrutura de dados). Compare
e discuta essas solues em termos de redigibilidade (das operaes da
lista) e flexibilidade (em termos de necessidade de recompilao do
mdulo lista quando for necessrio alterar ou incluir um novo tipo de
dado no campo informao).

10. Em C possvel criar estruturas de dados heterogneas, isto , com
elementos de tipos diferentes. Contudo, ao se retirar um elemento da
estrutura necessrio identificar quais operaes so vlidas sobre o
elemento sendo removido de modo a evitar erros no sistema de tipos
da LP. De quais maneiras isso pode ser feito? Compare as solues
propostas em termos de redigibilidade e em termos da necessidade de
alterao do cdigo usurio quando da alterao ou incluso de um
novo tipo de elemento na lista.

11. Caracterize a diferena entre unies livres e unies disjuntas em ter-
mos de cardinalidade e segurana quanto ao sistema de tipos da lin-
guagem. Discuta e exemplifique como as unies de C podem ser utili-
zadas para criar estruturas de dados heterogneas (isto , que abrigam
tipos de informao distintos), destacando como o programador (ou a
linguagem, se for o caso) deve proceder para garantir o uso desse tipo
de dado sem que haja violaes do sistema de tipos. Discuta ainda
quo genrica pode ser uma estrutura de dados heterognea que se ba-
seia no mecanismo de unies.

12. Muito embora JAVA seja fortemente influenciada por C, os projetistas
dessa LP resolveram incluir o tipo boolean, o qual no existe em C.
Explique porque essa deciso foi tomada. D exemplo de situao na
qual a postura de C traz alguma vantagem. Faa o mesmo em relao a
postura de JAVA. Justifique suas respostas.

89

Captulo IV Variveis e Constantes


O cone persiste porque a potncia da proecia persiste.`
Luis lernando Verissimo ,se reerindo a Karl Marx,
Os valores dos tipos de dados devem ser armazenados em entidades de
computao para que possam ser manipulados pelos programas. Variveis
e constantes so as principais entidades nas quais valores podem ser ar-
mazenados. Nesse captulo so discutidas as caractersticas fundamentais
associadas a esses conceitos.
nfase especial dada na forma como feito o gerenciamento da mem-
ria principal do computador para o armazenamento dessas entidades e
tambm nos mecanismos disponveis nas LPs para a programao de per-
sistncia de dados.
4.1 Variveis
David Watt reproduz em seu livro [WATT, 1990] uma conhecida citao
de Dijkstra, um famoso cientista da computao:
Uma vez que o programador tenha entendido o uso de variveis, ele
entendeu a essncia da programao.
Segundo Watt, essa frase pode ser considerada um exagero, visto que o
conceito de variveis em LPs funcionais e lgicas diferente do conceito
nas LPs imperativas. Por outro lado, ela se aplica perfeitamente ao para-
digma imperativo, que caracterizado fundamentalmente pelo uso de va-
riveis e pelo conceito de atribuio.
No paradigma imperativo, uma varivel uma entidade de computao
que contm um valor, o qual pode ser inspecionado e atualizado sempre
que necessrio. Mais especificamente, uma varivel uma abstrao para
uma ou mais clulas de memria responsveis por armazenar o estado de
uma entidade de computao. Variveis so normalmente usadas em pro-
gramas para modelar entidades, animadas ou inanimadas, do mundo real
ou virtual, que possuam estados.
O conceito de varivel muito importante para linguagens de programa-
o. Para se ter uma idia, a mudana de linguagem de mquina para as-
sembly marcada primordialmente pela substituio dos endereos
numricos de memria por nomes, tornando os programas muito mais
legveis e fceis de escrever e manter. Alm disso, essa mudana permitiu
que se escapasse do problema de endereamento absoluto de memria,
90
visto que, a partir da, o tradutor se responsabiliza por converter os nomes
em endereos reais.
O exemplo 4.1 apresenta um trecho de cdigo em C onde uma varivel
criada e inicializada com o valor 7. Em seguida, seu valor incrementado
de 3.
unsigned x;
x = 7;
x = x + 3;
Exemplo 4. 1 - Criao, Inicializao e Atualizao de Varivel
Durante a execuo desse trecho de programa, um nmero de clulas su-
ficientes (por exemplo, 2 bytes) para armazenar um valor do tipo unsig-
ned int ser alocado e o endereo da clula inicial dessa rea ser associa-
do s referncias varivel x no cdigo do exemplo. O passo seguinte
atribui o valor 7 a essa rea de memria. O ltimo passo da execuo
consiste em obter o valor armazenado nessa rea, som-lo ao valor inteiro
3 e atribuir o resultado quela rea de memria. A figura 4.1 ilustra grafi-
camente esses passos.












Figura 4. 1 - Operaes sobre Variveis
4.1.1 Propriedades das Variveis
Uma varivel pode ser caracterizada pelo seu nome, endereo, tipo, valor,
tempo de vida e escopo de visibilidade.
00000000
00000000
00000000
00000000
FF01
FF02
FF00
FF03
x
Passo 1 Espao de
memria alocado e
associado varivel x
00000000
00000111
00000000
00000000
FF01
FF02
FF00
FF03
x
Passo 2 Valor 7 atri-
budo a x colocado
no espao de memria
associado a x
00000000
00001010
00000000
00000000
FF01
FF02
FF00
FF03
x
Passo 3 Valor cor-
rente de x somado a
3 e resultado coloca-
do no espao de me-
mria associado a x
91
Normalmente, variveis possuem um nome definido pelo programador.
Esses nomes obedecem a regra de formao de identificadores da LP. No
exemplo 4.1, o nome da varivel x. Embora programadores frequente-
mente pensem em variveis como nomes para locaes de memria, exis-
tem variveis que no possuem nomes. Neste caso, elas s podem ser re-
ferenciadas atravs de variveis ponteiros (apontadores).
O endereo de uma varivel a posio na memria da primeira clula
ocupada pela varivel. No exemplo representado na figura 4.1, o endereo
da varivel x FF01. Algumas linguagens, tais como C e C++, permitem
que o endereo das variveis seja acessado livremente. Embora tal prtica
oferea flexibilidade ao programador, ela pode ser perigosa e comprome-
ter a segurana e confiabilidade dos programas. O trecho de programa em
C, apresentado no exemplo 4.2, mostra como um programa pode obter o
endereo de uma varivel e us-lo para acessar uma outra rea de mem-
ria (a qual no deveria ser acessvel pelo programa).
char l;
char *m;
m = &l + 10;
printf(&l = %p\nm = %p\n*m = %c\n, &l, m, *m);
Exemplo 4. 2 - Acesso a Endereo de Varivel
Diz-se que variveis so sinnimas (aliases) quando existe mais de
um nome referenciando o mesmo endereo em um mesmo ambiente de
amarrao. Isto prejudica a legibilidade de programas. O exemplo 4.3, em
C++, mostra a ocorrncia de sinonmia entre as variveis r e s:
int r = 0;
int &s = r;
s = 10;
Exemplo 4. 3 - Sinonmia
No exemplo 4.3 a varivel inteira r criada e inicializada com o valor
zero. Em seguida, criada uma varivel s do tipo referncia associada
varivel r. A atribuio do valor 10 a s tambm produz como efeito cola-
teral a alterao do valor de r para 10. Tal efeito colateral prejudica a le-
gibilidade do programa porque no fica explcito no cdigo que a varivel
r est sendo atualizada no momento da atribuio a s.
O tipo de uma varivel determina o conjunto de valores que essa varivel
pode assumir. A varivel x do exemplo 4.1 do tipo unsigned int. Ele po-
de ser definido explicitamente, determinado atravs de regras sintticas
ou atravs da semntica de uso da varivel. C, por exemplo, requer que se
declare o tipo da varivel junto com o seu nome. J verses iniciais de
FORTRAN usavam a segunda abordagem, uma vez que determinavam o
92
tipo da varivel pela letra inicial de seu nome. Por exemplo, variveis ini-
ciadas pelas letras I, J e K so do tipo inteiro. Por sua vez, ML emprega
eventualmente a terceira abordagem, uma vez que possvel definir o ti-
po da varivel atravs das operaes que so realizadas sobre ela.
O valor de uma varivel corresponde ao seu contedo corrente e compa-
tvel com o seu tipo. Na medida que uma varivel atualizada, seu valor
alterado. No exemplo da figura 4.1, a varivel x criada com valor des-
conhecido (na verdade, o valor da varivel x aps a criao depende da
configurao de bits existente na memria no momento da criao). Pos-
teriormente, o valor 7 atribudo a x. Por fim, o valor de x modificado
novamente, agora para 10.
O tempo de vida de uma varivel corresponde ao perodo em que ela exis-
te, ou seja, o tempo em que existem clulas de memria alocadas para a
varivel. O tempo de vida de variveis globais corresponde a todo o per-
odo de execuo do programa onde foi declarada. O tempo de vida de
variveis locais corresponde ao perodo em que o bloco onde foram de-
claradas se encontra alocado. Desde que variveis dinmicas podem ser
criadas e destrudas em qualquer instante do programa, o seu tempo de
vida no obedece a nenhuma regra padro.
Variveis podem ser transientes (aquelas cujo tempo de vida limitado
pelo tempo de ativao do programa que as criou), ou persistentes (aque-
las cujo tempo de vida transcende ao tempo de ativao de um programa
particular).
O escopo de visibilidade de uma varivel determina o trecho do programa
onde essa varivel pode ser referenciada. importante no confundir es-
copo com tempo de vida. Em algumas situaes, uma varivel pode estar
alocada num determinado ponto de execuo de um programa, mas no
ser acessvel naquele ponto. No exemplo 4.4, em C, todas as duas vari-
veis x existem durante a execuo de main. Contudo, s a varivel local
visvel dentro de main. Portanto, nesse exemplo, o valor impresso 10.
int x = 15;
main() {
int x;
x = 10;
printf(x = %d\n, x);
}
Exemplo 4. 4 - Tempo de Vida X Escopo de Visibilidade
93
4.1.2 Atualizao de Variveis Compostas
Variveis compostas so variveis cujo tipo composto. Elas se com-
pem de outras variveis.
O contedo de variveis compostas pode ser inicializado, inspecionado e
atualizado de forma completa ou seletiva. Enquanto na forma completa
todos os componentes da varivel composta so inicializados, inspecio-
nados ou atualizados em uma nica etapa, na forma seletiva essas opera-
es so aplicadas individualmente sobre os seus componentes. Considere
o exemplo 4.5 em C:
struct data { int d, m, a; };
struct data f = {7, 9, 1965};
struct data g;
g = f;
g.m = 17;
Exemplo 4. 5 - Atualizaes Completa e Seletiva em C
A segunda linha de cdigo do exemplo 4.5 mostra a inicializao comple-
ta da varivel composta f. A penltima linha mostra a atualizao comple-
ta da varivel g. Por sua vez, a ltima linha mostra a atualizao seletiva
do componente m da varivel g.
LPs normalmente oferecem a operao de atualizao seletiva em vari-
veis compostas. Contudo, o programador deve ter cuidado ao usar esse
tipo de operao para ela no provocar problemas de violao na abstra-
o de dados. A ltima linha do exemplo 4.5 ilustra uma situao na qual
isso ocorre. Observe que o valor atribudo ao componente m (referente ao
ms da data) o nmero 17, o qual no denota ms algum. Tal atribuio
quebra a abstrao de dados para o tipo struct data, uma vez que os valo-
res desse tipo deveriam ser apenas datas.
Claramente, esse tipo de problema tambm poderia acontecer com a atua-
lizao completa. No entanto, como o valor atribudo uma data comple-
ta, a tendncia que esse tipo de erro ocorra com menor frequncia.
4.2 Constantes
Certos valores necessitam ser armazenados durante uma computao,
contudo no devem ser modificados ao longo do programa. Constantes
so entidades de programao usadas para armazenar esses valores. Tal
como variveis, elas tambm possuem nome, endereo, tipo, valor, tempo
de vida e escopo de visibilidade.
Constantes podem ser pr-definidas na LP ou podem ser declaradas pelo
usurio. Enquanto constantes pr-definidas so referenciadas atravs de
94
smbolos ou identificadores associados aos valores dos tipos de dados da
LP, constantes declaradas pelo usurio devem ter um nome associado no
programa.
No exemplo 4.6, em C, os valores 3, g, bola so exemplos de cons-
tantes pr-definidas. Como os valores de constantes no devem ser modi-
ficados ao longo do programa, C no permite que se possa obter o ende-
reo de uma constante pr-definida. A linha comentada no exemplo 4.6
provocaria um erro de compilao caso no fosse um comentrio. Por ou-
tro lado, o compilador C no impede que sejam feitas atribuies para
constantes apontadas por ponteiros, como na ltima linha do exemplo 4.6.
Tal permissividade pode gerar um erro de execuo ou, ainda mais grave,
alterar o valor da constante pr-definida.
char x = g;
int y = 3;
char* z = bola;
/* int *w = &3; */
*z = c;
Exemplo 4. 6 - Constantes Pr-Definidas em C
Constantes declaradas pelo usurio so teis para aumentar a legibilidade
e a modificabilidade de programas. No exemplo 4.7, em C++, o trecho de
programa se torna muito mais legvel ao usar o nome pi ao invs da cons-
tante 3.1416. Esse mesmo caso ilustra como o uso de constantes declara-
das pelo usurio pode aumentar a modificabilidade de programas. Caso se
queira aumentar a preciso do valor de pi para 3.14159, basta alterar a
linha de declarao da constante e todo o programa ser atualizado.
const float pi = 3.1416;
float raio, area, perimetro;
raio = 10.32;
area = pi * raio * raio;
perimetro = 2 * pi * raio;
Exemplo 4. 7 Uso de Constantes Declaradas pelo Usurio
Pode-se argumentar que, com os recursos de substituio de cadeias de
caracteres oferecidos pelos editores de texto, tambm seria fcil modificar
o programa sem o uso de constantes declaradas pelo usurio. Esse argu-
mento no totalmente verdadeiro porque poderia ser trabalhoso ter de
alterar os vrios arquivos que poderiam compor o programa. Alm disso,
poderiam ocorrer situaes onde o valor 3.1416 no fosse referente a
constante pi. Nesse caso, uma alterao completa no texto do programa
poderia introduzir erros difceis de serem encontrados.
95
Tentativas de alterar o valor de constantes declaradas pelo usurio em
C++ produzem erros de compilao. O mesmo ocorre quando se tenta
fazer um ponteiro comum apontar para uma constante. Isso s permitido
quando se indica na declarao do ponteiro que ele apontar para uma
constante (assim, o compilador pode impedir a alterao de qualquer va-
lor apontado por esse ponteiro). No exemplo 4.8, as linhas de cdigo co-
mentadas gerariam erros de compilao, uma vez que so tentativas de
alterar o valor de uma constante ou de fazer um ponteiro comum apontar
para ela. Observe que a ltima linha do trecho vlida porque z decla-
rado como um ponteiro para uma constante.
int* x;
const int y = 3;
const int* z;
// y = 4;
// y++;
// x = &y;
z = &y;
Exemplo 4. 8 - Constantes Declaradas em C++
ANSI C tambm adota a palavra reservada const introduzida por C++,
embora com um intuito diferenciado. A finalidade de const em C anun-
ciar objetos que possam ser colocados em memria somente de leitura, e
talvez aumentar oportunidades de otimizao [KERNIGHAN & RIT-
CHIE, 1989]. Segundo Kernighan e Ritchie, exceto por ter que diagnosti-
car tentativas explcitas de alterar objetos const, um compilador pode ig-
norar este qualificador. Talvez seja por isso que alguns compiladores C
no tratem as linhas comentadas do exemplo 4.8 como erros, apresentan-
do apenas mensagens de aviso a respeito da tentativa de alterao de
constantes.
4.3 Armazenamento de Variveis e Constantes
Enquanto variveis e constantes transientes apenas necessitam ser arma-
zenadas na memria principal do computador durante a execuo do pro-
grama que as utiliza, as persistentes tambm necessitam ser armazenadas
em memria secundria para serem utilizadas por outros programas ou
por execues futuras do programa que as criou.
Entender como variveis e constantes so armazenadas na memria prin-
cipal e secundria pode ser de grande valia para um programador. Nessa
seo, apresenta-se uma viso geral sobre como o armazenamento em
memria principal e em memria secundria pode ser realizado.
96
4.3.1 Armazenamento na Memria Principal
A memria principal de um computador consiste de uma enorme sequn-
cia contgua e finita de bits. Contudo, a menor unidade de memria que
pode ser diretamente endereada corresponde normalmente ao tamanho
da palavra do computador (8 bits, 16 bits, 32 bits, etc.). Pode-se imaginar,
ento, a memria como sendo um vetor de tamanho finito cujos elemen-
tos correspondem ao tamanho da palavra do computador, o qual chama-
remos aqui de vetor de memria.
Quando da execuo de um programa, alm do cdigo executvel, deve
ser reservado um espao na memria para armazenar os dados manipula-
dos pelo programa. Uma forma de se fazer isso seria identificar quais va-
riveis so utilizadas no programa, qual o seu tamanho e reservar um es-
pao na memria correspondente a soma dos tamanhos de todas as vari-
veis. A primeira linguagem de programao de alto nvel, FORTRAN,
adotou essa abordagem. Contudo, essa alternativa apresenta alguns pro-
blemas.
Muitas vezes, a memria disponvel acaba sendo mal utilizada. Isso ocor-
re porque o tamanho das variveis necessrias pode variar de execuo
para execuo de programa. Tipicamente, a soluo adotada por progra-
madores nessas linguagens usar o maior tamanho possvel da varivel.
Contudo, falhas eventuais nessas estimativas podem ocasionar erros in-
convenientes em tempo de execuo. Alm disso, essa soluo provoca
desperdcio de memria por se reservar mais espao para as variveis do
que o necessrio no caso geral.
Outra causa de desperdcio de memria nessa abordagem a necessidade
de alocar espao para as variveis locais de todos os subprogramas. Como
na execuo de um programa muito freqente se invocar apenas um
subconjunto dos subprogramas, todo o espao ocupado pelas variveis
locais dos subprogramas no utilizados tambm reservada desnecessari-
amente.
Por fim, essa abordagem impede a implementao de subprogramas re-
cursivos, isto , subprogramas que invocam a si mesmos durante sua pr-
pria execuo. importante perceber a necessidade de existir um conjun-
to de variveis locais para cada subprograma ativo na execuo do pro-
grama. Como no se pode saber, antecipadamente, quantas chamadas de
subprogramas recursivos sero feitas durante a execuo de um progra-
ma, a alocao de memria para as variveis locais desses subprogramas
s pode ser realizada em tempo de execuo.
A soluo para lidar com esses problemas permitir a alocao dinmica
de memria, isto , somente reservar espao para as variveis quando o
97
tamanho necessrio for conhecido e quando elas forem efetivamente ne-
cessrias. Isto pode ocorrer somente durante a execuo do programa.
Para realizar alocao dinmica, os programas devem implementar ou
usar um mecanismo de gerenciamento dinmico de memria.
Uma possibilidade para implementao desse tipo de mecanismo alocar,
na medida que se constate a necessidade e se identifique o tamanho de
uma varivel, o nmero de palavras correspondentes ao seu tamanho no
vetor de memria. Para uma melhor utilizao da memria, as diferentes
variveis seriam alocadas contiguamente no vetor.
Contudo, essa soluo apresenta alguns problemas. Pode ser necessrio
aumentar o tamanho das variveis durante uma mesma execuo de um
programa. Alm disso, variveis podem ser teis em apenas um intervalo
da execuo do programa.
A memria se esgotaria rapidamente ao se usar a poltica de alocar as no-
vas reas necessrias sempre aps o ltimo bloco alocado e no reutilizar
as reas desalocadas. Por outro lado, utilizar as reas desalocadas deman-
daria excluses e movimentaes de elementos no vetor. Tais operaes
demandam bastante esforo computacional e diminuem a eficincia da
execuo. Alm disso, elas requeririam que as referncias s variveis no
cdigo executvel fossem atualizadas com o seu endereo absoluto sem-
pre que elas fossem alocadas ou realocadas. Essas operaes tambm pre-
judicariam a execuo eficiente do programa.
A forma mais comum de implementao dos mecanismos de gerencia-
mento dinmico de memria de execuo de programas subdivide a me-
mria de dados do programa em duas reas: a rea de pilha e a rea de
monte (ou heap). LPs que implementam esse tipo de gerenciamento de
memria so conhecidas como ALGOL-like.
A pilha utilizada para resolver os problemas de alocao de subprogra-
mas recursivos e de alocao dinmica dos dados locais de blocos e sub-
programas. Toda vez que ocorre uma entrada em um bloco ou uma invo-
cao de um subprograma, reserva-se no topo da pilha espao suficiente
para armazenar as variveis locais (e os parmetros) do bloco ou do sub-
programa. Ao se encerrar o bloco ou subprograma, o espao que lhe foi
destinado no topo da pilha liberado. Tal procedimento s possvel
porque o ltimo bloco ou subprograma ativado sempre o primeiro a ser
encerrado.
Com essa poltica, as variveis locais e parmetros s ficam alocados en-
quanto podem ser necessrias, isto , durante a execuo do bloco ou
subprograma. Por se tratar de uma pilha, a liberao da rea de memria
do subprograma envolve apenas o deslocamento do marcador do topo da
98
pilha, no requerendo movimentaes de elementos no vetor de memria.
Isso torna esse mecanismo bastante eficiente.
A figura 4.2 apresenta um exemplo de uso da pilha. Nesse exemplo, o
programa de nome p possui duas variveis inteiras chamadas a e b, e o
subprograma f possui dois parmetros x e y e uma varivel local z, todos
inteiros. O momento da execuo retratado na figura ocorre durante a
chamada de f pelo programa p.





Figura 4. 2 - Estado da rea de Pilha
Contudo, o problema das variveis cujo tamanho se modifica em tempo
de execuo ainda permanece. Alocar uma varivel desse tipo na pilha
provocaria um grande problema quando fosse necessrio aumentar o seu
tamanho. Seria preciso retirar todas as variveis localizadas acima dela na
pilha, copiando-as em outra rea de memria, aumentar o espao reserva-
do para a varivel e, posteriormente, empilhar as variveis retiradas na
mesma ordem em que haviam sido alocadas originalmente. Isto seria cla-
ramente ineficiente.
A soluo para o problema do aumento dinmico de variveis o uso da
rea de monte. O monte uma rea de memria que no obedece a uma
regra bem estabelecida como a pilha. Se existe necessidade de armazenar
uma varivel, reserva-se um espao no monte onde exista espao cont-
guo suficiente para alocar a varivel.
Se, em algum momento, esse tamanho se mostra insuficiente, pode-se
adotar duas alternativas. A primeira alternativa consiste em alocar um
novo espao de memria, agora de tamanho suficiente, e copiar os dados
da rea anteriormente alocada para esse novo espao. A segunda alterna-
tiva alocar um novo espao de memria suficiente apenas para comple-
tar o tamanho requerido e fazer uma ligao da rea previamente existen-
te com a nova rea alocada.
A figura 4.3 ilustra essas duas alternativas. A primeira alternativa ilus-
trada pela varivel x. Observe que x inicialmente uma varivel alocada
no monte com valor 10. Quando h necessidade de aumentar essa vari-
vel, um novo espao no monte alocado, o valor original de x copiado,
x passa a referenciar essa nova rea e o espao ocupado anteriormente por
ela desalocado. A segunda alternativa ilustrada na figura 4.3 pela vari-
vel y. Essa varivel aumentada alocando-se uma nova rea de memria
p
f
a
b
x
y
z
10
9
10
9
10
99
e ligando o final da rea corrente ao incio dessa nova rea atravs do uso
de um ponteiro.




Figura 4. 3 - rea de Monte
Note que as duas alternativas apresentam vantagens e desvantagens. En-
quanto a alternativa de cpia torna o redimensionamento da varivel uma
operao de alto custo, ela no causa qualquer prejuzo no acesso aos
componentes da varivel. J a alternativa de completar a varivel no
causa maiores custos para redimensionar a varivel, mas torna mais lento
o acesso aos seus componentes.
Voc pode estar se perguntando o porqu de dividir a memria em pilha e
monte. Por que no usar toda a memria apenas como se fosse um mon-
te? A resposta para essa pergunta que isso no seria to eficiente.
A alocao dinmica de memria no monte demanda que subreas da
memria sejam alocadas e desalocadas ao longo da execuo do progra-
ma. Como no existe uma regra bem estabelecida sobre quando se deve
alocar ou desalocar uma varivel, necessrio possuir estruturas de dados
adicionais responsveis por controlar as reas livres e j alocadas. Sempre
que uma nova rea de memria necessria, percorre-se a lista de reas
livres procurando uma rea com espao suficiente para armazenar a vari-
vel. Toda vez que uma rea de memria alocada, ela retirada da lista
de reas livres e colocada na lista de reas alocadas. Toda vez que uma
rea desalocada, a movimentao inversa realizada.
Esse tipo de controle demanda um esforo computacional muito maior
que o necessrio para a alocao dinmica via pilha. Portanto, a combina-
o de pilha com monte visa obter o melhor dos dois modelos. Quando
uma varivel no necessita mudar seu tamanho dinamicamente ela alo-
cada na pilha para se obter mais eficincia nas operaes de alocao e
desalocao. Por outro lado, quando isso necessrio, utiliza-se o monte.
A figura 4.4 mostra um exemplo do uso de gerenciamento de memria
dinmica com reas de pilha e monte. Note que, para se ter acesso s va-
riveis na rea do monte, necessrio existir uma varivel ponteiro ou
referncia armazenada na pilha. Na figura 4.4, a varivel p exerce esse
papel.

10
10 20 30
x y
x
100





Figura 4. 4 - reas de Pilha e Monte
4.3.1.1 Pilha de Registros de Ativao
Nessa seo apresenta-se um modelo simplificado de como podem ser
implementadas as reas de pilha de linguagens ALGOL-like. Nesse
modelo, a pilha composta por registros de ativao (RA) de subprogra-
mas, os quais armazenam vrios tipos de informaes e dados relativos a
uma ativao de um subprograma. A figura 4.5 apresenta um esquema de
um registro de ativao:










Figura 4. 5 - Esquema de Registro de Ativao de Subprogramas
O campo constantes locais armazena os valores das constantes declara-
das localmente. O campo variveis locais armazena as variveis declara-
das localmente. O campo parmetros armazena os parmetros do sub-
programa. O campo link dinmico uma referncia para a base do regis-
tro de ativao anterior. Ele indica o endereo base a ser usado para aces-
sar as informaes do RA aps o fim do subprograma corrente. O campo
link esttico uma referncia para a base do RA do subprograma imedia-
tamente externo ao subprograma do registro de ativao corrente, isto , o
do subprograma onde ele foi definido. O campo endereo de retorno
armazena o endereo da prxima instruo a ser executada ao final da
execuo do subprograma.
Considere o exemplo 4.9 na linguagem ADA:
p
Pilha
Monte
variveis locais
parmetros
link dinmico
link esttico
endereo de retorno
constantes locais
101
procedure Principal is
x: integer;
procedure Sub1 is
a, b, c: integer;
procedure Sub2 is
a, d: integer;
begin --Sub2
a := b + c;
d := 8; -- 1
if a < d then
b := 6;
Sub2; -- 2
end if;
end Sub2;
procedure Sub3 (x: integer) is
b, e: integer;
procedure Sub4 is
c, e: integer;
begin --Sub4
c := 6; e:= 7;
Sub2; -- 3
e := b + a;
end Sub4;
begin --Sub3
b := 4; e := 5;
Sub4; -- 4
b := a + b + e;
end Sub3;
begin --Sub1
a := 1; b:= 2; c:= 3;
Sub3(19); -- 5
end Sub1;
begin --Principal
x := 0;
Sub1; -- 6
end Principal;
Exemplo 4. 9 - Registros de Ativao e Chamadas de Subprogramas em ADA
No exemplo 4.9 o programa Principal chama o subprograma Sub1, que
chama o subprograma Sub3, que chama o subprograma Sub4, que chama
o subprograma Sub2, o qual se chama recursivamente. A figura 4.6 mos-
tra o estado da pilha de registros de ativao quando o programa passa
pela linha marcada pelo nmero comentado -- 1. Os demais nmeros co-
102
mentados ao final de algumas linhas (por exemplo, -- 4) representam en-
dereos de retorno dos subprogramas.

d 8
a 9
Dinmico
Esttico
Endereo de Retorno: --2
d 8
a 5
Dinmico
Esttico
Endereo de Retorno: -- 3
e 7
c 6
Dinmico
Esttico
Endereo de Retorno: -- 4
e 5
b 4
x 19
Dinmico
Esttico
-- 5
c 3
b 6
a 1
Dinmico
Esttico
Endereo de Retorno: -- 6
x 0
Figura 4. 6 - Estado de Pilha de Registros de Ativao
Note que o link esttico de Sub2 e Sub3 aponta para a base do registro de
ativao de Sub1, o link esttico de Sub4 aponta para a base do registro de
Sub3 e que o link esttico de Sub1 aponta para a base do registro de Prin-
cipal. Perceba ainda que, para obter o valor de a no cdigo de Sub4 ne-
cessrio seguir dois passos na cadeia de links estticos at encontr-lo em
Sub1. Observe por fim que o link dinmico sempre aponta para a base do
registro de ativao anterior. Isso ocorre at quando existe uma chamada
recursiva. Por exemplo, o link dinmico do RA correspondente a chama-
da recursiva de Sub2 o RA da chamada anterior de Sub2.
4.3.1.2 Gerenciamento do Monte
O controle da rea de memria no monte um pouco mais complexo (e
computacionalmente menos eficiente) que na pilha, uma vez que no e-
xiste uma regra geral definindo onde a rea de memria requisitada deve
ser alocada nem em qual momento essa rea deve ser desalocada. Portan-
Sub2
Sub4
Sub3
Sub1
Principal
Sub2
103
to, implementaes diferentes de LPs podem seguir polticas significati-
vamente distintas para a realizao dessas operaes.
Uma forma possvel de gerenciar a memria do monte envolve a utiliza-
o de duas listas de controle contendo informaes a respeito dos espa-
os de memria livres e ocupados. A LED (Lista de Espaos Disponveis)
contm, em cada um de seus elementos, informaes (local de incio e
tamanho) sobre cada bloco de memria que se encontra livre para ser alo-
cado. Por sua vez, a LEO (Lista de Espaos Ocupados), contm, em cada
um de seus elementos, informaes (local de incio e tamanho) sobre cada
bloco de memria que se encontra ocupado. Quando o programa faz uma
requisio para alocao de memria, percorre-se a LED em busca de um
elemento cuja rea de memria atenda essa requisio. Quando encon-
trado, ele retirado da LED e transferido para a LEO. Quando o progra-
ma faz uma requisio para desalocao de memria, busca-se o elemento
correspondente na LEO. Quando encontrado, ele removido da LEO e
transferido para a LED.
Diversas polticas de alocao de memria podem ser adotadas para esco-
lher qual elemento da LED ser alocado dentre todos que satisfazem a
requisio de memria. Pode-se escolher o primeiro encontrado, ou o de
tamanho mais prximo, ou o de maior tamanho. Essa escolha determinar
a ocorrncia mais rpida ou no do problema de fragmentao da mem-
ria, isto , a situao na qual os espaos de memria disponveis na LED
se tornam to pequenos que no mais atendem s requisies. Nesses ca-
sos, haver necessidade de alocao de memria adicional para o progra-
ma ou da execuo de um processo de desfragmentao.
Enquanto o momento de alocao de memria sempre bem definido
pelo programa (isto , o momento no qual feita uma operao de criao
de varivel ou constante dinmica), o momento de desalocao depende
da linguagem de programao. Em algumas LPs, o controle do momento
de desalocao feito pelo programador (por exemplo, em C, C++ e
PASCAL). Nesses casos, o programa deve conter instrues explcitas
para chamar a operao de desalocao. Em outras LPs, o prprio sistema
de implementao da LP define o momento de desalocao. Nesse caso, o
programa no contm operaes explcitas de desalocao e utiliza-se um
mecanismo chamado de coletor de lixo. Esse o caso de JAVA.
A primeira soluo permite a construo de programas mais eficientes (o
construtor do programa quem diz o momento no qual se deve desalocar
a memria). Contudo, essa soluo deixa sob responsabilidade do pro-
gramador o processo de desalocao de memria, sendo menos confivel
e muito mais trabalhosa. Erros frequentes de programao associados a
essa soluo so a falta de desalocao, criando objetos pendentes e pro-
104
vocando vazamentos de memria, e a desalocao indevida de memria,
deixando-se referncias pendentes e provocando erros de acesso indevido.
Por sua vez, a segunda soluo envolve a utilizao de um coletor de lixo
que, de tempos em tempos, entra em ao transformando as clulas de
memria no mais utilizadas em clulas livres disponveis para alocao.
Se isso torna a vida do programador muito mais fcil (ele normalmente
no precisa se preocupar com a gerncia da memria), essa soluo faz
com que programas sejam normalmente executados menos eficientemen-
te, alm de tornar mais complexa a implementao da LP.
A perda de eficincia computacional ocorre porque um gerenciador gen-
rico de memria no pode tirar vantagem de situaes especficas de mo-
do a tornar o programa mais eficiente. Normalmente, o coletor de lixo
tem de percorrer todos os ponteiros das estruturas de dados do programa
marcando qual a memria utilizada. Isso demora muito mais do que desa-
locar explicitamente a memria na medida que ela no mais necessria.
Outro problema com o uso de coletor de lixo a falta de controle sobre o
momento em que ele vai atuar (frequentemente, isso ocorre quando falta
memria livre). Tal fato pode ocorrer em um momento no qual a eficin-
cia de execuo seja mais crtica. O risco disso ocorrer pode ser reduzido
quando o gerenciamento deixado na mo do programador.
O uso do coletor de lixo em JAVA permite a adoo de uma poltica de
alocao de memria no monte quase to eficiente quanto a alocao de
memria na pilha. Sempre que ocorre uma requisio de alocao de
memria dinmica no programa, aloca-se uma rea de memria do tama-
nho requisitado imediatamente aps o final da ltima rea de memria
alocada pelo programa. Assim, no necessrio manter listas de espaos
disponveis e ocupados, embora se chegue ao final da rea de monte mais
rapidamente. Nesse momento, o coletor de lixo entra em ao removendo
basicamente as reas no mais utilizadas e realocando de maneira cont-
gua as reas ocupadas de memria para o incio da rea de monte.
4.3.2 Armazenamento na Memria Secundria
Variveis e constantes necessitam ser armazenadas na memria secund-
ria e tambm precisam ser recuperadas de l. O termo persistncia de
dados usado para designar e agrupar temas relacionados com o arma-
zenamento e recuperao de dados em memria secundria.
A existncia de uma rea da cincia da computao (Banco de Dados)
dedicada primordialmente ao estudo e investigao de tcnicas para mo-
delagem e implementao dos processos de persistncia de dados ilustra a
sua relevncia nos sistemas de computao.
105
As Linguagens de Programao tambm tm se interessado em incorporar
mecanismos para facilitar a implementao de programas nos quais ocor-
ram esses processos. A pretenso maior e final oferecer LPs nas quais
no exista qualquer distino entre entidades transientes e persistentes.
4.3.2.1 Arquivos
Um exemplo comum de variveis persistentes em LPs so os arquivos, os
quais so responsveis por armazenar valores em dispositivos de memria
secundria (discos, fitas, etc.). Desse modo, esses valores podem continu-
ar existindo independentemente da execuo de um programa, o que
caracteriza essas variveis como persistentes.
Normalmente, os valores dos arquivos so armazenados contigamente
na memria secundria. A maior parte das LPs restringe os modos de a-
cesso e atualizao de arquivos. Em geral, proibida a atribuio de ar-
quivos completos.
Arquivos podem ser seriais ou diretos. A distino entre arquivos seriais e
diretos est fundamentalmente na forma como os componentes desses
arquivos so acessados. Os componentes dos arquivos seriais so acessa-
dos sequencialmente, isto , para acessar o componente de ordem n+1
estritamente necessrio acessar previamente o componente de ordem n.
Componentes de arquivos diretos no precisam obedecer esta regra, po-
dendo ser acessados a qualquer momento. A implementao de arquivos
diretos pode envolver o tratamento do arquivo como um vetor de compo-
nentes ou pode ser feita atravs do uso de uma tabela indexadora dos
componentes do arquivo.
LPs normalmente requerem que programas realizem uma operao de
abertura do arquivo antes de acessar seus componentes e uma operao de
fechamento do arquivo aps o seu uso. Alm disso, por questes de efici-
ncia de acesso, esses programas necessitam converter os dados em me-
mria para uma sequncia de bytes na memria secundria. Considere o
exemplo 4.10, em C:
#include <stdio.h>
struct data {
int d, m, a;
};
struct data d = {7, 9, 1999};
struct data e;
main() {
FILE *p;
char str[30];
printf("\n\n Entre com o nome do arquivo:\n");
106
gets(str);
if (!(p = fopen(str,"w"))) {
printf ("Erro! Impossivel abrir o arquivo!\n"); exit(1);
}
fwrite (&d, sizeof(struct data), 1, p);
fclose(p);
p = fopen(str,"r");
fread (&e, sizeof(struct data), 1, p);
fclose (p);
printf ("%d/%d/%d\n", e.a, e.m, e.d);
}
Exemplo 4. 10 - Persistncia atravs de Arquivos em C
Observe que a operao fwrite converte a varivel transiente d numa ca-
deia de bytes para salv-la num arquivo binrio e a operao fread con-
verte uma cadeia de bytes do arquivo na varivel transiente e. Observe
tambm que para acessar individualmente o valor do campo m, armaze-
nado na varivel persistente, de maneira independente dos demais cam-
pos, seria necessrio realizar operaes de posicionamento de cursor no
arquivo. Isto contrasta com a forma direta de acesso fornecida para as va-
riveis transientes d e e.
Outra forma comum de se obter persistncia de dados atravs de inter-
faces com gerenciadores de bancos de dados. Nesse caso, os valores de
variveis transientes precisam ser transformados em registros de tabelas
em um banco de dados atravs do uso de comandos em linguagens como
SQL. Nesse caso, a linguagem de programao no emprega ou oferece
um tipo de dados persistente. O programador deve escrever sentenas
compostas por termos da linguagem de gerenciamento de banco de dados
e por valores das variveis transientes. O gerenciador executa essas sen-
tenas para armazenar e recuperar os valores das variveis persistentes.
O exemplo 4.11 mostra um trecho de um programa em JAVA no qual se
utiliza um comando em SQL para recuperar informaes de pessoas (no-
me, idade e data de aniversrio) armazenadas em um banco de dados.
int idadeMaxima = 50;
Statement comando = conexao.createStatement();
ResultSet resultados = comando.executeQuery (
"SELECT nome, idade, nascimento FROM pessoa " +
"WHERE idade < " + idadeMaxima);
);
while (resultados.next()) {
String nome = resultados.getString("nome");
int idade = resultados.getInt("idade");
107
Date nascimento = resultados.getDate("nascimento");
}
Exemplo 4. 11 - Recuperao de Dados Persistentes Atravs de Gerenciador de Banco de Dados
A terceira linha do trecho de cdigo do exemplo 4.11 onde se envia um
comando em SQL para o gerenciador de banco de dados. Observe a utili-
zao da varivel idadeMaxima no comando SQL para somente selecio-
nar pessoas de idade inferior ao valor desta varivel. Os valores retorna-
dos pela consulta ao banco de dados so armazenados na varivel resul-
tados.
4.3.2.2 Persistncia Ortogonal
A maioria das LPs fornece diferentes tipos para variveis persistentes e
transientes. Em C, por exemplo, uma varivel transiente pode ser de
qualquer tipo, mas uma varivel persistente deve ser do tipo arquivo bin-
rio ou texto.
Porque no permitir que variveis persistentes sejam de tipos primitivos,
registros, vetores ou variveis annimas pertencentes a estruturas dinmi-
cas? Idealmente, todos os tipos deveriam ser permitidos tanto para vari-
veis transientes quanto para persistentes. Alm disso, no deveria haver
qualquer distino no cdigo que lida com variveis persistentes daquele
que lida com variveis transientes. A identificao de persistncia se daria
atravs da percepo da continuidade da necessidade de utilizao da
varivel.
Uma LP aplicando esses princpios se qualifica como ortogonalmente
persistente [ATKINSON & MORRISON, 1995]. Ela seria simplificada
por no ter tipos especiais e comandos ou procedimentos para entrada e
sada. O programador estaria liberado de ficar convertendo dados de um
tipo de dados persistente para um transiente, ao necessitar usar os dados,
ou vice-versa, ao necessitar armazenar os dados.
Acredita-se que cerca de 30% do cdigo de uma aplicao destinado a
essas converses [JORDAN & ATKINSON, 1998]. Isso oferece uma i-
dia do quo impactante podem ser esses processos de converso entre
variveis persistentes e transientes no projeto, programao e desempe-
nho das aplicaes.
Contudo, a implementao de sistemas ortogonalmente persistentes um
grande desafio. Dentre as principais dificuldades podem ser citadas a per-
da eventual de eficincia computacional, a necessidade de recuperao de
dados quando a execuo do programa interrompida inesperadamente e
a demanda por integrao dos mltiplos e diversos ambientes envolvidos
nas aplicaes, tais como sistemas operacionais, bancos de dados e lin-
108
guagens de programao. Por isso, sistemas ortogonalmente persistentes
ainda so raros.
4.3.2.3 Serializao
Alguns aspectos importantes necessitam ser considerados ao se imple-
mentar a persistncia de variveis e constantes:
1. A varivel (ou constante) transiente deve ser convertida de sua repre-
sentao na memria primria para uma sequncia de bytes na mem-
ria secundria.
2. Como os valores absolutos dos ponteiros na memria no tero signi-
ficado na prxima vez que o programa for executado, esses ponteiros
devem ser relativizados quando armazenados.
3. As variveis (ou constantes) annimas apontadas por valores ponteiros
tambm devem ser armazenadas e recuperadas.
4. Ao restaurar uma varivel (ou constante) da memria secundria, os
ponteiros devem ser ajustados de modo a respeitar as relaes existen-
tes anteriormente entre as variveis annimas.
Como uma varivel tem de ser convertida do seu layout na memria
para uma representao serial em memria secundria e vice-versa, esses
processos so chamados de serializao (escrita da varivel no disco) e
desserializao (leitura da varivel no disco).
Embora seja extremamente conveniente suportar esses processos direta-
mente, eles podem sobrecarregar a LP e sua implementao. C++ uma
LP que opta por no fornec-los diretamente. Ela deixa para o programa-
dor implementar funes membro especiais nas suas classes para suportar
a serializao e desserializao. Assim, o criador da classe, que quem
melhor conhece como os componentes da classe devem ser lidos ou escri-
tos, responsvel por programar esses processos. Contudo, isso requer
um esforo muito maior de programao.
JAVA possui um mecanismo de serializao de objetos que possibilita
transformar qualquer objeto que implemente a interface Serializable nu-
ma sequncia de bytes que pode ser posteriormente restaurada no objeto
original. Isto verdade inclusive em um ambiente de rede de computado-
res, o que significa que o mecanismo de serializao compensa automati-
camente as diferenas entre sistemas operacionais, ou seja, possvel cri-
ar um objeto em uma mquina Windows, serializ-lo e envi-lo pela rede
para uma mquina Unix onde ele ser reconstrudo corretamente. O pro-
gramador no precisa se preocupar com a forma de representao nas di-
ferentes mquinas, nem com a ordenao de bytes e outros detalhes.
109
Embora o mecanismo de serializao de objetos ainda no seja o ideal
para persistncia (existem esforos no sentido de tornar JAVA ortogo-
nalmente persistente [JORDAN & ATKINSON, 1998]), ele permite no
apenas salvar a imagem do objeto mas tambm seguir e salvar todos os
objetos referenciados nesse objeto e nos objetos referenciados por ele.
Essa caracterstica remove do programador todo o trabalho necessrio
para manter o esquema de serializao seguindo todas as conexes de ob-
jetos, enxugando significativamente o cdigo dos programas.
O exemplo 4.12 ilustra o uso do mecanismo de serializao em JAVA.
Nesse exemplo criada uma lista w contendo os seis primeiros nmeros
mpares positivos, a qual armazenada no arquivo impares.dat. Posteri-
ormente, essa lista lida do arquivo na varivel z.
import java.io.*;
public class Impares implements Serializable {
private static int j = 1;
private int i = j;
private Impares prox;
Impares(int n) {
j = j + 2;
if(--n > 0)
prox = new Impares(n);
}
Impares() {
j = j + 2;
prox = new Impares(9);
}
public String toString() {
String s = "" + i;
if(prox != null)
s += " : " + prox.toString();
return s;
}
public static void main(String[] args) {
Impares w = new Impares(6);
System.out.println("w = " + w);
try {
ObjectOutputStream out =
new ObjectOutputStream(
new FileOutputStream("impares.dat"));
out.writeObject("Armazena Impares");
out.writeObject(w);
out.close();
110
ObjectInputStream in =
new ObjectInputStream(
new FileInputStream("impares.dat"));
String s = (String)in.readObject();
Impares z = (Impares)in.readObject();
System.out.println(s + ", z = " + z);
in.close();
} catch(Exception e) {
e.printStackTrace();
}
}
}
Exemplo 4. 12 - Serializao em JAVA
importante observar que a operao
out.writeObject(w)
responsvel por gravar a lista w no arquivo. Note que toda a lista de m-
pares gravada sem que seja necessrio criar uma operao para percor-
rer seus elementos e grav-los um a um. De modo similar, a operao
Impares z = (Impares)in.readObject()
responsvel por resgatar o contedo da lista no arquivo e criar a lista z
sem que seja necessrio criar separadamente cada elemento dessa lista.
Observe ainda que, para conseguir esse tipo de comportamento, basta de-
clarar que a classe Impares implementa a interface Serializable.
4.4 Consideraes Finais
Nesse captulo destacou-se a importncia do papel de variveis e constan-
tes em LPs. Discutiu-se, em particular, como o armazenamento de vari-
veis e constantes pode ser feito na memria principal e secundria do
computador.
Algumas questes chave sobre esse tema devem ser esclarecidas ao se
estudar uma nova LP. Por exemplo, importante saber se ela permite o
acesso direto ao endereo das variveis e constantes, se permite ao pro-
gramador definir suas prprias constantes, se as constantes definidas se
comportam efetivamente como uma constante pr-existente na LP.
Especial ateno deve ser tomada no estudo do gerenciamento de mem-
ria e persistncia de dados. necessrio saber se a responsabilidade pela
desalocao de memria do programador ou se a LP possui coletor de
lixo. Em caso do programador ser o responsvel, deve-se conhecer de que
forma e em que escopo do programa essa operao deve ser realizada.
111
Com relao persistncia, fundamental conhecer o poder de persistn-
cia oferecido pela LP. Assim, deve-se saber quais os tipos de dados que
admitem persistncia, como se pode denotar e manipular um tipo persis-
tente, e como a implementao do mecanismo de persistncia.
4.5 Exerccios
1. Sinonmia ocorre quando uma mesma varivel ou constante pode ser
referenciada por mais de um nome em um mesmo ambiente de amar-
rao. Mostre exemplos de situaes nas quais isso pode ocorrer em C
e JAVA.

2. Mostre situaes nas quais a permisso de acesso ao endereo de vari-
veis pode ser benfica ao programador. Mostre tambm quando isso
pode ser prejudicial a confiabilidade dos programas.

3. Edite o programa seguinte, compile-o e o execute. Relate o que ocor-
reu na compilao e durante a execuo.







4. Faa um programa com as linhas de cdigo do exemplo 4.8, retirando
os comentrios das linhas de cdigo comentadas. Compile o programa
usando seu compilador C e seu compilador C++. Relate o que aconte-
ceu.

5. Explique as vantagens de se utilizar um modelo de gerenciamento de
memria principal com regies de pilha e monte em relao aos mode-
los que s utilizam a pilha ou s utilizam o monte ou que alocam vari-
veis apenas em tempo de carga do programa.

6. Enquanto implementaes de linguagens de programao que incluem
o conceito de ponteiros (por exemplo, C e C++) tipicamente deixam
parte da alocao e desalocao de memria sob a responsabilidade do
programador, implementaes de linguagens que no possuem pontei-
ros (por exemplo, JAVA) devem necessariamente incluir um sistema
de gerenciamento de memria que controle e libere o espao de me-
mria utilizado. Compare estas diferentes abordagens em termos de e-
ficincia, redigibilidade e confiabilidade.
main() {
char* z = bola;
*z = c;
printf(%s\n, z);
printf(bola);
}
112

7. Identifique inicialmente a quais subprogramas pertencem as variveis
referenciadas nas linhas de cdigo do programa do exemplo 4.9. Veri-
fique se sua avaliao est correta seguindo a cadeia de links estticos
na figura 4.6.

8. Desenhe o estado da pilha de registros de ativao aps cada chamada
e encerramento de subprograma ao longo da execuo do programa do
exemplo 4.9.

9. Desenhe a pilha de registros de ativao, incluindo o endereo de re-
torno e as cadeias esttica e dinmica, quando a execuo atinge o
ponto marcado com # no seguinte esqueleto de programa ADA























10. Em JAVA, todos os objetos (variveis compostas) so alocados no
monte. Explique por que isso no to ineficiente em relao a LPs
que alocam essas variveis na pilha.

11. Explique como a persistncia de dados implementada em C. Compa-
re essa abordagem com a de JAVA em termos de redigibilidade e le-
gibilidade. Justifique sua resposta enfocando especialmente a imple-
procedure Main is
x: integer;
procedure A;
y: integer;
procedure B (k: boolean);
w: integer;
begin -- B
if k then
B (false);
else -- #;
end B;
procedure C;
z: integer;
begin -- C
B (true);
end C;
begin -- A
C;
end A;
begin -- Main
A;
end Main;

113
mentao de persistncia em estruturas de dados que utilizam pontei-
ros. Na sua opinio haveria algum efeito adverso em deixar sob o con-
trole da linguagem de programao todo o processo de persistncia de
dados?

12. Considere um programa que armazena em um grafo (usando encade-
amento dinmico) as distncias terrestres entre grandes cidades brasi-
leiras e grava essa estrutura em memria secundria para recuper-la
posteriormente em uma outra varivel. Compare como seriam os cdi-
gos das implementaes em C e JAVA desse programa em termos de
redigibilidade, legibilidade e confiabilidade.
114

Captulo V Expresses e Comandos


Computadores sao bons para seguir instruoes, mas nao para ler sua mente.`
Donald Knuth
Esse captulo aborda os conceitos envolvidos na construo e execuo
de expresses e comandos nas LPs. So apresentados e discutidos os tipos
fundamentais de expresses e comandos. Especial ateno dada aos es-
tudos sobre os modos de avaliao de expresses e sobre a presena de
efeitos colaterais em expresses e comandos.
5.1 Expresses
Uma expresso uma frase do programa que necessita ser avaliada e pro-
duz como resultado um valor. Expresses so caracterizadas pelo uso de
operadores, pelos tipos dos operandos e pelo tipo de resultado que produ-
zem. Operadores designam o tipo de operao a ser realizada. Operandos
indicam os valores sobre os quais a operao deve ser avaliada. O resul-
tado o valor produzido pela avaliao da expresso.
Expresses podem ser simples ou compostas. Expresses simples so a-
quelas que envolvem apenas um operador. Expresses compostas envol-
vem mais de um operador.
Um operador possui uma aridade (isto , o nmero de operandos requeri-
dos para a realizao da operao): operadores unrios possuem aridade
um (requerem um operando); operadores binrios possuem aridade dois
(requerem dois operandos); operadores ternrios possuem aridade trs
(requerem trs operandos); e assim progressivamente. Operadores ene-
rios possuem aridade varivel (podem ter qualquer nmero de operan-
dos).
Operadores podem ser pr-existentes na LP ou podem ser criados pelos
programadores atravs da definio de funes. Normalmente, a maior
parte dos operadores pr-existentes de uma LP so unrios e binrios
(com predominncia dos binrios). A presena de operadores pr-
existentes de maior aridade rara. Por exemplo, C, C++ e JAVA ofere-
cem um nico operador ternrio e nenhum de aridade superior.
Definies de funes permitem aos programadores criar operadores de
qualquer aridade (inclusive os de aridade mais alta). Para isso, basta defi-
nir a lista de parmetros da funo com o nmero correspondente a arida-
de desejada. Contudo, no muito conveniente criar operadores de arida-
115
de muito elevada pois isso torna o programa menos legvel, alm de redu-
zir a sua redigibilidade, desestimulando o uso desses operadores.
Operadores enerios so raros nas LPs mais conhecidas. JAVA e PAS-
CAL no possuem qualquer operador enerio. C e C++ permitem a cons-
truo de funes com nmero de parmetros varivel, possibilitando as-
sim aos programadores construrem operadores enerios. Contudo, no
existe qualquer operador enerio pr-existente nessas linguagens. APL e
dialetos de LISP oferecem operadores enerios.
Algumas LPs possuem um operador de composio para permitir que o-
peradores sejam operandos de outros operadores. Dessa forma, o progra-
mador pode criar novos operadores a partir da combinao dos operado-
res existentes. A funo impar do exemplo 2.19 ilustra esse tipo de ope-
rao em ML. APL tambm possibilita operaes de composio de ope-
radores.
Operadores podem ser usados com notao prefixada (o operador colo-
cado antes dos operandos), infixada (o operador colocado entre os ope-
randos) e posfixada (o operador colocado aps os operandos). Normal-
mente, operadores binrios so usados na forma infixada e os demais ope-
radores na forma prefixada. Uma mesma linguagem pode adotar diferen-
tes tipos de notao para os seus diversos operadores. Por exemplo, C e
C++ usam a notao prefixada para a maioria dos seus operadores unrios
(tais como, ! & sizeof), a notao infixada para seus operadores binrios e
ternrios (tais como, * / % && ?:) e a notao posfixada para alguns de
seus operadores unrios (++ --). Alm disso, algumas LPs permitem o
uso de mais de uma notao para um mesmo operador. Dialetos de LISP,
por exemplo, permitem que se use o operador + com as notaes prefixa-
da e infixada.
Expresses podem realizar operaes de diferentes naturezas. Algumas
produzem valores. Algumas consultam ou analisam propriedades de uma
varivel, constante ou tipo. Outras modificam os valores de variveis. Por
fim, outras destroem variveis.
5.1.1 Tipos de Expresses
Expresses podem ser classificadas de acordo com a natureza da opera-
o que realizam, com os tipos de seus operandos e com o tipo do resul-
tado que produzem.
5.1.1.1 Literais
Expresses literais so consideradas como o tipo mais simples de expres-
so. Essa expresso usada para produzir um valor fixo e manifesto de
116
um tipo e no demanda o uso de um operador explcito. A linha seguinte
ilustra vrios exemplos de expresses literais em C:
2.72 99 0143 c 0x63
A expresso 2.72 produz um valor do tipo real. Todas as demais expres-
ses da linha produzem o valor inteiro 99.
5.1.1.2 Agregao
Uma expresso de agregao utilizada para construir um valor compos-
to a partir de seus componentes. Os valores dos componentes do agregado
resultante so determinados atravs da avaliao das subexpresses que
compem a expresso de agregao. Um operador de agregao nor-
malmente denotado por marcadores de incio e fim da expresso de agre-
gao. Geralmente, as diferentes subexpresses que compem a expres-
so so separadas por um smbolo separador. O exemplo 5.1 mostra v-
rios exemplos de expresses de agregao em C.
int c[ ] = {1, 2, 3};
struct data d = {1, 7, 1999};
char * x = {a,b,c, \0};
int b[6] = {0};
char * y = abc;
Exemplo 5. 1 - Expresses de Agregao em C
Observe que, nas quatro primeiras linhas do exemplo 5.1, o operador de
agregao denotado pelos caracteres de abre ({) e fecha (}) chaves. Nas
trs primeiras linhas as subexpresses so separadas pelo caractere vrgu-
la. Note que o tamanho do vetor c definido pelo nmero de subexpres-
ses no agregado. Na quarta linha, uma nica subexpresso utilizada
para iniciar todos os valores do vetor b. Por isso, no se usa qualquer ca-
ractere de separao.
A expresso de agregao da ltima linha do exemplo 5.1 equivalente a
expresso de agregao da terceira linha. Em razo de agregados de ca-
racteres serem muito utilizados, C fornece uma comodidade sinttica para
a sua utilizao. Nessa notao simplificada (tal como a da ltima linha
do exemplo) o operador de agregao denotado pelos caracteres aspas
duplas e no preciso usar caracteres de separao.
As subexpresses utilizadas para formar o agregado podem ser estticas
(avaliadas em tempo de compilao) ou dinmicas (avaliadas em tempo
de execuo). O exemplo 5.2 ilustra o uso de expresses de agregao
para a inicializao de trs vetores em C.
void f(int i) {
int a[] = {3 + 5, 2, 16/4};
117
int b[] = {3*i, 4*i, 5*i};
int c[] = {i + 2, 3 + 4, 2*i};
}
Exemplo 5. 2 - Expresses de Agregao Estticas e Dinmicas
Observe que o agregado do primeiro vetor do exemplo 5.2 s contm
subexpresses estticas. O agregado do segundo vetor s contm subex-
presses dinmicas. J o agregado do terceiro vetor contm subexpres-
ses estticas e dinmicas.
C apenas possibilita o uso de expresses de agregao em operaes de
inicializao e para a criao de cadeias de caracteres constantes. Assim,
no possvel usar uma expresso de agregao para atribuir um valor a
uma estrutura ou a um vetor. ADA no possui essa restrio, permitindo o
uso de agregados tanto em inicializaes como em outras operaes, tal
como a de atribuio. O exemplo 5.3 ilustra o uso de agregados em ADA
para a inicializao e atribuio de registros.
type data is record
dia : integer range 1..31;
mes : integer range 1..12;
ano : integer range 1900..2100;
end record;
aniversario: data;
data_admissao: data:= (29, 9, 1989);
aniversario :=(28, 1, 2001);
data_admissao := (dia => 5, ano => 1980, mes => 2);
Exemplo 5. 3 - Expresses de Agregao em ADA
Note que as trs ltimas linhas do exemplo 5.3 utilizam agregados para
inicializar data_admissao e para atribuir valores a aniversario e da-
ta_admissao. Observe ainda que existem duas maneiras de criar agrega-
dos de registros. Uma utiliza a correspondncia posicional entre os valo-
res do agregado e os campos do registro. A outra utiliza correspondncia
nominal entre os valores e os campos, no sendo necessrio colocar os
valores na mesma ordem em que foram listados na definio do tipo.
5.1.1.3 Expresses Aritmticas
Expresses aritmticas so expresses similares s existentes nessa rea
da matemtica. Elas envolvem operandos e produzem valores de tipos
numricos (inteiros, decimais, ou ponto flutuante). Exemplos de expres-
ses aritmticas so aquelas que denotam as operaes de soma, subtra-
o, multiplicao e diviso. C possui os seguintes operadores aritmti-
cos: +, , *, / e %.
118
Os operadores + e podem ser usados como operadores unrios ou bin-
rios. Quando usados como binrios, eles denotam respectivamente as ope-
raes de soma e subtrao. O operador unrio provoca a inverso da
sinalizao do valor do operando, isto , um valor positivo se transforma
no mesmo valor negativo e vice-versa. O operador unrio + equivale a
uma funo identidade, retornando o mesmo valor do operando sem qual-
quer alterao.
Os operadores binrios *, % e / denotam respectivamente as operaes de
multiplicao, resto da diviso inteira e diviso (inteira e real). O opera-
dor / em expresses aritmticas pode denotar a operao de diviso inteira
(quando os operandos so de tipos inteiros) ou a operao de diviso real
(quando pelo menos um dos operandos real). O exemplo 5.4 mostra o
uso do operador / denotando diviso inteira e diviso real.
float f;
int num = 9;
f = num/6;
f = num/6.0;
Exemplo 5. 4 - Diviso Inteira e Diviso Real em C
No exemplo 5.4, a varivel f recebe o valor 1.0 na primeira atribuio e o
valor 1.5 na segunda atribuio.
5.1.1.4 Expresses Relacionais
Uma expresso relacional usada para comparar os valores de seus ope-
randos. Os valores dos operandos devem ser de um mesmo tipo. Tipica-
mente, o resultado de uma expresso relacional um valor booleano.
Contudo, em LPs que no possuem o tipo booleano (tal como C), o resul-
tado de uma expresso relacional um valor que indica se a expresso
relacional verdadeira ou falsa.
C possui os seguintes operadores relacionais: ==, !=, >, <, >= e <=. Os
operadores maior > , menor < , maior ou igual >= e menor ou igual <=
devem ser aplicados sobre operandos de um mesmo tipo cujos valores
sejam enumerveis. Os operadores de igualdade == e desigualdade !=
no necessitam cumprir essa exigncia.
5.1.1.5 Expresses Booleanas
Expresses booleanas realizam operaes da lgebra de Boole. Os ope-
randos das expresses booleanas so valores de tipo booleano ou equiva-
lente (quando a LP no oferece esse tipo). O tipo do resultado de uma ex-
presso booleana tambm um valor booleano ou equivalente.
119
C possui o operador booleano unrio de negao ! (conhecido como no
lgico) e os operadores booleanos binrios de conjuno && e disjuno
|| (mais conhecidos respectivamente como e e ou lgicos). Alm desses,
JAVA possui ainda os operadores & e | que realizam as mesmas opera-
es de && e ||. A necessidade para a existncia desses dois operadores
adicionais para realizar as operaes de e e ou lgico est relacionada
com a forma como essas operaes so implementadas computacional-
mente (esse tpico ser melhor compreendido na seo 5.1.2.4).
5.1.1.6 Expresses Binrias
Algumas LPs fornecem operadores para a construo de expresses bin-
rias. Essas expresses realizam operaes sobre conjuntos de dgitos bi-
nrios, isto , manipulam bits. A vantagem de se oferecer expresses bi-
nrias em LPs permitir a criao de programas com tempo de proces-
samento mais eficiente e que tambm consomem menos memria.
C oferece seis operadores para a construo de expresses binrias: &, |,
^, <<, >> e ~. Eles podem ser aplicados a operandos de tipos inteiros. O
operador & realiza a operao de conjuno binria (AND). O operador |
realiza a operao de disjuno inclusiva (OR). O operador ^ realiza a
operao de disjuno exclusiva (XOR). Os operadores << e >> reali-
zam respectivamente as operaes de deslocamento esquerda (LEFT
SHIFT) e deslocamento direita (RIGHT SHIFT). Todos esses operado-
res so binrios. O operador unrio ~ realiza a operao de complemento
a um (NOT). O exemplo 5.5 mostra o uso de cada um desses operadores
em C.
main() {
int j = 10;
...char c = 2;
printf(%d\n, ~0); /* imprime 1 */
printf(%d\n, j & c); /* imprime 2 */
printf(%d\n, j | c); /* imprime 10 */
printf(%d\n, j ^ c); /* imprime 8 */
printf(%d\n, j << c); /* imprime 40 */
printf(%d\n, j >> c); /* imprime 2 */
}
Exemplo 5. 5 - Expresses Binrias em C
Observe que os operandos dos operadores binrios so de tipos inteiros
diferentes no exemplo 5.5. Nesses casos, ocorre uma converso implcita
do valor do tipo de menor intervalo para um valor equivalente no tipo de
maior intervalo.
120
5.1.1.7 Expresses Condicionais
Expresses condicionais so compostas por vrias (pelo menos, mais que
uma) subexpresses e retornam como resultado o valor de exatamente
uma dessas subexpresses. So usadas para avaliar expresses condicio-
nalmente de acordo com o contexto. O exemplo 5.6 mostra o uso de duas
expresses condicionais em ML:
val c = if a > b then a - 3 else b + 5
val k = case i of
1 => if j > 5 then j - 8 else j + 5
| 2 => 2*j
| 3 => 3*j
| _ => j
Exemplo 5. 6 - Expresses Condicionais em ML
A primeira linha do exemplo 5.6 mostra uma expresso condicional if
formada por trs subexpresses. Caso a subexpresso a > b seja verdadei-
ra, o valor retornado pela expresso condicional ser o resultado da sub-
expresso a 3. Caso contrrio, o valor retornado ser o resultado da sub-
expresso b + 5. J a expresso condicional case, mostrada a partir da
segunda linha do exemplo 5.6, possui cinco subexpresses. Se o valor da
subexpresso i for 1, o resultado da expresso condicional corresponder
ao resultado da expresso condicional if j > 5 then j - 8 else j + 5. Se o
valor de i for 2, o resultado ser 2*j. Se o valor for 3, o resultado ser 3*j.
Por fim, qualquer outro valor de i far a expresso condicional retornar o
valor de j.
C, C++ e JAVA fazem uso do operador ternrio ?: para oferecer um ni-
co tipo de expresso condicional. O exemplo 5.7 mostra dois exemplos do
uso de expresses condicionais em JAVA. Na primeira linha, a expresso
condicional retornar o maior valor entre x e y. Esse valor ser atribudo a
varivel max. Na segunda linha, a expresso condicional usada para a-
valiar se z par ou no.
max = x > y ? x : y;
par = z % 2 == 0 ? true : false;
Exemplo 5. 7 - Expresses Condicionais em JAVA
Algumas LPs (tais como, PASCAL e ADA) no oferecem expresses
condicionais, forando os programadores a utilizarem os comandos con-
dicionais. O seguinte comando condicional usado para encontrar o mai-
or valor entre os nmeros x e y em ADA.
if x > y then max := x; else max := y;
121
Observe que o comando obriga uma repetio da atribuio varivel
max, tornando-se assim menos redigvel.
5.1.1.8 Chamadas de Funes
Chamadas de funes tambm so expresses. Elas produzem um resul-
tado atravs da aplicao de um operador (o nome da funo) a um ou
mais operandos (os valores correspondentes aos parmetros da funo).
Por exemplo, na chamada de funo f(a), f o operador e a o operando.
O resultado retornado por f(a) o resultado dessa expresso.
Funes possibilitam a criao de expresses com qualquer aridade. Por
exemplo, se quisermos definir uma expresso de aridade m, basta definir
uma funo com m parmetros. Nesse caso, o formato mais comum da
chamada o seguinte:
f(a
1
, a
2
, ..., a
m
)
Esse formato de chamada de funo conhecido como posicional e
adotado pela maior parte das LPs (por exemplo, C, C++ e JAVA). Existe
um outro formato de chamada de funo por palavras chave (usado em
ADA, por exemplo). A discusso sobre esses formatos ser aprofundada
no captulo 6.
Na maioria das LPs, a nica forma de se chamar uma funo atravs do
uso exclusivo de seu identificador. Por exemplo, ao se designar o identifi-
cador f como nome de uma funo em sua definio, ela s pode ser cha-
mada atravs do formato f(a). Isso uma restrio. Em LPs que tratam
funes como valores de primeira classe, a chamada de uma funo pode
ser feita atravs de qualquer expresso que retorne uma funo. O exem-
plo seguinte ilustra essa possibilidade em ML:
val taxa = (if difPgVenc > 0 then desconto else multa) (difPgVenc)
Caso o valor de difPgVenc (a diferena entre o dia do pagamento da taxa
e o dia de seu vencimento) for um valor positivo, o pagamento anteci-
pado e a taxa cobrada calculada pela chamada da funo desconto apli-
cada sobre o total de dias antecipados. Caso contrrio, o pagamento fei-
to em atraso e a taxa cobrada calculada pela chamada da funo multa
aplicada sobre o total de dias atrasados
5.1
.
Para se obter uma funcionalidade equivalente em C necessrio utilizar
ponteiros para funo, o que reduz significativamente a legibilidade e re-
digibilidade do cdigo, conforme se v no exemplo 5.8:
double (*p)(double);
p = difPgVenc > 0 ? desconto: multa;

5.1
Se o pagamento feito em dia, a funo multa retorna o valor da taxa sem multa.
122
taxa = (*p) (difPgVenc);
Exemplo 5. 8 - Chamada Condicional de Funo em C
A relao entre chamadas de funo e expresses fortalecida quando se
constata que um operador denota uma funo. Essa perspectiva fica mais
explcita e pode ser melhor observada quando se substitui a notao infi-
xada pela notao prefixada na aplicao de operadores. A tabela 5.1
mostra algumas expresses aritmticas em C (as quais usam a notao
infixada) e como seriam suas representaes em notao prefixada.
Expresso Representao Prefixada
a * b * (a , b)
c / d / (c , d)
a * b + c / d + (* (a , b) , / (c , d))
Tabela 5. 1 Operadores em C e Representao Prefixada
Outro modo de observar a relao entre operadores e funes
constatando a correspondncia entre os operadores e as assinaturas das
funes que denotam. A tabela 5.2 mostra a correspondncia entre alguns
operadores de JAVA e suas respectivas assinaturas das funes.
Operador Assinatura da Funo
! [boolean boolean]
&& [boolean x boolean boolean]
* [char x char char]
[short x short short]
[int x int int]
[long x long long]
[float x float float]
[double x double double]
Tabela 5. 2 Operadores em JAVA e suas Assinaturas
Observe na tabela 5.2 que o operador * denota vrias funes. Quando
isso ocorre, diz-se que o operador sobrecarregado. Em C, a analogia
entre funes e operadores fraca. Sobrecarga peculiar a operadores,
mas funes no podem ser sobrecarregadas. LPs mais modernas (como
C++, ADA, ML e JAVA) reconhecem explicitamente a analogia entre
operadores e funes, permitindo que as ltimas tambm sejam sobrecar-
regadas. C++, ADA e ML vo ainda alm que JAVA pois permitem que
novas funes sejam associadas aos operadores pelos programadores.
Nessas LPs, <operando1> <operador> <operando2> exatamente a
mesma coisa que <operador> (<operando1>, <operando2>). Se por um
lado, ao evitar separar regras para operadores e funes, essas LPS facili-
tam o seu aprendizado, por outro lado, a permisso de definio de novos
123
comportamentos para os operadores tende a tornar a linguagem mais
complexa e dificultar seu aprendizado.
5.1.1.9 Expresses com Efeitos Colaterais
O objetivo principal de avaliar uma expresso retornar um valor. Na
maioria das LPs imperativas, contudo, possvel avaliar uma expresso
que tenha o efeito colateral de atualizar variveis. O exemplo seguinte
mostra uma expresso (no lado direito da atribuio a x) em C que produz
como efeito colateral o incremento da varivel c.
x = 3.2 * ++c;
C disponibiliza vrios operadores de atribuio. Todos eles retornam va-
lores e produzem como efeito colateral a atualizao da varivel sobre o
qual so aplicados.
Normalmente, efeitos colaterais tornam os programas mais difceis de
serem lidos e entendidos. Efeitos colaterais tambm podem introduzir no
determinismo na avaliao de expresses. Por exemplo, na avaliao de
uma expresso do tipo <expresso1> <operador> <expresso2>, quan-
do <expresso1> afeta <expresso2>. Veja o exemplo 5.9 em C:
x = 2;
y = 4;
z = (y = 2 * x + 1) + y;
Exemplo 5. 9 - No Determinismo em Expresso com Efeito Colateral em C
No exemplo 5.9, os valores admissveis para z so 9 e 10. Tudo depende
de quando ser feita a atribuio a y na ltima linha do exemplo. Se ela
for feita antes da avaliao da expresso y direita da soma, o valor de z
ser 10. Caso contrrio, o valor de z ser 9. Normalmente, a resoluo
desse tipo de no determinismo no especificada na LP, deixando para o
compilador definir como este tipo de expresso ser avaliada. Assim, este
tipo de expresso com efeito colateral pode comprometer a portabilidade
de programas.
Uma funo pode ter em seu corpo uma srie de comandos antes de re-
tornar seu valor. permitido declarar variveis locais, usar comandos e
fazer atribuies. Funes possibilitam a ocorrncia de efeitos colaterais
atravs da atualizao de variveis globais ou da passagem de parmetros
por referncia. Por exemplo, a funo fgetc(f) da biblioteca padro de C
produz o efeito colateral de avanar a posio de leitura sobre f. Portanto,
chamadas de funo so expresses que podem produzir efeitos colate-
rais.
124
Existem expresses cujo nico objetivo provocar efeitos colaterais. Em
C++, por exemplo, o operador delete usado para produzir o efeito cola-
teral de desalocar a memria alocada referenciada por um ponteiro. Este
tipo de expresso no retorna qualquer valor (no caso de C++, retorna
void).
5.1.1.10 Expresses de Referenciamento
Expresses de referenciamento so utilizadas para acessar o contedo de
variveis ou constantes
5.2
ou para retornar uma referncia a esses objetos
(geralmente, o endereo inicial onde esses objetos esto alocados). Nor-
malmente, a expresso utilizada para obter uma referncia a uma varivel
ou constante tambm utilizada para acessar o seu contedo. Nesses ca-
sos, a distino entre a operao de acesso a contedo e a de obteno de
referncia dada pelo local do programa onde a expresso utilizada,
isto , o seu contexto. A linha de cdigo seguinte ilustra essa caractersti-
ca em C.
*q = *q + 3;
Observe que a mesma expresso *q usada no lado esquerdo e no lado
direito da atribuio. A expresso do lado esquerdo retorna uma refern-
cia ao objeto apontado por q. J a expresso do lado direito retorna o con-
tedo do objeto apontado por q.
A forma mais simples de expresso de referenciamento a de referenci-
amento direto de variveis e constantes
5.3
. No exemplo 5.10, o lado es-
querdo das atribuies das trs linhas do exemplo 5.10 contm expresses
de referenciamento que retornam respectivamente o endereo da constan-
te pi e os endereos das variveis raio e perimetro. J no lado direito da
atribuio da ltima linha do exemplo 5.10, as expresses de referencia-
mento pi e raio retornam o contedo desses objetos.
const float pi = 3.1416;
int raio = 3;
float perimetro = 2*pi*raio;
Exemplo 5. 10 - Referenciamento Direto de Variveis e Constantes
Outra maneira comum de expresso de referenciamento usada para a-
cessar estruturas de dados compostas ou annimas. Os operandos dessas
expresses so nomes ou valores que permitem identificar a varivel ou
constante cuja referncia deve ser obtida ou cujo valor deve ser acessado.

5.2
A operao de acesso a contedo tambm conhecida como derreferenciamento. Optou-se aqui por
utilizar o termo referenciamento para indicar tanto a operao de obteno de referncia quanto a de
derreferenciamento.
5.3
Essas expresses no demandam o uso de um operador explcito.
125
Por exemplo, para acessar o valor de um elemento de um vetor necess-
rio ter como operandos o nome do vetor e o ndice do elemento. Para rea-
lizar esses tipos de operao, LPs normalmente oferecem um conjunto de
operadores. A tabela 5.3 mostra os operadores de referenciamento em C,
juntamente com o seu significado.
Operador Significado
[] Acesso a valor ou retorno de refern-
cia de elemento de vetor
* Acesso a valor ou retorno de refern-
cia de varivel ou constante apontada
por ponteiro
. Acesso a valor ou retorno de refern-
cia de elemento de estrutura
-> Acesso a valor ou retorno de refern-
cia de elemento de estrutura apontada
por ponteiro
& Retorno de referncia a qualquer tipo
de varivel ou constante
Tabela 5. 3 - Operadores de Referenciamento em C
Com exceo do operador &, o qual no pode ser usado para acessar o
contedo de variveis (uma vez que no pode ser colocado no lado es-
querdo de atribuies), todos os demais operadores da tabela 5.3 so usa-
dos para acessar o valor ou retornar uma referncia para uma varivel ou
constante. O exemplo 5.11 ilustra o uso dos operadores da tabela 5.3 para
acessar o contedo de uma varivel e para referenci-la.
p[i] = p[i + 1];
*q = *q + 3;
r.ano = r.ano + 1;
s->dia = s->dia +1;
t = &m;
Exemplo 5. 11 - Uso de Operadores de Referenciamento em C
Na primeira linha do exemplo 5.11, p[i] retorna o endereo do i-simo
elemento do vetor p e p[i+1] retorna o valor do elemento seguinte. Nas
trs linhas seguintes as expresses no lado esquerdo das atribuies retor-
nam respectivamente o endereo da varivel apontada por q, o endereo
do campo ano da estrutura r e o endereo do campo dia da estrutura a-
pontada por s. As mesmas expresses do lado direito das atribuies re-
tornam o contedo desses elementos. Por fim, a expresso no lado direito
da ltima linha do exemplo retorna o endereo da varivel m.
C++ e JAVA possuem ainda uma outra forma de expresso de referenci-
amento. Essa forma surge com a aplicao do operador new. Esse opera-
126
dor tem por objetivo principal produzir o efeito colateral de alocar mem-
ria para um objeto e retorna como resultado uma referncia para o objeto
alocado. Por retornar uma referncia, as expresses que usam new so
classificadas como de referenciamento.
5.1.1.11 Expresses Categricas
Expresses categricas realizam operaes sobre tipos de dados. Elas
servem para extrair informaes a respeito do tipo de uma varivel ou
constante ou para converter um valor de um tipo para outro. Os operandos
de expresses categricas podem ser um tipo de dados ou um objeto cujo
tipo deve ser investigado ou modificado.
C oferece dois operadores para a produo de expresses categricas: o
operador sizeof e o operador de converso de tipo
5.4
( <tipo> ). O opera-
dor sizeof utilizado para se obter o tamanho em bytes do objeto ou do
tipo sobre o qual ele aplicado. O exemplo 5.12 ilustra o uso desse ope-
rador em C.
float * p = (float *) malloc (10 * sizeof (float));
int c [] = {1, 2, 3, 4, 5};
for (i = 0; i < sizeof c / sizeof *c; i++) c[i]++;
Exemplo 5. 12 Uso de sizeof em Expresses Categricas
A primeira linha do exemplo 5.12 mostra o operador sizeof sendo usado
para determinar o tamanho em bytes do tipo float. O resultado obtido
usado para alocar um tamanho de memria capaz de armazenar 10
elementos do tipo float. Observe com ateno a ltima linha desse
exemplo. Nessa linha o operador sizeof usado tanto para obter o
tamanho do vetor c quanto para obter o tamanho do elemento de c. Isso
permite que essa linha no seja alterada quando for necessrio alterar o
tamanho do vetor c.
O operador de converso de tipo ( <tipo> ) utilizado para converter um
valor de um tipo para outro. Seus operandos so o valor (ou varivel) que
se deseja converter e o tipo para o qual o valor deve ser convertido. O e-
xemplo 5.13 ilustra o uso desse operador em C.
float f;
int num = 9, den = 5;
f = (float)num/den;
Exemplo 5. 13- Operador de Converso de Tipo
Na ltima linha do exemplo 5.13, o valor do tipo inteiro int, armazenado
em num, convertido para o valor correspondente no tipo float. Desse
modo, o valor atribudo a f ser o resultado da operao de diviso real

5.4
O operador de converso de tipo tambm conhecido como operador de cast.
127
pois um dos seus operandos se tornou um valor float. C++ oferece vrios
outros operadores de converso de tipos. Esses operadores sero discuti-
dos no captulo 7.
C++ e JAVA oferecem outra forma de expresso categrica, a qual possi-
bilita verificar dinamicamente se uma determinada varivel ou constante
pertence a um certo tipo. C++ utiliza o operador typeid e JAVA utiliza o
operador instanceof para realizar esse tipo de operao. O exemplo 5.14
mostra o uso do operador instanceof em JAVA.
Profissao p = new Engenheiro ( );
if (p instanceof Medico) then
System.out.println (Registre-se no CRM);
if (p instanceof Engenheiro) then
System.out.println (Registre-se no CREA);
Exemplo 5. 14 - Uso de instanceof em JAVA
No exemplo 5.14, caso p fosse uma referncia para um objeto do tipo
Medico, a expresso categrica na segunda linha do exemplo seria satis-
feita e uma mensagem seria apresentada indicando que o objeto referen-
ciado por p deveria se registrar no CRM (Conselho Regional de Medici-
na). Como no esse o caso, essa mensagem no ser apresentada. J a
expresso categrica da penltima linha do exemplo ser satisfeita e o
programa apresentar uma mensagem indicando que o objeto deve se re-
gistrar no CREA (Conselho Regional de Engenharia e Arquitetura).
5.1.2 Avaliao de Expresses Compostas
Expresses compostas sempre envolvem duas ou mais operaes. A or-
dem de avaliao das operaes de uma expresso composta pode influ-
enciar completamente o resultado obtido. Por exemplo, a expresso
3+2*5 pode resultar no valor 25 (caso a operao de soma seja efetuada
primeiro) ou no valor 13 (caso a primeira operao seja a multiplicao).
Os conceitos importantes para a determinao da ordem de avaliao de
expresses compostas so as regras de precedncia, associatividade e cur-
to circuito dos operadores e as regras de precedncia entre operandos.
5.1.2.1 Precedncia de Operadores
Normalmente, os operadores de uma LP so ordenados por grau de pre-
cedncia. Operaes cujos operadores possuem maior precedncia so
realizadas antes das operaes relacionadas aos operadores de menor pre-
cedncia.
128
Caso no exista precedncia entre operadores na LP (APL e SMALL-
TALK no possuem), certas expresses compostas so avaliadas em uma
ordem pouco natural. Por exemplo, expresses compostas aritmticas no
so avaliadas na mesma sequncia usada pela matemtica bsica. Assim,
uma operao de soma pode preceder uma operao de multiplicao em
uma expresso composta como 3+2*5. Isso certamente dificulta o enten-
dimento do programa. Por outro lado, a existncia de graus de precedn-
cia entre operadores exige que o programador se lembre das suas prece-
dncias na hora de construir uma expresso composta, o que pode provo-
car enganos.
Parnteses podem ser usados para garantir que a avaliao ocorra na or-
dem desejada em situaes nas quais o programador no se lembra da
precedncia dos operadores ou precisa garantir uma ordem de avaliao
distinta daquela determinada pelos graus de precedncia. De fato, o uso
de parnteses pode inclusive dispensar a LP de definir o conceito de pre-
cedncia entre operadores. Quando se deseja escrever uma expresso
composta avaliada segundo uma ordem natural, basta usar os parnteses.
Embora o uso de parnteses possa at facilitar o entendimento de certas
expresses, o seu uso excessivo pode baixar a redigibilidade (tem de se
fechar muitos parnteses e escrever mais) e legibilidade (a expresso se
torna muito longa e dificil de ser lida) da expresso composta. Outro pro-
blema com parnteses impedir que o compilador realize certos tipos de
otimizao de cdigo que demandam a alterao na ordem de avaliao
das expresses compostas.
A escolha inadequada das precedncias entre operadores numa LP pode
afetar a redigibilidade de programas e provocar erros de programao.
Por exemplo, em PASCAL, os operadores relacionais tm menor prece-
dncia que os lgicos. Como resultado, expresses bem formadas que
combinam estes operadores requerem o uso de parnteses para alterar a
ordem de precedncia. O exemplo 5.15 ilustra esse problema em PAS-
CAL.
/* if a > 5 and b < 10 then */
if (a > 5) and (b < 10) then a := a + 1;
Exemplo 5. 15 Escolha Inapropriada de Precedncia de Operadores em PASCAL
No exemplo 5.15, a linha comentada geraria um erro de compilao, uma
vez que a ordem de precedncia impe ao compilador a gerao de cdi-
go para avaliar primeiro a expresso 5 and b. Como nenhum dos dois o-
perandos do tipo booleano, essa expresso no vlida. Nesse caso, a
soluo incluir parnteses, tal como na linha final do exemplo. Essa e-
xigncia, contudo, afeta a redigibilidade do programa torna tedioso o tra-
balho do programador.
129
A tabela 5.4 mostra a ordem de precedncia dos operadores de C [KER-
NIGHAN & RITCHIE, 1989]. Os parnteses so usados para designar o
operador de chamada de funo ( ) e o operador de converso de tipos
(<tipo>). Observe que a maior precedncia a de grau 1. Note ainda que
os operadores , * e & com grau 2 de precedncia so os unrios. Cabe
destacar tambm que os operadores relacionais tem maior precedncia
que os operadores booleanos binrios.
Precedncia Associatividade Operadores
1 esquerda para a direita ( ) [ ] >
2 direita para a esquerda ! ~ ++ + (<tipo>) * & sizeof
3 esquerda para a direita * / %
4 esquerda para a direita +
5 esquerda para a direita << >>
6 esquerda para a direita < <= > >=
7 esquerda para a direita == !=
8 esquerda para a direita &
9 esquerda para a direita ^
10 esquerda para a direita |
11 esquerda para a direita &&
12 esquerda para a direita ||
13 direita para a esquerda ?:
14
direita para a esquerda = += = *= /= %= &= |= ^=
<<= >>=
15 esquerda para a direita ,
Tabela 5. 4 - Tabela de Precedncia e Associatividade de Operadores em C
5.1.2.2 Associatividade de Operadores
Regras de associatividade de operadores so usadas quando a LP no de-
fine regras de precedncia entre operadores ou quando operadores adja-
centes da expresso composta tm a mesma precedncia. Nesses casos, a
regra de associatividade quem determina qual operador ser avaliado
primeiro.
Na maioria das LPs a regra de associatividade de operadores usada a
avaliao da esquerda para direita. O exemplo 5.16 mostra a aplicao
dessa regra em duas expresses compostas em C.
x = a + b c;
y = a < b < c;
Exemplo 5. 16 - Associatividade de Operadores
130
Na primeira linha do exemplo 5.16, a soma dos valores de a e b feita
primeiro e do seu resultado subtrado o valor de c. Na segunda linha o
valor de a comparado com o valor de b e o resultado comparado com
o valor de c. Note que essa expresso composta no avalia se os valores
de a, b e c so crescentes. De fato, essa expresso s vlida porque as
expresses relacionais de C retornam um valor inteiro. Assim, o valor
retornado pela primeira comparao (0 ou 1) comparado com o valor de
c.
Embora a maior parte dos operadores de uma LP siga a regra geral de as-
sociatividade, frequentemente existem operadores que no a obedecem.
Isso ocorre em C, como pode ser observado na tabela 5.4. No entanto, boa
parte dos operadores de C que adotam regra de associatividade invertida
(isto , da direita para a esquerda) no teriam sentido se definidos de ou-
tra forma. O exemplo 5.17 ilustra o uso de alguns desses operadores.
x = **p;
if (!!x) then y = 3;
a = b = c;
Exemplo 5. 17 - Associatividade da Direita para a Esquerda em C
Perceba que, para avaliar **p, !!x e a = b = c, absolutamente necessrio
associar os operadores da direita para a esquerda. Nem sempre isso ocorre
dessa maneira. Em C, a expresso !x++ pode gerar dvidas no programa-
dor quanto a ordem de avaliao. Em FORTRAN, o operador de expo-
nenciao associativo da direita para a esquerda, embora os demais ope-
radores sejam da esquerda para a direita. Observe que nesse caso esse o-
perador poderia ser associativo da esquerda para a direita. Embora essa
opo seja decorrente da forma como essa operao realizada na mate-
mtica, programadores inadvertidos podem cometer erros por conta dessa
opo.
Algumas LPs podem fazer opes controvertidas com relao a associati-
vidade dos operadores. APL, por exemplo, alm de no estabelecer qual-
quer precedncia entre os operadores, ainda opta por avaliar sempre os
operadores da direita para a esquerda. Na linha de cdigo APL apresenta-
da a seguir a subtrao ser realizada antes da diviso.
X = Y W Z
Alguns compiladores podem usar situaes onde no existe precedncia
entre os operadores da expresso para fazer otimizaes de cdigo. Por
exemplo, na seguinte linha de cdigo C, as funes f, g e h retornam intei-
ros e x uma varivel inteira:
x = f() + g() + h();
131
Nesse caso a ordem de avaliao da esquerda para a direita das funes
poderia ser alterada para somar f com h e depois somar o resultado com g.
Contudo, isso pode gerar problemas srios. Por exemplo, se f e h retor-
nam inteiros positivos muito grandes e g retorna um inteiro negativo mui-
to grande, a soma de f com h pode provocar overflow, enquanto que a
soma de f com g no provoca. Situaes como essa devem ser evitadas
atravs do uso de parnteses para garantir a ordem de avaliao de opera-
dores.
5.1.2.3 Precedncia de Operandos
O exemplo 5.9 mostra uma situao em C na qual ocorre no determinis-
mo por causa do uso de expresses com efeito colateral. Esse no deter-
minismo ocorre porque C no especifica a ordem na qual os operandos de
um operador devem ser avaliados (as nicas excees a essa regra so os
operadores && || ?: ,). Assim, o compilador quem decide qual operan-
do ser avaliado primeiro. Como a avaliao de um operando pode afetar
ou no a avaliao do outro, surge o no determinismo. importante des-
tacar que esse no determinismo pode ocorrer em situaes comuns. O
exemplo seguinte mostra uma dessas situaes de no determinismo.
Nesse exemplo, no se sabe se o ndice do vetor ser o valor de i antigo
ou o novo.
a[i] = i++;
Exemplo 5. 18 - No Determinismo em C (extrado de [KERNIGHAN & RITCHIE, 1989])
C no fixa intencionalmente essa ordem para que o compilador possa ex-
plorar adequadamente a arquitetura especfica do computador e poder ge-
rar cdigo mais eficiente.
Uma forma de evitar o no determinismo na avaliao dessas expresses
estabelecer regras de precedncia para a avaliao dos operandos. Por
exemplo, JAVA estabelece que operandos so sempre avaliados da es-
querda para a direita. Assim, implementaes em JAVA dos exemplos
5.9 e 5.18 so determinsticas. No caso do exemplo 5.9, o valor atribudo
a z 10 e, no exemplo 5.18, o valor de i o antigo. Contudo, a adoo de
precedncia entre operandos inibe certos tipos de otimizao de cdigo,
impedindo ao cdigo executvel se tornar mais eficiente.
5.1.2.4 Avaliao de Expresses Compostas com Curto Circuito
A avaliao de expresses em curto circuito ocorre quando o resultado
determinado antes que todos os operadores e operandos tenham sido ava-
liados.
132
A linha de cdigo seguinte mostra uma situao potencial em C na qual
poderia ser empregada a avaliao com curto circuito.
z = (x - y) * (a + b) * (c d);
Quando o valor de um dos operandos de uma multiplicao resulta em
zero (por exemplo, quando o valor de x igual ao valor de y), no seria
necessrio avaliar o outro operando para saber o resultado da expresso
composta. Como este tipo de curto circuito no ocorre frequentemente,
no vale a pena inclu-lo em LPs visto que ele tornaria o cdigo mais efi-
ciente somente quando um dos operandos da multiplicao resultasse no
valor zero, mas em todos os outros casos ele embutiria testes desnecess-
rios, reduzindo assim a eficincia do cdigo gerado.
Avaliaes em curto circuito so muito usadas para avaliar as expresses
booleanas binrias de conjuno e disjuno. Quando o primeiro operan-
do avaliado na conjuno no satisfeito, no existe necessidade de ava-
liar o outro operando. O mesmo ocorre na disjuno, desde que o operan-
do avaliado primeiro seja satisfeito. Observe que nesses casos tambm
necessrio incluir um teste adicional ao cdigo para realizar curto circui-
to. Contudo, como essas condies ocorrem com bastante frequncia, va-
le a pena utilizar curto circuito.
Programas em JAVA usam curto circuito em situaes tais como a mos-
trada no exemplo 5.19.
int[] a = new int [n];
i = 0;
while (i < n && a[i] != v) i++;
Exemplo 5. 19 - Uso de Curto Circuito em JAVA
Esse trecho de programa usado para procurar a posio do valor v no
vetor a. Observe o uso do curto circuito quando o valor v no est presen-
te no vetor a. Nesse caso, o valor de i se igualar a n e a primeira condi-
o da expresso booleana no ser satisfeita, no havendo necessidade
de avaliar a segunda condio. Caso no se utilizasse curto circuito, have-
ria um erro de execuo na avaliao da segunda condio pois o pro-
grama tentaria acessar uma posio inexistente no vetor a (a posio de
ndice n).
Em PASCAL, os operadores binrios booleanos no empregam curto cir-
cuito. Isso impede que os programadores criem cdigo como o do exem-
plo 5.19. Cabe ressalvar que alguns compiladores PASCAL permitem ao
programador optar por gerar cdigo com ou sem avaliao em curto cir-
cuito. Esta postura mais flexvel, embora no permita o uso dos dois
tipos de avaliao simultaneamente e possa comprometer a portabilidade
do cdigo.
133
Algumas LPs adotam operadores booleanos especficos para a avaliao
com e sem uso de curto circuito. Esse o caso de ADA e JAVA. Os ope-
radores booleanos and e or de ADA e & e | de JAVA no usam curto cir-
cuito. J os operadores booleanos and then e or else de ADA e && e || de
JAVA usam. Essa uma soluo flexvel e geral, embora implique na
existncia de um maior nmero de operadores, o que torna a LP mais
complexa.
Os nicos operadores booleanos de C e C++ so && e ||, os quais usam
curto circuito. Contudo, os operadores binrios & e | no usam e podem
ser usados para avaliar expresses booleanas sem o uso de curto circuito.
Para tanto, preciso levar em conta que uma expresso relacional retorna
o valor zero, quando no satisfeita, e o valor um, em caso contrrio. As-
sim, a aplicao dos operadores binrios | e & a operandos que so ex-
presses relacionais geram os mesmos comportamentos dos respectivos
operadores booleanos. Observe, no entanto, que o operador binrio de
conjuno pode no funcionar a contento caso um dos operandos da ex-
presso binria no seja uma expresso relacional.
A avaliao com curto circuito pode gerar programas difceis de serem
entendidos quando em associao com efeitos colaterais. O trecho de c-
digo em C apresentado a seguir no incrementa o valor de i quando b <
2*c. Nesse caso, o elemento de a incrementado o correspondente ao va-
lor de i sem incremento. Caso contrrio, se a segunda condio da disjun-
o for satisfeita, o elemento de a incrementado correponde ao do valor
de i com incremento. Por outro lado, se a segunda condio tambm no
for satisfeita, nenhum elemento de a ser incrementado.
if (b < 2*c || a[i++] > c ) { a[i]++ };
5.2 Comandos
Comandos so instrues do programa que tem o objetivo de atualizar as
variveis ou controlar o fluxo de controle do programa. Comandos so
caractersticos de linguagens imperativas. De fato, o nome imperativo
advm da viso que programas nessas linguagens usam comandos para
determinar as computaes a serem realizadas pelo computador. Em ou-
tras palavras, a linguagem imperativa porque os programas comandam o
computador.
Os comandos podem ser primitivos ou compostos. Enquanto comandos
primitivos (por exemplo, um comando de atribuio) em geral no podem
ser subdivididos em outros comandos, comandos compostos normalmente
possuem um ou mais subcomandos em seu escopo de ao (por exemplo,
um comando de repetio possui pelo menos um subcomando que ser
repetido para alterar o estado da condio de parada do comando).
134
Para uma LP imperativa ser suficientemente expressiva ela necessita de
pelos menos trs tipos de comandos: um comando de atribuio para
permitir a atualizao de variveis; um comando de seleo para permitir
a existncia de caminhos alternativos no fluxo de controle do programa e
um comando de desvio do fluxo de controle para permitir a realizao de
repeties de comandos. claro que uma LP com apenas esses tipos de
comandos seria extremamente limitada. De fato, at mesmo linguagens
consideradas de baixo nvel possuem uma maior variedade de comandos
do que a listada nesse pargrafo. Assim, normalmente, as LPs oferecem
vrias alternativas para esses tipos de comandos, bem como alguns outros
comandos complementares.
5.2.1 Tipos de Comandos
Comandos de LPs imperativas podem ser classificados de acordo com a
natureza da operao que realizam. Eles podem ser divididos nas seguin-
tes categorias fundamentais [WATT, 1990]: atribuies, comandos se-
qenciais, comandos colaterais, comandos condicionais, comandos itera-
tivos, chamadas de procedimento e comandos de desvio incondicional.
5.2.1.1 Atribuies
Em sua forma geral, o comando de atribuio envolve o uso de um sm-
bolo que designa o comando, uma expresso que produz uma referncia
varivel cujo valor ser atualizado e uma expresso que produz como re-
sultado um valor a ser armazenado nessa varivel.
Cada LP usa um smbolo prprio para designar a operao de atribuio.
C, C++ e JAVA usam o smbolo =. ADA, PASCAL e MODULA-2 usam
:= porque o smbolo = costuma causar problemas no uso e aprendizado
da linguagem.
Uma das dificuldades enfrentadas pelo aprendiz a tendncia em confun-
dir o comando de atribuio com a operao de igualdade da matemtica.
Assim, um comando do tipo i = i + 1 tende a ser entendido como uma
equao sem soluo ao invs de um comando de atribuio.
Essa dificuldade de entendimento agravada pelo fato da varivel i de-
signar dois conceitos distintos nesse comando (respectivamente, uma re-
ferncia varivel e o contedo dessa varivel). Cabe ressalvar, contudo,
que esse problema no relacionado com o smbolo usado e sim com o
significado de uma atribuio. ML reconhece esse problema e obriga a
utilizao de um operador de derreferenciamento ( ! ) para acessar o con-
tedo da varivel. Assim, um comando de atribuio equivalente em ML
tem o seguinte formato: i := !i + 1.
135
Outra dificuldade relacionada com uso do smbolo = para designar uma
atribuio a confuso da operao de atribuio com a operao de
comparao de igualdade. Esse problema se agrava quando o smbolo =
tambm usado para designar a operao de igualdade, tal como em PL-
1, ou quando se pode usar uma atribuio no local onde se espera uma
comparao, como o caso de C e C++. A linha seguinte de cdigo C
vlido mostra o uso de uma atribuio onde deveria haver uma compara-
o. Essa linha certamente um erro de programao ou de digitao
pois, dessa maneira, a condio sempre satisfeita e o valor de a sem-
pre incrementado de 3. Isso torna incua a existncia desse comando if.
if (a = 10) a += 3;
Existem vrios tipos de comandos de atribuio [SEBESTA, 1999]. Cada
um desses tipos apresentado a seguir:
a. Atribuio simples: o tipo mais comum de atribuio. Nesse tipo o
resultado de uma expresso atribudo a uma varivel, tal como no
prximo exemplo em C.
a = b + 3 * c;
b. Atribuio mltipla: ocorre quando se atribui o mesmo valor a diver-
sas variveis. Por exemplo, em C, o seguinte comando atribui o valor
zero para as variveis a e b.
a = b = 0;
c. Atribuio condicional: atribui o valor de uma expresso ao resultado
de uma expresso condicional. Por exemplo, na linha seguinte de c-
digo ML, o valor 2 ser atribudo a varivel a ou b, dependendo da a-
valiao da expresso a<b.
(if a < b then a else b) := 2;
d. Atribuio composta: permite a combinao de operadores binrios
com o comando de atribuio. Nos exemplos em C, mostrados a se-
guir, a varivel a tem seu valor incrementado de 3, multiplicado por 3
e modificado para o resultado da sua conjuno com 3, respectivamen-
te.
a += 3; a *= 3; a &= 3;
e. Atribuio unria: permite a atribuio ser realizada com um nico
operando (o outro implcito). Em todos os exemplos seguintes em C
a varivel a tem seu valor incrementado ou decrementado de 1.
++a; a++; --a a--;
A distino entre a posio prefixada e posfixada dos operadores ++ e
-- se refere ao valor retornado pela atribuio. Na primeira linha do
136
cdigo C seguinte, o valor corrente de a atribudo a b e, depois, in-
crementado. Na segunda linha ocorre o inverso, isto , o valor de a
incrementado e, somente depois disso, atribudo a b.
b = a++;
b = ++a;
f. Atribuio expresso: o comando de atribuio retorna o valor atribu-
do. Todos os tipos de atribuio em C tambm so exemplos de atri-
buio expresso. Esse tipo de atribuio possibilita ao programador
criar atalhos interessantes em certas situaes, tal como ilustrado no
seguinte cdigo C.
while (( ch = getchar ( ) ) != EOF ) { printf(%c, ch); }
Por outro lado, a atribuio expresso em C pode incentivar o progra-
mador a criar cdigo pouco legvel e, quando associada com a ausn-
cia de tipo booleano, tende a provocar erros que no podem ser detec-
tados pelo compilador (tal como aquele em que se coloca uma atribui-
o onde se espera uma comparao). JAVA elimina esse problema s
permitindo o uso de expresses booleanas nessas situaes.
5.2.1.2 Seqenciais
O modo mais comum de fluxo de controle existente em toda LP imperati-
va a composio seqencial de comandos. O comando antecedente na
seqencia executado antes do subsequente. Normalmente, o conceito de
bloco usado na implementao dos comandos seqenciais. Nesses ca-
sos, um bloco permite que uma srie de comandos seqenciais sejam abs-
trados em um nico comando. O exemplo 5.20 mostra como blocos so
usados em C para compor comandos seqenciais:
{
n = 1;
n += 3;
if (n < 5){
n = 10;
m = n * 2;
}
}
Exemplo 5. 20- Comandos Seqenciais em C
Algumas LPs no usam diretamente o conceito de blocos para agrupar
comandos seqenciais. Nessas LPs, o agrupamento de comandos seqen-
ciais embutido dentro dos outros comandos da LP. Exemplos de LPs
que adotam essa postura so ADA e MODULA-2.
137
5.2.1.3 Colaterais
Esse tipo de comando pouco comum em LPs imperativas. Nesses co-
mandos no existe uma ordem prvia para a execuo dos comandos. No
exemplo seguinte as variveis a e b so atualizadas independentemente e
a ordem de execuo irrelevante, isto , o(s) processador(es) pode(m)
executar os dois comandos na ordem que for mais conveniente.
a = f(x), b = g(y);
Ao se programar numa LP com comandos colaterais necessrio ter cui-
dado para evitar o uso inadequado desses comandos. No exemplo seguin-
te o valor de a depende da ordem de execuo do comando colateral.
a = 0;
a = 3, a = a + 1;
Os valores possveis de a so 4, se os comandos colaterais forem execu-
tados na ordem da esquerda para direita, ou 3, se na ordem inversa, ou
mesmo 1. Esse ltimo valor obtido atravs da sequncia: o valor 0 a-
tribudo a varivel a, esse valor utilizado para avaliar a expresso a + 1,
o valor 3 atribudo a varivel a, o resultado da expresso a + 1 (no caso,
o valor 1) atribudo a varivel a.
Uma computao determinstica quando se pode prever a ordem de exe-
cuo dos comandos. De outra maneira, a computao no determinsti-
ca. Comandos colaterais so no determinsticos. Uma computao no
determinstica pode ter um efeito previsvel, embora a ordem de execuo
dos comandos no seja conhecida (nesse caso, ela considerada efetiva-
mente determinstica). Um comando colateral efetivamente determins-
tico se nenhum subcomando pode acessar uma varivel atualizada por
outro subcomando. O exemplo 5.21 ilustra o uso de comandos colaterais
em ML. No se pode saber previamente a ordem em que as definies de
altura, largura e comprimento sero realizadas.
val altura = 2
and largura = 3
and comprimento = 5
Exemplo 5. 21 - Comando Colateral em ML
Cabe destacar que ML impede o uso de um identificador definido em um
dos subcomandos em qualquer dos outros subcomandos. Assim, seria ile-
gal adicionar ao exemplo 5.21 a seguinte linha de cdigo:
and volume = altura * largura * comprimento
Comandos colaterais so fundamentais em LPs destinadas ao processa-
mento paralelo de programas. Nessas LPs, os comandos colaterais indi-
138
cam quais comandos podem ser executados paralelamente em diferentes
processadores.
5.2.1.4 Condicionais
Um comando condicional (tambm conhecido como comando de seleo)
permite a especificao de caminhos alternativos para o fluxo de controle
do programa. Ele possui um nmero de subcomandos dos quais exata-
mente um escolhido para ser executado.
A maioria das LPs fornece trs tipos de comandos de seleo. Cada um
desses tipos apresentado a seguir.
5.2.1.4.1 Seleo de Caminho Condicionado
Esse comando permite que um trecho de programa seja executado se de-
terminada condio satisfeita. O comando if de C pode atuar dessa ma-
neira.
if (x < 0) { x = y + 2; x++; }
O trecho entre chaves s executado quando a condio verdadeira.
5.2.1.4.2 Seleo de Caminho Duplo
Esse comando permite que exista uma condio para a escolha entre dois
trechos alternativos de programa. O comando if de C tambm pode atuar
dessa maneira.
if (x < 0) { x = y + 2; x++; } else { x = y; x--; }
O primeiro trecho entre chaves somente executado quando a condio
verdadeira, e o segundo, somente quando ela falsa.
Em muitas LPs, tais como C e PASCAL, ocorre um problema de ambi-
gidade sinttica na escrita de comandos condicionais duplos aninhados.
O exemplo 5.22 ilustra esse problema em C.
if ( x == 7 )
if ( y == 11) {
z = 13;
w = 2;
}
else z = 17;
Exemplo 5. 22 - Problema com Aninhamento em Comando Duplo de Seleo
No existe no comando acima um distino sinttica indicando a qual if o
else pertence. De fato, existe ambigidade apesar da semntica sugerida
pela disposio do texto do cdigo. C adota regra que o else se refere ao if
139
mais interno. Por ser uma deciso arbitrria, ela difcil de ser memori-
zada e fcil de provocar erro. Alm disso, para se conseguir a semntica
indicada pela disposio do texto, estritamente necessrio colocar mar-
cadores de bloco no comando if mais externo (tal como ilustrado no e-
xemplo 5.23.
if ( x == 7 ) {
if ( y == 11) {
z = 13;
w = 2;
}
} else z = 17;
Exemplo 5. 23- Uso de Marcadores de Bloco em Comando Duplo de Seleo em C
O problema de C e PASCAL acontece pela falta de um smbolo especfi-
co indicador de fechamento de comando condicional. MODULA-2 e
ADA possuem palavras especiais de fechamento de comandos condicio-
nais. O exemplo 5.24 ilustra essa soluo em ADA.
if x > 0 then
if y > 0 then
z := 0;
end if;
else
z := 1;
end if;
Exemplo 5. 24 - Terminao de Comandos de Seleo em ADA
5.2.1.4.3 Seleo de Caminhos Mltiplos
Esse comando permite que exista uma escolha entre vrias alternativas de
execuo do programa conforme o resultado de uma expresso. O exem-
plo 5.25 ilustra como o comando switch de C pode atuar dessa maneira.
switch (nota) {
case 10:
case 9: printf (Muito Bom!!!);
break;
case 8:
case 7: printf (Bom!);
break;
case 6:
case 5: printf (Passou);
break;
default: printf (Estudar mais!);
140
}
Exemplo 5. 25 - Comando de Caminhos Mltiplos em C
Note no exemplo 5.25 que tanto a nota 9 quanto a nota 10 implicam na
impresso da mensagem Muito Bom!!!. Observe tambm a necessidade
do uso do comando break. Sem ele, todas as quatro mensagens seriam
impressas no caso da nota ser 9 ou 10.
Existem variaes significativas nos comandos condicionais de caminhos
mltiplos das diversas LPs. Estas variaes vo desde o nome do coman-
do (GOTO em FORTRAN, case em ADA e PASCAL e switch em C) at
o funcionamento do comando (enquanto no case de ADA e PASCAL a
execuo vai automaticamente para o fim do comando condicional aps a
execuo de um caminho, no switch de C necessrio inserir o comando
break ao final do caminho para se ter o mesmo comportamento).
LPs como C e PASCAL restringem as expresses usadas como condio
e seus possveis resultados a constantes de tipos ordinais. Quando a sele-
o deve ser feita com base em expresses lgicas devem ser usados co-
mandos if aninhados. O exemplo 5.26 mostra esse tipo de abordagem em
C.
if (rendaMes < 1000)
iR = 0;
else if (rendaMes < 2000)
iR = 0.15 * (2000 - rendaMes);
else
iR = 0.275 * (rendaMes - 2000) + 0.15 * (2000 rendaMes);
Exemplo 5. 26 - Caminhos Mltiplos com if Aninhados
Algumas LPs, como ADA, MODULA-2 e FORTRAN-90 reconhecem a
importncia desse tipo de construo e usam um nico comando if com
um mecanismo especfico (elsif) para tratar esses casos.
5.2.1.5 Iterativos
Um comando iterativo (tambm conhecido como comando de repeti-
o
5.5
) permite a especificao de ciclos no fluxo de controle do progra-
ma. Iteraes so tpicas de LPs imperativas. Um comando iterativo pos-
sui um subcomando (o seu corpo) o qual executado repetidamente at
que a satisfao de algum tipo de condio determine o seu fim.
Comandos iterativos podem ter o nmero de repeties definido previa-
mente (antes da primeira execuo do corpo do comando) ou no.

5.5
Comandos de repetio so frequentemente designados pelo termo loop.
141
5.2.1.5.1 Nmero Indefinido de Repeties
Esse comando usado quando o nmero de iteraes no determinado
previamente. Em geral, existem 2 tipos desses comandos em LPs: um
com pr-teste e outro com ps-teste. O exemplo 5.27 ilustra esses dois
comandos em C.
f = 1;
y = x;
while ( y > 0) {
f = f * y;
y--;
}
f = 1;
y = 1;
do {
f = f * y;
y++;
} while (y <= x);
Exemplo 5. 27 - Comando Iterativo com Nmero de Repeties Indefinido
No exemplo 5.27 os dois trechos com comando de repetio calculam o
fatorial f de um nmero natural x. No comando com pr-teste (o comando
while), o corpo da repetio s executado se a condio de parada ver-
dadeira pelo menos uma vez. No comando com ps-teste (o comando do-
while), o corpo da repetio sempre executado pelo menos uma vez.
Embora atendam a maior parte dos casos nos quais comandos com nme-
ro indefinido de repeties so necessrios, em algumas situaes pode
ser conveniente interromper a repetio aps algum comando interno do
corpo da repetio. O exemplo 5.28 mostra o uso dos comandos iterativos
de C em uma dessas situaes.
s = 0;
printf (n: );
scanf (%d, &n);
while (n > 0) {
s +=n;
printf (n: );
scanf (%d, &n);
}
s = 0;
do {
printf (n: );
scanf (%d, &n);
if (n > 0) s+=n;
} while (n > 0);


Exemplo 5. 28 - Situao na qual Comandos com Pr ou Ps-Teste no so os mais Indicados
No exemplo 5.28 os dois trechos com comando de repetio calculam a
soma s de vrios nmeros inteiros positivos n at que seja lido um nme-
ro inteiro no positivo. Enquanto no comando while necessrio repetir o
uso dos comandos printf e scanf para a leitura de n (impactando a redigi-
bilidade do cdigo), no comando do-while necessrio repetir a verifica-
o se o nmero n inteiro positivo (impactando a redigibilidade e, prin-
cipalmente, a eficincia do cdigo).
Alm do tipo de situao ilustrado no exemplo 5.28, muitas vezes o pro-
gramador necessita incluir vrias condies de sada da iterao em pon-
142
tos distintos do corpo da repetio. As LPs oferecem comandos de escape
(sero vistos na seo 5.2.1.7), os quais podem ser combinados com co-
mandos de repetio e seleo, para atender esses tipos de demandas.
5.2.1.5.2 Nmero Definido de Repeties
Esse comando usado quando o nmero de iteraes determinado pre-
viamente. Esse tipo de iterao caracterizado pelo uso de uma varivel
de controle. O corpo da repetio executado com a varivel de controle
assumindo cada valor de uma seqncia pr-determinada de valores.
Em geral, alm da varivel de controle, esses comandos possuem como
elementos as expresses que determinam os valores inicial e final da vari-
vel de controle e, opcionalmente, a expresso que determina o valor da
variao da varivel de controle entre um ciclo e outro da repetio. To-
das essas expresses devem ser avaliadas antes da execuo do primeiro
ciclo da repetio. O exemplo 5.29, em MODULA-2, utiliza todos esses
elementos para somar os nmeros inteiros mltiplos de j no intervalo
compreendido entre a j-sima dezena e a dezena seguinte.
s := 0;
FOR i := 10 * j TO 10 * (j + 1) BY j DO
s := s + i;
END;
Exemplo 5. 29 - Comando Iterativo com Nmero de Repeties Definido em MODULA-2
Observe no exemplo 5.29 que i a varivel de controle, 10*j a expres-
so usada para determinar o valor inicial de i, 10*(j+1) a expresso u-
sada para determinar o valor final de i, e j a expresso que determina o
valor da variao da varivel de controle. Note tambm que os valores a
serem assumidos por i em cada repetio podem ser conhecidos antes da
realizao do primeiro ciclo. Por exemplo, se o valor de j 4, os valores
assumidos por i sero 40, 44 e 48.
Existem algumas posies consensuais a respeito do comando iterativo
com nmero de repeties definida. A realizao do teste de parada deve
ser feita antes da execuo do corpo da repetio para cada valor da vari-
vel de controle. O nmero de repeties a ser realizada deve ser avaliado
antes do comeo da repetio e permanecer fixo. Deve-se ainda impedir
mltiplas entradas no corpo da repetio (por exemplo, atravs do uso de
um comando de desvio do fluxo do controle).
Contudo, tambm existem grandes variaes nas caractersticas desse tipo
de comando entre as diversas LPs. Variaes ocorrem nos tipos possveis
da varivel de controle e no seu escopo de visibilidade, na possibilidade
de alterao da varivel de controle e dos outros elementos do comando
143
no corpo da repetio e na possibilidade de existncia da expresso defi-
nidora da variao do valor da varivel de controle.
Em ADA, a varivel de controle somente pode ser de um tipo primitivo
discreto (inteiro ou intervalo de inteiros ou enumerao) e seu escopo
restrito ao corpo da repetio. Isso significa que no possvel referenciar
a varivel de controle fora do comando de repetio. ADA no permite
que a varivel de controle tenha seu valor alterado por um comando do
corpo da repetio, mas as variveis usadas para especificar o intervalo da
repetio podem ser alteradas, uma vez que as expresses s so avalia-
das uma nica vez no incio da execuo da repetio. Caso o intervalo de
variao determinado para a varivel de controle seja nulo, o corpo da
repetio no executado. A varivel de controle de ADA deve assumir
todos os valores do intervalo de variao especificado, uma vez que no
h possibilidade de se especificar a variao de valor entre um ciclo e ou-
tro. No entanto, ADA possibilita que a ordem de atribuio dos valores
seja normal ou reversa.
J FORTRAN e PASCAL consideram a varivel de controle como uma
varivel ordinria cujo valor atribudo sucessivamente aps a execuco
de cada corpo da repetio. Essa postura no esclarece as questes sobre
o valor da varivel de controle aps o encerramento do comando de repe-
tio e se ela pode ser alterada dentro dele. Isso deixado para o imple-
mentador da LP, o que pode comprometer a portabilidade dos programas.
MODULA-2 determina que a varivel de controle deve ser de um tipo
primitivo discreto e a expresso que determina o tamanho do passo tem
que ser inteira. A varivel de controle no pode ser um parmetro passado
por referncia, um campo de agregado ou uma varivel importada. Quan-
do termina a repetio o valor da varivel de controle indefinido. O cor-
po da repetio no pode alterar a varivel de controle nem as variveis
utilizadas para definir as expresses que determinam o valor inicial e final
da iterao.
C possui o comando for, que embora aparente ser um comando iterativo
de repetio definida, tambm pode ser usado como comando de repeti-
o indefinida. Esse comando de C estabelece quatro regies nas quais
podem ser includos subcomandos. O trecho de inicializao, onde so
inicializadas uma ou mais variveis de controle, executado apenas uma
vez no incio da execuo da repetio. O trecho de teste executado an-
tes de cada iterao. O trecho de avano executado depois de cada itera-
o. Por fim, o corpo da repetio contm os comandos que so executa-
dos repetidamente. O exemplo 5.30 ilustra esse comando em C calculan-
do a diferena dif entre a soma de todos os nmeros divisveis por 2 e a
soma de todos os nmeros divisveis por 3 do vetor a:
144
dif = 0;
for (i = 0; i < n; i++) {
if (a[i] % 2 == 0) dif += a[i];
if (a[i] % 3 == 0) dif -= a[i];
}
Exemplo 5. 30 - Comando for de C
importante destacar as peculiaridades do comando for de C. Cada regi-
o do comando pode ser composta por uma seqncia de expresses (o
valor retornado pela regio o valor da ltima expresso). Todas as qua-
tro regies do for so opcionais. No existe uma varivel de controle ex-
plcita. possvel misturar condies lgicas e de contagem no teste de
parada e todas as variveis envolvidas podem ser alteradas dentro do cor-
po da repetio. O exemplo 5.31 ilustra vrias dessas possibilidades.
for (i = 10 * j, s = 0; i <= 10 * (j + 1); s += i++);
for (i = 0, s = 0; i <= n && s < 101 && a[i] > 0 ; ) s += a[i++];
for (;;);
Exemplo 5. 31 - Possibilidades Oferecidas por Comando for de C
A primeira linha do exemplo 5.31 cumpre a mesma funcionalidade reali-
zada pelo exemplo 5.29 em MODULA-2. Observe que as variveis i e s
so inicializadas e incrementadas nas regies de inicializao e avano do
prprio comando for. Observe ainda que esse comando no possui corpo.
A segunda linha do exemplo 5.31 usada para somar os elementos do
vetor a at que se chegue ao final desse vetor ou a soma seja superior a
100 ou seja encontrado um nmero no positivo no vetor. Note que a
condio de parada do for uma expresso booleana que combina vrias
expresses relacionais, a regio de avano no possui qualquer comando
e que as variveis i e s tm seu valor alterado no corpo do comando.
A terceira linha do exemplo 5.31 contm um comando for no qual todas
as regies no possuem comandos. Embora esse comando seja sintatica-
mente vlido em C, ele no correto do ponto de vista semntico, uma
vez que o programa entraria em uma repetio infinita.
Cabe ressalvar ainda que comandos tais como os das duas primeiras li-
nhas do exemplo 5.31 no devem ser utilizados de modo geral pois, em-
bora ofeream uma boa redigibilidade, eles comprometem muito a legibi-
lidade dos programas.
O comando for de C++ se difere do de C apenas por poder incluir a defi-
nio das variveis de controle dentro do prprio comando. Contudo, a
varivel definida visvel a partir do comando for at o final do bloco
onde o for definido. J em JAVA, o escopo de visibilidade da varivel
limitado pelo prprio comando for.
145
Para finalizar a discusso sobre o comando iterativo com nmero de repe-
ties definida, cabe ressaltar a no existncia de uma razo fundamental
para a seqncia de controle ser restrita a uma progresso (ou regresso)
sobre tipos primitivos discretos. Um tipo mais geral desse tipo de coman-
do deve permitir a iterao sobre qualquer coleo de elementos. Os co-
mandos de repetio existentes nas LPs abordadas anteriormente seriam
um caso particular no qual a coleo formada pelos elementos especifi-
cados no intervalo entre o valor da expresso inicial e final do comando.
Tal postura facilitaria muito a programao pois permitiria percorrer listas
de valores, arquivos, conjuntos e outros valores compostos com facilida-
de. O exemplo 5.32 apresenta o comando foreach de PERL, o qual permi-
te a iterao sobre os elementos de listas e vetores.
@dias = ("Dom", "Seg", "Ter", "Qua","Qui", "Sex", "Sab");
foreach $dia (@dias) {
print $dia
}
Exemplo 5. 32 - Comando foreach de PERL
Embora seja muito vantajoso, poucas LPs oferecem comandos que per-
correm diretamente colees de elementos. Uma vez que no possvel
encontrar uma forma genrica para percorrer os diferentes tipos de cole-
es e, em particular, conhecer de antemo como essas colees so im-
plementadas, LPs optam por deixar para o implementador da coleo a
responsabilidade de criar uma funo iteradora para cumprir essa funcio-
nalidade. Cabe destacar que LPs orientadas a objetos, como C++ e JAVA,
oferecem objetos iteradores associados s classes de coleo de sua bibli-
oteca padro os quais proporcionam exatamente essa funcionalidade.
5.2.1.6 Chamadas de Procedimentos
Assim como chamadas de funes so expresses, chamadas de procedi-
mentos so comandos. Chamadas de procedimento tm por objetivo atua-
lizar variveis. Isso feito atualizando variveis passadas pela lista de
parmetros ou alterando o valor de variveis no locais.
A diferena mais importante de um procedimento para uma funo o
fato do primeiro no retornar um valor. Dessa forma, boa parte do que foi
discutido na seo sobre chamadas de funo (seo 5.1.18) tambm se
aplica s chamadas de procedimento. Tal como uma chamada de funo,
a chamada de um procedimento tambm feita atravs da aplicao do
nome do procedimento a um ou mais valores correspondentes aos par-
metros do procedimento. Tal como uma chamada de funo, uma chama-
da de procedimento pode ter qualquer aridade e o seu formato pode ser
posicional ou por palavras chave.
146
5.1.2.7 Desvios Incondicionais
Todos os comandos listados at aqui exibem um fluxo de controle do tipo
entrada e sada nica. Esse padro adequado para a maioria dos propsi-
tos e incentiva a construo de programas estruturados, mais fceis de se
ler e de se manter. Contudo, em certas situaes ele pode ser muito restri-
tivo.
Existe consenso que comandos que permitem entrada nica e sada mlti-
pla podem ser bastante teis. Por outro lado, comando com entradas ml-
tiplas so verdadeiramente nocivos a programao estruturada e devem
ser evitados, seno proibidos.
Para permitir um fluxo de controle com entradas e sadas mltiplas, apro-
ximadamente todas LPs imperativas dispem de comandos de desvio in-
condicional, tais como desvios irrestritos, escapes e excees. Essas lti-
mas no sero discutidas aqui pois sero apresentadas em um captulo
prprio (o captulo 8).
5.2.1.7.1 Desvios Irrestritos
Esse comando transfere a execuo do programa para qualquer ponto es-
pecificado atravs de um rtulo. O comando de desvio irrestrito (mais
conhecido como goto) confere um grande poder e flexibilidade ao pro-
gramador. De fato, todos os demais comandos de fluxo de controle po-
dem ser construdos a partir do goto e de uma operao de seleo.
Por outro lado, esse comando oferece liberdade excessiva ao programa-
dor. Isso pode ser nocivo a boa qualidade de programao (DIJKSTRA,
1968), facilitando a ocorrncia da chamada programao macarrnica. Ao
contrrio do que postula a programao estruturada (estabelece que a le-
gibilidade de programas superior quando a execuo segue a ordem na
qual os comandos aparecem), programas com goto tendem a no seguir
ordem alguma. Por conta disso, alguns estudiosos de programao deseja-
ram que o comando goto fosse banido das LPs. Esse desejo foi atendido
em MODULA-2. Essa LP no possui o comando goto ou equivalente.
Contudo, em certas ocasies, o ganho em eficincia que o goto pode pro-
porcionar compensa uma possvel perda de legibilidade [KNUTH, 1974].
Em particular, o uso mais comum do goto para abandonar o processa-
mento em um fluxo de controle aninhado, tal como no caso em que se
deseja sair de duas repeties aninhadas ao mesmo tempo. O exemplo
5.33 usa o comando goto de C em uma situao como essa.
for (i = 0; i < n; i++)
for (j = 0; j < n; j++)
147
if ( a[i] == b[j] ) goto saida;
saida:
if (i < n) printf (achou!!!);
else printf (no achou!!!);
Exemplo 5. 33 - Uso de Comando goto em C
O exemplo 5.33 compara os elementos de dois vetores a e b. Ao encon-
trar um elemento comum, o fluxo de controle transferido para a linha
rotulada com o identificador saida. Nesse caso, o comando goto evita que
seja necessrio continuar as repeties at o final. Observe que o cdigo
escrito com goto pode ser reescrito sem ele, mas com o custo da insero
de uma varivel adicional e da realizao de alguns testes repetidos. O
exemplo 5.34 mostra essa alternativa.
achou = 0;
for (i = 0; i < n && !achou; i++)
for (j = 0; j < n && !achou; j++)
if ( a[i] == b[j] ) achou = 1;
if (achou) printf (achou!!!);
else printf (no achou!!!);
Exemplo 5. 34 - Cdigo Equivalente em C sem o uso de goto
Existem outras situaes nas quais o comando goto pode facilitar a pro-
gramao. Um exemplo na propagao de condies de erros de um
subprograma mais interno para um mais externo. Esse recurso muito
utilizado por LPs que no oferecem mecanismos de tratamento de exce-
es, tal como PASCAL.
Um ltimo comentrio sobre desvios irrestritos diz respeito aos rtulos
usados em associao ao comando goto. ADA requer que os identificado-
res de rtulos sejam envoltos entre << e >>. PASCAL s permite rtulos
numricos.
5.2.1.7.2 Escapes
Escapes so usados para permitir uma finalizao diferente da usual em
um comando, subprograma ou programa. Comandos de escapes so des-
vios incondicionais considerados estruturados, uma vez que s permitem
a realizao de desvios disciplinados no fluxo de controle do programa.
Em contraste com os comandos de desvio irrestrito, eles no podem ser
usados para criar ou entrar em repeties, nem para desviar o fluxo de
controle para um local qualquer do programa.
O escape mais comum permite sair de comandos iterativos. C, C++ e
JAVA possuem o comando de escape break. Esse comando tambm
148
usado para sair de comandos condicionais de caminhos mltiplos. O e-
xemplo 5.35 ilustra o uso do comando break em C.
s = 0;
for(;;) {
printf (n: );
scanf (%d, &n);
if (n <= 0) break;
s+=n;
}
Exemplo 5. 35 - O Comando break de C
O exemplo 5.35 realiza a mesma funcionalidade que os comandos iterati-
vos do exemplo 5.28. Note a falta de necessidade de repetir os comandos
printf e scanf para leitura de n e de duplicar a verificao do valor de n.
Isso possvel porque o comando break possibilita colocar o ponto de
parada (isto , a sada) do comando iterativo em qualquer local do seu
corpo.
De fato, a combinao de um comando de repetio infinita como o
for(;;), um comando simples de seleo como o if e um comando de es-
cape como o break suficiente para atender todas as demandas por co-
mandos iterativos com nmero de repeties indefinido. Por exemplo,
para substituir o comando com pr-teste basta colocar o ponto de parada
imediatamente aps o for(;;) e para substituir o comando com ps-teste
basta colocar o ponto de parada imediatamente antes do final do corpo da
repetio.
Outro tipo de escape existente em C, C++ e JAVA o comando continue.
Ele permite continuar o comando iterativo a partir do ponto onde foi co-
locado. Nesse caso, ao invs de finalizar o comando iterativo, o escape
usado para passar diretamente para o incio da prxima iterao. O exem-
plo 5.36 mostra o uso do comando continue para somar dez nmeros na-
turais.
i = 0;
s = 0;
while(i < 10) {
printf (n: );
scanf (%d, &n);
if (n < 0) continue;
s+=n;
i++;
}
Exemplo 5. 36 - O Comando continue de C
149
A associao de escapes com iteraes rotuladas pode ser til em situa-
es nas quais se quer sair ou continuar uma repetio mais externa a par-
tir de uma mais interna. Enquanto C, C++ e MODULA-2 no permitem
escapes rotulados, ADA e JAVA oferecem este tipo de escape. O exem-
plo 5.37 mostra o uso de escapes rotulados em JAVA em um trecho de
programa que identifica se existe um elemento comum presente em dois
vetores ordenados.
saida:
for (i = 0; i < n; i++) {
for (j = 0; j < n; j++) {
if ( a[i] < b[j] ) continue saida;
if ( a[i] == b[j] ) break saida;
}
}
if (i < n) printf (achou!!!);
else printf (no achou!!!);
Exemplo 5. 37 - Escapes Rotulados em JAVA
Outra categoria comum de escapes interrompe a execuo de subprogra-
mas ou programas. C possui utiliza o escape return para interromper a
execuo de um subprograma e a funo da biblioteca padro exit para
interromper a execuo de programas. O exemplo 5.38 ilustra o uso des-
ses comandos em C.
void trata (int erro) {
if (erro == 0) {
printf (nada a tratar!!!);
return;
}
if (erro < 0) {
printf (erro grave nada a fazer!!!);
exit (1);
}
printf(erro tratado!!!);
}
Exemplo 5. 38 - Escapes de Subprograma e Programa em C
No exemplo 5.38, o escape return usado para sair da funo trata quan-
do no ocorreu erro e o escape exit usado para terminar o programa
quando o erro grave demais para ser tratado.
A combinao de comandos de escapes, escapes rotulados e mecanismo
de tratamento de excees tem possibilitado a JAVA no possuir coman-
do de desvio irrestrito. O comando goto uma palavra reservada em JA-
VA, porm no faz parte da linguagem atualmente. Isso significa que, em
150
princpio, os projetistas de JAVA no consideram que o comando goto
seja realmente necessrio. Contudo, eles deixam aberta a possibilidade de
incorporar esse comando linguagem, caso seja constatada sua necessi-
dade para atender usos no contemplados por aquelas outras construes
da LP.
5.3 Consideraes Finais
Nesse captulo foram discutidos os diversos tipos fundamentais de ex-
presses e comandos existentes em LPS imperativas. Uma LP imperativa
fica empobrecida quando no possui ou restringe arbitrariamente qual-
quer dos tipos de expresses e comandos apresentados aqui. Por outro
lado, expresses e comandos adicionais podem muito bem no acrescen-
tar nada ao poder expressivo da LP. Por exemplo, comandos de entrada e
sada existentes em FORTRAN e COBOL poderiam ser substitudos por
chamadas de procedimentos (tal como em PASCAL, C e ADA).
Pode-se observar uma certa interseo entre as funcionalidade de alguns
tipos de expresses e comandos. Expresses e comandos condicionais,
expresses com efeito colateral e atribuies, chamadas de funes e pro-
cedimentos tm uma correlao funcional. Por conta disso, algumas LPs
optaram por suprimir todas as distines entre comandos e expresses.
So chamadas de LPs orientadas a expresses. Nessas LPs, a avaliao de
uma expresso retorna um valor e pode ter como efeito colateral a atuali-
zao de uma varivel. So exemplos de LPs orientadas a expresso AL-
GOL-68 e ML.
C tambm pode ser considerada uma LP orientada a expresses desde que
se considere o retorno das operaes de fluxo de controle como void. As-
sim, a atribuio v = e uma expresso que retorna o valor da expresso
e junto com o efeito colateral de atribuir esse valor a v. Dessa maneira,
operaes como atribuio mltipla u = v = e; se tornam natural.
5.6

As principais vantagens das LPs orientadas a expresses so a obteno
de maior simplicidade, visto que se evita a duplicao entre comandos e
expresses anlogas, e uniformidade, visto que se elimina a separao das
operaes de controle de fluxo e atribuio em comandos e expresses. A
principal desvantagem dessa abordagem encorajar o uso de efeitos cola-
terais, o que frequentemente incentiva a m prtica de programao.

5.6
Algumas LPs orientadas a expresso retornam 0 ou void para a atribuio. Por exemplo, ML.
151
5.4 Exercicios
1. Um programa deve ler uma sequncia de nmeros inteiros e imprim-
los. O programa deve ser interrompido quando o nmero lido for zero.
Implemente trs verses desse programa em C usando respectivamen-
te os comandos iterativos com pr-teste, com ps-teste e um comando
de escape. Discuta as solues apresentadas em termos de redigibili-
dade e eficincia, indicando a melhor soluo apresentada.

2. Descreva o que ocorre em cada trecho que culmina com impresses no
seguinte programa em C, justificando suas afirmaes.

#include <stdio.h>
main () {
int a, b, c;
b = c = 10;
a = b++ + b++;
printf("%d\n", a );
printf("%d\n", b );
a = ++c + ++c;
printf("%d\n", a );
printf("%d\n", c );
b = 10;
a = b++ + b;
printf("%d\n", a );
printf("%d\n", b );
a = 10;
b = 5;
if (a>b || ++b>5)
printf("%d\n", b);
a = 1;
b = 5;
if (a>b || ++b>5)
printf("%d\n", b);
}

Esse programa em C portvel? O que ocorreria se um programa e-
quivalente (isto , usando classe e com o comando apropriado de sa-
da) fosse implementado em JAVA? Justifique todas as suas afirma-
es.

3. Faa um programa em C (sem usar os escapes return ou exit) para ler
um nmero inteiro positivo e calcular a tabuada de multiplicao de 0
a 9 de todos os nmeros positivos inferiores ao nmero lido. Sempre
que o resultado de uma multiplicao for mltiplo de dez, o programa
152
deve perguntar ao usurio se ele deseja continuar com o clculo da ta-
buada. Portanto, o programa deve se encerrar ao final do clculo da
tabuada ou quando o usurio responder que no deseja continuar o
clculo. A sua soluo a mais eficiente para esse problema? Porque?
Como a soluo mais eficiente seria implementada em JAVA?

4. Apresente um exemplo de expresso em C onde ocorre curto-circuito
associado a efeito colateral. Analise o efeito que tal expresso pode
produzir sobre a legibilidade de um programa.

5. O comando goto empregado para realizar desvios incondicionais no
fluxo de controle de programas. Com o advento das tcnicas de pro-
gramao estruturada, este comando foi muito criticado e, por muitas
vezes, se sugeriu que linguagens de programao no deveriam inclu-
lo entre seus comandos. C uma linguagem que adota os princpios da
programao estruturada. No entanto, C manteve o goto como um co-
mando da linguagem. Qual a razo dessa deciso? Exemplifique, com
um trecho de programa em C, uma situao na qual pode ser til em-
pregar o comando goto. Discuta como programadores da linguagem
MODULA-2 e JAVA, que no incluem o goto entre seus comandos,
lidam com esta situao.

6. Diga qual o valor das variveis a e n aps cada linha do seguinte tre-
cho de cdigo C. Justifique suas respostas.

n = 3;
a = - n ++;
a = - n + 1;
a = - n += 1;

7. Modifique o seguinte trecho de cdigo para que ele realize a semnti-
ca sugerida pela sua disposio textual.

if ( x == 7 )
if ( y == 11)
z = 13;
else z = 17;

8. Veja como funciona o exemplo 5.37. Execute-o passo a passo conside-
rando que n tem valor 8 e os vetores a e b foram definidos da seguinte
maneira:

int[] a = {1, 3, 6, 9, 10, 12, 16, 18};
int[] b = {2, 4, 5, 7, 8, 10, 11, 15};
153

Reimplemente esse exemplo em C garantindo que ele funcione exata-
mente como em JAVA.

9. Implemente e teste o seguinte programa em C e descreva o que acon-
tece. Justifique porque isso ocorre dessa maneira (isto , apresente o
racional da deciso tomada pelos projetistas ou implementadores dessa
LP).

void f() {
int i = 10;
entra:
i++;
}
main() {
f();
goto entra;
}

10. Analise o seguinte programa em C, identificando o que ele faz. Faa
uma crtica ao estilo de programao utilizado.

main() {
int i, j, k;
k = 1;
for (i = 0; i < 10; i++) {
entra:
j = 2 * i + 1;
printf(i: %d, j: %d\n, i, j);
}
if (k) {
k = 0;
i = 7;
goto entra;
}
}

11. Algumas LPs (tal como, C) consideram a operao de atribuio co-
mo sendo uma espcie de expresso (isto , a atribuio uma opera-
o que retorna um valor). D exemplos de situaes nas quais isso
pode ser vantajoso. Diga tambm quando essa caracterstica pode ser
danosa para a qualidade da programao. Justifique sua resposta.

154

Captulo VI Modularizao


Isto
s a
ponta de um
iceberg submerso.
O erdadeiro poema
esta embaixo deste erso.`
Luis lernando Verissimo
Na poca em que surgiram os primeiros computadores, os programadores
eram forados a buscar o mximo de eficincia nos seus programas por
causa das limitaes dos recursos computacionais. Os primeiros progra-
mas consistiam de um nico bloco monoltico de cdigo pois a diviso de
programas em vrios blocos exige um sistema de gerenciamento de me-
mria e consome mais recursos. O programador devia utilizar um peque-
no nmero de variveis, as quais cumpriam diferentes papis ao longo do
programa, com o intuito de economizar memria. Ao invs de se buscar
ordenao no fluxo de controle dos programas, os programadores tinham
de definir o fluxo mais eficiente, o que normalmente exigia o uso extensi-
vo e indiscriminado de comandos de desvio incondicional (tal como o
goto).
Enquanto as aplicaes desenvolvidas eram de pequeno porte, isto , en-
volviam um nmero relativamente pequeno de linhas de cdigo e realiza-
vam poucas tarefas (se comparadas s aplicaes atuais), essa forma de
desenvolver programas era satisfatria. Contudo, na medida que novos
recursos computacionais se tornavam disponveis e que se vislumbrava o
grande potencial dos computadores para a construo de aplicaes mais
complexas, foi se constatando que a programao em bloco monoltico
tornava pouco vivel a construo de grandes sistemas de programao.
Tipicamente, os programas eram escritos por um nico programador, pois
no havia como dividir o programa em trechos relativamente independen-
tes que pudessem ser construdos por programadores diferentes. Alm de
impedir a diviso do trabalho, o fato das mesmas variveis serem usadas
em todo o programa e o fluxo de controle poder ir de um ponto a qualquer
outro do programa provocava a ocorrncia de um grande nmero de erros
na programao, atrasando o desenvolvimento do sistema.
Com a maior disponibilidade de recursos computacionais, o principal fa-
tor inibidor do desenvolvimento e disseminao de aplicaes de grande
porte passa a ser a eficincia de programao, isto , o tempo de trabalho
155
dos programadores. Era preciso avanar um pouco mais no processo de
desenvolvimento de programas.
Para tornar mais eficiente o trabalho dos programadores, foi identificada a
necessidade de se apoiar o processo de resoluo de problemas comple-
xos. Apoiar esse processo envolve tanto a elaborao de tcnicas para
permitir o programador resolver um problema complexo totalmente novo
(criao de solues) quanto para permitir reusar solues criadas para
um problema em um novo problema semelhante (reuso de trabalho).
Em geral, a tcnica fundamental utilizada para resolver problemas com-
plexos consiste no uso da estratgia de dividir para conquistar. Usar
essa estratgia implica resolver um grande problema dividindo-o em v-
rios subproblemas mais gerenciveis e resolv-los independentemente.
Essa estratgia aumenta ainda a potencialidade de reuso de trabalho, uma
vez que se pode disponibilizar para reuso as solues dos subproblemas
ao invs de disponibilizar apenas uma soluo completa do problema.
A implementao da estratgia de dividir para conquistar em LPs rea-
lizada atravs de tcnicas de modularizao. Alm de apoiar a resoluo
de problemas complexos, essas tcnicas tornam mais fcil o entendimento
dos programas e viabilizam o reuso de cdigo. Basicamente, as tcnicas
de modularizao promovem a segmentao do programa em mdulos e
o encapsulamento de dados. Encapsular dados significa agrupar dados e
processos logicamente relacionados em uma mesma entidade de compu-
tao.
Abstrao um conceito fundamental para se atingir uma boa modulari-
zao. Abstrao o processo no qual se seleciona, do contexto do pro-
blema a resolver, o que importante de ser representado para resolver o
problema. A abstrao possibilita o trabalho em nveis no desenvolvimen-
to de programas. Em um nvel inferior, identificam-se as abstraes im-
portantes no contexto do problema e implementam-se mdulos corres-
pondentes a essas abstraes. Em um nvel superior, utiliza-se as abstra-
es do nvel inferior para resolver um problema sem que se necessite
preocupar com os detalhes da implementao dessas abstraes.
Inicialmente, esse captulo discute em maior detalhe o conceito de abstra-
o e o seu papel em LPs. Ateno especial dada a relao desse concei-
to com a idia de modularizao. Em seguida, apresenta-se e discute-se
tcnicas de modularizao oferecidas por LPs para permitir a implemen-
tao de abstraes. Por fim, discute-se algumas formas de operacionali-
zao dessas tcnicas em programas cujo cdigo fonte dividido em ml-
tiplos arquivos.
156
6.1 Abstraes
Abstrao um processo importante em todas as reas do conhecimento.
Abstrao fundamental para o raciocnio e resoluo de problemas em
geral porque impossvel representar qualquer fenmeno da realidade em
toda a sua riqueza de detalhes. Em outras palavras, devemos sempre nos
concentrar nos aspectos essenciais do problema e ignorar os aspectos irre-
levantes para a resoluo do problema em questo. Caso contrrio, a tare-
fa de representar a realidade se torna impraticvel e intratvel.
Como no poderia deixar de ser, o conceito de abstrao amplamente
disseminado dentro da rea de computao. Ele tem sido empregado fre-
quentemente no sentido de facilitar a resoluo de problemas por compu-
tador atravs do provimento de mecanismos que tornem mais simples a
sua operao e programao. Alguns exemplos do uso do conceito de
abstrao em computao so:
Ao se utilizar os comandos de um sistema operacional, est-se abs-
traindo do uso de instrues especficas de hardware para controlar
e manipular o computador.
Ao se usar instrues de uma linguagem de baixo nvel (assem-
bly), est-se abstraindo do uso de instrues binrias para a pro-
gramao de computadores.
Ao se usar instrues de uma linguagem de programao de alto
nvel, como PASCAL ou C, est-se abstraindo do uso de instrues
em linguagem assembly.
Ao se usar um programa de reservas de passagens, est-se abstra-
indo do conjunto de instrues que descrevem como se realiza o
processo de reserva.
Em LPs, especificamente, o conceito de abstrao utilizado segundo
duas perspectivas:
a) A LP funciona como uma abstrao sobre o hardware, isto , os
programadores podem entender o computador como sendo uma
nova mquina capaz de entender os comandos da LP.
b) A LP fornece um conjunto de mecanismos para o programador cri-
ar e representar suas abstraes sobre o problema. A partir do mo-
mento que o programador cria uma abstrao, ela se incorpora aos
elementos da LP que podem ser usados pelo programador ou por
outros programadores para criar novas abstraes e programas.
nessa ltima perspectiva que o conceito de abstrao serve como base
para a modularizao. Em programas bem modularizados, cada mdulo
157
corresponde a uma abstrao existente no contexto do problema. Diferen-
tes LPs fornecem mecanismos distintos para suportar o processo de abs-
trao dos programadores. Sees subsequentes desse captulo discutem
como LPs possibilitam ao programador criar e representar as abstraes
do programa. Particular destaque dado nas formas como esses meca-
nismos so usados para permitir a distino entre:
o que uma parte do programa faz (foco do programador que usa a
abstrao)
como isso implementado (foco do programador que implementa a
abstrao)
6.1.1 Tipos de Abstraes
Programas so conjuntos de instrues descrevendo como realizar pro-
cessos para manipular, alterar e produzir dados. Para se poder criar pro-
gramas, o conjunto de instrues da LP deve ser capaz de descrever dados
e descrever como realizar processos.
Nos captulos 3 e 4, foi visto que LPs fornecem valores, tipos, variveis e
constantes para a representao de estruturas de dados. No captulo 5,
tambm foi visto que elas fornecem diversos tipos de expresses e co-
mandos de controle de fluxo de execuo para a descrio de processos.
Contudo, esses mecanismos so insuficientes para suportar as necessida-
des de abstrao do programador, principalmente quando levamos em
conta as demandas por encapsulamento e reuso de cdigo.
Para atender essas necessidades, uma LP deve fornecer mecanismos para
permitir ao programador a criao de novas formas de expresses, co-
mandos ou representaes de dados. Nesse sentido, os mecanismos de
abstrao fornecidos por LPs podem ser classificados em:
a) Abstraes de Processos: so abstraes sobre o fluxo de controle
do programa.
b) Abstraes de Dados: so abstraes sobre as estruturas de dados
do programa.
O mecanismo mais comum em LPs para provimento de abstraes de
processos so os subprogramas. Subprogramas definem trechos limitados
de programa onde podem ser definidas entidades como variveis, cons-
tantes e tipos, as quais so utilizadas apenas localmente dentro do cdigo
do subprograma. Subprogramas podem ser chamados vrias vezes em um
mesmo programa ou at mesmo em programas diferentes, o que possibili-
ta o reuso de cdigo.
158
Subprogramas podem ser do tipo funo ou procedimento. Enquanto um
subprograma do tipo funo uma abstrao de uma expresso, um sub-
programa do tipo procedimento uma abstrao de um comando. Isso
significa que, ao construir um subprograma, o programador cria um novo
tipo de fluxo de controle dos programas, aumentando assim o conjunto de
instrues fornecidos pela LP.
Por exemplo, a funo sqrt da biblioteca padro de C uma abstrao
sobre um conjunto de instrues que produz a raiz quadrada de um nme-
ro como resultado. Na chamada dessa funo, o programador se abstrai de
todas as instrues que compem essa funo. Para ele, sqrt funciona
como uma instruo pr-existente da LP.
Anlogo s abstraes de processos, que permitem ver uma determinada
combinao de comandos e expresses como sendo um novo comando ou
expresso, abstraes de dados permitem ver uma determinada combina-
o de dados como sendo um novo dado ou tipo de dados.
Quando se v uma coleo de bits como sendo uma clula de memria e
passa-se a tratar essa coleo de bits como unidade bsica de armazena-
mento, est-se construindo uma abstrao de dados. Pode-se agora enxer-
gar a memria como composta por clulas de memria (em vez de bits) e
realizar operaes sobre essas clulas.
Abstraes a nvel de clula de memria ainda esto muito relacionadas
com a mquina fsica, sendo frequentemente usadas apenas em lingua-
gens de programao chamadas de baixo nvel. Desde o aparecimento das
linguagens de programao de alto nvel, uma de suas caractersticas mais
importantes tem sido a incluso de abstraes de dados num nvel mais
conceitual. Por exemplo, a incluso de tipos de dados inteiro, ponto flutu-
ante e vetorial na prpria LP permite ao programador utilizar dados des-
ses tipos sem que se tenha de preocupar significativamente em como es-
ses dados esto sendo efetivamente armazenados e manipulados. Assim,
os programadores podem construir mais facilmente novas abstraes de
dados cada vez mais complexas. Por exemplo, construir uma estrutura de
dados para a representao de uma agenda muito mais fcil se utiliza-
mos uma LP cujos conceitos de nmeros inteiros e matrizes sejam pr-
definidos do que em uma LP que manipule apenas dados binrios e clu-
las de memria.
J se constatou que o fornecimento de tipos de dados pr-definidos na LP
como nico mecanismo de abstrao de dados no suficiente para aten-
der s necessidades de programao com qualidade. Muitas vezes, o pro-
gramador precisa criar seus prprios tipos de dados (como por exemplo, o
tipo pilha) para tornar o cdigo mais legvel, redigvel, flexvel, confivel
159
e reusvel. LPs tm fornecido vrios mecanismos para permitir ao pro-
gramador criar suas abstraes de dados.
Os principais mecanismos fornecidos por LPs para a implementao de
abstraes de processos e de dados so discutidos na prxima seo.
6.2 Tcnicas de Modularizao
Tcnicas de modularizao foram desenvolvidas com o propsito princi-
pal de dar apoio programao de sistemas de grande porte, embora tam-
bm sejam teis programao de sistemas de pequeno porte.
Em contraste aos sistemas de pequeno porte, sistemas de grande porte se
caracterizam por envolverem um nmero grande de entidades de compu-
tao, por normalmente serem implementados por uma equipe de pro-
gramadores e por serem compostos por um nmero grande de linhas de
cdigo, geralmente distribudas em muitos arquivos fontes.
Outra distino importante entre sistemas de pequeno e grande porte pode
ser feita ao se considerar o processo de compilao desses tipos de siste-
mas. Enquanto em sistemas de pequeno porte, compilar e recompilar o
programa por completo aps uma modificao no muito dispendioso, o
mesmo no ocorre em sistemas de grande porte. Nesses ltimos conve-
niente evitar a recompilao das partes no alteradas do programa.
No estudo de LPs costuma-se considerar um mdulo como uma unidade
de programa a qual pode ser compilada separadamente. Um mdulo bem
projetado tem um nico propsito e uma boa interface com outros mdu-
los. Mdulos so reutilizveis (podem ser incorporados em vrios pro-
gramas) e modificveis (podem ser revisados sem forar mudanas nos
outros mdulos).
Um mdulo bem projetado deve identificar claramente:
Qual o seu objetivo? (preocupao do usurio)
Como ele atinge seu objetivo? (preocupao do implementador)
Um mdulo pode ser composto por um nico tipo, varivel, constante,
procedimento ou funo, ou mesmo um conjunto deles. mais comum,
porm, que um mdulo seja formado por um grupo composto por vrios
componentes distintos (tipos, constantes, variveis, procedimentos e fun-
es) declarados com um objetivo comum.
Tcnicas de modularizao permitem transformar trechos do programa
em unidades lgicas relacionadas. Para isso, a LP deve oferecer meca-
nismos a partir dos quais se possa encapsular trechos contendo vrias en-
tidades de programao correlacionadas em uma nica entidade de pro-
gramao.
160
6.2.1 Subprogramas
O primeiro avano em direo modularizao de programas foi a cria-
o do conceito de subprogramas. Eles permitem segmentar um programa
em vrios blocos logicamente relacionados. Subprogramas tambm ser-
vem para evitar que trechos de cdigo muito semelhantes tenham de ser
completamente reescritos simplesmente por que operam sobre dados dife-
renciados. Isso pode ser feito atravs dos mecanismos de parametrizao
dos subprogramas.
No faz sentido dividir um programa em vrios subprogramas levando
em conta, exclusivamente, o tamanho do cdigo de cada subprograma.
Modularizaes efetuadas dessa maneira possuem baixa qualidade. De
fato, um subprograma deve ser responsvel por realizar uma determinada
funcionalidade, mantendo sempre correspondncia com uma abstrao de
processo.
Essa postura aumenta a legibilidade dos programas e facilita a depurao,
manuteno e o reuso de cdigo. Quando um subprograma possui um
propsito nico e claro, a leitura do programa fica muito mais fcil pois
no h necessidade de se analisar o cdigo do subprograma para saber o
que ele faz. Tambm fica mais fcil identificar onde preciso modificar o
cdigo durante a depurao ou alterao do programa. Isso ocorre porque
as correes no cdigo ficam concentradas no subprograma cuja funcio-
nalidade necessita ser depurada ou alterada. Alm disso, se torna muito
mais simples e efetivo reusar cdigo pois se pode usar o subprograma
sempre que sua funcionalidade for necessria.
6.2.1.1 Perspectivas do Usurio e do Implementador de Subprogra-
mas
interessante analisar funes e procedimentos segundo as perspectivas
do usurio da abstrao e do implementador da abstrao [WATT, 1990].
Uma funo abstrai uma expresso a ser avaliada. Ela produz um valor
como resultado. O usurio da funo se importa apenas com o resultado,
no se interessando com o modo como ele foi obtido. A funo do exem-
plo 6.1, em C, calcula o fatorial de um nmero inteiro no negativo n
6.1
.
int fatorial(int n) {
if (n<2) {
return 1;
} else {

6.1
Assume-se que essa funo s aplicada a nmeros inteiros no negativos
161
return n * fatorial (n 1);
}
}
Exemplo 6. 1 Vises do Usurio e do Implementador de Funo em C
A viso do usurio em uma chamada de funo a de que ela mapeia os
argumentos usados na chamada em um resultado. Assim, a funo fatori-
al do exemplo 6.1 mapeia um inteiro n para o resultado n!.
A viso do implementador de uma funo a de que ela executa o corpo
da funo tendo recebido previamente os valores dos seus argumentos.
Ao implementador interessa o algoritmo da funo. Na funo fatorial, a
viso do implementador que ela calcula seu resultado atravs de um al-
goritmo recursivo.
Um procedimento abstrai um comando a ser executado. Quando um pro-
cedimento chamado, ele atualiza variveis. O usurio dessa abstrao
observa apenas essas atualizaes, no se importando em como elas fo-
ram efetivadas. O exemplo 6.2, em C, ordena crescentemente um vetor de
nmeros inteiros.
void ordena (int numeros[50]) {
int j, k, aux ;
for (k = 0; k < 50; k++) {
for (j = 0; j < 49; j++) {
if (numeros[j] > numeros[j+1]) {
aux = numeros[j];
numeros[j] = numeros[j+1];
numeros[j+1] = aux;
}
}
}
}
Exemplo 6. 2 - Perspectivas do Usurio e do Implementador de um Procedimento em C
6.2

O implementador enxerga essa abstrao de procedimento como a im-
plementao de um algoritmo de ordenao pelo mtodo da bolha. A vi-
so do usurio a de que existe um comando chamado ordena(v), o qual
produz como efeito a ordenao de v. Se a implementao for melhorada,
o procedimento executar mais eficientemente, mas o usurio observar o
mesmo efeito.

6.2
A ttulo de ilustrao desse conceito, funes em C que retornam void so consideradas como proce-
dimentos.
162
6.2.1.2 Parmetros
Parmetros facilitam o processo de aplicao de dados diferenciados a
chamadas distintas de um subprograma. Sem o uso de parmetros, a utili-
dade de subprogramas se concentraria na segmentao de cdigo em tre-
chos menores. O reuso de subprogramas seria muito mais difcil pois ha-
veria reduo de redigibilidade, legibilidade e confiabilidade. O exemplo
6.3 ilustra o uso de um subprograma em C implementado sem o uso de
parmetros.
int altura, largura, comprimento;
int volume () { return altura * largura * comprimento; }
main() {
int a1 = 1, l1 = 2, c1 = 3, a2 = 4, l2 = 5, c2 = 6;
int v1, v2;
altura = a1;
largura = l1;
comprimento = c1;
v1 = volume();
altura = a2;
largura = l2;
comprimento = c2;
v2 = volume();
printf (v1: %d\nv2: %d\n, v1, v2);
}
Exemplo 6. 3 - Funo Sem Parmetros em C
Observe no exemplo 6.3 que a funo volume usada para calcular o vo-
lume de um paraleleppedo. Como ela no possui parmetros, necess-
rio utilizar trs variveis globais altura, largura e comprimento para lhe
conferir generalidade e possibilitar o seu reuso em duas linhas de main.
Essa postura compromete a redigibilidade do cdigo porque antes de
chamar volume sempre necessrio incluir operaes para atribuir os va-
lores desejados s variveis globais. Ela tambm reduz a legibilidade do
cdigo pois na chamada de volume no existe qualquer meno necessi-
dade de uso dos valores das variveis altura, largura e comprimento. Ela
pode ainda diminuir a confiabilidade do cdigo por no exigir que sejam
atribudos valores a todas as variveis globais utilizadas em volume. As-
sim, um programador inadvertido poderia deixar de atribuir um valor a
uma das variveis altura, largura e comprimento e ainda assim o progra-
ma seria compilado e executaria normalmente.
Todos esses problemas se resolvem com a utilizao de parmetros.
Compare a implementao do exemplo 6.3 com a implementao do e-
163
xemplo 6.4, o qual realiza a mesma funcionalidade do exemplo 6.3, mas
agora usando parmetros.
int volume (int altura, int largura, int comprimento) {
return altura * largura * comprimento;
}
main() {
int a1 = 1, l1 = 2, c1 = 3, a2 = 4, c2 = 5, l2 = 6;
int v1, v2;
v1 = volume(a1, l1, c1);
v2 = volume(a2, l2, c2);
printf (v1: %d\nv2: %d\n, v1, v2);
}
Exemplo 6. 4 - Funo em C com Parmetros
fcil observar no exemplo 6.4 a melhoria da redigibilidade em virtude
da no necessidade de fazer operaes de atribuio a variveis globais.
A legibilidade tambm aumenta pois as chamadas de volume explicitam
os valores utilizados em cada chamada. Alm disso, a confiabilidade
incrementada porque qualquer tentativa de chamar volume sem a especi-
ficao de algum dos parmetros produz um erro de compilao.
Habitualmente se utiliza o termo parmetro para designar conceitos dis-
tintos. Em alguns contextos, o termo parmetro se refere aos identificado-
res listados no cabealho do subprograma e usados no seu corpo. No es-
tudo de LPs se convencionou usar o termo parmetro formal para desig-
nar esse conceito. importante lembrar que parmetros formais so, ne-
cessariamente, identificadores de variveis ou constantes, isto , no po-
dem ser valores nem tampouco expresses.
Em outros contextos, o termo parmetro se refere aos valores, identifica-
dores ou expresses utilizados na chamada do subprograma. Em LPs se
usa o termo parmetro real para denotar esse conceito. Ainda em outros
contextos, o termo argumento usado como sinnimo de parmetro. Em
LPs esse termo usado para designar o valor passado do parmetro real
para o parmetro formal.
O exemplo 6.5 ilustra esses conceitos atravs de um programa em C que
calcula a rea de um crculo. O parmetro formal de area o identifica-
dor r. O parmetro real e o argumento da chamada de area em main so
respectivamente a expresso diametro/2 e o valor 1.4.
float area (float r) {
return 3.1416 * r * r;
}
main() {
float diametro, resultado;
164
diametro = 2.8;
resultado = area (diametro/2);
}
Exemplo 6. 5 - Parmetro Formal, Parmetro Real e Argumento de Subprograma
6.2.1.3 Correspondncia entre Parmetros Formais e Parmetros
Reais
Existem duas formas de correspondncia entre a lista de parmetros for-
mais na declarao do subprograma e a lista de parmetros reais na cha-
mada do subprograma. A forma mais comum de correspondncia a po-
sicional, adotada por C, C++ e JAVA e pela imensa maioria de LPs. Nes-
se tipo de correspondncia a sequncia na qual os parmetros so escritos
determina a correspondncia entre os parmetros reais e formais. A outra
forma de correspondncia, conhecida como por palavras chave, envolve a
listagem explcita dos parmetros reais e seus correspondentes formais na
chamada do subprograma. Ressalta-se a necessidade, em ambos tipos de
correspondncia, de haver compatibilidade de tipos entre o parmetro real
e seu correspondente parmetro formal. O exemplo 6.6 ilustra o uso de
correspondncia por palavras chave em ADA.
procedure palavrasChave is
a: integer := 2;
b: integer := 3;
c: integer := 5;
res: integer;
function multiplica(x, y, z: integer) return integer is
begin
return x * y * z;
end multiplica;
begin
res := multiplica(z=>b, x=>c, y=>a);
end palavrasChave;
Exemplo 6. 6 - Correspondncia por Palavras Chave em ADA
A correspondncia por palavras chave til principalmente quando a lista
de parmetros longa, uma vez que no obriga o programador a lembrar
a sequncia na qual os parmetros formais foram declarados. Em contra-
partida, o programador deve saber o nome dado aos parmetros formais
na chamada do subprograma.
Algumas LPs, como ADA, permitem o uso das duas formas de corres-
pondncia de parmetros. De fato, em uma chamada de subprograma po-
de-se usar isoladamente qualquer uma das formas ou mesmo us-las de
modo combinado.
165
Em algumas LPs, tais como C++ e ADA, possvel definir valores de-
fault para os parmetros formais na declarao do subprograma. Valo-
res default so valores atribudos aos parmetros formais caso os pa-
rmetros reais correspondentes sejam omitidos na chamada do subpro-
grama. O exemplo 6.7 ilustra o uso desses valores em C++.
int soma (int a[], int inicio = 0, int fim = 7, int incr = 1){
int soma = 0;
for (int i = inicio; i < fim; i+=incr) soma+=a[i];
return soma;
}
main() {
int [] pontuacao = { 9, 4, 8, 9, 5, 6, 2};
int ptotal, pQuaSab, pTerQui, pSegQuaSex;
ptotal = soma(pontuacao);
pQuaSab = soma(pontuacao, 3);
pTerQui = soma(pontuacao, 2, 5);
pSegQuaSex = soma(pontuacao, 1, 6, 2);
}
Exemplo 6. 7 - Valores Default de Parmetros em C++
No exemplo 6.7 foi criada a funo soma para calcular a pontuao acu-
mulada por um jogador em diferentes dias de uma semana de competio.
Observe que trs parmetros formais de soma possuem valores de-
fault. Assim, possvel calcular a pontuao total, obtida pelo jogador
na semana, omitindo, na chamada de soma, todos parmetros reais cujos
formais possuem valor default. J para a pontuao obtida entre quarta
e sbado, deve-se especificar na chamada o parmetro real corresponden-
te ao formal inicio. Para se obter a pontuao acumulada de tera a quinta
preciso especificar os parmetros inicio e fim. Por fim, para obter a pon-
tuao acumulada na segunda, quarta e sexta necessrio especificar to-
dos os parmetros reais possveis.
Em C++ os parmetros default devem ser sempre os ltimos da lista.
Isso significa que uma tentativa de retirar o valor default dos parme-
tros formais fim ou incr na funo soma do exemplo 6.7 produziria um
erro de compilao. Essa restrio existe em C++ para evitar ambigida-
de na correspondncia posicional entre parmetros formais e reais.
Normalmente LPs requerem na definio da funo a especificao de
um nmero de parmetros reais igual ao nmero de parmetros formais.
Elas tambm requerem que os tipos dos parmetros reais sejam compat-
veis com os dos parmetros formais correspondentes. C e C++ so exce-
es pois permitem a criao de funes cujo nmero de parmetros reais
pode variar de uma chamada para outra. Uma forma de fazer isso em C++
166
atravs do uso de parmetros default. Contudo, as variaes permi-
tidas no nmero de parmetros so relativamente pequenas pois so limi-
tadas pelo nmero de parmetros default utilizados. Alm disso, os
tipos dos parmetros no podem variar de uma chamada para outra. Por-
tanto, esse mecanismo no suficientemente genrico para criar subpro-
gramas nos quais no se possa antecipar o nmero e o tipo dos parme-
tros.
Um exemplo dessa categoria de subprogramas a funo printf de C.
Como essa funo usada pelos mais variados programas nos mais varia-
dos contextos, qualquer limitao no seu nmero e tipo de parmetros a
tornaria insuficiente para atender todas suas possveis demandas. C e C++
possibilitam a criao de funes dessa categoria atravs do uso do sm-
bolo ... (reticncias) encerrando a lista de parmetros formais na definio
do cabealho da funo.
Ao se definir uma funo com tipo e nmero de parmetros varivel
necessrio ter algum modo do corpo da funo obter o valores e tipos dos
parmetros de uma chamada. Sem isso no haveria utilidade em se permi-
tir parmetros variveis, uma vez que o corpo do subprograma no pode-
ria operar sobre eles. Para resolver esse problema, C e C++ oferecem um
conjunto de macros na biblioteca padro stdarg.h e exigem que exista pe-
lo menos um parmetro nomeado e no varivel na definio do cabea-
lho da funo. Esse parmetro deve fornecer as informaes necessrias
sobre os tipos e sobre o nmero dos parmetros variveis. O prottipo da
funo printf de C mostrado a seguir.
int printf (char* fmt, ... );
Observe o uso da string de formatao fmt como parmetro nomeado. Es-
se parmetro oferece as informaes sobre os tipos e nmero dos parme-
tros reais de uma chamada do subprograma. Observe ainda o uso das reti-
cncias colocadas obrigatoriamente ao final da lista de parmetros. O e-
xemplo 6.8 mostra a definio completa de uma funo dessa categoria e
a sua utilizao por um programa.
#include <stdarg.h>
int ou (int n, ) {
va_list vl;
int i, r = 0;
va_start (vl, n);
for (i = 0; i < n; i++)
if (va_arg (vl, int)) r = 1;
va_end (vl);
return r;
}
167
main() {
printf (%d\n, ou (1, 3 < 2));
printf (%d\n, ou (2, 3 > 2, 7 > 5));
printf (%d\n, ou (3, 1 != 1, 2 != 2, 3 != 3));
printf (%d\n, ou (3, 1 != 1, 2 != 2, 3 == 3));
}
Exemplo 6. 8 - Funo com Nmero de Parmetros Varivel em C
A funo ou do exemplo 6.8 calcula a operao de conjuno lgica sobre
todos os parmetros reais de uma chamada (com exceo do parmetro
nomeado n). A funo ou espera parmetros reais inteiros na lista vari-
vel. Assim, o parmetro nomeado usado apenas para especificar o n-
mero de parmetros adicionais de cada chamada de ou. Observe ainda o
uso das macros va_list, va_start, va_arg e va_end de stdarg.h. O tipo
va_list usado para definir uma varivel vl a qual se refere a cada argu-
mento por sua vez. A macro va_start inicializa vl para designar o primei-
ro argumento no nomeado. Note que va_start recebe como argumento o
identificador do ltimo parmetro nomeado da lista de parmetros. J
va_arg retorna um dos argumentos da lista de parmetros variveis cada
vez que chamada. preciso informar na chamada de va_arg o tipo do
valor a ser retornado. Ao final preciso chamar va_end antes do retorno
da funo.
Se, por um lado, a existncia dessa categoria de funes oferece uma
maior flexibilidade s LPs, por outro lado, isso reduz a confiabilidade dos
programas uma vez que no possvel verificar os tipos dos parmetros
em tempo de compilao. Nesses subprogramas, tarefa exclusiva dos
programadores, que implementam e usam essas funes, garantir o corre-
to funcionamento do subprograma.
6.2.1.4 Passagem de Parmetros
Passagem de parmetros o processo no qual os parmetros formais as-
sumem seus respectivos valores durante a execuo de um subprograma.
Tambm faz parte do processo de passagem de parmetros a eventual a-
tualizao de valores dos parmetros reais durante a execuo do subpro-
grama. LPs fornecem modos distintos de passagem de parmetros. Esses
modos podem ser diferenciados pela direo da passagem, pelo mecanis-
mo de implementao e pelo momento no qual a passagem realizada.
6.2.1.4.1 Direo da Passagem dos Parmetros
A direo da passagem de parmetros pode ser unidirecional de entrada,
unidirecional de sada e bidirecional de entrada e sada. Na unidirecional
168
de entrada o valor do parmetro formal assume o valor passado pelo pa-
rmetro real, mas os valores eventualmente atribudos ao parmetro for-
mal no subprograma no so repassados ao parmetro real. Na unidire-
cional de sada os valores atribudos ao parmetro formal so passados ao
parmetro real, mas o parmetro formal no assume inicialmente qual-
quer valor. Na bidirecional de entrada e sada tanto o parmetro formal
assume o valor passado pelo parmetro real quanto os valores atribudos
ao parmetro formal so repassados para o parmetro real.
Enquanto na passagem unidirecional de entrada o parmetro real pode ser
uma varivel, constante ou expresso do tipo definido para o parmetro
formal, nas passagens unidirecional de sada e bidirecional o parmetro
real deve ser necessariamente uma varivel do tipo definido para o par-
metro formal, uma vez que o parmetro real poder ter seu valor alterado
na execuo do subprograma.
Existem duas variaes possveis no tratamento do parmetro formal na
passagem unidirecional de entrada. O parmetro formal pode ser conside-
rado como uma varivel ou como uma constante. Quando considerado
como varivel esse parmetro pode ter o seu valor alterado no corpo do
subprograma, embora essas modificaes s tenham efeito interno, no
sendo repassadas para o parmetro real correspondente. Quando conside-
rado como constante, qualquer tentativa de alterao do valor do parme-
tro formal no corpo do subprograma causa a ocorrncia de erro (geral-
mente identificado na prpria compilao).
A tabela 6.1 resume as informaes importantes a respeito da direo da
passagem dos parmetros. A coluna Direo da Passagem indica as for-
mas de direo da passagem: unidirecional de entrada com parmetro
formal varivel, unidirecional de entrada com parmetro formal constan-
te, unidirecional de sada e bidirecional de entrada e sada. A coluna
Forma do Parmetro Real destaca que, nas passagens unidirecionais de
entrada, o parmetro real pode ser uma varivel, uma constante ou mesmo
uma expresso e, nas outras passagens, ele deve ser necessariamente uma
varivel. A coluna Atribuio do Parmetro Formal ressalta que some-
ne na passagem unidirecional de entrada constante no permitido fazer
atribuies ao parmetro formal. Finalmente, a coluna Fluxo indica o
sentido da passagem de valor entre parmetros reais e formais. Nas pas-
sagens unidirecionais de entrada o fluxo de valor vai do parmetro real
para o parmetro formal. Na passagem unidirecional de sada o fluxo vai
do parmetro formal para o parmetro real. J na passagem bidirecional
de entrada e sada o fluxo ocorre nos dois sentidos.
169

Direo da Passa-
gem
Forma do Parmetro
Real (R)
Atrib. do Parm.
Formal (F)
Fluxo
Entrada Varivel Varivel, Constante
ou Expresso
Sim R F
Entrada Constante Varivel Constante ou
Expresso
No R F
Sada Varivel Sim R F
Entrada e Sada Varivel Sim R F
Tabela 6. 1 - Direo da Passagem dos Parmetros
C s oferece passagem unidirecional de entrada. Embora se possa usar a
palavra const na definio de um parmetro, a incluso dessa palavra no
torna esse parmetro necessariamente constante, uma vez que os compi-
ladores podem simplesmente ignor-la. Portanto, pode-se considerar que
C somente oferea passagem unidirecional de entrada varivel. Essa op-
o em C confere simplicidade a LP, mas traz algumas dificuldades em
subprogramas nos quais necessrio alterar os valores dos parmetros. A
nica forma de se fazer isso passando ponteiros como parmetros. O
exemplo 6.9 ilustra a passagem de parmetros em C atravs de dois sub-
programas. Um desses subprogramas aparentemente tenta trocar os valo-
res de duas variveis inteiras do programa, mas no consegue produzir
esse efeito. O outro utiliza ponteiros para efetuar a troca dos valores.
void naoTroca (int x, int y) {
int aux;
aux = x;
x = y;
y = aux;
}
void troca (int* x, int* y) {
int aux;
aux = *x;
*x = *y;
*y = aux;
}
main() {
int a = 10, b = 20;
naoTroca (a, b);
troca (&a, &b);
}
Exemplo 6. 9 - Passagem Unidirecional de Entrada Varivel em C
170
Note na funo naoTroca do exemplo 6.9 as atribuies feitas aos par-
metros x e y. Embora sejam permitidas e troquem os valores de x e y, seu
efeito apenas interno, no efetivando a troca dos valores das variveis a
e b de main. J a funo troca realiza a troca dos valores de a e b. Ela
consegue fazer isso porque x e y so ponteiros. Observe que o corpo do
subprograma no altera o valor dos parmetros e sim os valores das vari-
veis apontadas por eles. Contudo, isso fora usar como parmetros reais
expresses que obtm os endereos das variveis a e b, alm de obrigar o
uso de operaes de derreferenciamento no corpo do subprograma. Isso
acaba reduzindo a redigibilidade e legibilidade dos programas.
Alm da passagem unidirecional de entrada varivel, C++ tambm ofere-
ce as passagens unidirecional de entrada constante e bidirecional. O e-
xemplo 6.10 mostra o uso dessas formas de passagem de parmetros em
C++.
int triplica (const int x) {
// x = 23;
return 3*x;
}
void troca (int& x, int& y) {
int aux;
aux = x;
x = y;
y = aux;
}
main() {
int a = 10, b = 20;
b = triplica (a);
troca (a, b);
// troca (a, a + b);
}
Exemplo 6. 10 - Passagem Unidirecional de Entrada Constante e Bidirecional em C++
No exemplo 6.10, a funo triplica utiliza a passagem de parmetros uni-
direcional de entrada constante e a funo troca usa a passagem bidire-
cional. A execuo de troca faz com que os valores de a e b sejam troca-
dos. Caso a linha comentada da funo triplica tivesse sido compilada
haveria um erro, uma vez que no se pode realizar atribuies sobre o pa-
rmetro formal na passagem de entrada constante. Tambm haveria erro
de compilao caso a linha com a ltima chamada de troca no fosse co-
mentada em main. O erro seria ocasionado pelo uso da expresso a+b
como parmetro real na chamada, o que no permitido na passagem bi-
direcional.
171
JAVA oferece passagem unidirecional de entrada varivel ou constante
para tipos primitivos. Para tipos no primitivos, a passagem pode ser con-
siderada unidirecional de entrada varivel ou constante, pois atribuies
de valores completos do tipo no primitivo ao parmetro formal no pro-
duzem efeito no parmetro real, ou bidirecional, pois atribuies aos
componentes do parmetro formal tm efeito sobre os componentes do
parmetro real.
O exemplo 6.11 mostra uma funo implementada em JAVA, denomina-
da preencheVet, a qual utilizada para atribuir valores a um conjunto de
elementos de um vetor (correspondente ao parmetro formal a). O trecho
a ser preenchido no vetor especificado pelos valores assumidos pelos
parmetros formais i e j. Observe a variao do valor do parmetro formal
i nessa funo. Embora o valor de i seja alterado, o parmetro real corres-
pondente no modificado, uma vez que a passagem de tipos primitivos
unidirecional de entrada.
void preencheVet (final int[] a, int i, final int j) {
while (i <= j) a[i] = i++;
// j = 15;
// a = new int [j];
}
Exemplo 6. 11 - Passagem de Parmetros em JAVA
A palavra final antes da declarao do vetor a e do inteiro j indica que
esses parmetros so constantes. Assim, as linhas comentadas em preen-
cheVet, caso fossem compiladas, provocariam erros.
importante notar que essa funo realmente modifica os valores dos
elementos do parmetro real correspondente ao vetor a. Isso ocorre inde-
pendentemente do fato do parmetro a ter sido declarado (ou no) como
constante. De fato, a declarao final para tipos no primitivos simples-
mente impede a atribuio ao parmetro como um todo (tal como na l-
tima linha comentada de preencheVet), no proibindo a atribuio aos
seus componentes. Caso o parmetro a no fosse declarado como cons-
tante, a ltima linha comentada tambm seria legal, mas no produziria
qualquer efeito sobre o parmetro real correspondente, s produzindo e-
feitos internos.
ADA oferece passagem de parmetros unidirecional de entrada constante,
unidirecional de sada e bidirecional de entrada e sada. O exemplo 6.12
mostra essas trs formas de passagem de parmetros em ADA atravs da
declarao de dois subprogramas.
172
function triplica (x: in integer; out erro: integer) return integer;
procedure incrementa (x: in out integer; out erro: integer);
Exemplo 6. 12 - Passagem de Parmetros em ADA
Poder-se-ia omitir a palavra in na declarao do parmetro x da funo
triplica no exemplo 6.12, uma vez que a omisso do especificador de di-
reo da passagem determina a passagem unidirecional de entrada cons-
tante. Em ambos subprogramas desse exemplo existe um parmetro de
sada erro usado para retornar um cdigo indicador da ocorrncia de al-
gum problema na realizao do subprograma.
Deve-se ter cuidado com o uso das passagens de parmetros unidirecional
de sada e bidirecional de entrada e sada para evitar possveis colises no
retorno dos resultados. Por exemplo, a chamada
incrementa (i, i);
do subprograma incrementa declarado no exemplo 6.12 provoca esse tipo
de coliso, podendo gerar indeterminismo.
6.2.1.4.2 Mecanismos de Implementao da Passagem de Parmetros
Existem dois mecanismos para a implementao da passagem de parme-
tros. O mecanismo de passagem por cpia envolve a criao de uma cpia
do parmetro real no ambiente local do subprograma. O parmetro formal
designa essa cpia durante a execuo do subprograma. O mecanismo de
passagem por referncia envolve a criao de uma referncia ao parme-
tro real no ambiente local do subprograma. O parmetro formal designa
essa referncia durante a execuo do subprograma. A figura 6.1 ilustra a
diferena entre os mecanismos de passagem de parmetros por cpia e
por referncia.





Figura 6. 1 - Passagem por Cpia e por Referncia
A figura 6.1 retrata o momento em que a funo f, a qual possui dois pa-
rmetros x e y, est sendo executada, aps ser chamada por p. Quando o
mecanismo de cpia utilizado, os parmetros reais a e b tm seu tipo e
valor copiados para o ambiente de f. Qualquer alterao feita por f nos
parmetros formais implica em modificao nos valores das cpias x e y.
Se a direo da passagem de parmetros envolver o repasse dos valores
p
f
a
b
x
y
z
10
9
10
9
10
Cpia
p
f
a
b
x
y
z
10
9
10
Referncia
173
dos parmetros formais para os parmetros reais, isso s ser realizado ao
final da execuo de f.
Quando o mecanismo de referncia utilizado, os endereos dos parme-
tros reais a e b so copiados para o ambiente de f. Os parmetros formais
x e y so, na realidade, referncias para os parmetros reais a e b. Qual-
quer alterao feita por f nos parmetros formais implica na modificao
imediata dos valores dos parmetros reais.
Uma contribuio do mecanismo de cpia viabilizar a passagem unidi-
recional de entrada varivel de parmetros. Essa direo de passagem de
parmetros s pode ser feita atravs do mecanismo de cpia, uma vez que
o uso do mecanismo de referncia implicaria na modificao do parme-
tro real.
Adicionalmente, o mecanismo de cpia pode facilitar a recuperao do
estado prvio do programa quando um determinado subprograma termina
inesperadamente. Uma vez que as atualizaes executadas pelo subpro-
grama so efetuadas sobre o parmetro formal, e s so repassadas para o
parmetro real no final da execuo, uma interrupo inesperada do sub-
programa no afeta, em princpio, o estado das variveis do programa.
O mecanismo de referncia proporciona uma semntica simples e uni-
forme para passagem de parmetros, adequada a todos os tipos de valores
da LP e no somente para tipos nos quais possvel realizar atribuio
(como o caso da passagem por cpia). Por exemplo, o mecanismo de
referncia torna possvel passar subprogramas como parmetros para ou-
tros subprogramas. Nesse caso, durante a ativao do subprograma, o pa-
rmetro formal recebe o endereo inicial do trecho de memria onde um
determinado subprograma est alocado. Referncias ao parmetro formal
no corpo do subprograma equivalem a chamadas indiretas ao subprogra-
ma para o qual esse parmetro referencia. Passagem de parmetros sub-
programas aumentam as possibilidades de reuso de subprogramas, pois
um mesmo subprograma passa a poder ser usado para atingir diferentes
funcionalidades. Assim, alm de se poder alterar os dados sobre os quais
o subprograma atua, tambm se pode alterar a sua prpria funcionalidade.
Veja no exemplo 3.8, do captulo 3, uma situao na qual esse recurso
utilizado em C para alterar a funcionalidade de um subprograma. Observe
nesse exemplo que C usa ponteiros para funo para viabilizar a passa-
gem de parmetros subprogramas.
Em geral, a passagem de um parmetro que contenha um grande volume
de dados, tal como um vetor, mais eficiente atravs do mecanismo de
referncia do que no de cpia. Ao se usar o mecanismo de cpia neces-
srio alocar espao adicional suficiente para repetir todo o volume de da-
dos do parmetro real. Alm disso, necessrio realizar a cpia dos valo-
174
res do parmetro real para o formal e vice-versa. Isso contrastado com a
passagem por referncia, a qual demanda apenas a alocao de espao
adicional para armazenar um ponteiro. Alm disso, no necessrio copi-
ar quaisquer dados.
Por outro lado, como o mecanismo de referncia baseia-se no acesso indi-
reto aos dados do parmetro real, em uma implementao distribuda, o
corpo do subprograma pode estar sendo executado num processador re-
moto, distante dos dados. Nessa situao o acesso indireto pode ser me-
nos eficiente que o mecanismo de cpia seguido de acesso local.
Uma outra desvantagem do mecanismo de passagem por referncia a
possibilidade de ocorrncia de sinonmia, o que tende a dificultar o en-
tendimento dos programas. Isso pode ocorrer na passagem por referncia
quando dois parmetros formais so associados ao mesmo parmetro real
ou quando o subprograma faz uso de variveis globais e um parmetro
formal referencia uma dessas variveis globais. O exemplo 6.13 mostra
uma funo em C++ usada para incrementar de 1 os valores dos parme-
tros k e l.
void incr (int& k, int& l) {
k = k + 1;
l = l + 1;
}
Exemplo 6. 13 - Sinonmia em Passagem por Referncia em C++
Se a vale 10 e b vale 3, aps a chamada incr (a, b), a e b so incrementa-
dos de 1 e passam a valer 11 e 4, respectivamente. Contudo, se a chamada
fosse incr (a[i], a[j]) com o valor de i igual ao valor de j, o valor de a[i]
(ou a[j]) seria incrementado duas vezes aps a execuo do subprograma.
Isso pode ser difcil de identificar, em caso de engano, e entender, quando
for essa a inteno do implementador da funo.
C oferece apenas o mecanismo de passagem por cpia. C++, ADA e
PASCAL adotam tanto o mecanismo de passagem por cpia quanto por
referncia. C++ usa o operador & para designar um parmetro passado
por referncia (ver a funo troca do exemplo 6.10), diferenciando-o as-
sim dos parmetros que usam passagem por cpia.
ADA adota passagem por cpia para tipos primitivos e para tipos deriva-
dos dos primitivos. A especificao de ADA define uma lista de tipos cu-
ja passagem deve ser por referncia. Para os tipos no especificados, a
passagem pode ser por referncia ou por cpia. Isso especificado pela
implementao do compilador.
JAVA adota o mecanismo de passagem por cpia para passar valores dos
tipos primitivos. Existem duas perspectivas alternativas sobre a passagem
175
de parmetros de tipos no primitivos. A primeira perspectiva considera a
passagem como sendo por cpia. De fato, so feitas cpias no parmetro
formal das referncias para os valores dos tipos no primitivos. Contudo,
referncias no so tipos de dados que podem ser manipulados explicita-
mente em JAVA. Portanto, no existe uma passagem atravs de cpia
efetiva de um tipo de dados de JAVA.
A segunda perspectiva considera a passagem como sendo por referncia.
Como os dados do tipo no primitivo no so copiados para o ambiente
do subprograma, mas podem ser alterados por ele, essa perspectiva consi-
dera tal postura como caracterizadora de passagem por referncia. No en-
tanto, a passagem de tipos no primitivos em JAVA, em certas situaes,
tem um comportamento um pouco diferenciado do que se costuma consi-
derar passagem por referncia. Somente atribuies aos componentes do
parmetro formal produzem efeito no valor referenciado pelo parmetro
real. Atribuies de valores completos do tipo no primitivo feitas ao pa-
rmetro formal no produzem efeito sobre o parmetro real corresponden-
te. De fato, essas atribuies impedem at que os componentes do par-
metro real possam ser modificados a partir dali. O exemplo 6.14 ilustra
essa diferena apresentando implementaes de uma funo f (que recebe
dois parmetros do tipo no primitivo T) em JAVA e em C++ (a verso
em C++ usa passagem por referncia).
void f (T t1, T t2) {
t1 = t2;
}
void f(T& t1, T& t2) {
t1 = t2;
}
Exemplo 6. 14 - Passagem de Tipos No Primitivos em JAVA e C++
Enquanto na implementao de f em C++ o parmetro real corresponden-
te a t1 teria seu valor modificado, isso no ocorre na implentao de f em
JAVA. De fato, aps a atribuio em JAVA, t1 passa a designar o objeto
referenciado por t2 e perde qualquer associao com o valor passado para
ele na chamada do subprograma.
Agora fica claro porque JAVA oferece passagem unidirecional de entrada
para os tipos primitivos e unidirecional de entrada e bidirecional para os
tipos no primitivos. Quando a passagem de tipos primitivos, ocorre
cpia. Qualquer atribuio ao parmetro formal no produz efeito no pa-
rmetro real. Quando a atribuio de tipos no primitivos passa-se uma
cpia da referncia. Atribuies aos componentes do parmetro formal
produzem efeito no parmetro real, caracterizando a passagem bidirecio-
nal. Por sua vez, atribuies de valores completos no produzem qualquer
efeito no parmetro real, caracterizando a passagem unidirecional de en-
trada.
176
Cabe frisar que a passagem unidirecional de entrada implementada atra-
vs do mecanismo de cpia mais conhecida no estudo de LPs pelo ter-
mo de passagem por valor. A existncia da passagem unidirecional de
entrada constante implementada atravs do mecanismo de referncia ofe-
rece um poder expressivo similar ao da passagem por valor com a vanta-
gem de no demandar cpias de grandes volumes de dados.
6.2.1.4.3 Momento da Passagem de Parmetros
LPs podem definir diferentes momentos nos quais se deve avaliar o pa-
rmetro real para uso no subprograma. No modo normal (eager) a ava-
liao ocorre no momento da chamada do subprograma. No modo por
nome (by name) a avaliao ocorre em todos os momentos em que o
parmetro formal usado. No modo preguioso (lazy) a avaliao o-
corre no primeiro momento em que o parmetro formal usado.
Considere, no exemplo 6.15, a implementao da funo caso em C. Essa
funo retorna o valor de w caso x seja negativo, o valor de y se x posi-
tivo e o valor de z quando x zero.
int caso (int x, int w, int y, int z) {
if (x < 0) return w;
if (x > 0) return y;
return z;
}
Exemplo 6. 15 - Momento da Passagem de Parmetros em C
C adota o modo normal para o momento da passagem dos parmetros.
Assim, a chamada caso(p(), q(), r(), s()) implica na imediata avaliao
das funes p, q, r e s e na imediata passagem dos seus resultados para x,
w, y e z. O problema com o modo normal nessa situao que somente o
parmetro x e um dos outros parmetros so realmente necessrios em
cada chamada de caso. Se alguma das funes q, r e s no podem ser exe-
cutadas em certos contextos, a funo caso tambm no pode ser execu-
tada nesses contextos. Por exemplo, se em um determinado contexto, s
executasse uma diviso por zero, a funo caso no poderia ser executada
nesse contexto tambm. Alm disso, se q, r e s realizam um processamen-
to computacional intensivo, o modo normal acaba reduzindo a eficincia
do processamento de caso, uma vez que todas essas funes seriam sem-
pre executadas em cada chamada.
Admitindo-se hipoteticamente que C adotasse o modo por nome para o
momento da passagem de parmetros, a execuo das funes p, q, r ou s
s seria realizada quando, na execuo do corpo do subprograma, fosse
necessrio utilizar os valores dos parmetros formais correspondentes.
177
Dessa maneira os problemas que ocorrem no modo normal seriam mini-
mizados. Por exemplo, no contexto no qual s executasse uma diviso por
zero, a funo caso poderia ainda ser executada desde que p no resultas-
se no valor zero. Como q, r ou s s so executadas no momento em que a
execuo de caso referencia w, y ou z, tambm no haveria realizao de
computaes desnecessrias, uma vez que o subprograma s se refere a
um desses parmetros em cada chamada.
Por outro lado, o modo por nome provocaria a execuo repetida de p
quando o resultado dessa funo fosse no negativo (uma execuo na
comparao x < 0 e outra na comparao x > 0). Esse problema se agra-
varia ainda mais se o subprograma usasse x em uma repetio. Nesse ca-
so, haveria uma execuo de p a cada repetio da referncia a x. Pode-se
concluir, portanto, que o modo por nome claramente ineficiente.
Outro problema com o modo por nome pode ocorrer em LPs que permi-
tem efeitos colaterais. Considere, por exemplo, que a funo p usada na
chamada de caso atuasse como um iterador de uma lista, retornando sem-
pre o elemento seguinte. Nessa situao, o valor de x utilizado na primei-
ra comparao seria diferente do valor usado na segunda. Isso certamente
contradiz o propsito da funo caso e facilita o uso equivocado desse
subprograma.
Caso C adotasse o modo preguioso, p s seria executada na primeira a-
pario de x (isto , na comparao x < 0) e o valor usado na segunda a-
pario seria o mesmo usado na primeira, mesmo que a funo p produ-
zisse efeitos colaterais. Alm disso, somente uma das funes q, r ou s
seria executada em cada chamada, eliminando os problemas relacionados
com a avaliao de parmetros reais desnecessrios.
Portanto, o modo preguioso apresenta boas solues para os problemas
de flexibilidade (caso o parmetro formal no seja necessrio o parmetro
real no ser avaliado), eficincia (a avaliao do parmetro real reali-
zada no mximo uma nica vez) e efeitos colaterais (o parmetro formal
ser associado a um nico valor, mesmo que haja efeitos colaterais).
A maior parte das LPs, como C, C++, JAVA, ADA, ML e PASCAL, for-
necem apenas o modo normal. ALGOL-60 permite ao programador esco-
lher entre o modo normal e o por nome. SML permite ao programador
escolher entre o modo normal e o preguioso. HASKELL e MIRANDA
utilizam o modo preguioso.
6.2.1.5 Verificao de Tipos dos Parmetros
Algumas LPs no verificam se o nmero e tipo dos parmetros na cha-
mada do subprograma so compatveis com o nmero e tipo dos parme-
178
tros declarados no cabealho do subprograma e utilizados no seu corpo.
Nessas LPs s se pode verificar se uma determinada operao do subpro-
grama pode ser executada no momento em que ela for realizada. Tal pos-
tura retarda a descoberta de erros e tende a produzir programas menos
robustos.
Uma propriedade interessante proporcionada por uma LP permitir aos
compiladores garantir a no ocorrncia de erros de tipos no uso dos pa-
rmetros durante a ativao dos subprogramas. Para isso, necessrio
fazer verificao de tipos dos parmetros. A maioria das LPs ditas AL-
GOL-like, tais como, PASCAL, MODULA-2, ADA e JAVA fazem
verificao de tipos.
Verses prvias de algumas LPs no requeriam a realizao de verifica-
o de tipos. Contudo, hoje j incorporaram mecanismos para possibilitar
essa verificao. Na verso original de C no se requeria aos compilado-
res a verificao do nmero e tipos dos parmetros. Embora pudessem
existir programas a parte para realizar a verificao, o programador pode-
ria no utiliz-los.
Verses atuais, tal como ANSI C, j permitem ao programador definir se
os compiladores devem ou no realizar a verificao de tipos na chamada
dos subprogramas. O exemplo 6.16 mostra um programa com uma fun-
o, chamada origem, a qual usa a forma prvia de declarao de C, e ou-
tra funo, chamada distancia, a qual usa a nova forma. A funo origem
usada para dizer se um ponto se encontra ou no na origem do eixo de
coordenadas. A funo distancia calcula a distncia entre a origem e um
determinado ponto.
#include math.h
typedef struct coordenadas {
int x, y, z;
} coord;
int origem (c)
coord c;
{
return c.x*c.y*c.z;
}
float distancia (coord c) {
return sqrt(c.x*c.x + c.y*c.y +c.z*c.z);
}
main() {
coord c = { 1, 2, 3 };
printf(%d\n, origem(2));
printf(%d\n, origem(1, 2, 3, 4));
179
printf(%d\n, origem(c));
// printf(%f\n, distancia(2));
// printf(%f\n, distancia (1, 2, 3));
printf(%f\n, distancia (c));
}
Exemplo 6. 16 - Verificao de Tipos dos Parmetros em C
Como a funo origem usa a forma prvia de declarao de parmetros,
todas as chamadas a essa funo so vlidas em main, embora as duas
primeiras no faam muito (ou qualquer) sentido. Por outro lado, como
distancia usa a nova forma de declarao, caso as linhas com as duas
primeiras chamadas dessa funo no estivessem comentadas, haveriam
erros de compilao.
importante saber a diferena entre a declarao de uma funo na qual a
lista de parmetros omitida f() e a declarao de uma funo sem par-
metros f(void). A declarao f() segue o padro prvio de C, ou seja, no
requer a verificao de tipos. Isso significa que chamadas a f podem ser
feitas com qualquer nmero ou tipo de parmetros reais ou mesmo sem
parmetro real algum. A declarao f(void) segue o novo padro e
estabelece que f s pode ser chamada sem parmetros.
Em ANSI C e C++ a verificao de tipos tambm pode ser evitada atra-
vs do uso do operador elipse ( ), cujo uso foi ilustrado no exemplo
6.8.
6.2.2 Tipos de Dados
A criao de novos tipos de dados uma forma de modularizao usada
para implementar abstraes de dados. Tipos de dados permitem agrupar
dados correlacionados em uma mesma entidade computacional. Usurios
dessa nova entidade computacional passam a enxergar o grupo de dados
como um todo pr-definido e no precisam se preocupar em como essa
entidade foi implementada ou em como seus dados so armazenados.
LPs oferecem diferentes mecanismos para a definio de novos tipos de
dados, tais como tipos annimos, tipos simples e tipos abstratos de dados.
6.2.2.1 Tipos de Dados Annimos
Tipos annimos so definidos exclusivamente durante a criao de vari-
veis e definio de parmetros. O exemplo 6.17 ilustra o uso de um tipo
annimo em C para criar uma varivel chamada pilhaNumeros.
struct {
int elem[100];
180
int topo;
} pilhaNumeros;
Exemplo 6. 17 - Tipo de Dados Annimo
Observe que a definio do tipo s pode ser usada uma nica vez em vir-
tude do tipo no possuir um nome. Se uma outra varivel necessitar ter os
mesmos dados de pilhaNumeros, toda a definio ter de ser repetida,
diminuindo a redigibilidade do programa e impedindo o reuso desse tre-
cho de cdigo.
6.2.2.2 Tipos de Dados Simples
A idia fundamental relacionada a essa tcnica de modularizao com-
binar um grupo de dados relacionados em uma nica entidade nomeada, a
qual permite manipul-los como um todo. O exemplo 6.18, em C, ilustra
a definio e uso de um tipo de dados simples struct pilha (declarado com
o nome tPilha).
#define max 100
typedef struct pilha {
int elem[max];
int topo;
} tPilha;
tPilha global;
void preenche (tPilha *p, int n) {
for (p->topo=0; p->topo < n && p->topo < max; p->topo++)
p->elem[p->topo] = 0;
p->topo;
}
main( ) {
tPilha a, b;
preenche(&a, 17);
preenche(&b, 29);
preenche(&global, 23);
}
Exemplo 6. 18 - Tipo de Dados em C
Note no exemplo 6.18 que a definio do tipo de dados tPilha possibilita
tratar um vetor e uma varivel inteira (representantes dos elementos e do
topo da pilha) das variveis global, a e b como uma entidade nica.
O exemplo 6.18 destaca algumas das vantagens dessa tcnica de modula-
rizao. Em primeiro lugar, ela torna o cdigo mais reusvel, uma vez
que a definio de tPilha pode ser usada em diversos pontos do programa
para definir variveis e parmetros desse tipo.
181
Ela tambm torna o cdigo mais redigvel. Caso no fosse possvel defi-
nir o tipo de dados tPilha seria necessrio criar variveis e parmetros do
tipo vetor de inteiros e inteiro para representar isoladamente os elementos
e os topos de cada varivel ou parmetro que representa uma pilha no
programa. Isso certamente tornaria a escrita do cdigo do programa muito
mais trabalhosa.
Outra vantagem dessa tcnica dar maior legibilidade ao cdigo explici-
tando que as variveis e parmetros criados com a estrutura tPilha devem
armazenar valores relacionados ao conceito abstrato de pilha.
Em algumas LPs, a definio de tipos de dados tambm aumenta a confi-
abilidade da programao, assegurando que s possam ser atribudos para
as variveis e parmetros do tipo definido, vriaveis e valores desse
mesmo tipo. Isto , o compilador impede a atribuio de valores de outros
tipos (mesmo com uma estrutura equivalente) a variveis e parmetros do
tipo criado.
O maior problema da definio de tipos de dados simples no possibili-
tar o ocultamento da informao, fornecendo acesso livre aos dados inter-
nos do tipo. Quando um tipo simples criado, suas operaes j esto
pr-definidas pela prpria LP. Normalmente, essas operaes fornecem
acesso indiscriminado implementao do tipo, permitindo ao usurio
alterar os seus dados sem garantir a manuteno de sua consistncia.
Isso provoca dificuldades na legibilidade, confiabilidade e modificabili-
dade dos programas. O exemplo 6.19 mostra um programa que utiliza o
tipo tPilha, definido no exemplo 6.18, para realizar operaes sobre uma
varivel desse tipo e ilustrar a ocorrncia desses problemas.
main( ) {
tPilha a;
preenche(&a, 10);
a.elem[++a.topo] = 11;
a.topo= 321;
}
Exemplo 6. 19 - Problemas no Uso de Tipos de Dados Simples
A legibilidade do exemplo 6.19 sacrificada porque o usurio do tipo
tPilha acessa e modifica seu contedo sem o uso de operaes especiais.
Dessa maneira, ele mistura o cdigo relacionado com a implementao do
tipo com o cdigo relacionado ao seu uso, tornando os programas menos
legveis. Caso o tipo tPilha tivesse uma operao para empilhar elemen-
tos, o exemplo 6.19 se tornaria muito mais legvel pois o comando de in-
cluso do valor 11 na pilha seria substitudo por uma chamada a essa ope-
rao e o usurio no teria de implement-la no seu prprio cdigo.
182
A confiabilidade tambm afetada porque o usurio tem acesso indiscri-
minado a estrutura interna do tipo. Assim, ele pode alterar inadvertida-
mente e de maneira inconsistente algum dos elementos do tipo. No exem-
plo 6.19, a ltima linha de cdigo atribui um valor inconsistente ao topo
da pilha. Tal alterao inviabiliza a utilizao correta da varivel a.
Por fim, a modificabilidade reduzida porque a alterao da estrutura in-
terna do tipo geralmente implica na necessidade de alteraes nos trechos
de cdigo que usam o tipo. O exemplo 6.19 necessitaria ser totalmente
alterado caso o implementador do tipo tPilha resolvesse implement-lo
usando uma lista encadeada.
6.2.2.3 Tipos Abstratos de Dados
Conceitualmente, tipos abstratos de dados (TADs) so conjuntos de valo-
res que apresentam um comportamento uniforme definido por um grupo
de operaes (geralmente, um grupo de constantes iniciais e um conjunto
de funes e procedimentos). O conjunto de valores definido indireta-
mente atravs da aplicao sucessiva das operaes, comeando pelas
constantes.
Em Linguagens de Programao, TADs [GUTTAG, 1977] so novos ti-
pos de dados cuja representao e operaes associadas so especificadas
pelo programador que implementa o TAD. O implementador do TAD
escolhe uma representao para os valores do tipo abstrato e implementa
as operaes em termos da representao escolhida. As implementaes
de TADs so usadas por programadores usurios para criar estruturas de
dados desse novo tipo e para realizar operaes sobre elas. O usurio do
TAD s pode utilizar as operaes definidas pelo implementador do TAD
para manipular os dados daquele tipo. Assim, o usurio do TAD utiliza
sua representao e operaes, como uma caixa preta, para resolver seu
problema.
Para permitir a implementao de TADs essencial que a LP oferea
meios para o ocultamento da informao. Isto , faz-se necessrio tornar a
implementao interna do TAD invisvel para o usurio. Isso normalmen-
te feito atravs da especificao da interface do TAD. Na interface so
includos todos os componentes do TAD (tipicamente, operaes) que
devem ser pblicos. Componentes pblicos so os que podem ser usados
diretamente pelo cdigo usurio.
A figura 6.2 apresenta um esquema de uso de um TAD qualquer. As ope-
raes do TAD atuam como um invlucro protetor dos dados do TAD. O
cdigo usurio s acessa ou modifica os dados do TAD atravs dessas
operaes.
183
Existem quatro tipos diferentes de operaes que podem ser realizadas
sobre um TAD. Operaes construtoras so usadas para inicializar o
TAD. Uma dessas operaes deve ser usada antes de qualquer outra para
garantir que o TAD foi inicializado corretamente. Dessa maneira, pode-se
ter certeza que as demais operaes sero realizadas apropriadamente.
Operaes consultoras so usadas para obter informaes relacionadas
com os valores do TAD. J operaes atualizadoras permitem a alterao
dos valores do TAD. Por fim, operaes destrutoras so responsveis por
realizar qualquer atividade de finalizao quando o TAD no mais ne-
cessrio, tal como desalocar memria.
Todos os problemas mencionados com o uso de tipos simples so resol-
vidos com o uso de TADs, uma vez que o programador pode especificar e
restringir as operaes (um conjunto de subprogramas) a serem realizadas
sobre o tipo. Programadores usurios desse tipo somente acessam os da-
dos internos atravs do uso desses subprogramas. Assim, o cdigo fica
legvel pois s inclui chamadas a essas operaes, no necessitando inclu-
ir cdigo de implementao do tipo. O cdigo tambm fica confivel pois
o usurio no mais efetua livremente mudanas nos dados. Isso s feito
atravs das operaes oferecidas pelo implementador do tipo. Por fim, o
cdigo usurio geralmente no precisa ser alterado quando a implementa-
o do TAD modificada. Para isso ocorrer basta no haver alteraes na
interface do TAD.










Figura 6. 2 - Representao Esquemtica de um TAD
6.2.2.3.1 Simulao de TADS em C
C e PASCAL no oferecem mecanismos para a implementao de TADs.
Mesmo assim, simular o uso de TADS nessas LPs uma boa prtica de


Dados

Operaes
(consultoras)
Operaes
(construtoras)
Operaes
(atualizadoras)
Operaes
(destrutoras)
Cdigo
Usurio
TAD
184
programao. Para isso, necessrio definir um tipo de dados simples e
um conjunto de operaes (subprogramas) que se aplicam sobre valores
desse tipo. Tanto o tipo quanto suas operaes devem ser disponibilizadas
para os programadores usurios. O exemplo 6.20 ilustra a simulao do
TAD pilha de nmeros naturais em C.
#define max 100
typedef struct pilha {
int elem[max];
int topo;
} tPilha;
tPilha cria () {
tPilha p;
p.topo = 1;
return p;
}
int vazia (tPilha p) {
return p.topo == 1;
}
tPilha empilha (tPilha p, int el) {
if (p.topo < max1 && el >= 0)
p.elem[++p.topo] = el;
return p;
}
tPilha desempilha (tPilha p) {
if (!vazia(p)) p.topo;
return p;
}
int obtemTopo (tPilha p) {
if (!vazia(p)) return p.elem[p.topo];
return 1;
}
Exemplo 6. 20 - Simulao de TAD Pilha em C
A implementao da simulao do TAD tPilha do exemplo 6.20 utiliza
um vetor de inteiros para armazenar nmeros naturais na pilha e um intei-
ro indicador do topo da pilha. J as operaes dessa simulao possuem
as seguintes assinaturas:
cria: void tPilha
vazia: tPilha int
empilha: tPilha x int tPilha
desempilha: tPilha tPilha
obtemTopo: tPilha int
185
A operao construtora cria retorna uma pilha inicializada. A operao
consultora vazia recebe uma pilha e retorna um inteiro indicando se a pi-
lha contm elementos. A operao atualizadora empilha recebe uma pilha
e um inteiro e retorna a pilha com o inteiro no topo, caso seja um nmero
natural. Se o inteiro no for um nmero natural, retorna a pilha tal como
recebida. A operao atualizadora desempilha recebe uma pilha e a retor-
na sem o elemento do topo. Por fim, a operao consultora obtemTopo
retorna o elemento do topo da pilha caso ela no esteja vazia. Caso con-
trrio, retornado um nmero inteiro no natural. Como no existe aloca-
o dinmica na implementao dessa simulao de TAD, no h neces-
sidade de de uma operao destrutora.
O exemplo 6.21 mostra o uso da simulao do TAD do exemplo 6.20.
Observe dessa vez que as funes preenche e main do cdigo usurio no
acessam a estrutura interna do tipo tPilha, realizando suas funcionalida-
des apenas atravs das operaes do TAD.
tPilha global;
void preenche (tPilha *p, int n) {
int i;
for (i = 0; i < n; i++) *p = empilha (*p, 0);
}
main( ) {
tPilha a, b;
global = cria();
a = cria();
b = cria();
preenche(&a, 17);
preenche(&b, 29);
preenche(&global, 23);
a = empilha(a, 11);
// a.elem[++a.topo] = 11;
// a.topo= 321;
// global = a;
}
Exemplo 6. 21 - Uso de Simulao de TAD em C
O uso disciplinado da simulao do TAD garante uma srie de proprieda-
des interessantes ao cdigo usurio. Primeiramente, o cdigo fica mais
legvel. Isso pode ser observado comparando a implementao da funo
preenche com a implementao da mesma funo no exemplo 6.18. Alm
dessa melhoria, o cdigo usurio se torna muito mais fcil de ser redigi-
do, uma vez que o programador usurio no mais necessita implementar o
cdigo das operaes do TAD. Por fim, o cdigo usurio no precisa ser
alterado caso seja necessrio realizar uma alterao na implementao do
186
tipo tPilha ou nas suas operaes (isso verdadeiro somente quando os
cabealhos das funes no so modificados).
Contudo, alm de no promover o encapsulamento das operaes e dados
em uma nica unidade sinttica, essa abordagem no impede o uso indis-
ciplinado do TAD. Caso o programador esquea ou no chame a opera-
o cria antes de qualquer outra operao sobre uma varivel do tipo tPi-
lha, o uso correto do TAD ficar comprometido. Alm disso, importan-
te observar que o programador usurio pode realizar operaes adicionais
sobre o TAD alm das especificadas pelos subprogramas. Por exemplo, o
programador pode acessar diretamente a estrutura de dados interna do
TAD, o que acaba com todas as vantagens mencionadas no pargrafo an-
terior, ou mesmo realizar atribuio entre duas pilhas (isso poderia gerar
problemas de compartilhamento de dados se a pilha fosse implementada
como uma lista encadeada). Para se ter clareza sobre esse aspecto, note
que as linhas comentadas no exemplo 6.21 seriam sentenas vlidas do
programa C caso no fossem comentrios.
6.2.2.3.2 Uso de Interface e Implementao nos TADS em ADA
Uma abordagem usada em LPs para a implementao de TADs envolve a
diviso da definio do TAD em duas unidades sintticas do programa.
Em uma unidade definida a interface do TAD enquanto na outra defi-
nida a sua implementao. Somente o que definido na unidade de inter-
face exportado. Isso significa que os programadores usurios do TAD
s tero acesso s entidades definidas nessa unidade. A unidade de im-
plementao contm detalhes a respeito de como as entidades da interface
so implementadas e tambm contm outras entidades utilizadas para au-
xiliar a implementao das entidades da interface. MODULA-2 e ADA
so exemplos de linguagens que adotam essa abordagem. O exemplo 6.22
mostra a definio e uso do TAD tPilha em ADA.
package pilha_naturais is
type tPilha is limited private;
procedure cria (p: out tPilha);
function vazia (p: in tPilha) return boolean;
procedure empilha (p: in out tPilha; el: in integer);
procedure desempilha (p: in out tPilha);
function obtemTopo (p: in tPilha) return integer;
private
max: constant integer := 100;
type tPilha is record
elem: array (1 .. max) of integer;
topo: integer;
187
-- topo: integer := 0;
end record;
end pilha_naturais;
package body pilha_naturais is
procedure cria (p: out tPilha) is
begin
p.topo := 0;
end cria;
function vazia (p: in tPilha) return boolean is
begin
return (p.topo = 0);
end vazia;
procedure empilha (p: in out tPilha; el: in integer) is
begin
if p.topo < max and then el >= 0 then
p.topo := p.topo + 1;
p.elem(p.topo) := el;
end if;
end empilha;
procedure desempilha (p: in out tPilha) is
begin
if not vazia(p) then
p.topo = p.topo 1;
end if;
end desempilha;
function obtemTopo (p: in tPilha) return integer is
begin
if not vazia(p) then
return p.elem(p.topo);
return 1;
end topo;
end pilha_naturais;
use pilha_naturais;
procedure main is
pilha: tPilha;
numero: integer;
cria (pilha);
empilha (pilha, 1);
empilha (pilha, 2);
while not vazia(pilha) loop
numero := obtemTopo(pilha);
desempilha(pilha);
188
end loop;
end main;
Exemplo 6. 22- TAD Pilha em ADA
O primeiro bloco do exemplo 6.22, formado pela unidade package pi-
lha_naturais, contm a definio da interface do TAD tPilha. Nessa uni-
dade so declarados o TAD tPilha e os procedimentos e funes corres-
pondentes s operaes do TAD. Embora a implementao
6.3
do tipo tPi-
lha tambm seja colocada nessa unidade, somente o nome tPilha se torna
acessvel para o usurio, uma vez que a implementao de tPilha feita
na parte privada (private) da unidade. Observe ainda que somente os pro-
ttipos dos procedimentos e funes so colocados nessa unidade.
O segundo bloco do exemplo 6.22, formado pela unidade package body
pilha_naturais, contm a definio da implementao do TAD tPilha. No
caso especfico do TAD tPilha essa unidade contm as implementaes
dos procedimentos e funes cujos prottipos foram declarados na unida-
de de interface.
O terceiro bloco do exemplo 6.22 contm um programa que usa o TAD
tPilha. Note que o usurio do TAD tem de criar variveis do tipo tPilha e
s pode aplicar sobre essas variveis as operaes definidas na unidade de
interface. De fato, ao se declarar o tipo tPilha como limited consegue-se
garantir que as operaes de atribuio e comparao tambm no pos-
sam ser aplicadas. Essa caracterstica garante o uso disciplinado do tipo
tPilha, conferindo uma maior legibilidade, redigibilidade, confiabilidade
e modificabilidade (desde que os prottipos na interface no sejam altera-
dos) ao cdigo usurio.
A necessidade de chamar a operao criar antes de qualquer outra da pi-
lha permanece nessa verso do TAD em ADA. Se o programador usurio
no realizar essa operao, o uso das demais operaes ser incorreto.
Isso reduz a confiabilidade do TAD. ADA oferece alguns mecanismos
para contornar essa dificuldade. No exemplo 6.22, uma opo seria elimi-
nar a necessidade da existncia da operao criar fazendo a inicializao
do topo da pilha no momento de sua declarao. Para isso, basta substituir
a linha onde topo declarado na unidade de interface pela linha comenta-
da imediatamente subsequente.

6.3
Em princpio, a implementao de tPilha deveria ser feita na unidade de implementao. No entanto,
existe uma razo operacional para ser colocada na unidade de interface, a qual ser explicada posteri-
ormente ainda nesse captulo.
189
6.2.2.3.3 TADS como Classes em C++
Com o advento da programao orientada a objetos, um novo tipo de en-
tidade de computao, chamado de classe, foi introduzido nas Linguagens
de Programao. C++ e JAVA utilizam esse conceito para implementar
TADs.
Classes permitem ao programador criar um novo tipo de dados, incluindo
de uma forma encapsulada tanto a representao do novo tipo, quanto as
operaes associadas ao tipo. Classes oferecem proteo dos dados do
tipo atravs do uso de especificadores de acesso. Por exemplo, em C++ e
JAVA, os dados da classe e as suas operaes podem ser privados (s so
visveis para a implementao das operaes do TAD) ou pblicos (so
visveis para qualquer trecho do programa)
6.4
.
Ao proporcionar os mecanismos de encapsulamento de dados e oculta-
mento da informao, necessrios para a implementao de TADs, clas-
ses conferem ao cdigo usurio as vantagens de maior legibilidade, redi-
gibilidade, confiabilidade e modificabilidade. Alm disso, classes tornam
especiais as operaes construtoras (resolvendo o problema da inicializa-
o de TADs) e destrutoras, e do suporte aos conceitos de herana e po-
limorfismo, fundamental para a orientao a objetos
6.5
. O exemplo 6.23
ilustra a implementao e uso do TAD tPilha em C++.
class tPilha {
static const int max = 100;
int elem[max];
int topo;
public:
tPilha () {
topo = 1;
}
int vazia () {
return topo == 1;
}
void empilha (int el);
void desempilha (void);
int obtemTopo (void);
}
tPilha::empilha (int el) {
if (topo < max1 && el >= 0)

6.4
Existem outros tipos de especificadores de acesso em C++ e JAVA, os quais sero discutidos poste-
riormente.
6.5
Esses conceitos sero estudados no captulo 7.
190
elem[++topo] = el;
}
void tPilha::desempilha (void) {
if (!vazia()) topo;
}
void tPilha::int obtemTopo (void) {
if (!this->vazia()) return elem[topo];
return 1;
}
main () {
tPilha p;
int n;
p.empilha (1);
p.empilha (2);
while (! p.vazia ()) {
n = p.obtemTopo ();
p.desempilha ();
}
}
Exemplo 6. 23 - TAD Pilha como Classe em C++
O exemplo 6.23 contm inicialmente a definio da classe tPilha. A
estrutura de dados do tipo tPilha privada
6.6
e s pode ser acessada pelo
cdigo usurio atravs das operaes pblicas declaradas na classe (fun-
es declaradas aps a palavra public). Alm da implementao da estru-
tura de dados e da declarao das operaes da interface de tPilha, essa
unidade tambm contm a implementao de algumas das operaes da
interface da classe (por exemplo, a funo vazia).
Observe que somente os prottipos das operaes empilha, desempilha e
obtemTopo so colocados na definio da classe. Suas implementaes
so externas definio da classe. C++ requer o uso do operador de reso-
luo de escopo :: para relacionar a implementao dessas funes com
as operaes da classe tPilha.
Outro aspecto importante a ser observado na implementao de classes,
em contraste simulao de TADS em C e implementao de TADS
em ADA, a ausncia de necessidade de declarao do parmetro tPilha
nas operaes da classe. LPs orientadas a objeto assumem que as opera-
es de uma determinada classe so aplicadas a um objeto dessa classe,
cujos membros podem ser acessados diretamente na implementao das
suas operaes. Note isso ocorrendo nas implementaes das operaes

6.6
C++ considera os membros da classe privados quando o especificador de acesso omitido.
191
de tPilha. As referncias a topo e elem so feitas livremente sem qualquer
associao a uma pilha, aumentando assim a redigibilidade desse cdigo.
A chamada dessas funes no cdigo usurio em main tambm feita
com uma sintaxe diferenciada. Por exemplo, a funo obtemTopo cha-
mada como p.obtemTopo(), indicando que a operao topo aplicada so-
bre a pilha p. Isso significa que as referncias a elem e topo dentro da im-
plementao da operao obtemTopo so referncias aos atributos elem e
topo do objeto p.
Em algumas situaes, necessrio referenciar o prprio objeto sobre o
qual se aplica a operao dentro da implementao dessa operao. Uma
dessas situaes ocorre quando o objeto deve ser passado como parme-
tro em uma chamada de funo. Para que isso seja feito, linguagens orien-
tadas a objeto incluem implicitamente um novo membro na classe, o qual
usado especificamente para referenciar o prprio objeto dentro das ope-
raes. No caso de C++, esse membro um ponteiro e se chama this. A
operao obtemTopo do exemplo 6.23 usa this para chamar a operao
vazia. Note que esse uso meramente ilustrativo do conceito, uma vez
que no preciso usar this para chamar a funo vazia, tal como ocorre
na operao desempilha.
Em linguagens orientadas a objeto, as operaes construtoras possuem
caractersticas especiais. Alm de no se especificar um tipo de retorno
para essas funes, elas tambm so sempre chamadas no momento em
que um objeto da classe criado. Dessa maneira, no existe risco do usu-
rio chamar uma outra operao antes de chamar a operao construtora.
Em C++ e JAVA, as operaes construtoras so identificadas pelo seu
nome, o qual deve ser o mesmo da sua classe.
Em geral, possvel criar vrias funes construtoras distintas para uma
mesma classe atravs do mecanismo de sobrecarga
6.7
. Isso permite a apli-
cao de diferentes funes construtoras de acordo com o contexto de
criao do objeto. No exemplo 6.23 a funo tPilha a nica funo
construtora da classe. Observe que essa funo chamada implicitamente
em main no momento da declarao da varivel p. Funes construtoras
tambm so chamadas em C++ no momento em que um objeto criado
dinamicamente atravs do operador new. Essa ltima a nica forma de
chamada de funes construtoras em JAVA. A construo dinmica de
um objeto da classe tPilha em C++ pode ser vista na seguinte linha de
cdigo:
tPilha* i = new tPilha;

6.7
O mecanismo de sobrecarga ser estudado no captulo 7.
192
Objetos em C++ precisam sempre ser criados atravs da chamada de um
construtor. Caso no seja especificado qualquer construtor para uma clas-
se, um construtor default criado implicitamente pelo compilador.
Um construtor default chamado sem a passagem de parmetros re-
ais, tal como o construtor de tPilha. O construtor default inicializa os
membros objetos da classe chamando os seus construtores default. J
os membros de tipos primitivos no so inicializados. Construtores de-
fault so especialmente importantes em C++ porque so os nicos que
podem ser usados durante a criao de um vetor de objetos da classe.
JAVA tambm cria um construtor default quando a classe no tem
um. Esse construtor no realiza nada alm das inicializaes dos mem-
bros da classe tais como foram declaradas.
Objetos em C++ sempre podem ser copiados na sua inicializao ou atra-
vs de atribuio. Para permitir a cpia na inicializao, C++ cria um
construtor de cpia para a classe quando no foi especificado um. Para
permitir a cpia na atribuio, o operador de atribuio de C++ possui
uma forma de atuao padro aplicvel a todas as classes. A atuao do
construtor de cpia criado pelo compilador e do operador de atribuio
so equivalentes. Eles fazem a cpia membro a membro do objeto copia-
do para o objeto cpia. O exemplo 6.24 mostra o uso do construtor de c-
pia e do operador de atribuio em objetos da classe tPilha.
main() {
tPilha p1, p2;
tPilha p3 = p1; // construtor de copia
p3 = p2; // operador de atribuicao
}
Exemplo 6. 24 - Construtor de Cpia e Operador de Atribuio em C++
A terceira linha do cdigo do exemplo 6.24 mostra o uso do construtor de
cpia para criar uma nova pilha p3 com contedo idntico pilha p1. J a
ltima linha mostra o uso do operador de atribuio para fazer p3 assumir
um contedo idntico pilha p2.
Esse comportamento do construtor de cpia e do operador de atribuio
pode no ser adequado em todas as situaes. Em particular, quando exis-
tem membros da classe que so ponteiros, esse comportamento tende a
gerar compartilhamento de dados no monte ao invs de cpia. Nesses ca-
sos, a classe deve ter a sua prpria definio do construtor de cpia e do
operador de atribuio, especificando como deve ser feita a cpia em ca-
da situao.
O construtor de cpia muito importante em C++ porque ele chamado
implicitamente nas passagens de parmetros por cpia e nos retornos de
193
objetos em funes. Como JAVA copia referncias na atribuio de obje-
tos, nas passagens de parmetros objeto e nos retornos de objetos em fun-
es, no necessrio definir construtores de cpia e operadores de atri-
buio para as classes nessa linguagem.
Quando um objeto deixa de ser usado em C++, necessrio desaloc-lo.
Se o objeto foi declarado localmente em um bloco, sua desalocao ser
feita ao final do bloco. Se o objeto foi alocado no monte, ele ter de ser
desalocado explicitamente atravs do uso do operador delete. Contudo, a
simples desalocao dos objetos em ambos casos pode no ser satisfat-
ria. Por exemplo, se os objetos contiverem membros alocados no monte,
esses membros no sero desalocados e provocaro vazamento de mem-
ria.
Para evitar isso, preciso definir uma operao destrutora nica para a
classe especificando o que precisa ser feito antes de desalocar o objeto.
Essa operao destrutora, quando definida na classe, sempre chamada
implicitamente quando um objeto dessa classe desalocado, seja por fim
de bloco ou por uso de delete. A operao destrutora em C++ possui uma
sintaxe especial, consistindo do nome da classe prefixado pelo smbolo ~,
tal como em ~tPilha. Funes destrutoras em C++ no podem possuir
parmetros.
Como a desalocao de memria em JAVA feita automaticamente pelo
coletor de lixo, no existe necessidade de uma operao destrutora espec-
fica para esse fim nas classes dessa linguagem. Quando necessrio al-
gum outro tipo de finalizao, que no seja desalocao de memria, a
classe deve possuir uma funo especfica para esse propsito e o cdigo
usurio deve cham-la explicitamente quando o objeto no for mais ne-
cessrio.
Em alguns contextos, pode ser necessrio compartilhar alguns membros
de uma classe por todos os objetos dessa classe, isto , ao invs de se ter
um membro para cada objeto da classe, tem-se apenas um membro usado
por todos os objetos da classe. Esses membros so conhecidos como
membros de classe e so caracterizados em C++ e JAVA pela ocorrncia
da palavra reservada static precedendo a definio desse membro.
Quando o membro de classe uma varivel, esse membro permanecer
alocado em memria durante toda a execuo do programa, independen-
temente da existncia ou no de objetos da classe. Quando o membro da
classe uma funo, ela poder ser chamada independentemente da exis-
tncia ou no de um objeto da sua classe.
194
6.2.3 Pacotes
A modularizao atravs de subprogramas e tipos ainda no totalmente
suficiente para atender as demandas existentes na construo de grandes
sistemas. Sistemas construdos com uso exclusivo dessas tcnicas acabam
apresentando baixa granularidade, deixando as entidades do programa
espalhadas pelo cdigo, o que dificulta sua identificao.
Quando se considera a reutilizao de cdigo em grandes sistemas, tam-
bm se constata que parte substancial dos componentes de cdigo desses
sistemas provm de fontes diversas, muitas vezes utilizados como uma
caixa preta, isto , o programa usurio desconhece como o cdigo foi im-
plementado pela sua fonte. Um problema com essa forma de reuso a
possibilidade de ocorrncia de conflitos entre os nomes das entidades das
diferentes fontes de cdigo utilizadas e os nomes das entidades do sistema
sendo criado.
Existem diferentes tipos de fontes de cdigo, as quais so colees de en-
tidades reusveis de computao. Elas se diferenciam pelo forma como
so usadas. Bibliotecas so a fonte mais conhecida e comum. Elas agru-
pam tipos, variveis, constantes e subprogramas usados para realizar fun-
cionalidades similares. Por exemplo, a biblioteca padro stdio de C ofere-
ce um conjunto de tipos, funes e macros para a realizao de operaes
de entrada e sada.
Outros tipos de fontes de cdigo so aplicaes utilitrias, frameworks
e aplicaes completas. Aplicaes utilitrias so sistemas construdos
para serem usados como caixa preta por outros sistemas. O sistema usu-
rio simplesmente invoca a aplicao utilitria quando necessita realizar a
funcionalidade para a qual ela destinada. Frameworks so implemen-
taes parciais de um determinado sistema. Para se criar uma aplicao
especfica completa, necessrio que o construtor da aplicao comple-
mente o cdigo fornecido pelo framework. Aplicaes completas so
sistemas prontos para serem usados pelo usurio final.
Pacotes so unidades sintticas que podem agrupar diversas entidades de
computao, das quais algumas so exportveis (isto , visveis para o
programa usurio do pacote) e outras no. LPs usam o conceito de paco-
tes para permitir a organizao das entidades de computao em mdulos
funcionais como bibliotecas, aplicaes utilitrias, frameworks e aplica-
es completas. De fato, pacotes tambm so usados internamente em
cada uma dessas fontes de cdigo para organizar as entidades de compu-
tao do sistema segundo sua arquitetura funcional.
Por possurem um nome prprio, pacotes podem ser usados para resolver
conflito de nomes de entidades provenientes de diferentes fontes. Quando
195
isso ocorre, basta usar a especificao completa do nome da entidade, cu-
ja composio formada pelo nome do pacote e pelo nome da prpria
entidade.
O exemplo 6.22 usa os pacotes de ADA para a implementao de um tipo
abstrato de dados. Contudo, o conceito de pacotes em ADA muito mais
amplo, admitindo a definio e o agrupamento de vrias entidades alm
de tipos e subprogramas.
6.2.3.1 Pacotes em C++
C++ usa a palavra namespace para definir um pacote. Cada conjunto de
definies em uma biblioteca ou programa pode ser embutido em uma
namespace de C++, e se alguma outra definio tem um identificador i-
dntico, mas em uma outra namespace, ento possvel resolver o confli-
to de nomes. A criao de uma namespace muito similar a criao de
uma classe e ilustrada no exemplo 6.25.
namespace umaBiblioteca {
int x = 10;
void f() {}
class tC {}
}
Exemplo 6. 25 - Pacote em C++
Contudo, em contraste a uma definio de classe, uma nova definio de
uma namespace no implica na redefinio da namespace e sim em uma
continuao da definio anterior. O exemplo 6.26 continua a namespace
umaBiblioteca inicialmente definida no exemplo 6.25.
// Adiciona mais entidades a umaBiblioteca
namespace umaBiblioteca { // nao eh redefinicao!
int y = 15;
void g(){}
// int x = 13;
}
Exemplo 6. 26 - Continuao de Pacote em C++
Aps a definio do exemplo 6.26 a namespace umaBiblioteca compos-
ta pelas variveis x e y, pelas funes f e g e pela classe tC. Note que no
seria possvel redefinir a varivel x, tal como na linha comentada no e-
xemplo 6.26, pois isso geraria um conflito de definies interno names-
pace. Tal fato no seria problema caso a nova definio de x fosse reali-
zada em uma nova namespace, como ilustrado no exemplo 6.27.
namespace outraBiblioteca {
int x = 13;
196
void h(){}
}
Exemplo 6. 27 - Outro Pacote em C++
C++ usa o operador de resoluo de escopo para especificar de qual na-
mespace a entidade de computao referenciada no programa. Dessa
maneira, no existe conflito entre nomes coincidentes usados em diferen-
tes pacotes. O exemplo 6.28 ilustra como isso feito.
main() {
umaBiblioteca::y = 20;
umaBiblioteca::f();
umaBiblioteca::x = 5;
outraBiblioteca::x = 5;
outraBiblioteca::h();
}
Exemplo 6. 28 - Usando Entidades Empacotadas em C++
C++ permite associar uma namespace a um outro nome, possibilitando ao
programador no usar nomes grandes ou esquisitos dados namespace. O
exemplo 6.29 mostra isso sendo feito.
namespace bib1 = umaBiblioteca;
namespace bib2 = outraBiblioteca;
main() {
bib1::y = 20;
bib1::x = 5;
bib2::x = 5;
bib2::h(){};
}
Exemplo 6. 29 - Renomeando Pacotes em C++
Ter sempre de usar o nome da namespace junto com o operador de reso-
luo de escopo para referenciar as entidades da namespace reduz signifi-
cativamente a redigibilidade dos programas, tornando enfadonho o pro-
cesso de escrita de programas. Para contornar esse problema, C++ usa a
palavra reservada using, a qual possibilita usar declaraes e definies
de uma certa namespace sem o uso do operador de resoluo de escopo.
O exemplo 6.30 ilustra a utilizao de using.
using namespace umaBiblioteca;
using namespace outraBiblioteca;
main() {
y = 20;
f();
h(){};
197
// x = 5;
umaBiblioteca::x = 5;
outraBiblioteca::x = 5;
}
Exemplo 6. 30 - A Palavra Reservada using em C++
Com a utilizao de using no mais necessrio usar o nome da names-
pace para referenciar as entidades dos pacotes. Essa regra somente no
vlida quando existem entidades de mesmo nome definidas em diferentes
pacotes. No exemplo 6.30, caso no estivesse comentada, a linha na qual
a varivel x atribuda produziria um erro na compilao. Nessas situa-
es, o conflito se resolve atravs do uso do operador de resoluo de es-
copo, como ilustrado nas duas ltimas linhas desse exemplo.
Toda a biblioteca padro de C++ est embutida na namespace std. Portan-
to, a incluso da frase using namespace std; em um programa possibilita
o uso de qualquer entidade pertencente a biblioteca padro.
6.2.3.2 Pacotes em JAVA
JAVA usa a palavra reservada package para definir um pacote. Pacotes
em JAVA contm um conjunto de classes relacionadas. O exemplo 6.31
mostra como definir duas classes chamadas umaClasse e outraClasse per-
tencentes ao pacote umPacote.
package umPacote;
public class umaClasse {}
class outraClasse {}
Exemplo 6. 31- Definio de Pacote em JAVA
Existem duas maneiras de usar as classes de um pacote na implementao
de uma classe externa. Pode-se especificar o nome do pacote em toda re-
ferncia classe do pacote ou usar o comando import seguido do nome
do pacote. O exemplo 6.32 ilustra essas duas maneiras
// usando diretamente o nome do pacote
umPacote.umaClasse m = new umPacote.umaClasse();
// usando import
import umPacote.*;
umaClasse m = new umaClasse();
Exemplo 6. 32 - Uso de Pacote em JAVA
Note que o uso de import torna o cdigo mais redigvel. A linha
import umPacote.*;
198
faz com que todas as classes de umPacote possam ser usadas pelo cdigo
usurio. Se o uso for apenas da classe umaClasse, tambm possvel es-
pecificar unicamente o nome dessa classe, tal como a linha
import umPacote.umaClasse;
Nesse caso, somente umaClasse poder ser usada pelo cdigo usurio.
Um aspecto interessante a respeito do conceito de pacote em JAVA sua
relao com a organizao de arquivos do programa em diretrios do sis-
tema de arquivo. Cada pacote deve necessariamente ter um diretrio cor-
respondente com o mesmo nome no qual so colocadas todas as classes
do pacote. Alm disso, tal como se pode criar subdiretrios no sistema de
arquivos, tambm possvel organizar os pacotes em nveis hierrquicos.
Por exemplo, todos os pacotes padres da linguagem JAVA fazem parte
do pacote java. Para se ter acesso s classes do pacote padro util de JA-
VA necessrio usar o comando
import java.util.*;
JAVA faz uso da sequncia de nomes de pacotes especificada no import
para determinar em qual diretrio deve encontrar as classes usadas pelo
cdigo usurio.
Pacotes em JAVA tambm so usados para definir um novo tipo de espe-
cificador de acesso para os membros das classes. Quando o especificador
de acesso de um membro omitido, JAVA considera esse membro como
acessvel pelos mtodos de todas as classes do pacote. Embora isso possa
parecer uma quebra na proteo dos dados, esse novo especificador foi
criado para oferecer mais uma opo para os implementadores, liberando
os construtores da classe de ter de disponibilizar funes pblicas usadas
exclusivamente pelos construtores do pacote para acessar e modificar os
membros privados dessa classe. De fato, nessas situaes, a soluo apre-
sentada por JAVA oferece maior proteo aos dados da classe, uma vez
que os mtodos de classes no pertencentes ao pacote no tero como a-
cessar ou modificar esses dados. Alm disso, caso no seja desejvel tor-
nar os membros da classe acessveis para as outras classes do pacote,
sempre se pode definir esses membros como privados. O exemplo 6.33
mostra o uso desse tipo de especificador de acesso em JAVA.
package umPacote;
public class umaClasse {
int x;
private int y;
public int z;
}
class outraClasse {
199
void f() {
umaClasse a = new umaClasse();
// a.y = 10;
a.z = 15;
a.x = 20;
}
}
Exemplo 6. 33 - Especificador de Acesso Baseado em Pacotes JAVA
O membro x de umaClasse no possui especificador de acesso no exem-
plo 6.33. Como outraClasse pertence ao mesmo pacote, a sua operao f
pode acessar e alterar o valor de x de a (um objeto de umaClasse). Como
o membro y de umaClasse privado, ele no pode ser acessado direta-
mente em f. J o membro z, por ser pblico, pode ser acessado por qual-
quer mtodo de qualquer classe.
6.3 Modularizao, Arquivos e Compilao Separada
A maioria das tcnicas de modularizao vistas at agora podem ser apli-
cadas para a modularizao de programas contidos em um nico arquivo.
Contudo, medida que o tamanho dos programas cresce, alguns proble-
mas prticos surgem com essa abordagem e acabam reduzindo a produti-
vidade dos programadores.
Em primeiro lugar, a redao e modificao de programas de tamanho
razovel em um nico arquivo se torna mais difcil, uma vez que o pro-
gramador necessita vasculhar todo o programa para encontrar partes a
serem modificadas ou usadas. Aps encontrar a parte procurada, e enten-
der como us-la ou modific-la, esse processo deve ser repetido, agora
para voltar ao ponto onde ele estava no programa. Todo esse processo de
busca no arquivo se repete inmeras vezes ao longo da construo ou
modificao dos programas, tornando o processo de programao bem
mais lento.
Outro problema com essa abordagem envolve o fato de que algumas enti-
dades de programao (subprogramas, tipos, variveis e constantes) po-
dem ser reusadas em vrios programas. Quando os programas so escritos
em arquivos nicos, a nica forma de reusar essas entidades atravs da
sua busca nos arquivos existentes e do processo de cpia do trecho de c-
digo que as implementa para o arquivo do programa que as usar.
A soluo para resolver esses dois problemas permitir a diviso do pro-
grama em vrios arquivos separados. Cada arquivo seria responsvel por
definir uma ou mais entidades de programao relacionadas lgica e fun-
cionalmente. Assim, os diversos arquivos servem como indexadores para
200
o programador encontrar a parte desejada do programa mais rapidamente.
Alm disso, quando um novo programa construdo, basta incluir os ar-
quivos que implementam as entidades usadas pelo programa. Um exem-
plo dessa abordagem, em C, seria criar uma biblioteca de arquivos com
cdigo de implementao de entidades de programao (costuma-se usar
a terminao .c no nome desses arquivos) e incluir os arquivos necess-
rios no programa a ser criado.
No entanto, essa forma de modularizao preserva outro problema tam-
bm existente na abordagem com arquivo nico. No caso de um progra-
ma pequeno, compilar e recompilar o programa por completo aps uma
modificao no demanda muito tempo e esforo. Mas, quando o pro-
grama cresce, o custo da compilao e recompilao tambm cresce subs-
tancialmente. Isso acaba atrasando o trabalho do programador e deman-
dando muito mais esforo computacional do que o realmente necessrio,
uma vez que no se pode restringir a compilao ou recompilao s par-
tes alteradas ou ainda no compiladas do programa.
Para resolver esse problema se torna necessrio permitir a compilao
separada dos vrios arquivos que fazem parte do cdigo fonte de um pro-
grama. A compilao de cada um desses arquivos gera arquivos objetos
com cdigo em linguagem de mquina. Depois da compilao de todos os
arquivos fonte preciso utilizar um programa especial, chamado ligador
(linker), para coletar os arquivos objeto gerados e lig-los em um ni-
co arquivo executvel.
Dessa maneira, se alguma modificao necessria, basta recompilar os
arquivos fonte modificados e chamar novamente o ligador para montar o
arquivo executvel, sem que seja necessrio recompilar todos os arquivos
do programa.
Contudo, para permitir a compilao separada dos diferentes mdulos, os
compiladores tinham de relaxar certos tipos de verificao de erros. Por
exemplo, ao se compilar um arquivo que fizesse chamadas a funes de-
finidas em outro arquivo no era possvel verificar se a funo existia de
fato, nem se os argumentos passados e valores retornados na chamada
eram do tipo e nmero apropriado. A mesma dificuldade ocorria no caso
do arquivo referenciar variveis definidas em outro arquivo. Nesse caso,
no era possvel verificar se o tipo da varivel era adequado s operaes
a qual essa varivel era submetida.
Para contornar esse problema, C permite que variveis e funes defini-
das em um arquivo sejam novamente declaradas no arquivo onde so u-
sadas. Isso feito atravs do uso dos prottipos das funes e da palavra
extern precedendo a declarao da varivel (ver captulo 2 para maiores
informaes sobre declaraes de variveis e funes). No entanto, essa
201
abordagem no suficientemente genrica pois pode ser necessrio criar
variveis em um arquivo de tipos de definidos em outros arquivos.
Outra alternativa, mais geral, para permitir a compilao separada de ar-
quivos, mantendo a possibilidade de verificao de erros, consiste basi-
camente em dividir os arquivos fontes em um arquivo de interface e outro
de implementao.
No arquivo de interface so declaradas ou definidas as entidades de com-
putao a serem exportadas, isto , as entidades que sero usadas por ou-
tros arquivos. No arquivo de implementao so definidas as entidades de
computao declaradas no arquivo de interface e as entidades usadas in-
ternamente, isto , as que no so exportadas. Quando um arquivo, que
usa entidades definidas em outros arquivos, necessita ser compilado sepa-
radamente, basta importar os arquivos de interface, os quais contm a in-
formao necessria para fazer a verificao de tipos adequadamente.
importante notar que normalmente os arquivos de interface contm de-
finies de variveis, constantes e tipos e apenas declaraes de subpro-
gramas. Assim, a parte mais trabalhosa e pesada da compilao se encon-
tra nos arquivos de implementao, onde se encontram as definies de
todos os subprogramas.
Alm de permitir a compilao separada com verificao de erros, essa
abordagem oferece mais uma maneira para a realizao de ocultamento
de informao. Isso realizado definindo a entidade a ser ocultada uni-
camente no arquivo de implementao.
Exemplos dessa abordagem so os arquivos de definio (DEFINITION
MODULE) e de implementao (IMPLEMENTATION MODULE) de
MODULA-2, os arquivos .h e .c de C, .h e .cpp de C++, e o package e o
package body de ADA.
Por possibilitar a compilao separada com verificao de erros e permitir
a realizao de ocultamento de informao, a separao dos arquivos fon-
te em interface e implementao se tornaram o principal instrumento usa-
do por programadores para construir TADs em boa parte das LPs. No ar-
quivo de interface se define o tipo da estrutura de dados e so declarados
os prottipos dos subprogramas correspondentes s operaes do TAD.
No arquivo de implementao, so definidas as operaes do TAD e
quaisquer outras entidades de computao necessrias para a implemen-
tao das operaes do TAD.
Um problema com o uso dessa abordagem a necessidade de definio
da estrutura de dados do TAD no arquivo de interface. Tal necessidade
ocorre em LPs como C, ADA e C++ porque na compilao dos arquivos
202
usurios do TAD necessrio saber o tamanho a ser alocado para os valo-
res, variveis, constantes e parmetros desse tipo
6.8
.
Como visto na seo 6.2.2.3.1, esse problema em C mais grave pois os
membros do TAD se tornam visveis para os programadores usurios, os
quais podem acessar diretamente a estrutura interna do tipo sem usar as
operaes definidas no arquivo de interface. Isso, alm de quebrar o ocul-
tamento de informao e poder provocar inconsistncias no uso do TAD,
tambm diminui a modificabilidade do cdigo, visto que uma alterao na
implementao da estrutura de dados do TAD pode implicar na necessi-
dade de reescrever o cdigo usurio.
ADA e C++ minimizam os problemas de quebra de ocultamento de in-
formao e necessidade de reescrita de cdigo usurio permitindo ao pro-
gramador declarar a definio da estrutura de dados do tipo como privada
no prprio arquivo de interface. Contudo, caso seja necessrio alterar a
estrutura interna do TAD (mesmo mantendo inalterados os prottipos das
operaes da interface do TAD), alm de ser necessrio recompilar o
prprio TAD, tambm preciso recompilar os arquivos usurios. Isso po-
de ser especialmente inconveniente em situaes em que o TAD um
tipo utilitrio muito usado em vrias aplicaes.
MODULA-2 introduz o conceito de tipo opaco para contornar esses pro-
blemas com a implementao de TADs. Tipos opacos so ponteiros espe-
ciais utilizados no arquivo de interface para apontar para um tipo definido
no arquivo de implementao. Os prottipos das operaes do TAD se
referem apenas ao tipo opaco. Assim, os programadores usurios somente
podem realizar sobre o TAD as operaes definidas no arquivo de inter-
face, visto que os usurios no sabem para onde o tipo opaco aponta. Isso
impede o uso inconsistente do TAD, visto que a sua estrutura interna no
pode ser acessada nos arquivos usurios, e limita a necessidade de altera-
o do cdigo usurio apenas s situaes nas quais as operaes declara-
das na interface do TAD so alteradas.
Por sua vez, no arquivo de implementao, define-se a representao do
tipo apontado pelo tipo opaco e implementa-se as operaes do TAD le-
vando-se em conta o fato do tipo opaco ser um ponteiro para esse tipo.
Com o uso do tipo opaco s necessrio recompilar os arquivos usurios
quando os prottipos das operaes do TAD so alterados. Quando ape-
nas a estrutura interna do TAD ou a implementao das operaes alte-
rada, no preciso recompilar os arquivos usurios. Isso ocorre porque os
compiladores de MODULA-2 podem verificar o uso apropriado do TAD
nos arquivos usurios (somente podem ser usados nas operaes definidas

6.8
Essa a razo para a colocao da definio do tipo tPilha na unidade de interface no exemplo 6.22
em ADA.
203
no arquivo de interface) e tambm sabem quanto de memria necessrio
alocar para criar valores, variveis, constantes e parmetros do tipo do
TAD ( necessrio alocar o espao para um ponteiro).
Um grande inconveniente do uso do tipo opaco em MODULA-2 obrigar
ao programador a utilizar ponteiros e alocao dinmica de memria na
implementao das operaes do tipo opaco. Isso reduz significativamen-
te a redigibilidade e legibilidade do cdigo, alm de causar perda de efici-
ncia, uma vez que se torna necessrio fazer endereamento indireto para
acessar os valores desse tipo.
Os problemas de redigibilidade e legibilidade poderiam ser resolvidos
caso os prprios compiladores de MODULA-2 se incumbissem de gerar
cdigo para as tarefas de derreferenciar os ponteiros e gerenciar a aloca-
o e desalocao dinmica de memria. Contudo, isso demandaria um
sistema de gerenciamento de memria para a linguagem (o que tornaria
muito mais complexa a implementao de MODULA-2) e reduziria ainda
mais a eficincia do cdigo. Essas so possveis razes para os projetistas
de ADA e C++ no oferecerem um mecanismo equivalente para a im-
plementao de TADs nessas LPs.
Arquivos com cdigo usurio em JAVA no necessitam ser recompilados
quando a estrutura interna ou a implementao das operaes da classe
so alteradas. Isso possvel porque JAVA sempre aloca objetos no mon-
te e possui um coletor de lixo. Assim, o cdigo usurio s necessita alocar
espao para uma referncia para o objeto (sempre do tamanho de um pon-
teiro). A criao de um objeto no cdigo usurio feita atravs da cha-
mada de uma operao construtora da classe, a qual tem a responsabilida-
de de definir como o objeto ser alocado no monte. Portanto, importante
atentar para o fato do cdigo de alocao ser colocado no arquivo de im-
plementao da classe. Por sua vez, a desalocao de memria respon-
sabilidade do coletor de lixo.
Algumas LPs s requerem a escrita do arquivo de implementao. Ao
escrever esse arquivo, o programador especifica quais so as entidades
exportveis e quais no so. Em algumas LPs, o compilador gera automa-
ticamente o arquivo de interface, o qual includo em uma biblioteca de
unidades de interface para ser usado pelos outros mdulos. Em outras LPs
(por exemplo, JAVA), a informao sobre as entidades exportveis
mantida no prprio arquivo compilado (os arquivos .class).
6.4 Consideraes Finais
Nesse captulo foi apresentada uma viso abrangente dos mecanismos
oferecidos por linguagens de programao para apoiar a modularizao
de programas. Vrios so os benefcios obtidos com a modularizao,
204
com destaque para o aumento da legibilidade, redigibilidade, modificabi-
lidade, reusabilidade, confiabilidade e eficincia de programao.
A legibilidade dos programas aumenta significativamente em consequn-
cia da diviso lgica do programa em unidades funcionais e da separao
do cdigo relacionado com a implementao do cdigo relacionado ao
uso de uma abstrao. Muitas vezes, para entender genericamente todo
um programa, basta analisar um pequeno trecho do cdigo. Outras vezes,
quando o objetivo entender detalhamente uma funcionalidade especfica
do programa, isso s requer uma anlise detalhada do mdulo no qual
essa funcionalidade foi implementada, sem demandar a anlise de todo o
cdigo do programa.
A redigibilidade tambm aprimorada porque um mesmo mdulo pode
ser usado em vrios pontos do programa, no requerendo assim que o c-
digo de sua implementao tenha de ser reescrito vrias vezes.
A modificabilidade dos programas aumentada porque, em um grande
nmero de vezes, a alterao da implementao de um mdulo no requer
a modificao dos seus programas e cdigos usurios.
A reusabilidade incrementada porque ao se criar mdulos que cumprem
uma certa funcionalidade, esse mesmo mdulo pode ser reutilizado sem-
pre que essa funcionalidade for necessria.
A eficincia da programao tambm aumentada porque o programador
tem mais facilidade para construir o programa dividindo-o em mdulos
menores. Assim, ele pode se dedicar programao de cada um desses
mdulos ao invs de tentar escrever o programa como um todo, o que di-
ficultaria sua implementao. Alm disso, possvel compilar os mdulos
separadamente, evitando dessa maneira que qualquer modificao em um
mdulo implique em um atraso provocado pela necessidade de recompi-
lao de todo o sistema computacional. Por ltimo, o desenvolvimento do
sistema pode ser dividido entre vrios programadores, os quais podem
codificar, compilar e testar os mdulos paralelamente.
A modularizao ainda aumenta a confiabilidade do cdigo visto que ca-
da mdulo criado pode ser verificado independentemente e extensiva-
mente antes de ser usado pelos outros mdulos, permitindo assim que o
uso e a reutilizao desse cdigo sejam feitos com mais garantias.
Tcnicas de modularizao so apropriadas para apoiar o processo de de-
senvolvimento de programas top-down (orientado a funcionalidades)
ou bottom-up (orientado a dados).
O processo top-down (tambm conhecido pelo termo de refinamentos
sucessivos) prope um mtodo de desenvolvimento hierrquico-funcional
dos programas. Nessa perspectiva, um programa visto como uma des-
205
crio de um processo para realizao de uma determinada funcionalida-
de. Para atingir essa funcionalidade, ele dividido em subprogramas, os
quais so responsveis por cumprir partes da funcionalidade geral do pro-
grama. Por sua vez, cada um desses subprogramas pode ser subdividido
em novos subprogramas em um processo recorrente.
O processo bottom-up prope um mtodo de desenvolvimento baseado
na identificao das entidades (objetos) reais existentes no domnio do
problema no qual o programa atuar. Alm de selecionar as entidades do
domnio, necessrio identificar as caractersticas e comportamento des-
sas entidades. Cada uma das entidades identificadas representada por
uma estrutura de dados especfica, normalmente atravs da definio de
um tipo de dados.
importante ressaltar a complementaridade dos mtodos top-down e
bottom-up ao invs da sua alternncia. O processo top-down requer
a representao de estruturas de dados usadas na comunicao entre os
subprogramas. Essa representao mais adequada quando a estrutura de
dados possui um mapeamento claro para as entidades do domnio, con-
forme advoga o mtodo bottom-up. Por sua vez, o processo bottom-
up requer a representao do comportamento das entidades, os quais so
implementados atravs de subprogramas. Essa representao mais apro-
priada quando o subprograma desenvolvido usando refinamentos suces-
sivos, isto , o mtodo top-down.
Finalmente, cabe lembrar que esse captulo apresenta uma rpida pincela-
do sobre o conceito de classes e sua implementao em C++ e JAVA.
Isso claramente no suficiente para dirimir a maior parte das questes
relacionadas com esse tema. O leitor interessado pode obter informaes
bastante completas sobre isso nos livros de C++ de Bjarne Stroustrup [S-
TROUSTRUP, 2000] e Bruce Eckel [ECKEL, 2000] [ECKEL & ALLI-
SON, 2003] e tambm no livro de JAVA de Bruce Eckel [ECKEL, 2002].
6.5 Exerccios
1. Implemente uma funo sem parmetros em C na qual se efetue a tro-
ca de dois valores. Utilize-a em um programa executor de trocas de
valores entre diversos pares de variveis. Explique porque os proble-
mas de redigibilidade, legibilidade e confiabilidade seriam ainda mais
graves nesse caso do que no exemplo 6.3.

2. possvel implementar, para cada tipo primitivo, funes em JAVA
nas quais sejam trocados os valores dos seus parmetros formais? Ca-
so sua resposta seja afirmativa, implemente uma dessas funes e ex-
206
plique como funciona, destacando como a troca feita. Em caso de
resposta negativa, justifique. Existiria alguma diferena na sua respos-
ta caso a troca fosse realizada entre parmetros de um mesmo tipo ob-
jeto? Justifique.

3. Um TAD (tipo abstrato de dados) definido pelo comportamento uni-
forme de um conjunto de valores. Embora a linguagem C no suporte
a implementao do conceito de TADs, o programador pode simular o
seu uso. Explique como isto pode ser feito. Descreva os problemas
com essa aproximao.

4. Considere uma funo em JAVA recebendo um objeto como nico
parmetro e simplesmente realizando a atribuio de null ao seu par-
metro formal. Qual o efeito dessa atribuio no parmetro real? Justi-
fique.

5. JAVA no permite a criao de funes com lista de parmetros vari-
vel, isto , funes nas quais o nmero e o tipo dos parmetros pos-
sam variar, tal como a funo printf de C. Como JAVA faz para pos-
sibilitar a criao da funo System.out.println com funcionalidade
similar funo printf de C? Como o problema da falta de lista de pa-
rmetros varivel pode ser contornado de maneira geral pelo progra-
mador JAVA em situaes nas quais esse tipo de caracterstica pode
ser til? Compare essa abordagem geral de JAVA com a adotada por
C e C++ em termos de redigibilidade e legibilidade.

6. Uma das vantagens de se programar usando a tcnica de tipos abstra-
tos de dados (TADs) aumentar a modificabilidade dos programas.
Isso ocorre porque a maior parte das alteraes no cdigo do TAD no
implicam em necessidade de modificao do cdigo usurio. Indique
em quais tipos de alteraes do cdigo do TAD essa vantagem no
pode ser aproveitada.

7. O uso de parmetros em um subprograma visa aumentar as possibili-
dades de reuso desse subprograma. Normalmente, os valores dos pa-
rmetros correspondem a dados que sero manipulados pelo subpro-
grama. Contudo, os parmetros podem servir tambm para alterar a
funcionalidade do subprograma, tornando sua aplicao mais abran-
gente e aumentando sua possibilidade de reuso. Mostre, atravs de um
exemplo em C, como valores do tipo ponteiro para funo podem ser
utilizados como parmetros para tornar um determinado cdigo mais
reusvel. Discuta como esse problema seria resolvido sem o uso do
parmetro ponteiro para funo. Analise e compare as duas solues
207
propostas em termos de redigibilidade, legibilidade, eficincia e reu-
sabilidade.

8. Contrastando com a maioria das LPs imperativas, em C possvel cri-
ar funes cuja lista de parmetros varivel (tome como exemplo, a
funo printf). Analise a abordagem adotada por C em comparao a:
abordagem adotada por MODULA-2, que no permite a exis-
tncia de subprogramas com lista de parmetros varivel (enfo-
que a comparao nos conceitos de redigibilidade e confiabili-
dade)
abordagem adotada por PASCAL, que permite a existncia de
lista de parmetros varivel para funes pr-definidas da lin-
guagem, tais como read e readln, mas no permite ao progra-
mador criar subprogramas com lista de parmetros varivel (en-
foque a comparao nos conceitos de reusabilidade e ortogona-
lidade)

9. Tipos Abstratos de Dados (TADs) so uma ferramenta poderosa de
projeto e programao. Descreva, de uma forma geral, como a pro-
gramao com TADs pode ser feita em C, ADA e C++. Exemplifique
com a descrio do tipo abstrato de dados fila de elementos inteiros
(no necessrio implementar as operaes da fila). Compare as trs
abordagens em termos de encapsulamento, ocultamento de informa-
o, confiabilidade do uso e necessidade de alterao do cdigo fonte
usurio quando ocorrem alteraes no cdigo do TAD.

10. Considere o seguinte programa escrito na sintaxe de C:

void calculoMaluco (int a, int b) {
a = a + b;
b = a + b;
}
void main() {
int valor = 0;
int lista [5] = { 1, 3, 5, 7, 9 };
calculoMaluco ( valor, lista [valor] );
}

Determine qual o valor das variveis valor e lista aps a execuo do pro-
grama, supondo que:
a) A direo da passagem de parmetros unidirecional de entrada
varivel, o mecanismo por cpia e o momento de passagem de-
finido pelo modo normal.
208
b) A direo bidirecional de entrada e sada, o mecanismo por re-
ferncia e o momento normal.
c) A direo bidirecional de entrada e sada, o mecanismo por re-
ferncia e o momento por nome.
Explique os resultados alcanados.

11. Os dois trechos de cdigo seguintes apresentam definies (em arqui-
vos .h) do tipo abstrato de dados BigInt em C e C++, respectivamente.
BigInt um tipo de dados que permite a criao de nmeros inteiros
maiores que long.

// C
struct BigInt {
char* digitos;
unsigned ndigitos;
}
struct BigInt criaBigIntC (char*); // cria a partir de string
struct BigInt criaBigIntN (unsigned n); // cria a partir de unsigned
struct BigInt criaBigIntB (struct BigInt); // cria a partir de outro BigInt
void atribui (struct BigInt*, struct BigInt*);
struct BigInt soma (struct BigInt, struct BigInt);
void imprime (FILE* f, struct BigInt);
void destroi (struct BigInt);

// C++
class BigInt {
char* digitos;
unsigned ndigitos;
public:
BigInt (const char *);
BigInt (unsigned n = 0);
BigInt (const BigInt&);
void atribui (const BigInt&);
BigInt soma (const BigInt&) const;
void imprime (FILE* f = stdout) const;
~ BigInt();
};

Compare essas definies em termos de encapsulamento, proteo dos
dados e confiabilidade das operaes de inicializao e terminao das
instncias desse TAD. Justifique sua resposta.
A operao atribui da classe BigInt tambm poderia ser definida atravs
do seguinte prottipo:
void atribui(BigInt);
209
Compare essa definio com a usada na classe BigInt em termos de efici-
ncia de execuo e confiabilidade na proteo dos dados do parmetro
formal. Explique sua resposta.

12. Ao se modificar a estrutura de dados de uma classe em C++, ainda que
mantida a mesma interface (isto , as assinaturas das operaes pbli-
cas da classe continuam idnticas s existentes antes da alterao),
necessrio recompilar no apenas o cdigo da prpria classe, mas
tambm os programas usurios dessa classe. Explique porque isso o-
corre levando em conta que no h alteraes no cdigo fonte dos
programas usurios. Explique como JAVA evita a necessidade de re-
compilao nesses casos. Apresente razes para justificar a no incor-
porao dessa caracterstica em C++?

13. Execute o seguinte trecho de cdigo em C++, mostrando o seu resul-
tado.

void incrementa (int& x, int& y) {
x = x + y;
y++;
}
main ( ) {
int a [ ] = { 1, 2, 3 };
for ( int i = 0; i < 3; i++ ) {
incrementa ( a [ i ], a [ 1 ] );
cout << a [ i ] << "\n" ;
}
}

Explique como o resultado foi produzido. A execuo desse cdigo pro-
duz algum efeito estranho prejudicial a legibilidade? Justifique sua res-
posta.

14. Em uma LP, o momento da avaliao dos parmetros reais, durante a
passagem de parmetros, pode ser por definido pelo modo normal (e-
ager), por nome (by name) ou preguioso (lazy). Explique o sig-
nificado de cada um desses modos. Execute trs vezes o programa C
seguinte, supondo que a linguagem adotasse, em cada execuo, um
tipo diferente de modo de avaliao. Explique os resultados alcana-
dos.

void avalia (int c) {
int i;
for (i = 0; i < 3; i++) {
210
printf ("%d\n", c);
}
}

void main ( ) {
int j = 0;
avalia (j++);
}

15. Descreva como deve ser feita a operao de desalocao de memria
em um tipo abstrato de dados lista de inteiros em C, C++ e JAVA.
Compare as diferentes abordagens adotadas por essas LPs na imple-
mentao e uso dessa operao em termos de redigibilidade, confiabi-
lidade e eficincia.

16. Qualifique os tipos de passagem de parmetros oferecidos por C, C++
e JAVA em termos da direo da passagem e do mecanismo de passa-
gem. Compare-os em termos de confiabilidade e eficincia.
211

Captulo VII Polimorfismo


Se oc tem uma maa e eu tenho uma maa e as trocamos entao cada um de ns con-
tinuara a ter uma maa. Mas, se oc tem uma idia e eu tenho outra idia e as trocamos,
entao cada um de ns tera duas idias.`
George Bernard Shaw
Tipos de dados definem um conjunto de valores e as operaes aplicveis
sobre esses valores. Eles servem fundamentalmente para oferecer infor-
maes relevantes aos programadores e aos compiladores (ou interpreta-
dores) sobre os dados usados pelos programas.
Linguagens de mquina so consideradas como no tipadas porque nessas
linguagens existe um nico tipo, representado pela palavra da memria, o
qual uma cadeia de bits de tamanho fixo. Programar nessas linguagens
significa, em ltima instncia, representar tudo (caracteres, nmeros, pon-
teiros, estruturas de dados, instrues e programas) como cadeias de bits.
Assim, a anlise de programas escritos em linguagens no tipadas no
permite identificar de maneira clara o que est sendo representado. Esse
tipo de informao obtido apenas atravs de uma interpretao externa
ao programa, a qual normalmente mantida apenas na mente do progra-
mador.
importante atentar que, mesmo em linguagens no tipadas, o conceito
de tipos de dados surge naturalmente com a atividade de programao.
To logo inicie a programar nessas linguagens, o programador comea a
organizar os dados (as cadeias de bits) para us-los de acordo com seus
propsitos. Nmeros, caracteres e instrues precisam ser tratados de ma-
neira distinta pelo programador. A categorizao das cadeias de bits em
termos dos propsitos para os quais so usados corresponde a uma defini-
o informal desses tipos.
O problema com essa abordagem a impossibilidade de evitar violaes
na organizao dos dados. Por exemplo, no h qualquer impedimento
para o programador efetuar a atribuio de uma instruo a algo que ante-
riormente representava um nmero. Afinal, ambos so cadeias de bits, o
nico tipo realmente reconhecido por essas linguagens.
Linguagens de alto nvel so tipadas. Cada qual define um sistema de ti-
pos composto por um conjunto prprio de tipos de dados. Sistemas de
tipos facilitam o processo de organizao dos dados, oferecendo no so-
mente facilidades para a definio e o uso dos dados, mas tambm para
garantir o seu tratamento apropriado. Por exemplo, um sistema de tipos
que inclui um tipo booleano facilita o programador a criar variveis desse
212
tipo (no necessrio definir a representao de um valor booleano), o
uso dessas variveis (as operaes boolenas j esto definidas) e tambm
garantir que no sero usadas em operaes inapropriadas (tal como uma
adio com um valor inteiro).
O nmero e as propriedades dos tipos de dados, as facilidades e as garan-
tias oferecidas variam de LP para LP, de acordo com a sofisticao do
sistema de tipos. Uma deciso importante diz respeito ao grau de poli-
morfismo do sistema. Polimorfismo em LPs se refere a possibilidade de
se criar cdigo capaz de operar sobre valores de tipos diferentes. Em ge-
ral, quanto mais polimrfico um sistema de tipos, maior a possibilida-
de se criar cdigo reutilizvel em uma LP.
Esse captulo tem por objetivo descrever os diferentes tipos de polimor-
fismo oferecidos por LPs. O captulo comea apresentando uma breve
introduo ao estudo dos sistemas de tipos. Em seguida, so apresentados
e discutidos os diferentes tipos de polimorfismo. Especial ateno dada
ao tipo de polimorfismo que intrisicamente associado a orientao a ob-
jetos.
7.1 Sistemas de Tipos
O uso de um sistema de tipos serve para descrever de modo mais adequa-
do os dados, aumentando a legibilidade e a redigibilidade de programas.
Alm disso, a disciplina relacionada com o uso de tipos evita que pro-
gramas executem operaes incoerentes, tal como, somar um inteiro e um
caracter. Neste sentido, LPs de alto nvel diferenciam-se grandemente de
LPs de baixo nvel onde o nico tipo byteou word.
Um conceito importante nesse contexto o de verificao de tipos. Para
se ter uma definio bem abrangente desse conceito vale lembrar que
sempre se pode considerar subprogramas como operadores e seus par-
metros como operandos. Verificao de tipos , portanto, a atividade de
garantir que operandos de um certo operador so de tipos compatveis.
Um tipo compatvel um tipo cujos valores sejam adequados para a rea-
lizao da operao designada pelo operador ou que pode ser convertido
implicitamente em um tipo cujos valores sejam adequados.
Verificao a priori de tipos normalmente recomendada pois evita a rea-
lizao de operaes sem sentido. Por exemplo, a verificao a priori de
tipos deve analisar se os operandos de uma expresso aritmtica so re-
almente de um tipo numrico.
213
7.1.1 Verificao de Tipos
LPs podem possibilitar ou no a realizao de uma ampla verificao de
tipos. Algumas LPs, tal como C, adotam uma postura fraca com relao a
isso pois somente parte dos erros de tipos so verificados. Por exemplo,
em C, possvel fazer um ponteiro para um float acessar uma rea de
memria onde est alocado um vetor de caracteres. Embora oferea flexi-
bilidade, esse tipo de caracterstica, se usada inapropriadamente, pode
gerar problemas desastrosos. Outras LPs, tais como ADA e JAVA, procu-
ram realizar uma verificao extremamente ampla de tipos. A vantagem
de se fazer isso aumentar a produtividade do programador e a confiabi-
lidade dos programas.
A verificao de tipos deve ser necessariamente realizada antes da execu-
o das operaes. De modo geral, elas devem ser feitas o quanto antes
possvel. Isso garante que os erros de tipos sero identificados mais cedo,
trazendo significativos benefcios para todo o processo de desenvolvi-
mento de programas. Contudo, nem sempre isso possvel ou desejvel.
Algumas LPs possuem brechas no seu sistema de tipos que impedem a
realizao de algum tipo de verificao. Alm disso, em certas situaes,
pode ser conveniente retardar a verificao de tipos. Por exemplo, pro-
gramas em LPs orientadas a objetos podem precisar converter tipos de
objetos durante a execuo. Nesses casos, a verificao dessa operao s
pode ser realizada tardiamente.
LPs estaticamente tipadas executam a verificao de tipos em tempo de
compilao. Todos os parmetros e variveis devem possuir um tipo fixo
identificvel a partir da anlise do texto do programa. Desse modo, o tipo
de cada expresso pode ser identificado e cada operao pode ser verifi-
cada pelo compilador. Em certas situaes, LPs estaticamente tipadas po-
dem ser excessivamente restritivas, reduzindo a redigibilidade dos pro-
gramas. Exemplo de LP quase estaticamente tipada MODULA-2, reco-
nhecida pelas restries que impe escrita dos programas. Cabe dizer
que, embora a verificao de tipos em MODULA-2 seja toda feita em
tempo de compilao, ela no pode ser considerada uma LP estaticamente
tipada por causa do conceito de registro variante, o qual abre uma brecha
para a ocorrncia de violaes do sistemas de tipos em tempo de execu-
o
7.1
.
LPs dinamicamente tipadas s executam a verificao de tipos em tempo
de execuo. Somente os valores dessas LPs tm tipo fixo. Normalmente,
cada valor tem associado a ele um tag indicando o seu tipo. Uma vari-
vel ou parmetro no possui um tipo associado, podendo designar valores

7.1
Ver seo 3.2.2.2 desse livro.
214
de diferentes tipos em pontos distintos da execuo. Esse fato impe a
realizao da verificao dos tipos de operandos imediatamente antes da
execuo da operao. LISP, BASIC, APL e SMALLTALK so LPs di-
namicamente tipadas.
Algumas LPs realizam a maior parte das verificaes de tipos em tempo
de compilao, mas deixam algumas verificaes para serem feitas em
tempo de execuo. Exemplos de LPs que possuem essa caracterstica so
C++, ADA e JAVA.
Linguagens fortemente tipadas [CARDELLI, 1991] devem possibilitar a
deteco de todo e qualquer erro de tipo em seus programas. Cardelli re-
comenda usar a verificao esttica tanto quanto o possvel e a verifica-
o dinmica quando for necessrio nessas linguagens. ALGOL-68 uma
linguagem fortemente tipada. ADA e JAVA so aproximadamente forte-
mente tipadas.
importante entender como verificaes estticas de tipos so efetuadas
pelo compilador. Observe o exemplo 7.1 em C.
int par (int n) {
return (n % 2 == 0);
}
main() {
int i = 3;
par (i);
}
Exemplo 7. 1 - Verificao Esttica de Tipos
O compilador de C efetua as seguintes verificaes de tipo no cdigo do
exemplo 7.1:
1. Os operandos de % tem de ser inteiros, logo n tambm tem de ser
inteiro. Como n e 2 so inteiros, a operao % vlida.
2. Os operandos de == devem ser de um mesmo tipo. Como o ope-
rando 0 e o resultado de % so inteiros, a operao tambm vli-
da.
3. O tipo de retorno de par deve ser inteiro. Como o resultado de ==
inteiro, ele compatvel com o tipo retornado pela funo.
4. O parmetro formal de par inteiro. Como o parmetro real usado
na chamada da funo par inteiro, ele compatvel com o tipo do
parmetro formal correspondente.
Em linguagens dinamicamente tipadas, as verificaes de tipo so efetua-
das imediatamente antes da execuo das operaes. O exemplo 7.2 em
LISP mostra uma funo que efetua a multiplicao de um nmero por 3.
(defun mult-tres (n)
215
(* 3 n))
(mult-tres 7)
(mult-tres abacaxi)
Exemplo 7. 2 - Verificao Dinmica de Tipos em LISP
O exemplo 7.2 mostra inicialmente a funo mult-tres, que tem um par-
metro formal n. Como o tipo de n no conhecido previamente, essa fun-
o pode ser chamada com argumentos de tipos diferentes. Para cada
chamada da funo mult-tres necessrio verificar o tipo de n em tempo
de execuo para assegurar que seja um nmero (nico tipo vlido como
argumento da operao *). Assim, na primeira chamada a mult-tres, o pa-
rmetro n associado ao valor 7 e a funo retorna o resultado de sua
multiplicao por trs. Note que somente quando a funo tentar efetuar a
multiplicao que ser feita a verificao do tipo de n. Na segunda
chamada, ocorrer um erro de execuo, uma vez que o tipo de n no
um valor legal para a operao de multiplicao.
LPs dinamicamente tipadas oferecem maior flexibilidade para produzir
cdigo reutilizvel. O exemplo 7.3 mostra a funo segundo em LISP.
Essa funo usada para retornar o segundo elemento de uma lista. O
corpo de segundo utiliza as funes car (retorna o primeiro elemento da
lista) e cdr (retorna a lista sem o primeiro elemento) de LISP para atingir
o seu objetivo.
(defun segundo (l)
(car(cdr l)))
(segundo (1 2 3))
(segundo ((1 2 3) (4 5 6)))
(segundo (manga abacaxi 5 6))
Exemplo 7. 3 - Vantagem de Tipagem Dinmica
Note as trs chamadas de segundo no exemplo 7.3. O fato de l no ser
previamente amarrada a um tipo especfico de lista, possibilita que, em
cada chamada, a lista l assuma valores de tipos distintos. Na primeira
chamada, l uma lista de nmeros, na segunda, uma lista de listas e, na
terceira, uma lista de strings e nmeros.
Por outro lado, LPs dinamicamente tipadas perdem eficincia computa-
cional devido incluso da verificao de tipos em tempo de execuo e
ao aumento de espao de memria ocupada, uma vez que necessrio
manter o tipo dos valores durante a execuo do programa. Alm disso,
necessrio manter em memria o cdigo necessrio para a verificao dos
tipos em tempo de execuo e para tratar qualquer tipo legal que possa ser
utilizado pelas funes.
216
LPs estaticamente tipadas evitam os problemas de eficincia e espao ci-
tados no pargrafo anterior e proporcionam confiabilidade. Erros de tipos
so detectados em tempo de compilao, ao invs de terem de serem de-
tectados por testes (procedimento pouco confivel). Por outro lado, elas
impem mais restries redigibilidade e reutilizao de cdigo.
LPs fortemente tipadas que seguem a recomendao de Cardelli, citada
anteriormente nessa seo, oferecem a melhor combinao de eficincia e
flexibilidade, alm de aumentarem a confiabilidade, uma vez que erros de
tipo sero sempre detectados.
7.1.2 Inferncia de Tipos
LPs estaticamente tipadas no devem necessariamente exigir a declarao
explcita de tipos. Um sistema de inferncia de tipos pode ser usado para
identificar os tipos de cada entidade e expresso do programa. Para ter
clareza sobre essa propriedade, suponha que na funo par do exemplo
7.1 o tipo do parmetro n no tivesse sido declarado. Ainda assim, um
compilador poderia deduzir, pelo fato da operao % requerer operandos
do tipo inteiro, que o parmetro n tem de ser inteiro.
Exemplos de LPs cujos compiladores realizam inferncia de tipos so
HASKELL e ML. Cabe ressaltar que esses compiladores so bem mais
exigidos que um compilador de uma LP com declarao explcita de ti-
pos. Cabe dizer ainda que ML liberal quanto a declarao de tipos, isto
, o programador pode definir explicitamente o tipo da entidade declarada
ou deixar o compilador inferir o tipo.
No declarar explicitamente o tipo das entidades certamente aumenta a
redigibilidade dos programas. Contudo, isso tambm provoca uma redu-
o de legibilidade pois pode ser difcil descobrir o tipo de uma funo ou
entidade. Muitas vezes necessrio percorrer vrias outras funes para
poder realizar a identificao. Outra desvantagem ocorre quando surge
um erro de programao. Isso pode fazer com que o compilador produza
mensagens de erro obscuras.
7.1.3 Equivalncia de Tipos
LPs podem adotar posturas distintas em situaes nas quais se aplica um
operando de tipo diferente do esperado por uma operao. Algumas LPs,
como C, so extremamente liberais permitindo que isso seja feito livre-
mente. Nessas LPs o operando convertido implicitamente e tratado co-
mo se fosse do tipo esperado pela operao. Outras LPs, como MODU-
LA-2, so extremamente rigorosas e, por isso, no admitem isso ser feito.
217
Assim, o programador forado a converter o valor explicitamente para o
tipo esperado da operao ou no realiz-la.
Algumas LPs, tal como PASCAL, adotam uma postura intermediria, fa-
zendo converses implcitas sempre que possvel e acusando erro quando
isso no vlido. LPs estabelecem regras definindo quais converses im-
plcitas so vlidas. Essas regras podem ser especficas para cada tipo de
converso, baseadas no conceito de incluso de tipos ou no conceito de
equivalncia de tipos.
Segundo o conceito de incluso de tipos, a converso permitida se os
valores do tipo do operando tambm so valores do tipo esperado pela
operao. Por exemplo, a converso implcita de um valor do tipo int para
um long em JAVA permitida porque os valores de int tambm so valo-
res de long.
Regras tambm podem ser baseadas na equivalncia estrutural ou nomi-
nal de tipos. No caso da equivalncia estrutural, a converso permitida
se o conjunto de valores do tipo do operando o mesmo do tipo esperado
pela operao. Essa equivalncia chamada de estrutural porque a verifi-
cao da igualdade do conjunto de valores realizada comparando-se as
estruturas dos dois tipos, uma vez que a comparao por enumerao de
valores invivel. Watt [WATT, 1990] lista as seguintes regras para de-
finir a equivalncia estrutural de dois tipos T e T.
Figura 7. 1 - Equivalncia Estrutural de Tipos Segundo Watt [WATT, 1990]
No caso da equivalncia nominal, dois tipos s so equivalentes se e so-
mente se possuem o mesmo nome. Essa uma abordagem mais restritiva
pois sua aplicao implica na inexistncia de possibilidade de converso
implcita de tipos por equivalncia. De fato, em LPs que adotam esse mo-
do de equivalncia, a nica forma de fazer converses atravs de regras
especficas ou baseadas no conceito de incluso. Com o advento da pro-
Se T e T so primitivos, ento T e T devem ser idnticos
Por exemplo, inteiro inteiro
Se T e Tso produtos cartesianos e T = A x B e T= A x B,
ento A A e B B
Por exemplo, inteiro x booleano inteiro x booleano
Se T e Tso unies e T = A + B e T= A + B;
ento A A e B B ou A B e B A
Por exemplo, inteiro + booleano booelano + inteiro
Se T e Tso mapeamentos e T = A B e T = A B;
ento A A e B B.
Por exemplo, inteiro booleano inteiro booleano
218
gramao baseada em TADs, a equivalncia nominal passou a ser adotada
pela maioria das LPs pois ela sempre garante a consistncia entre os ope-
randos e as operaes.
O exemplo 7.3, escrito em C, pode ser usado para ilustrar as diferenas
entre a equivalncia estrutural e a nominal. No exemplo so definidos
dois tipos a partir do tipo float.
typedef float quilometros;
typedef float milhas;
quilometros converte (milhas m) {
return 1.6093 * m;
}
main() {
milhas s = 200;
quilometros q = converte(s); // ambas
s = converte(q); // estrutural apenas
}
Exemplo 7. 4 - Equivalncia de Tipos
Em caso de equivalncia estrutural, todo o cdigo do exemplo vlido.
No caso de equivalncia nominal, o retorno da funo converte, a atribui-
o e a chamada da ltima linha do cdigo so invlidas. C adota equiva-
lncia estrutural nesse caso. No entanto, se em vez de tipos primitivos,
quilometros e milhas fossem associados a estruturas annimas idnticas, a
equivalncia seria nominal.
7.1.4 Sistemas de Tipos Monomrficos e Polimrficos
PASCAL e MODULA-2 tm um sistema de tipos no qual todas constan-
tes, variveis e subprogramas devem ser definidos com um tipo especfi-
co. Um sistema de tipos como esse chamado de monomrfico. Entretan-
to, a simplicidade imposta por um sistema de tipos monomrfico pode ser
insatisfatria quando se deseja escrever cdigos reutilizveis. Muitos al-
goritmos e estruturas de dados so inerentemente genricos, no sentido de
que eles so praticamente independentes do tipo dos valores manipulados.
Contudo, uma LP monomrfica no permite defini-los e trat-los generi-
camente.
Considere, por exemplo, as operaes de pertinncia, contingncia, unio
e interseo de conjuntos. Conceitualmente, essas operaes so genri-
cas pois podem ser realizadas independentemente dos tipos dos elementos
dos conjuntos. De fato, nada impede aos conjuntos de possurem at ele-
mentos de tipos distintos e ainda assim se poder realizar essas operaes
sobre eles.
219
Contudo, linguagens monomrficas no proporcionam flexibilidade para
se fazer nada disso. Nessas LPs necessrio criar um tipo conjunto para
cada tipo de elemento e operaes especficas para cada um dos tipos
conjunto. Isso provoca grande redundncia no cdigo, reduzindo signifi-
cativamente a redigibilidade, pois as operaes so efetivamente as mes-
mas, somente variando o tipo do elemento dos conjuntos. Alm disso,
linguagens monomrficas no possibilitam que os conjuntos sejam com-
postos por elementos de tipos diferentes.
Embora basicamente monomrficas, PASCAL E MODULA-2 no so
puramente monomrficas. PASCAL possui os subprogramas pr-
definidos read, readln, write, writeln e eof, os quais aceitam valores de
vrios tipos na sua chamada. MODULA-2 e PASCAL possuem operado-
res (por exemplo, o operador +) os quais atuam sobre diversos tipos.
Linguagens, cujos sistemas de tipos favorecem a construo e uso de es-
truturas de dados e algoritmos que atuam sobre elementos de tipos diver-
sos, so chamadas de polimrficas. Tipos de dados polimrficos so a-
queles cujas operaes so aplicveis a valores de mais de um tipo. Sub-
programas polimrficos so aqueles cujos parmetros (e, no caso de fun-
es, o tipo de retorno) podem assumir valores de mais de um tipo.
C, por exemplo, possui o tipo void*, o qual possibilita a criao de vari-
veis e parmetros cujos valores podem ser ponteiros para tipos quaisquer.
Esse tipo de C possibilita a construo de estruturas de dados genricas e
algoritmos genricos para manipul-las. C tambm permite a criao de
funes polimrficas atravs do uso de uma lista de parmetros varivel.
Outros exemplos de linguagens polimrficas so ADA, ML, C++ e JA-
VA.
7.2 Tipos de Polimorfismo
Polimorfismo em LPs se refere a possibilidade de se criar cdigo capaz
de operar (ou, pelo menos, aparentar operar) sobre valores de tipos distin-
tos. Existem diferentes tipos de polimorfismo. Uma classificao [CAR-
DELLI & WEGNER, 1985] dos tipos de polimorfismo apresentada na
tabela7.1.
Coero Ad-hoc
Sobrecarga
Paramtrico

Polimorfismo
Universal
Incluso
Tabela 7. 1 - Tipos de Polimorfismo [CARDELLLI & WEGNER, 1985]
A diferena entre polimorfismo ad-hoc e universal diz respeito ao uso e
capacidade de proporcionar reuso de cdigo. O polimorfismo ad-hoc se
220
aplica apenas a abstraes de controle e somente proporciona reuso do
cdigo no qual feita a chamada da abstrao de controle. J o polimor-
fismo universal se aplica tanto s abstraes de dados quanto s de con-
trole. Alm disso, ele proporciona reuso de cdigo tanto na chamada
quanto na implementao das abstraes de controle.
O polimorfismo ad-hoc aparentemente proporciona reuso do cdigo de
implementao da abstrao, mas na realidade no o faz. Tipicamente,
so criadas abstraes de controle especficas para operar sobre cada tipo
admissvel.
De fato, o polimorfismo ad-hoc ocorre quando um mesmo smbolo ou
identificador associado a diferentes trechos de cdigo que atuam sobre
diferentes tipos. Para quem l o cdigo, pode parecer que o smbolo ou
identificador denota um nico trecho de cdigo polimrfico, atuando so-
bre elementos de tipos diferentes. Contudo, isso apenas aparente, uma
vez que, atravs dos argumentos associados ao identificador ou smbolo,
se faz a identificao do trecho de cdigo especfico a ser executado.
O polimorfismo universal ocorre quando uma estrutura de dados pode ser
criada incorporando elementos de tipos diversos ou quando um mesmo
cdigo pode ser executado e atuar sobre elementos de diferentes tipos.
Por isso, o polimorfismo universal tido como o "verdadeiro polimor-
fismo enquanto que o polimorfismo ad-doc tido como um polimorfismo
"aparente".
A tabela 7.1 tambm mostra que o polimorfismo ad-hoc pode ser dividido
em polimorfismo de coero e polimorfismo de sobrecarga. Por sua vez,
o polimorfismo universal se subdivide em polimorfismo paramtrico e
polimorfismo por incluso. Cada um desses tipos de polimorfismos sero
detalhados nas prximas sees desse captulo.
7.2.1 Coero
Coero significa converso implcita de tipos. Por exemplo, em C, ao se
atribuir um valor de um tipo char para uma varivel do tipo int, haver
uma converso implcita do valor do tipo char para um valor do tipo int.
Para fazer isso, o compilador de C necessita ter uma tabela embutida de
converses permitidas. Assim, quando uma operao aplicada sobre um
operando de tipo diferente do esperado, o compilador verifica se existe
uma converso na tabela que seja aplicvel e, em caso positivo, inclui c-
digo para realizar a converso do tipo do operando para o tipo esperado
pela operao. O exemplo 7.5 mostra como isso feito em um programa
C.
221
void f (float i) { }
main() {.
long num;
f (num);
}
Exemplo 7. 5 - Coero em C
No exemplo 7.5, apesar de a funo receber um float como parmetro, ela
chamada passando um long. Aparentemente, a funo f seria capaz de
tratar tanto valores do tipo float quanto do tipo long. Contudo, essa fun-
o s lida efetivamente com valores do tipo float. De fato, o prprio
compilador se encarrega de embutir cdigo para fazer a converso.
Frequentemente, coeres so ampliaes ou estreitamentos. A ampliao
ocorre quando um valor de um tipo com menor conjunto de valores
convertido em um valor de um tipo cujo conjunto de valores mais am-
plo. No estreitamento ocorre o inverso, o que implica no risco de perda de
informao, uma vez que o valor do tipo mais amplo pode no possuir
representao no tipo mais estreito.
Em alguns casos, coeres podem no ser ampliaes nem estreitamen-
tos. Por exemplo, quando, em C, se converte um int em um unsigned,
embora os dois tipos possuam o mesmo nmero de valores, os seus con-
juntos de valores so diferenciados. Nesses casos, embora no se possa
dizer que houve ampliao ou estreitamento, tambm existe a possibili-
dade de perda de informao.
Uma LP frequentemente fornece um conjunto de regras para definir as
converses implcitas de tipos vlidas. Por exemplo, algumas das regras
de C so:
1. Valores de tipo mais estreito podem ser promovidos para tipos mais
amplos. Por exemplo: char para int, int para float, float para double.
2. Quando um valor do tipo ponto flutuante convertido para um tipo
inteiro, a parte fracionria eliminada; se o valor resultante no puder
ser representado no tipo inteiro, o comportamento definido pela im-
plementao do compilador.
Coeres do a entender que determinada operao ou subprograma pode
ser realizada com operandos de tipos diferentes. Contudo, no isso o
que ocorre. Veja o exemplo 7.6 em C.
main() {
int w = 3;
float x;
float y = 5.2;
x = x + y; // x = somafloat (x, y)
222
x = x + w; // x = somafloat (x, intToFloat (w) );
}
Exemplo 7. 6 - Expresso com Coero em C
Embora a coero na ltima linha do exemplo 7.6 possa dar a impresso,
para quem cria ou l esse programa, que o operador + corresponde a uma
funo capaz de realizar tanto a operao de somar dois valores do tipo
float (float x float float) quanto a operao de somar um float e um int
(float x int float), ela s realiza mesmo a primeira dessas operaes.
Como pode ser observado no exemplo, antes da chamada da operao de
soma, no segundo uso do operador +, ocorre uma chamada implcita a
uma funo capaz de converter valores do tipo int em valores do tipo flo-
at.
Em C, coeres ocorrem com grande frequncia em expresses e atribui-
es. O exemplo 7.7 mostra vrias coeres ocorrendo em atribuies.
main() {
int i;
char c = a;
float x;
i = c;
c = i + 1;
x = i;
i = x / 7;
}
Exemplo 7. 7 - Coeres em Atribuies em C
Na primeira atribuio do exemplo 7.7 o valor char de c convertido em
um int. Na segunda atribuio, o valor int de i convertido em um char.
Nesse caso, pode haver perda de informao pois os bits mais significati-
vos de i so descartados. Na penltima atribuio, o valor int de i con-
vertido em um float. Por sua vez, na ltima atribuio, o valor float de x
convertido em um int, atravs do truncamento do valor de x. Novamente
nesse caso pode haver perda de informao pois a parte fracionria des-
cartada e o valor inteiro de x pode no possuir correspondente no interva-
lo definido por int.
Existem posies controversas a respeito da incluso de coeres em LPs.
Enquanto elas tornam a LP mais redigvel (uma vez que no necessrio
chamar explicitamente as funes de converso), elas podem impedir a
deteco de certos tipos de erros por parte do compilador, o que pode re-
duzir a confiabilidade dos programas criados na linguagem. Considere o
exemplo 7.8 em C.
main() {
223
int a, b = 2, c = 3;
float d;
d = a * b; // d = (float) (a * b) ;
a = b * d; // a = b * c;
}
Exemplo 7. 8 - Diferentes Perspectivas sobre Coeres
No exemplo 7.8, caso no houvesse coero de int para float, na primeira
atribuio, o programador teria de converter explicitamente (atravs do
uso do operador de cast) o resultado da multiplicao para float. Isso
reduziria a redigibilidade da linguagem e tornaria a programao enfado-
nha. Por outro lado, se na segunda atribuio o programador desejasse
multiplicar b e c, mas tivesse digitado equivocadamente d, o compilador
C no poderia fornecer qualquer auxlio, uma vez que a converso impl-
cita de tipos legal.
Por causa dos problemas de confiabilidade ADA e MODULA-2 no ad-
mitem coeres, sacrificando assim a redigibilidade. C adota uma postura
bastante permissiva em relao a coeres. JAVA busca um meio termo
s admitindo a realizao de coeres para tipos mais amplos. O exemplo
7.9 ilustra a postura adotada por JAVA.
byte a, b = 10, c = 10;
int d;
d = b;
c = (byte) d;
a = (byte) (b + c);
Exemplo 7. 9 Coero e Converso Explcita em JAVA
No exemplo 7.9 a coero do valor byte de b para o valor int de d na pri-
meira atribuio vlida porque ocorre uma ampliao. As converses
explcitas nas demais atribuies so obrigatrias pois h estreitamento.
Observe, na ltima linha do exemplo, a existncia de coeres dos valores
de b e c para valores int antes da realizao da operao de soma. O resul-
tado dessa operao ser do tipo int e, portanto, deve ser convertido ex-
plicitamente em um byte.
7.2.2 Sobrecarga
Um identificador ou operador sobrecarregado quando pode ser usado
para designar duas ou mais operaes distintas. Em geral, sobrecarga (ou
overloading, como mais conhecida) somente aceitvel quando o
uso do operador ou identificador no ambguo, isto , quando a opera-
o apropriada pode ser identificada usando-se apenas as informaes
disponveis a respeito dos tipos dos operandos. Veja, por exemplo, o caso
224
do operador de C. Ele pode ser usado para denotar um nmero signifi-
cativamente elevado de operaes, tais como:
negaes inteiras (int int ou short short ou long long)
negaes reais (float float ou double double)
subtraes inteiras (int x int int)
subtraes reais (float x float float)
O conjunto de operaes associadas ao operador pode ser muito maior
que o listado acima, uma vez que se pode associar a ele, para cada tipo
distinto de dados numrico, uma operao de negao e uma de subtra-
o.
Tal como a coero, a sobrecarga tambm sugere que determinada opera-
o ou subprograma pode ser realizada com operandos de tipos diferen-
tes. Contudo, no isso que ocorre. O exemplo 7.10 em C ilustra esse
fato.
main(){
int a = 2, b = 3;
float x = 1.5, y = 3.4;
a = a + b; // a = somaint (a, b);
x = x + y; // x = somafloat (x, y);
}
Exemplo 7. 10 - Sobrecarga de Operador + em C
Embora a sobrecarga do operador + no exemplo 7.10 possa dar a impres-
so, para quem cria ou l esse trecho de programa, que esse operador re-
presenta uma funo capaz de realizar tanto a operao de somar dois va-
lores do tipo int (int x int int), quanto a operao de somar dois valores
do tipo float (float x float float), na realidade, cada ocorrncia de +
invoca funes especficas para cada uma dessas operaes.
MODULA-2 e C embutem sobrecarga em seus operadores, mas os pro-
gramadores s podem usar essa sobrecarga, sem poder implementar no-
vas sobrecargas. Alm disso, no existe qualquer sobrecarga de subpro-
gramas. PASCAL adota postura similar. No entanto, tambm existem
subprogramas sobrecarregados na biblioteca padro, tais como read e
write. JAVA embute sobrecarga em operadores e em subprogramas de
suas bibliotecas, mas somente subprogramas podem ser sobrecarregados
pelo programador. ADA e C++ adotam a postura mais ampla e ortogonal
realizando e permitindo que programadores realizem sobrecarga de sub-
programas e operadores.
ADA e C++ permitem aos programadores sobrecarregar os operadores da
linguagem, mas no admitem a criao de novos operadores, isto , ope-
radores diferentes dos existentes na LP. Alm disso, quando feita a so-
225
brecarga dos operadores, sua sintaxe e precedncia no pode ser alterada.
O motivo para essas restries evitar a criao de ambigidades.
Nem todos operadores de C++ podem ser sobrecarregados. Por exemplo,
os operadores :: (resoluo de escopo), . (seleo de membro), sizeof (ta-
manho do objeto/tipo) no podem ser sobrecarregados. Os motivos para
no permitir a sobrecarga desses operadores so evitar a quebra de meca-
nismos de segurana, o surgimento de confuses e dificuldades no enten-
dimento do cdigo. O exemplo 7.11 ilustra a sobrecarga de subprogramas
e operadores em C++.
class umValor {
int v;
public:
umValor() { v = 0; }
umValor(int j) { v = j; }
const umValor operator+(const umValor& u)const {
return umValor(v + u.v);
}
umValor& operator+=(const umValor& u) {
v += u.v;
return *this;
}
const umValor& operator++() { // prefixado
v++;
return *this;
}
const umValor operator++(int) { // posfixado
umValor antes(v);
v++;
return antes;
}
};
main() {
int a = 1, b = 2, c = 3;
c += a +b;
umValor r(1), s(2), t;
t += r + s;
r = ++s;
s = t++;
}
Exemplo 7. 11 - Sobrecarga em C++ (adaptado de [Eckel, 2000])
O exemplo 7.11 mostra a sobrecarga da funo construtora umValor e dos
operadores +, += e ++ na classe umValor. Sobrecarga de subprogramas
226
muito til em linguagens orientadas a objeto para permitir a criao de
objetos segundo diferentes contextos. Observe, na terceira linha da funo
main do exemplo, que t criada com o construtor default de umValor
e que r e s so criados usando a outra funo umValor, a qual parame-
trizada.
O exemplo 7.11 tambm mostra a sintaxe utilizada por C++ para o pro-
gramador sobrecarregar operadores. Ela envolve o uso da palavra opera-
tor@, onde @ o operador a ser sobrecarregado. Note como esses opera-
dores so usados em main da mesma maneira que seus equivalentes j
pr-definidos. Note tambm que as funes de sobrecarga dos operadores
binrios s requerem um parmetro, uma vez que o outro o prprio ob-
jeto, o qual ser o argumento da esquerda nas expresses que aplicam o
operador.
O exemplo 7.11 mostra ainda a notao sinttica adotada em C++ para
permitir a sobrecarga dos operadores unrios ++ e --. Como esses opera-
dores podem ser usados de maneira prefixada ou posfixada, necessrio
haver um meio de definir qual modo a ser usado. C++ usa uma notao
sinttica no mnimo esquisita para resolver isso. Se a lista de parmetros
da funo de sobrecarga for vazia, adota-se a forma prefixada. J se a lista
conter um parmetro fictcio do tipo int, adota-se a forma posfixada. Esse
claramente um exemplo de soluo contra-intuitiva uma vez que no
fica claro porque o operador posfixado tem um parmetro fictcio e o pre-
fixado no.
C++ e JAVA usam sobrecarga independente do contexto. Em outras pa-
lavras, o tipo de retorno no pode ser usado para diferenciar uma funo
de outra. Assim, todo e qualquer operador, ou identificador de subpro-
grama, sobrecarregado deve possuir uma lista de parmetros diferenciada
em nmero ou tipo dos parmetros.
O exemplo 7.12, em C++, ajuda entender a sobrecarga independente de
contexto. Todas as funes f definidas no exemplo 7.12 possuem listas de
parmetros distintas, exceto aquela colocada na linha comentada. Caso
no fosse assim, essa linha causaria ambiguidade na primeira chamada de
funo em main, impedindo o compilador de determinar se a funo a ser
chamada a que retorna void ou a que retorna int. A sobrecarga indepen-
dente de contexto adotada exatamente para evitar esses problemas. As-
sim, os compiladores sempre reportam um erro quando o programador
cria funes sobrecarregadas cuja diferenciao feita pelo tipo de retor-
no.
void f(void) { }
void f(float) { }
void f(int, int) { }
227
void f(float, float) { }
// int f(void) { }
main() {
f();
f(2.3);
f(4, 5);
f(2.2f, 7.3f);
// f(3, 5.1f);
// f(1l, 2l);
}
Exemplo 7. 12 - Sobrecarga Independente de Contexto em C++
No exemplo 7.12 as funes chamadas so aquelas cuja lista de parme-
tros corresponde exatamente ao nmero e tipo dos parmetros passados
na chamada, com exceo da segunda chamada e das duas ltimas colo-
cadas em linhas comentadas. No caso da segunda chamada de f, o argu-
mento passado um valor do tipo double. Como no existe qualquer de-
clarao de f cuja lista de parmetros seja composta por um valor desse
tipo, o compilador de C++ verifica se possvel realizar uma coero
desse valor para um tipo de modo a fazer um casamento com alguma de-
finio de f. Dessa maneira, a funo f(float) chamada. Por outro lado,
esse mesmo processo no bem sucedido nas duas chamadas feitas em
linhas comentadas, pois h ambigidade causada pela combinao de so-
brecarga com coero. Em ambas o compilador no consegue determinar
se deve chamar a funo f(int, int) ou f(float, float). Observe ainda que
esse problema tambm ocorreria caso o especificador f de literal float fos-
se omitido na chamada f(2.2f, 7.3f).
ADA adota sobrecarga dependente de contexto. Isso significa que cada
definio de funo sobrecarregada deve possuir uma assinatura diferen-
ciada. Tal exigncia se contrasta com o outro tipo de sobrecarga, o qual
requer uma lista de parmetros distinta. Assim, na sobrecarga dependente
de contexto, as funes podem ter o mesmo nmero e tipos de parmetros
e se distingirem apenas pelo tipo do valor retornado pela funo. A so-
brecarga dependente de contexto exige mais esforo dos compiladores e
pode provocar erros de ambigidade mesmo em LPs nas quais no h co-
ero.
Existe controvrsia quanto a possibilidade dos programadores sobrecarre-
garem os operadores de uma LP. Nem todos consideram uma caractersti-
ca desejvel. Enquanto alguns consideram que programas podem ficar
mais fceis de serem lidos e redigidos com a sobrecarga, outros conside-
ram que, alm de aumentar a complexidade da LP, esse recurso frequen-
temente mal utilizado e acaba levando a elaborao de programas mais
difceis de serem lidos e entendidos. Os criadores de JAVA resolveram
228
no incluir a sobrecarga de operadores por consider-la capaz de gerar
confuses e aumentar a complexidade da LP.
7.2.3 Paramtrico
Nesse tipo de polimorfismo, pode-se construir abstraes de dados e con-
trole que atuam uniformemente sobre valores de vrios tipos. A principal
caracterstica desse polimorfismo a parametrizao das estruturas de
dados e subprogramas com relao ao tipo do elemento sobre o qual ope-
ram. Pode-se dizer, ento, que essas abstraes recebem um parmetro
implcito adicional especificando o tipo sobre o qual elas agem. O exem-
plo 7.13 mostra a implementao de uma funo em C que aceita um in-
teiro e simplesmente retorna ele mesmo.
int identidade (int x) {
return x;
}
Exemplo 7. 13 - Funo Especfica para Tipo Inteiro
A chamada identidade (12) produziria 12. J a chamada identidade (7.5)
retornaria 7, um valor incorreto, visto que C converteria implicitamente o
argumento em um inteiro. Em outras LPs menos permissivas com relao
a coeres (MODULA-2, por exemplo) essa chamada seria ilegal, geran-
do erro de compilao, porque o argumento no do tipo inteiro.
Contudo, essa postura no parece ser muito adequada. Semanticamente, a
funo identidade pode ser aplicada a valores de qualquer tipo. De fato,
no existe qualquer operao especfica de inteiros no corpo dessa fun-
o. Seria mais razovel, ento, poder escrever a funo identidade tal
como no exemplo 7.14.
T identidade (T x) {
return x;
}
Exemplo 7. 14 - Funo Genrica
Essa funo do tipo T T no qual T uma varivel tipo pois pode
designar um tipo qualquer. Agora a chamada identidade (7.5) produziria
o valor esperado. Mais que isso, em uma LP na qual existisse o tipo
boolean, por exemplo, a chamada identidade(true) tambm seria vlida e
legal.
Chamadas da funo identidade podem assumir tipos diferentes. Esse tipo
pode ser determinado combinando o tipo dos valores dos argumentos com
os tipos declarados no cabealho da funo. Por exemplo, na chamada
identidade(true) combina-se o tipo do argumento boolean, com o tipo da
funo T T, substituindo sistematicamente o T pelo boolean. Portanto,
229
o tipo do resultado ser boolean (e seu valor ser true) e o tipo dessa
chamada de identidade boolean boolean.
Um tipo como T T chamado de politipo porque a partir dele se pode
derivar uma famlia de muitos tipos. No existe qualquer impedimento
em se usar mais de uma varivel tipo em um politipo. Por exemplo, o po-
litipo U x T T possui duas variveis tipo e pode ser associado com fun-
es de diversas assinaturas, tais como, int x float float, float x int int
e int x int int.
C++ usa o mecanismo de template para incorporar o polimorfismo
paramtrico. Observe como a funo identidade pode ser implementada
em C++ no exemplo 7.15.
template <class T>
T identidade (T x) {
return x;
}
class tData {
int d, m, a;
};
main () {
int x;
float y;
tData d1, d2;
x = identidade (1);
y = identidade (2.5);
d2 = identidade (d1);
// y = identidade (d2);
}
Exemplo 7. 15 - Funo Genrica em C++
A funo identidade foi usada na funo main para denotar funes do
tipo (int int), (float float) e (data data). A chamada de identida-
de na linha comentada provocaria erro de compilao. Isso ocorreria por-
que ela retorna um valor do tipo data, mas a atribuio demanda um float.
Nem todas funes baseadas em polimorfismo paramtrico so to gen-
ricas quanto a funo identidade. Muitas vezes, os tipos aplicveis a essas
funes so limitados por operaes realizadas no corpo da funo. Nes-
ses casos, o programador deve levar isso em conta durante o uso da fun-
o para evitar a ocorrncia de erros. O exemplo 7.16 mostra a funo
maior, em C++, a qual usa o mecanismo template para retornar o maior
entre dois valores de um tipo. Para poder ser usada, o tipo do valor dos
argumentos deve ter uma operao associada ao operador >, uma vez que
maior vai us-la para identificar o maior dos dois valores.
230
template <class T>
T maior (T x, T y) {
return x > y ? x : y;
}
class tData {
int d, m, a;
};
main ( ) {
tData d1, d2;
printf ("%d", maior (3, 5));
printf ("%f", maior (3.1, 2.5));
// d1 = maior (d1, d2);
}
Exemplo 7. 16 Funo Genrica Restrita a Certos Tipos em C++
As duas primeiras chamadas de maior no exemplo 7.16 so vlidas por-
que o operador > pr-definido para valores dos tipos int e float. Contu-
do, a chamada na linha comentada provocaria erro de compilao porque
o operador > no est definido para a classe tData. Esse erro no ocorre-
ria caso existisse uma sobrecarga do operador > para a classe tData.
Alm de subprogramas com polimorfismo paramtrico, tambm poss-
vel criar estruturas de dados parametrizadas em relao ao tipo dos seus
elementos. Para criar essas estruturas fundamental o conceito de tipo
parametrizado, isto , um tipo cuja definio parametrizada em relao
a outros tipos. Tipos parametrizados frequentemente existem em LPs cujo
sistema de tipos inclui tipos compostos. Com ele se pode especificar pro-
priedades de arquivos, vetores e conjuntos sem se preocupar com os tipos
reais de seus componentes. Os tipos file, array e set de PASCAL e array
de ADA so parametrizados porque possvel criar a partir deles tipos
cujos elementos so inteiros, reais, caracteres, etc.
Contudo, em uma linguagem basicamente monomrfica, somente existem
tipos parametrizados pr-definidos. O programador no pode definir no-
vos tipos parametrizados. C++ tambm usa o mecanismo template para
permitir a criao de tipos parametrizados pelos programadores. O exem-
plo 7.17 mostra a definio e uso do tipo parametrizado tPilha.
template <class T, int tam>
class tPilha {
T elem[tam];
int topo;
public:
tPilha(void) { topo = -1; }
int vazia (void) { return topo == -1; }
231
void empilha (T);
void desempilha (void);
T obtemTopo (void);
};
template <class T, int tam>
void tPilha<T, tam>::empilha (T el){
if (topo < tam-1)
elem[++topo] = el;
}
template <class T, int tam>
void tPilha<T, tam>::desempilha (void){
if (!vazia()) topo--;
}
template <class T, int tam>
T tPilha<T, tam>::obtemTopo (void) {
if (!this->vazia()) return elem[topo];
}
class tData {
int d, m, a;
};
void main () {
tData d1, d2;
tPilha <int, 3> x;
tPilha <tData, 2> y;
x.empilha (1);
y.empilha (d1);
x.empilha (3);
y.empilha (d2);
while (!x.vazia() || !y.vazia()) {
x.desempilha();
y.desempilha();
}
}
Exemplo 7. 17 - Classe Genrica em C++
O mecanismo de template usado no exemplo 7.17 parametriza a classe
tPilha pelo tipo T do seu elemento e tambm pelo valor tam do nmero
mximo de elementos que podem ser colocados nas pilhas. A varivel
tipo T tambm usada na declarao e definio das operaes empilha e
obtemTopo de tPilha. Em main, o tipo tPilha usado para criar uma pilha
capaz de armazenar at 3 inteiros e outra pilha capaz de armazenar at
dois objetos da classe tData.
232
A forma de implementao do mecanismo template em C++ curiosa.
Ao contrrio do que seria mais desejado (o reuso de cdigo fonte e obje-
to), esse mecanismo s possibilita o reuso de cdigo fonte. Isso significa
que no possvel compilar o cdigo usurio das funes ou classes defi-
nidas com polimorfismo paramtrico separadamente do cdigo de im-
plementao dessas funes ou classes. De fato, para compilar funes ou
classes paramtricas, o compilador necessita saber quais tipos sero asso-
ciados a elas. A partir de uma varredura do cdigo usurio, o compilador
identifica os tipos associados a essas funes e classes e replica todo o
cdigo de implementao para cada tipo utilizado, criando assim um c-
digo objeto especfico para cada tipo diferente utilizado.
A razo para isso tudo a necessidade do compilador C++ saber o tama-
nho e forma do tipo para gerar cdigo que aloque memria na pilha. A-
lm disso, para o compilador garantir que qualquer uso inadequado do
tipo ser detectado em compilao, ele necessita verificar se o tipo passa-
do compatvel com as operaes definidas nas implementaes das fun-
es paramtricas.
ADA tambm incorpora o polimorfismo paramtrico permitindo a defini-
o de pacotes genricos. Embora JAVA tenha reservado a palavra gene-
ric para uma futura incluso de polimorfismo paramtrico na LP, esse
conceito ainda no foi implementado.
7.2.4 Incluso
O polimorfismo por incluso caracterstico de linguagens orientadas a
objetos. Ele se baseia no uso de uma hierarquia de tipos para criar abstra-
es de dados e controle polimrficas. A idia fundamental do polimor-
fismo por incluso que elementos dos subtipos so tambm elementos
do supertipo (da o nome incluso). Assim, abstraes formadas a partir
do supertipo podem tambm envolver elementos dos seus subtipos.
Um subtipo S de um tipo T formado por um sub-conjunto dos valores
de T. Assim, todo valor de S deve ser tambm um valor de T. Adicional-
mente, cada uma das operaes associadas ao tipo T tambm deve ser
aplicvel ao subtipo S. Pode-se pensar, portanto, que S herda todas as o-
peraes do tipo T.
7.2.4.1 Herana
O conceito de tipo implementado atravs de classes em LPs orientadas a
objetos. Uma classe define uma representao dos dados (chamados de
atributos) e um conjunto de operaes (chamadas de mtodos). Objetos
so instncias de uma classe e correspondem aos valores do tipo definido
233
por ela. Subclasses herdam os atributos e mtodos de uma classe e, por-
tanto, implementam subtipos do tipo definido por essa classe.
Por exemplo, um objeto x tem um ou mais atributos e um ou mais mto-
dos definidos pela sua classe X. Um objeto y, declarado como sendo uma
instncia de uma subclasse Y de X, tambm possui todos os atributos e
mtodos de x. Alm disso, y pode ter alguns atributos e mtodos adicio-
nais.
Pode-se concluir, portanto, que o mecanismo de herana associa classe
herdeira uma representao inicial para os objetos dessa classe (os atribu-
tos herdados) e um conjunto inicial de mtodos aplicveis aos objetos
dessa classe (os mtodos herdados). A classe herdeira pode conter atribu-
tos e mtodos adicionais, especializando assim o estado e o comporta-
mento dos novos objetos a serem criados. O exemplo 7.18 mostra o uso
de herana em JAVA.
// Pessoa.java
public class Pessoa {
private String nome;
private int idade;
public Pessoa (String n, int i) {
nome = n;
idade = i;
}
public void aumentarIdade () {
idade++;
}
}
// Empregado.java
public class Empregado extends Pessoa {
private float salario;
public Empregado (String n, int i, float s) {
super(n, i);
salario = s;
}
public void mudarSalario (float s) {
salario = s;
}
}
// Cliente.java
public class Cliente {
public static void main(String[] args) {
Pessoa p = new Pessoa (Denise, 34);
p.aumentarIdade();
234
Empregado e1 = new Empregado (Rogerio, 28, 1000.00);
e1.mudarSalario(2000.00);
e1.aumentarIdade();
}
}
Exemplo 7. 18 - Herana em JAVA
No exemplo 7.18 so definidas trs classes chamadas Pessoa, Empregado
e Cliente, cada uma em seu arquivo .JAVA correspondente
7.2
. Uma vez
que todo empregado uma pessoa, a classe Empregado definida por
herana a partir da classe Pessoa. Como pode ser observado no exemplo,
alm dos atributos e mtodos herdados da classe Pessoa, a classe Empre-
gado ainda possui um novo atributo salario, um mtodo especfico para a
construo de objetos Empregado e outro para a mudana de seu salrio.
A classe Cliente usada apenas para definir a funo main, a qual um
programa para criar objetos Pessoa e Empregado e usar seus mtodos.
Uma das vantagens do mecanismo de herana aumentar a reusabilidade
do cdigo. No exemplo 7.18, a herana torna desnecessrio redefinir os
atributos e mtodos da classe Pessoa na classe Empregado. Destaque-se,
em particular, o mtodo aumentarIdade(), usado para mudar a idade de
uma Pessoa, e tambm aplicado para mudar a idade de um Empregado.
Isso mostra porque herana um exemplo de polimorfismo universal. Por
fim, cabe esclarecer que a chamada super(n,i); na primeira linha do cons-
trutor da classe Empregado, a forma adotada por JAVA para chamar o
construtor da classe herdada quando o mesmo possui parmetros.
7.2.4.1.1 Especificador de Acesso para Classes Herdeiras
A herana traz consigo uma demanda por um novo tipo de especificador
de acesso usado para possibilitar s classes herdeiras terem acesso livre
aos atributos da classe herdada sem que para isso seja necessrio tornar
esses atributos pblicos ou criar mtodos pblicos de acesso na classe
herdada.
Considere ainda o exemplo 7.18. Caso se desejasse criar uma funo em
Empregado para responder se um empregado j pode se aposentar, seria
necessrio acessar o campo idade definido em Pessoa. Contudo, isso no
possvel pois esse atributo foi declarado como private e, portanto, s
pode ser acessado diretamente pelos mtodos de Pessoa. A alternativa de
se criar mtodos pblicos de acesso a esse atributo em Pessoa pode no

7.2
Classes pblicas em JAVA devem ser definidas em um arquivo .JAVA de mesmo nome da classe.
Os prximos exemplos deixaro subentendido que cada classe pblica escrita em seu arquivo .JAVA
correspondente.
235
ser satisfatria porque isso significaria tornar esses mtodos disponveis
para mtodos de qualquer outra classe.
A soluo adotada por JAVA e C++ para esse problema foi criar um novo
especificador de acesso para os atributos da classe. Esse especificador foi
denominado como protected. Enquanto ele libera o acesso ao atributo da
classe para os mtodos das suas subclasses, ele ao mesmo tempo impede
o acesso para os mtodos de outras classes. O exemplo 7.19 ilustra o uso
desse especificador de acesso em JAVA.
public class Pessoa {
protected int idade;
}
public class Empregado extends Pessoa {
public Empregado (int i) { idade = i; }
public boolean aposentavel() {
if (idade >= 65) return true;
return false;
}
}
public class Cliente {
public static void main(String[] args) {
Empregado e = new Empregado (32);
if (e.aposentavel()) System.out.println(Chega de trabalho!);
// e.idade = 70;
}
}
Exemplo 7. 19 - Especificador de Acesso para Classes Herdeiras
No exemplo 7.19 a classe Pessoa possui um nico atributo idade declara-
do como sendo protected. Os dois mtodos da subclasse Empregado ago-
ra tm livre acesso ao atributo de Pessoa. No entanto, caso se tentasse
compilar a linha comentada, haveria erro de compilao uma vez que a
classe Cliente no uma subclasse
7.3
de Pessoa e, portanto, no pode a-
cessar o seu atributo protegido.
7.2.4.1.2 Inicializao de Atributos com Herana
Quando se cria um objeto de uma classe necessrio inicializar os seus
atributos e, em seguida, chamar o construtor dessa classe. Contudo, quan-
do essa classe uma subclasse, ele herda atributos que tambm precisam
ser inicializados. Isso precisa ser feito antes da chamada do contrutor da

7.3
Considera-se a classe Cliente como pertencente a outro pacote pois mtodos de classes de um pacote
tambm tm livre acesso aos atributos protegidos das demais classes do pacote.
236
classe. Dessa forma, o procedimento adotado na criao de um objeto de-
ve ser realizado na superclasse antes de ser feito na classe. Esse processo
deve ser aplicado recorrentemente para cada nvel de herana existente. O
exemplo 7.20 mostra uma situao na qual isso ocorre em JAVA.
class Estado {
Estado(String s) {
System.out.println(s);
}
}
class Pessoa {
Estado p = new Estado("Ativo");
Pessoa () {
System.out.println("Pessoa");
}
}
class Idoso extends Pessoa {
Estado i = new Estado("Sabio");
Idoso () {
System.out.println("Idoso ");
}
}
class Avo extends Idoso {
Estado a1 = new Estado ("Alegre");
Avo() {
System.out.println("Avo");
a3 = new Estado ("Orgulhoso");
}
Estado a2 = new Estado ("Amigo");
void fim() {
System.out.println("Fim");
}
Estado a3 = new Estado ("Satisfeito");
}
public class Inicializacao {
public static void main(String[] args) {
Avo a = new Avo();
a.fim();
}
}
Exemplo 7. 20 - Inializao com Herana
O resultado obtido no exemplo 7.20 ser a impresso linha a linha da se-
qncia Ativo Pessoa Sabio Idoso Alegre Amigo Satisfeito Avo Orgulhoso
237
Fim. Um detalhe importante diz respeito ordem de inicializao dos a-
tributos da classe Avo. Em JAVA (e C++) eles so inicializados na ordem
na qual foram declarados. Embora o processo de inicializao do exemplo
7.20 tenha sido simples e fcil de entender, isso nem sempre ocorre as-
sim. Outros fatores podem ser complicadores, tais como a existncia de
atributos de classe e a necessidade de passar argumentos para os constru-
tores.
7.2.4.1.3 Sobrescrio
Em algumas situaes o mtodo herdado de uma classe pode no ser o
mais adequado para realizar a mesma operao nos objetos de suas sub-
classes. Para lidar com essas situaes, LPs orientadas a objetos permitem
que os mtodos de uma classe sejam sobrescritos em suas subclasses. As-
sim, pode-se criar, em uma subclasse, um novo mtodo com a mesma in-
terface (nome, parmetros e resultado) do mtodo na classe herdada. Se
um mtodo sobrescrito em uma subclasse, ento o novo mtodo vis-
vel para os usurios dessa classe. O exemplo 7.21 mostra o uso do meca-
nismo de sobrescrita em JAVA.
class XY {
protected int x = 3, y = 5;
public int soma () {
return x + y;
}
}
class XYZ extends XY {
int z = 17;
public int soma () {
return x + y + z;
}
}
public class Sobrescrita {
public static void main(String[] args) {
XYZ xyz = new XYZ();
System.out.println(xyz.soma());
}
}
Exemplo 7. 21 - Mtodo Sobrescrito em JAVA
O mtodo soma da classe XY sobrescrito na classe XYZ. Assim, quando
objetos da classe XYZ (tal como xyz no mtodo main da classe Sobrescri-
ta) chamam o mtodo soma, o mtodo da classe XYZ ser chamado ao
invs do mtodo homnimo que seria herdado.
238
Muitas vezes, s se necessita estender a funcionalidade produzida pelo
mtodo sobrescrito de modo a complementar a funcionalidade com o que
especfico da classe herdeira. Para evitar repetio de cdigo, possvel
invocar o mtodo sobrescrito de dentro do mtodo da subclasse. O exem-
plo 7.22 mostra como isso pode ser feito no mtodo soma da classe XYZ,
apresentada no exemplo 7.21.
class XYZ extends XY {
int z = 17;
public int soma () {
return super.soma() + z;
}
}
Exemplo 7. 22 - Extenso de Mtodo
Para permitir a chamada do mtodo sobrescrito, necessrio utilizar o
nome super no mtodo da subclasse. Caso contrrio, o compilador enten-
deria como sendo uma chamada recursiva do prprio mtodo que est
sendo definido. C++ usa uma sintaxe baseada no operador de resoluo
de escopo :: para oferecer essa facilidade. Portanto, a chamada equivalen-
te ao super.soma() de JAVA seria XY::soma() em C++
A sobrescrio de mtodos uma outra maneira de se fazer polimorfismo
de sobrecarga, uma vez que existe uma implementao especfica do m-
todo para cada classe. O fato da lista de parmetros dos mtodos sobres-
critos ser aparentemente a mesma nas diferentes implementaes pode ser
fonte de confuso. Contudo, no se pode esquecer a existncia de um pa-
rmetro implcito passado para os mtodos. Esse parmetro o prprio
objeto sobre o qual o mtodo vai ser aplicado. Desse modo, a lista de pa-
rmetros de cada mtodo diferenciada, caracterizando ainda mais o po-
limorfismo de sobrecarga.
7.2.4.2 Identificao Dinmica de Tipos
O polimorfismo por incluso permite ainda a elaborao de trechos de
cdigo polimrficos nos quais mtodos invocados por um mesmo refe-
renciador de objetos se comportam de maneira diferenciada de acordo
com o tipo verdadeiro do objeto sendo manipulado naqueles trechos. Isso
pode ser til em situaes nas quais o programador desconhece o tipo
verdadeiro de um objeto.
Nessas situaes, objetos diferentes podem ser tratados de modo seme-
lhante. Tal caracterstica confere grande versatilidade as classes e pro-
gramas que lhes so beneficirios. No entanto, para oferecer isso, a LP
deve possuir um meio de identificar o tipo do objeto em tempo de execu-
239
o, ou seja, realizar identificao dinmica de tipos. Essa seo discute
como isso pode ser feito e utilizado.
7.2.4.2.1 Ampliao
Uma distino comum realizada em orientao a objetos entre membros
e instncias de uma classe. Instncias de uma classe so os objetos decla-
rados exclusivamente como sendo daquela classe. Membros de uma clas-
se so todas as instncias da classe e de suas subclasses. Considere, por
exemplo, um objeto x, instncia da classe X, e um objeto y, instncia da
classe Y, subclasse de X. Enquanto o nico membro da classe Y o objeto
y, a classe X possui x e y como membros.
Em orientao a objetos, sempre legal atribuir todos os membros de
uma classe a objetos declarados como sendo dessa classe. Assim, poss-
vel atribuir a objetos da classe X tanto objetos dessa classe (como x)
quanto da classe Y (como y). Por outro lado, no sempre possvel atribu-
ir a objetos da classe Y, objetos da classe X (por exemplo, x) uma vez que
eles no so membros dessa classe.
Ampliao (upcasting) o termo usado para descrever o movimento
de objetos na sua linha de ancestrais no sentido da subclasse para as su-
perclasses. Em outras palavras, a capacidade de uma instncia de uma
subclasse poder aparecer quando um membro de uma superclasse solici-
tado. O exemplo 7.23 usa as classes Pessoa e Empregado, definidas no
exemplo 7.18, para ilustrar a realizao de ampliao em JAVA.
public class Cliente {
public void paga (Pessoa pes) {}
public void contrata (Empregado emp) {}
public static void main(String[] args) {
Pessoa p = new Pessoa ("Lucas", 30);
Empregado e = new Empregado ("Luis", 23, 1500.00);
p = e;
// e = p;
Cliente c = new Cliente();
c.paga(e);
// c.contrata(p);
Exemplo 7. 23 - Ampliao em JAVA
A ampliao de um Empregado para uma Pessoa sempre legal e ocorre
tanto na atribuio quanto na chamada do mtodo paga. J as duas linhas
comentadas so ilegais porque uma Pessoa no pode aparecer quando se
espera um Empregado. O compilador de JAVA geraria erros de compila-
o caso elas no estivessem como comentrios.
240
A ampliao uma operao sempre vlida pois garante a segurana do
sistema de tipos. Ao se mover para cima na linha de ancestrais se conver-
ge para uma classe mais geral. A nica perda possvel no se poder mais
usar os mtodos especficos da subclasse. O objeto s poder usar os m-
todos da superclasse, os quais so sempre legalmente aplicveis para os
objetos das subclasses. Por causa dessa segurana, os compiladores de
LPs orientadas a objetos permitem a realizao de ampliao sem a ne-
cessidade de uma operao explcita de converso de tipos.
C++ tambm permite a realizao de ampliao, mas s quando se usa
ponteiros ou referncias para os objetos. O exemplo 7.24 mostra um tre-
cho de cdigo em C++ no qual ocorre ampliao.
Pessoa p, *q;
Empregado e, *r;
q = r;
// r = q;
// p = e;
// e = p;
Exemplo 7. 24 - Ampliao em C++
A atribuio do ponteiro r para o ponteiro q uma ampliao vlida. J as
trs linhas comentadas no exemplo 7.24 so ilegais. Na primeira delas,
embora se use ponteiros, o objeto apontado por q no membro de Em-
pregado e, portanto, no pode ser apontado por r. Na linha seguinte, em-
bora seja uma operao de ampliao, ela no vlida pois no se trata de
uma atribuio de ponteiros. Na ltima linha, a atribuio no vlida
porque no se tem ampliao e tambm porque no se trata de uma ope-
rao sobre ponteiros.
O motivo dessa limitao em C++ o mecanismo de cpia de objetos
utilizado pela operao de atribuio e para passagem de parmetros por
valor. Se fosse permitido fazer esse tipo de cpia em uma operao de
ampliao haveria perda de informao quando se atribusse um objeto da
subclasse a um da superclasse (os campos adicionais da subclasse seriam
perdidos).
7.2.4.2.2 Amarrao Tardia de Tipos
Considere uma situao, durante a execuo de um programa, na qual j
tenha havido uma operao de ampliao de um objeto y de uma classe Y
para um objeto x da superclasse X. Considere tambm que um mtodo da
superclasse X tenha sido sobrescrito em Y. A chamada desse mtodo atra-
vs de x invoca a execuo do mtodo definido em Y, ao invs do defini-
do em X. O mecanismo pelo qual esse processo realizado se chama a-
241
marrao tardia de tipos. O exemplo 7.25 mostra um trecho de cdigo em
JAVA no qual isso ocorre.
class Pessoa {
String nome;
int idade;
public Pessoa (String n, int i) {
nome = n;
idade = i;
}
public void aumentaIdade () { idade++; }
public void imprime(){
System.out.print(nome + " , " + idade);
}
}
class Empregado extends Pessoa {
float salario;
Empregado (String n, int i, float s) {
super(n, i);
salario = s;
}
public void imprime(){
super.imprime();
System.out.print(" , " + salario);
}
}
public class Cliente {
public static void main(String[] args) {
Pessoa p = new Empregado (Rogerio, 28, 1000.00);
p.aumentaIdade();
p.imprime();
}
}
Exemplo 7. 25 - Amarrao Tardia de Tipos em JAVA
O programa do exemplo 7.25 cria um objeto da classe Empregado e o
amplia imediatamente para a superclasse Pessoa. Ento, o mtodo au-
mentarIdade, definido em Pessoa e herdado por Empregado, chamado.
Em seguida, o mtodo imprime de Empregado invocado, muito embora
isso seja feito atravs de p, o qual foi declarado como sendo da classe
Pessoa.
Note que a deciso tomada a respeito de qual mtodo executar, quando
imprime chamado, s pode ser tomada em tempo de execuo. Se essa
deciso tivesse de ter tomada em tempo de compilao, como p foi decla-
242
rado como da classe Pessoa, o mtodo invocado teria de ser o dessa clas-
se pois o compilador no teria como saber para qual tipo p estaria refe-
renciando no momento da execuo.
importante entender como funciona o mecanismo de amarrao tardia
de tipos. A figura 7.2 ilustra como esse mecanismo pode ser implementa-
do
7.4
.








Figura 7. 2 - Mecanismo de Amarrao Tardia de Tipos
A figura 7.2 retrata o estado do programa do exemplo 7.25 aps a execu-
o da linha na qual o objeto da classe Empregado criado. A varivel p,
declarada como Pessoa, alocada na base da pilha de registros de ativa-
o e aponta para o objeto recm-criado da classe Empregado. Alm dos
atributos de sua classe, esse objeto possui uma referncia (indicada na
figura pelo nome pclasse) para a tabela de mtodos de sua classe (no ca-
so, Empregado). Essa tabela, por sua vez, tambm possui uma referncia
(indicada na figura pelo nome psuper) para a tabela de mtodos de sua
superclasse. Isso se repete sucessivamente, at que se chegue a uma clas-
se sem superclasse. Quando um mtodo invocado por p, essa cadeia de
ponteiros seguida at chegar a primeira tabela de mtodos da classe na
qual esse mtodo foi definido. Assim, na chamada do mtodo aumentaI-
dade, o mtodo invocado o de Pessoa e na chamada do mtodo impri-
me, o mtodo invocado o de Empregado.
O mecanismo de amarrao tardia de tipos oferece maior flexibilidade
para a escrita de cdigo reutilizvel. Contudo, a eficincia computacional
se reduz quando comparada com a obtida com amarrao esttica. Na
amarrao tardia necessrio manter essa cadeia de ponteiros e segui-la
em todas as chamadas de mtodos. Nada disso necessrio quando o
subprograma a ser chamado definido em tempo de compilao.

7.4
Essa no a nica forma possvel de implementar o mecanismo de amarrao tardia de tipos.
p
PESSOA
psuper
aumentarIdade
imprime
EMPREGADO
psuper
imprime
pclasse
nome:Rogerio
idade: 28
salario: 1000.00
243
Por no desejar comprometer a eficincia, C++ adota uma postura dife-
rente da de JAVA. Em C++, o implementador da classe pode decidir se
deseja o uso de amarrao tardia ou no para cada mtodo da classe. Para
o mtodo ter amarrao tardia, a sua declarao deve vir precedida da pa-
lavra virtual. Se essa palavra omitida, a amarrao esttica. Em C++,
portanto, o programador deve decidir caso a caso se deseja versatilidade
ou eficincia. O exemplo 7.26 mostra a definio de uma classe com m-
todos que utilizam amarrao esttica e amarrao tardia em C++.
class Pessoa {
public:
void ler(){}
virtual void imprimir() {}
};
Exemplo 7. 26 - Amarrao Esttica e Amarrao Tardia em C++
No exemplo 7.26, o mtodo ler amarrado estaticamente e o mtodo im-
primir amarrado tardiamente. Sempre que um ponteiro para a classe
Pessoa invocar ler executada a funo definida em Pessoa, independen-
temente do tipo do objeto que est sendo apontado. Por outro lado, sem-
pre que imprimir for invocado, o mtodo executado ser o definido na
classe do objeto sendo apontado.
A amarrao tardia de tipos possibilita a criao de cdigo usurio com
polimorfismo universal, isto , cdigo usurio capaz de operar uniforme-
mente sobre objetos de tipos diferentes. O exemplo 7.27 ilustra essa pos-
sibilidade.
class Militar {
void operacao(){}
}
class Exercito extends Militar {
void operacao(){System.out.println(Marchar);}
}
class Marinha extends Militar {
void operacao(){System.out.println(Navegar);}
}
class Aeronautica extends Militar {
void operacao(){System.out.println(Voar);}
}
public class Treinamento {
public static void treinar(Militar[] m) {
for (int i = 0; i < m.length; i++) {
m[i].operacao();
}
244
}
public static void main (String[] args) {
Militar[] m = new Militar[] {
new Exercito(), new Marinha(),
new Aeronautica(), new Militar()
}
treinar(m);
}
}
Exemplo 7. 27 - Amarrao Tardia e Polimorfismo Universal
O mtodo de classe treinar da classe Treinamento polimrfico universal
pois opera sobre qualquer elemento do vetor m independentemente desse
elemento ser um objeto da classe Exercito, Marinha, Aeronautica ou
mesmo Militar.
7.2.4.2.3 Classes Abstratas
Classes abstratas possuem membros, mas no possuem instncias. Embo-
ra no se possa criar instncias de classes abstratas, elas possuem como
membros as instncias de suas subclasses no abstratas (tambm chama-
das de classes concretas). Portanto, uma classe abstrata deve ser necessa-
riamente estendida, ou seja, deve ser uma classe herdada por outra, mais
especfica, contendo os detalhes no includos na classe abstrata.
Classes abstratas so especialmente teis quando uma classe, ancestral
comum para um conjunto de classes, se torna to geral a ponto de no ser
possvel ou razovel ter instncias dessa classe. O exemplo 7.27 um ca-
so tpico no qual uma classe abstrata seria til. A classe Militar s existe
para especificar as caractersticas comuns de suas subclasses Exercito,
Marinha e Aeronautica. Qualquer membro de Militar deve necessaria-
mente ser uma instncia dessas classes pois s existem militares nessas
trs foras armadas. O exemplo 7.28 mostra como a classe Militar pode-
ria ter sido definida como abstrata no exemplo 7.27. Para tornar uma clas-
se abstrata em JAVA basta incluir a palavra abstract como prefixo da de-
finio.
abstract class Militar {
void operacao(){}
}
Exemplo 7. 28 - Classe Abstrata em JAVA
Com essa definio de Militar no mais possvel criar instncias dessa
classe. Assim, ocorreria erro ao se tentar compilar a classe Treinamento
245
do exemplo 7.27, uma vez que a funo main dessa classe tentaria criar
uma instncia de Militar.
Classes abstratas normalmente possuem um ou mais mtodos abstratos,
isto , mtodos declarados, mas no implementados. A implementao
desses mtodos deixada para as suas subclasses. Por exemplo, o mtodo
operacao de Militar deveria ser abstrato, uma vez que no possui qual-
quer instruo. O exemplo 7.29 mostra como isso pode ser feito em JA-
VA.
abstract class Militar {
abstract void operacao();
}
Exemplo 7. 29 - Classe Abstrata com Mtodo Abstrato em JAVA
O fato de classes abstratas poderem possuir mtodos abstratos no signi-
fica que classes abstratas no podem possuir mtodos definidos e atribu-
tos prprios. De fato, classes abstratas podem at possuir construtores,
embora eles nunca possam ser chamados para criar instncias dessa clas-
se. Eles s podem ser chamados no momento da construo das instncias
das subclasses da classe abstrata. O exemplo 7.30 redefine a classe abstra-
ta Militar para incluir o atributo patente e o mtodo concreto (no abstra-
to) toString. Observe tambm a incluso do mtodo construtor (no abs-
trato) nessa nova definio.
abstract class Militar {
String patente;
Militar(String p) { patente = p; }
String toString() { return patente; }
abstract void operacao();
}
Exemplo 7. 30 - Classe Abstrata com Atributo, Mtodo Abstrato e Mtodo Concreto
Em JAVA, possvel, mas no comum, criar classes abstratas nas quais
nenhum mtodo abstrato. J em C++, para uma classe ser abstrata,
obrigatrio possuir pelo menos um mtodo abstrato. De fato, a presena
de um mtodo abstrato caracteriza a classe como abstrata. A sintaxe ado-
tada por C++ para definir um mtodo abstrato tambm estranha, como
pode ser vista no exemplo 7.31.
class Militar {
public:
virtual void operacao()=0;
void imprime { cout << Militar; }
}
Exemplo 7. 31 - Classe Abstrata em C++
246
A classe Militar do exemplo 7.31 abstrata porque possui o mtodo abs-
trato operacao. A indicao de que operacao um mtodo abstrato se d
pela incluso da terminao =0. Mtodos abstratos em C++ devem neces-
sariamente ser declarados como virtuais, uma vez que o seu comporta-
mento ter de ser definido nas subclasses da classe abstrata. A classe
Militar do exemplo 7.31 tambm possui o mtodo concreto, e no virtual,
imprime.
Classes abstratas so um meio conveniente para disciplinar a construo
de classes. Para cada mtodo abstrato em uma classe abstrata, todas as
suas subclasses devem implementar esse mtodo ou tambm devem ser
abstratas.
Afim de exemplificar a convenincia das operaes abstratas, considere
uma aplicao em JAVA envolvendo um conjunto de formas geomtricas
que devem ser mostradas na tela do vdeo. Diferentes algoritmos so ne-
cessrios para mover as diferentes formas. De modo a impor a necessida-
de de operaes distintas para cada tipo de forma, uma classe abstrata
Forma pode ser criada, tal como no exemplo 7.32.
abstract class Forma {
abstract void mover(int dx, int dy);
abstract void desenhar();
}
Exemplo 7. 32 Especificao por Classe Abstrata
As vrias formas geomtricas da aplicao (por exemplo, Circulo,
Retangulo, Triangulo, etc.) podem ser implementadas a partir da classe
Forma. O exemplo 7.33 mostra a implementao de uma dessas
subclasses.
class Circulo extends Forma {
int x, y, raio;
Circulo (int x, int y, int r) {
this.x = x;
this.y = y;
raio = r;
}
void mover(int dx, int dy) {
x += dx;
y += dy;
}
void desenhar() {
System.out.println (Circulo:);
System.out.println ( Origem: ( + x + , + y +));
System.out.println ( Raio: + raio);
}
247
}
}
Exemplo 7. 33 - Implementao de Especificao por Classe Abstrata
Caso os mtodos mover e desenhar no fossem definidos na classe Circu-
lo, haveria erro de compilao, uma vez que uma subclasse de uma classe
abstrata deve necessariamente implementar os mtodos abstratos (no caso
de ser concreta) ou adiar a sua implementao (declarando-se, e declaran-
do os mtodos abstratos no implementados, como abstract).
A convenincia de disciplinar a construo de classes atravs de opera-
es abstratas to significativa que os projetistas de JAVA incluram o
conceito de interface
7.5
para designar classes abstratas nas quais todos os
mtodos so abstratos. Interfaces tambm no podem definir atributos de
objetos.
Pode-se resumir, portanto, como principais vantagens da utilizao de
classes abstratas a melhoria da organizao hierrquica de classes, atravs
do encapsulamento de atributos e mtodos na raiz da estrutura; a promo-
o de uma maior disciplina na programao, visto que fora o compor-
tamento necessrio nas suas subclasses; e o incentivo ao uso de amarra-
o tardia, permitindo um comportamento mais abstrato e genrico para
os objetos.
Deve-se ressaltar que nem todas as superclasses devem ser classes abstra-
tas. Por exemplo, poder-se-ia implementar uma classe Arco como uma
subclasse da classe Circulo. Instncias da classe Circulo so ainda teis,
apesar de se utilizar essa classe como superclasse da classe Arco. Alm
disso, tambm possvel criar subclasses abstratas de classes concretas.
Isso ilustra como uma hierarquia de classes pode crescer atravs do pro-
cesso de especializao das classes existentes. preciso atentar, contudo,
para o projeto dessa hierarquia para evitar torn-la mais complexa do que
o realmente necessrio.
7.2.4.2.4 Estreitamento
Estreitamento (downcasting) o termo usado para descrever a con-
verso de tipos de objetos no sentido da superclasse para as subclasses. A
operao de estreitamento no completamente segura para o sistema de
tipos porque um membro da superclasse no necessariamente do mesmo
tipo da subclasse para a qual se faz a converso. Em outras palavras, o
objeto sob o qual se far estreitamento pode ser uma instncia de uma
subclasse diferente daquela para a qual ser feito a converso. Nesse caso,

7.5
Outro motivo para a incluso desse conceito em JAVA ser visto ainda nesse captulo na seo de
herana mltipla.
248
as operaes especficas da nova classe do objeto sero incompatveis
com a classe original do objeto.
Por exemplo, se for feito o estreitamento de uma Forma, ela pode ser um
crculo, um quadrado, um tringulo, ou qualquer outra subclasse de For-
ma. Se a Forma for um Circulo e o estreitamento for feito para a classe
Quadrado, na hora de calcular a rea do quadrado, a operao no pode-
ria ser feita consistentemente visto que um crculo no possui lado. por
esses problemas de violao do sistema de tipos que algumas LPs no
permitem que se faa estreitamento e outras exigem que ele seja feito a-
travs de uma operao de converso explcita (cast).
JAVA somente permite a realizao de estreitamento atravs do uso de
uma converso explcita. Caso a converso seja feita entre classes no
pertencentes a uma mesma linha de descendncia na hierarquia, ocorrer
erro de compilao. Caso a converso seja na mesma linha de descendn-
cia, mas o objeto designado pela superclasse realmente pertena a outra
linha de descendncia, ocorrer uma exceo
7.6
em tempo de execuo.
Para que o programador possa evitar esse ltimo tipo de erro, JAVA ofe-
rece o operador instanceof, o qual permite testar dinamicamente se o ob-
jeto designado pela superclasse realmente da classe para a qual se dese-
ja fazer a converso. O exemplo 7.34 ilustra como isso feito em JAVA.
class UmaClasse {}
class UmaSubclasse extends UmaClasse {}
class OutraSubclasse extends UmaClasse{}
public class Estreitamento {
public static void main (String[] args) {
UmaClasse uc = new UmaSubclasse();
UmaSubclasse us = (UmaSubclasse) uc;
OutraSubclasse os;
// os = (OutraSubclasse) us;
// os = (OutraSubclasse) uc;
if (uc instanceof OutraSubclasse) os = (OutraSubclasse) uc;
}
}
Exemplo 7. 34 - Estreitamento em JAVA
A primeira linha da funo main de Estreitamento cria um objeto de u-
maSubclasse, o qual ampliado para a referncia uc de umaClasse. Na
linha seguinte feito o estreitamento de uc para us, declarado como uma-
Subclasse. Essa operao vlida pois umaClasse e umaSubclasse esto

7.6
Excees so discutidas com maior profundidade no captulo 8 desse livro.
249
na mesma linha de descendncia e porque uc est se referindo realmente a
uma instncia de umaSubclasse.
As duas linhas comentadas de main causariam erros caso no fossem co-
mentrios. Na primeira linha comentada, o erro seria de compilao pois
umaSubclasse e outraSubclasse no se encontram na mesma linha de
descendncia. Esse tipo de erro verificado pelo compilador da mesma
maneira como na verificao da validade da operao de ampliao. Isso
implica na necessidade do compilador poder acessar, durante a compila-
o, as informaes sobre todas as classes da linha de ascendncia de uma
classe usada no programa.
Na segunda linha comentada, o erro seria detectado em tempo de execu-
o, o que provocaria o disparo de uma exceo. Embora umaClasse e
outraSubclasse se encontrem na mesma linha de descendncia, a refern-
cia a umaClasse est designando realmente um objeto de umaSubclasse, o
que provocaria uma converso invlida. Note que esse tipo de erro s po-
de ser detectado em tempo de execuo porque, de modo geral, s se sabe
o tipo do objeto designado por uma referncia ao longo da execuo.
Erros tais como o mencionado no pargrafo anterior podem ser evitados
usando-se previamente o operador instanceof para testar se a converso
vlida, isto , se o objeto a ser convertido realmente membro da classe
para a qual ser feita a converso. O uso de instanceof ilustrado no co-
mando if na ltima linha de main.
recomendvel usar instanceof antes de um estreitamento especialmente
quando no se tem outra informao indicando o tipo de objeto sobre o
qual ser feita a operao. Caso isso no seja feito e a converso for inde-
vida, ocorrer a exceo ClassCastException.
Cabe ressaltar que a converso de um objeto pode ser feita ou testada para
cada classe da qual ele membro. Isso significa que o estreitamento de
um objeto no precisa necessariamente ser feito para a classe da qual ele
instncia. O estreitamento tambm pode ser feito para as suas superclas-
ses.
Para fazer a verificao dinmica do estreitamento ou usar instanceof,
necessrio seguir a cadeia de ponteiros ilustrada na figura 7.2. Caso a
classe para a qual se deve fazer a converso for encontrada em algum
momento, a operao vlida.
C++ oferece trs maneiras diferentes para permitir a realizao de estrei-
tamento: o mecanismo usual de converso explcita (cast), o mecanis-
mo de converso explcita esttica (static_cast) e o mecanismo de con-
verso explcita dinmica (dynamic_cast). C++ tambm oferece o meca-
250
nismo de verificao dinmica de tipos (typeid). Considere o exemplo
7.35.
#include <typeinfo>
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 = static_cast <UmaClasse*> (&us);
pc = &us;
OutraClasse* poc = (OutraClasse*) pc;
// OutraClasse* poc = static_cast <OutraClasse*> (pc);
// terceira parte do exemplo
if (typeid(pc) == typeid(ps))
ps = static_cast<UmaSubclasse*>(pc);
if (typeid(pc) == typeid(pos))
pos = static_cast<OutraSubclasse*>(pc);
}
Exemplo 7. 35- Estreitamento em C++
Na primeira parte do exemplo 7.35 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 operao ser um pon-
teiro para o tipo desejado, no caso do estreitamento ser apropriado. De
outra forma, o valor retornado ser zero (null) para indicar que o tipo no
era o esperado.
Ao se usar dynamic_cast trabalha-se com a verdadeira hierarquia do po-
limorfismo. Somente se pode usar o dynamic_cast em classes com fun-
es virtuais. Isso ocorre porque o dynamic_cast usa a informao arma-
zenada em uma tabela de mtodos virtuais para determinar o tipo atual.
No exemplo, o ponteiro pos receber zero pois o estreitamento para Ou-
traSubclasse* incorreto. responsabilidade do programador verificar se
251
o resultado do estreitamento por dynamic_cast diferente de zero. Tal
como a verificao dinmica em JAVA, a operao de dynamic_cast so-
brecarrega um pouco a execuo do programa. Portanto, se um programa
usa muito dynamic_cast, isso poder diminuir a eficincia de execuo.
Nesse caso aconselhvel reavaliar o projeto do programa.
Em alguns casos possvel saber durante a prpria redao do programa
com qual tipo se est lidando no local do estreitamento. Nessas situaes,
a sobrecarga adicional de se usar dynamic_cast desnecessria. melhor
fazer um static_cast, pois assim a verificao feita em tempo de
compilao.
Usar static_cast para realizar estreitamento tambm melhor do que o
mecanismo de converso explcita tradicional (cast) pois o primeiro no
permite fazer converses fora da hierarquia de classes, o que permitido
pelo segundo. Como a eficincia mesma para cdigos gerados usando
ambos mecanismos, static_cast deve ser preferido pois mais seguro.
A segunda parte do exemplo mostra o uso do mecanismo de static_cast.
Nessa parte, um objeto (us) de UmaSubclasse criado e feita uma am-
pliao para um ponteiro para UmaClasse. Essa mesma operao repe-
tida usando static_cast. Como se trata de uma ampliao, no existe uma
obrigatoriedade de usar static_cast, mas seu uso pode ser conveniente
para tornar mais explcita a ampliao e para evitar a realizao equivo-
cada de converses fora da hierarquia de classes.
Aps as operaes de ampliao, o exemplo mostra a diferena entre se
fazer estreitamento com static_cast e o mecanismo tradicional. Com o
mecanismo tradicional possvel fazer a converso fora da hierarquia de
classes entre os ponteiros para UmaClasse e para OutraClasse. Isso no
permitido no caso do static_cast. Caso a linha de cdigo na qual essa ope-
rao feita no estivesse comentada, ocorreria um erro de compilao.
Na terceira parte do exemplo, o identificador especial typeid usado para
detectar dinamicamente os tipos dos ponteiros e o static_cast usado para
fazer o estreitamento. Contudo, esse uso de typeid no exemplo produz
efetivamente o mesmo efeito do mecanismo usando dynamic_cast pois o
programador deve fazer algum teste para descobrir se a converso obteve
sucesso. Alm disso, o uso de typeid torna o cdigo com dynamic_cast
to eficiente quanto esse.
Resumindo, boa poltica usar preferencialmente os mecanismos de dy-
namic_cast e typeid. Embora seja mais rpido fazer estreitamento estati-
camente, a converso dinmica mais segura pois os mecanismos estti-
cos de converso podem produzir converses inapropriadas.
252
Existem controvrsias a respeito do uso de estreitamento. Para alguns, o
uso de estreitamento indica um projeto de sistema inadequado. Esses a-
creditam que operaes de estreitamento vo contra os princpios do pro-
jeto orientado a objetos, uma vez que a verdadeira origem do objeto ne-
cessita ser conhecida ou presumida para que o programa funcione apro-
priadamente.
No caso da origem ser presumida, isso pode provocar violao no sistema
de tipos (caso a pressuposio seja equivocada). No caso da origem ser
conhecida atravs de alguma espcie de teste dinmico, perde-se bastante
a modificabilidade proporcionada pela orientao a objetos, uma vez que
o cdigo usurio da hierarquia de classes necessita incluir operaes para
testar o tipo do objeto sendo manipulado. Nesse caso, quando uma nova
subclasse includa na hierarquia, ao contrrio do apregoado pela orien-
tao a objetos, o cdigo usurio ter de ser modificado para levar em
conta os objetos dessa nova classe.
Para outros, a ausncia de estreitamento considerada uma limitao sig-
nificativa no poder expressivo da linguagem. Segundo eles, existem situ-
aes nas quais a soluo atravs de estreitamento muito mais fcil de
ser redigida e manipulada.
Considere novamente, por exemplo, a situao na qual temos uma lista de
formas (crculos, tringulos, etc.) apresentadas na tela. Se a operao de
highlight de um tipo de forma (por exemplo, tringulos) necessita ser
realizada, necessrio percorrer a lista identificando o tipo do objeto e
realizando a operao de highlight sobre ele caso seja um tringulo.
A soluo puramente orientada a objetos seria criar um mtodo hig-
hlight na classe base com um parmetro indicando qual tipo deveria
ser submetido a essa operao. Cada subclasse teria de sobrescrever esse
mtodo. Essa soluo no boa porque nem sempre essa operao de
highlight deve ser feita em todas as subclasses, o que acaba retirando
a naturalidade do projeto orientado a objetos, isso sem falar no aumento
de mtodos na classe base e de mtodos desnecessrios nas subclasses.
7.2.4.3 Herana Mltipla
A herana pode ser simples ou mltipla. Se uma classe herda de uma ni-
ca classe temos herana simples. Se ela herda de mais de uma classe te-
mos herana mltipla. A figura 7.3 mostra diagramas de classes com e-
xemplos de herana simples e herana mltipla.


253







Figura 7. 3 - Herana Simples x Herana Mltipla
Herana mltipla considerada importante em orientao a objetos por-
que alguns objetos no mundo real pertencem a vrias classes. Contudo,
nem todas linguagens orientadas a objetos permitem a realizao de he-
rana mltipla. Esse o caso de SMALLTALK. Outras, como C++, su-
portam herana mltipla. Por sua vez, JAVA adota uma postura interme-
diria, suportando herana simples e uma forma restrita de herana mlti-
pla.
Em linguagens nas quais no existe herana mltipla, quando uma classe
necessita herdar de mais de uma classe, ela deve incluir objetos das clas-
ses extras como atributos da classe, provocando a insero de uma signi-
ficativa quantidade de mtodos envoltrios (isto , mtodos usados ape-
nas para chamar mtodos de outras classes) ou mesmo a duplicao de
cdigo. O exemplo 7.36 apresenta uma possvel implementao em JA-
VA para a classe AlunoProfessor mostrada na figura 7.3.
class Professor {
String n = Marcos;
int matr = 53023;
public String nome() { return n; };
public int matricula() { return matr; };
}
class Aluno {
String n = Marcos;
int matr = 127890023;
float coef = 8.3;
public String nome() { return n; };
public int matricula() { return matr; };
public float coeficiente() { return coef; };
}
class ProfessorAluno extends Professor {
Aluno aluno = new Aluno();
public float coeficiente() { return aluno.coeficiente(); };
public String matriculaAluno() { return aluno.matricula(); };
}
Exemplo 7. 36 - Herana Simples em JAVA
Pessoa
Empregado
Professor
ProfessorAluno
Aluno
Herana Simples Herana Mltipla
254
Observe a necessidade de se incluir um atributo aluno e os mtodos en-
voltrios coeficiente e matriculaAluno na classe ProfessorAluno para
tambm faz-la se comportar como um Aluno. Observe ainda a duplica-
o do atributo nome em ProfessorAluno. Tal duplicao desnecessria
uma vez que somente o nome herdado de Professor utilizado em Pro-
fessorAluno. Isso faz sentido visto que, em geral, uma pessoa s possui
um nome, mas implica em desperdcio de espao de memria.
Esse desperdcio poderia ser evitado criando-se atributos para abrigar o
coeficiente e a matrcula do aluno em ProfessorAluno. Nesse caso, o atri-
buto aluno no seria mais necessrio. No entanto, essa alternativa impli-
caria em no poder reutilizar os mtodos de Aluno em ProfessorAluno, o
que acarretaria a necessidade de uma implementao especfica desses
mtodos em ProfessorAluno.
Um problema mais srio com essa soluo impedir a subtipagem mlti-
pla, isto , impedir um objeto do tipo ProfessorAluno participar de uma
estrutura de dados contendo objetos do tipo Aluno, uma vez que Profes-
sorAluno deixa de ser subtipo de Aluno.
Linguagens com herana mltipla resolvem o problema da subtipagem
mltipla e no necessitam de mtodos envoltrios ou da reimplementao
de mtodos de uma das classes, mas ainda precisam lidar com problemas
de coliso de nomes e herana repetida.
A coliso de nomes ocorre quando existem nomes idnticos (homnimos)
de atributos ou mtodos nas classes herdadas. Nesses casos, a linguagem
deve especificar uma regra para permitir aos mtodos da classe herdeira
identificar a classe do atributo ou mtodo ao qual o nome est se referin-
do.
Existem diferentes abordagens para tratar o problema de coliso de no-
mes. EIFFEL fora as classes derivadas a renomear os membros das clas-
ses base que conflitam. SELF define uma lista de prioridades entre as
classes base. CLOS unifica em um nico membro todos os membros que
conflitam. C++ detecta um erro de ambigidade em tempo de compilao
se h conflito, mas permite o uso do operador de resoluo de escopo pa-
ra resolver o conflito explicitamente. O exemplo 7.37 ilustra o problema
de coliso de nomes em C++.
class Aluno {
float nota;
public:
void imprime();
};
class Professor {
float salario;
255
public:
void imprime();
};
class ProfessorAluno: public Professor, public Aluno { };
main() {
ProfessorAluno indeciso;
// indeciso.imprime();
}
Exemplo 7. 37 - Coliso de Nomes na Herana Mltipla em C++
Caso a linha de cdigo comentada fosse compilada, na funo main do
exemplo 7.37, na qual um objeto da classe ProfessorAluno criado e ten-
ta utilizar a funo imprime, seria detectado um erro de ambigidade. Pa-
ra evitar esse erro, preciso especificar qual classe herdada est sendo
referenciada pelo objeto da classe herdeira. No exemplo 7.37, poder-se-ia
eliminar a ambigidade sobrescrevendo-se a operao de impresso na
classe ProfessorAluno. Na sobrescrio, possvel utilizar o operador de
resoluo de escopo :: para acessar as operaes desejadas nas classes
herdadas. O exemplo 7.38 mostra uma maneira como a sobrescrio po-
deria ser feita.
class ProfessorAluno: public Professor, public Aluno {
public:
void imprime();
};
void ProfessorAluno::imprime() {
Aluno::imprime();
}
main() {
ProfessorAluno indeciso;
indeciso.imprime();
}
Exemplo 7. 38 - Sobrescrio para Resolver Conflito de Nomes em C++
Com a sobrescrio de imprime em ProfessorAluno no mais ocorre o
erro de ambigidade em main. Alm disso, o operador de resoluo de
escopo pode ser usado dentro da funo sobrescrita para chamar os mto-
dos homnimos das classes herdadas. Em caso do conflito de nomes o-
correr em um atributo, preciso usar o operador de resoluo de escopo
para indicar a classe do atributo referenciado.
O problema de herana repetida ocorre quando uma classe faz herana
mltipla de classes descendentes de uma mesma classe. Os atributos des-
sa classe comum so repetidos na classe na qual feita a herana mlti-
pla. Por isso, esse tipo de problema conhecido como herana repetida.
256
Alm de poder desperdiar memria, esse tipo de situao pode causar
tambm conflito de nomes. A figura 7.4 ilustra uma situao na qual o-
corre herana repetida.












Figura 7. 4 - Herana Repetida
Os atributos da classe Pessoa sero duplicados em ProfessorAluno. Alm
da duplicao de memria, pode haver coliso de nomes quando os mto-
dos de ProfessorAluno se referem aos atributos de Academico. Nesse ca-
so, preciso saber se o atributo referenciado o herdado pela classe Alu-
no ou pela classe Professor. O conflito de nomes tambm pode ocorrer
quando um mtodo de Academico sobrescrito em Professor ou Aluno.
Novamente, ser necessrio saber de qual classe o mtodo a ser chama-
do.
No caso de herana repetida as alternativas tambm so muitas. Em
CLOS os atributos compartilhados so obrigatoriamente unificados em
um nico atributo. EIFFEL tambm unifica os atributos por default,
com exceo daqueles que so renomeados ao longo do caminho de he-
rana. C++ fornece um mecanismo especial, atravs do uso da palavra
virtual, para resolver esse problema. Se a especificao da classe herdada
precedida por virtual somente um objeto daquela classe compor o ob-
jeto das classes herdeiras, independentemente de a classe ser herdada
mltiplas vezes ou no. O exemplo 7.39 mostra como esse mecanismo
pode ser usado.
class Academico {
int i;
int m;
public:
int idade ( ) { return i; }
int matricula ( ) { return m; }
};
Academico
Professor
Academico
Aluno
Academico
ProfessorAluno




Aluno Professor
Academico Academico
257
class Professor: virtual public Academico {
float s;
public:
float salario ( ) { return s; }
};
class Aluno: virtual public Academico {
float coef;
public:
int coeficiente ( ) { return coef; }
};
class ProfessorAluno: public Professor, public Aluno {};
Exemplo 7. 39 - Uso de virtual para Evitar Herana Repetida em C++
Se a palavra virtual no fosse usada na especificao das classes Aluno e
Professor, os atributos de idade (i) e matricula (m)de Academico sero
repetidos na classe ProfessorAluno. Isso no ocorre na implementao do
exemplo 7.39.
Contudo, a abordagem de C++ apresenta dois problemas. Primeiro, a es-
pecificao da palavra virtual no feita na classe na qual ocorre a heran-
a repetida. Em princpio, ao se criar as classes Professor e Aluno, no se
sabe se elas sero herdadas futuramente por uma mesma subclasse e mui-
to menos se essa subclasse precisar ou no compartilhar os atributos co-
muns.
O segundo problema relacionado com esse e reflete um problema de
expressividade da linguagem. No exemplo do ProfessorAluno, um objeto
dessa classe deveria possuir um nico atributo de idade ( a idade da
mesma pessoa). Contudo, ele deveria possuir dois atributos matricula (u-
ma vez que a matrcula de um professor normalmente diferente da de
um aluno).
Em C++ no possvel especificar que um atributo deve ser repetido e
outro no. O mecanismo de EIFFEL permite fazer isso, mas requer mais
trabalho para especificar herana mltipla pois se deve especificar indivi-
dualmente os atributos que compartilhados e os repetidos.
JAVA no permite herana mltipla de classes, mas usa o conceito de
interfaces para permitir subtipagem mltipla. Como dito anteriormente,
interfaces so classes abstratas nas quais so definidos apenas prottipos
de mtodos, mas no fornecem a implementao dos mtodos. Mtodos
da interface so considerados automaticamente pblicos. Interfaces po-
dem conter valores de tipos primitivos, mas estes so implicitamente de-
finidos como static e final, isto , so constantes de classe. Uma classe
implementa uma interface quando ela implementa os mtodos definidos
258
na interface. O exemplo 7.40 mostra a definio e uso de uma interface
em JAVA.
interface Aluno {
void estuda();
void estagia();
}
class Graduando implements Aluno {
public void estuda() {}
public void estagia() {}
}
Exemplo 7. 40 - Interface em JAVA
No exemplo 7.40 a interface Aluno declara os mtodos estuda e estagia e
implementada pela classe Graduando. Embora Aluno seja de fato uma
classe abstrata, a herana em Graduando estabelecida usando a palavra
implements. Note ainda que a declarao de mtodos na interface no uti-
liza a palavra public, mas na classe Graduando obrigatrio utilizar essa
palavra na definio dos mtodos especificados em Aluno. Isso ocorre
porque os mtodos das interfaces so sempre implicitamente declarados
como pblicos. Se algum mtodo da interface herdada no for implemen-
tado na classe herdeira, essa classe deve ser declarada como abstrata para
no ocorrer erro de compilao.
Embora uma classe s possa herdar apenas de uma outra classe, ela pode
implementar um nmero qualquer de interfaces. Alm disso, interfaces
tambm podem herdar de outras interfaces. A combinao de herana
simples de classes com herana mltipla de interfaces utilizada em JA-
VA para permitir a um objeto ser subtipo de vrios tipos. Assim, um ob-
jeto de um determinado tipo pode ser usado em situaes nas quais se es-
peram objetos de seus supertipos. O exemplo 7.41 mostra como isso pode
ser feito.
interface Cirurgiao { void opera(); }
interface Neurologista { void consulta(); }
class Medico { public void consulta() {} }
class NeuroCirurgiao extends Medico
implements Cirurgiao, Neurologista {
public void opera() { }
}
public class Hospital {
static void plantoCirurgico (Cirurgiao x) { x.opera(); }
static void atendimentoGeral (Medico x) { x.consulta(); }
static void neuroAtendimento (Neurologista x) { x.consulta(); }
static void neuroCirurgia (NeuroCirurgiao x) { x.opera(); }
259
public static void main(String[ ] args) {
NeuroCirurgiao doutor = new NeuroCirurgiao();
plantoCirurgico(doutor);
atendimentoGeral(doutor);
neuroAtendimento(doutor);
neuroCirurgia(doutor);
}
}
Exemplo 7. 41 - Subtipagem Mltipla em JAVA
O exemplo 7.41 mostra como um objeto da classe NeuroCirurgiao pode
ser usado nos locais onde se espera um objeto de Cirurgiao, Neurologis-
ta, Medico ou NeuroCirurgiao. Note que a classe concreta NeuroCirurgi-
ao no precisa necessariamente definir o mtodo consulta da interface
Neurologista. Isso no preciso porque a definio do mtodo herdada
da classe Medico. J o mtodo opera tem de ser implementado em Neu-
roCirurgiao para que essa classe no seja considerada abstrata.
Como interfaces no podem conter valores (a menos de constantes de
classe, que so nicas para a classe e no se repetem nos objetos) e no
implementam mtodos, os problemas de conflito de nomes e herana re-
petida tambm so evitados. Portanto, o mecanismo de interfaces oferece
uma soluo elegante para esses problemas e ao mesmo tempo possibilita
a ocorrncia de subtipagem mltipla.
Por outro lado, esse mecanismo demanda das classes concretas que im-
plementam interfaces ficarem repletas de implementaes de mtodos
necessrios para satisfazer todas as interfaces relacionadas. Mais que isso,
eventualmente, o mesmo cdigo ter de ser repetido em diferentes classes
que implementam a mesma interface. De fato, se a herana mltipla fosse
irrestrita, mtodos desse tipo poderiam ser implementados uma nica vez
em uma superclasse e herdados pelas subclasses sem necessidade de rees-
crev-los.
Por fim, cabe comentar a existncia de herana dinmica em algumas
LPs. Herana dinmica a habilidade de adicionar, excluir ou modificar
superclasses dos objetos (ou classes) em tempo de execuo. Assim, ela
possibilita que objetos possam se modificar e evoluir com o tempo.
CLOS e SMALLTALK oferecem herana dinmica. C++ e JAVA no
oferecem.
7.2.4.4 Metaclasses
Uma metaclasse uma classe de classes, isto , uma classe cujas instn-
cias so outras classes. Em LPs com metaclasses, classes tambm so ob-
260
jetos. Tal como classes normais, metaclasses so descritas atravs de atri-
butos e mtodos. Tipicamente, os valores dos atributos so os mtodos,
instncias e as superclasses das classes que so instncias da metaclasse.
Os mtodos da metaclasse normalmente oferecem servios aos programas
de aplicao, tais como, retornar os conjuntos de mtodos, instncias e
superclasses de uma dada classe.
JAVA possui uma nica metaclasse, chamada de Class. Esta classe for-
nece servios aos programas de aplicao, tais como, a recuperao de
mtodos, atributos, classes internas, a criao de novas instncias e a in-
vocao de mtodos de um objeto. O exemplo 7.42 mostra isso sendo fei-
to em JAVA.
import java.lang.reflect.*;
class Info {
public void informa(){}
public int informa(Info i, int x) { return x; }
}
class MetaInfo {
public static void main (String args[]) throws Exception {
Class info = Info.class;
Method metodos[] = info.getMethods();
Info i = (Info)info.newInstance();
Method m = info.getDeclaredMethod("informa", null);
m.invoke(i, null);
}
}
Exemplo 7. 42 - Metaclasse em JAVA
No exemplo 7.42 info um objeto da metaclasse Class designando a clas-
se Info. O objeto info usado para se obter os mtodos da classe Info, pa-
ra criar uma instncia dessa classe e para chamar seu mtodo informa (a-
quele sem parmetros). Cada LP possui um modo especfico de lidar com
metaclasses. Os diversos modos se enquadram em um dos seguintes sis-
temas:
Nvel nico: Todos objetos so vistos como classes e todas as
classes so vistas como objetos. No h necessidade de metaclasses
porque os objetos se autodescrevem. S existe um tipo de objeto.
Um exemplo de LP que adota este sistema SELF.
Dois Nveis: Todos objetos so instncias de uma classe, mas clas-
ses no so acessveis por programas. Existem dois tipos de obje-
tos: objetos e classes. Um exemplo de LP que adota este sistema
C++.
261
Trs Nveis: Todos objetos so instncias de uma classe e todas as
classes so instncias de uma nica metaclasse. A metaclasse
uma classe e, portanto, uma instncia de si mesma. Isto possibilita
que classes sejam manipuladas diretamente pelos programas de a-
plicao. Existem dois tipos de objetos (objetos e classes), com
uma classe atpica (a metaclasse). Um exemplo de LP que adota es-
te sistema JAVA.
Vrios Nveis: Semelhante ao sistema de trs nveis, mas com n-
veis adicionais que permitem a criao de metaclasses especializa-
das para um conjunto de classes. Existem trs tipos de objetos (ob-
jetos, classes e metaclasses). Um exemplo de LP que adota este sis-
tema SMALLTALK.
7.3 Consideraes Finais
Nesse captulo foram abordados conceitos relacionados com sistemas de
tipos polimrficos, os quais aumentam a possibilidade de reutilizao de
cdigo. Mostrou-se que o polimorfismo pode ser ad-hoc (coero ou so-
brecarga) ou universal (paramtrico ou incluso). Discutiu-se com pro-
fundidade cada um desses tipos de polimorfismo, com especial destaque
para o polimorfismo de incluso, fundamental em linguagens orientadas a
objetos.
Uma discusso interessante relativa ao polimorfismo de incluso diz res-
peito s indicaes de uso de composio ou herana para a reutilizao
de cdigo. A composio ocorre quando um atributo de uma classe um
objeto de outra classe. Isso significa que o cdigo da classe do objeto po-
de ser reutilizado pela classe que o tem como atributo. Herana tambm
pode ser vista como a colocao implcita de um objeto da classe herdada
dentro de uma classe herdeira. Portanto, tanto composio como herana
permitem a colocao de objetos dentro de uma nova classe e a reutiliza-
o do cdigo da classe desse objeto.
Composio geralmente utilizada quando se quer as caractersticas de
uma classe existente dentro de uma nova classe, mas no sua interface.
Isto , a insero de um objeto ocorre de modo a utiliz-lo para imple-
mentar a funcionalidade da nova classe, mas o usurio acessa a nova clas-
se somente atravs da sua prpria interface, no usando a interface do ob-
jeto inserido. Para alcanar esse efeito se deve incluir os objetos de clas-
ses existentes como privados na nova classe.
A herana parte de uma classe existente e cria uma nova verso dessa
classe. Em geral, isso significa que se est tomando uma classe de prop-
sito mais geral e especializando-a para uma necessidade particular. Alm
262
de usar as caractersticas, a classe herdeira tambm usa a interface da
classe herdada.
Uma regra geral para tomar a deciso sobre herana ou composio a
seguinte: um relacionamento entre classes do tipo -um expresso como
herana e um relacionamento do tipo tem-um expresso como composi-
o. Por exemplo. no faz sentido compor um automovel a partir de um
objeto veculo pois um automovel no contm um veiculo. De fato, um
automovel um veculo. Logo, nesse caso, a regra mencionada sugere
que se deve usar herana.
Outro ponto interessante diz respeito aos tipos de polimorfismo envolvi-
dos com o polimorfismo de incluso. Os mtodos herdados da superclasse
so polimrficos universais pois seu cdigo usado tanto para as instn-
cias da superclasse quanto das subclasses. Estruturas de dados cujos ele-
mentos so da superclasse tambm so polimrficas universais pois po-
dem abrigar instncias da prpria superclasse e de qualquer uma de suas
subclasses. J o polimorfismo puramente decorrente da sobrescrio de
mtodos um polimorfismo ad-hoc de sobrecarga pois as chamadas des-
ses mtodos invocam cdigos diferentes para cada tipo de objeto. Existe
ainda o polimorfismo decorrente da amarrao tardia de tipos no cdigo
usurio. Esse polimorfismo tambm universal pois o cdigo usurio po-
de ser usado com instncias de diferentes classes.
Um ltimo assunto para ser discutido so as diferentes possibilidades ofe-
recidas por C++ e JAVA para criar cdigo polimrfico na implementao
de estruturas de dados genricas, tanto homogneas quanto heterogneas.
Estruturas de dados genricas so capazes de armazenar e operar sobre
elementos de tipos diferentes. Estruturas genricas podem ser preenchidas
com elementos de um mesmo tipo (nesse caso so chamadas de estruturas
homogneas) ou de tipos diferentes (nesse caso so chamadas de estrutu-
ras heterogneas).
C++ utiliza o polimorfismo paramtrico proporcionado pelo mecanismo
de template para a criao de estruturas de dados genricas homogneas e
o mecanismo de polimorfismo por incluso para a criao de estruturas de
dados heterogneas. possvel ainda combinar o mecanismo de template
com o polimorfismo de incluso para criar estruturas de dados genricas
heterogneas.
JAVA utiliza o polimorfismo por incluso para permitir a criao de es-
truturas de dados genricas heterogneas. Para isso, JAVA considera to-
das as classes existentes como subclasses (diretas ou indiretas) da classe
Object.
263
Assim, estruturas de dados cujos elementos so do tipo Object podem a-
brigar elementos de qualquer classe em JAVA. Para ter uma estrutura de
dados homognea o programador deve garantir que os elementos inseri-
dos so sempre de um mesmo tipo. Isso pior do que a soluo de C++,
na qual o compilador garante a homogeneidade da estrutura. Alm disso,
a soluo de JAVA obriga a realizao de estreitamento sempre que um
elemento deve ser acessado a partir da lista. Por outro lado, a soluo de
JAVA simplifica a linguagem pois no tm sido necessrio incluir o po-
limorfismo paramtrico, o qual imprescindvel para C++.
7.4 Exerccios
1. Segundo a classificao 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 cdigo em C, C++ ou JAVA. Identifique o tipo de polimorfismo
que ocorre em cada exemplo e explique porque cada um dos trechos
de cdigo polimrfico. Indique ainda se o polimorfismo ad-hoc ou
universal e justifique.

2. O que so classes abstratas? Quando devem ser usadas e quais as suas
vantagens? Quais diferenas existem na definio de classes abstratas
em C++ e JAVA?

3. Enquanto em C++ somente os mtodos precedidos pela palavra virtual
utilizam o mecanismo de amarrao tardia de tipos, em JAVA todos
os mtodos empregam este mecanismo. Justifique esta deciso dos
criadores dessas linguagens.

4. Linguagens de programao orientadas a objetos podem adotar heran-
a simples ou mltipla. C++, por exemplo, adota herana mltipla.
Quais os dois problemas que podem ocorrer quando se adota herana
mltipla? Explique-os usando exemplos em C++. Mostre de que forma
C++ permite contornar esses problemas. Apresente um exemplo de si-
tuao na qual os mecanismos de C++ so inadequados para trat-los.

5. C++ oferece trs mecanismos distintos para permitir a realizao de
estreitamento. Mostre exemplos do uso desses trs mecanismos e os
compare em termos de confiabilidade e eficincia.

6. Amarrao tardia de tipos o processo de identificao em tempo de
execuo do tipo real de um objeto. Esse processo pode ser utilizado
para a identificao dinmica do mtodo a ser executado (quando ele
264
sobrescrito) e para a verificao das operaes de estreitamento. Ex-
plique como implementado o mecanismo de amarrao tardia de ti-
pos em linguagens como C++ e JAVA e como ele usado para reali-
zar as operaes mencionadas na frase anterior.

7. Tanto C++ quanto JAVA oferecem bibliotecas de classes que disponi-
bilizam estruturas de dados genricas, tais como listas, rvores e tabe-
las hash. Ambas utilizam polimorfismo para a implementao dessas
estruturas, embora sejam formas diferentes de polimorfismo. Explique
como essas linguagens usam o polimorfismo para a implementao
dessas estruturas. Discuta as vantagens e desvantagens de cada abor-
dagem. Justifique tambm porque os criadores dessas linguagens ado-
taram essa postura diferenciada.

8. Implemente o tipo abstrato de dados lista genrica em C, C++ e JA-
VA. suficiente apresentar a definio do tipo e o cabealho dos m-
todos de construo, ordenao, destruio, verificao de lista vazia,
incluso e excluso de elemento (no preciso codificar os mtodos
da lista). Atente para o fato de que a operao de ordenao na lista
deve ser nica, mas deve permitir que a lista seja ordenada por crit-
rios distintos. Justifique a sua implementao, enfocando o modo co-
mo se obtm a generalidade da lista e o funcionamento da operao de
ordenao.

9. Em alguns problemas pode ser conveniente permitir a um mesmo ob-
jeto participar de duas ou mais listas cujos campos de informao so
de tipos diferentes. Por exemplo, em um problema no qual necess-
rio armazenar em listas os diversos tipos de dependncias de um apar-
tamento haveria uma lista para quartos e outra para salas. Contudo,
possvel haver uma mesma dependncia usada como quarto e como
sala. Nesse caso, essa dependncia participaria tanto da lista de quar-
tos quanto da lista de salas.
a) Como voc resolveria esse problema em uma linguagem que no
possui mecanismos para a realizao de subtipagem mltipla, ou seja,
que permita a um mesmo objeto fazer parte de listas de dados cujos ti-
pos de informao sejam de tipos distintos. Existe alguma desvanta-
gem na sua soluo.
b) Sabendo que C tambm no permite a realizao de subtipagem
mltipla, responda se possvel resolver esse mesmo problema de ou-
tra maneira? Se a resposta for positiva, explique como seria essa solu-
o e faa uma anlise de suas vantagens e desvantagens.
265
c) Mostre atravs da implementao desse exemplo como C++ e JA-
VA permitem a realizao de subtipagem mltipla.
Compare ainda os mecanismos oferecidos por C++ e JAVA para realiza-
o de subtipagem mltipla apresentando vantagens e desvantagens de
cada um.

10. Considere as seguintes definies de funes 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 cdigo de main abaixo so legais e explique
as que no so.
main () {
tdata a;
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);
266
c = zpto (a, b);
}
Explique ainda como o compilador C++ implementa o mecanismo de po-
limorfismo paramtrico. Discuta essa soluo em termos de reusabilidade
de cdigo.

11. Cada um dos programas seguintes, escritos em C++, utiliza um tipo de
polimorfismo visto nesse captulo. Defina o que polimorfismo.
Descreva as caractersticas 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 execuo.



































// 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 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());
}

// 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();
}

267
12. Qual a diferena 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 posio.

13. Uma loja especializada em produtos de arte vende livros, discos e fitas
de vdeo. Ela necessita montar um catlogo com as informaes sobre
cada produto. Todo produto possui um nmero nico de registro, um
preo e uma quantidade em estoque. Alm disso, deve-se saber o n-
mero de pginas dos livros, o nmero de msicas dos discos e a dura-
o da fita de vdeo. Outro aspecto importante a ser considerado a
existncia 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 definio do tipo e os cabealhos de suas ope-
raes de criao, leitura, obteno de dados, escrita e destruio).
b) Supondo que os produtos da loja esto armazenados em uma lista
genrica, fazer uma funo/mtodo para receber a lista dos produtos e
uma quantidade mnima de produtos a serem mantidos em estoque, e
listar quais produtos necessitam de reposio.
c) Fazer uma funo/mtodo para receber a lista dos produtos, uma
quantidade de msicas e uma durao mnima de filme, e listar quais
discos possuem menos msicas que a quantidade especificada e quais
filmes possuem maior durao que a especificada.
Compare os mecanismos oferecidos por cada uma das linguagens (C,
C++ e JAVA) para lidar com o produto combinado. Enfoque na sua com-
parao os aspectos de facilidade de reutilizao de cdigo, facilidade
para a realizao do tem c desta questo e o potencial para induo de
erros de programao.

14. O Ministrio da Defesa te contratou para desenvolver um prottipo de
um sistema de informao em C++ que cadastre os militares existentes
nas Foras Armadas Brasileiras e gere duas listagens.

a. Crie uma classe abstrata Militar com um atributo inteiro representando
sua matrcula e outro atributo representando sua patente. Garanta que
todas subclasses concretas de Militar implementem obrigatoriamente
os mtodos de leitura de dados de um militar, impresso de dados de
um militar e verificao se o militar est habilitado para progredir na
carreira.
Utilize a classe seguinte para representar a patente do militar.

268
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 MilitarA-
eronautica, que ser usada para representar os militares dessa diviso
das foras armadas. Essa classe possui como atributo adicional o n-
mero de horas de vo efetuadas pelo militar. Note que um militar da
aeronutica est em condies de progredir se tem mais de 100 horas
de vo e est a mais de 2 anos naquela patente.
c. Considerando a existncia de duas outras subclasses de Militar seme-
lhantes a MilitarAeronutica, denominadas MilitarExercito e Mili-
tarMarinha, utilize uma classe ListaMilitares (consistindo de uma lis-
ta de ponteiros para militares) para implementar um programa que:
Solicite ao usurio o nmero total de militares das Foras Armadas
Solicite ao usurio a corporao e os dados de cada militar das For-
as Armadas
apresente os dados de todos os militares em condies de progredir
na carreira.
apresente os dados de todos os militares da Aeronutica.
Observao: Voc no deve implementar a classe ListaMilitares. Consi-
dere que, alm de possuir o mtodo construtor default e o destrutor, ela
tambm possui os seguintes mtodos:
void incluir(Militar* m); // inclui um militar ao final da lista
Militar* retornar(int i); // retorna o militar na i-sima posio


269

Captulo VIII Excees


Depois eio Darwin e nos disse que mesmo neste pequeno mundo entre mundos nao
ramos tao excepcionais assim.`
Luis lernando Verissimo
Nem todas condies geradoras de erro podem ser detectadas em tempo
de compilao, mas um software seguro e confivel dever implementar
um comportamento aceitvel mesmo na presena dessas condies anor-
mais. Diviso por zero, falha na abertura de um arquivo, fim de arquivo,
overflow, acesso a um ndice invlido e utilizao de um objeto no
inicializado so exemplos tpicos de condies anormais.
Caso essas condies especiais no sejam tratadas, o bloco de cdigo na
qual esto inseridas ser interrompido quando executado ou gerar incon-
sistncias, comprometendo a confiabilidade do software.
No estudo de linguagens de programao o termo exceo usado para
designar um evento ocorrido durante a execuo de um programa que
desvia o fluxo normal de instrues. Em outras palavras, uma exceo
uma condio provocada por uma situao excepcional a qual requer uma
ao especfica imediata.
Como se pode ver acima, muitos tipos de erros podem causar excees.
As causas de excees podem variar desde erros srios de hardware, tal
como uma falha no disco rgido, a erros simples de programao, tal co-
mo tentar acessar um ndice inexistente de um vetor.
Erros causam excees, mas podem existir excees que no so erros.
Por exemplo, em uma funo usada para encontrar um dado em uma se-
quncia, a chegada ao final da sequncia, sem encontrar o dado, uma
condio excepcional considerada como exceo. Normalmente, esse tipo
de exceo ocorre em subprogramas capazes de produzir mltiplos resul-
tados e alterar o fluxo normal de execuo do programa.
A tabela 7.1 apresenta uma classificao das possveis motivaes para
ocorrncia de excees durante a execuo de um programa.
Hardware
Erros Software

Excees
Fluxo Mltiplos Resultados
Tabela 8. 1 - Causas de Excees

270
Nesse captulo so apresentados o conceito de excees, sua importncia,
como se apresentam e como podem ser tratadas nas diversas LPs. Inici-
almente, discute-se como linguagens que no oferecem mecanismos de
excees lidam com situaes anmalas. Em seguida, discute-se de forma
geral os mecanismos de excees oferecidos por linguagens de programa-
o, apresentando-se como uma exceo sinalizada, tratada, propagada,
relanada e especificada. Mostra-se tambm como o fluxo de controle do
programa pode ser continuado aps o tratamento de uma exceo e como
funciona a integrao dos conceitos de orientao a objetos com os de
tratamento de excees.
8.1 Ausncia de Mecanismo Especfico para Excees
Linguagens de programao podem no oferecer qualquer mecanismo
especfico para tratamento de excees. Essas LPs tendem a produzir
programas menos confiveis e mais obscuros. Os programas podem se
tornar menos confiveis porque a linguagem no requer o tratamento das
possveis excees. O cdigo pode ficar mais obscuro porque o progra-
mador pode misturar cdigo relacionado com a funcionalidade desejada
com o cdigo responsvel por tratar as excees.
Linguagens como C, PASCAL e MODULA-2 no oferecem mecanismos
prprios para o tratamento de excees e aumentam, assim, o trabalho dos
programadores. Eles devem tratar as condies de erro em todos os locais
onde possam aparecer. Vrias alternativas tm sido adotadas pelos pro-
gramadores para lidar com esse tipo de problema nessas LPs. As mais
conhecidas so listadas a seguir:
1. Deixar abortar o programa.
2. Testar a condio excepcional antes de ela ocorrer e realizar o tra-
tamento imediato.
3. Retornar cdigo de erro indicando a exceo ocorrida em uma va-
rivel global, no resultado da funo ou em um parmetro especfi-
co.
A alternativa de deixar abortar o programa no uma boa soluo porque
muitas excees podem ser contornadas sem que seja necessrio inter-
romper a execuo do programa. Alm disso, o simples aborto do pro-
grama reduz a confiana do usurio no sistema e tambm dificulta a de-
purao dos erros, visto que no fornece indicaes sobre o problema o-
corrido.
A alternativa de testar a condio excepcional e trat-la imediatamente
apresenta vrios problemas. Em primeiro lugar, ela carrega muito o texto
do programa com cdigo de tratamento, obscurescendo a funcionalidade

271
do algoritmo. Esse problema pode ser reduzido utilizando-se subprogra-
mas especficos para tratamento de excees. O local de tratamento da
exceo ento separado do cdigo onde a exceo ocorre, tornando-o
mais claro. Alm disso, por separar o cdigo de tratamento da exceo
em um nico subprograma, ele pode ser reutilizado em outras situaes
nas quais o mesmo tipo de exceo ocorre. O exemplo 8.1 ilustra o uso
desse tipo de abordagem em C.
int divideInteiros (int numerador, int denominador) {
if (denominador == 0 )
trata_divisao_zero();
else
return numerador / denominador;
}
int trata_divisao_zero(void) {
printf("Divisao por zero");
return 1;
}
Exemplo 8. 1 Uso de Subprogramas Especficos no Tratamento de Excees em C
Observe o uso da funo trata_divisao_zero, a qual evita a colocao in-
tegral do cdigo da funo dentro da funo divideInteiros. Caso v se
realizar uma outra funo de diviso (por exemplo, diviso de nmeros
reais), basta chamar novamente trata_divisao_zero para realizar o mesmo
tratamento dessa exceo.
Mesmo trazendo alguns benefcios, essa abordagem ainda problemtica
porque o programador tem de lembrar, identificar e testar todas as poss-
veis condies causadoras de excees. Isto normalmente no ocorre. Ou-
tro problema a sobrecarga do texto do programa com testes de excees,
misturando o algoritmo com a identificao de excees. Essa tcnica
ainda provoca, portanto, perda de legibilidade, uma vez que mistura a es-
sncia do subprograma (sua funcionalidade bsica) com o cdigo de iden-
tificao do erro. Por fim, algumas excees no podem ser tratadas lo-
calmente, s podendo ser tratadas adequadamente em um ponto mais ex-
terno do cdigo. O exemplo 8.2 mostra essa alternativa em uma funo C.
void executaFuncionalidade(int x) {
printf (Faz alguma coisa!!!);
};
void f(int x) {
if (condicao1(x)) trata1;
if (condicao2(x)) trata2;
if (condicao3(x)) {
printf(Nao consegue tratar aqui);

272
exit(1);
}
executaFuncionalidade(x);
}
Exemplo 8. 2 - Teste e Tratamento Imediato de Excees em C
A funo f do exemplo 8.2 tem o objetivo de executar uma determinada
funcionalidade. Contudo, antes de poder executar essa funcionalidade
necessrio verificar se o valor passado para o parmetro x apropriado.
Caso o valor de x satisfaa a condicao1 ou a condicao2, ocorre uma situ-
ao anmala que demanda um tratamento especfico. Caso o valor de x
satisfaa a condicao3, ocorre uma outra situao anmala, mas que no
pode ser tratada de dentro de f. Nesse caso, a soluo adotada pelo pro-
gramador de f abortar a execuo.
A funo f mostra que essa alternativa de tratamento de excees no
muito satisfatria. Alm de ainda ser necessrio abortar a execuo em
certa condio, ela carrega o cdigo de testes, dificultando a legibilidade.
Mais que isso, ela no apia o programador na identificao das excees
que precisam ser tratadas. Por via de regra, o programador acaba esque-
cendo de testar alguma condio excepcional.
A alternativa de retornar um cdigo de erro implica na necessidade do
cdigo chamador realizar um tratamento para cada cdigo de retorno.
Embora essa alternativa resolva o problema de tratamento no local da
exceo, ela mantm os dois outros problemas. De fato, o problema de
sobrecarga de cdigo fica ainda maior, uma vez que agora se necessita
testar, em toda chamada de funo, todos os possveis cdigos de erro
retornveis. Isso pode facilmente duplicar o tamanho de um programa. O
exemplo 8.3 mostra como isso pode ocorrer em um cdigo C.
int f(int x) {
if (condicao1(x)) return 1;
if (condicao2(x)) return 2;
if (condicao3(x)) return 3;
executaFuncionalidade(x);
}
void g() {
int resp;
resp = f(7);
if (resp == 1) trata1;
if (resp == 2) trata2;
if (resp == 3) trata3;
}
Exemplo 8. 3 - Tratamento No Local de Excees em C

273
No exemplo 8.3, o tratamento das situaes anmalas no mais feito
dentro de f. Isso agora feito na funo g, chamadora de f. Agora, alm
de se fazer testes dentro de f, tm-se tambm de fazer testes em g, aps o
retorno da chamada de f. As chances do programador esquecer de tratar
alguma exceo tambm aumentam uma vez que o esquecimento tambm
pode ocorrer nos testes em g.
A opo de usar o resultado da funo como cdigo de retorno nem sem-
pre possvel porque pode haver incompatibilidade de valores e de tipo
com o resultado normal da funo (afinal, o retorno da funo normal-
mente usado para retornar o resultado da funo e no um cdigo). Por
exemplo, a funo obtemTopo do exemplo 6.1 retorna o valor 1, quando
a pilha de nmeros naturais est vazia. Essa soluo no poderia ser usada
caso a pilha fosse de nmeros inteiros, uma vez que o valor 1 um n-
mero inteiro que poderia estar no topo da pilha.
C usa normalmente uma varivel global, definida na biblioteca padro e
chamada errno, para retornar o cdigo de retorno. Essa soluo no
muito boa porque o usurio da funo pode no ter cincia de que essa
varivel existe (uma vez que isso no fica explcito na sua chamada) e
tambm porque uma outra exceo pode ocorrer antes do tratamento da
anterior, sobrescrevendo o cdigo de retorno da primeira exceo antes
dela ser tratada efetivamente. Em particular, essa opo no funciona bem
em programas concorrentes.
J a opo de usar um parmetro para retornar o cdigo de exceo me-
lhor do que o retorno em varivel global ou no resultado da funo. No
obstante, ela exige a incluso de um novo parmetro nas chamadas dos
subprogramas e requer a propagao desse parmetro at o ponto de tra-
tamento da exceo, diminuindo a redigibilidade do cdigo.
Contudo, o grande problema relacionado com essa soluo o fato da
experincia ter mostrado que, na maioria das vezes, o programador que
chama a funo no testa todos os cdigos de retorno possveis, uma vez
que no obrigatrio faz-lo.
C, especificamente, oferece ainda outras opes para o tratamento de ex-
cees, tal como a utilizao do sistema de manipulao de sinais de sua
biblioteca padro. Sinais podem ser gerados a partir da funo raise ou
em resposta a um comportamento excepcional do programa como um
overflow ou um acesso indevido a memria e podem ser tratados atra-
vs da funo signal quando um desses eventos ocorre.
Outra opo usar as funes da biblioteca padro setjmp e longjmp para
salvar e recuperar respectivamente um determinado estado do programa.
A funo longjmp um goto no local pois passa o controle do programa
para o ponto onde o ltimo setjmp foi executado. Assim, na ocorrncia de

274
um erro, pode-se usar o longjmp para ir para o local de tratamento defini-
do em setjmp.
Ambas solues com utilizao das funes da biblioteca padro reque-
rem o imediato tratamento da exceo, mas uma soluo concentra o tra-
tamento em signal e a outra, a que usa longjmp, permite a sua localizao
em qualquer ponto do programa. Alm disso, ambas solues so com-
plexas e difceis de serem entendidas.
Como C no oferece um mecanismo especfico para tratamento de exce-
es, fica a critrio do programador implementar ou no o tratamento de
excees. Fica tambm a seu critrio decidir qual a abordagem de trata-
mento ser utilizada.
8.2 LPs com Mecanismos de Tratamento de Excees
LPs com mecanismos de tratamento de excees buscam garantir e esti-
mular o tratamento das condies excepcionais sem que haja uma grande
sobrecarga do texto do programa. Adicionalmente, elas oferecem um
meio mais apropriado para se modularizar e reutilizar o cdigo de trata-
mento de erros.
Linguagens como ADA, C++, JAVA e EIFFEL possuem mecanismos
prprios de tratamento de excees os quais facilitam a vida dos
programadores tornando o cdigo mais legvel uma vez que separam o
cdigo executor da funcionalidade desejada do cdigo de tratamento do
erro.
Quando uma exceo ocorre ela necessita ser tratada. O bloco ou unidade
de cdigo que manipula a exceo denominado tratador de exceo e a
ao de indicar a ocorrncia da exceo e transferir o controle para o tra-
tador denominada sinalizao ou disparo da exceo. Tratadores de ex-
ceo se comportam como procedimentos chamados implicitamente pela
ocorrncia de uma exceo.
8.2.1 Tipos de Excees
As LPs podem possuir excees pr-definidas como parte da prpria lin-
guagem ou de sua biblioteca padro (por exemplo, a exceo overflow
normalmente uma exceo pr-definida) e tambm permitir ao progra-
mador criar excees especficas para uma aplicao ou biblioteca (por
exemplo, uma aplicao de cadastro de produtos de uma loja pode neces-
sitar disparar uma exceo sinalizando a condio de tamanho do estoque
abaixo do normal).
Tal como quase tudo em C++ e JAVA, excees so implementadas atra-
vs de objetos. Para tanto, permitido criar classes descritoras de exce-

275
es. Embora sejam objetos de uma classe, excees so objetos com
uma caracterstica diferenciada dos demais objetos. Elas podem ser lan-
adas para outras partes do programa seguindo um fluxo de controle dis-
tinto dos conhecidos at agora. Esse fluxo determinado pelo mecanismo
de tratamento de excees de C++ e JAVA.
Por serem classes, as excees podem (e devem) ser organizadas dentro
de uma hierarquia de modo a descrever de forma natural o relacionamen-
to entre os diferentes tipos de excees. O exemplo 8.4 define uma hie-
rarquia de excees em C++.
class ErroMedico {};
class ErroDiagnostico: public ErroMedico {};
class ErroCirurgia: public ErroMedico {};
Exemplo 8. 4 - Hierarquia de Excees em C++
C++ possui apenas oito excees padro organizadas em uma hierarquia
prpria, a partir da classe exception. A figura 8.1 apresenta um esquema
parcial dessa hierarquia [STROUSTRUP, 2000].












Figura 8. 1 - Hierarquia de Excees Padro em C++ ( adaptado de [STROUSTRUP, 2000])
Como pode ser notado no exemplo 8.4, nem toda exceo de C++
derivada de exception, embora as excees padro sejam. Alm disso,
nem toda exceo derivada de exception uma exceo da biblioteca
padro. Os programadores podem acrescentar suas prprias excees
hierarquia de exception.
Em JAVA, ao contrrio de C++, toda exceo deve ser declarada como
instncia de uma subclasse de java.lang.Throwable, uma classe especial
de JAVA. Essa classe age como me de todos os objetos lanados e cap-
exception
logic_error runtime_error
bad_alloc
bad_exception
bad_cast
bad_typeid
ios_base::failure
overflow_error out_of_range

276
turados usando o mecanismo de tratamento de excees. Os principais
mtodos definidos na classe java.lang.Throwable recuperam a mensagem
de erro associada com a exceo e imprimem a pilha rastreada mostrando
onde ocorreu a exceo. Alguns mtodos de java.lang.Throwable so lis-
tados a seguir.
void printStackTrace - lista a seqncia de mtodos chamados at o
ponto onde a exceo foi lanada.
String getMessage - retorna o contedo de um atributo com uma
mensagem indicadora da exceo.
String ToString - retorna uma descrio da exceo e de seu conte-
do.
Existem trs categorias essenciais de excees em JAVA: Error, Runti-
meException, e Exception. Error e Exception so subclasses diretas de
Throwable e RuntimeException subclasse direta de Exception. A figura
8.2 ilustra essa hierarquia.










Figura 8. 2 - Principais Classes de Excees em JAVA
A classe Error indica um problema grave de difcil (seno, impossvel)
recuperao. Dois exemplos so: OutOfMemoryError e StackOverflo-
wError. No se pode esperar de um programa nessas condies ser exe-
cutado at o final. As excees desse tipo so tratadas pelo prprio JA-
VA, implicando normalmente em terminao do programa. Elas no so
usadas pelos programadores.
A classe Exception representa as excees lanadas por mtodos das clas-
ses da biblioteca padro do JAVA e pelos mtodos das classes dos aplica-
tivos. Excees das subclasses de Exception podem ser lanadas, captura-
das e tratadas pelos programadores.
Dentre as excees pr-definidas na biblioteca padro, a classe especial
RuntimeException se destaca por apresentar um comportamento diferen-
ciado. Elas no necessitam ser lanadas explicitamente pelo programa. O
sistema JAVA se incumbe de fazer isso quando elas ocorrem. Alm disso,
elas no necessitam ser tratadas obrigatoriamente pelo programador, em-
Throwable

Exception

Error

Outras Excees

RuntimeException


277
bora isto seja possvel. Exemplos desse tipo de excees so: NullPointe-
rException e IndexOutOfBoundsException.
Se, por um lado, a existncia desse tipo de excees em JAVA poupa o
programador de uma grande dose de trabalho (uma vez que ele no ne-
cessita identificar os pontos do programa onde a exceo ocorre, nem
tampouco precisa fornecer tratadores para essas excees), por outro lado,
isso torna os programas um pouco menos confiveis (uma vez que no se
exige o tratamento dessas excees).
As excees dessa natureza normalmente indicam problemas srios e de-
vem implicar na terminao do programa. Contudo, ao incluir esse tipo de
excees com comportamento diferenciado na linguagem, oferece-se ao
programador a opo de trat-las e, de alguma maneira, continuar a exe-
cuo do programa.
As outras subclasses de Exception, agrupadas na figura 8.2 pelo retngulo
tracejado, apresentam o comportamento padro, isto , precisam ser lan-
adas explicitamente no cdigo e, obrigatoriamente, necessitam ser trata-
das (ou propagadas) pelo programador.
Assim, o programador no ir usar diretamente nem a classe Throwable,
nem a classe Error. De fato, o programador normalmente utilizar uma
classe ou criar uma subclasse na hierarquia definida a partir de Excepti-
on. O exemplo 8.5 mostra a definio de uma exceo em JAVA.
class UmaExcecao extends Exception {
private float f;
public UmaExcecao(String msg, float x) {
super(msg);
f = x;
}
public float contexto() {
return f;
}
}
Exemplo 8. 5 - Definio de Exceo em JAVA
A definio de uma exceo em JAVA ocorre como a definio de uma
classe qualquer, como pode ser observado no exemplo 8.5. A exceo
UmaExcecao possui um atributo especfico f, um construtor e um mtodo
especfico chamado contexto. O nico requisito adicional de uma classe
exceo ser herdeira direta ou indireta de Exception. O construtor de
UmaExcecao usa a palavra super para chamar o construtor de Exception.

278
8.2.2 Sinalizao de Excees
A sinalizao de excees pode ser feita automaticamente pelo prprio
mecanismo de excees ou pode ser feita explicitamente pelo prprio
programador em casos especficos da aplicao. Enquanto no primeiro
caso as excees podem ser disparadas em qualquer ponto do programa,
de maneira geral, no segundo caso elas s podem ser sinalizadas em tre-
chos demarcados do programa.
Em C++ e JAVA, por exemplo, objetos de tipo exceo so criados atra-
vs do uso da clusula throw em trechos demarcados pelo bloco try. O
exemplo 8.6 mostra o disparo de uma exceo em C++.
try {
throw ErroMedico();
}
Exemplo 8. 6 - Sinalizao de Exceo em C++
A nica distino da sinalizao de excees de C++ para JAVA a re-
quisio por parte da ltima do uso do operador new para a criao do
objeto exceo. O exemplo 8.7 mostra o disparo de uma exceo em JA-
VA.
try {
throw new Exception();
}
Exemplo 8. 7 - Sinalizao de Exceo em JAVA
8.2.3 Tratadores de Excees
Tratadores de exceo so trechos de cdigo do programa responsveis
por tomar atitudes em resposta ocorrncia de uma exceo. Eles no so
chamados explicitamente e, por isso, no precisam possuir nome. Por ou-
tro lado, preciso haver uma forma de identificar o tratador a ser ativado
quando uma determinada exceo ocorre. Isso normalmente feito atra-
vs do casamento com o nome ou tipo da exceo.
Em C++ e JAVA, tratadores de excees so definidos atravs do uso de
uma ou mais clusulas catch colocadas imediatamente aps o encerra-
mento de um bloco try. Pode-se colocar quantos catch se desejar. A cada
catch est associada uma classe de excees, listada entre parnteses i-
mediatamente aps essa palavra, a qual permite manipular todas as exce-
es membros dessa classe. O cdigo dos tratadores de exceo coloca-
do logo aps a declarao da classe associada e tambm delimitado por
chaves. Apresenta-se no exemplo 8.7 um bloco try-catch completo em
JAVA.

279
String n = "635";
String d = "27";
try {
int num = Integer.valueOf(n).intValue();
int den = Integer.valueOf(d).intValue();
int resultado = num / den;
} catch (NumberFormatException e){
System.out.println ("Erro na Formatacao");
} catch (ArithmeticException e){
System.out.println ("Divisao por zero ");
}
Exemplo 8. 8 - Definio de Tratadores de Excees em JAVA
As operaes de atribuio s variveis inteiras num e den, no bloco try,
envolvem a converso das strings n e d para valores inteiros. Caso a for-
matao dessas strings no fosse adequada, ocorreria uma exceo a qual
seria capturada pelo tratador de excees definido na primeira clusula
catch. J a operao de diviso tambm poderia disparar uma exceo
aritmtica a qual seria capturada pelo tratador de excees definido na
segunda clusula catch.
Observe como o mecanismo de excees melhora a legibilidade do cdi-
go. Em uma linguagem sem tratamento de excees, aps a chamada de
cada operao de converso seria necessrio colocar testes para verificar
se ocorreu uma exceo. Isso implicaria na colocao de cdigo para ve-
rificao de erro entre as linhas de cdigo responsveis pela execuo da
funcionalidade desejada. Tal problema no ocorre com o mecanismo de
excees.
As operaes de converso de String podem disparar uma exceo do tipo
NumberFormatException e a operao de diviso pode disparar uma Ari-
thmeticException. O casamento do tipo da exceo disparada com o tipo
declarado na clusula catch determina qual dos tratadores deve ser execu-
tado.
O processo de casamento da exceo ocorrida com a exceo declarada
no tratador feito de maneira sucessiva, isto , tenta-se casar a exceo
com a declarada no primeiro catch e, se no houver casamento, tenta-se
casar com a declarada no segundo, e assim sucessivamente. Uma vez que
tenha havido o casamento em uma clusula catch, o cdigo corresponden-
te a esta clusula executado. Aps a execuo do cdigo de um tratador,
nenhum outro ser executado.
O casamento feito com qualquer membro da classe declarada no trata-
dor. Em outras palavras, o casamento acontece se a exceo ocorrida for
uma instncia da classe declarada ou de qualquer uma das suas subclas-

280
ses. Logo, nunca se deve declarar um tratador associado a uma classe an-
tes dos tratadores associados a qualquer de suas subclasses. Caso contr-
rio, os tratadores das subclasses nunca seriam ativados. No exemplo 8.9,
em C++, os tratadores das excees ErroDiagnostico e ErroCirurgia
nunca so executados.
try {
// codigo no qual varias excecoes podem ser sinalizadas
} catch (ErroMedico &e){
// trata qualquer erro medico
} catch (ErroDiagnostico &e){
// trata apenas erro de diagnostico
} catch (ErroCirurgia &e){
// trata apenas erro de cirurgia
}
Exemplo 8. 9 - Distribuio Inapropriada de Tratadores
Isso ocorre porque o tratador da exceo ErroMedico, superclasse de Er-
roDiagnostico e ErroCirurgia, aparece antes dos tratadores dessas exce-
es. Assim, a ocorrncia de qualquer exceo, seja ErroDiagnostico,
ErroCirurgia ou qualquer outra subclasse de ErroMedico ser sempre
capturada pelo primeiro tratador. Para colocar os tratadores de ErroDiag-
nostico e ErroCirurgia em atividade, basta colocar o tratador de ErroMe-
dico em ltimo lugar.
Em JAVA, como a classe Exception a classe me de todas as excees,
ela pode ser usada para capturar qualquer exceo. Como ela captura
qualquer exceo, ela deve ser sempre colocada no ltimo tratador asso-
ciado ao bloco try. O exemplo 8.10 mostra como isso pode ser feito no
exemplo da diviso.
try {
int num = Integer.valueOf(n).intValue();
int den = Integer.valueOf(d).intValue();
int resultado = num / den;
} catch (NumberFormatException e){
System.out.println ("Erro na Formatacao ");
} catch (ArithmeticException e){
System.out.println("Divisao por zero");
} catch (Exception e){
System.out.println ("Qualquer outra Excecao");
}
Exemplo 8. 10 - Captura de Qualquer Exceo em JAVA

281
Observe como o ltimo tratador passa a ser responsvel por tratar qual-
quer exceo disparada que no seja uma NumberFormatException ou
ArithmeticException.
Como no existe em C++ uma superclasse nica para todas as excees,
esse tipo de abordagem no pode ser usado. C++ utiliza o operador ... (re-
ticncias) para capturar as excees de qualquer tipo. Observe como ele
funciona no exemplo 8.11.
try {
// codigo que dispara excecoes
} catch (ErroDiagnostico &e){
// trata apenas erro de diagnostico
} catch (ErroCirurgia &e){
// trata apenas erro de cirurgia
} catch (ErroMedico &e){
// trata qualquer erro medico
} catch ( ... ) {
// trata qualquer outra excecao
}
Exemplo 8. 11 - Captura de Qualquer Exceo em C++
O ltimo catch captura qualquer exceo ocorrida no bloco try que no
seja um ErroDiagnostico, ErroCirurgia ou ErroMedico. O tratador com
reticncias deve necessariamente ser colocado na ltima clusula catch.
Caso contrrio, existiro tratadores que nunca sero ativados.
8.2.4 Propagao de Excees
Eventualmente podem ocorrer situaes nas quais o tratamento de exce-
o no deve ser realizado no mesmo bloco try-catch no qual a exceo
ocorre. Nesses casos, a exceo deve ser propagada para ser tratada por
tratadores associados a blocos try-catch mais externos.
Por isso, caso no se encontre o tratador correspondente exceo no
bloco try-catch no qual ela ocorre, ela propagada para um nvel mais
externo e assim sucessivamente.
Quando uma funo invocada lana uma exceo, cujo tratamento no
especificado na prpria funo, ela retornada para a funo chamadora
(apesar da funo chamada no possuir esse tipo de retorno!). Quando
isso ocorre a funo chamadora desvia o controle para o bloco de cdigo
de tratamento de excees ou, caso no possua tratamento prprio, retor-
na a exceo para o nvel mais externo. Esta propagao ocorre at a ex-
ceo ser capturada por um tratador ou at se atingir o nvel mais externo
do programa.

282
Caso uma exceo disparada no seja capturada por algum tratador, o
procedimento mais comum abortar a execuo do programa, apresen-
tando eventualmente uma mensagem indicando onde ocorreu a exceo.
Em C++, se uma exceo no for capturada por algum tratador, a funo
terminate ser chamada. A funo terminate contm um ponteiro para
funo cujo valor default aponta para a funo abort. Caso se queira
especificar outra funo de terminao, basta utilizar a funo
set_terminate para apontar terminate para essa outra funo. Em JAVA,
somente excees RuntimeException no necessitam ser capturadas obri-
gatoriamente por tratadores. Nesse caso, a execuo ser interrompida e
uma mensagem indicando informaes sobre a ocorrncia da exceo
apresentada. O exemplo 8.12 mostra como ocorre a propagao de uma
exceo em JAVA.
public static void main(String[] args) {
System.out.println("Bloco 1");
try {
System.out.println("Bloco 2");
try {
System.out.println("Bloco 3");
try {
switch(Math.abs(new Random().nextInt())%4+1){
default:
case 1: throw new NumberFormatException();
case 2: throw new EOFException();
case 3: throw new NullPointerException();
case 4: throw new IOException();
}
} catch (EOFException e) {
System.out.println("Trata no bloco 3");
}
} catch (IOException e) {
System.out.println("Trata no bloco 2");
}
} catch (NullPointerException e){
System.out.println("Trata no bloco 1");
}
}
Exemplo 8. 12 - Propagao de Excees entre Blocos try-catch de uma Mesma Funo em JAVA
No programa do exemplo 8.12, existem trs blocos try-catch aninhados.
Dentro do mais interno deles quatro diferentes tipos de excees podem
ser lanadas. No caso de ser lanada a EOFException, o mecanismo de
excees tentar casar essa exceo com a do tratador do bloco mais in-

283
terno. Nesse caso, haver casamento e o tratador do terceiro bloco ser
executado. Aps a execuo desse tratador, o programa se encerrar. Se
for lanada a IOException, o mecanismo de excees tentar casar essa
exceo com a do tratador do bloco mais interno. Nesse caso, no haver
casamento e a exceo ser propagada para o segundo bloco, no qual ha-
ver casamento. O tratador do segundo bloco ser executado e o progra-
ma se encerrar. Caso a exceo lanada seja NullPointerException, o
processo se repetir, mas somente haver casamento no tratador do pri-
meiro bloco. Por fim, se a exceo lanada for NumberFormatException,
ela no se casar com nenhum tratador. Por se tratar de uma RuntimeEx-
ception, que no exige tratamento, ela ser propagada para fora do mto-
do main e o programa se encerrar mostrando a mensagem indicando on-
de ocorreu a exceo.
8.2.5 Relanamento de Excees
Algumas vezes preciso tratar parcialmente uma exceo em um deter-
minado bloco try e relan-la para ser tratada por outro tratador em um
bloco try mais externo. Isso normalmente ocorre devido a falta de infor-
mao necessria no bloco try no qual ocorreu a exceo para promover o
seu tratamento completo. Veja como isso pode ser feito em JAVA no e-
xemplo 8.13.
public static void main(String[] args) {
try {
try {
throw new IOException();
} catch (IOException e) {
System.out.println("Trata primeiro aqui");
throw e;
}
} catch (IOException e) {
System.out.println("Continua tratando aqui ");
}
}
Exemplo 8. 13 - Relanamento de Exceo em JAVA
No exemplo 8.13, uma exceo lanada no bloco try mais interno e
capturada pelo tratador desse bloco. Ao final da execuo do cdigo des-
se tratador, a exceo capturada lanada novamente para ser capturada
agora pelo tratador do bloco try-catch mais externo. Observe que o relan-
amento tambm foi realizado atravs do uso da clusula throw dentro do
tratador de excees do bloco try-catch mais interno.

284
8.2.6 Especificao de Excees
Em certas situaes interessante permitir a um subprograma lanar uma
exceo sem, contudo, trat-la. Nesse caso, quando a exceo ocorrer, ela
ser propagada para o trecho de cdigo invocador do subprograma, o qual
dever trat-la ou repropag-la, conforme o caso.
Portanto, ao se escrever um programa no qual se precise chamar um de-
terminado subprograma, necessrio saber quais possveis excees esse
subprograma pode disparar. Assim, o usurio desse subprograma pode
providenciar o tratamento ou repropagao das excees disparveis.
Uma forma possvel de identificar quais excees podem ser disparadas
por um subprograma seria consultar o cdigo fonte do subprograma. Essa
forma inconveniente por duas razes. Primeiramente se trata de um pro-
cedimento muito trabalhoso, uma vez que seria necessrio, alm de con-
sultar o cdigo do subprograma, consultar os cdigos dos subprogramas
chamados por esse subprograma e assim sucessivamente. A segunda ra-
zo decorre do fato dos cdigos fontes dos subprogramas no estarem
disponveis para os usurios desse subprograma em muitas situaes.
Outra possibilidade seria deixar para o compilador indicar quais excees
precisam ser tratadas. Essa possibilidade tambm no adequada porque
o programador necessitaria compilar o cdigo para ento saber quais ex-
cees precisa tratar. Alm disso, o processo de verificao de excees
na compilao de um arquivo usurio de subprogramas compilados previ-
amente em outros arquivos de difcil execuo quando os fontes desses
mdulos no esto disponveis.
A soluo frequentemente adotada para informar ao usurio do subpro-
grama quais excees deve tratar requer a especificao dessas excees
no cabealho do subprograma. Agora, para o usurio saber quais exce-
es um dado subprograma pode disparar e no tratar, basta olhar para o
cabealho desse subprograma, no sendo mais necessrio consultar o c-
digo fonte que o implementa.
Alm disso, esse procedimento pode facilitar o processo de verificao
das excees durante a compilao. Analisando apenas o cdigo de um
subprograma, o compilador pode indicar facilmente se ele dispara exce-
es no tratadas em sua implementao e no especificadas em seu ca-
bealho. Do mesmo modo, o compilador pode indicar se um subprograma
chama outro subprograma e no trata ou repropaga as excees dispar-
veis pelo subprograma chamado.
Em C++, throw tambm pode ser usado para indicar aos usurios de uma
funo quais excees podem ser lanadas. Existem trs especificaes
possveis, ilustradas no exemplo 8.14.

285
void f() throw (A,B,C);
void g() throw();
void h();
Exemplo 8. 14 - Formas de Especificao de Exceo em C++
A primeira forma ilustrada no exemplo 8.14 indica que a funo f pode
lanar excees do tipo A, B ou C. A segunda maneira indica que a fun-
o g no pode lanar excees. O terceiro modo indica que a funo h
pode lanar qualquer exceo.
O compilador C++ no verifica se o compromisso assumido na especifi-
cao est sendo cumprido. Portanto, se a funo f lanar uma exceo D,
isso s ser tratado em tempo de execuo. Nesse caso, a funo unexpec-
ted ser executada. A funo set_unexpected pode ser usada para deter-
minar o que unexpected deve fazer. O exemplo 8.15 mostra como isso
pode ser feito.
class ErroI { };
class ErroII { };
void f () throw (ErroI) {
throw ErroII();
}
void exc() {
cout <<"erro nao esperado";
exit(1);
}
main() {
set_unexpected(exc);
try {
f ();
} catch (ErroI){
cout<<"erro em f";
}
}
Exemplo 8. 15 Descumprimento de Especificao de Exceo pela Prpria Funo em C++
No exemplo 8.15, quando a funo f for executada, ser disparada a exce-
o ErroII, a qual no tratada ou repropagada pela funo f. Isso causar
a execuo da funo exc.
C++ tambm no obriga a funo chamadora a tratar todas as excees
possveis de serem geradas pela funo chamada. O cdigo do exemplo
8.16 ser aceito pelo compilador e interrompido em tempo de execuo.
class ErroI { };
class ErroII { };

286
void f () throw (ErroI) {
throw ErroI();
}
void exc(){
cout <<"erro nao esperado";
exit(1);
}
main() {
set_unexpected(exc);
try {
f ();
} catch (ErroII) {
cout<<"erro em f";
}
}
Exemplo 8. 16 - Descumprimento de Especificao de Exceo pela Funo Invocante em C++
JAVA exige a especificao das excees no tratadas nos cabealhos
dos mtodos, os quais no sejam RuntimeException, atravs do uso da
clusula throws. O exemplo 8.17 ilustra o uso dessa clusula em JAVA.
public static void main(String[] args) throws IOException {
System.out.println("Bloco 1");
try {
System.out.println("Bloco 2");
try {
System.out.println("Bloco 3");
try {
switch(Math.abs(new Random().nextInt())%4+1){
default:
case 1: throw new NumberFormatException();
case 2: throw new EOFException();
case 3: throw new NullPointerException();
case 4: throw new IOException();
}
} catch (EOFException e) {
System.out.println("Trata no bloco 3");
}
} catch (NumberFormatException e) {
System.out.println("Trata no bloco 2");
}
} catch (NullPointerException e){
System.out.println("Trata no bloco 1");
}

287
}
Exemplo 8. 17 - Especificao de Excees em JAVA
Observe a declarao da clusula throws logo aps a lista de parmetros
do mtodo main no exemplo 8.17. Ela indica que, dentro do mtodo ma-
in, pode ocorrer uma exceo IOException, a qual no ser tratada. Por-
tanto, a ocorrncia dessa exceo implicar na sua propagao para o c-
digo no qual o mtodo for chamado. Como IOException no uma Run-
timeException, se a clusula throws no fosse utilizada, o compilador re-
portaria um erro de no tratamento de exceo.
A imposio de JAVA para que todo mtodo tenha de indicar as excees
propagveis em seu cabealho serve para alertar explicitamente os poten-
ciais usurios daquele mtodo sobre tal possibilidade. Esse outro aspec-
to positivo de JAVA. Isso facilita o trabalho dos usurios pois no mais
precisam ler a implementao do mtodo para descobrirem quais exce-
es podem ser propagadas por ele. O exemplo 8.18 mostra a reimple-
mentao do exemplo 8.17 enfocando a propagao de excees entre
mtodos.
public static void main(String[] args) throws IOException {
System.out.println("Bloco 1");
try {
primeiro();
} catch (NullPointerException e){
System.out.println("Trata no bloco 1");
}
}
public static void primeiro() throws IOException,
NullPointerException {
System.out.println("Bloco 2");
try {
segundo();
} catch (NumberFormatException e) {
System.out.println("Trata no bloco 2");
}
}
public static void segundo() throws IOException,
NullPointerException {
System.out.println("Bloco 3");
try {
switch(Math.abs(new Random().nextInt())%4+1) {
default:
case 1: throw new NumberFormatException();
case 2: throw new EOFException();

288
case 3: throw new NullPointerException();
case 4: throw new IOException();
}
} catch (EOFException e) {
System.out.println("Trata no bloco 3");
}
}
Exemplo 8. 18 - Propagao de Excees entre Mtodos em JAVA
Ao contrrio de IOException e NullPointerException, NumberFormatEx-
ception no necessita ser especificada no cabealho da funo segundo
porque ela uma RuntimeException.
8.2.7 Modos de Continuao Aps o Tratamento de Excees
Quando um programa executa sem a ocorrncia de excees ele segue o
seu fluxo de controle normal. Contudo, para onde deve ir o fluxo de con-
trole aps a ocorrncia e o tratamento de excees? Para responder a essa
pergunta importante ter em mente o fato de, em muitos casos, o local de
tratamento da exceo se encontrar bem distante do ponto no qual ela o-
corre.
Em geral, existem dois modos de continuao do fluxo de controle aps o
tratamento das excees:
Terminao: assume o erro como crtico, no existindo condies
de retornar ao ponto no qual a exceo foi gerada. O controle re-
torna para um ponto mais externo do programa. Nessa alternativa
todas as unidades na pilha de registros de ativao, a partir da uni-
dade na qual ocorreu a exceo at a unidade anterior a que o trata-
dor de exceo foi executado, so encerradas. A execuo do pro-
grama continua na unidade na qual o tratador foi encontrado, aps
a regio de tratamento.
Retomada: assume o erro como corrigvel e a execuo pode retor-
nar para o bloco no qual ocorreu a exceo. Portanto, o retorno
feito para o bloco gerador do erro. Embora, primeira vista, essa
soluo parea ser a mais apropriada, uma vez que a exceo seria
supostamente tratada, a experincia tem indicado uma baixa efeti-
vidade dessa alternativa.
Como consequncia da baixa efetividade do modelo de retomada, a maio-
ria das LPs tem adotado atualmente o modelo de terminao. Essa a
postura de C++ e JAVA. O exemplo 8.19, em C++, mostra como funcio-
na o modo de continuao por terminao.
class ErroI { };

289
class ErroII { };
class ErroIII { };
void f () throw (ErroI) {
throw ErroI();
}
main() {
cout << "comeca aqui\n";
try {
cout << "passa por aqui\n";
try {
f ();
} catch (ErroIII){
cout<<"no passa por aqui\n";
}
cout<<"tambm no passa por aqui\n";
} catch (ErroI){
cout<<"erro I em f\n";
} catch (ErroII){
cout<<"no passa por aqui\n";
}
cout << "termina aqui\n";
}
Exemplo 8. 19 - Modo de Continuao por Terminao em C++
Durante a execuo de f, a exceo ErroI disparada. Ela no capturada
pelo tratador associado ao bloco try-catch no qual ocorreu a exceo (o
nico tratador existente para ErroIII). A exceo propagada ento pa-
ra o bloco try-catch mais externo, onde capturada pelo primeiro trata-
dor. Ao final da execuo desse tratador, o programa continua a partir do
final do bloco de cdigo do ltimo tratador de excees associado ao blo-
co try-catch mais externo.
Muito embora o modo de continuao por retomada no seja efetivo na
maioria das ocorrncias de excees, em algumas situaes, pode ser pos-
svel e desejvel tratar um erro e tentar repetir a execuo do trecho de
cdigo no qual ele ocorre. O exemplo 8.20 mostra como isso pode ser
feito em JAVA.
import java.util.*;
public class Retomada {
static class ImparException extends Exception {}
public static void main(String[] args) {
boolean continua = true;
Random r = new Random();
while (continua) {

290
continua = false;
try {
System.out.print ("Escolha um numero par: ");
int i = r.nextInt();
if (i%2 != 0) throw new ImparException();
} catch(ImparException e) {
System.out.println("Tente novamente!!!");
continua = true;
}
}
}
}
Exemplo 8. 20 - Implementao de Modo de Continuao por Retomada em JAVA
A abordagem apresentada no exemplo 8.20 s pode ser utilizada quando
o tratamento da exceo ocorre no mesmo bloco try-catch no qual a exce-
o ocorreu.
Em certas situaes pode ser interessante executar um trecho de cdigo
associado ao bloco try-catch independentemente da ocorrncia do trata-
mento da exceo no mesmo bloco try-catch ou no. Em JAVA, usa-se a
clusula finally colocada aps o ltimo tratador de exceo para obter es-
sa funcionalidade. O exemplo 8.21 ilustra o uso desse mecanismo.
public class Sempre {
public static void main(String[] args) {
System.out.println("Bloco 1");
try {
System.out.println("Bloco 2");
try {
throw new Exception();
} finally {
System.out.println("finally do bloco 2");
}
} catch(Exception e) {
System.out.println("Excecao capturada");
} finally {
System.out.println("finally do bloco 1");

}
}
Exemplo 8. 21 - Clusula finally em JAVA
No exemplo 8.21 tanto o finally do bloco mais interno quanto o do mais
externo sero executados. Como pode ser observado no bloco mais inter-

291
no, possvel ter blocos try sem associao de clusulas catch. Porm,
no pode haver blocos try sem que haja pelo menos uma associao de
clusulas catch ou finally.
A clusula finally muito usada quando se necessita restabelecer um es-
tado de um objeto independentemente da ocorrncia e propagao de ex-
cees ou no. No exemplo 8.22, o carro tem de ser desligado aps a mo-
vimentao independentemente de ter havido fogo ou superaquecimento.
public class CarroBomba {
class SuperAquecimentoException extends Exception {}
class FogoException extends Exception {}
Random r = new Random();
public void ligar() {}
public void mover() throws SuperAquecimentoException,
FogoException {
float temperatura = r.nextFloat();
if (temperatura > 100.0) {
throw new SuperAquecimentoException();
}
throw new FogoException();
}
public void desligar() {}
public static void main(String[] args) {
try {
CarroBomba c = new CarroBomba();
try {
c.ligar();
c.mover();
} catch(SuperAquecimentoException e) {
System.out.println("vai fundir o motor!!!");
} finally {
c.desligar();
}
} catch (FogoException e) {
System.out.println ("vai explodir!!!");
}
}
}
Exemplo 8. 22 - Clusula finally para Garantir Procedimento de Finalizao em JAVA
Na ocorrncia de uma FogoException o carro s desligado por causa do
uso da clusula finally. Se essa clusula no existisse e o comando de des-
ligar fosse colocado aps o trmino do bloco try-catch mais interno,
quando ocorresse uma FogoException, o fluxo de controle do programa

292
seria direcionado diretamente para o tratador de excees do bloco try-
catch mais externo.
A existncia da clusula finally no mecanismo de excees em JAVA
trouxe um problema para a linguagem: uma exceo pode ocorrer e ser
ignorada [ECKEL, 2002]. O exemplo 8.23 ilustra uma situao na qual
isso pode ocorrer.
public class Perda {
class InfartoException extends Exception {
public String toString() { return "Urgente!"; }
}
void infarto() throws InfartoException {
throw new InfartoException ();
}
class ResfriadoException extends Exception {
public String toString() { return "Descanse!"; }
}
void resfriado() throws ResfriadoException {
throw new ResfriadoException ();
}
public static void main(String[] args) throws Exception {
Perda p = new Perda();
try {
p.infarto();
} finally {
p.resfriado();
}
}
}
Exemplo 8. 23 - Perda de Exceo em JAVA
No exemplo 8.23, a exceo InfartoException no tratada no bloco try-
catch no qual ocorre. Assim, o fluxo de execuo se direciona diretamen-
te ao bloco da clusula finally, no qual uma nova exceo (ResfriadoEx-
ception) ocorre. Com isso, a primeira exceo ignorada e a nova exce-
o passa a ser o foco de tratamento e propagao.
8.2.8 Excees e Polimorfismo
A combinao dos mecanismos de orientao a objetos, tais como heran-
a e amarrao tardia de tipos, com os mecanismos de tratamento de ex-
cees aumenta a complexidade da linguagem. De um modo geral, so
estabelecidas regras na linguagem para garantir o uso apropriado do me-
canismo de excees. JAVA, por exemplo, estabelece as seguintes regras:

293
1. Os construtores podem adicionar novas excees a serem propaga-
das s declaradas no construtor da superclasse.
2. Os construtores devem necessariamente propagar as excees de-
claradas no construtor da superclasse usado. Se o construtor da su-
perclasse pode propagar excees, o da subclasse tambm dever
propag-las pois o ltimo necessariamente chama o primeiro.
3. Mtodos declarados na superclasse no podem ter novas excees
propagadas. Afinal, o cdigo usurio dos objetos da superclasse
deve ser capaz de lidar com os objetos da subclasse. Se novas ex-
cees pudessem ser propagadas, elas poderiam ser disparadas e
aquele cdigo no as trataria.
4. No obrigatrio propagar as excees dos mtodos da superclas-
se. Isso no problema porque o cdigo usurio do mtodo sim-
plesmente tratar uma exceo que no ocorrer quando ele for
chamado por um objeto da subclasse.
5. Os mtodos sobrescritos podem disparar excees que sejam sub-
classes das excees propagadas na superclasse. Isso no proble-
ma porque o tratador da superclasse captura todas as excees que
sejam subclasses da classe associada ao tratador.
O exemplo 8.24 ilustra como o mecanismo de excees se integra orien-
tao a objetos em JAVA. No exemplo so definidas inicialmente algu-
mas classes de excees organizadas em hierarquias. definida tambm
uma classe abstrata Dirigir.
As especificaes do construtor dessa classe e o mtodo irTrabalhar in-
dicam a propagao da exceo InfraoTrnsito. Contudo, os corpos
desses mtodos no disparam exceo alguma. Isso no causa maiores
problemas porque o cdigo usurio desses mtodos ter necessariamente
de tratar ou propagar a exceo especificada. Por outro lado, isso muito
til porque permite a programao do cdigo usurio sem que a imple-
mentao desses mtodos esteja concluda, aumentando assim a produti-
vidade da equipe de programao, a qual pode trabalhar em paralelo de-
senvolvendo o cdigo da classe e o cdigo usurio. Alm disso, essa ca-
racterstica tambm permite preparar uma verso inicial do programa para
uma futura reimplementao desses mtodos (agora disparando a exce-
o) sem que a insero do cdigo lanador da exceo implique na ne-
cessidade de alterao do cdigo usurio.
class InfracaoTransito extends Exception {}
class ExcessoVelocidade extends InfracaoTransito {}
class AltaVelocidade extends ExcessoVelocidade {}
class AvancarSinal extends InfracaoTransito {}
class Acidente extends Exception {}
class Batida extends Acidente {}

294
abstract class Dirigir {
Dirigir() throws InfracaoTransito { }
void irTrabalhar () throws InfracaoTransito {}
abstract void viajar() throws ExcessoVelocidade, AvancarSinal;
void caminhar() {}
}
interface Perigo {
void irTrabalhar () throws Batida;
void congestionamento() throws Batida;
}
public class DirecaoPerigosa extends Dirigir implements Perigo {
DirecaoPerigosa() throws Batida, InfracaoTransito {}
DirecaoPerigosa (String s) throws ExcessoVelocidade,
InfracaoTransito {}
// void caminhar() throws AltaVelocidade {}
// public void irTrabalhar() throws Batida {}
public void irTrabalhar() {}
public void congestionamento() throws Batida {}
void viajar() throws AltaVelocidade {}
public static void main(String[] args) {
try {
DirecaoPerigosa dp = new DirecaoPerigosa ();
dp.viajar ();
} catch(AltaVelocidade e) {
} catch(Batida e) {
} catch(InfracaoTransito e) {}
try {
Dirigir d = new DirecaoPerigosa();
d.viajar ();
} catch(AvancarSinal e) {
} catch(ExcessoVelocidade e) {
} catch(Batida e) {
} catch(InfracaoTransito e) {}
}
}
Exemplo 8. 24 - Excees e Polimorfismo
No exemplo 8.24 tambm so definidas a interface Perigo e uma subclas-
se de Dirigir, denominada DirecaoPerigosa, que implementa Perigo. As
especificaes dos mtodos de Perigo indicam a propagao da exceo
Batida.
Como DirecaoPerigosa subclasse de Dirigir, seus construtores devem
necessariamente propagar a exceo propagada no construtor de Dirigir.

295
Como pode ser observado no exemplo, os construtores de DirecaoPeri-
gosa propagam outras excees alm da propagada pelo construtor de
Dirigir.
A implementao dos mtodos caminhar e irTrabalhar de DirecaoPeri-
gosa se encontram comentadas porque elas indicam a propagao de ex-
cees no listadas na especificao desses mtodos na superclasse Diri-
gir. Isso geraria erro de compilao, caso no estivessem como coment-
rios. Uma implementao vlida de irTrabalhar includa no exemplo
para mostrar que mtodos sobrescritos no necessitam propagar as exce-
es propagadas na implementao desses mtodos na superclasse.
A implementao do mtodo viajar de DirecaoPerigosa mostra que m-
todos sobrescritos podem disparar excees de subclasses da exceo
propagada no mtodo da superclasse.Por fim, o mtodo main mostra a
diferena nas exigncias de tratamento de excees quando se utiliza re-
ferncias para DirecaoPerigosa e para Dirigir. No primeiro caso, s
necessrio tratar as excees propagadas pelo construtor de DirecaoPeri-
gosa e pelo mtodo viajar dessa classe. No segundo caso, mesmo que o
objeto criado seja um DirecaoPerigosa, o compilador exige que sejam
tratadas as excees propagadas pelo mtodo viajar de Dirigir. Isso ocor-
re porque diferentes implementaes do mtodo viajar podem ser utiliza-
das, dependendo da classe do objeto referenciado.
8.3 Consideraes Finais
Nesse captulo foi visto como funcionam os mecanismos de tratamento de
excees e quais as suas vantagens. Eles melhoram a legibilidade dos
programas pois separam o cdigo com a funcionalidade principal do pro-
grama do cdigo responsvel pelo tratamento de excees. Alm disso, os
mecanismos de tratamento de excees tambm aumentam a confiabili-
dade e robustez dos programas, uma vez que normalmente requerem o
tratamento obrigatrio das excees ocorridas e porque promovem a idia
de recuperao dos programas mesmo na presena de situaes anmalas.
Outros benefcios dos mecanismos de tratamento de excees so incen-
tivar o reuso e a modularidade do cdigo responsvel pelo tratamento.
Em particular, em linguagens orientadas a objetos, excees so instn-
cias de classes, o que faz com que essa parte do programa herde todas as
propriedades relativas ao reuso e a modularidade fornecidas pela progra-
mao orientada a objetos.
Tratadores de exceo oferecem um mecanismo de fluxo de controle dife-
renciado do oferecido por subprogramas. Ao se chamar um subprograma,
o fluxo de controle transferido para o subprograma e, aps sua execu-
o, o fluxo de controle retorna para o ponto onde o subprograma foi

296
chamado. J no caso do lanamento de uma exceo, o fluxo de controle
transferido para um ponto, na cadeia dinmica formada pelos sucessivos
blocos try-catch, invocados para chegar ao ponto de ocorrncia da exce-
o, no qual existe um tratador capacitado a lidar com aquela exceo.
Tambm diferentemente do fluxo de subprogramas, aps o tratamento da
exceo, o fluxo de controle passa para o final do bloco try-catch no qual
a exceo foi tratada, ao invs de retornar para o ponto no qual a exceo
foi disparada. A figura 8.3 ilustra as diferenas entre o fluxo de controle
de subprogramas e tratamento de excees.
No fluxo de controle normal do programa, indicado pela sequncia de
setas 1-2-3-6, no h ocorrncia de excees. Quando a exceo A ocorre,
o fluxo de controle indicado pela sequncia 1-2-4-6. J na ocorrncia da
exceo B, o fluxo de controle indicado pela sequncia 1-2-5. Nesse
ltimo caso, o fluxo de controle no retornado para a funoI, que cha-
mou a funoII, na qual ocorreu a exceo.














Figura 8. 3 - Fluxo de Controle de Excees
A combinao dos mecanismos de orientao a objetos com os de trata-
mento de excees traz uma maior complexidade para a linguagem, tor-
nando mais difcil o aprendizado da linguagem e podendo induzir a ocor-
rncia de erros de programao. Questes sobre como a linguagem inte-
gra o tratamento de excees com os conceitos de polimorfismo, herana,
inicializao e destruio de objetos devem ser esclarecidas antes do uso
main {
.
.
try {
.
funoI();
.
.
.
.
} catch(B){
.
.
}
.
.
}

funoI {
.
try {
.
funoII();
.
}catch(A){
.
.
}
}
funoII {
.
throw A();
.
throw B();
.
.
}
3
4
5
6 2
1

297
do tratamento de excees associado a esses conceitos. Por exemplo, em
C++, preciso saber que na ocorrncia de uma exceo, todos objetos
criados pelo bloco de cdigo responsvel pela exceo so automatica-
mente destrudos, desde que seus construtores tenham sido completados.
Se a exceo for gerada dentro de um construtor, ento recursos j aloca-
dos por esse construtor no sero liberados.
A incluso do mecanismo de tratamento de excees em uma LP pode
reduzir a eficincia computacional dos programas nessa linguagem. Isso
ocorre principalmente porque os programas em linguagens no possuido-
ras desse mecanismo normalmente no fazem tratamento de excees al-
gum. Por exemplo, um programa com uso de vetores em JAVA menos
eficiente do que um em C porque o mecanismo de excees de JAVA
verifica automaticamente a legalidade dos acessos aos ndices do vetor, o
que no ocorre em C.
Ao se comparar o tratamento de excees em C++ e JAVA, observa-se
que essas linguagens implementam mecanismos semelhantes, mas com
diferenas significativas entre eles.
Inicialmente, o mecanismo de excees no foi implementado em C++.
Posteriormente, esse mecanismo foi includo inspirado na implementao
de ADA. Para manter a compatibilidade com verses anteriores de C++ e
para no comprometer a eficincia computacional dos programas, em
comparao aos programas em linguagem C, no foi adotada uma postura
mais rigorosa no mecanismo de tratamento de excees de C++.
Os pontos seguintes sintetizam fragilidades do mecanismo de C++:
nmero reduzido de excees pr-definidas na biblioteca padro;
as funes no so obrigadas a especificar as excees que podem
propagar;
no deteco em tempo de compilao da quebra de compromisso
com uma dada especificao (lanamento de exceo no prevista
na especificao);
no existe obrigao de explicitar a exceo relanada para o nvel
superior.
JAVA baseou-se em C++ para implementar um mecanismo de tratamento
de excees bastante seguro, claro e que favorece a construo de softwa-
re confivel. Esse mecanismo torna praticamente impossvel a construo
de software sem utilizao de um bom esquema de tratamento de exce-
es.

298
JAVA oferece vrios tipos de excees pr-definidas verificadas automa-
ticamente, tais como, excees capazes de verificar o acesso indevido a
vetores e a realizao inapropriada da operao de estreitamento.
JAVA tambm apresenta a vantagem de constatar em tempo de compila-
o a utilizao indevida do mecanismo de tratamento de excees. Caso
uma exceo disparada no seja tratada ou propagada, o compilador indi-
car um erro de no tratamento dessa exceo.
Apesar disso, JAVA deixa uma brecha para o no tratamento de excees
do tipo RuntimeException. Para excees desse tipo, o compilador no
indica erro quando elas no so tratadas ou propagadas. Embora isso sa-
crifique um pouco do rigor do mecanismo de excees de JAVA, essa
opo foi escolhida para livrar o programador do desconforto de ficar es-
pecificando vrios tipos de excees freqentes, reduzindo assim a redi-
gibilidade dos programas em JAVA.
Uma linguagem com um mecanismo de excees bastante interessante
EIFFEL [MEYER, 1988]. Essa LP baseia seu mecanismo de tratamento
de exceo na filosofia de projeto por contrato. Nessa concepo, clientes
e fornecedores honram seus compromissos de modo a evitar o surgimento
de excees.
Uma rotina cliente deve garantir o cumprimento de pr-condies ao
chamar uma rotina. Essa, por sua vez, deve garantir o cumprimento de
ps-condies. A rotina cliente se beneficia das ps-condies e a rotina
servidora se beneficia das pr-condies. Esse contrato especificado
formalmente na rotina servidora. Alm das asseres de pr-condies e
ps-condies, podem ser especificadas tambm asseres invariantes
que devem ser vlidas antes e aps a execuo da rotina. Violaes dessas
asseres ativam o mecanismo de tratamento.
Assim, EIFFEL apresenta um mecanismo mais estruturado se comparado
com os oferecidos por JAVA e C++, uma vez que as excees so inseri-
das no programa como efeito do mtodo de programao por contrato.
Em contraste, as excees de JAVA e C++ so inseridas nos programas
sem uma disciplina de programao, isto , sem um critrio sistemtico
sobre quando e como importante utilizar o mecanismo de excees. Ou-
tra diferena significativa em EIFFEL a possibilidade de se adotar tanto
o modo de continuao por retomada quanto por terminao.
8.4 Exerccios
1. Explique as vantagens de se possuir um mecanismo de excees in-
corporado a LP. Ilustre essas vantagens apresentando exemplos de c-
digo com funcionalidade equivalente em C e JAVA.

299

2. Erros ordinrios podem ser tratados no mesmo ambiente no qual fo-
ram identificados. Cite vantagens no uso de um mecanismo de exce-
es como o de JAVA para o tratamento desse tipo de erro.


3. Analise o seguinte trecho de programa em C:




































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

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

300


Considere que as funes leNomeArq, abreArq, carregaArq e fechaArq
retornam 0 (zero) se no forem bem sucedidas e 1 (um), caso contrrio.
Considere tambm que a funo continua pergunta ao usurio se ele dese-
ja tentar novamente e retorna 1 (um) em caso afirmativo e 0 (zero) em
caso negativo. Refaa esse programa usando o mecanismo de tratamento
de excees de C++. Na verso em C++, as funes leNomeArq, abreArq,
carregaArq e fechaArq retornam void mas disparam respectivamente as
seguintes excees nomeExc, arqExc, cargaExc e fechaExc. Compare as
duas solues em termos de redigibilidade e legibilidade, justificando.

4. Considere o seguinte esqueleto de programa em C++:


























Indique, para cada possvel exceo disparada, o local onde ela ser trata-
da.

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

throw j;

throw g;

}catch(int){

}

}catch (float){

}

}
}
main ( ) {
A a;

a.f2 ( );

}

301
5. Explique os mecanismos oferecidos por C, C++ e JAVA para o trata-
mento de excees. Enfoque sua explicao na comparao dos se-
guintes aspectos: a) obrigatoriedade ou no do tratamento de excees
por um usurio de uma funo que dispara excees; b) existncia de
excees disparadas pelo prprio mecanismo de excees da lingua-
gem (tal como quando ocorre diviso por zero).

6. Considere o seguinte trecho de cdigo 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 cdigo acima apresenta dois erros identificveis em tempo de
compilao. Que erros so esses? Justifique sua resposta.

302

7. Apresente as abordagens que linguagens como C podem usar para li-
dar com erros em situaes nas quais no h conhecimento suficiente
para tratar o erro no local onde ele ocorre. Essas abordagens devem
passar as informaes do erro para um contexto mais externo para que
ele possa ser tratado. Enumere e explique os problemas com cada uma
delas.

8. Embora o esqueleto de programa JAVA seguinte seja vlido sintati-
camente, ele no se comporta apropriadamente em uma situao espe-
cfica (considere que uma operao s completada se realizada sem
ocorrncia de exceo). Identifique que situao essa. Justifique sua
resposta. Reformule o programa para que esse problema seja corrigi-
do.
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();
c.mover();
} catch(SemArranque e) {
System.out.println("tem de empurrar!!!");
} catch(SuperAquecim e) {
System.out.println("vai fundir!!!");
} finally {
c.desligar();
}
}
}


303
9. Ao se compilar o seguinte programa JAVA ocorre um erro de compi-
lao relacionado ao uso de excees. Identifique qual esse erro, a-
tentando para o fato que nenhuma exceo disparvel no programa
herdeira de RuntimeException, e diga como voc o corrigiria. Consi-
derando que o problema foi corrigido tal como voc props, mostre o
que ser impresso durante a execuo do programa. Mostre o que seria
impresso caso o valor atribudo a varivel i do mtodo 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");
}
}


304
10. Existem posies controversas com relao a incorporao de um me-
canismo rigoroso de tratamento de excees em LPs. Enquanto alguns
defendem o rigor, outros preferem um mecanismo mais flexvel. Por
exemplo, alguns programadores JAVA so defensores do uso exclusi-
vo das excees da classe RuntimeException ou de suas subclasses.
Indique a caracterstica dessas classes de exceo que justifica essa
postura. Apresente argumentos favorveis e contrrios a posio ado-
tada por esses programadores.

305

Captulo IX Concorrncia
Autores:
Jociel Cavalcante Andrade
Mariella Berger
Flvio Miguel Varejo


Nao a mais orte das espcies que sobreie, nem a mais inteligente, mas a que melhor
responde a mudana`
Charles Darwin
Os programas de computador nada mais so do que seqncias de instru-
es passveis de serem executadas por um processador. Eles no apre-
sentam comportamento nem possuem estado. De fato, programas so, em
ltima instncia, os contedos dos arquivos. Todas as cpias de um mes-
mo programa so idnticas.
Programas em execuo so chamados de processos. Ao contrrio dos
programas, os processos so entidades ativas e, mesmo quando se referem
ao mesmo programa, podem apresentar comportamento e caractersticas
diferentes em funo do contexto. Por exemplo, possvel ter duas ins-
tncias de um jogo de damas em execuo, ou seja, dois processos origi-
nrios de um mesmo programa. Ao longo das jogadas, a disposio das
peas nos dois tabuleiros se torna distinta para cada instncia do jogo.
Os primeiros sistemas de computao s permitiam a execuo de um
programa de cada vez. Apenas um processo utilizava o processador at
seu trmino sem sofrer interrupes. Assim, todos os recursos do sistema
(impressora, udio, vdeo, memria, etc.) ficavam disponveis quele pro-
cesso no momento em que ele precisasse. O problema com esse tipo de
computao a ociosidade do processador enquanto espera o acesso a
dispositivos externos. Essa espera pode ser longa. Como exemplo, se um
processo comea a imprimir um documento, o processador estar ocioso
at o trmino da impresso, embora pudesse estar sendo utilizado para
executar instrues de outro processo.
Os sistemas computacionais modernos conseguem executar simultanea-
mente mltiplos processos. Um processador consegue processar apenas
uma instruo por vez. Entretanto, o sistema operacional estabelece fatias
de tempo (time slices) para que cada processo tenha posse do pro-
cessador por um certo perodo, dando a impresso de que eles esto sendo
executados ao mesmo tempo. Essa caracterstica pode ocasionar uma si-
tuao em que mais de um processo requisite os servios de um mesmo
dispositivo computacional do sistema. Por exemplo, um processo em exe-
306
cuo inicia a impresso de um documento. Em seguida, o sistema opera-
cional alterna a execuo para outro processo, que tambm solicita a im-
presso de um documento. Isso produziria como resultado a impresso de
parte do documento do primeiro processo seguida de parte do documento
do segundo processo, e assim sucessivamente
9.1
. Este um exemplo de
concorrncia, que o termo usado em computao para designar situa-
es nas quais diferentes processos competem pela utilizao de algum
recurso (processador, dispositivos perifricos, etc.) ou cooperam para a
realizao de uma mesma tarefa.
A competio por um mesmo recurso deve ser feita de forma sincroniza-
da e justa, para que apenas um processo acesse o recurso de cada vez e
para que todos tenham esse acesso em algum momento. Caso o acesso
no seja mutuamente exclusivo, podero ocorrer inconsistncias nos re-
sultados, como ser visto nas sees seguintes.
Uma atividade pode ser realizada atravs da cooperao de diversos pro-
cessos, diminuindo assim o seu tempo de execuo. Para que essa coope-
rao ocorra, deve existir uma forma de comunicao entre os processos e
um protocolo de comunicao pr-determinado.
Os sistemas operacionais atuais tambm permitem a um mesmo processo
ter mais de um fluxo de execuo (threads), agravando o problema da
concorrncia. Esse agravamento ocorre porque muitos fluxos de execuo
podem manipular o valor de uma mesma varivel simultaneamente.
importante ter ainda em mente que a programao concorrente consiste
em construir programas contendo mltiplas atividades que progridem em
paralelo. Atividades podem progredir em paralelo sendo realmente execu-
tadas em paralelo, cada uma de posse de um processador diferente ou
tendo o processador se revezando entre as mesmas, de forma que cada
atividade possa fazer uso do processador em um dado instante.
Esse captulo enfoca os mecanismos oferecidos por linguagens de pro-
gramao para facilitar a construo de sistemas computacionais concor-
rentes. Inicialmente, so discutidos vrios conceitos envolvidos no estudo
da concorrncia. Em seguida, so apresentadas algumas tcnicas para sin-
cronizao de processos e threads. Por fim, mostra-se e discute-se os
mecanismos oferecidos pelas linguagens de programao para facilitar a
programao concorrente.

9.1
Na realidade, isso no ocorre porque existe um processo responsvel por gerenciar a fila de impres-
so.

307
9.1 Processos e Threads
Para compreender a idia de concorrncia em sistemas computacionais,
importante ter um bom entendimento sobre o que so processos e thre-
ads e sobre como eles se comunicam e interagem. Essa seo apresenta
uma viso geral desses conceitos.
9.1.1 Processos
Programas de computador so seqncias de instrues passveis de se-
rem executadas por um processador. Quando estes programas esto sendo
executados, eles so chamados de processos. Diferentemente dos progra-
mas, os processos so entidades ativas cujo estado alterado durante a
sua execuo. No processo, alm do conjunto de instrues, incluem-se
tambm as informaes correntes sobre a execuo.
Embora vrios processos possam estar associados a um mesmo programa,
eles so tratados independentemente. Cada processo possuir um contex-
to, isto , o seu prprio espao de endereamento, suas informaes de
controle, sua identificao, suas variveis de ambiente, entre outros. Isso
torna possvel a utilizao de um mesmo programa por mais de um usu-
rio simultaneamente, sem que a execuo de um processo sofra interfe-
rncia da execuo do outro.
Os processos podem ser classificados em seqenciais, quando a sua exe-
cuo for estritamente seqencial, ou em concorrentes, quando a execu-
o de dois ou mais processos pode ser feita de modo simultneo.
9.1.2 Estado
Durante a execuo de um processo, o seu estado modificado. O estado
de um processo definido pela atividade que est realizando. Os poss-
veis estados de um processo so os seguintes:
a. Novo: estado no qual o processo est sendo criado;
b. Executvel: estado no qual o processo est aguardando a liberao de
um processador para iniciar a sua execuo;
c. Em execuo: estado no qual o processo ocupa o processador;
d. Em espera: estado no qual o processo est esperando a ocorrncia de
algum evento;
e. Encerrado: estado do processo aps ter sua execuo encerrada.
Apenas um processo pode ocupar o processador a cada instante, ou seja,
apenas um processo poder estar no estado em execuo. Entretanto, po-
308
de haver inmeros processos nos estados executvel e em espera. A fi-
gura 9.1 apresenta um diagrama ilustrando as possveis transies de es-
tados de um processo.

Figura 9. 1 - Diagrama de Transio de Estados de Um Processo
Inicialmente, o processo encontra-se no estado novo. A partir desse esta-
do, ele pode somente passar para o estado executvel, quando est pronto
para ser executado. Caso no haja processo no estado em execuo, um
processo passa do estado executvel para o estado em execuo.
Quando um processo est no estado em execuo, ele poder fazer cha-
madas de sistema. Enquanto espera esta chamada ser atendida, ele no
continuar a sua execuo, passando assim para o estado em espera e
liberando o processador para a execuo de outro processo em estado e-
xecutvel.
Quando ocorre o evento esperado, o estado daquele processo passa a ser
executvel. O sistema operacional pode tambm passar o processo do
estado em execuo diretamente para o estado executvel, caso tenha
terminado sua fatia de tempo de posse do processador.
Um processo no estado executvel voltar ao estado em execuo quan-
do novamente tomar posse do processador. Terminada a execuo do
processo, este colocado no estado encerrado para que o sistema opera-
cional libere os recursos ainda no desalocados daquele processo.
Sempre que um processo passa do estado em execuo para o estado e-
xecutvel ou para o estado em espera, o sistema operacional salva o con-
texto de execuo daquele processo. O contexto de execuo de um pro-
cesso compreende dados de controle, como, por exemplo, a prxima ins-
truo a ser executada e o estado dos registradores. De forma anloga,
quando um processo volta ao estado em execuo, necessrio recuperar
o contexto em que ele se encontra, para que a execuo possa prosseguir
a partir de onde parou.
309
9.1.3 Threads
O modelo de concorrncia visto at agora envolve a execuo de vrios
processos simultaneamente, sendo cada processo formado por um conjun-
to de recursos e por um nico fluxo de execuo.
Os sistemas operacionais atuais permitem a um mesmo processo possuir
vrios fluxos de execuo. Threads so fluxos de execuo concorren-
tes que compartilham recursos do processo do qual so originrios. Pode-
se pensar em threads como sendo funes ou procedimentos dentro de
um mesmo programa, executando concorrentemente e compartilhando o
contexto.
Os threads tambm possuem estados, caractersticas prprias e neces-
sidade de gerncia como os processos. Eles tambm so conhecidos como
processos leves.
O compartilhamento de recursos de um mesmo processo por vrios t-
hreads caracteriza a concorrncia intraprocessos, e agrava ainda mais a
concorrncia interprocessos, visto que threads podem compartilhar
recursos com outros threads de um mesmo processo ou com thre-
ads de processos diferentes.
Por outro lado, threads podem oferecer algumas vantagens para a im-
plementao de sistemas concorrentes. Enquanto processos impem um
custo expressivo para a sua construo e destruio, uma vez que manipu-
lam uma quantidade mais significativa de dados, threads so mais le-
ves, pois utilizam os recursos de um processo criado previamente. Alm
disso, um thread compartilha memria com o processo que o criou e
com os demais threads. Como cada processo possui sua prpria me-
mria, torna-se elevado o custo das trocas entre processos.
Os threads tambm possibilitam a utilizao de mais de um mtodo ou
funo de uma mesma aplicao, simultaneamente. Por exemplo, quando
voc est utilizando um editor de textos e, enquanto um arquivo carre-
gado, voc consegue alter-lo, a aplicao est usando threads. Apesar
dessa tcnica ser possvel tambm com a utilizao de processos concor-
rentes, o uso de threads facilita a implementao e torna a aplicao
mais eficiente.
A figura 9.2 representa um sistema operacional servindo como uma ca-
mada entre a complexidade do hardware e os processos. Os processos
disputam a posse do processador entre si. Alm disso, threads em um
mesmo processo competem por recursos.
310
PROCESSO PROCESSO
SISTEMA OPERACIONAL
HARDWARE
THREADS
THREADS

Figura 9. 2 - Processos e Threads
9.1.4 Processos Concorrentes
Os programas seqenciais so determinsticos, isto , seguem uma se-
qncia de passos pr-determinada. Essa pr-determinao da ordem de
execuo dos passos torna o comportamento do programa totalmente pre-
visvel. Entretanto, os programas concorrentes so no-determinsticos,
ou seja, a ordem na qual as instrues dos processos so executadas no
pode ser definida antes da sua execuo. Isso acarreta alguns problemas,
tal como a indeterminao do valor de uma varivel.
O exemplo 9.1 leva em conta que o contedo do endereo de memria i
([i]) 100, e i uma varivel compartilhada.
; Processo 1: ; Processo 2:
mov ax, [i] mov ax, [i] ; guarda o valor da varivel i em ax
mul ax, 2 sub ax, 5 ; multiplica/subtrai ax de 2/5
mov [i], ax mov [i], ax ; guarda o valor do registrador ax
; na varivel i
Exemplo 9. 1 - Indeterminismo
O processo 1 apenas multiplica o contedo da varivel i por 2 (equivalen-
te em C a i = i * 2) e o processo 2 apenas subtrai o contedo da varivel i
por 50 (equivalente em C a i = i - 50). O contedo do endereo de mem-
ria i, ao final da execuo dos dois processos em concorrncia, poderia
ser 150, 100, 200 ou 50.
A figura 9.3 mostra as quatro possveis situaes. Na primeira situao,
representada na figura 9.3(a), os processos 1 e 2 so executados sucessi-
vamente e sem interrupo. Primeiramente, o valor inicial de i (100) do
processo 1 carregado no registrador ax, multiplicado por 2, e o resultado
(200) atribudo de volta a i. Em seguida, o processo 2 carrega o valor
atual de i (200) no registrador ax, subtrai 50 desse valor e atribui o resul-
tado (150) de volta a i. Na segunda situao, representada na figura
311
9.3(b), ocorre o inverso. Assim, o valor inicial de i (100) primeiro sub-
trado de 50 para depois ser multiplicado por 2, resultando em um valor
final de 100 para i.
Na terceira situao, representada na figura 9.3(c), o processo 1 comea a
ser executado, carregando o valor inicial de i (100) no registrador ax. A-
ps realizar essa operao, ocorre uma interrupo e a posse do processa-
dor passada para o processo 2, que carrega o valor corrente de i (o qual
continua sendo o valor inicial 100) no registrador ax. Em seguida, esse
valor subtrado de 50 e o resultado (50) atribudo a i. Aps o trmino
do processo 2, o contexto no momento da interrupo do processo 1 re-
cuperado (o registrador ax continha o valor 100) e sua execuo conti-
nuada multiplicando-se o contedo de ax por 2 e atribuindo-se o resultado
(200) a i. Observe que nessa situao como se o processo 2 no tivesse
sido executado, uma vez que o valor final de i seria o mesmo produzido
se o processo 1 tivesse sido o nico executado. Na quarta situao, repre-
sentada na figura 9.3(d), ocorre a situao inversa representada na figu-
ra 9.3(c). Dessa maneira, a execuo do processo 2 define sozinha o valor
final de i (50).
Figura 9. 3 - Exemplo de Indeterminismo
Outros problemas comuns que podem ocorrer em processos concorrentes
so o lockout (trancamento), o deadlock (impasse) e a starvati-
on (inanio).
Processo1 Processo2
mov ax, [i]
sub ax, 50
mov [i], ax
mov ax, [i]
mul ax, 2
mov [i], ax
b. Valor final de i: 100
Processo1 Processo2
mov ax, [i]
mov ax, [i]
mul ax, 2
mov [i], ax
sub ax, 50
mov [i], ax
d. Valor final de i: 50
Processo1 Processo2
mov ax, [i]
mul ax, 2
mov [i], ax
mov ax, [i]
sub ax, 50
mov [i], ax
a. Valor final de i: 150
Processo1 Processo2
mov ax, [i]
mov ax, [i]
sub ax, 50
mov [i], ax
mul ax, 2
mov [i], ax
c. Valor final de i: 200
312
O lockout ocorre quando dois ou mais processos ficam esperando por
um evento que nunca acontecer. O deadlock ocorre quando todos os
processos esto bloqueados esperando por um evento, o qual s pode ser
gerado por outro processo tambm bloqueado. Isso impede o progresso da
execuo do sistema. Essa situao s pode ser alterada por iniciativa de
um processo no pertencente ao grupo em deadlock.
Starvation ocorre quando um processo tem a aquisio de um deter-
minado recurso postergada indefinidamente. Como exemplo, em uma po-
ltica de prioridades, um processo de baixa prioridade pode nunca obter a
posse do processador caso existam outros com prioridades maiores. Para
resolver este problema podem-se utilizar mecanismos de fila ou tcnicas
de envelhecimento.
9.1.5 Tipos de Interao
Existem duas maneiras em que processos concorrentes podem interagir:
eles podem competir por um mesmo recurso, mas no compartilhar da-
dos, ou podem cooperar pela realizao de uma atividade, compartilhando
dados ou sendo afetados pela execuo de outros processos.
9.1.5.1 Competio
Os processos competitivos so independentes, apenas concorrem com
outros processos pela utilizao de um mesmo recurso, como exemplo,
pela ocupao do processador. A execuo concorrente de processos
competitivos efetivamente determinstica pois, como no h comparti-
lhamento de dados, a ordem de execuo, mesmo no podendo ser deter-
minada antecipadamente, no interfere nos resultados obtidos.
Neste tipo de interao, a sincronizao para utilizao dos recursos fei-
ta pelo sistema operacional, no provocando qualquer dificuldade para os
programadores.
9.1.5.2 Cooperao
Um processo chamado cooperativo quando afeta ou afetado pela exe-
cuo de outro processo em prol da realizao de uma atividade. A coo-
perao entre processos visa aumentar a velocidade de computao de um
programa atravs da modularizao das tarefas em vrios processos.
Para que ocorra a cooperao entre processos, necessrio que exista
uma forma de comunicao entre eles. A comunicao possvel atravs
do uso dos mecanismos de troca de mensagens ou de compartilhamento
de memria.
313
A comunicao entre processos por troca de mensagens tem como objeti-
vo permitir a troca de informaes, sem que haja a necessidade de dados
compartilhados. Para que essa comunicao ocorra, deve existir um me-
canismo bidirecional de passagem de dados, isto , um canal de comuni-
cao.
Os mecanismos de troca de mensagens so diretamente aplicveis a pro-
cessos distribudos. Os processos esto em mquinas diferentes e, portan-
to, no compartilham a mesma memria fsica. Eles tambm podem ser
usados para a comunicao entre processos em uma mesma mquina.
Contudo, a troca extensiva de mensagens pode ser um fator de sobrecarga
(overhead).
A comunicao entre processos por memria compartilhada tem como
objetivo prover comunicao atravs de dados compartilhados. Processos
que compartilham memria tendem a ser mais eficientes que processos
cuja comunicao feita atravs de troca de mensagens. Entretanto, a
comunicao por memria compartilhada pode provocar problemas de
inconsistncia de dados, os quais no ocorrem quando se utiliza troca de
mensagens.
Assim, o compartilhamento dos dados deve ser feito de forma sincroniza-
da e padronizada, para que se tenha controle dos recursos compartilhados
de modo a no causar inconsistncias.
9.1.6. Troca de Mensagens
Geralmente, o mecanismo de troca de mensagens entre processos im-
plementado pelo sistema operacional, disponibilizando duas chamadas de
sistema bsicas: send (envio) e receive (recepo). A chamada send
permite o envio de uma mensagem de um processo para outro e a chama-
da receive permite a um processo receber uma mensagem enviada para
ele.
Existem dois tipos de comunicao por troca de mensagens: direta e indi-
reta. Na comunicao direta, um processo envia mensagens diretamente
para outro processo. Neste caso, o processo remetente deve conhecer o
identificador do processo para o qual quer enviar a mensagem.
Na comunicao indireta, um processo no envia mensagens para outro
processo, mas sim para caixas postais. Cabe a cada processo retirar as
mensagens de suas caixas postais. Assim, para enviar uma mensagem, o
processo deve conhecer o identificador da caixa postal do destinatrio e
ter permisso para escrita nessa caixa. Normalmente, o sistema operacio-
nal disponibiliza chamadas de sistema para criao, manipulao e des-
truio de caixas postais.
314
importante lembrar que caixas postais permitem uma comunicao de
vrios processos para vrios outros, enquanto que uma comunicao dire-
ta de um processo para outro.
A chamada send deve possuir, pelo menos, um identificador do processo
ou da caixa postal do destinatrio e a mensagem a ser enviada. A chama-
da receive deve possuir, pelo menos, o endereo da varivel que rece-
ber o contedo da mensagem postada para aquele processo.
A comunicao de processos atravs de troca de mensagens pode ser blo-
queante ou no-bloqueante. A comunicao dita bloqueante quando,
depois de enviar uma mensagem, o processo remetente aguarda (fica blo-
queado) at que a mensagem seja confirmada pelo processo destinatrio.
Na comunicao no-bloqueante, o processo que envia a mensagem no
fica esperando confirmao e continua a sua execuo.
Pode ocorrer a situao de um processo enviar uma mensagem para outro
processo utilizando comunicao direta, e esse ltimo no a retirar imedi-
atamente. Freqentemente, o sistema operacional possui um buffer pa-
ra armazenar as mensagens que ainda no foram retiradas. Entretanto,
quando no h recursos no sistema para tal armazenamento, o sistema
operacional normalmente bloqueia o remetente at que o destinatrio reti-
re a mensagem. Alguns autores costumam chamar esse tipo de comunica-
o de rendezvous (encontro), porque a comunicao s ocorre quando o
remetente e o destinatrio se encontram. Nesse esquema, o primeiro pro-
cesso que chega ao ponto de encontro espera pelo outro.
9.1.7. Compartilhamento de Memria
Quando processos cooperam entre si compartilhando uma mesma mem-
ria fsica, h a necessidade de proteo de acesso a dados compartilhados
para evitar inconsistncias. Tome como exemplo o problema clssico do
produtor e consumidor. Neste problema, h um processo que produz algo
e um processo que utiliza o que foi produzido. H tambm um buffer
intermedirio que serve para armazenar o que foi produzido pelo produtor
e disponibilizar o que est armazenado para o consumidor. A figura 9.4
esquematiza esse exemplo.

Figura 9.4 - Problema Produtor-Consumidor
315
Uma possvel implementao para esse problema mostrada no exemplo
9.2. Considere nesse exemplo que o bloco de inicializao executado
antes dos blocos do produtor e do consumidor, os quais so executados de
maneira concorrente.
// inicializacao
fim = 0;
ini = 0;
n = 0;
// codigo do produtor
for (i=0; i<1000; i++) {
while (n == capacidade);
buf[fim] = produzir(i);
fim = (fim + 1) % capacidade;
n++;
}
// codigo do consumidor
for (i=0; i<1000; i++) {
while (n == 0) ;
consumir(buf[ini]);
ini = (ini + 1) % capacidade;
n--;
}
Exemplo 9.2 - Implementao do Problema do Produtor e Consumidor.
O produtor apenas produz algo, o coloca na posio fim do buffer buf e
incrementa a quantidade de elementos disponveis. O consumidor, por sua
vez, apenas consome o dado da posio ini de buf e decrementa o valor da
quantidade de elementos disponveis.
Os laos while na linha 2 dos cdigos do produtor e do consumidor ser-
vem apenas para garantir a no extrapolao do tamanho mximo do bu-
ffer, pelo produtor, e para impedir o consumo de elementos ainda no
produzidos, pelo consumidor. Note que os laos while so exemplos de
busy wait (espera ocupada), ou seja, esperam por um evento, mas no
liberam o uso do processador. Na seo 9.3 ser mostrado um mecanismo
usado por algumas linguagens de programao para tratar esse problema.
No momento, o importante mostrar os problemas que podem ocorrer
quando o produtor e o consumidor compartilham o buffer buf e o
endereo de memria n.
Apesar do cdigo mostrado no estar completo, estas so as partes princi-
pais da implementao. fcil verificar que, se esse programa fosse exe-
cutado seqencialmente com tamanho do buffer (capacidade) maior
ou igual a 1000 (para evitar a repetio infinita nos laos while), no ha-
316
veria problema algum. O produtor produziria 1000 elementos consumidos
posteriormente pelo consumidor.
No entanto, essa soluo torna o incio da execuo do consumidor de-
pendente do trmino da execuo do produtor. Isso pode no ser a solu-
o mais apropriada. Considere o produtor como sendo um editor de tex-
tos produzindo dados para serem impressos e o consumidor como um
processo que envia esses dados para a impressora. Com a soluo se-
qencial, a impressora s conseguiria imprimir aps o trmino do envio
de dados pelo produtor. Com a soluo concorrente, medida que os da-
dos so colocados no buffer pelo processo produtor, eles j podem ser
enviados para a impressora pelo processo consumidor para serem impres-
sos.
Portanto, a utilizao de processos concorrentes para resolver esse pro-
blema mais apropriada. Entretanto, surge outro problema: a possibilida-
de de inconsistncia dos dados. Isso pode ocorrer pelo fato de os proces-
sos compartilharem a varivel n, que identifica o nmero de elementos
em buf. Suponha, como exemplo, que em um dado instante o valor da va-
rivel n seja 5. Vamos supor que em um instante seguinte o processo pro-
dutor faa n++ concorrente com o n-- do processo consumidor. Ao final
do processamento concorrente, poderamos ter como valores da varivel n
os nmeros 4, 5 ou 6, quando o correto seria o valor 5.
Para entender como esses valores podem ser obtidos, importante lem-
brar que o processador s faz operaes aritmticas com valores armaze-
nados em registradores. Logo, a traduo para assembly das operaes
n++ e n-- envolvem, alm da operaes de incremento e decremento, o-
peraes de cpia de valores entre memria e registrador. Essas instru-
es poderiam ser representadas como no exemplo 9.3.
; n++
mov ax, [n]
inc ax
mov [n], ax
; n--
mov ax, [n]
dec ax
mov [n], ax
Exemplo 9.3 - Representao da Codificao Assembly das Instrues n++ e n--
A seqncia de operaes apresentada na tabela 9.1 produziria o valor 6
em n ao final da execuo das operaes n++ e n-- dos dois processos. Se
a ordem de execuo das ltimas instrues do produtor e do consumidor
fosse invertida, o valor final de n seria 4. Se no existisse intercalao na
execuo das instrues do produtor e do consumidor, o valor final de n
seria 5.


317
Produtor Consumidor
mov ax, [n]
inc ax
mov ax, [n]
dec ax
mov [n], ax
mov [n], ax
Tabela 9.1 - Uma Possvel Seqncia de Execuo do Exemplo 9.3
O indeterminismo ocorre porque ambos processos podem executar simul-
taneamente os segmentos de cdigo que realizam a alterao do recurso
compartilhado. O segmento de cdigo de um programa que manipula re-
cursos compartilhados por processos denominado de regio crtica.
Para evitar o problema de indeterminismo deve haver algum tipo de sin-
cronizao entre os processos de modo a garantir acesso exclusivo a sua
regio crtica, ou seja, caso o processo produtor tente modificar o valor da
varivel n, o processo consumidor dever esperar at o trmino dessa ins-
truo para s ento tambm poder modificar seu valor. A atribuio
varivel n nunca deve ser executada de forma concorrente.
9.2 Sincronizao
Nessa seo sero vistas duas tcnicas de sincronizao utilizadas para
garantir que programas concorrentes no provoquem inconsistncia nos
dados compartilhados.
9.2.1. Semforos
Um mecanismo de sincronizao entre processos muito empregado o
semforo. Um semforo um tipo abstrato de dados que possui um valor
inteiro, uma lista de processos em espera e duas operaes: P (do holan-
ds Proberen, testar) e V (do holands Verhogen, incrementar). Para ga-
rantir excluso mtua a uma determinada regio (regio crtica), cada
processo deve chamar a operao P antes de acessar tal regio e chamar a
operao V aps sair dessa regio.
A operao P sobre um semforo S testa se o processo que executou P(S)
pode ou no entrar na regio crtica, isto , se j existir outro processo na
regio crtica, ele no poder entrar e ser colocado na fila de espera da-
quele semforo.
A operao V sobre um semforo S sinaliza ao semforo que o processo
no est mais na regio crtica e retira outro processo da fila de espera,
colocando-o novamente em execuo. A fila de espera pode ser imple-
mentada de vrias formas diferentes. Para evitar starvation, normal-
318
mente implementada uma fila simples, ou seja, o processo que executou
primeiro a operao P(S) o primeiro a ser colocado de volta em execu-
o quando ocorre a chamada a V(S).
A implementao do semforo deve ser atmica, ou seja, no pode haver
mudana de contexto de um processo para outro no meio de uma opera-
o P(S) ou V(S). Um esquema de implementao possvel para o sem-
foro S mostrado no exemplo 9.4.
P(S)
S.valor -= 1;
Se (S.valor < 0)
// bloqueia o processo e insere em S.fila
V(S)
S.valor += 1;
Se (S.valor <= 0)
// retira algum processo de S.fila e o coloca em execucao
Exemplo 9.4 - Esquema de Implementao de Semforo
Inicialmente o contador S.valor iniciado com 1. Quando P(S) chama-
do, S.valor decrementado de 1, indicando que h um processo tentando
entrar na regio crtica. Caso S.valor seja um valor negativo, isso indica
que h outro processo na regio crtica e que o processo corrente deve
esperar. Este processo ento inserido em uma fila para ser chamado as-
sim que o outro processo sair da regio crtica. Por sua vez, quando V(S)
chamado, o contador incrementado de 1, indicando que aquele processo
saiu da regio crtica. Outro processo que esteja na fila de espera ento
retirado e colocado em execuo. Se S.valor possui um valor negativo,
seu valor em mdulo indica quantos processos esto na fila de espera.
Com esta tcnica podemos facilmente resolver o problema de acesso mu-
tuamente exclusivo regio crtica do produtor e consumidor. O exemplo
9.5 uma modificao do exemplo 9.2, no qual foi adicionado um sem-
foro delimitando a regio crtica. Aqui tambm se deve considerar que os
processos produtor e consumidor so executados concorrentemente.
// inicializacao
fim = 0;
ini = 0;
n = 0;
semaforo S;
S.valor = 1;
// codigo do produtor
for (i=0; i<1000; i++) {
while (n == capacidade) ;
buf[fim] = produzir(i);
319
fim = (fim + 1) % capacidade;
P(S);
n++;
V(S);
}
// codigo do consumidor
for (i=0; i<1000; i++) {
while (n == 0) ;
consumir(buf[ini]);
ini = (ini + 1) % capacidade;
P(S);
n--;
V(S);
}
Exemplo 9.5 - Alterao do Exemplo 9.2 para Garantir Acesso Exclusivo Regio Crtica
Inicialmente, o valor do semforo 1 e os cdigos do produtor e do con-
sumidor so executados concorrentemente. Se o processo produtor faz
P(S), ento o contador, que inicialmente tinha como valor 1, decremen-
tado e passa a valer 0, indicando que h um processo na regio crtica.
Se no momento seguinte o processo consumidor fizer tambm P(S) ten-
tando entrar na regio crtica, o valor de S.valor passa a ser 1 e, como
esse nmero negativo, o processo consumidor vai para a fila de espera e
permanece l at que o processo produtor faa a chamada a V(S). Quando
o produtor sai da regio crtica fazendo a chamada V(S), S.valor in-
crementado, passando a valer 0.
O processo consumidor que estava na fila de espera liberado, acessando
assim a regio crtica. Se, novamente, o processo produtor tentar entrar na
regio crtica chamando P(S), esse ir decrementar S.valor tornando-o
negativo e, assim, ser bloqueado e colocado na fila de espera.
9.2.2. Programao Concorrente Estruturada
Embora semforos ofeream um mecanismo simples e eficaz para sincro-
nizao entre processos, problemas podem ocorrer devido a uma m pro-
gramao. Para garantir acesso exclusivo, a regio crtica deve estar limi-
tada pelas operaes P e V aplicados a um semforo, nessa ordem. Agora,
suponha que por um erro ou por malcia do programador, essa ordem seja
afetada ou que alguma dessas operaes no seja feita. As seguintes situ-
aes podem ocorrer:
a. Faz-se primeiro a operao V, entra-se na regio crtica, e aps sair da
regio crtica, faz-se a operao P: Nesse caso, vrios processos pode-
320
riam estar executando em sua regio crtica violando seu acesso exclu-
sivo. Esse um erro difcil de ser identificado e reproduzido, pois apa-
rece apenas quando os processos entram simultaneamente na regio
crtica.
b. Coloca-se a operao P no lugar da operao V ou omite-se V. Nessa
situao ocorre deadlock, pois o processo, ao entrar na regio crti-
ca, nunca vai liber-la.
c. Coloca-se a operao V no lugar da operao P, ou omite-se P. Nessa
situao ocorre violao no acesso exclusivo regio crtica, pois o
processo no testa se h algum outro na regio crtica.
Para lidar com esses tipos de erros, alguns mecanismos foram propostos
para abstrair o conceito de semforos em LPs. Esses mecanismos deixam
para o compilador a responsabilidade de garantir o acesso exclusivo re-
gio crtica.
Regies crticas condicionais foram propostas com essa finalidade. Toda
varivel compartilhada necessita ser declarada como tal. Adicionalmente,
regies crticas so demarcadas atravs de um bloco de instrues especi-
al. Assim, fcil para o compilador verificar se a varivel compartilhada
acessada apenas dentro das regies crticas. Alm disso, o compilador
pode inserir automaticamente as operaes P(S) e V(S) nas posies a-
propriadas.
Monitor outro mecanismo proposto para esse fim. Ele une as vantagens
das regies crticas condicionais com as da modularizao. Por conta dis-
so, LPs que oferecem recursos para a construo de programas concorren-
tes tm optado por disponibilizar mecanismos para suportar a implemen-
tao de monitores.
9.2.2.1 Monitores
Monitores so mecanismos de sincronizao compostos por um conjunto
de variveis, procedimentos e estruturas de dados dentro de um mdulo
cuja finalidade a implementao automtica da sincronizao, garantin-
do excluso mtua entre seus procedimentos.
Nenhum procedimento dentro do monitor pode ser chamado por mais de
um processo simultaneamente. Sempre que um desses procedimentos
chamado, o monitor verifica se j existe outro processo executando algum
de seus procedimentos. Caso exista, o processo colocado em uma fila de
espera at que esse procedimento seja liberado. Fica a cargo do compila-
dor a implementao da excluso mtua dos procedimentos, livrando os
programadores dessa complexidade. O esquema bsico de um monitor
pode ser visto no exemplo 9.7.
321
Monitor nome-do-monitor {
// declaraes de varivel
p1(...) {
...
}
p2(...) {
...
}
}
Exemplo 9.7 - Esquema Bsico de um Monitor
No exemplo 9.7 mostrada a definio de dois procedimentos p1 e p2.
Eles possuem acesso exclusivo, ou seja, se um processo chamar um des-
ses procedimentos, outro processo no poder faz-lo at que o primeiro
termine a chamada. Normalmente, as linguagens que suportam a imple-
mentao de monitores tambm implementam operaes que permitem o
bloqueio de um processo at que uma condio seja satisfeita.
9.3. Abordagens das LPs
Nesta seo sero mostradas algumas abordagens utilizadas por LPs para
permitir a implementao de sistemas concorrentes.
9.3.1. Ausncia de Mecanismos
Algumas LPs, tal como C, no oferecem mecanismos prprios para lidar
com concorrncia. A maneira pela qual possvel criar e manipular pro-
cessos e threads, e ainda resolver os principais problemas de concor-
rncia, atravs do uso de chamadas de sistema ou de bibliotecas de fun-
es (especficas da plataforma de execuo). C, tal como freqente-
mente utilizada no sistema operacional UNIX, ser usada para exemplifi-
car essa abordagem. Ser tambm discutido as possveis formas de sin-
cronizao entre processos e threads em C.
9.3.1.1. Chamada de Sistema fork
Os sistemas de computao tradicionais utilizavam principalmente cha-
madas de sistema para criar e manipular processos concorrentes. No U-
NIX e no LINUX existe a chamada de sistema fork, a qual permite a du-
plicao de um processo.
Quando se faz uma chamada fork, o sistema operacional cria um processo
idntico ao que fez a chamada e retorna um nmero identificador daquele
processo. Ambos processos prosseguem a partir do ponto onde ocorreu a
chamada de fork.
322
Para diferenciar a execuo do processo-filho do processo-pai, o progra-
mador necessita testar o nmero retornado pelo fork. Se o retorno for ze-
ro, trata-se do processo-filho. Se contiver um valor maior que zero, trata-
se do processo-pai e o valor retornado o identificador do processo-filho.
Caso o retorno de fork seja negativo, isso indica a ocorrncia de algum
erro durante a criao do processo.
O exemplo 9.8 mostra a criao de dois processos-filhos a partir do pro-
cesso-pai atravs de chamadas fork. O primeiro processo criado imprime
na tela 1000000 pontos e o segundo processo imprime a mesma quantida-
de de traos. O comando wait faz com que o processo-pai espere at que
um processo-filho termine sua execuo. A quantidade de traos ou pon-
tos mostrados na tela depender do escalonamento dos processos pelo
sistema operacional.
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
void imprime(char *arg) {
int i;
for (i=0; i<1000000; i++)
printf(arg);
exit(0);
}
int main() {
int pid;
int estado_filho;
pid = fork();
if (pid < 0) { // erro
perror("Erro de criao do primeiro filho.");
} else {
if (pid == 0) { // filho
imprime("-");
}
}
pid = fork();
if (pid < 0) {// erro
perror("Erro de criao do segundo filho.");
} else {
if (pid == 0) { // filho
imprime(".");
323
}
}
wait(&estado_filho); // aguarda o trmino de um filho
wait(&estado_filho); // aguarda o trmino do outro filho*/
return 0;
}
Exemplo 9.8 - Chamadas fork
A chamada de sistema exit() na funo imprime faz com que os proces-
sos-filhos terminem. O argumento passado nesta chamada de sistema
capturado pela chamada de sistema wait do processo-pai. No exemplo, a
varivel estado_filho receber o valor 0. vlido lembrar que os proces-
sos-filhos poderiam estar retornando qualquer valor para a varivel esta-
do_filho capturado pelo wait.
Deve-se perceber que esto sendo utilizados dois waits, j que existem
dois processos-filhos. Cada wait utilizado faz com que o processo-pai a-
guarde o fim de um processo-filho.
Dependendo do sistema operacional, caso o processo-pai termine sua e-
xecuo antes de seus filhos, todos os outros processos criados pelo pro-
cesso-pai tambm tm sua execuo encerrada.
possvel implementar semforos
9.2
e monitores para permitir sincroni-
zao entre processos. Contudo, isso necessita ser feito atravs de chama-
das de sistema e no uma tarefa muito fcil.
Cada sistema operacional pode implementar de modo diferente as chama-
das de sistema para lidar com processos e por isso no sero apresentados
detalhes sobre os recursos oferecidos por chamadas fork.
9.3.1.2. POSIX Threads
Essa seo apresenta uma forma de gesto de processos leves (thre-
ads) em C atravs de bibliotecas de funes que obedecem ao padro
POSIX (Portable Operating System Interface for U-
NIX), conhecidas como Pthreads (POSIX Threads).
POSIX um padro definido pela IEEE e pela ISO, o qual define no
mundo do Unix como os programas devem se comunicar visando portabi-
lidade
9.3
.
O POSIX threads um padro adotado para manipular threads em
C. Apesar de ser um padro para UNIX, existem projetos de definio de

9.2
Maiores informaes em http://www.cs.uh.edu/~paris/4330/NOTES/Ipsyn.pdf.
9.3
Maiores informaes em http://socrates.if.usp.br/~guioc/Library/pthreads_em_C.pdf
324
APIs (Aplication Programming Interface) para outros ambi-
entes, inclusive para Windows
9.4
.
A API padro do pthreads conta com aproximadamente 60 funes.
As principais funes existentes nessa biblioteca so:
pthread_create: criao de um thread;
pthread_exit: terminao de um thread;
pthread_join: espera pelo trmino de um thread dependente;
pthread_attr_init e pthread_attr_destroy: definio de propriedades de
um thread;
pthread_detach: terminao de um thread independente;
sched_yield: liberao do processador.
O exemplo 9.9 tem o mesmo propsito do exemplo 9.8, entretanto faz uso
de chamadas de procedimentos da biblioteca pthread para manipula-
o de processos leves (threads).
O comando pthread_attr_init inicializa os atributos do thread com os
valores padres na varivel attr. Esses atributos podem ser o tipo de esca-
lonamento utilizado para o thread ou a definio se o trmino do t-
hread dependente ou no do processo que a criou. Na API de pthre-
ads existe funes para manipular esses atributos.
/* ex1.c */
/* Para compilar com gcc, use: */
/* gcc lpthread -oex1 ex1.c */
#include <stdio.h>
#include <string.h>
#include <pthread.h>
void *imprime(void *arg) {
int i;
for (i=0; i<1000000; i++)
printf((char *)arg);
pthread_exit(0);
}
int main() {
pthread_t tid1, tid2;
pthread_attr_t attr;
char msg1[30];
char msg2[30];
strcpy(msg1, ".");
strcpy(msg2, "-");

9.4
Maiores informaes em http://sources.redhat.com/pthreads-win32.
325
pthread_attr_init(&attr);
pthread_create(&tid1, &attr, imprime, msg1);
pthread_create(&tid2, &attr, imprime, msg2);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
return 0;
}
Exemplo 9.9 - Uma Implementao Usando Pthreads
O comando pthread_create cria um novo thread tendo como ponto de
partida a execuo da funo imprime, recebendo como argumento msg1
no primeiro caso e msg2 no segundo caso. O primeiro argumento de pt-
hread_create define a varivel que conter o identificador do novo thre-
ad. Somente atravs desse identificador, o processo-pai poder se comu-
nicar com os threads-filho. O terceiro parmetro de pthread_create
um endereo de uma funo (nesse caso, imprime). Esta funo deve ter
uma assinatura recebendo um argumento void * e tendo como tipo de
retorno void *.
O comando pthread_join faz com que o processo-pai espere pelo trmino
dos seus threads-filhos. Normalmente, o atributo padro para os t-
hreads define o trmino dos novos threads como ligados ao trmino
do processo-pai. Assim, se o processo-pai termina sua execuo, o siste-
ma operacional fora o trmino da execuo de todos os threads cria-
dos por ele que possuam essa propriedade.
A API pthread inclui tambm primitivas para sincronizao entre thre-
ads. O mecanismo de sincronizao utilizado um dispositivo de exclu-
so mtua conhecido como mutex (excluso mtua). O mutex um
tipo de semforo binrio, podendo estar nos estados bloqueado ou no-
bloqueado.
O semforo mutex possui duas operaes: lock (bloqueia) e unlock
(desbloqueia). A operao lock determina o bloqueio do processo caso
o semforo esteja em uso e a operao unlock permite a liberao de
algum processo bloqueado na fila daquele semforo. Essas operaes so
equivalentes s operaes P e V apresentadas na seo 9.2.1 sobre sem-
foros.
A biblioteca Pthreads oferece ainda um meio de evitar que threads
fiquem em espera ocupada. Isso conseguido atravs do uso de variveis
de condio, s quais devem ser usadas em conjunto com um mecanismo
de sincronizao para garantir excluso mtua. Uma varivel de condio
326
suspende a execuo de um thread ou processo at que uma condio
seja satisfeita pela ao de outros threads ou processos.
O exemplo 9.10 uma implementao do exemplo do produtor-
consumidor j abordado anteriormente, utilizando um semforo mutex
e variveis condicionais.
/* ex2.c */
/* Para compilar com gcc, use: */
/* gcc lpthreads -oex2 ex2.c */
#include<stdio.h>
#include<pthread.h>
#include <stdlib.h>
#define CAPACIDADE 10
typedef struct BufferLim {
int buf[CAPACIDADE];
int fim;
int ini;
int n;
pthread_mutex_t mut;
pthread_cond_t vazio, cheio;
} BufferLimitado;
void *produtor(void *arg) {
BufferLimitado *bl = (BufferLimitado *)arg;
int i;
for (i=0; i<1000; i++) {
pthread_mutex_lock(&bl->mut);
while (bl->n == CAPACIDADE)
pthread_cond_wait(&bl->cheio, &bl->mut);
bl->buf[bl->fim] = i;
printf("produzido: %d\n", bl->buf[bl->fim]);
bl->fim = (bl->fim + 1) % CAPACIDADE;
bl->n++;
pthread_mutex_unlock(&bl->mut);
pthread_cond_signal(&bl->vazio);
}
pthread_exit(0);
}
void *consumidor(void *arg) {
BufferLimitado *bl = (BufferLimitado *)arg;
int i;
for (i=0; i<1000; i++) {
pthread_mutex_lock(&bl->mut);
while (bl->n == 0)

327
pthread_cond_wait(&bl->vazio, &bl->mut);
printf("consumido: %d\n",bl->buf[bl->ini]);
bl->ini = (bl->ini + 1) % CAPACIDADE;
bl->n--;
pthread_mutex_unlock(&bl->mut);
pthread_cond_signal(&bl->cheio);
}
pthread_exit(0);
}
int main() {
BufferLimitado bl;
Pthread_t tid1, tid2;
bl.fim = bl.ini = bl.n = 0;
pthread_mutex_init(&bl.mut, NULL);
pthread_cond_init(&bl.cheio, NULL);
pthread_cond_init(&bl.vazio, NULL);
pthread_create(&tid1, NULL, produtor, &bl);
pthread_create(&tid2, NULL, consumidor, &bl);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
return 0;
}
Exemplo 9.10 Produtor e Consumidor com mutex e Variveis de Condio.
No exemplo 9.10 foi criado o tipo de dados BufferLimitado, agregando
atributos relacionados ao buffer. Valores desse tipo possuem um sem-
foro mut, para sincronizar o acesso regio crtica do produtor e do con-
sumidor, e as variveis condicionais vazio e cheio, usadas para sinalizar
as condies de buffer no vazio e buffer no cheio, respectiva-
mente. Um valor do tipo BufferLimitado deve ser passado como argumen-
to para o produtor e consumidor.
Inicialmente, durante a execuo de main o buffer bl criado. Em se-
guida, suas variveis de controle, o semforo e as variveis condicionais
so inicializadas e os threads produtor e consumidor so criados.
A chamada pthread_cond_wait(&bl->cheio, &bl->mut), no cdigo do
produtor, destrava o semforo mut e faz com que ele espere por uma sina-
lizao na varivel bl->cheio. Essa sinalizao indica que o buffer
compartilhado no est mais cheio, liberando o produtor para continuar
produzindo. A sinalizao feita pelo consumidor aps o consumo de
algum elemento. J a chamada pthread_cond_signal(&bl->vazio) envia
um sinal pela varivel bl->vazio, liberando um dos threads em espera
por essa condio. Nesse caso, se o consumidor estiver em espera por e-
328
lementos disponveis no buffer, caso ele esteja vazio, ele s poder
consumir aps o envio desse sinal (indicando a produo de algum ele-
mento). O produtor ento sinaliza que acabou de produzir. Todos esses
passos so realizados atomicamente.
O cdigo do consumidor anlogo, apenas aguardando caso o buffer
esteja vazio e sinalizando que acabou de consumir algum elemento, ou
seja, que o buffer no est mais cheio.
O uso do mecanismo de variveis condicionais torna a implementao
muito complexa quando h a necessidade de controlar mais de uma con-
dio. importante salientar, por fim, que o exemplo 9.10 uma forma
de simular um monitor na linguagem C.
9.3.2. Classes Threads e Mtodos Sincronizados
Uma outra abordagem, adotada por JAVA, envolve a disponibilizao de
classes especiais para a criao e manipulao de threads e ainda ofe-
rece mecanismos de sincronizao de mtodos. Ao se criar um objeto de
uma dessas classes especiais, JAVA cria um novo fluxo de execuo. A-
lm disso, possvel especificar mtodos de qualquer classe como sendo
sincronizados. A seguir, mostrado como criar threads e como pos-
svel trabalhar com sincronizao em JAVA.
9.3.2.1 Classes Threads
H duas maneiras de se criar threads em Java: herdando da classe T-
hread ou implementando a interface Runnable. Em ambas, o corpo de
implementao do thread est no mtodo run. Se uma classe herda de
Thread, ento ela j um thread e pode ser iniciada atravs de seu
mtodo start. Se uma classe implementa Runnable, ento ela deve ser e-
xecutada a partir de um thread. O exemplo 9.11 mostra a criao de t-
hreads utilizando herana.
class Carro extends Thread {
public Carro(String nome) {
super(nome);
}
public void run() {
for (int i=0; i<10;i++) {
try {
sleep((int)(Math.random() * 1000));
}
catch (Exception e) {
};
329
System.out.print(getName());
for (int j=0; j<i; j++)
System.out.print("--");
System.out.println(">");
}
System.out.println(getName() + " completou a prova.");
}
}
public class Corrida {
public static void main(String args[]) {
Carro carroA = new Carro("Barrichelo");
Carro carroB = new Carro("Schumacher");
carroA.start();
carroB.start();
try {
carroA.join();
} catch (Exception e) {
}
try {
carroB.join();
} catch (Exception e) {
}
}
}
Exemplo 9.11 Criao de Threads por Herana
O exemplo 9.11 mostra uma corrida de threads com dois participantes:
Barrichelo e Schumacher. O primeiro carro tem a vantagem de iniciar
primeiro atravs do mtodo start. O thread-pai espera o trmino dos dois
carros atravs do mtodo join.
O cdigo do mtodo run implementa o comportamento de um thread, ou
seja, neste mtodo que ocorrer a definio das tarefas que sero reali-
zadas pelo thread. No exemplo o thread ir executar um lao 10 ve-
zes. A cada iterao, o thread ir dormir por um perodo de tempo es-
pecificado aleatoriamente atravs do mtodo sleep. Quando acordar, ir
imprimir o nome do corredor seguido de sua situao, definida pela quan-
tidade de traos a serem impressos.
Um possvel resultado da execuo do exemplo 9.11 mostrado na figura
9.7.
Barrichelo>
Schumacher>
Schumacher-->
330
Barrichelo-->
Schumacher---->
Barrichelo---->
Schumacher------>
Barrichelo------>
Barrichelo-------->
Schumacher-------->
Barrichelo---------->
Schumacher---------->
Barrichelo------------>
Schumacher------------>
Barrichelo-------------->
Schumacher-------------->
Barrichelo---------------->
Barrichelo------------------>
Barrichelo completou a prova.
Schumacher---------------->
Schumacher------------------>
Schumacher completou a prova.
Figura 9.7 - Resultado da Execuo do Exemplo 9.11
O exemplo 9.12 mostra a classe Carro2, agora utilizando Runnable.
class Carro2 implements Runnable {
private String nome;
public Carro2(String nome) {
this.nome = nome;
}
public void run() {
for (int i=0; i<10;i++) {
try {
Thread.sleep((int)(Math.random() * 1000));
}
catch (Exception e) {};
System.out.print(nome);
for (int j=0; j<i; j++)
System.out.print("--");
System.out.println(">");
}
System.out.println(nome + " completou a prova.");
}
}
public class Corrida2 {
public static void main(String args[]) {
Carro2 carroA = new Carro2("Barrichelo");
Carro2 carroB = new Carro2("Schumacher");
Thread threadA = new Thread(carroA);
331
Thread threadB = new Thread(carroB);
threadA.start();
threadB.start();
try {
threadA.join();
} catch (Exception e) {
}
try {
threadB.join();
} catch (Exception e) {
}
}
}
Exemplo 9.12 Criao de Threads Atravs de Runnable.
A classe Corrida2 no herdeira de Thread. Assim, a criao de um ob-
jeto dessa classe no produz mais um novo fluxo de execuo. No entan-
to, JAVA permite a criao de um objeto da classe Thread a partir de um
objeto que implementa a interface Runnable. Como a classe Corrida2 no
herdeira de Thread, para chamar sleep no mtodo run, preciso explici-
tar a classe na qual sleep definida (Thread).
Na maioria das vezes, usa-se a interface Runnable quando a classe a ser
implementada deve ser uma subclasse de uma classe diferente de Thread.
Caso isso no seja necessrio utiliza-se herana da classe Thread.
9.3.2.2 Mtodos Sincronizados
Todo objeto em JAVA possui um bloco de operaes nico. Quando m-
todos de um objeto em JAVA so declarados como synchronized, a JVM
(Java Virtual Machine) faz com que threads chamem esses mtodos
em excluso mtua.
Se um thread inicia a execuo de um mtodo synchronized, nenhum
outro thread conseguir executar qualquer mtodo synchronized da-
quele objeto at que o primeiro libere o bloco de operaes do objeto.
Quando um thread faz chamada a um mtodo declarado como sync-
hronized, verificado se outro thread j est de posse do bloco de o-
peraes daquele objeto. Em caso positivo, o thread bloqueado e
colocado no conjunto de threads de entrada para aquele objeto. Caso
contrrio, o thread toma posse do bloco de operaes e inicia a execu-
o do mtodo.
332
Quando o thread libera o bloco de operaes do objeto e h threads
no conjunto de entrada, a JVM seleciona arbitrariamente algum thread
desse conjunto e d a ele a posse do bloco de operaes do objeto.
H duas formas de um thread liberar o bloco de operaes: atravs do
trmino do mtodo synchronized ou atravs da chamada ao mtodo
wait().
Quando um thread chama o mtodo wait(), ele libera o bloco de ope-
raes, bloqueado e colocado no conjunto de threads em espera e a
JVM seleciona outro thread do conjunto de entrada para tomar seu
lugar.
A chamada do mtodo notify() faz com que a JVM selecione um thread
do conjunto de threads em espera e o transfira para o conjunto de en-
trada, fazendo com que esse thread tambm possa competir pela posse
do bloco de operaes. A chamada a notifyAll() faz com que a JVM trans-
fira todos os threads do conjunto em espera para o conjunto de entra-
da.
A figura 9.5 mostra o ciclo de estados possveis para threads que aces-
sam mtodos synchronized de um objeto.
Figura 9.5. Estados de Threads no Acesso a Mtodos synchronized
O thread C detm a posse do bloco de operaes do objeto X, enquan-
to os threads D e E aguardam a liberao do bloco. J os threads A
e B aguardam pela realizao de um evento.
333
Desse modo, somente um thread executa um mtodo synchronized de
um objeto por vez, garantindo excluso mtua. O exemplo 9.13 mostra a
implementao do problema do produtor e consumidor com um buffer
limitado, isto , um buffer de capacidade finita compartilhado pelos
dois threads.
class BufferLimitado {
private int capacidade;
private int n;
private int buffer[];
private int fim;
private int ini;
public BufferLimitado(int capacidade) {
this.capacidade = capacidade;
n = 0;
fim = 0;
ini = 0;
buffer = new int[capacidade];
}
public synchronized void inserir(int elemento) {
while ( n == capacidade) {
try {
wait();
} catch (Exception e) {
}
}
buffer[fim] = elemento;
fim = (fim + 1) % capacidade;
n = n + 1;
notify();
}
public synchronized int retirar() {
while ( n == 0) {
try {
wait();
} catch (Exception e) {
}
}
int elem = buffer[ini];
ini = (ini + 1) % capacidade;
n = n 1;
notify();
return elem;
334
}
}
class Produtor extends Thread {
private BufferLimitado buffer;
public Produtor(BufferLimitado buffer) {
this.buffer = buffer;
}
public void run() {
int elem;
while (true) {
elem = (int)(Math.random() * 10000);
buffer.inserir(elem);
System.out.println("produzido: " + elem);
Try {
Thread.sleep((int)(Math.random() * 1000));
} catch (Exception e) {
}
}
}
}
class Consumidor extends Thread {
private BufferLimitado buffer;
public Consumidor(BufferLimitado buffer) {
this.buffer = buffer;
}
public void run() {
int elem;
while (true) {
elem = buffer.retirar();
System.out.println("consumido: " + elem);
Try {
Thread.sleep((int)(Math.random() * 1000));
}catch (Exception e) {
}
}
}
}
public class Fabrica {
public static void main(String args[]) {
BufferLimitado buffer = new BufferLimitado(10);
Produtor produtor = new Produtor(buffer);
Consumidor consumidor = new Consumidor(buffer);
Produtor.start();
335
Consumidor.start();
Try {
Produtor.join();
} catch (Exception e) {
}
try {
consumidor.join();
} catch (Exception e) {
}
}
}
Exemplo 9.13 Produtor e Consumidor com Classe Thread
A classe Fabrica cria uma nova instncia da classe BufferLimitado de
capacidade igual a 10 e os threads Produtor e Consumidor. A classe
BufferLimitado possui um buffer compartilhado pelos threads Pro-
dutor e Consumidor e mtodos para insero e retirada de elementos do
buffer. Observe que os mtodos inserir e retirar da classe BufferLimi-
tado so declarados como synchronized, garantindo excluso mtua no
acesso ao buffer.
O mtodo inserir chama o mtodo wait() quando o buffer fica cheio, o
que faz o thread liberar o bloco de operaes, ser bloqueado e colocado
no conjunto de threads em espera. H tambm uma chamada ao m-
todo notify(), que faz com que a JVM transfira um thread em espera
para o conjunto de entrada, para que ele novamente possa competir pela
posse do bloco de operaes. Observe que o mtodo retirar anlogo,
apenas chamando o mtodo wait() quando o buffer se tornar vazio.
Caso o mtodo retirar no tivesse sido declarado como syncronized, po-
deria haver inconsistncia dos dados j que um mtodo syncronized pode
ser executado ao mesmo tempo de outro no declarado como tal.
O exemplo 9.13 mostra uma implementao de monitor em JAVA. ex-
ceo do construtor, todos os mtodos da classe BufferLimitado so syn-
cronized, o que garante a excluso mtua no acesso aos dados do buf-
fer.
9.3.3. Mdulos Concorrentes
Outra abordagem para permitir a construo de sistemas concorrentes
consiste na definio e uso de mdulos concorrentes. A execuo de pro-
gramas na linguagem de programao ADA consiste na execuo de uma
ou mais tarefas (tasks). Em geral, cada tarefa representa um thread
336
de processamento independente e concorrente com pontos de interao
com outras tarefas.
ADA disponibiliza dois mecanismos de comunicao entre tarefas: pas-
sagem de mensagens (rendevouz) e objetos protegidos.
9.3.3.1. Passagem de Mensagens (Rendezvous)
O rendevouz o mecanismo bsico de sincronizao e comunicao
de tarefas em ADA. O modelo de ADA baseado no modelo de interao
cliente-servidor.
Uma tarefa, a servidora, declara um conjunto de servios disponibilizados
para outras tarefas (as clientes). Isto pode ser feito declarando um ou mais
pontos de entrada na especificao da tarefa.
A troca de mensagem rendevouz feita quando uma tarefa chama uma
entrada de outra tarefa. Como dito na seo 9.1.6, o rendevouz apenas
ocorre quando os dois lados, o cliente e o servidor, se encontram. Quando
a tarefa cliente faz chamada a uma entrada, ela espera at sua chamada
ser aceita pela tarefa servidora. Quando a tarefa servidora indica que acei-
ta uma determinada entrada, ela espera at alguma tarefa cliente fazer
chamada quela entrada.
Uma tarefa possui uma especificao e um corpo. Na especificao po-
dem-se declarar entry points (pontos de entrada), os quais definem
interfaces com o mundo exterior tarefa. No corpo feita a implementa-
o do restante da descrio da tarefa. Para declarao de um ponto de
entrada utilizado o comando entry e o cdigo relacionado entrada
definido no corpo da tarefa atravs do co-mando accept. A Figura 9.6
mostra o esquema de uma tarefa simples, com destaque para o modo de
utilizao dos comandos entry e accept.
task <nome-da-task> is
...
entry <nome-da-entrada>
...
end <nome-da-task>

task body <nome-da-task> is
...
accept <nome-da-entrada>
do
<comandos>
end <nome-da-entrada>
...
end <nome-da-task>
Figura 9.6 - Esquema de uma Tarefa Simples em ADA
337
Os pontos de entrada so mutuamente exclusivos, ou seja, um ponto de
entrada nunca pode ser executado concorrentemente com outro ponto de
entrada da mesma tarefa.
Quando a tarefa executa o comando accept para alguma entrada, ela vai
dormir at que a entrada seja solicitada por outra rotina. O comando
accept no feito na forma de espera ocupada, como j discutido anteri-
ormente.
Uma tarefa pode possuir vrios pontos de entrada opcionais, permitindo
assim que outras tarefas se comuniquem com ela atravs deles. Para tor-
nar isso possvel, ADA disponibiliza o comando select, o qual permite a
utilizao de vrios comandos accept opcionais. Isso demonstrado no
exemplo 9.14, que define um tipo task Carro que possui trs pontos de
entrada: iniciar, andar_para_frente e andar_para_tras.
with Text_IO; use Text_IO;
procedure teste is
task type Carro is
entry iniciar(id: integer);
entry andar_para_frente;
entry andar_para_tras;
end Carro;
task body Carro is
posicao: integer;
meu_id: integer;
begin
accept iniciar(id: integer) do
posicao := 0;
meu_id := id;
end iniciar;
Put_Line("O carro " & Integer'Image(meu_id) &
" esta na posicao " & Integer'Image(posicao));
select
accept andar_para_frente do
posicao := posicao + 1;
end andar_para_frente;
or
accept andar_para_tras do
posicao := posicao - 1;
end andar_para_tras;
end select;
Put_Line("O carro " & Integer'Image(meu_id) &
" esta na posicao " & Integer'Image(posicao));
end Carro;
338
carro1: Carro;
carro2: Carro;
begin
carro1.iniciar(1);
carro2.iniciar(2);
carro1.andar_para_frente;
carro2.andar_para_tras;
end teste;
Exemplo 9.14 - Utilizao de Tarefas e Entradas em ADA
Quando a varivel carro1 declarada, uma tarefa do tipo Carro iniciada
parando no primeiro comando accept em espera pela solicitao da entra-
da iniciar. Quando ocorre a chamada ao ponto de entrada car-
ro1.iniciar(1), a tarefa continua a execuo imprimindo na tela que o car-
ro 1 est na posio 0, e novamente interrompida no comando select
esperando por uma chamada entrada andar_para_frente ou entrada
andar_para_tras. feita uma chamada entrada andar_para_frente fa-
zendo com que a varivel posio seja incrementada e seja impresso que
o carro 1 est na posio 1. A execuo anloga para o carro 2, diferin-
do apenas em que feita uma chamada entrada andar_para_trs.
vlido lembrar que as entradas so mutuamente exclusivas e, portanto,
nesse exemplo, um mesmo carro no pode andar para frente e para trs. A
figura 9.7 mostra um dos possveis resultados da execuo do programa
do exemplo 9.14.
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
Figura 9.7 Resultado da execuo do Exemplo 9.14
9.3.3.2. Objetos Protegidos
ADA 95 tambm fornece sincronizao atravs de objetos protegidos.
Operaes protegidas podem ser procedimentos (procedures), funes
(functions) e entradas (entries).
Chamadas a procedimentos e entradas protegidos so executadas em ex-
cluso mtua, ou seja, nenhuma operao do mesmo objeto protegido po-
de ser executada concorrentemente. As funes podem ser executadas em
paralelo, mas no quando um procedimento ou entrada protegidos esto
sendo executados. Funes no podem afetar o estado do objeto protegi-
do, ou seja, possuem permisso apenas para leitura dos dados encapsula-
dos.
339
Um objeto protegido em ADA encapsula dados e prov acesso a esses
dados apenas atravs de entradas protegidas ou subprogramas protegidos.
O compilador garante que estes subprogramas e entradas so executados
em excluso mtua.
A unidade protegida pode ser declarada com um tipo ou uma simples ins-
tncia. No ltimo caso, ns dizemos que a unidade corresponde a um tipo
annimo. A unidade protegida possui uma especificao e um corpo.
A Figura 9.7 mostra o esquema de uma unidade protegida simples. Fun-
es, procedimentos e entradas podem tambm receber argumentos, ape-
sar de no ser mostrado na figura.
protected type <nome-da-unidade> is
function <nome-da-funo> return <tipo-retorno>;
procedure <nome-do-procedimento>;
entry <nome-da-entrada>;
<declarao de variveis>
end <nome-da-unidade>;

protected body <nome-da-unidade> is
entry <nome-da-entrada> when <condio> is
begin
<comandos>
end <nome-da-entrada>;
procedure <nome-do-procedimento> is
begin
<comandos>
end <nome-do-procedimento>;
function <nome-da-funo> return <tipo-retorno> is
begin
<comandos>
return <valor>;
end <nome-da-funo>;
end <nome-da-unidade>;
Figura 9.8 - Esquema de uma Unidade Protegida Simples em ADA
Uma entrada protegida similar a um procedimento protegido, garantin-
do excluso mtua e possuindo acesso de leitura e escrita aos dados en-
capsulados. Entretanto, o acesso a uma entrada protegida feito atravs
da verificao de uma expresso booleana identificando uma barreira para
execuo do corpo da entrada. Se a condio da barreira falsa quando
ocorre uma chamada entrada, a tarefa chamadora suspensa at que a
condio da barreira se torne verdadeira e nenhuma outra tarefa esteja
executando dentro do objeto protegido.
Entradas protegidas podem ser usadas para implementar condies de
sincronizao. O exemplo 9.15 mostra a implementao de um objeto
340
protegido Objeto_Sinal que pode estar em dois estados: aberto (Aberto =
true) e fechado (Aberto = false).
protected type Objeto_Sinal is
entry Espera;
procedure Sinal;
function Esta_Aberto return boolean;
private
aberto: boolean := false;
end Objeto_Sinal;
protected body Objeto_Sinal is
entry Espera when aberto is
begin
aberto := false;
end Espera;
procedure Sinal is
begin
aberto := true;
end Sinal;
function Esta_Aberto return boolean is
begin
return aberto;
end Esta_Aberto;
end Objeto_Sinal;
Exemplo 9.15 Implementao de um Tipo Objeto Protegido
O corpo da chamada entrada Espera somente executado quando o si-
nal est aberto (when Aberto). Se uma tarefa faz chamada entrada Espe-
ra de algum objeto protegido do tipo Objeto_Sinal e o estado desse objeto
est como fechado, a tarefa vai dormir at que o sinal abra e nenhuma
outra tarefa esteja executando no objeto protegido.
O Exemplo 9.16 tem o mesmo propsito do exemplo 9.13, entretanto,
nesse exemplo so utilizados os recursos da linguagem ADA para traba-
lhar com concorrncia.
package Fabrica is
type Buffers is array(positive range <>) of integer;
protected type BufferLimitado(capacid : natural) is
entry Retirar(elem : out Integer);
entry Inserir(elem : in Integer);
private
buf: Buffers(1..capacid);
n: natural := 0;
ini, fim: positive := 1;
341
capacidade: natural := capacid;
end BufferLimitado;
type ABuffer is access bufferLimitado;
task type Produtor is
entry Iniciar(buf : in ABuffer);
end produtor;
task type Consumidor is
entry Iniciar(buf : in ABuffer);
end consumidor;
end Fabrica;
a Arquivo BufferLimitado.ads (Declaraes)
with Ada.Text_Io; use Ada.Text_Io;
package body Fabrica is
protected body BufferLimitado is
entry Retirar(elem : out integer) when n > 0 is
begin
elem := buf(ini);
if (ini = capacidade) then ini := 1;
else ini := ini + 1;
end if;
n := n - 1;
end Retirar;
entry Inserir(elem : in integer) when n < capacidade is
begin
buf(fim) := elem;
if (fim = capacidade) then fim := 1;
else fim := fim + 1;
end if;
n := n + 1;
end Inserir;
end BufferLimitado;
task body Produtor is
elem: integer;
pbuf: ABuffer;
i: integer;
begin
accept Iniciar(buf : in ABuffer) do
pbuf := buf;
end;
for i in 0..1000 loop
elem := i;
pbuf.inserir(elem);
342
Put_Line("Produzido: " & Integer'Image(elem));
end loop;
end Produtor;
task body Consumidor is
elem: integer;
cbuf: ABuffer;
i: natural;
begin
accept Iniciar(buf : in aBuffer) do
cbuf := buf;
end;
for i in 0..1000 loop
cbuf.retirar(elem);
Put_Line("Consumido: " & Integer'Image(elem));
end loop;
end Consumidor;
end Fabrica;
b Arquivo BufferLimitado.adb (Implementao)
with Fabrica; use Fabrica;
with Ada.Text_Io; use Ada.Text_Io;
procedure Teste is
prod: Produtor;
cons: Consumidor;
buf: ABuffer := new BufferLimitado(10);
begin
prod.Iniciar(buf);
cons.Iniciar(buf);
end Teste;
c Arquivo teste.adb (Instanciao)
Exemplo 9.16 BufferLimitado como Objeto protegido.
Na implementao do BufferLimitado foi criado um tipo objeto protegido
que ser compartilhado por tarefas produtoras e consumidoras. Os mto-
dos inserir e retirar foram declarados como entradas, ou seja, no pode-
ro ter acesso ao buffer em um mesmo momento, como visto na parte de
declaraes (parte a) do exemplo 9.16.
J na parte de implementao (parte b) do exemplo 9.16 importante no-
tar no corpo de BufferLimitado que as entradas inserir e retirar possuem
condicionais que s permitiro a ao caso o buffer no estiver cheio ou
vazio, respectivamente.
343
A implementao das tarefas Produtor e Consumidor apenas inserem e
retiram elementos do buffer, respectivamente, e esses mtodos foram de-
clarados de forma a garantir excluso mtua.
J na parte de instanciao (parte c) do 9.16 foi declarado um produtor,
um consumidor e um buffer que ser compartilhado pelas duas tare-
fas. As tarefas Produtor e Consumidor so inicializadas no momento em
que so declaradas, mas elas ficam paradas no mtodo accept de seus
corpos at serem chamadas em prod.Iniciar(buf) e cons.Iniciar(buf), res-
pectivamente. Este tipo de implementao caracteriza um rendezvous.
importante notar que o compilador assegura a excluso mtua. Portan-
to, o exemplo uma implementao de monitor na linguagem ADA.
9.3 Consideraes Finais
Nesse captulo foram apresentados os benefcios e os problemas que po-
dem surgir com a programao concorrente. Como soluo, foram discu-
tidos mecanismos propostos para apoiar a programao concorrente, tais
como os semforos e os monitores.
Os semforos so mecanismos de sincronizao eficientes que garantem
excluso mtua. Embora simples e eficazes, sua implementao nem
sempre fcil e erros podem facilmente acontecer com pequenos deslizes
do programador.
Para lidar com esses tipos de erros, alguns mecanismos foram propostos
para abstrair o conceito de semforos em LPs, com destaque para os mo-
nitores. Esses mecanismos deixam para o compilador a responsabilidade
de garantir o acesso exclusivo regio crtica.
Tambm foram apresentadas algumas abordagens utilizadas por LPs para
permitir a implementao de sistemas concorrentes.
A linguagem C no oferece mecanismos prprios para lidar com concor-
rncia. A criao, manipulao e sincronizao de processos e threads
possvel atravs do uso de chamadas de sistema ou de bibliotecas de
funes. Um problema da utilizao de bibliotecas de funes que elas
so especficas da plataforma de execuo. Outro grande problema que
a implementao de alguns mecanismos de sincronizao, tal como sem-
foros, no trivial.
A linguagem JAVA disponibiliza classes especiais para a criao e mani-
pulao de threads e oferece mecanismos de sincronizao de mto-
dos. A implementao de sistemas concorrentes se torna simples nesta
linguagem.
344
J a linguagem ADA permite a construo de sistemas concorrentes atra-
vs da definio e uso de mdulos concorrentes. A execuo de progra-
mas nesta linguagem consiste na execuo de uma ou mais tarefas. A sin-
cronizao entre as tarefas pode ser feita atravs de troca de mensagens
ou de objetos protegidos. Enquanto o mecanismo de troca de mensagens
mais elegante, o mecanismo de objetos protegidos mais eficiente. ADA,
portanto, oferece mais recursos e flexibilidade para a implementao de
sistemas concorrentes.
Maiores informaes sobre concorrncia podem ser obtidas nos livros de
Watt [WATT, 1992], Silberchatz, Gagne e Galvin [SILBERSCHATZ,
GAGNE & GALVIN, 2001], Oliveira, Carissimi e Toscani [OLIVEIRA,
CARISSIMI & TOSCANI, 2001] e Burns e Wellings [BURNS &
WELLINGS, 2001].
9.4 Exerccios
1. Se a programao concorrente traz dificuldades para a programao,
quais vantagens se tm com a sua utilizao?

2. Quais so as principais diferenas entre threads e processos? Cite
as respectivas vantagens e desvantagens de sua utilizao.

3. Quais so as principais diferenas entre memria compartilhada e de
troca de mensagens? Cite vantagens e desvantagens.

4. Mostre como possvel utilizar semforos em substituio aos laos
while dos cdigos do produtor e do consumidor no problema mostrado
no exemplo 9.2, reduzindo assim o overhead do sistema.

5. Faa uma classe Semaforo em JAVA que implemente as operaes P e
V de um semforo. Utilize para isso os mtodos wait() e notify(). A
classe deve possuir mtodos P e V em excluso mtua (synchronized).

6. Implemente uma tarefa Semaforo em ADA utilizando entradas P e V.

7. Suponha que sejam retiradas as chamadas s entradas iniciar de
carro1 e carro2 no exemplo 9.14. Indique a opo abaixo com o
resultado correto da execuo.

a) Aparecer na tela as seguintes mensagens:
O carro 1 esta na posicao 0
O carro 2 esta na posicao 0

345
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) No aparecer nada na tela.
d) O programa dar erro em tempo de compilao.

8. Implemente o programa do exemplo 9.10 retirando o semforo,
descreva o que acontece e justifique.

9. Quais so as principais caractersticas das linguagens C, JAVA e ADA
relacionadas programao concorrente?
346

Captulo X Avaliao de Linguagens


A linguagem nao sere somente para expressar pensamentos, mas para tornar possel
pensamentos que nao poderiam existir sem ela.`
Bertrand Russell
Linguagens de programao so ferramentas fundamentais para o profis-
sional de computao. Programadores adquirem maior habilidade para
resolver problemas e para aprender novas LPs quando tm um bom co-
nhecimento a respeito dos conceitos de linguagens de programao.
Mesmo aqueles profissionais que no atuam diretamente como progra-
madores necessitam ter conhecimento a respeito desses conceitos. Afinal,
esse conhecimento necessrio em todas as etapas do processo de desen-
volvimento de software. Ele importante para analisar a viabilidade do
desenvolvimento de uma aplicao, para estimar o tempo necessrio e o
custo da implementao, para definir a tcnica de projeto de software a
ser utilizada e mesmo para aumentar a efetividade na comunicao entre
programadores e projetistas de software.
Conhecimento sobre os conceitos de LPs requisito essencial para reali-
zar uma das tarefas mais fundamentais no processo de desenvolvimento
de software: a seleo da linguagem mais apropriada para a implementa-
o de uma aplicao. Nessa direo, esse captulo objetiva apresentar
alguns critrios para serem usados na avaliao de LPs com vistas sele-
o de linguagens. Esses critrios so utilizados posteriormente para fazer
uma comparao entre C, C++ e JAVA.
Definir critrios para avaliao de LPs no uma tarefa fcil. Existem
inmeras possibilidades. Para se ter uma idia, cada um dos conceitos
apresentados nesse livro tem potencial para se tornar um critrio de avali-
ao. Outra dificuldade se relaciona com o fato desses critrios poderem
estar em diferentes nveis de granularidade. Critrios mais gerais podem
ser aplicados em um maior expectro de situaes, enquanto os mais espe-
cficos permitem uma comparao mais precisa e detalhada. Por fim, e-
xiste a questo da relevncia do critrio. Critrios so considerados mais
ou menos importantes de acordo com a perspectiva de quem os analisa.
Certamente, outros profissionais de computao podem perfeitamente
escolher um conjunto diferente de critrios (e que, eventualmente, conte-
nham critrios de maior relevncia) ao apresentado aqui.
Dificuldade semelhante surge na realizao de comparaes entre lingua-
gens. Independentemente do conjunto de critrios selecionado, certo
que existiro controvrsias e argumentos contrrios a respeito das avalia-
347
es realizadas nesse captulo com relao a uma ou outra LP em um ou
mais critrios.
Por conta disso, o leitor deve considerar os critrios e a comparao apre-
sentados nesse captulo apenas como uma viso do autor. Eles no podem
e no devem ser encarados como a nica expresso da realidade, a qual
pode ser sempre analisada segundo diferentes perspectivas. Inclusive, re-
comenda-se ao prprio leitor, quando necessrio, definir o seu prprio
conjunto de critrios, de acordo com o que considera mais relevante, e
fazer sua prpria comparao entre LPs.
10.1 Critrios para Avaliao
A avaliao de linguagens de programao pode ser necessria em vrias
situaes. Ao se desenvolver um sistema computacional, necessrio a-
valiar as LPs disponveis para escolher aquela que pode trazer maiores
facilidades e benefcios para a elaborao e operao do sistema. Avaliar
LPs tambm importante para uma instituio no momento de definir ou
padronizar as linguagens usadas na implementao de seus projetos. A
avaliao de LPs pode ainda ser necessria em comparaes tcnico-
cientficas.
Os critrios escolhidos para avaliao foram separados em dois grupos.
No primeiro grupo foram colocados os critrios mais gerais, os quais pro-
piciam uma comparao abrangente das linguagens mas no abordam es-
pecificamente os mecanismos e conceitos existentes na LP. O segundo
grupo de critrios contm os critrios mais especficos, os quais enfocam
fundamentalmente os mecanismos e conceitos oferecidos pelas LPs para
possibilitar a implementao de uma ou outra caracterstica em um siste-
ma computacional.
10.1.1 Critrios Gerais
A avaliao dos seguintes critrios gerais deve ser suficiente para definir
a LP a ser adotada em um sistema ou por uma instituio em boa parte
das situaes nas quais isso necessrio.
a. Aplicabilidade: avalia se a LP oferece todos os mecanismos necess-
rios para o desenvolvimento de aplicaes em geral ou de uma aplica-
o especfica.
b. Confiabilidade: avalia se a LP promove o projeto e implementao de
sistemas computacionais confiveis atravs do uso de conceitos que
maximizem a deteco automtica de erros e no estimulem a ocor-
rncia de erros.
348
c. Facilidade de Aprendizado: avalia se a LP oferece uma quantidade
regular de conceitos e simples o bastante para ser facilmente apren-
dida por um programador.
d. Eficincia: avalia o quanto a LP demanda de recursos de memria e
processamento durante a execuo dos programas.
e. Portabilidade: avalia a facilidade para se migrar os cdigos fonte dos
programas de uma plataforma para outra.
f. Suporte ao Mtodo de Projeto: avalia se a LP suporta o mtodo de
projeto a ser usado na aplicao especfica ou adotado pela instituio.
g. Evolutibilidade: avalia se a LP oferece conceitos adequados para esti-
mular a criao de programas legveis e facilmente atualizveis.
h. Reusabilidade: avalia os meios e facilidades oferecidas pela LP para
permitir a reutilizao de cdigo.
i. Integrao com outros softwares: avalia quais mecanismos a LP ofere-
ce (ou necessita) para construo de programas que incorporem (ou
sejam incorporados) por programas implementados em outras LPs.
j. Custo: avalia o custo financeiro necessrio para o uso da linguagem,
para a aquisio das ferramentas de desenvolvimento (tais como, edi-
tores de programas, compiladores, depuradores) na plataforma na qual
ser desenvolvida a aplicao e para uso da aplicao (tal como, por
exemplo, se existe custo para executar a aplicao utilizando uma m-
quina virtual).
10.1.2 Critrios Especficos
Esse grupo de critrios mais apropriado para realizar comparaes tc-
nico-cientficas entre LPs, embora alguns deles, em algumas situaes,
tambm possam ser importantes na definio da LP adotada em um sis-
tema ou por uma instituio. Para no tornar o processo de comparao
muito extenso, optou-se por selecionar apenas critrios considerados mais
relevantes. Os critrios especficos so listados a seguir.
a. Escopo: avalia se a LP requer a definio (ou declarao) explcita das
entidades de programao, associando-lhes um escopo de visibilidade
determinado pelo organizao textual do programa.
b. Expresses e Comandos: avalia se a LP oferece uma boa variedade de
expresses e/ou comandos para a construo de programas estrutura-
dos.
c. Tipos Primitivos e Compostos: avalia se a LP oferece uma ampla vari-
edade de tipos primitivos e compostos, permitindo representar qual-
quer categoria importante de dado. Complementarmente, esse critrio
deve levar em conta a forma de tratamento do tipo String na LP.
349
d. Gerenciamento de Memria: avalia se a LP oferece um mecanismo
prprio para gerenciamento de memria ou se o deixa sob a
responsabilidade do programador.
e. Persistncia de Dados: avalia o suporte oferecido pela LP para a reali-
zao de operaes relacionadas com a persistncia de dados.
f. Passagem de Parmetros: avalia os modos e mecanismos disponveis
na LP para realizao da passagem de parmetros.
g. Encapsulamento e Proteo: avalia se a LP oferece mecanismos para
encapsulamento e proteo dos dados.
h. Sistema de Tipos: avalia se a LP impe (ou no) uma disciplina rigo-
rosa na realizao de operaes sobre os dados, coibindo a execuo
de operaes sobre dados de tipos para os quais no foi planejada e
impedindo a manipulao de dados de um determinado tipo como se
fossem de outro.
i. Verificao de Tipos: avalia se a verificao de tipos das operaes
feita esttica ou dinamicamente.
j. Polimorfismo: avalia os tipos de polimorfismos oferecidos pela LP.
Em caso de existncia de polimorfismo de incluso, importante ava-
liar se a LP oferece herana simples ou mltipla.
k. Excees: avalia se a LP oferece mecanismos especficos para trata-
mento de excees ou se o controle de erros deixado sob responsabi-
lidade exclusiva dos programadores.
l. Concorrncia: avalia os recursos oferecidos pela LP para a construo
de programas concorrentes.
10.2 Uma Comparao entre C, C++ e JAVA
Nessa seo feita uma comparao das LPs C, C++ e JAVA com base
nos critrios gerais e especficos apresentados na seo anterior. Para ca-
da critrio indicado se a LP o atende, atende apenas parcialmente ou
no atende. Para cada critrio so apresentadas as razes que levaram a
essa classificao.
Critrios Gerais:
a. Aplicabilidade: C e C++ atendem complementamente esse critrio,
mas JAVA s o atende parcialmente. C, C++ e JAVA so linguagens
de propsito geral, contudo JAVA no oferece recursos para controlar
diretamente o hardware. Para fazer isso programas em JAVA devem
declarar mtodos nativos e implement-los em outras LPs.
b. Confiabilidade: JAVA atende esse critrio, mas C++ e C no o aten-
dem. C e C++ possuem inmeras caractersticas estimuladoras de er-
ros em programao (tais como, comando de desvio incondicional ir-
restrito e manipulao direta de endereos de memria atravs de
operaes com ponteiros).
350
c. Facilidade de Aprendizado: Nenhuma das trs linguagens atende a es-
se requisito, embora certamente o aprendizado de C e JAVA seja mui-
to mais fcil que o de C++. Embora seja uma linguagem simples, com
poucos conceitos e bastante ortogonal, a exigncia de uso massivo do
conceito de ponteiros acaba aumentando a dificuldade de aprendizado
de C. JAVA tambm uma linguagem bem projetada, mas no sim-
ples. Existem muitos conceitos (os quais nem sempre se combinam or-
togonalmente) e muitas maneiras de se atingir uma determinada fun-
cionalidade. C++ une as dificuldades de aprendizado de C e JAVA,
alm de possuir um nmero excessivamente alto de conceitos diferen-
tes.
d. Eficincia: Dentre as LPs mais conhecidas, C a LP mais eficiente
computacionalmente. C++ se aproxima de C nesse aspecto, mas o uso
dos mecanismos de orientao a objetos e um sistema de tipos mais ri-
goroso lhe impem alguma perda de eficincia. JAVA a menos efi-
ciente das trs LPs consideradas. Alm de implementar vrios meca-
nismos de verificao dinmica de erros, o acionamento do coletor de
lixo implica em perda substancial de desempenho de execuo.
e. Portabilidade: Embora C e C++ sejam linguagens com verses padro-
nizadas pela ANSI e pela ISO, comum em implementaes de com-
piladores oferecer caractersticas adicionais dependentes da plataforma
na qual o compilador atua. O uso dessas caractersticas implica em re-
duo da portabilidade. Adicionalmente, e com mais riscos de com-
prometimento da portabilidade, as definies de C e C++ deixam bre-
chas para que certos conceitos sejam definidos conforme a plataforma
de execuo (tais como, o intervalo de valores dos tipos inteiros). JA-
VA, por outro lado, foi projetada de modo a evitar brechas que com-
prometessem a portabilidade. Contudo, JAVA ainda no foi padroni-
zada por entidades como ANSI ou ISO. De fato, a detentora dos direi-
tos de propriedade sobre essa LP foi quem estabeleceu um padro, o
qual deve ser obedecido por qualquer implementao de JAVA.
f. Suporte ao Mtodo de Projeto: C suporta o mtodo estruturado. JAVA
suporta o mtodo orientado a objetos. C++ suporta predominantemen-
te o mtodo orientado a objetos, mas como se trata de uma extenso de
C, ela tambm suporta o mtodo estruturado.
g. Evolutibilidade: C possui caractersticas que podem ser facilmente u-
sadas para criar cdigo ilegvel e difcil de manter. C++ melhora esse
aspecto em relao a C quando a orientao a objetos utilizada, pois
estimula o encapsulamento e a proteo de dados. JAVA s admite a
programao orientada a objetos e ainda oferece estmulos para a
construo de cdigo bem documentado.
h. Reusabilidade: C oferece apenas o mecanismo de bibliotecas, compos-
tas por funes, tipos, variveis e constantes, para possibilitar o reuso
351
de cdigo. C++ e JAVA oferecem bibliotecas de classes e ainda pos-
suem o mecanismo de pacotes. Alm disso, essas linguagens dispem
de meios para promover o encapsulamento e proteo de dados, o que
estimula a construo de componentes reusveis, e o polimorfismo u-
niversal, o que facilita a criao de cdigo reusvel e a implementao
de frameworks.
i. Integrao: C e C++ permitem a criao de programas que podem in-
vocar cdigo compilado por qualquer LP. JAVA possui uma interface
especfica, chamada JNI, que permite a integrao de programas com
cdigo nativo em C e C++ apenas.
j. Custo: As verses padronizadas de C e C++ so linguagens de dom-
nio pblico e, portanto, podem ser usadas gratuitamente. JAVA de
propriedade da SUN Mycrosystems, a qual tem liberado o uso gratuito
dessa linguagem. Existem inmeras ferramentas de apoio ao desen-
volvimento de programas nessas linguagens nas diversas plataformas.
Algumas dessas ferramentas so distribudas gratuitamente. No existe
custo para uso das aplicaes em qualquer dessas LPs.
A tabela 10.1 sumariza as avaliaes dos critrios gerais nessas LPs.
Critrios Gerais C C++ JAVA
Aplicabilidade Sim Sim Parcial
Confiabilidade No No Sim
Facilidade de
Aprendizado
No No No
Eficincia Sim Sim Parcial
Portabilidade No No Sim
Suporte ao Mtodo
de Projeto
Estruturado Estruturado ou Ori-
entado a Objetos
Orientado a Objetos
Evolutibilidade No Parcial Sim
Reusabilidade Parcial Sim Sim
Integrao Sim Sim Parcial
Custo Dependente da
Ferramenta de
Desenvolvimento
Dependente da
Ferramenta de
Desenvolvimento
Dependente da
Ferramenta de
Desenvolvimento
Tabela 10. 1 - Avaliao de Critrios Gerais
Critrios Especficos:
a. Escopo: Todas as trs LPs requerem a definio explcita de entidades.
Essa definio associa s entidades um escopo de visibilidade. As re-
gras de escopo so praticamente as mesmas nas trs LPs, havendo a-
penas pequenas diferenas, tais como, JAVA no permite a definio
aninhada de entidades com mesmo nome; C no permite a definio
de variveis no bloco de definio do comando for; JAVA e C++ pos-
sibilitam tornar visvel, ou no, os campos de uma estrutura.
352
b. Expresses e Comandos: Todas as trs LPs oferecem uma ampla vari-
edade de expresses e comandos. Novamente, a maioria das expres-
ses e comandos apresenta significativa similaridade nas trs LPs. As
principais diferenas so o retorno booleano das condies dos co-
mandos de seleo e repetio em JAVA, enquanto em C e C++ eles
retornam inteiros; a substituio do comando goto em JAVA pelos es-
capes rotulados e tratamento de excees; e a existncia de precedn-
cia entre operandos em JAVA.
c. Tipos Primitivos e Compostos: C no oferece o tipo booleano. C++
chega a definir esse tipo, contudo ele equivale a um tipo inteiro. Seu
efeito, portanto, s aumenta a legibilidade da LP. Expresses boolea-
nas continuam produzindo e utilizando valores inteiros. C e C++ utili-
zam o tipo ponteiro para implementar tipos recursivos e para manipu-
lar valores do tipo funo. JAVA no trata mtodos como valores de
primeira classe e no oferece unies e tipos enumerados. Tais ausn-
cias so compensadas por propriedades da orientao a objetos nessa
LP. Nenhuma dessas LPs oferece conjuntos potncia.
d. Gerenciamento de Memria: C e C++ deixam a responsabilidade para
o programador. JAVA usa um coletor de lixo.
e. Persistncia de Dados: C e C++ oferecem funes (e classes) de bibli-
oteca para realizao de operaes sobre arquivos e deixam toda a res-
ponsabilidade pela execuo da persistncia de dados com o progra-
mador. possvel fazer interface com gerenciadores de bancos de da-
dos, mas no existe uma definio na linguagem sobre como isso deve
ser feito. Alm de dispode de uma biblioteca de classes para realizao
de operaes sobre arquivos, JAVA possui o recurso de serializao, o
que facilita sobremaneira a realizao de persistncia. JAVA tambm
define o JDBC, uma interface padro para o trabalho com gerenciado-
res de bancos de dados.
f. Passagem de Parmetros: C usa apenas o mecanismo de passagem por
valor, o que provoca o uso de ponteiros como parmetros em vrias
oportunidades, e permite a criao de funes com lista de parmetros
varivel. JAVA usa apenas passagem por valor para tipos primitivos e
por cpia de referncia para objetos. Dessas LPs, C++ a que oferece
um maior leque de opes, tendo funes com lista de parmetros va-
rivel, parmetros default, passagem por valor e por referncia.
g. Encapsulamento e Proteo: C oferece apenas encapsulamento de da-
dos, isto , no possvel estabelecer em uma nica entidade sinttica
dados e as operaes sobre eles. C tambm no possibilita a proteo
dos dados. C++ e JAVA oferecem recursos tanto para encapsulamento
quanto para proteo.
h. Sistemas de Tipo: O sistema de tipos de C facilmente violvel. In-
meras caractersticas, tais como, unies livres, coeres e aritmtica de
353
ponteiros, possibilitam tratar valores definidos de um tipo como sendo
de outro. C++ apresenta um sistema de tipos mais rigoroso quando so
usadas as caractersticas de orientao a objetos. Contudo, todas as
fragilidades do sistema de tipos de C tambm esto presentes. JAVA
possui um sistema de tipos bastante rigoroso.
i. Verificao de Tipos: As verificaes de tipos feitas por C so todas
estticas. C++ e JAVA fazem quase toda a verificao estaticamente,
mas tambm podem fazer alguma verificao dinmica. Isso ocorre,
por exemplo, ao se usar o polimorfismo de incluso e, somente no ca-
so de JAVA, ao se tentar realizar um acesso posio inexistente em
um vetor.
j. Polimorfismo: C somente possui coero e sobrecarga embutidas na
LP. C++ possui todos os tipos de polimorfismo embutidos na LP e
permite ao programador fazer sobrecarga de funes e operadores, a-
lm de criar cdigo com polimorfismo paramtrico e por incluso.
JAVA possui todos os tipos de polimorfismos embutidos, mas s ofe-
rece ao programador a possibilidade de sobrecarregar operadores e u-
sar polimorfismo por incluso.
k. Excees: C no oferece um mecanismo especfico para lidar com ex-
cees. C++ oferece um mecanismo, mas ele no precisa necessaria-
mente ser usado ou respeitado. JAVA oferece um mecanismo de exce-
es rigoroso para lidar com excees.
l. Concorrncia: C e C++ no oferecem recursos especficos para lidar
com programas concorrentes. necessrio utilizar bibliotecas espec-
ficas da plataforma na qual o sistema ser executado. JAVA oferece
recursos para criao de threads e sincronizao de mtodos de um
objeto.
A tabela 10.2 sumariza as avaliaes dos critrios especficos nessas LPs.
Critrios
Especficos
C C++ JAVA
Escopo Sim Sim Sim
Expresses e Co-
mandos
Sim Sim Sim
Tipos Primitivos e
Compostos
Sim Sim Sim
Gerenciamento de
Memria
Programador Programador Sistema
Persistncia de Da-
dos
Biblioteca de Fun-
es
Biblioteca de Clas-
ses e Funes
Biblioteca de Clas-
ses , Serializao e
JDBC
Passagem
de Parmetros
Lista varivel e pas-
sagem por valor
Lista varivel, pa-
rmetros default,
passagem por valor
e por referncia
Passagem por valor
e por cpia de refe-
rncia
354
Encapsulamento e
Proteo
Parcial Sim Sim
Sistema de Tipos No Parcial Sim
Verificao de
Tipos
Esttica Esttica e Dinmica Esttica e Dinmica
Polimorfismo Coero e Sobre-
carga
Coero, Sobrecar-
ga, Paramtrico e
Incluso
Coero, Sobrecar-
ga e Incluso
Excees No Parcial Sim
Concorrncia No No Sim
Tabela 10. 2 - Avaliao de Critrios Especficos
10.3 Consideraes Finais
Existem vrias outros conjuntos de critrios que podem ser usados para
comparar LPs. Existem tambm vrias outras comparaes interessantes
que podem ser encontradas na literatura ou na prpria Internet. David A.
Wheeler [Wheeler, 1997] utiliza o documento de especificao de ADA
para realizar uma ampla comparao entre ADA 95, C, C++ e JAVA. Pa-
tricia K. Lawlis [Lawlis, 1997] faz uma comparao de diversas LPs ba-
seada em um conjunto de critrios considerados importantes para a esco-
lha de linguagens em organizaes. Lutz Prechelt [Prechelt, 2000] faz
uma comparao emprica entre C, C++, JAVA e outras linguagens base-
adas em implementaes de um mesmo conjunto de requisitos.
10.4 Exerccios
1. Escolha uma outra LP de seu conhecimento e a inclua na comparao
feita na seo 10.2 desse captulo.

2. Especifique os requisitos de um sistema que voc precise implemen-
tar. Selecione algumas LPs disponveis para serem usadas na imple-
mentao. Use os critrios especificados na seo 7.1 para escolher a
LP a ser utilizada. Justifique a sua escolha.

3. Defina o seu prprio conjunto de critrios de avaliao e o utilize para
avaliar as LPs do exerccio 3 e escolher a LP a ser utilizada pelo mes-
mo sistema especificado no exerccio 3. Justifique a sua escolha.

355
Referncias Bibliogrficas
Appleby, D. Programming Languages: Paradigm and Practice, McGraw-Hill,
Inc., 1991.
Atkinson, M.P. & Morrison, R. Orthogonally Persistent Object Systems. Very
Large Databases (VLDB) Journal, vol. 4, #3, pp 319-401, 1995.
Burns, A. & Wellings, A. Real-Time Systems and Programming Languages
Chapter 8: Shared Variable-Based Synchronization and Communication,
Addison-Wesley, 2001.
Cardelli, L. & Wegner, P. On Understanding Types, Data Abstraction and
Polymorphism, Computing Surveys, vol. 17, # 4, pp 471-522, 1985.
Cardelli, L. Typeful Programming, Formal Descriptions of Programming
Concepts, Neuhold, E.J. & Paul, M. (eds.), Springer-Verlag, 1991.
Dijkstra, E.W. Goto Statement Considered Harmful, Communications of the
ACM, vol. 11, #3, pp 147-148, 1968.
Eckel, B. Thinking in C++, volume 1, 2 ed., Prentice Hall Inc., 2000 (disponvel
em formato digital em http://www.bruceeckel.com).
Eckel, B. Thinking in Java, 3 ed., Prentice Hall Inc., 2002 (disponvel em formato
digital em http://www.bruceeckel.com).
Eckel, B. & Allison, C. Thinking in C++, volume 2, 2 ed., Prentice Hall Inc.,
2003 (disponvel em formato digital em http://www.bruceeckel.com).
Flanagan, D. Java in a Nutshell, A Desktop Quick Reference, OReilly &
Associates, Inc., 2 ed., 1997.
Goldberg, D. What Every Computer Scientist Should Know About Floating-point
Arithmetic, ACM Computing Surveys, vol. 23, #1, pp. 5-48, 1991.
Guttag, J. Abstract Data Types and the Development of Data Structures,
Communications of the ACM, vol. 20, # 6, pp. 396-404, 1977.
Jordan, M. & Atkinson, M. Orthogonal persistence for java - a mid-term report.
Third International Workshop on Persistence and Java, Tiburon, CA, Sep,
1998.
Kernighan, B. W. & Ritchie, D. M. C: A Linguagem de Programao Padro
ANSI, Editora Campus Ltda., 1989.
Knuth, D.E. Structured Programming with Goto Statements, ACM Computing
Surveys, vol. 6, #4, pp 261-302, 1974.
Lawlis, P. K. Guidelines for Choosing a Computer Language: Support for the
Visionary Organization, 2
a
ed., 1997 (disponvel em formato digital em
http://archive.adaic.com/docs/reports/lawlis/content.htm).

356
Meyer, B. Object Oriented Software Construction, Prentice Hall International
Ltd., 1988.
Oliveira, R. S., Carissimi, A.S. & Toscani, S.S. Sistemas Operacionais, 2
a
edio,
Srie Livros Didticos, Instituto de Informtica da UFRGS, n.11, 2001.
Prechelt, L. An Empirical Comparison of C, C++, Java, Perl, Python, Rexx, and
Tcl for a Search/String Processing Program. Technical Report 2000-5,
Fakultt fr Informatik, Universitt Karlsruhe, Germany, 2000.
Pressman, R. S. Software Engineering: A Practitioners Approach, 4 ed.,
McGraw-Hill, Inc., 1997.
Sebesta, R. W. Concepts of Programming Languages, 4 ed., Addison-Wesley
Longman, Inc., 1999.
Silberschatz, A., Gagne, G. & Galvin, P.B. Sistemas Operacionais:Conceitos e
Aplicaes, 1
a
ed., Editora Campus, 2000.
Stroustrup, B. A Linguagem de Programao C++, 3
a
ed., Bookman Companhia
Editora, 2000.
Watt, D. A. Programming Languages: Concepts and Paradigms, Prentice Hall
International Series in Computer Science, 1990.
Wheeler, D. A. ADA, C, C++ and JAVA vc. The Steelman, ACM SIGAda Ada
Letters, Volume XVII, Issue 4, pp.88-112, 1997, ACM Press.

Lista de Erratas
Lamentavelmente, apesar de todo o esforo de escrita e reviso, o texto do livro
contm erros. Esses erros sero corrigidos na prxima tiragem do livro. Segue abaixo
uma lista dos erros j identificados:
1) Exemplo 2.13 (Pgina 36): h um espao em branco aps a virgula que est sendo
atribuda.
Como est no livro:
char virgula = ', ';
Como deveria ser:
char virgula = ',';
2) Exemplo 2.21 (Pgina 41): no existe a palavra then associada ao primeiro
comando if do exemplo.
Como est no livro:
if (n == 0) then {
Como deveria ser:
if (n == 0) {
3) Lista de Exerccios do Captulo 2 (Pgina 44): h duas questes de nmero 6. O
correto que a segunda tenha o nmero 7.
4) Lista de Exerccios do Captulo 2 (Pgina 44), na segunda questo de nmero 6: as
funes f e main esto na ordem invertida, isto o cdigo da funo f deveria estar
antes do cdigo da funo main.
5) Linha com converso binria para decimal (Pgina 48): o expoente do ltimo
nmero 2 deve ser 0 e no 1
Como est no livro:
11111101 = 1x2
7
+ 1x2
6
+ 1x2
5
+ 1x2
4
+1x2
3
+ 1x2
2
+ 0x2
1
+ 1x2
1
= 253
Como deveria ser:
11111101 = 1x2
7
+ 1x2
6
+ 1x2
5
+ 1x2
4
+1x2
3
+ 1x2
2
+ 0x2
1
+ 1x2
0
= 253
6) Linha com converso binria para decimal (Pgina 49): o expoente do ltimo
nmero 2 deve ser 0 e no 1
Como est no livro:
00000011 = 0x2
7
+ 0x2
6
+ 0x2
5
+ 0x2
4
+0x2
3
+ 0x2
2
+ 1x2
1
+ 1x2
1
= 3
Como deveria ser:
00000011 = 0x2
7
+ 0x2
6
+ 0x2
5
+ 0x2
4
+0x2
3
+ 0x2
2
+ 1x2
1
+ 1x2
0
= 3
7) Figura 3.3 (Pgina 53): a sequncia binria est incorreta, contendo valores de
nmeros superiores a um algarismo em algumas sequncias de 4 dgitos binrios
Como est no livro:




Como deveria ser:



8) Exemplo 3.2 (Pgina 57): falta a letra m de medio em uma das linhas do exemplo
Como est no livro:
edicao.centimetros=180;
Como deveria ser:
medicao.centimetros=180;
9) Figura 3.8 (Pgina 59): a segunda clula contm apenas 15 bits quando deveriam
ser 16
10) Exemplo 3.15 (Pgina 74): uso da palavra desreferenciamento em vez de
derreferenciamento na legenda do exemplo
Como est no livro:
Exemplo 3.15 Desreferenciamento implcito em FORTRAN 90 e explicito em C
Como deveria ser:
Exemplo 3.15 Derreferenciamento implcito em FORTRAN 90 e explicito em C
11) Exerccio 6 do Captulo 3 (Pgina 79): falta ponto e vrgula aps a definio de
union cidadania e struct pessoa.
Como est no livro:
union cidadania {
enum classe c;
enum instrucao i;
}



1010 0011 1011 0000 1110 0110 1111 1000 0000 0010 1000 1011
2 bytes
4 casas decimais
4 bytes
7 casas inteiras
1 sinal
sinal
0010 0011 0011 0000 1000 0110 0111 1001 0000 0010 1000 0011
2 bytes
4 casas decimais
4 bytes
7 casas inteiras
1 sinal
sinal
Como deveria ser:
union cidadania {
enum classe c;
enum instrucao i;
};

Como est no livro:
struct amostra {
int n;
struct pessoa p[10];
}

Como deveria ser:
struct amostra {
int n;
struct pessoa p[10];
};
12) Exerccio 7 do Captulo 3 (Pgina 80): a linha return p; est com problema de
identao.
Como est no livro:
#include <iostream>
int& xpto (int sinal) {
int p = 4;
if (!sinal) {
p*=sinal;
} else {
p++;
}
return p;
}

Como deveria ser:
#include <iostream>
int& xpto (int sinal) {
int p = 4;
if (!sinal) {
p*=sinal;
} else {
p++;
}
return p;
}


13) Exerccio 9 do Captulo 4 (Pgina 104): falta end if; do comando if na procedure
B.
Como est no livro:
procedure B (k: boolean);
w: integer;
begin -- B
if k then
B (false);
else -- #;
end B;

Como deveria ser:
procedure B (k: boolean);
w: integer;
begin -- B
if k then
B (false);
else -- #;
end if;
end B;
14) Exemplo 4.11 (Pgina 99): existe uma linha adicional desnecessria com );
Como est no livro:
ResultSet resultados = comando.executeQuery (
"SELECT nome, idade, nascimento FROM pessoa " +
"WHERE idade < " + idadeMaxima);
);
while (resultados.next()) {
Como deveria ser:
ResultSet resultados = comando.executeQuery (
"SELECT nome, idade, nascimento FROM pessoa " +
"WHERE idade < " + idadeMaxima);
while (resultados.next()) {
15) Linha com exemplos de expresses literais (Pgina 107): a expresso literal c
est com aspas duplas e deveria ser aspas simples
Como est no livro:
2.72 99 0143 c 0x63
Como deveria ser:
2.72 99 0143 c 0x63
16) Linha em cdigo ADA (Pgina 111): falta end if; ao final da linha
Como est no livro:
if x > y then max := x; else max := y;
Como deveria ser: