Você está na página 1de 5

Paradigma Imperativo

O paradigma imperativo é aquele no qual os programas são sequências de instruções (ordens) e estão
associados ao conceito de algoritmo. Nem toda sequência de instruções é um algoritmo e nem todo programa
é uma sequência de instruções. Os paradigmas da programação declarativa, que serão vistos mais à frente,
são conjuntos de declarações de relacionamentos, usados para descrever uma solução ou resultado de um
problema.
Os temas abordados aqui (variáveis, tipos de dados, avaliação de expressões e subprogramas – com a
exceção de controle de fluxo) não são específicos da programação imperativa, eles aparecem em todos os
paradigmas de programação. Porém, estão organizados desta forma para ficar em acordo com a ementa da
disciplina. Todos na disciplina já têm alguma experiência com programação imperativa, portanto resta
apresentar apenas alguns detalhes.

1. Variáveis
As variáveis são associações que identificam informações. Elas podem ser caracterizadas por:
• nome: as linguagens possuem regras a respeito de quais sequências de caracteres podem ser usadas
como nomes, essas regras não variam muito nas linguagens modernas, mas cabe destacar que a
possibilidade usar caracteres acentuados vem ganhando força;
• endereço: toda informação precisa ficar guardada em algum lugar na memória e para isso, variáveis
precisam ser associadas a endereços, que podem ser usados para acesso à informação mesmo na
ausência de um nome;
• valor: toda posição da memória contém um valor mas algumas linguagens de programação fazem
um controle que permite usar variáveis como se elas pudessem não ter valor, essa ausência de valor
pode ser usada como um valor especial;
• tipo: toda variável precisa ser associada a um tipo de dados, porém algumas linguagens fazem um
controle que permite usar variáveis como se elas não tivessem um tipo, isso é feito modificando o
tipo associado durante a execução do programa;
• tempo de vida: o tempo de vida de uma variável é período da execução em que a variável está
associada com uma posição de memória, se uma variável não está associada, então ela não pode ser
usada (não existe);
• escopo: é trecho do código fonte em que um nome está associado à variável, variáveis podem ser
usadas mesmo sem um nome associado, desde possam ser identificada por ponteiros (endereços).
Essas características já foram mais variáveis no passado, em especial, as regras de escopo das linguagens de
programação eram muito diferentes até a década de 90 (mais ou menos).

2. Tipos de Dados
Os tipos de dados são apresentados na disciplina inicial de programação e não precisam ser apresentados
novamente. Porém, serão reforçados alguns casos mais importantes no contexto do nosso curso.

2.1 Integrais e Ponto Flutuantes


Ao escolher tipos de dados para valores, é importante lembrar que a aritmética para tipos integrais é exata
enquanto que a aritmética para ponto flutuante é aproximada. Portanto, é preciso mais cuidado com as
operações usando ponto flutuante, evitando operações desnecessárias. Nem sempre é possível fazer poucas
operações com os dados e assim, pode ser necessário ter algum tipo de controle algorítmico para evitar que
os erros das operações se acumulem ao ponto de o valor resultante possa ser considerado errado.
A representação de dinheiro é uma fonte frequente de dúvidas para os programadores porque costuma-se usar
tipos não integrais e se deseja exatidão de resultados. Algumas linguagens (normalmente mais antigas)
possuem um tipo, geralmente chamado de "decimal", que proporciona representação de valores com um
número fixo de casas decimais. Vale lembrar que os processadores não possuem representação para tais tipos
e sua existente meramente torna automático algum cuidado que o programador deveria tomar.
As soluções comuns são:
• usar tipos integrais para representar a menor unidade – por exemplo, representar o centavos ao invés
de reais;
• usar ponto flutuante com alta precisão e usar formatação de escrita;
• usar ponto flutuante e eventualmente fazer conversões para integral e de volta para ponto flutuante,
lembrando que isso não funciona com números de magnitude grande.
Apesar disso, usar mais armazenamento do que variáveis próprias da linguagem, com 64 ou 128 bits, como
acontece com bibliotecas para números de precisão dinâmica, é um desperdício de armazenamento.

2.2 Variáveis sem Valor


Outra diferença entre linguagens, em relação aos tipos de dados, que é importante, é a capacidade de
algumas linguagens de permitir que as variáveis possam “não ter valor”. Isso é possível pelo uso de
ponteiros, ou seja, a variável que aparenta ser do tipo T é, na verdade, um ponteiro para T. Isso obviamente
tem um custo: toda variável ocupa na memória não apenas o espaço para guardar a sua informação, mas
também para guardar um endereço de memória.

2.3 Vetores Associativos


Algumas linguagens permitem que os índices de vetores sejam de tipos que não sejam integrais. O uso de
strings como índices é o caso mais comum e versátil. Os vetores desse tipo recebem nomes bem diversos na
documentação de linguagens diferentes. Alguns exemplos: em Python, são chamados de "dicionários", em
Perl são chamados de "hashes", em Lua são chamados de "tabelas" e em JavaScript, a linguagem aproveita
os recursos de encapsulamento, junto com a tipagem dinâmica para produzir o efeito de vetores associativos,
chamando essas estruturas de objetos.
Por causa da flexibilidade, os vetores associativos podem ser usados no lugar de registros, mas vale lembrar
que os registros não costumam ser usados em função da Orientação a Objetos. Vetores associativos são
geralmente implementados como árvores de busca ou como tabelas hash, tendo características de ocupação
de armazenamento e de velocidade para inserção e remoção muito diferentes de um vetor verdadeiro.

3. Avaliação de Expressões
As linguagens costumam permitir que se escreva conjuntos de vários operadores para representar um valor,
mas os operadores precisam ser processados um de cada vez. As linguagens possuem regras diferentes para
determinar qual a ordem de avaliação dos operadores. Existem dois elementos importantes nessas regras:
precedência e associatividade. O uso de parênteses é importante para produzir ordens diferentes e para evitar
a necessidade de se decorar tais regras.
Para produzir resultados mais rápidos, é comum que a avaliação termine sem que todos os operadores
tenham sido avaliados, nos casos em que é possível. Esse recurso é conhecido como avaliação em curto-
circuito. É comum que os operadores booleanos sejam avaliados em curto-circuito e os aritméticos não
sejam. De qualquer forma é bom estar atento às regras de cada linguagem; a mesma expressão pode estar
certa ou errada dependendo da forma como é avaliada.
4. Controle de Fluxo
As instruções de controle de fluxo são aquelas que definem qual a sequência de instruções a ser seguida na
execução do programa.
Os tipos mais comuns de controle de fluxo são os seletores (comandos que servem para escolher um entre
vários caminhos de execução) e os comandos de repetição. Quanto aos comandos de repetição, existem
quatro tipos e as linguagens oferecem um ou mais comandos de alguns desses tipos:
• Repetição controlada por contador: são aquelas em que não há um teste, ao invés disso o
programador informa quantas vezes a repetição deve ocorrer, possivelmente, com quais valores a
variável de controle estará associada.
• Repetição controlada por teste: são aquelas em que o programador precisa pensar num teste que
define se a repetição deve parar ou continuar. Existem quatro variações, segundo dois subtipos:
◦ teste no início (repete zero ou mais vezes) versus teste no fim (repete uma ou mais vezes)
◦ teste positivo (teste indica se repete) versus teste negativo (teste indica se para de repetir)
Nas linguagens declarativas puras, não existem instruções de controle de fluxo, porém é comum existir
operadores de seleção, em que um teste determina se o valor de uma expressão é esse ou aquele.

5. Subprogramas
Para lidar com a complexidade de programas grandes, é importante dividi-los em pedaços independentes, ou
seja, que possam ser entendidos, modificados e corrigidos sem que seja necessário levar em consideração os
outros pedaços que ele usa ou os que o usam.
Existem vários tipos diferentes de subprogramas: sub-rotinas, corrotinas e threads. As sub-rotinas podem ser
divididas em funções e procedimentos, várias linguagens seguem o modelo de C, em que a sintaxe das duas
tem os mesmos elementos. As corrotinas são um recurso antigo para programação concorrente e não são
comuns nas linguagens modernas. Threads são um recurso mais moderno para programação concorrente e
geralmente não aparecem com uma sintaxe diferente de subprogramas, mas sim como uma forma diferente
de ativar subprogramas, geralmente disponibilizada por meio de bibliotecas.
Na Orientação a Objetos, os subprogramas estão associados aos tipos de dados e são chamados de
"métodos". Na programação lógica, os subprogramas têm um significado um pouco diferente e são
chamados de "predicados".
As linguagens variam bastante em relação à flexibilidade que apresentam para declarar e para usar
subprogramas. A possibilidade e a forma como subprogramas podem ser associados à variáveis ou passados
como parâmetros para outros subprogramas é um ponto de destaque na expressividade das linguagens.
Existem ainda recursos diversos em relação à passagem de parâmetros.

5.1 Passagem de Parâmetros


Parâmetros são a porção variável de um subprograma. Eles podem ser passados por valor, por referência, por
resultado, por valor-resultado ou por nome. As linguagens mais modernas costumam disponibilizar apenas as
passagens por valor e por referência, sendo a segunda menos comum. O uso de ponteiros faz com que apenas
a passagem por valor possa atender a todas as necessidades do programador, que são apenas três: receber
valores, retornar valores ou receber valores que serão modificados durante a execução do subprograma.
As várias formas de passagem de parâmetros possuem custos de tempo de execução e espaço de
armazenamento diferentes, que precisam ser considerados na implementação de programas. Pode ser
importante evitar cópias de grandes quantidades de informação, outra necessidade do programador que pode
ser resolvida usando ponteiros. A linguagem C++ possui também a passagem por referência constante, que
nada mais é do que a declaração de um parâmetro passado por referência como sendo uma constante. Essa
construção junta a segurança da passagem por valor com a eficiência da passagem por referência.
A passagem por valor é considerada segura porque as modificações em seu valor que forem realizadas dentro
do subprograma não afetam a execução dos outros subprogramas. A passagem por referência é eficiente
porque não requer a criação de uma cópia da informação em outro lugar da memória. As passagens por
nome, por resultado e por valor-resultado foram consideradas desnecessárias e fonte de problemas de
manutenção, não aparecendo em linguagens modernas.
Como o uso de ponteiros simplifica o projeto de passagem de parâmetros, linguagens como Java
transformaram (quase) todas as variáveis em ponteiros, que são usados de uma forma mais controlada para
evitar os problemas tradicionalmente associados ao uso de ponteiros.
As linguagens de programação apresentam grande quantidade de recursos relacionados ao uso de
subprogramas. O objetivo é flexibilizar o uso, possibilitando que o programador use um mesmo subprograma
numa grande quantidade de casos distintos. Por exemplo: um subprograma para ordenar um vetor não
deveria ter que ser escrito novamente para cada tipo de dados que for ordenar. Ordenar números inteiros ou
ordenar funcionários pelo nome deveria ser realizado pelo mesmo subprograma. Ordenar em ordem
crescente ou decrescente também deveria ser realizado pelo mesmo subprograma. O critério de ordenação
(por nome, data, salário, etc.) também não deveria exigir subprogramas diferentes.
A seguir são descritos alguns recursos para flexibilização de subprogramas.

5.2 Sobrecarga
A sobrecarga é criação de subprogramas diferentes com o mesmo nome. Não é uma forma de reuso
propriamente dita porque consiste na declaração de novos subprogramas; porém é um recurso que aumenta a
facilidade de escrita e, se bem usado, aumenta a legibilidade do código. Por exemplo, a comparação entre
valores de tipos diferentes (inteiro, string, funcionários) não deveria ter nomes diferentes. É desejável que o
programador ative o subprograma "menor" para ver se um valor é menor que outro, sem ter que se preocupar
se ele está comparando um outro tipo. Preferencialmente, esses subprogramas devem se chamar "<" em
todos os casos e a linguagem deve ter implementações pré-definidas para os tipos primitivos da linguagem,
permitindo que o programador crie outras implementações de "<" para os tipos que ele criar. Subprogramas
com nomes especiais e sintaxe especial como o "<" são chamados de operadores, mas não deixam de ser
subprogramas.
Comparar dois inteiros ou comparar dois nomes não é algo que compartilhe a mesma sequência de
instruções. É por isso que um recurso de flexibilidade em que é necessário "escrever de novo" faz sentido.

5.3 Subprogramas Genéricos


Uma linguagem projetada para permitir que se encontrem muitos erros de tipo é natural uma linguagem que
tem flexibilidade menor. É desejável fazer subprogramas que possam receber tipos diferentes, evitando assim
que o subprograma precise ser escrito mais de uma vez, mas isso conflita com a capacidade de verificar se os
tipos usados estão corretos.
Os subprogramas genéricos são uma estratégia para tentar conciliar a vinculação estática de tipos com a
flexibilidade do tratamento igual para tipos diferentes. A ideia é parametrizar não apenas os valores que
variam num subprograma mas também seus tipos. Na declaração de parâmetros, usa-se uma especificação
incompleta de tipo (tipo genérico) que determina o tipo de parâmetros para uma ativação em particular (e não
para todas ativações), sem que seja necessário saber qual é o tipo em tempo de compilação. Subprogramas
genéricos são, portanto, subprogramas que a declaração de um ou mais tipos de parâmetros foi
parametrizada.
5.4 Subprogramas como Parâmetros
Subprogramas são conjuntos de instruções. Conjuntos de instruções são armazenados em memória tal qual
valores numéricos, mas são informação de natureza bem diferente. Não é comum que uma linguagem
permita armazenar subprogramas em variáveis. Ainda assim, é desejável que um subprograma possa ser
passado como parâmetro para outro subprograma.
Imagine um subprograma de ordenação. Deseja-se ordenar funcionários. Entretanto, algumas vezes o
programa vai querer ordenar por nome, outras por data de admissão, outras por salário, etc. Para produzir um
subprograma de ordenação capaz de atender todas essas demandas, seria útil que os parâmetros fossem: o
conjunto de itens a ordenar e o critério de ordenação. O critério de ordenação seria um subprograma que
recebe dois funcionários e retorna um booleano, indicando qual deles deve vir primeiro na ordenação.
Usando outros critérios de ordenação, esse mesmo subprograma de ordenação poderia ser usado num
programa que ordena pedidos de clientes.
As linguagens variam bastante a respeito de como permitir isso, mas o objetivo normalmente é que se use o
identificador de um subprograma na passagem de parâmetros e que tal subprograma possa ser ativado pelo
seu nome local.

Você também pode gostar