notas-de-aula- Schneider
29 pág.

notas-de-aula- Schneider


DisciplinaProgramação I21.263 materiais243.669 seguidores
Pré-visualização11 páginas
A inferência de tipos nem sempre 
funciona, ou seja, nem sempre é possível determinar o tipo das variáveis pelo seu uso, o que faz 
com que as linguagens que usam esse recurso ofereçam formas de declarar tipos, permitindo que o 
programador resolva esses casos especiais.
2.2.3.2 Verificação de tipos
Deve-se verificar se os operadores (ou sub-rotinas) e seus operandos fazem sentido juntos. Os tipos 
devem ser compatíveis1 ou deve existir alguma regra que permita que seja feita coerção de tipos.
Pode ser estática (em tempo de compilação) ou dinâmica (tempo de execução). A forma dinâmica 
exige que se mantenha informação sobre os tipos em tempo de execução.
Existe uma tendência a se preferir que haja verificação de tipos e que ela seja feita em tempo de 
compilação (perda de flexibilidade para o programador).
Uma linguagem que permite ao compilador/interpretador detectar qualquer erro de tipos é dita uma 
linguagem com tipificação forte. Na prática, sempre existe algum erro de tipo que não pode ser 
verificado, porém existem linguagens com tipificação notadamente \u201cmais forte\u201d do que outras. 
Essas linguagens são tidas como \u201cmais confiáveis\u201d.
Além da vinculação dinâmica de tipos, a existência de muitas regras para coerção automática de 
tipos também reduz a capacidade de verificação de erros de tipo, levando ao que chamamos de 
\u201clinguagem com tipificação fraca\u201d. A linguagem C, por exemplo, apesar da vinculação estática de 
tipos, é considerada uma linguagem com tipificação fraca.
A forma como uma linguagem lida com a verificação de tipos é frequentemente chamada de o 
sistema de tipos (ver 2.8) da linguagem. Ela é de extrema importância pois os erros de tipos são 
fortes indícios de outros erros.
2.2.3.3 Compatibilidade de Tipos
Alguns tipos deveriam ser passíveis de serem usados em lugar de outros sem que haja necessidade 
de coerção. Isso pode feito de duas formas:
\u2022 compatibilidade de nome: duas variáveis tem o mesmo tipo se estão na mesma declaração 
ou são declaradas com tipos de mesmo nome.
\u2022 compatibilidade de estrutura: duas variáveis tem o mesmo tipo se têm estruturas idênticas.
A primeira forma é muito restritiva e com frequência diferencia tipos que o programador não queria 
diferenciar. Exige que tipos sejam declarados globalmente, podendo causar problemas com 
bibliotecas.
A segunda forma traz problemas de indireção pois tipos podem ser formados de outros tipos e 
podem ser recursivos. Ela torna impossível a diferenciação de tipos com a mesma estrutura (o que 
pode ser desejável)
1 Na versão traduzida do livro texto, onde se define o que é a verificação de tipos, lê-se que um tipo que seja 
convertido automaticamente é compatível. Isso causa algum conflito com a definição da compatibilidade de tipos 
(ver 2.2.3.3) e provavelmente dificulta a fixação desse segundo conceito. Por isso, a definição de verificação de 
tipos aparece aqui ligeiramente diferente.
prof. Bruno Schneider 9
Notas de Aula de GCC105 - BCC e BSI - UFLA
Na prática as linguagens usam uma mistura dos dois tipos pois nenhum dos dois produz os 
resultados tidos como desejáveis em alguns casos. Na orientação a objetos a compatibilidade de 
tipos está fortemente ligada ao conceito de herança.
2.2.4 Vinculação de Armazenamento (tempo de vida)
O tempo de vida de uma variável é o tempo durante o qual ela está vinculada à uma célula de 
armazenamento (um endereço).
Em função do tempo de vida, uma variável pode ser classificada como: 
\u2022 estática: a vinculação existe durante toda a execução do programa e não muda; tem 
endereçamento direto (mais rápido); não existe overhead para alocar e desalocar; permitem 
criar variáveis \u201csensíveis à história\u201d; podem ser um desperdício de espaço se não forem 
usadas ao longo de toda a execução; podem conflitar com a recursividade.
\u2022 stack-dinâmica: a vinculação é criada quando a execução atinge a declaração da variável e 
deixa de existir quando o bloco de código termina sua execução; as vinculação de tipo são 
estáticas; funciona como uma forma de compartilhar memória entre os vários subprogramas.
\u2022 heap-dinâmica explícita: são criadas (sem nome) por operadores e funções de alocação de 
memória e são acessadas por meio de referências; tem vinculação estática de tipo; 
apresentam o overhead do acesso indireto. Ex.: objetos em Java.
\u2022 heap-dinâmica implícita: são vinculadas ao armazenamento e ao tipo durante uma 
atribuição; são muito flexíveis; reduzem a capacidade do compilador de encontrar erros.
O gerenciamento do heap pode ser feito por algoritmos de garbage collection.
O tempo de vida de uma variável geralmente é tempo de execução de um bloco. Várias linguagens 
oferecem formas de vinculação estática ao armazenamento, o que permite a criação de um tipo de 
memória em subprogramas.
Nem sempre uma variável vinculada a um armazenamento é acessível, o que é desejável, seguindo 
o princípio do escopo mínimo.
Vinculação dinâmica ao armazenamento é uma propriedade que conflita com instruções tipo \u201cgoto\u201d.
2.2.5 Inicialização
Inicialização é a vinculação ao valor no momento da vinculação ao armazenamento. Variáveis 
estáticas em termos de armazenamento precisam ser vinculadas a um valor antes da execução. 
Nesse caso, pode não ser possível usar uma expressão para a inicialização.
Nem toda linguagem oferece recursos de inicialização (ex.: Pascal).
2.3 Avaliação de expressões
Uma linguagem deveria determinar precedência de operadores, regras para não coincidência de 
tipos e para avaliação em curto-circuito.
2.3.1 Expressões aritméticas
Consistem em operadores, operandos, parênteses e funções. Operadores podem lidar com um ou 
prof. Bruno Schneider 10
Notas de Aula de GCC105 - BCC e BSI - UFLA
mais operandos e podem ser sobrecarregados em algumas linguagens.
Quase sempre as linguagens usam parênteses para fazer associação explícita.
2.3.1.1 Ordem de avaliação de operadores
É parcialmente determinada pela precedência dos operadores e parcialmente pela associatividade na 
expressão; seguindo os conceitos da matemática.
Um operador pode ter associatividade à direita (ex.: x = z = 1;) ou à esquerda (à esquerda é mais 
comum). Um operador pode ser não associativo, exigindo a presença de parênteses. Quando os 
operadores são de mesma precedência, a associatividade à esquerda diz que o operador da esquerda 
Por serem aproximações das expressões matemáticas, os limites dos tipos podem tornar uma 
expressão não-associativa (ex.: soma com números muito grades e muito pequenos).
As linguagens C, C++ e Java oferecem um operador condicional ternário (?:).
Algumas ordem podem ser mais rápidas que outras, pode ser interessante deixar que o compilador 
escolha a ordem.
2.3.1.2 Ordem de avaliação dos operandos
A ordem de avaliação dos operandos pode influenciar o resultado (ex.: funções com efeitos 
colaterais).
Algumas ordens de avaliação podem ser mais rápidas que outras.
Algumas linguagens deixam a ordem a critério do compilador e existem que o programador se 
preocupe em evitar os casos em que a ordem é importante (ex.: Pascal e Ada) enquanto outras 
determinam uma ordem (ex.: Java). 
2.3.2 Sobrecarga de Operadores
A sobrecarga, no contexto de linguagens de programação, é a capacidade de fazer com que um 
único identificador seja usado denotar várias abstrações.
Usar o mesmo operador para representar funções completamente diferentes prejudica a legibilidade. 
Erros de digitação ficam mais difíceis de serem detectados.
Algumas linguagens (notadamente as que permitem tipos abstratos) permitem que o programador 
sobrecarregue operadores. Quando bem usado, esse recurso pode aumentar a legibilidade.
Se diversas bibliotecas sobrecarregarem os mesmos operadores, podem surgir conflitos.
2.3.3 Conversões de tipo
Podem ser de estreitamento (inseguras) ou alargamento (o novo tipo inclui aproximações para todos 
os valores do tipo original). Os nomes \u201calargamento\u201d e \u201cestreitamento\u201d