Escolar Documentos
Profissional Documentos
Cultura Documentos
Kesede R Julio
Apostila de
Paradigmas de
Linguagens de Programação
versão: 2011
Prof. Kesede R Julio
Índice
Capítulo 1 Introdução......................................................................................................4
1.1 Recomendações ao Aluno 4
1.2 Alguns Aspectos Básicos 4
1.3 Exercícios 9
Capítulo 2 Paradigma Imperativo....................................................................................9
2.1 Tipos 9
2.1.1 Tipos Primitivos 10
2.1.1 Tipos Compostos 12
2.1.1 Tipos Recursivos 13
2.1 Expressões 14
2.1.1 Operadores Sobrecarregados 17
2.1.2 Erros em expressões 18
2.1.3 Expressões Relacionais 18
2.1.4 Expressões Booleanas 19
2.1.5 Avaliação Curto-Circuito 19
2.2 Comandos 20
2.2.1 Instruções de Atribuição 21
2.2.1 Instruções Compostas e Blocos 22
2.2.2 Condicionais 23
2.2.3 Iterativos 24
2.2.4 Desvio Incondicional 25
2.3 Abstrações 26
2.3.1 Abstração de Processos 26
2.3.1.1 Funções 27
2.3.1.1 Procedimentos 28
2.3.1.2 Parâmetros 28
2.4 Exemplo de Linguagem: Pascal 30
Capítulo 3 Paradigma Orientado à Objeto.....................................................................33
3.1 Tipo abstrato de dados 33
3.2 Herança 39
3.3 Acoplamento dinâmico 41
3.4 Polimorfismo 41
3.5 Exemplo de linguagem: Python 42
3.5.1 Características 42
Paradigmas de Linguagens de Programação - versão 2011 2
Prof. Kesede R Julio
1 Introdução
Programa fonte
Analisador Léxico:
constroem os símbolos léxicos (lexical tokens) que são
os identificadores, palavras reservadas, operadores etc,
para serem analisados sintaticamente
Analisador Semântico
verifica se há conflitos difíceis/impossíveis de
serem resolvidos pelo Parser. Ex.: coersão de tipos
Código de máquina
juntar arquivos pré-compilados (do sistema operacional ou do usuário)
afim de gerar um único executável.
Programa-fonte
Interpretador
Código
executado
1.3 Exercícios
2 Paradigma Imperativo
2.1 Tipos
ponteiros.
Os booleanos permitem a representação dos valores 0 e 1. Já
o tipo caractere permite o agrupamento de símbolos ('A'..'Z', 'a'..'z',
'0'..'9', ':', ';' etc) e o tipo ponteiro agrupa endereços de memória, afim
de suportar endereçamento direto pelas linguagens, além de alocação e
desalocação dinâmica de varáveis.
Exemplo em Pascal:
var
caractere: Character; // variável tipo caractere
booleano: Boolean; // variável tipo boolean
pont_int: ^Integer; // variável tipo ponteiro para inteiro
enum DiasC{1,2,3,4,5,6,7,8,9,10,
11,12,13,14,15,16,17,18,19,20,
21,22,23,24,25,26,27,28,29,30,31}
enum MesesC{jan,fev,mar,abr,mai,jun,
jul,ago,set,out,nov,dez}
struct DataC{
DiasC d;
MeseC m;
};
Exemplo em Pascal:
Var
tempP:array[1..30] of real;
As funções também podem ser consideradas como
mapeamento, uma vez que o domínio seria os argumentos de entrada
e a imagem o retorno da função.
Exemplo em Pascal:
type
IntListP = ^IntNode;
IntNode = record
valor: Integer;
prox: IntListP
end;
2.1 Expressões
Ou:
media=(cont==0)?0:soma/cont;
int fun(&a){
int b=a/2;
a=a*2;
return b;
}
Se a função não modificar o parâmetro não teremos
problema, porem se houver modificação a ordem da avaliação dos
operandos incidirá diretamente no resultado. Por exemplo, vamos
considerar que a função “fun()” retorne o valor do parâmetro dividido
por 2 (5) e modifique o valor do parâmetro para o seu dobro (20). A
ordem de avaliação aqui é muito importante, pois se “a” for trazido da
memória antes da execução de fun(), então a expressão valerá 15; se
fun() for avaliada antes, a expressão valerá 25.
O mesmo acontece com modificações em variáveis globais.
Por exemplo:
...
int a=5;
int func1(){
a=17;
return 3;
}
void func2(){
a=a+func1();
}
void main(){
func2();
}
A ordem de avaliação dos operandos, neste caso, também
alterará o resultado de a na função func2(). O resultado poderá ser 8
ou 20.
Pascal e Ada permitem que a avaliação seja feita em qualquer
ordem, definida pelo implementador, e portanto, estas linguagens
estão sujeitas a efeitos colaterais. Java evita estes efeitos definindo que
a avaliação será feita da esquerda para direita.
c = a&b;
(a>b) || (b++ / 3)
neste caso, b só será modificado se “a>b= false”.
Em Ada a avaliação pode ser determinada pelo programador,
então:
indice:=1;
while (indice <= limite) and then (lista(indice) /= chave)
loop
indice := indice + 1;
end loop;
2.2 Comandos
2.2.2 Condicionais
Comandos condicionais são aqueles que permitem que o
programa execute determinada(s) instrução(ões) de acordo com uma
condição, ou seja, podemos selecionar quais instruções devem ser
executadas. Existem dois tipos de seleção que podemos fazer: seleção
bidirecional e seleção n-direcional.
Comandos bidirecionais existem em todas as linguagens de
programação. Exemplo em C:
if (a<5){
b=7;
}
else{
b=10;
}
else begin
contcons := contcons + 1
end
2.2.3 Iterativos
Os comandos iterativos são constituídos basicamente de corpo
do comando e controlador da iteração. No corpo do comando estão as
instruções a serem executadas, dependendo da condição contida no
controlador da iteração. Existem os comandos iterativos com numero
de iterações preestabelecido e os comandos iterativos com numero de
iterações indefinido.
Aqueles que são preestabelecidos, o numero de iterações é
determinado a priori através de uma variável de controle. O numero de
iterações define quantas vezes as instruções contidas no corpo do
comando serão executadas. Sintaxe do comando em Pascal:
for <variável> := <expressão 1> (to/downto) <expressão 2
> do
<corpo do comando>
Aqui, a variável de controle é inicializada pela expressão 1 e é
incrementada de 1 (to) ou decrementada de 1 (downto) até que seja
maior ou menor, respectivamente, que a expressão 2. Enquanto isto
não acontecer, o corpo do comando é executado. A variável de controle
não pode ser modificada no interior do corpo do comando.
Algumas linguagens (C, C++ e Java) permitem a modificação
da variável no corpo do comando, assim como mais de uma variável de
controle. Exemplo em C:
somai=0;
somar=0.0;
for (i=10,r=2.0; (i<100 || r<20.0);i=i+2,r=r*2.5){
somai=somai + i;
somar=somar + r;
}
Os comandos com numero de iterações indefinido são
controlados por uma ou mais condição que pode ser avaliada no inicio
ou no final do comando. Em C, Java, Ada, C++, temos o comando
while que avalia a expressão condicional antes da execução das
instruções que fazem parte do corpo do comando. A avaliação pode
também ser feita no final do comando (do-while, repeat-until). Exemplo
em C:
somai=0;
somar=0.0;
i=10;
r=2.0;
while ( i<100 || r<20.0){
somai=somai + i;
somar=somar + r;
i=i+2;
r=r*2.5;
}
Neste exemplo, como em qualquer caso dos comandos
iterativos indefinidos, podemos alterar as variáveis que estão sendo
avaliadas na expressão condicional.
2.3 Abstrações
2.3.1.1 Funções
Uma função é a representação do mapeamento entre o
domínio e a imagem da função. Para cada conjunto de dados de
entrada (domínio) teremos um dado como saída (imagem).
Podemos encontrar este tipo de abstração em linguagens
imperativas, funcionais e OO.
Exemplo de uma função em Pascal: Cálculo de
exponenciação:
2.3.1.1 Procedimentos
Assim como as funções, estas abstrações descrevem um
comportamento computacional. Porém, não resulta em uma imagem a
partir do domínio, como nas funções, mas sim, alteram o estado do
programa através dos argumentos fornecidos a ele. Em C, os
procedimentos são tratados como funções que retornam “void” (nulo).
Um exemplo de sintaxe de um procedimento em Pascal:
procedure P(PF1,...,PFn);
<bloco>
2.3.1.2 Parâmetros
Chamamos de parâmetros os dados que são enviados para as
funções e procedimentos. A expressividade dos processos estão
diretamente ligados aos seus parâmetros. Exemplo em C:
float pot_restrita(){
float potência;
if (n==1){
potência=x;
}
else{
n=n-1;
potência=x*pot_restrita();
}
return potência;
}
...
int n=2;
float x=10;
void main(){
float pot10_2;
...
pot10_2=pot_restrita();
...
}
Exemplo 1: Programa em que o usuário entra com sua idade e o ano atual e o
programa lhe informa o seu ano de nascimento.
Program AnoNasc;
Var
intIdade:integer;
intAno:integer;
intDataNascimento : integer;
Begin
write( 'Digite sua Idade:' );
readln (intIdade) ;
write( 'Digite o Ano Atual:' );
readln (intAno) ;
intDataNascimento := ( intAno - intIdade);
writeln( 'Voce nasceu em: ', intDataNascimento );
End.
variável.
Program Ponteiros;
Var a: integer;
p: ^integer;
Begin
a := 8 ; // Guarda o valor 8 em a
p := nil; // O ponteiro não guarda nenhum endereço
writeln( 'Valor armazenado em a: ' , a );
End.
Exemplo 5: Este programa mostra mostra como construir listas lineares usando
ponteiros.
Program ListasLineares;
Begin
pinicio := nil ;
readln ;
End.
empilha(pilha1, cor1);
empilha(pilha1, cor2);
if (!vazia(pilha1))
temp=topo(pilha1);
…
class pilha{
private:
int *ptr_pilha;
int tam_max;
int top_ptr;
public:
pilha(){
ptr_pilha = new int [100];
tam_max = 99;
top_ptr = -1;
}
~pilha(){
delete [] ptr_pilha;
}
void empilha(int numero){
if (top_ptr == tam_max)
cerr << "a pilha esta cheia";
else
ptr_pilha[++top_ptr] = numero;
}
void desempilha(){
if (top_ptr == -1)
cerr << "a pilha esta vazia";
else
top_ptr--;
}
int topo(){
return ptr_pilha[top_ptr];
}
int vazia(){
return top_ptr == -1;
}
void exibe(){
int i;
if (top_ptr == -1)
cerr << "a pilha esta vazia";
else{
i=0;
while (i <= top_ptr)
cout << ptr_pilha[i++] << endl;
}
}
};
int main(){
int top_one;
pilha stk;
stk.empilha(42);
stk.empilha(17);
stk.empilha(10);
stk.empilha(30);
stk.empilha(15);
stk.exibe();
getch();
top_one = stk.topo();
stk.desempilha();
stk.exibe();
getch();
stk.desempilha();
stk.desempilha();
stk.exibe();
getch();
}
3.2 Herança
A herança veio resolver dois principais problemas: reutilização
de uma classe com necessidade de ser modificada para a nova
aplicação e também para organizar os programas.
Herdar uma classe significa estender atributos e/ou funções
de uma classe já existente. Por exemplo, podemos já ter criado uma
classe ponto, contendo como atributo “x” e “y”. Quando formos
construir a classe circunferência, podemos declarar apenas o atributo
“raio”, herdando os atributos “x” e “y” do centro da circunferência a
partir da classe ponto.
Chamamos de classe derivada (subclasse), a classe que
herda entidades de outra. Já a classe de onde as entidades foram
B A M
E D F
Herança simples Herança Múltipla
3.4 Polimorfismo
É, de certa forma, decorrente do acoplamento dinâmico, pois
é a característica em que, mesmo recebendo mensagens iguais, objetos
receptores de mensagem diferentes podem gerar respostas diferentes,
dependendo do método de sua classe. Mais importante, o objeto
emissor não precisa conhecer a classe do objeto receptor e como este
objeto irá responder à mensagem. Isto significa que, por exemplo, a
mensagem print pode ser enviada para um objeto sem a necessidade
de saber se o objeto é um caracter, um inteiro ou uma string. O objeto
receptor responde com o método que é apropriado à classe a qual ele
pertence.
3.5.1 Características
Exemplo:
a=2
b = 12
if a < 5 and b * a > 0:
print "ok"
Outro exemplo:
if nome == "pedro":
idade = 21
elif nome == "josé":
idade = 83
else:
idade = 0
...
verao
outono
inverno
primavera
1a volta
2a volta
3a volta
while condição:
# bloco de código
else:
# bloco executado na ausência de um break
Exemplo 1:
opc = 1
while opc != 0:
# qualquer código
opc = int(raw_input("Digite 0 para sair \n > "))
Exemplo 2:
>>> m = 3 * 19
>>> n = 5 * 13
>>> contador = 0
>>> while m < n:
... m = n / 0.5
... n = m / 0.5
... contador = contador + 1
...
>>> print "Iteramos %d vezes." % contador
novoCD.adicionarFaixa(1,'Custard Pie')
novoCD.adicionarFaixa(2,'The Rover')
4 Paradigma Lógico
4.1 Proposições
Proposição é uma declaração lógica da relação entre objetos,
que pode ou não ser verdadeira. Os objetos podem ser termos simples,
constantes ou variáveis.
As proposições mais simples (proposições atômicas) são
formadas por termo composto. O termo composto consiste em um
functor, que nomeia a relação, e uma lista de parâmetros. Exemplo:
homem(jake)
gosta(bob, bife)
que dará significado as relações não serão seus nomes, mas sim o
contexto do problema a ser resolvido.
Podemos ligar duas proposições através de conectores
lógicos, formando assim, proposições compostas. A tabela abaixo
mostra os conectores e seus significados.
B1 ∪ B2 ∪ ... ∪ Bn ⊂ A1 ∩ A2 ∩ ... ∩ Am
Teoremas.
iguais dos dois lados. Pronto, temos agora a nova proposição. Observe
o exemplo:
pai(bob, jake) ∪ mãe(bob, jake) ⊂ pais(bob, jake)
avô(bob, fred) ⊂ pai(bob, jake) ∩ pai(jake, fred)
aplicando a resolução:
…
Q :- Pn
homem(X) :- pai(X).
A conclusão não é trivial uma vez que não temos um fato para
casarmos diretamente com a meta. Assim, o sistema procurará o
primeiro termo contendo o functor da meta e fará a instanciação da
variável X para bob. Portanto homem(X) tornará homem(bob), e com
esta instanciação feita, pai(X) torna-se pai(bob). pai(bob) torna-se
verdadeira, a partir do primeiro fato da base. A este tipo de inferência
usada pelo Prolog chamamos de “encadeamento retrógrado”.
Um outro recurso usado pelo Prolog para inferir metas, é
chamado de backtracking. Ele é usado quando uma meta de proposição
composta (ou seja, com submetas) tenta ser inferida. O sistema tenta
provar uma submeta, e na falha desta tentativa, retorna tentando
provar a submeta anterior. Isto pode se tornar bem complexo e
demorado, dependendo da organização da base de conhecimento e da
meta. Considere o exemplo de meta abaixo: Existe um homem X, tal
que X seja um dos pais de shelley?
velocidade(ford, 100).
velocidade(chevy, 105).
velocidade(dodge, 100).
velocidade(volvo, 100).
tempo(ford, 20).
tempo(chevy, 21).
tempo(dodge, 24).
tempo(volvo, 24).
distância(X, Y) :- velocidade(X, Velocidade), tempo(X,
Tempo), Y is Velocidade * tempo.
distância(chevy, Distância_do_Chevy).
trace.
distância(chevy, Distância_do_Chevy).
Distância_do_Chevy = 2205
gosta(jake, chocolate).
gosta(jake, damascos).
gosta(darcie, alcaçuz).
gosta(darcie, damascos).
trace.
gosta(jake, X), gosta(darcie, X).
X = damascos
nova_lista([Cabeça_da_Nova_Lista| Cauda_da_Nova_Lista])
[Elemento_1 | Lista_2]
trace.
append([bob, jo], [jake, darcie], Família) .
N = 5 > 0 → OK F = ? 120
N1 = 4 is 5 - 1 → OK
fatorial(4, F1 = ? 24) :- ...
N = 4 > 0 → OK F = ? 24
N1 = 3 is 4 - 1 → OK
fatorial(3, F1 = ? 6) :- ...
N = 3 > 0 → OK F=?6
N1 = 2 is 3 - 1 → OK
fatorial(2, F1 = ? 2) :- ...
N = 2 > 0 → OK F=?2
N1 = 1 is 2 - 1 → OK
fatorial(1, F1 = ? 1) :- ...
N = 1 > 0 → OK F=?1
N1 = 0 is 1 - 1 → OK
fatorial(0, F1 = ? 1) :- ...
fatorial(0, 1). →
OK
F = 1 is 1 * 1
F = 2 is 2 * 1
F = 6 is 3 * 2
F = 24 is 4* 6
F = 120 is 5 *
24
X = 120
5 Paradigma Funcional
5.1 Fundamentos
(λ(x)x*x*x)(2)
O resultado é 8.
5.3 Forma Funcional
g(x)≡3*x
h(x)≡f(g(x)) ou h(x)≡(3*x)+2
Outra maneira de escrevermos em forma funcional é através
da construção, que recebe uma lista de funções como parâmetro.
Quando aplicada a um argumento, ela aplica todos os parâmetros
funcionais a este argumento. As funções devem estar entre colchetes
“[]”. Dado que:
g(x)≡x*x
h(x)≡2*x
i(x)≡x/2
Portanto,
[g,h,i](4) resulta em (16,8,2)
Expressão Retorno
42 42
(* 3 7) 21
(+ 5 7 8) 20
(- 5 6) -1
(- 15 7 2) 6
(- 24 (* 4 3)) 12
Expressão Retorno
(' A) A
(' (A B C)) (A B C)
Expressão Retorno
(CAR '(A B C)) A
(CAR '((A B) C D)) (A B)
(CAR 'A) Erro, pois A não é lista
(CAR '(A)) A
(CAR '()) Erro, pois lista vazia não tem cabeça
Expressão Retorno
(CDR '(A B C)) (B C)
(CDR '((A B) C D)) (C D)
(CDR 'A) Erro, pois A não é lista
(CDR '(A)) ()
Expressão Retorno
(CONS 'A '()) (A)
(CONS 'A '(B C)) (A (B C))
(CONS '() '(A B)) (() (A B))
(CONS '(A B) '(C D)) ((A B) (C D))
Expressão Retorno
(EQ? 'A 'A) #T
(EQ? 'A 'B) ()
(EQ? 'A '(A B)) ()
(LIST? '(X Y)) #T
(LIST? 'X) ()
(LIST? '()) #T
(NULL? '(A B)) ()
(NULL? '()) #T
(NULL 'A) ()
(NULL? '( () ) ) ()
Função descrição
= igual
<> diferente
> maior
>= Maior ou igual
< menor
<= Menor ou igual
EVEN? Verifica se é par
ODD? Verifica se é ímpar
ZERO? Verifica se é zero
EQV? Verifica se dois átomos (simbólicos ou numéricos) são
iguais
Exemplo:
(DEFINE (fatorial n)
(IF (= n 0)
1
(* n (fatorial (- n 1) ) )
)
)
Exemplo:
(DEFINE (Compare x y)
(COND
((> x y) (DISPLAY “x é maior que y”))
5.4.5 Exemplos