1. Resumo de Linguagens de
Programação I
Paradigmas de Linguagens de Programação
As linguagens adotam técnicas comuns de programação, desenvolvidas de forma
independente de uma linguagem conforme a área de Linguagens de Programação
evolui.
As linguagens de programação podem ser classificadas de muitas formas
diferentes. Uma das formas de classificação é quanto ao paradigma de programação.
No contexto de linguagens de programação, podemos dizer de maneira bem
simples que paradigma é um estilo. Porém estamos nos referindo a estilo numa
conotação forte. Para serem chamados de paradigmas de programação, estilos precisam
ser suficientemente diferentes e completos para serem considerados uma forma de ver o
mundo.
Autores diferentes classificam as linguagens de programação de formas
diferentes. Apesar disso, é geralmente aceito que existem dois tipos principais de
programação:
(a) Programação imperativa(ou procedural), de acordo com a qual os
programas são construídos por meio de uma sequência de ordens - o programador
escreve um procedimento que leva à solução de um problema;
(b) Programação declarativa (ou descritiva), de acordo com a qual os
programas são construídos simplesmente pela descrição da solução de um problema -
cabe ao compilador encontrar uma sequência de instruções que produz a solução
declarada. Na programação declarativa, existem duas formas principais de se descrever
soluções:
1 - Através de funções (conceito matemático de associação entre elementos de
um conjunto domínio para um conjunto imagem) e
2 - Através de predicados (elementos sintáticos que permitem expressar relações
entre elementos de conjuntos). Vale lembrar que uma função é um tipo especial de
relação de associação.
São três os paradigmas de programação:
• Programação imperativa (ou procedural) - em que programas são implementações de
algoritmos (sequência de instruções que levam à solução de um problema);
• Programação funcional - em que programas são implementações de funções
• Programação lógica - em programas são implementações de predicados (relações entre
elementos de conjuntos diversos).
2. É comum encontrar autores que classificam a Programação Orientada a Objetos
como sendo um paradigma de programação. Essa classificação considera que o projeto
de programas orientados a objetos segue conceitos de destaque suficiente para que esse
estilo de programação seja classificado à parte da programação imperativa.
Tipos de Dados
Dizem o que os bits representam.
O uso do tipo depende do problema.
Algumas linguagens não tem o tipo de dado que você precisa, pode ser
necessário implementá-lo. (Ex.: implementar listas ou árvores usando vetores).
Um tipo primitivo de dados é aquele que não pode ser decomposto em partes
mais simples. Para tal classificação, considera-se a linguagem propriamente dita (e não
suas bibliotecas). Exemplo: Inteiros.
Um fator importante para determinar a facilidade com que os computadores
podem executar uma tarefa é quão bem os tipos de dados coincidem com o espaço de
problema do mundo real. Portanto, é crucial que uma linguagem suporte uma variedade
de tipos de dados e estruturas.
Os tipos definidos pelos usuários oferecem uma melhor legibilidade pelo uso de
nomes de tipos significativos.
Descritor É um conjunto de células de memória que armazenam atributos de
variáveis. São construídos pelo compilador e são usados para a verificação de tipos e
pelas operações de alocação e de desalocação.
Inteiros
Os inteiros são infinitos, porém a capacidade de bits para inteiro é fixa.
Atualmente, muitos computadores suportam diversos tamanhos de inteiros, e
essas capacidades são refletidas em algumas linguagens de programação. Por exemplo,
o Ada permite que as implementações incluam ate três tamanhos de inteiros: Short
Integer, Integer e Long Integer. Em outras linguagens, como o C++, incluem tipos
inteiros sem sinal, usados frequentemente com dados binários.
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. A vantagem desses tamanhos diferentes é não
desperdiçar memória.
Um inteiro negativo poderia ser armazenado em uma notação de sinal-
magnitude, na qual o bit de sinais é fixado para indicar o valor negativo, enquanto o
restante da cadeia de bits representa o valor absoluto de número, entretanto, essa
notação não se presta para a aritmética computadorizada.
A maioria dos computadores atualmente usa uma notação chamada
complemento de dois para armazenar inteiros negativos, o que é conveniente para a
adição e para a subtração. Para converter a representação binária de um
número em seu decimal correspondente segundo a notação de
complemento a dois é um pouco mais trabalhoso. Primeiramente, é
necessário verificar se o dígito mais à esquerda do número é um ou
zero. Se for zero, o número será zero ou positivo. Se for um, o número
decimal será negativo. Nesse caso, deve-se inverter a representação
binária do número e incrementá-lo de um. Assim, obtém-se a
3. representação do número na base binária. Basta, então convertê-la
para a base decimal levando em conta o seu sinal. Por exemplo:
11111101 (representação na notação de complemento a dois)
1 (dígito mais a esquerda –> número é negativo)
00000010 (representação com inversão binária)
00000001 (representação binária do número um)
00000011 (adição binária do número invertido e um)
00000011 = 0x27+0x26+0x25+0x24+0x23+0x22+1x21+1x20 = 3
11111101 = -3 (considerando o sinal)
As principais vantagens da notação de complemento a dois são
ter uma representação única para o número zero e também poder
utilizar os operadores aritméticos binários para implementar suas
próprias operações.
Wrap Arround quando é estourado o limite de um tipo de dado,
o resultado possui uma parte da operação certa, pois o computador
opera até o número máximo de bits possível.
Inteiros com sinal possuem menos espaço do que os inteiros
sem sinais.
Inteiros sem sinal possuem mais espaço, pois não precisam
representar o caractere ‘’-‘’.
Ponto Flutuante
Modelam os números reais, mas as representações são somente
aproximações para a maioria dos números. A ideia parte da notação
científica, que evidencia a parte mais importante do número. Isso
gera uma imprecisão do número representado, o que faz com que
haja uma perda de exatidão nas operações aritméticas. Se o
computador tiver mais memória, haverá mais precisão e informação.
A imprecisão aumenta quando o número é muito grande.
Os pontos flutuantes são armazenados em dígitos binários. A
maioria dos computadores usam a norma IEEE 754, que estabelece
23 bits de mantissa e 8 de expoente para precisão simples; 52 bits de
mantissa e 11 bits de expoente para precisão dupla. Ponto Flutuante
não é somente número, existe também o NaN (not a number,
exemplo: operações impossíveis, tais como 0/0) e o Inf (mais ou
menos infinito).
A comparação de inteiro com real é altamente imprevisível,
porque usam representações diferentes em bits. A comparação de
real com real é chamada de verificação de equivalência, como os dois
números são aproximações, é imprevisível dizer se vão ser
considerados números iguais ou não.
Decimais
4. A maioria dos grandes computadores projetados para suportar
aplicações de sistemas comerciais tem suporte de hardware para
tipos de dados decimais, que armazenam um número fixo de dígitos
decimais, com a vírgula decimal em uma posição fixa no valor.
Vantagem capacidade de armazenar com precisão valores
decimais(dentro de uma faixa restrita), o que não pode ser feito em
ponto flutuante.
Desvantagem os números decimais usam códigos binários e
ocupam mais espaço do que as representações binárias. Essas
representações são chamadas decimais codificadas em binário (BCD).
Não é presente nas linguagens modernas devido a ausência da
capacidade do hardware. Algumas máquinas tinham hardware próprio para esse
tipo e representavam números como strings de bits, desperdiçando um pouco de espaço.
Hoje em dia, costuma-se usar emulação desse tipo no software.
Booleanos
Os tipos booleanos são, talvez, os mais simples de todos. Sua faixa de valores
tem somente dois elementos: verdadeiro e falso. Algumas linguagens não possuem esse
tipo (exemplo: linguagem C, no qual expressões numéricas podem ser usadas como
condicionais. Nessas expressões, todos os operandos com valores diferentes de zero são
considerados verdadeiros, e zero é considerado falso).
Vantagem aumentam a legibilidade.
Um valor booleano poderia ser representado por um único bit, mas pela
dificuldade e pela falta de eficiência do acesso de um bit, geralmente os booleanos são
armazenados na menor célula de memória eficientemente endereçável: o byte.
Caracteres
Representa um símbolo com um número correspondente, a representação muda
de acordo com a cultura e há problema de padronização.
Nem sempre os símbolos gráficos possuem o mesmo tamanho.
Tabela ASCII um dos primeiros padrões (7 bits, 128 posições)
UTF8 cada caractere = 4bits, é compatível com o ASCII, padrão de fato e de
direito.
UNICODE Lista com símbolos gráficos com objetivo de padronização, ainda
não terminado.
Geralmente supõe-se que o caractere tem 1 byte, pois a maioria dos
codificadores da nossa cultura usa dessa forma, porem isso não é bom porque nem
sempre será desse jeito nos outros países.
Cadeias de caracteres
Podem ser tipos primitivos (strings) ou não, depende da linguagem.
Quando são representados como vetores de caracteres, frequentemente a
linguagem fornece poucas operações.
Operações com strings: atribuição, comparação, concatenação, conversão para
maiúsculo.
Exemplo de concatenação name1 := name1 + name2;
5. A maior parte dos usos de cadeias e a maioria das funções de biblioteca usam a
convenção segundo a qual as cadeias de caracteres são finalizadas com um caractere
especial nulo (zero). Essa é uma alternativa para manter o tamanho da string.
Para manter o tamanho variável num espaço estático de memória, é preciso
algum tipo de controle. FORTRAN usa strings sempre cheias. C usa o terminador zero,
favorecendo em armazenamento. Pascal usa o tamanho no primeiro elemento do vetor
(problemas de limite para o tamanho), eficiente em buscas na string.
Algumas linguagens modernas possuem vários recursos para strings, tais como
casamento de padrão e interpolação [Writeln (‘Resultado =’ resultado);].
Tem um importante papel para a segurança de computadores (buffer overflow)
Quanto as opções de tamanho da cadeia, existem 3:
Cadeia de tamanho estático – usado no Pascal, Ada, FORTRAN 90 são
sempre cheias, se uma cadeia mais curta for atribuída a uma variável, os caracteres
vazios normalmente serão ajustados como brancos (espaços).
Cadeia de tamanho dinâmico limitado – usado no C e C++, as cadeias tem
tamanhos variáveis até um máximo declarado e fixo estabelecido pela definição da
variável. Usam um caractere especial para indicar o final da string, em vez e manterem
seu tamanho.
Cadeia de tamanho dinâmico – usado no Java, Perl, SNOBOL4, as cadeias tem
tamanhos variáveis sem nenhum máximo, tal opção exige a sobretaxa de alocação e
desalocação dinâmica de armazenamento, mas proporciona máxima flexibilidade.
Tratar cadeia de caracteres como vetores pode ser mais incômodo do que lidar
com um tipo de cadeia primitivo. A sua adição como um tipo primitivo a uma
linguagem não é custosa, tanto em termos de complexidade da linguagem como do
compilador.
OBS: Alocação é definida antes da execução do programa. (tempo de
carregamento).
Durante a compilação que se decide quanto espaço o programa precisa
para os dados e como esse espaço será dividido entre os vários dados. (tempo de
compilação).
Enumerados
Em algumas LPs, tais como PASCAL, ADA, C e C++, é permitido que o
programador defina novos tipos primitivos através da enumeração dos identificadores
que denotarão os valores do novo tipo.
Tipos enumerados são utilizados basicamente para aumentar a legibilidade e
confiabilidade do código. A legibilidade é aumentada porque identificadores de valores
são mais facilmente reconhecidos do que códigos numéricos. Por exemplo, num
programa de processamento bancário é mais fácil identificar os bancos específicos se os
descrevemos por seu nome ao invés de usar seus códigos numéricos.
A confiabilidade é aumentada porque valores fora da enumeração não são
válidos. Além disso, operações aritméticas comuns não podem ser realizadas sobre estes
tipos, permitindo que o compilador identifique possíveis erros lógicos e tipográficos.
Como C e C++ tratam enumerações como inteiros, estas duas vantagens de
confiabilidade não estão presentes.
A repetição de constantes simbólicas pode gerar problemas, por exemplo:
6. Tipo flor rosa
Tipo cor rosa
Para solucionar esse problema, ou o programador exibe uma mensagem de erro
ou então ele cria uma variável para pegar o nome, ex: a := rosa;
Subfaixas
Aumentam a legibilidade ao tornar explícitos limites de um tipo numérico.
Aumenta a confiabilidade porque a atribuição de um valor a uma variável de subfaixa
fora da faixa especificada é detectada como um erro pelo compilador ou pelo sistema
em tempo de execução.
Ponteiros
Um tipo ponteiro é aquele em que as variáveis têm uma faixa de valores que
consiste em endereços de memória e um valor especial, o nil. O nil/null não é um
endereço válido e serve para indicar que um ponteiro não pode ser usado atualmente
para referenciar qualquer célula de memoria.
Esse tipo foi projetado para dois usos distintos. Primeiro, eles apresentam parte
do poder de endereçamento indireto, em segundo, fornecem um método de
gerenciamento de armazenamento dinâmico.
Geralmente são usados para referenciar alguma outra variável, em vez de
armazenar dados de alguma espécie.
Há duas operações com ponteiros: atribuição e desreferenciamento. A atribuição
fixa o valor de uma variável de ponteiro em um endereço útil.
Um ponteiro armazena um valor inteiro e sem sinal (ponteiros são números
naturais) que representa um endereço na memória, permitindo acessar os dados contidos
nesse endereço de memória.
Exemplo de flexibilidade de ponteiros:
‘’Agora vamos supor que seu programa precisa trabalhar com uma informação,
do tipo número inteiro, mas pode ser também que essa informação não exista e o
programa deve ser capaz de lidar com esse caso. Usando somente uma variável
do tipo inteiro, não é possível representar a situação "não existe nenhum inteiro
aqui". Porém, se o inteiro for na verdade um ponteiro para um inteiro, essa
representação é possível. Podemos dizer que tal inteiro pode não ter valor. Nesse
caso, o uso de um ponteiro para inteiro mais um inteiro aumentou a flexibilidade
do programar ao lidar com inteiros. ‘’
Os ponteiros sempre existem nas linguagens de programação, às vezes eles são
‘’escondidos’’.
Em algumas linguagens, usam para acessar endereços de memoria, em outras
usam somente recursos. Em java, o compilador reconhece as variáveis como ponteiros.
Vetores
Agregado homogêneo e unidimensional de valores.
7. Possui dificuldade para mudar o seu tamanho, porém tem acesso em tempo
constante.
Os valores estão em posições consecutivas, sendo que no pascal é possível
escolher o valor mínimo do índice. Ex: [10..15]
Vetor dinâmico troca o endereço da memoria, dando a impressão de que o
vetor aumentou.
O único vetor heterogêneo existente é o vetor de ponteiros, pois eles podem
apontar para vários tipos de dados.
Os vetores heterogêneos além de guardar a informação, também guardam os
ponteiros, por isso mais memória é gasta.
Operadores: () e []
Vetores Associativos
São estruturas chaves-valor (associação)
Quando é digitado determinada chave, retorna um valor.
Não é organizado.
Algumas linguagens só usam vetores associativos, porque podem ser usados
como vetores, listas ou registros.
São implementados por árvores ou funções hash.
Função hash entra um monte de bytes e sai uma quantidade fixa de bytes
Exemplo: CEP 123.456.779-15
_________
Operações desses números geram o número 15
Vetores associativos por árvores são mais fáceis de inserir/remover porque
gasta pouco recursos visto que é uma estrutura segmentada, porém o acesso não é em
tempo constate.
Vetores associativos por funções hash o oposto do de árvores.
O vetor associativo tem custo em questão de memória e processamento.
Listas
Aglomerado homogêneo, assim como vetores.
São recursivas (lista é vazia ou é valor + uma lista) e segmentadas
O acesso não é em tempo constante, requer que a estrutura seja percorrida para
acessar um valor.
Inserir/Remover elementos de listas é muito fácil.
São geralmente dinâmicas, mas podem ser estáticas.
Vetores x Listas
Vetores são melhores em processamento, é mais fácil achar um elemento num vetor do
que numa lista, pois nos vetores o acesso é em tempo constante.
Listas são melhores para mudar a estrutura dos dados, já que é uma estrutura
segmentada. É mais fácil de inserir/remover elementos de uma lista do que em vetores.
Registros
8. É um agregado possivelmente heterogêneo de elementos de dados. Cada
elemento individual é identificado por seu nome.
Os campos de um registro normalmente não são referenciados por índices. A
referência é feita por identificadores (que são nomeados).
Registros em C, C++ são chamados de estruturas, não incluem registros
variantes ou uniões.
Referências a campos do registro:
A maioria das linguagens usa uma notação de pontos para as referências a
campos, cujos componentes da referência são conectados por pontos.
Referência amplamente qualificada é aquela que todos os nomes de registro
intermediários, do maior registro envolvente ao campo específico, são nomeados a
referência.
Referência elíptica nelas o campo é nomeado, mas qualquer um ou todos os
nomes de registro envolventes podem ser omitidos, contanto que a referência resultante
não seja ambígua no ambiente de referência. As referências elípticas são prejudiciais
para a legibilidade.
Operações em registros
Atribuição na maioria dos casos, os tipos dos dois lados devem ser idênticos.
MOVE CORRESPONDING no COBOL, essa instrução copia um campo do
registro-fonte especificado para o de destino somente se este tiver um campo com o
mesmo nome.
O uso de registros é seguro, o único aspecto dos registros que não é claramente
legível são as referências elípticas permitidas pelo COBOL e PL/I.
Os registros são usados quando um conjunto de valores de dados é heterogêneo e
os diferentes campos não são processados da mesma maneira, além disso, os campos de
um registro, muitas vezes, não precisam ser processados em uma ordem sequencial
particular. O acesso aos campos é muito eficiente.
União
É um tipo que pode armazenar diferentes valores de tipo durante a execução do
programa. Em alguns casos, as uniões limitam-se a fazer parte das estruturas de registro,
mas, em outros, não.
Uniões livres é permitida completa liberdade aos programadores quanto a
verificação de tipos em seu uso. (exemplo de linguagens: FORTRAN, C e C++)
Uniões discriminadas cada construção de união inclui um indicador de tipo
(discriminante) (exemplo de linguagens: Pascal, Ada, ALGOL 68). Também chamada
de registro variante.
9. Desvantagem As uniões são construções potencialmente inseguras em muitas
linguagens. Elas constituem um dos motivos pelos quais linguagens como o
FORTRAN, Pascal, C, C++ não são fortemente tipificadas, pois não permitem
verificação de tipos das referências a suas uniões.
Vantagem as uniões economizam memória e proporcionam flexibilidade de
programação, em algumas linguagens permite a aritmética de ponteiros. Há algumas
linguagens, Ada por exemplo, que podem ser usadas com segurança.
Um grande número de linguagens não inclui uniões. Isso talvez reflita a grande
preocupação com a segurança nas linguagens de programação.
‘’O problema das uniões é a verificação de tipos, ou seja, garantir que as operações que
usam uniões estão corretas em termos de tipos de dados. Essa dificuldade vem do fato
de que o valor de uma união pode ser de mais de um tipo.’’
Conjuntos
Tipo cujas variáveis podem armazenar coleções não-ordenadas de valores
distintos de algum tipo ordinal.
Vantagens Exemplo de vantagem: ‘’Se o conjunto de vogais fosse representado
como um vetor char no Pascal, identificar se determinada variável armazenou uma
vogal exigiria um laço para procurá-la no vetor de vogais. Se estas fossem representadas
como um conjunto, a mesma determinação poderia ser feita com uma aplicação do
operador in ( if ch in [‘a’, ‘e’, ‘i’, ‘o’, ‘u’] ). Isso não somente é eficiente para o
programador, mas, provavelmente, também será eficiente para o computador. Nesse
caso é melhor lidar com o conjunto inteiro como sendo uma unidade.’’
Desvantagens não pode repetir os elementos num mesmo conjunto, limitações
de capacidades (no Pascal tem limite de 60 elementos) e operações. É complicado
realizar operações de união, intersecção, pertencente, etc, por não poder repetir
elementos.
Unidade
Guarda um valor possível (zero). Void.