Programmazione: Metodi, Problemi, Linguaggi

Programmazione:
Metodi,
Problemi,
Linguaggi
Nicola
Galesi
Dipar;mento
di
Informa;ca
Sapienza
Università
Roma
OII
Olimpiadi
Italiane
di
Informa;ca
Sirmione
11‐16
OGobre
2010
Programmazione
• Strumen;
– Sequenzialità
e
Flusso
di
Controllo
– Selezione
– Iterazione
– Ricorsione
• StruGura
– Informazione
e
suo
flusso
– Scatole
nere/Funzioni
– Input
e
Output
– StruGurare
con
metodo
• Da;
– Variabili
– Da;
Scalari
– Da;
struGura;
• VeGori
• Matrici
• Aggregazioni
• Metodo
– Dal
problema
al
programma
– Informazione
e
pseudo‐codice
– CorreGezza
• Esercizi
Selezioni
Scolas;che
– Problema

Programma
– Programma

Problema
Dati:
Variabili
I
linguaggi
impera;vi
sono
basa;
sull’uso
dell’istruzione
di
assegnamento
e
sul
conceGo
di
variabile.
Una
variabile
puó
pensarsi
come
una
“scatola”
con
quaGro
caraGeris;che
che
la
iden;ficano
in
un
programma:
 Un
nome
 Un
;po
 Un
contenuto
somma
 Un
indirizzo
intero
25
001001001
Variabili
In
un
programma
C
possiamo
usare
variabili
per
salvare
dei
valori
che
vogliamo
usare
nel
programma.
Prima
di
usare
una
variabile,
cioè
prima
di
assegnarle
un
valore,
dobbiamo
DICHIARARLA,
vale
a
dire
dobbiamo
fare
in
modo
che
il
programma
sappia
che
quella
variabile
esiste.
Per
dichiararla
dobbiamo
specificarne
il
nome
e
il
;po.
In
un
programma
C
ogni
dato
(in
par;colare
le
variabili)
deve
essere
PRIMA
DICHIARATO
e
POI
USATO
Esempio
/* Lunghezza in km di una maratona */
#include <stdio.h>
int main(void)
{
int miles,yards;
float km;
questo
è
un
commento
questa
è
una
dichiarazione
miles = 26;
yards = 385;
km = 1.609 *(miles + yards / 1760.0);
questa
è
una
istruzione
di
printf(“Una maratona e’ di %f km\n”,km);
assegnamento
return 0;
}
questa
è
una
espressione
Analisi:
int miles,yards;
float km;
int
miles,
yards;
è
una
dicharazione
di
variabili.
In
questa
dichiarazione
s;amo
dichiarando
due
variabili
di
;po
intero.
Per
poter
dichiarare
più
di
una
variabile
nella
stessa
dichiarazione
è
sufficiente
separarare
i
nomi
di
variabile
con
una
virgola.
int
è
la
parola
chiave
riservata
dal
C
al
;po
intero.
Analogamente
float
km;
è
la
dichiarazione
di
una
variable
di
;po
float
(reale);
Parole
Chiave
int
e
float
sono
parole
chiave.
Sono
cioè
parole
riservate
dal
C
e
che
quindi
non
è
possibile
u;lizzare
altrimen;.
Per
esempio
non
è
possibile
usarle
come
nomi
di
variabili.
Il
C
possiede
un
certo
numero
di
parole
chiavi,
e
parole
che
non
conviene
usare
perchè
sono
nomi
di
funzioni
di
uso
frequente
in
C.
Per
esempio
prind
e
un
nome
riservato
ad
una
funzione
di
una
libreria
standrd
e
non
deve
essere
usato
come
nome
per
una
varaibile.
Strumenti:
Sequenzialità e
Flusso di Controllo
Istruzioni
di
assegnamento
miles = 26;
è
una
istruzione
di
assegnamento
il
cui
effeGo
è
quello
di
dare
il
valore
26
alla
variabile
miles.
yards = 385;
è
una
istruzione
di
assegnamento
il
cui
effeGo
è
quello
di
dare
il
valore
26
alla
variabile
miles
km = 1.609 *(miles + yards / 1760.0);
è
un
istruzione
di
assegnamento
che
da
alla
variabile
km
risultato
dell’espressione
1.609 *(miles + yards / 1760.0)
dove
le
variabile
miles
e
yards
hanno
i
valri
appena
calcola;
Flusso
di
controllo
Che
differenza
c`è
tra
un
programma
in
cui
le
tre
istruzioni
sono
nelle
sequenza
miles = 26;
yards = 385;
km = 1.609 *(miles + yards / 1760.0);
piuGosto
che
nella
sequenza
km = 1.609 *(miles + yards / 1760.0);
miles = 26;
yards = 385;
Flusso
di
controllo
Quando
eseguiamo
un
programma
le
sue
istruzioni
vengono
eseguite
in
sequenza
a
par;re
dalla
prima.
miles = 26;
yards = 385;
km = 1.609 *(miles + yards / 1760.0);
Flusso
di
controllo
miles = 26;
yards = 385;
km = 1.609 *(miles + yards / 1760.0);
miles yards
km
miles =26;
26
---
---
yards =385;
26
385
---
km = 1.609
*(miles +
yards /
1760.0);
26
385
42.185
Flusso
di
controllo
km = 1.609 *(miles + yards / 1760.0);
miles = 26;
yards = 385;
miles yards
km
km = 1.609
*(miles +
yards /
1760.0);
---
---
---
yards =385;
--
385
---
miles =26;
26
385
---
Leggere
e
Scrivere
Come
abbiamo
visto
per
scrivere
risulta;
sullo
schermo
il
C
ci
permeGe
di
usare
la
funzione
prind.
Tecnicamante
prind
e’
una
funzione
non
del
linguaggio
C
ma
del
sistema
C,
è
cioè
presente
in
ogni
sistema
che
usa
il
C.
In
stdio.h
vi
è
il
proto;po
di
prind
cioà
la
dichiarazione
della
funzione
Prima
di
usare
una
funzione
dobbiamo
sempre
definirne
il
proto;po.
Questo
vuol
dire
che
in
ogni
qualvolta
programma
in
cui
abbiamo
bisogno
di
usare
prind,
dobbiamo
inserire
la
direhve
del
preprocessore
#include
<stdio.h>
Leggere
e
Scrivere
Come
possiamo
introdurre
dei
da;
in
un
programma
?.
Cioè,
come
fa
un
programma
a
leggere
dei
da;
dall’esterno
?
Un
modo
ci
è
fornito
dalla
funzione
scanf.
EsaGamente
come
per
prind,
scanf
riceve
una
lista
di
parametri
formata
ncome
segue:
stringa
di
controllo
lista
parametri
dove
stringa
di
controllo
e’
una
stringa
(“...”)
che
può
contenere
delle
specifiche
di
conversione
deh
anche
forma;
e
solo
per
prind
la
lista
dei
parametri
è
una
lista
di
espressioni
(variabili,
costan;
o
espressioni
stesse)
che
verrano
formaGa;
nell’ordine
specificato
dai
forma;
della
stringa
di
controllo.
Esempio
prind
prind(“abc”);
prind(“%s”,”abc”);
int
x
=
5;
prind(“x
=
%d”,x+4);
prind(“%c%c%c”,’a’,’b’,’c’);
CaraGere
Formato
di
stampa
su
schermo
c
caraGere
d
intero
notazione
decimale
e
reale
in
notazione
scien;fica
f
numero
reale
s
stringa
scanf
Se
vogliamo
leggere
un
dato
dall
esterno
ed
usarlo
in
programma
dobbiamo
“salvarlo”
da
aualche
parte.
Le
variabili
(e
piu
in
generale
le
struGure
da;)
in
un
programma,
servono
anche
a
questo
scopo.
avere
un
oggeGo
nel
programm
che
noi
sappiamo
contenre
il
valore
(qualsiasi)
che
abbiamo
introdoGo
dall’esterno.
stringa
di
controllo
lista
parametri
la
lista
dei
parametri
per
scanf
non
e’
una
lista
di
espressioni,
ma
una
lista
di
indirizzi
di
memoria
!.
Cioè
dobbiamo
fornire
a
scanf
gli
indirizzi
di
memoria
delle
variabili
nelle
quali
vogliamo
salvare
i
valori
leh
dall’input.
scanf
Esempio
int
x;
scanf(“%d”,
&x);
Il
formato
%d
viene
associato
all’espressione
&x,
facendo
si
che
scanf
interpre;
i
caraGeri
in
input
come
un
intero
in
notaione
decimale
e
ne
memorizzi
il
valore
all’indirizzo
delle
variabile
x.
&
è
un
operatore
che,
applicato
ad
una
variabile,
conesente
di
estrapolarne
l’indirizzo
in
memoria
x
scanf(“%d”,&x)
20
Parole
chiave
Vi
sono
delle
parole
che
non
possono
essere
usate
in
C
come
iden;ficatori.
Queste
sono
le
parole
chiavi
cioè
parole
riservate
Esempio
Tipi
e
Da;:
int,
double,
float,
char,
void,
long,
struct,
enum,
typedef,union,
extern,
unsigned,
register
StruGure
di
controllo:
do,
if,
for,
while,
else,
switch,
goto,case,
return,
break,
con;nue,
default
Qualificatori:
const,vola;le,
auto,sta;c
Operatori
Per
scrivere
espressioni
è
necessario
avere
un
modo
di
definire
gli
operatori
aritme;ci,logici
e
relazionali.
Gli
operatori
usualmente
si
dividono
nelle
tre
categorie:
operatori
aritme;ci
operatori
logici
operatori
relazionali
In
C
per
la
sua
natura
flessibile,
si
aggiungono
altre
due
categorie
di
operatori:
operatori
di
incremento
e
decremento
operatori
di
assegnamento
Tuh
gli
operatori
si
dividono
a
seconda
del
numero
di
operandi
a
cui
si
applicano
in:
operatori
unari
operatori
binari
Operatori
Aritme;ci
Gli
operatori
aritme;ci
sono
operatori
per
i
quali
operandi
e
risulta;
sono
numeri.
Si
suddividono
a
loro
volta
in
 addi;vi:
+
(somma),
‐
(soGrazione
binaria)
,
‐,
+
(cambio
segno
unario)
 mol;plica;vi:
*
(prodoGo),
/
(divisione),
%
(modulo)
Nota.
L’operatore
di
divisione
/
è
una
divisione
intera
se
i
suoi
due
operandi
sono
interi,
e
reale
nel
caso
uno
dei
due
operandi
sia
reale
7/2
=
3
7.0/2=3.5
7/2.0=3.5
L’operazione
di
modulo
resituisce
il
resto
della
divisione
intera
Priorità
Siamo
sicuri
di
come
viene
valutata
un
espressione
aritme;ca
?
Esempio
Che
risultato
da
la
seguente
espressione
in
C
?
3
+
4
–
5
%
6
*
7
Esistono
norme,
deGa
di
priorità,
per
stabilire
accuratamente
come
il
C
calcola
le
espressioni,
vale
a
dire
come
inserirebbe
le
parentesi.
 priorità
tra
;pi
di
operatori
 a
parità
di
priorità,
associa;vità
In
ordine
dal
maggiore
al
minore
la
priorità
per
gli
aritme;ci
e`
Unari
+
Mol;plica;vi
Addi;vi
‐
Associa;vità
Supponiamo
di
avere
l’espressione
5‐4+5.
Gli
operatori
hanno
tuh
la
stessa
priorità.
Come
inserisce
le
parentesi
il
C
?
Associa;vità
Addi;vi:
da
sinistra
a
destra
5‐4+5
(5‐4)+5
((5‐4)+5)
Associa;vità
Mol;plica;vi:
da
sinistra
a
destra
5%3%4
(5%3)%4
((5%3)%4)
Associa;vità
Unari:
da
destra
a
sinistra
+
‐
+
4
+‐(+4)
+(‐(+4))
(+(‐(+4)))
Operatori
Relazionali
Gli
operatori
relazionali
sono
operatori
che
permeGono
comparare
il
valore
di
espressioni.
Resituiscono
un
valore
booleano
(vero
o
falso).
Esempio
34
<
45
è
vero,
<
(minore
di)
è
un
operatore
relazionale
Operatori
Relazionali:
<
(minore),
<=
(minore
o
uguale),
>
(maggiore)
>=(maggiore
o
uguale),
==
(uguale)
,
!=(diverso)
Valori
booleani
Falso
e
Vero
in
C:
In
C
gli
operatori
relazionali
res;tuiscono
un
int.
0
se
falso
e
1
se
vero
In
generale
qualunque
valore
differente
da
0
è
considerato
vero.
Qualunque
valore
nullo
viene
considerato
come
falso,
Es
0,0.0,
null,
‘\0’,
mentre
ogni
altro
valore
rappresenta
il
vero
Es
0<0
è
0,
0
<
1
è
1,
1<
0
è
0.
+
Priorità
Unari
Mol;plica;vi
Addi;vi
‐
Associa;vità:
da
sinistra
a
destra
Relazionali
(<,<=,>,>=)
Associa;vità:
da
sinistra
a
destra
Relazionali
(==,
!=)
La
valutazione
di
una
espressione
come
a
<op_relaz>
b
viene
valutata
a
livello
di
macchina
stabilendo
se
a‐b
<op_relaz>
0.
valore di
a-b
a<b
a<=b
a>b a>=b
a==b a!=b
positivo
0
1
0
1
0
1
zero
0
0
1
1
1
0
negativo
1
0
1
0
0
1
AGenzione:
come
viene
valutata
4<7<6
in
C?
per
associa;vità
come
(4<7)<6
quindi
come
1<6
cioè
1
Operatori
Logici
Gli
operatori
logici
sono
operatori
che
(in
teoria)
sono
applica;
a
valori
booleani
(vero/
falso)
e
res;tuiscono
valori
booleani.
In
C
vedremo
che
sono
generalizza;
Logica
a b a∧b a∨b ¬a
0 0 0
0
1
0 1 0
1
1
1 0 0
1
0
1 1 1
1
0
Linguaggio
C
unari
:
!
(negazione)
addi;vi:
||
(disgiunzione)
mol;plica;vi:
&&
(congiunzione)
a
b
a&&b a||b
!a
0
0
0
0
1
0
≠0
0
1
1
≠0 0
0
1
0
≠0 ≠0
1
1
0
+
Unari,
!
Priorità
&
Esempi
Associa;vità:
da
destra
a
sinistra
Mol;plica;vi
Addi;vi
Relazionali
(<,<=,>,>=)
Relazionali
(==,
!=)
‐
&&
Associa;vità:
da
sinistra
a
destra
||
Associa;vità:
da
sinistra
a
destra
int
a=1,
b=2
c=
3;
Espres.
Espres.
equiv.
Valore
1.
a
>
b
&&
c
<
d
(a
>
b)
&&
(c
<
d)
0
2.
a
<
!
b
||
!
!
a
(a<
(!b))
||
(!
(!
a))
1
3.
a+b
<
!
c
+
c
(a
+
b)
<
((!c)
+c)
0
Esercizi:
tra
logica
e
programmazione
Scrivere
espresioni
booleane
per
le
seguen;
specifiche.
1. Da;
due
coppie
di
interi
(a,b)
e
(c,d)
che
rappresentano
il
ver;ce
superiori
sinistro
e
inferiore
destro
di
un
reGangolo
in
un
sistema
cartesiano,
determinare
la
condizione
per
cui
un
terzo
punto
(x,y)
è
dentro
del
reGangolo.
2. Determinare
se
un
numero
rappresenta
un
anno
bises;le.
Un
anno
è
bises;le
se
è
mul;plo
di
4
escludendo
i
mul;pli
di
100
che
no
lo
sono
di
400..
Es
1964,2004
e
2400
sono
bises;li,
pero
1977
e
2100
no.
3. Determinare
se
due
intervallli
[a,b]
e
[c,d]
si
intersecano.
Che
succede
se
gli
intervali
sono
aper;
?
4. Deteminare
se
un
naturale
di
al
più
4
cifre
è
palindromo.
(Cioe`può
essere
leGo
da
sinistra
a
destra
o
da
destra
a
sinistra
indifferentemente.)
5. Determinare
se
tre
pun;
del
piano
con
coordinate
(a,b),
(c,d),
(e,f),
tuGe
intere,
giacciono
sulla
stessa
reGa.
Strumenti:
Assegnamento + Selezione
Assegnamento
L’assegnazione
è
una
istruzione
che
permeGe
di
cambiare
il
contenuto
o
valore
di
una
variabile
Esempio
int
a,b,c;
a
=
5;
/*
assegna
ad
a
il
valore
5
*/
b=a;
/*
cambia
il
contenuto
di
b
assegnandogli
il
contenuto
di
a*/
c=
b‐a
/*
meGe
in
c
il
contenuto
di
b
meno
il
contenuto
di
a*/
Nota.
L’operazione
di
assegnamento
non
è
una
uguaglianza
matema;ca.
In
par;colare
a=b
è
diverso
da
b=a.
In
realtà
un
assegnazione
può
essere
pensata
come
un
istruzione
che
implica
un
flusso
di
da;
che
va
da
destra
a
sinistra
a
b
Assegnamento
In
un
assegnamento
la
parte
sinistra
è
sempre
una
variabile
il
cui
contenuto
deve
essere
modificato,
e
la
parte
destra
è
un
espressione
il
cui
valore
calcolato
verrà
copiato
come
contenuto
della
variabile
nella
parte
sinistra
/*
un
programma
per
scambiare
i
valori
*
*
di
due
variabili
a
,e
b
*/
int
main(void){
int
a,b,aux;
a=5;
b=3;
aux
=
a;
a=b;
b=aux;
}
a b aux
a=5
5 -- --
b=3
5 3
--
aux = a
5 3
5
a= b
3 3
5
b=aux
3 5
5
Assegnamento
in
C:
nuovi
operatori
In
un
linguaggio
di
programmazione
è
possibile
modificare
il
valore
di
una
variabile
in
funzione
del
suo
valore
precedente.
Un
assegnamento
del
;po
k
=
k+2
somma
2
al
valore
contenuto
in
k
e
lo
memorizza
come
nuovo
valore
in
k.
In
C
tali
espressioni
si
abbreviano
con
l’uso
di
nuovi
operatori
k
+=
2;
Tali
operatori
sono:
+=,
‐=,
*=,
/=
Nota.
Si
faccia
aGenzione
che
un’espressione
del
;po
k
*=
j+2
è
equivalente
a
k
=
k
*
(j+2)
e
non
a
k
=
(k*j)
+2
incremento
&
decremento
Una
delle
potenzialità
del
C
è
quella
di
offrire
degli
operatori
che
permeGono
l’incremento
e
il
decremento
di
variabili.
Tali
operatori
sono
++
e
–‐
e
possono
essere
u;lizza;
sia
in
notazione
prefissa
che
posdissa
int
i=0;
++i;
/*
(prefisso)
e`una
espressione
valida
che
incrementa
il
valore
di
i
*/
i‐‐;
/*
(posdisso)
e`un
espressione
valida
che
decrementa
il
valore
di
i
*/
Gli
operatori
di
incremento
e
decremento
possono
essere
applica;
solo
a
variabili
e
sono
equivalen;
a
le
espressioni
i
=i+1
e
i=i‐1.
Che
differenza
c`è
tra
l’uso
prefisso
e
posdisso
di
un
operatore
di
incremento.
??
i=
5;
i=5;
c
=
i
++;
c=
++i;
/*
l’ouptut
è
c
=
5,
i=6
*/
/*
l’output
è
c
=
6,
i
=
6
*/
Istruzioni
di
selezione
Eseguire
solo
istruzioni
di
assegnamento
in
un
progamma
è
piuGosto
limitato.Tuh
i
linguaggi
impera;vi
meGono
a
disposizione
del
programmatore
dei
costruh
che
permeGono
di
svolgere
determinate
azioni
solo
in
determina;
casi.
L’idea
e’
di
valutare
un
certa
proprietà
o
condizione
e
sulla
base
della
verità
o
falsità
di
tale
condizione
eseguire
o
meno
certe
altre
istruzioni.
Il
costruGo
in
generale
usato
si
chiama
if‐then‐else.
In
C
esistono
due
costruh
il
costruGo
if
e
il
costruGo
if‐else.
CostruGo
if
La
forma
generale
del
costruGo
if
è
la
seguente:
if
(expr)
I
dove
I
e’
una
macro
istruzione
(cioè
una
o
più
istruzioni)
La
seman;ca
è
la
seguente:
se
(expr)
viene
valutata
differente
da
0
allora
si
eseguono
le
istruzioni
in
I
altrimen;
si
procede
oltre
I
e
le
istruzioni
in
I
non
vengono
eseguite
Esempi
Generalmente
(expr)
è
un’
espressione
relazionale
o
logica,
ma
in
generale
in
C
potrebbe
essere
qualunque
espressione
if
(x
==0.0)
x
/=
y;
if
(c
!=
‘y’)
{
++blank;
if
(blank
==
(x+y))
y++;
}
Una
sola
istruzione
non
ha
bisogno
di
parentesi
{,}
Se
ci
sono
più
istruzioni
c’è
bisogno
di
parentesi
{,}
Dopo
la
“}”
non
va
il
“;”
Istruzione
if‐else
La
forma
generale
del
costruGo
if‐else
è
la
seguente:
if
(expr)
I
else
J
dove
I,J
sono
macro
istruzioni
(cioè
una
o
più
istruzioni)
La
seman;ca
e`la
seguente:
se
(expr)
viene
valutata
differente
da
0
allora
si
eseguono
le
istruzioni
in
I
altrimen;
le
istruzioni
in
I
non
vengono
eseguite
e
si
eseguno
le
istruzioni
in
J.
Esempi
Minimo
tra
due
interi
x
e
y
Errore
di
sintassi
!!
int
x,y,min;
int
i,j;
if
(x<y)
min
=x;
else
min
=
y;
if
(i
==
j){
i++;
j+=2;
};
else
i‐=j;
if
(c
>=‘a’
&&
c
<=
‘z’)
++lc_cnt;
else{
++other_cnt;
prind(“%c
non
è
minuscolo”,c);
}
If
indenta;
Abbiamo
visto
che
è
possibile
avere
degli
if
indenta;
if
(c
!=
‘y’)
{
++blank;
if
(blank
==
(x+y))
y++;
}
Supponiamo
di
aver
il
seguente
codice
if
(expr1)
if
(expr2)
Esempio
I
if
(a==1)
else
J
if
(b==0)
prind(“
Doppio
if”)
else
prind(“
.......”);
Metodo:
Correttezza e Verifica
assegnamento+selezione
Pre‐
e
Post‐condizione
Supponiamo
di
avere
un
programma
con
variabili:
int a,b,c;
e
un
certo
numero
di
istruzioni
che
le
riguardano.
a = E1;
b = E2;
c = E3;
dove
E1,E2,E3
sono
3
espressioni.
Ogni
istruzione
è
definita
da
una
precondizione
“Pre”ed
una
postcondizione
“Post”
che
rappresentano
esplicitamente
lo
stato
della
memoria
del
programma
rispehvamente
prima
e
dopo
l’esecuzione
della
istruzione.
[Pre:
a=A,b=B,c=C]
a
=E1;
[Post:
a
=
E1,
b=B,
c=C]
Cenni
su
Verifica
Supponiamo
di
avere
le
istruzioni
che
consentono
di
intercambiare
il
valore
di
due
variabili
Proprietà
int
a,b,aux;
[Pre:
a=A,
b=B]
{
{
aux
=
a;
aux
=
a;
a=b;
a=b;
b=aux;
b=aux
}
}
[Post:
a=B,
b=A]
Verifica
[Pre:
a=A,
b=B]
{
[a=A,b=B]
aux
=
a;
[a=A,aux=A,b=B]
a=b;
[a=B,aux=A,b=B]
b=aux;
[a=B,aux=A,b=A]
}
[Post
a=B,
b=A]
CorreGezza
di
un
If‐else
Supponiamo
di
avere
una
istruzione
if‐else
che
ci
consente
di
passare
da
una
precondizione
P
ad
una
postcondizione
Q.
[Pre:
P]
if
(expr)
I
else
J
[Post:
Q]
Esempio
[Pre:
x=X,
y=Y]
if
(x<y)
min
=
x;
else
min
=y;
[Post:
min
=
MIN(X,Y)]
VERIFICA
CORRETTEZZA
Per
verificare
che
il
codice
scriGo
sia
correGo
dobbiamo
verificare
che
che
partendo
dalla
precondizione
P
arriviamo
alla
postcondizione
Q
dopo
aver
eseguito
l’istruzione
if‐else
Verifica
di
un
If‐else
[Pre:
P]
if
(expr)
I
else
J
[Post:
Q]
[Pre:
P
∧
expr
!=0]
I
[Post:
Q]
È
CORRETTO
QUANDO
SONO
CORRETTE
ENTRAMBI
.....
[Pre:
P
∧
expr
==
0]
J
[Post:
Q]
Esempio
Verifica
min
[Pre:
x=X
∧
y=Y]
if
(x<y)
min
=
x;
else
min
=y;
[Post:
min
=
MIN(X,Y)]
Per
verificarne
la
correGezza
devo
verificare
che
........
[x=X
∧
y=Y
∧
x<y
]
min
=
x;
[min
=
MIN(X,Y)]
[x=X
∧
y=Y
∧
x<y
]
min
=
x;
[min
=
X
∧
y=Y
∧
x<y
]
[min
=
MIN(X,Y)]
/*
perché
min
=
X
e
x<y
*/
[x=X
∧
y=Y
∧
x
>=
y
]
min
=
y;
[min
=
MIN(X,Y)]
Dati
Tipi
di
Da;
Per tipo di dato si intende un insieme di valori e un insieme di
operazioni che si possono applicare ad esso.
Ogni tipo di dato ha una propria rappresentazione in memoria,
cioè viene rappresentato attraverso un’opportuna codifica
sfruttando un certo numero di celle di memoria.
In C ed in altri linguaggi ogni variabile ha un tipo associato. Ciò
permette di:
 associare un insieme di valori ammissibili per quella variabile
 determinazione a priori della quantità di memoria necessaria
per la memorizzazione di quella variabile.
Classificazione
di
;pi
di
da;
E’
chiaro
che
vi
è
un
numero
potenzialmente
infinito
di
da;
che
è
possibile
definire.
I
;pi
di
da;
si
classificano
nel
seguente
modo:
 ;pi
predefini;
(già
esisten;
nel
linguaggio)
 ;pi
defini;
dall’utente.
Sulla
base
della
loro
struGura
i
;pi
si
possono
suddividere
in:
 ;pi
semplici
 ;pi
struGura;
I
;pi
predefini;
in
C
CARATTERI
INTERO
char
short
signed
char
int
unsigned
char
long
unsigned
short
unsigned
unsigned
long
REALI
float
double
long
double
Vedremo
che
in
realtà
il
;po
caraGeri
è
traGato
come
un
intero
Tipo
intero
Gli
interi
vengono
traGa;
dal
C
mediante
l’uso
di
differen;
;pi:
short,
int,
long,
e
char.
Il
;po
standard
per
il
traGamento
degli
interi
e
l’int.
I
;pi
interi
sono
i
numeri
.......
‐2,.‐1,0,1,2,3...................
Chiaramente
su
una
macchina
non
possiamo
pensare
di
memorizzare
tuh
ques;
infini;
numeri.
Potremo
memorizzarne
solo
un
porzione.
La
quan;tà
di
numeri
memorizzabili
in
un
itpo
dipende
dalla
memoria
assegnata
a
quel
;po.
Al
;po
int
di
solito
è
assegnato
una
memoria
di
16
bit
(2
byte)
o
32
bit
(4
byte).
Tipo
intero
Quindi
su
una
macchina
con
int
di
4
byte
possiamo
memorizzare
i
numeri
mentre
in
una
macchina
con
interi
a
16
bit,
solo:
Cioè
gli
interi
rappresentabili
vanno
da
32768
a
32767.
Altri
;pi
interi
IL
C
fornisce
altri
predefini;
per
lavorare
con
gli
interi
char
(1
byte)
da
‐128
a
127
short
(2
byte
=
int
in
macchine
dove
ha
int
=
2byte)
SHRT_MAX
=
215
‐1,
SHRT_MIN=
‐215
long
4
byte
(=
int
dove
int
hanno
4
byte)
unsigned
stessa
dimensione
di
un
int,
ma
senza
segno
quindi
da
int
=
2
byte
(UINT_MAX
=
216
‐1
)
se
int
=
4
byte
UINT_MAX
=
232
‐1.
se
;po
UNSIGNED
Le
variabili
di
;po
unsigned
vengono
manipolate
con
l’aritme;ca
modulo
MAX_U
Esempio
unsigned
b;
b
=
UINT_MAX;
prind(“%u\n”,b+1);
/*
stamperà
1
*/
prind(“%u\n”,
b+b);
/+
stamperà
b
*/
Strumen;:
Iterazione
Iterazione
Gli
strumen;
vis;
fino
ad
ora
(assegnamento
e
istruzione
if‐then‐
else)
non
consentono
di
poter
implementare
tuh
gli
algoritmi
immaginabili
e
sono
di
potenza
limitata.
I
linguaggi
di
programmazione
meGono
a
disposizione
un
altro
costruGo,
che
consente
di
poter
ripetere
ciclicamente
una
sequenza
di
istruzioni,
un
numero
di
volte
che
può
dipendere
anche
da
un
valore
dato
in
input
e
non
definito
all’interno
del
programma.
Tali
costruh
prendono
il
nome
di
costruh
itera;vi
e
come
vedremo
ogni
linguaggio
ne
offre
differen;
;pi.
CostruGo
While
in
C
La
sintassi
del
costruGo
while
in
C
è
la
seguente:
while (expr)
I
La
seman;ca
di
tale
costruGo
è
la
seguente:
Ripe;
l’istruzione
I
mentre
expr
assume
valore
diverso
da
0
(cioè
vero).
L’esecuzione
dell’istruzione
while
avviene
nel
seguente
modo:
viene
valutata
expr.
Se
il
suo
valore
è
diverso
da
0
allora
si
esegue
I.
Tali
operazioni
vengono
ripetute
fin
quando
expr
non
assume
il
valore
0.
ProdoGo
di
due
numeri
>
0
int x,y; /* Input */
int prod; /* Output*/
Leggi x;
Leggi y;
prod = 0;
while (y != 0)}
prod+=x;
--y;
}
restituisci prod;
Metodo:
dal
problema
al
programma
Primo
Passo:
Iden;ficare:
da;
di
input
&
risulta;
in
output
int x,y; /* dati in input */
int prod; /* risultato in Output*/
\Pre: x,y >0 *\
…………………
\* Post: prod = x*y *\
€
Secondo
Passo:
Sviluppare
un’idea
risolu;va
(itera;va)


x * y = x + x + x + ........+ x
y volte
Terzo
Passo:
estrapolare
il
processo
itera;vo
Inizializzazione
€
Proseguimento
€
0
volte



x + x + x + ........+ x = 0
i
volte



x + x + x + ........+ x
i
volte



x +x
+
x
+
........+
x
+x


(i+1) volte
Terminazione
€


x + x + x + ........+ x = x * y
y volte
Quarto
Passo:
Definire
il
processo
itera;vo
(0)
Iden;ficare
le
variabili
coinvolte
nel
ciclo
(1)
Capire
il
valore
iniziale
di
tali
variabili
(INIZIALIZZIONE
del
CICLO)
(2)
Come
a
par;re
dall’ul;mo
valore
oGengo
il
seguente.
(PROSEGUIMENTO)
(3)
Assicurarmi
che
qualche
quan;tà
cambi
valore
(in
genere
decrementata
o
incrementata)
(4)
Decidere
quando
ho
raggiunto
l’obiehvo:
condizione
di
uscita
dal
ciclo
o
di
terminazione.
(CONDIZIONE
di
TERMINAZIONE
DEL
CICLO)
(5)
Verificare
che
la
condizione
di
terminazione
si
verifica
prima
o
poi
come
conseguenza
di
(3)
(VERIFICA
DELLA
TERMINAZIONE
DEL
CICLO)
Prima
Soluzione
Introduco
una
variabile
i
che
conta
il
numero
di
iterazioni
faGe.
Inizializzazione:
i
=
0
(all’inizio
ho
faGo
0
iterazioni)
prod
=
0
Proseguimento:
sommo
x
al
precedente
valore
di
prod
(prod
=prod
+x)
e
incremento
i
di
uno
(++i)
Condizione
Terminazion/Uscita:
Noto
che
dopo
aver
calcolato
incremento
i
=
y
e
quindi
voglio
terminare.
La
Condizione
di
Uscita
è
i
=
y
è
raggiunta
i=0;
prod
=
0;
while
(i
!=
y){
la condizione di entrata è la negazione della
prod
+=
x;
condizione di terminazione
++i;
}
Seconda
Soluzione
Osservazione:
i
ed
y
contengono
la
stessa
informazione
Inizializzazione:
prod
=
0
Proseguimento:
sommo
x
al
precedente
valore
di
prod
(prod
=prod
+x)
e
decremento
y
di
uno
(‐‐y)
Condizione
Terminazion/Uscita:
Noto
che
dopo
non
appena
y=0
voglio
terminare.
La
Condizione
di
Uscita
è
y
==
0.
prod
=
0;
while
(y
!=
0){
prod
+=
x;
‐‐y;
}
la condizione di entrata è la negazione della
condizione di terminazione
Verifica
Terminazione
\Pre: x,y >0 *\
prod = 0;
while (y != 0){ prod += x;
—y;
}
\* Post: prod = x*y *\
Verifica
del
raggiungimento
della
condizione
di
terminazione:
‐
Dalle
precondiz.
Sappiamo
che
>0.
‐
Ad
ogni
passo
dell’iterazione
y
si
decrementa
di
1.
‐
Prima
poi
y
arriverà
ad
essere
0.
‐
E
quindi
la
condizione
di
terminzione
y==0
è
verificata.
IL
CICLO
TERMINA
Esempio:
resto
&
quoziente
Scrivere
una
algoritmo
che
da;
due
interi
posi;vi
x
ed
y,
calcoli
il
resto
e
il
quoziente
della
divisione
di
x
per
y
int
x,y;
/*
da;
in
input
*/
int
q,r;
/*da;
in
output*/
/
*
[Pre:
x=
X,
X>0,
y
=Y,
Y>0]
*/
/
*
[Post
x=q*Y+r,
r<Y]
*/
IDEA
per
una
STRATEGIA:
q
lo
possiamo
calcolare
soGraendo
itera;vamente
y
da
x
fino
ad
oGenere
un
resto
r
<
di
y
resto
&
quoziente:
pseudo‐codice
q
lo
possiamo
vedere
come
il
numero
di
volte
che
dobbiamo
soGrarre
y
da
x
per
oGenere
un
resto
r<y.
q
=
numero
delle
soGrazioni
effeGuate
r=
resto
di
x
dopo
q
soGrazioni
Inizializzazione:
q=0
(abbiamo
effeGuato
0
soGrazioni)
r=x
(senza
soGrazioni
il
resto
è
=x)
Proseguimento:
q=q+1
(soGraiamo
una
volta
in
più)
r=r‐y
(da
ciò
che
resta
di
x
soGraiamo
y)
Condizione
di
Terminazione:
r<y
(Non
possiamo
più
soGrarre)
Verifica
del
raggiungimento
della
Condizione
di
Terminazione:
Ad
ogni
iterazione
soGraggo
y
da
r.
Primo
a
poi
r
arriva
ad
Essere
un
numero
<
di
y
resto
&
quoziente:
implementazione
#include <stdio.h>
int main(void){
int x,y; /* input */
int q,r; /*output*/
/ * [Pre: x= X, X>0, y =Y, Y>0]
*
* [Post x=q*Y+r, r<Y] */
printf(“Immetti x e y”);
scanf(“%d%d”,&x,&y);
q=0; r=x;
while (r>=y){
++q;
r-=y;
}
printf(“Resto = %d”,Quoz. =%d\n”,r,q);
}
Esempio:
faGoriale
Scrivere
un
programma
itera;vo
per
calcolare
il
faGoriale
di
un
numero
naturale
(intero
>=0).
n!
=
1*2*3*.....*n.
(ricordare
che
0!=1)
int
n;
/*
input*/
int
f;
/*
output
*/
/*
[Pre
n=N,
N>=0]
*/
/*
[Post
f
=
N!]
*/
€
faGoriale:
algoritmo
Calcoliamo
la
f
per
iterazioni
successive
1× 2 × 3........× i
1× 2 × 3........× i × (i + 1)
Usiamo
una
variabile
i
intera
per
contare
da
0
a
n
e
usiamo
f
per
salvare
valori
delle
fi
€
Inizializzazione:
i=0
(0
è
il
più
piccolo
valore
per
n)
f=1
(0!=1)
Proseguimento:
i=i+1
(passo
al
seguente
i)
f=f*i
(calcolo
la
nuova
f
a
par;re
dal
valore
di
i)
Terminazione:
ho
calcolato
n!
quando
i=n.
Pertanto:
Condizione
di
terminazione
i=n
faGoriale:
implementazione
#include <stdio.h>
int main(void){
int n; /* input */
int f; /*output*/
int i; /* var ausiliare */
/ * [Pre: n=N, N>=0]
*
* [Post f=N!] */
printf(“Immetti n”);
scanf(“%d”,&n);
i=0; f=1;
while (i<n)}
++i;
f*=i;
}
printf(“n = %d”,fatt=%d\n”,n,f);
}
Differenze
.....
Cosa
succede
se
cambio
l’ordine
nelle
istruzioni
nel
ciclo
while
?
i=0; f=1;
while (i<n)}
f*=i;
++i;
}
i=0; f=1;
while (i<n)}
++i;
f*=i;
}
Il valore di f dipende dal valore calcolato
della variabile !!
Input
n=
4;
1
Ciclo
0
ª
iterazione
[i=0,f=1]
2
Ciclo
[i=0,f=1]
1ª
iterazione
[i=1,f=1]
[i=1,f=0]
2ª
iterazione
[i=2,f=2]
3ª
iterazione
[i=3,f=6]
4ª
iterazione
[i=4,f=24]
[i=2,f=0]
[i=3,f=0]
[i=4,f=0]
massimo
comune
divisore
Scrivere
un
programma
itera;vo
per
calcolare
il
massimo
comune
divisore
fra
due
interi
posi;vi
X
e
Y,
usando
la
seguente
proprietà
del
mcd:
se
X>Y
allora
MCD(X,Y)=MCD(X‐Y,Y)
se
Y>X
allora
MCD(X,Y)
=
MCD(X,Y‐X)
se
X=Y
allora
MCD(X,Y)=X=Y
int
x,y;
/*
input*/
int
mcd;
/*
output
*/
/*
[Pre
x=X,y=Y,
X,Y>0]
*/
/*
[Post
mcd
=
MCD(X,Y)]
*/
massimo
comune
divisore:
algoritmo
IDEA
algoritmo:
Se
ad
ogni
passo
dell’iterazione
soGraggo
y
da
x
se
x>y
oppure
soGraggo
x
da
y
se
y>x,
arriverò
ad
un
momento
in
cui
x=y.
!!
In
entrambi
i
casi
per
la
proprietà
precedente
non
sto
modificando
il
valore
del
massimo
comune
divisore
di
x
ed
y
iniziali.
Quando
termino
res;tuisco
il
valore
trovato
per
cui
x=y
Inizializzazione:
x
e
y
hanno
i
loro
valori
originali.
Proseguimento:
x=
x‐y
se
x>y
oppure
y
=y‐x
se
y>x
Terminazione:
Non
voglio
con;nuare
il
ciclo
non
appena
x=y.
Condizione
di
terminazione:x=y
massimo
implementazione
comune
divisore:
#include <stdio.h>
int main(void){
int x,y; /* input */
int mcd; /*output*/
int i; /* var ausiliare */
/ * [Pre: x=x,y=Y, X,Y>0]
* [Post mcd= MCD(X,Y)] */
printf(“Immetti x ed y”);
scanf(“%d%d”,&x,&y);
while (x!=y)}
if (x<y) x-=y;
else y-=x;;
}
mcd =x;
printf(“MCD =%d\n”,mcd);
}
*
Il
ciclo
for
Il
ciclo
for
è
analogo
al
ciclo
while,
serve
cioè
per
ripetere
del
codice
itera;vamente,
specialmente
in
quei
casi
in
cui
il
numero
di
iterazioni
può
essere
indicizzato
mediante
una
variabile
intera.
Il
costruGo
e’
definito
dalla
seguente
sintassi
for(expr1;
expr2;expr3)
I
dove
I
è
una
istruzione
composta.
Il
ciclo
for
è
equivalente
al
while
essendo
esprimibile
come
un
ciclo
while
della
seguente
forma:
expr1;
while
(expr2)
I
expr3;
quando
expr2
è
presente
e
non
vi
è
una
con;nue
nel
ciclo
for.
Il
ciclo
for
La
seman;ca
può
essere
spiegata
usando
il
ciclo
while
for(expr1;
expr2;expr3)
I
Expr1:
rappresenta
le
istruzioni
di
Inizializzazione
Expr2:
e’
la
condizione
di
entrata
nel
ciclo
Expr3:
sono
le
istruzioni
che
in
un
ciclo
while
si
occupano
di
incrementare
o
decrementare
la
variabile
contatore
for(i=1; i<=10; i++)
sum +=i;
i=1
while(i<=10){
sum +=i;
++i;
}
Metodo:
Correttezza e Verifica
Iterazione
Cenni
su
verifica
di
correGezza
dei
cicli
while
Consideriamo
l’esempio
del
prodoGo
di
due
numeri.
int
x,y;
prod =0;
while(y !=0)
prod +=x;
--y;
}
Per
verificare
che
questo
ciclo
sia
correGo,
studiamo
come
evolve
la
situazione
della
variabile
prod.
Cosa
devo
verificare
?.
(1) Che
inizialmente,
cioè
che
dopo
le
istruzioni
di
inizializzazione,
il
valore
di
prod
sia
correGo
(2) che
da
una
iterazione
all’altra
il
valore
di
prod
cambia
correGamente
(3) che
dopo
aver
concluso
l’iterazione
prod
con;ene
il
valore
che
mi
aGendo,
cioè
il
prodoGo
dei
due
numeri.
Cenni
su
verifica
di
correGezza
dei
cicli
while
Per
poter
verificare
il
punto
(2),
non
mi
posso
servire
di
valori
concre;
di
prod,
come
potrei
invece
fare
per
verificare
il
suo
valore
dopo
la
inizializzazione.
Devo
trovare
una
proprietà
che
prod
deve
verificare
AD
OGNI
ITERAZIONE
e
che
mi
garan;sca
la
correGezza
del
ciclo
in
qualunque
istante:
all’inizio,
da
un
iterazione
all’altra
e
alla
fine
dopo
esserne
uscito
Tale
propriètà
per
questa
caraGeris;ca
viene
deGa
INVARIANTE.
verifica
di
correGezza
dei
cicli
while
Supponiamo
di
avere
un
ciclo
della
forma
[Pre:
P]
expr1
while
(expr2)
I
[Post:
Q]
Il
ciclo
è
correGo
se
dalla
precondizione
P
mi
porta
alla
postcondizione
Q.
Cosa
devo
verificare
per
garan;rmi
che
il
ciclo
sia
correGo?
Data
la
proprietà
Invariante,
che
chiamo
Inv,
devo
verificare
la
correGezza
delle
seguen;
tre
specifiche:
verifica
di
correGezza
dei
cicli
while
(1) Dopo
l’inzializzazione
l’invariante
è
vera
[Pre:
P]
expr1
[Post:
Inv]
(2)
se
prima
di
entrare
nel
ciclo
l’invariante
si
verifica
,
allora
dop
essere
entrato
ed
aver
eseguito
le
istruzioni
I
del
ciclo
l’invariante
con;nua
a
valere
[Pre:
Inv,
expr2
==
vera]
I
[Post:
Inv]
(3)
Non
appena
esco
dal
ciclo
l’invarante
implica
la
postcondizione
Q
del
ciclo
[Pre:
Inv
,
expr2
==
falsa]
→
[Post:
Q]
Struttura:
Funzioni
Programmazione
struGurata
La
scriGura
di
un
programma
di
grandi
dimensioni,
si
basa
sulla
possibilità
che
offre
un
linguaggio
di
programmazione
di
  implementare
facilmente
la
scomposizione
di
problemi
in
soGoproblemi
la
possibilità
di
suddividere
un
programma
su
più
files
MODULARIZZAZIONE
Il
C
è
soGo
entrambi
ques;
aspeh
uno
dei
linguaggi
più
flessibile.
In
par;colare
la
scomposizione
di
problemi
in
soGoproblemi,
viene
implementata
in
C
scomponendo
un
problema
in
piu
funzioni,
ognuna
delle
quali
implementa
un
par;colare
soGoproblema.
Definizione
e
dichiarazione
di
funzione
Per
poter
usare
una
funzione
dobbiamo
  definire
la
funzione,
cioè
specificarne
le
istruzioni
di
C
che
definiscono
ciò
che
la
funzione
deve
eseguire,
dichiarare
la
funzione,
perchè
le
altre
funzioni
del
programma
“siano
a
conscenza”
della
presenza
di
quella
funzione.
L’uso
di
una
funzione
avviene
aGraverso
la
chiamata/invocazione
ad
una
funzione.
Mentre
in
genere
è
bene
tenere
separate
la
definizione
e
la
dichiarazione
di
una
funzione,
in
cer;
s;li
di
programmazione
e
possibile
far
coincidere
la
dichiarazione
con
la
definizione.
Definizione
di
funzione
Esempio
int
faGoriale
(int
n){
int
i=1,
product
=1;
for
(i=2;i<=n,++i)
product*=i;
return
product;
}
Il
primo
int
indica
che
la
il
valore
res;tuito
dalla
funzione
viene
conver;to
a
int
prima
di
essere
passato.
La
lista
dei
parametri
in
questo
caso
è
formata
da
int
n
che
specifica
che
la
funzione
riceve
in
input
un
valore
intero.
Il
parametro
formale
n
può
essere
usato
come
una
normale
variabile
all’interno
del
corpo
della
funzione.
Scatole
Nere
e
Flusso
di
Informazioni
Le
funzioni
possono
essere
pensate
come
delle
scatole
nere,
che
eseguono
determinate
operazioni
e
che
comunicano
con
l’esterno
aGraverso
i
parametri
e
il
valore
res;tuito
in
output.
par1
par2
parn
f
output
Quando
un’altra
funzione
ha
bisogno
di
usare
la
funzione
f,
esegue
una
chiamata
a
f
passandole
in
input
i
parametri
ai
quali
vuole
applicare
la
funzione
f,
nello
stesso
ordine
in
cui
sono
specificate
nella
definizione
delle
funzione
f.
/*
calcola
i
faGoriali
da
1
a
MAX
e
li
stampa
/*
for(i=1;i<=MAX,++i){
faGoriale(i);
prind(“%d”,i);
}
Proto;pi
di
funzioni
Per
poter
usare
una
funzione
f
in
una
altra
funzione
g
è
necessario
che
g
sia
“a
conoscenza”
dell’esistenza
di
f.
In
C
ciò
avviene
mediante
la
dichiarazione
della
funzione
mediante
ciò
che
viene
chiama;
normalmente
il
proto;po
della
funzione.
EsaGamente
come
per
la
variabili
è
buona
regola
dichiarare
le
funzioni
prima
di
usarle.
Ciò
avviene
sempre
aGraverso
i
proto;pi
delle
funzioni.
In
un
proto;po
specifichiamo
 il
nome
della
funzione
 Il
;po
di
dato
res;tuito
dalla
funzione
 i
;pi
dei
parametri
nell’ordine
esaGo
in
cui
compaiono
nella
definizione
della
funzione
Parametri
formali
e
aGuali
Come
abbiamo
visto
le
funzioni
prevedono
in
fase
di
definizione
la
specifica
dei
parametri.
Tali
parametri
sono
deh
parametri
formali
in
quanto
specificano
l’ordine
con
cui
i
parametri
sono
passa;
all’aGo
di
una
chiamata
e
perché
sono
usa;
all’interno
della
definizione
di
una
funzione.
I
parametri
usa;
all’interno
di
una
chiamata
di
funzione
invece
sono
deh
parametri
aGuali
in
quanto
indicano
i
valori
aGuali
sulla
quale
una
funzione
è
chiamata
a
lavorare.
parametro
aGuale
in
una
chiamata
ad
una
funzione
possono
essere
un’
espressione
e
non
sempre
sono
solo
variabili.
Al
contrario
i
parametri
formali
sono
solo
variabili
Istruzione
return
L’istruzione
return
si
usa
nelle
funzioni
per
 res;tuire
il
controllo
all’ambiente
chiamante
 per
permeGere
alla
funzione
di
res;tuire
l’output.
Esempio
.
Una
funzione
cha
calcola
il
valore
assoluto
double
val_ass
(double
x){
if
(x>=0.0)
return
x;
else
return
–x;
}
Non
ha
bisogno
di
parentesi
Chiamata
di
funzione
e
ordine
dei
parametri
Supponiamo
di
avere
una
funzione
cmp
che
riceve
due
interi
e
stampa
un
messaggio
i
errore
se
la
radice
quadrata
del
primo
e
maggiore
del
secondo.
#include<stdio.h>
proto;po
void
cmp(int,
int);
int
main(void){
int
n,m;
........./*
legge
m,n
e
li
modifica
*/
cmp(m,n);
cmp(n,m);
}
void
cmp(int
a,
int
b){
if
sqrt(a)
>
b
prind(“ERRORE”);
}
prima
chiamata
seconda
chiamata
definizione
Esempio
Scrivere
una
funzione
che
dato
un
intero
n
calcoli
e
stampi
la
radice
quadrata
di
tuh
gli
interi
da
1
a
n
usando
l’algoritmo
visto
prima.
#include<stdio.h>
double rad_quad(int); /* prototipo della funzione rad_quad*/
int main(void){
int n;
/*input*/
printf(“Immetti n:”);
scanf(“%d”);
/* legge l’input */
for (i=1;i<=n,++i)
printf(“La radice quadrata di %d e`%f”,i,rad_quad(i));
}
Esempio
double rad_quad(int a){
/* [Pre: a=A] */
/* [Post:rad_quad = radice quadrata di a secondo
l’algoritmo Newton Raphson */
double x0,x1;
x0 = 1;
// inizializzo x0
x1 = 0.5*(x0+(a/x0));
// inizializzo x1
while (x0 != x1){
// calcola la radice
x0 = x1;
// quadrata
x1 = 0.5*(x0+(a/x0));
}
return x0;
}
Precondizione
e
Postcondizione
Ogni
volta
che
scriviamo
e
implemen;amo
una
funzione
dobbiamo
specificarne
l’interfaccia
con
l’esterno.
Vuol
dire
specificare
 precondizione
sui
parametri
e
 postcondizione
sull’output.
Cio
e’
bene
per
una
verifica
di
correGezza,
per
la
leggibilità
e
sopraGuGo
anche
per
la
ges;one
degli
errori.
Esempio
int
sum
(int
n)
{
/*
[Pre:
n>0]
i2
*
[Post:
sum
=
]
∑
i=1n
}
if
(n<=0)
prind(“Error”);
else
........
/*
calcola
la
somma
dei
quadra;
*/
€
Passaggio
dei
parametri
per
indirizzo
A
parte
il
passaggio
dei
parametri
per
valore
esiste
un’altra
metodologia
deGa:
passaggio
dei
parametri
per
indirizzo
o
referenza.
L’idea
è
quella
di
permeGere
la
modifica
del
valore
del
parametro
aGuale.
Alla
funzione
vine
passato
non
il
valore
del
parametro
aGuale,
ma
il
suo
l’indirizzo
in
memoria.
Parametro
AGuale
A
243
Parametro
Formale
X
243
per valore
ind(A)= 2034
B
243
ind(B)= 1089
Y
1089
per indirizzo
Passaggio
dei
parametri
per
indirizzo
In
C
la
modalità
di
passaggio
dei
parametri
è
sempre
quella
per
valore.
È
possibile
realizzare
però
l’effeGo
di
un
passaggio
per
indirizzo
nel
seguente
modo:
   u;lizzando
il
costruGore
di
;po
puntatore
per
la
definizione
dei
parametri
formali,
che
vedremo
più
avan;.
usando
l’operatore
di
dereferenziazione
di
puntatore
all’interno
del
corpo
della
funzione
(*
o
‐>)
passando
al
momento
della
chiamata
della
funzione
come
parametro
aGuale,
un
indirizzo
di
variabile
(usando
eventualmente
l’operatore
di
indirizzo
&)
Visibilità delle variabili
Ambien;:
 Ambiente
globale
è
l’insieme
dei
da;
dichiara;
nella
parte
dichiara;va
globale
 Ambiente
locale
di
una
funzione
è
insieme
di
elemen;
dichiara;
nella
parte
dichiara;ve
e
nell’intestazione
 Ambiente
di
blocco
l’insieme
dichiara;
nella
parte
dichiara;va
di
un
blocco
Il
conceGo
di
ambiente
permeGe
di
usare
gli
iden;ficatori
con
molta
flessibilità.
Infah
è
permesso
usare
gli
stessi
iden;ficatori
anche
on
significa;
diversi
purché
siano
in
ambien;
diversi
Ambiente
ed
esecuzioni
di
funzioni
Abbiamo
visto
che
la
conseguenza
di
una
chiamata
di
funzione
è
l’ahvazione
di
un
ambiente
di
memoria
o
stato
della
funzione
(e
di
una
corrispondente
allocazione
di
memoria)
che
viene
rilasciata
nel
momento
in
cui
la
funzione
res;tuisce
il
valore
dovuto
con
return
oppure
termina
la
sua
esecuzione.
ambiente main
int sum (int, int);
int main(void){
int x,y,z;
x= 4;
y = 6;
z = sum(x,y);
}
int sum (int x, int y){
int f;
f =5;
return x+y+f;
}
x
y
ambiente sum
z
x
y
4
4
4
6
4
6
4
15
f
6
6
5
Strumen;:
Ricorsione
Funzioni
che
chiamano
se
stesse
Abbiamo
visto
che
una
volta
dichiarata
una
funzione
può
essere
chiamata
da
qualunque
altra
funzione.
In
par;colare
dunque
una
funzione
potrebbe
chiamare
se
stessa.....
Chiamata
direGa
int f( int a){
int z;
z = f(a);
return z;
}
Chiamata
indireGa
int f( int a){
int z;
z = g(a);
return z;
}
int g (int b){
int z = f(b);
}
Che
succede
........
?
A
prima
vista
la
cosa
può
sembrare
sorprendente
perché
implicitamente
è
come
se
stessimo
dicendo
che
per
calcolare
la
funzione
f
dobbiamo
calcolare
la
funzione
f
stessa,
oppure
che
per
calcolare
f
dobbiamo
calcolare
g
che
a
sua
volta
ha
bisogno
di
calcolare
f
per
essere
calcolata..........
È
possibile
???
Si
.
La
ricorsione
è
uno
strumento,
usato
non
solo
nella
Programmazione,
la
cui
base
razionale
sono
le
seguen;
due
regole:
  La
soluzione
di
un
caso
generico
di
un
problema
può
essere
oGenuta
a
par;re
dalla
soluzione
di
un
caso
più
semplice
dello
stesso
problema.
Vi
sono
dei
casi
semplici
o
base
che
si
sanno
risolvere
molto
facilmente
senza
dover
ricorrere
allo
stesso
problema.
Formulazione ricorsiva di problemi e algoritmi
Un
analisi
aGenta
mostra
che
la
ricorsione
si
nasconde
dietro
mol;ssimi
conceh,
specialmente
quelli
a
caraGere
matema;co.
Esempi
Supponiamo
di
sapere
solo
sommare
1
ad
un
intero
,
S(x)
=
x+1.
Definiamo
la
somma
di
due
numeri
naturali
x
e
y.
+(x,y)
=
SUCC(+(x,y‐1))
+(x,0)
=
x
Supponiamo
di
sapere
sommare
due
numeri
+(x,y)
Definiamo
il
prodoGo
di
due
numeri
naturali
x
e
y.
*(x,y)
=
+(*(x,y‐1),x)
*(x,0)
=
0
caso ricorsivo
caso base
Formulazione
ricorsiva
di
problemi
e
algoritmi
Esempi
Supponendo
di
disporre
del
prodoGo
di
due
numeri
naturali,
Definiamo
il
faGoriale
di
un
numero
n.
n!
=
(n‐1)!*n
0!=1;
Supponendo
di
disporre
della
somma
tra
due
numeri,
definiamo
la
somma
di
n
numeri
a1,...,an
somma(a1,....,an)=an+
somma(a1,....,a(n‐1))
somma(a1)=a1;
Supponendo
di
disporre
del
prodoGo
cartesiano
tra
due
insiemi,
e
dell’unione
tra
due
insiemi,
definire
l’insieme
delle
par;zioni
di
una
lista
di
n
elemen;
a1,...,an.
perm(a1,...,an)=
perm(a1)={a1}
Metodo:
dal
problema
al
programma
FaGoriale
#include <stdio.h>
int fatt(int)
int main(void){
int x,y;
printf(“Immetti x ”);
scanf(“%d”,&x);
printf(“Il fattoriale di %d è:”,x,f(x));
}
int fatt(int n){
/* [Pre: n>=0]
[Post: fatt = n!] */
int ris;
if (n>0) ris = n*fatt(n-1);
else ris = 1;
return ris
}
Programmazione
ricorsiva
1. 2. 3. 4. 5. 6. Formulare
un’idea
risolu;va
ricorsiva
del
problema.
Scrivere
il
proto;po
delle
funzione
specificando
precisamente
1. parametri
2. pre
condizione
e
3. post‐condizione
Iden;ficare
il
parametro(i)
della
ricorsione,
ovvero
la
variabile(i)
dove
contenuta
l’informazione
che
si
semplifica
ad
ogni
chiamata
Formulare
un
caso
ricorsivo
o
induhvo,
che
permeGa
di
risolvere
il
problema
per
un
caso
generico
in
funzione
di
un
caso
“più
semplice”
1. Per
quali
valori
del
parametro
della
ricorsione
2. Cosa
fare
Un
caso
base,
che
permeGa
di
risolvere
il
problema
in
maniera
direGa,
senza
dover
cioè
ricorrere
alla
soluzione
di
un
caso
più
semplice
dello
stesso
problema
1. Per
quali
valori
del
parametro
ricorsione
(complemento
del
CR)
2. Cosa
fare
Terminazione.
Assicurarci
che
effehvamente
via
sia
una
quan;tà
che
si
s;a
“semplificando”
(in
genere
riducendo)
nella
definizione
del
caso
ricorsivo
e
che
tale
semplificazione
conduce
al
caso
base
e
quindi
la
ricorsione
termina.
NOTA
IMPORTANTE.
Per
poter
implementare
una
funzione
ricorsiva
dobbiamo
definire
una
funzione
nuova.
Non
possiamo
usare
il
main.
ProdoGo
Il
prodoGo
di
due
numeri
naturali.
1. Idea:
*(x,y)
=
*(x,y‐1)
+
x
*(x,0)
=
0
2. 3. 4. Proto;po
funzione
Int prod(int x, int y)
/* Pre: x,y >0 */
/* Post: prod(x,y)= x*y */
Param
ricorsione:
y
caso
ricorsivo
o
induhvo.
1. Cosa
fare
:
prod(x,y)
=
ris
=
prod(x,y‐1)+x.
2. Quando:
y
>
0.
ProdoGo
5. Un
caso
base
*(x,0)
=
0.
1. Quando:
y
=
0.
2. Cosa
Fare:
ris
=
prod(x,0)=0
6. Verifica
della
Terminazione.
1. A
ad
ogni
chiamata
ricorsiva
y
decresce
di
un’unità.
2. All’inizio
(vedi
Pre:)
è
>0/
3. Per
cui
ci
sarà
una
chiamata
in
cui
arriverà
ad
essere
0.
4. In
quel
caso
sapremo
risolvere
il
prodoGo
direGamente
come
specificato
nel
caso
base.
NOTA
IMPORTANTE.
In
una
funzione
ricorsiva
vi
sarà
sempre
una
selezione
(if‐then‐else)
per
poter
dis;nguere
tra
caso
base
e
casi
ricorsivo.
ProdoGo:implementazione
#include <stdio.h>
int f(int, int)
int main(void){
int x,y;
printf(“Immetti x ed y”);
scanf(“%d%d”,&x,&y);
printf(“Il prodotto di %d e %d è %d:”,x,y,f(x,y));
}
int f(int x, int y){
/* [Pre: x,y >=0]
[Post: f = x*y] */
if (y>0) ris = x+f(x,y-1));
else ris = 0;
return ris;
}
FaGoriale
Il
prodoGo
di
due
numeri
naturali.
1. Idea:
n!
=
n*
(n‐1)!
0!
=
1
2. 3. 4. Proto;po
funzione
Int fatt(int n)
/* Pre: n >= 0 */
/* Post: fatt(n)= n! */
Param
ricorsione:
n
caso
ricorsivo
o
induhvo.
1. Cosa
fare
:
[faG(n)
=
]
ris
=
faG(n‐1)*n.
2. Quando:
n
>
0.
FaGoriale
5. Un
caso
base
0!
=
1.
1. Quando:
n
=
=
0.
2. Cosa
Fare:
[faG(0)=]ris=1;
6. Verifica
della
Terminazione.
1. A
ad
ogni
chiamata
ricorsiva
n
decresce
di
un’unità.
2. All’inizio
(vedi
Pre:)
è
>0/
3. Per
cui
ci
sarà
una
chiamata
in
cui
arriverà
ad
essere
0.
4. In
quel
caso
sapremo
risolvere
il
prodoGo
direGamente
come
specificato
nel
caso
base.
FaGoriale
#include <stdio.h>
int fatt(int)
int main(void){
int x,y;
printf(“Immetti x ”);
scanf(“%d”,&x);
printf(“Il fattoriale di %d è:”,x,f(x));
}
int fatt(int n){
/* [Pre: n>=0]
[Post: fatt = n!] */
int ris;
if (n>0) ris = n*fatt(n-1);
else ris = 1;
return ris
}
SVILUPPARE
UN’IDEA
RICORSIVA
LA
POSTCONDIZIONE
AIUTA
A
SVILUPPARE
UN’IDEA
RICORSIVA
!
PERCHE
?
DICE
PRECISAMENTE
COSA
RESTITUISCE
LA
CHIAMATA
AD
UNA
FUNZIONE
SENZA
CONOSCERE
LA
FUNZIONE
NELLA
RICORSIONE
DOBBIAMO
USARE
UNA
CHIAMATA
ALLA
FUNZIONE
PER
DEFINIRE
IL
CASO
RICORSIVO
E
QUINDI
SENZA
SAPERE
COME
È
FATTA
LA
FUNZIONE
Somma‐cifre
Si
vuole
scrivere
una
funzione
ricorsiva
che
sommi
le
cifre
di
un
numero
naturale
decimale
n
dato.
int
sommacifre(int
n)
/*
pre:
n
>=0
*/
/*
Post:
se
n
è
aka(k‐1)….a0,
allora
sommacifre(n)=a0+…+ak
*/
IDEA
per
una
definizione
ricorsiva
di
sommacifre:
‐ ‐ Supponiamo
di
avere
il
nostro
n
=
aka(k‐1)….
a1a0
Noto
che
se
chiamo
sommacifre
su
m
=
aka(k‐1)….
a1
allora
sommacifre(n)
=
a0+
a1+………….+ak
a0+
sommacifre(m)
‐ Cosa
sono
a0
e
m
in
funzione
di
n
?
‐ a0
=
n
%
10
‐ m
=
n
/10
Somma‐cifre
Il
prodoGo
di
due
numeri
naturali.
1. Idea:
sommacifre(n)
=
n
%
10
+
sommacifre(n/10)
sommacifre(0)=0
2. 3. 4. Proto;po
funzione
Int sommacifre (int n)
/* Pre: n >= 0 */
/* Post: se n è aka(k-1)….a0, allora,
sommacifre(n)=a0+…+ak */ Param
ricorsione:
n
caso
ricorsivo
o
induhvo.
1. Cosa
fare
:
[sommacifre(n)
=
]
ris
=
sommacifre(n/10)+n/10.
2. Quando:
n
>
0.
Somma‐cifre
5. Un
caso
base
sommacifre(0)
=
0.
1. Quando:
n
=
=
0.
2. Cosa
Fare:
[sommacifre(0)=]ris=0;
6. Verifica
della
Terminazione.
1. A
ad
ogni
chiamata
ricorsiva
n
viene
divisa
per
10
2. All’inizio
(vedi
Pre:)
è
>=0/
3. Per
cui
ci
sarà
una
chiamata
in
cui
arriverà
ad
essere
0.
4. In
quel
caso
entriamo
nel
caso
base.
Somma
cifre:implementazione
#include <stdio.h>
int sommacifre(int)
int main(void){
int x,y;
printf(“Immetti x ”);
scanf(“%d”,&x);
printf(“La somma delle cifre di %d
è:”,x,sommacifre(x));
}
int sommacifre(int n){
/* [Pre: n>=0]
[Post: sommacifre = soma delle cifre di n]*/
int ris;
if (n>0) ris = (n%10)+sommacifre(n/10));
else ris = 0;
return ris;
}
Quasi‐primi
Si
vuole
scrivere
una
funzione
ricorsiva
che
sommi
dica
se
un
numero
naturale
n
È
divisibile
unicamente
per
2,
3
e
5.
Int
qprimo(int
n)
/*
pre:
n
>=0
*/
/*
Post:
qprimo(n)=1
sse
n
è
divisibile
solo
per
2,3
e
5
*/
IDEA
per
una
definizione
ricorsiva
di
sommacifre:
‐
Se
n
è
divisibile
per
2,
allora
qprimo(n)
=
qprimo
(n/2)
‐
Se
n
è
divisibile
per
3,
allora
qprimo(n)
=
qprimo
(n/3)
‐
Se
n
è
divisibile
per
5,
allora
qprimo(n)
=
qprimo
(n/5)
Se
n=2
opure
3
oppure
5,
allora
qprimo(n)
=
1
Quasi‐primi
Il
prodoGo
di
due
numeri
naturali.
1. Idea:
qprimi(n)
=
qprimi(n/)
sommacifre(0)=0
2. Proto;po
funzione
Int qprimo(int n)
/* Pre: n >= 0 */
/* Post: qprimo(n)=1 sse n è divisibile solo per
2,3 e 5 */
3. Param
ricorsione:
n
4. caso
ricorsivo
o
induhvo.
1. Cosa
fare
:
se
n
è
divisibile
per
2,
ris
=
qprimi(n/2)
se
n
è
divisibile
per
3,
ris
=
qprimi(n/3)
se
n
è
divisibile
per
5,
ris
=
qprimi(n/5)
1. Quando:
n
non
2,3,
5.
Quasi‐primi
5. Un
caso
base
1. Quando:
n
=
=
2,3,5.
2. Cosa
Fare:
[sommacifre(0)=]ris=1;
3. Altrimen;
ris
=0
6. Verifica
della
Terminazione.
1. A
ad
ogni
chiamata
ricorsiva
n
viene
divisa
per
2,3,5
2. All’inizio
(vedi
Pre:)
è
>=0/
3. Per
cui
ci
sarà
una
chiamata
in
cui
arriverà
ad
essere
0
oppure
2,3,5.
4. In
quei
casi
entriamo
nel
caso
base.
Dati:
Vettori
Vettori
Molte volte i programmi fanno uso di dati omogenei,tali cioè da
dover essere trattati nello stesso modo.
Esempio.
Supponiamo di dover usare un dato per contenere i voti degli
studenti di un corso. Non sappiamo in anticipo il numero di studenti.
Non possiamo dichiarare un numero variabile di variabili scalari.
I linguaggi mettono a disposizione del programmatore strutture
dette array che accorpano dati omogenei e la cui dimensione può
essere decisa dal programmatore.
Vettori
Gli array o vettori possono essere pensati com un’unica variabile
Di tipo vettore la cui dimensione può essere specificata.
La dichiarazione di un array che contiene interi il cui nome è voto e
di dimensione 3 è:
int voto[3];
dimensione
del
veGore
Nome
del
veGore
Tipo
dei
da;
contenu;
Vettori
float distanze[10];
2.34
2.0
posizioni
0
1
34.2
54.0
2.34
2
3
4
.98
5
2.34
2.42
6.6
6
Per
accedere
ad
un
elemento
di
un
array
dobbiamo
specificare
la
posizione.
Esempio
Il
primo
elemento
degli
array
è
in
posizione
0.
distanze[0] (=2.34)
L’ul;mo
elemento
dell’array
distanze
è
in
posizione
9.
distanze[9]
(=3.4)
Limite inferiore = 0
Limite superiore = lunghezza - 1
Lunghezza = limite superiore +1
7
8
3.4
9
Vettori
Se
prova
è
un
array
di
N
elemen;
int prova[N];
allora
l’espressione
prova[expr]
permeGe
di
accedere
ad
un
singolo
elemento
dell’array
prova.
Prima
cosa
viene
valutata
expr.
Quindi
si
accede
all’elemento
del
veGore
in
posizione
(expr
‐1).
Si
tenga
presente
che
se
la
valutazione
di
expr
produce
un
valore
inferiore
a
0
o
superiore
a
N‐1,
allora
viene
segnalato
un
Errore
in
fase
di
esecuzione
del
programma
(e
non
in
compilazione
!!)
E’
compito
del
programmatore
assicurarsi
che
gli
indici
assumano
valori
Nell’intervallo
correGo.
Somma elementi array
Sia tempi un vettore di 100 elementi interi. Scriviamo il codice
Per sommare tutti i tempi in un’unica variabile sum.
Idea iterativa: ad ogni iterazione sommiamo in sum un elemento dell’array tempi in maniera
incrementale (alla 1ª iterazione il 1° elemento, alla 2ª iterazione il 2 ° elemento…)
Inizializzazione: sum = 0 (inizialmente non abbiamo ancora sommato nulla)
i = 0 I è un variabile indice che serve per trattare tutto l’array e
deve iniziare dal primo elemento, quello in posizione 0.
Proseguimento: Aggiorno sum sommandogli l’elemento corrente di tempi
sum = sum + tempi[i]
incremento i di 1 per passare alla prossimo elemento del vettore
i = i+1
Terminazione: Quando i = 99 sommo l’ultimo elemento di tempi. Incremento i a 100 e
ho terminato. Devo quindi uscire dal ciclo. Pertanto
i == 100 è la condizione di uscita
Verifica Terminazione: Incremento i ad ogni iterazione iniziando da 0. Primo poi raggiungo i=100
e termino
Somma elementi array
Per
poter
elaborare
tuh
gli
elemen;
di
un
array
è
buona
norma
usare
un
ciclo
for.
Esempio:
Somma
degli
elemen;
di
un
array
#define
dim
int tempi[dim];
int i;
int sum;
100
/* vettore di 100 interi */
/* variabile indice per scorrere l’array */
/* variabile per la somma degli elementi degli array */
sum =0;
for(sum=0,i=0; i<dim; i++)
sum + = tempi[i];
/* soluzione con il while */
i=0;
/* inizializzazione dell’indice */
sum = 0;
/* inizializzazione della variabile sum */
while (i<dim)
/* Invariante: 0<=i<=dim,
sum += tempi[i++];
*/
Tipi definiti dal programmatore
Il C mette a disposizione una parola chiave per permetterci di definire
nuovi tipi. La parola chiave usata è typedef e la sua sintassi è la seguente
typedef <tipo esistente> <nuovotipo>
Esempio
typedef int gradi; gradi a,b,c,d; Per definire un nuovo tipo sugli array si usa la seguente dichiarazione:
typedef int VotiCanale2[100];
/* VotiCanale2 è un tipo array di 100 elementi interi */
VotiCanale2 Appello1, Appello2, Appello3;
/* ho dichiarato tre vettori di 100 interi */
Esempi
sull’uso
di
typedef
con
Array
1.
Quando
scrivo
typedef float NuovaLista[20];
definisco
il
;po
NuovaLista,
come
un
array
di
20
elemen;
di
;po
float
in
cui
l’indice
può
assumere
valori
da
0
a
19.
2.
La
dichiarazione
di
un
;po
array
può
essere
implicita
nalla
dichiarazione
di
una
variabile.
SOno
equivalente
le
seguen;
due
dichiarazioni:
int lista[20];
/* lista è un vettore di 20 interi */
typedef int List[20]; /* List è il tipo array di 20 interi */
List lista; /* lista è una variabile di tipo List */
Esempi
sull’uso
di
typedef
con
Array
2.
Mentre
non
conviene
usare
il
typedef
per
la
seguente
dichiarazione
typedef float VettorediReali[100];
VettorediReali v1,v2,v3;
e
semplificarlo
come
float v1[100],v2[100],v3[100];
Conviene
invece
usare
il
typedef
in
un
esempio
come
questo
typedef float PioggeMensili[12];
typedef float IndiciBorsa[12];
PioggeMensili Piogge02, Piogge03,Piogge04;
IndiciBorsa Indici02, Indici03, Indici04;
Dati:
Puntatori
Puntatori
Finora siamo abituati a pensare ad una variabile come ad un nome di
una zona di memoria. Al contenuto di una variabile accediamo attraverso
l’uso del suo nome :
int x,a=0;
x = a;
significa preleva il valore contenuto nella cella il cui nome è a e copialo nella cella
il cui nome è x.
Introduciamo ora le variabili di tipo puntatore.
Tali variabili consentono di immagazzinare indirizzi di memoria.
Esempio
int *p;
dichiara una variabile p di tipo puntatore ad interi.I valori ammessi per questa variabile
sono interi positivi interpretati come indirizzi fisici di memoria assegnata al C nel
sistema.
Puntatori
Esempio
int y;
int *p;
Valori ammessi per p sono ad esempio:
p = 0;
p= NULL; /* equivalente a p =0 */
p = &y; /* p punta a y */
p = (int *) 1776; /* indirizzo assoluto in memoria */
Lo * è di fatto un operatore unario detto operatore di indirizzamento indiretto o
dereferenziazione. È quindi soggetto alle stesse regole di priorità ed
associatività degli altri operatori.
Norma
int *p;
/* p assume come valori indirizzi di memoria */
/* *p contiene il valore contenuto all’indirizzo assunto da p */
Puntatori
Esempio
int
a=1,b=2,*p;
1
2
a
b
p
p
=
&a;
1
2
a
b
=
*p;
/è
quindi
equivalente
a
b
=
a;
*/
b
1
1
a
b
p
p
Tipo puntatore
Per definire un tipo puntatore, possiamo usare il typedef.
Esempio
typdef int *TipoPuntatoreIntero /* definisce un tipo per
puntatori a interi */
int x,y;
TipoPuntatoreIntero p,q;
/* equivalente a int *p,*q; */
Cosa succede con le istruzioni ?
p= &x; /* p punta a x */
q= &y; /* q punta a y */
p=q; /* p punta all’indirizzo dove punta q*/
p
q
p
x
y
q
x
y
Valore NULL
Se un variabile puntatore ha valore NULL allora *P è indefinito.
In altre parole P = NULL, significa che P non punta ad alcuna informazione
significativa. Graficamente P= NULL viene denotato come
P
Chiamata per indirizzo in C
Il passaggio dei parametri per indirizzo permette in altri linguaggi di cambiare
il valore dei parametri attuali all’interno della funzione dove vengono passati.
In C non esiste una modalità di passaggio dei parametri per indirizzo.
Ma è possibile modificare i valori di variabili all’interno di una funzione usando i
puntatori.
Esempio
int main(void){
int i=3, j=5;
swap(&i,&j);
printf(“i=%d,j=“d\n,i,j);
}
void swap(int *p, int *q){
int aux;
aux = *p;
*p = *q;
*q = aux;
}
Chiamata
per
indirizzo:simulazione
Nel
main
i
3
j
p
5
q
aux = *p;
i
aux
j
3
p
i
aux
j
5
p
i
q
3
aux
j
5
3
5
q
*q = aux;
p
5
q
*p = *q;
In
Swap
3
aux
3
Chiamata per indirizzo in C
L’effetto di una chiamata per indirizzo in C è ottenuto:
1. 2. 3. Dichiarando una parametro della funzione come puntatore
Utilizzando il puntatore deferenziato all’interno della funzione.
Passando come parametro attuale nella chiamata alla funzione l’indirizzo
della variabili che si vuole passare “per indirizzo”
Dati:
Vettori e Puntatori
Relazione tra puntatori ed array
Il nome di un array è di per sé un indirizzo.
int a[100];
/* a è l’indirizzo di inizio delle celle di memoria assegnate all’array a
cioè a = &a[0]*/
Quindi di fatto a è completamente analoga a una variabile puntatore.
Infatti in C i puntatori possono essere trattati esattamente come array e
quindi essere indicizzati.
Esempio
int a[100];
int *p;
a[i] è equivalente a *(a+i)
p[i] è equivalente a *(p+i)
Relazione tra puntatori ed array
int a[100];
int *p;
Un espressione come a+i assume come valore l’indirizzo di memoria ottenuto
spostandosi di i celle dall’indirizzo di basse assegnato al vettore a in fase di
esecuzione. Quindi a+i è equivalente a &a[i].
> Ciò vuol dire che usando l’operatore di dereferenziamento * un altro modo di accedere
all’elemento a[i] è quello di usare l’espressione *(a+i).
> Analogamente possiamo procedere per un puntatore generico.
> Espressioni come p+i denotano l’indirizzo ottenuto muovendosi di i posizioni
dall’indirizzo p.
Il linguaggio C consente di eseguire operazioni di somma e sottrazioni su
puntatori per consentire al programmatore maggiore flessibilità nella gestione
dell’indirizzamento esplicito e nella gestione della memoria
Se p punta a un particolare tipo di dato, l’espressione p+1 fornisce l’indirizzo in memoria
per l’accesso o la memorizzazione della prossima variabile di quello stesso tipo di dato
nella memoria.
Relazione tra puntatori ed array: differenze
int a[100];
int *p, *q;
a è un puntatore costante, cioè non può assumere altri indirizzi di memoria, a
parte quello assegnatogli in esecuzione al momento della sua dichiarazione.
Per cui espressioni come
a = p;
++a;
a+= 2;
che avrebbero l’effetto di modificare l’indirizzo di a non sono ammesse e quindi
scorrette. Mentre p e q possono assumere qualunque indirizzo, quindi
espressioni come:
p =q,
++p;
p +=2; sono invece corrette ed ammesse e sono parte dell’aritmetica dei
puntatori
Relazione tra puntatori ed array
int a[100]; *p, sum;
Se in esecuzione al vettore a è assegnata 300 come indirizzo base ed
un intero occupa 4 byte avremo che l’indirizzo del primo elemento di a è
300 (cioè &a[0]=300), l’indirizzo del secondo elemento di a è 304 (cioè
&a[1] =304) e cosi via.
L’assegnamento
p=a;
ha come effetto quello di assegnare l’indirizzo 300 al puntatore p. Vale a dire
è equivalente a scrivere p = &a[0].
p= a+1;
ha come effetto quello di assegnare a p l’indirizzo 304.Vale a dire è equivalente
a scrivere p = &a[1]
Relazione tra puntatori ed array
int a[100]; *p, sum=0,i;
Per cui la somma degli elementi di un vettore
Versione 1
for(i=0;i<dim,i++) sum += a[i];
Come si può scrivere?
Versione 2
for( p=a; p<&a[100]; p++) sum += *p;
Versione
3
for( i=0;
i<100;
i++) sum += *(a+i);
Versione
4
p=a;
for(i=0;
i<100;
i++) sum += p[i];
Aritmetica dei puntatori:dimensioni
Sizeof è un operatore che si usa per sapere l’occupazione in byte di una
variabile o di un tipo.
int a[5];
sizeof(a[3]) restituisce il valore 4
sizeof(a) restituisce il valore 20
Se p e q sono due puntatori ad elementi di un array, allora la loro differenza
p-q restituisce il valore int che rappresenta il numero di elementi nell’
array tra p e q (se p<q). Comunque siccome un elemento di un array è salvato su
più locazioni di memoria (Esempio i double occupano 8 byte), la differenza
in termini di locazioni di memoria tra p e q è differente tra la differenza in
termini di elementi dell’array
double a[2], *p,*q;
p =a;
q=p+1;
printf(“q-p=%d\n”, q-p);
/* stampa 1 */
printf(“q-p=%d\n”, (int) q – (int) p); /* stampa 8 */
Metodo:
Iterazione su Vettori
Ricerca di un elemento in un array
Idea algoritmo
Cerchiamo l’elemento nell’array a partire dal primo elemento e ci fermiamo quando
troviamo l’elemento o quando non abbiamo piu elementi da cercare
i
l’elemento
non
è
presente
Inizializzazione: i=0; trovato= 0 (falso)
Proseguimento: se t[i]=el, allora abbiamo terminato. quindi trovato =1; altrimenti
proseguiamo con il prossimo elemento da trattare. Quindi i++
Terminazione: trovato =1 oppure i=dim.
Verifica Terminazione: incrementiamo ad ogni iterazione, quindi prima o poi raggiungiamo
l’ultimo elemento oppure se troviamo l’elemento trovato = 1
Ricerca di un elemento in un array
int ricerca(int *t, int dim, int el){
int trovato=0;
int i=0;
while (! trovato && i< dim){
trovato = ((*(t + i++)==el) ?1 :0);
}
return trovato;
}
Conta‐elemen;:
pseudo‐codice
Scrivere un algoritmo che conti quanti pari e dispari vi sono in un array di
interi di dimensione d (cioè le posizioni vanno da 0 a d-1).
Iterativo. Ricorriamo tutto il vettore e ad ogni passo incrementiamo il cnt dei
pari o dei dispari a seconda che l’elemento del vettore sia pari o dispari
i
veGore
traGato
Inizializzazione: i=0; cnt_pari= 0, cnt_dsp=0
Proseguimento: se t[i] mod 2 =0, allora ++cnt_pari altrimento cnt_dsp++, incremento i
Terminazione: i=d.
Verifica Terminazione: incrementiamo ad ogni iterazione, quindi prima o poi raggiungiamo
l’ultimo elemento
Programma
void ricerca(int *t, int dim, int *cnt_dsp, int *cnt_pari){
int i;
*cnt_pari = *cnt_dsp =0;
i=0;
while (i< dim){
if ((t[i] % 2) == 0) ++(*cnt_pari);
else ++(*cnt_dsp);
i++;
}
Chiamata
dal
main
int cnt_dsp, int cnt_pari;
int t[dim];
ricerca(t,dim,&cnt_dsp,&cnt_pari);
occorrenze elemento in array: iterativo
Scrivere un algoritmo che conti il numero di volte che occorre un elemento in un
array di interi di dimensione d (cioè le posizioni vanno da 0 a d-1).
Iterativo. Ricorriamo iterativo tutto il vettore e ad ogni passo incrementiamo un cnt
Se l’eelmento attuale è quello cercato
i
veGore
traGato
Inizializzazione: i=0; cnt= 0
Iterazione: se t[i] = el , allora ++cnt, incrementiamo i
Terminazione: i=d.
Verifica Terminazione: incrementiamo ad ogni iterazione, quindi prima o poi raggiungiamo
l’ultimo elemento
occorrenze elemento in array: iterativo
int occ_el(int *t, int dim, int el){
int i,cnt;
cnt =0;
i=0;
while (i< dim){
if ((t[i++] == el) ++cnt;
}
occorrenze elemento in array: ricorsivo
Scriviamo la funzione occ_el(t,i,el)= # occorrenze di el in t
Caso Base: i=0; occ_el(t,0,el) = 1 se t[0]= el, ed è = 0 altrimenti
0
Caso Ricorsivo: i>0; occ_el(t,i,el) = occ_el(t,i-1,el)+1 se t[i] == el,
= occ_el(t,i-1,el)+0 se t[i] != el
occ_el(t,i-1, el)=
# occorr. di el in t[0,...,i-1]
i
Terminazione: il parametro della ricorsione si decrementa ad ogni chiamata. Inizianndo da
i=d-1 prima o poi raggiungeremo il caso base i=0
occorrenze elemento in array: ricorsivo
Algoritmo
int occ_el(int *t, int i, int el){
int ris;
if (i==0) ris = ((t[0]==el)? 1:0);
else ris = occ_el(t,i-1,el)+(t[i]==el) ?1:0);
}
return ris;
}
Chiamata
dal
main
int occ, el, t[dim];
occ=occ_el(t,dim-1,el);
Esercizi su Array
1. 2. 3. 4. 5. 6. 7. Scrivere un algoritmo per determinare se in un array di interi vi sono solo elementi
dispari.
Scrivere un algoritmo per determinare se una tavola è ordinata crescentemente
Scrivere un algoritmo iterativo per calcolare la media e la varianza di un array di reali.
Scrivere un algoritmo iterativo per calcolare la media e la varianza di un array di reali in
modo che ogni elemento dell’array venga ispezionato solo una volta.
Scrivere una algoritmo per decidere se in un array con un numero di elementi pari
t[0,...,2N-1] gli elementi di indice pari sono ordinati in ordine crescente e quelli di indice
dispari in ordine decrescente
Scrivere una procedure che modifichi un array sommando 1 a tutti i suoi elementi
Scrivere un algoritmo per contare il numero di cambi di segno in un array il cui primo
elemento è differente da 0. Un cambio di segno si verifica quando nell’array vi sono
due interi di segno opposto contigui o separati solo da 0.
BubbleSort
Supponiamo di avere un array non ordinato di lunghezza d e di volerlo ordinare
L’idea del bubble sort e’ quella di ordinare l’array come segue:
 ad ogni iterazione (i=0,...dim-1) vogliamo inserire il più piccolo elemento della parte da
dim ..... fino ad i e
 per trovare il più piccolo elemento nella parte dim....i confrontiamo tutti gli elementi
adiacenti a partire dagli ultimi due scambiandoli di posizione se il più piccolo segue il
più grande
Ordinata
i
Muoviamo il più piccolo elemento verso
la posizione i
BubbleSort: pseudo-codice
Ciclo esterno
Inizializzazione: i=0;
Proseguimento: Sposto usando il ciclo interno il più piccolo elemento di t[i,....,d-1] in
posizione t[i].
Terminazione: ho terminato quando ho ordinato le d-1 posizioni del vettore.
L’ultimo elemento sarà certamente ordinato. Quindi i = d-1 è
la condizione di terminazione
Ciclo interno: Iniziando dall’ultimo elemento e scendendo fino alla posizione i scambio
nel vettore due valori contigui se il piú grande precede il piú piccolo
Inizializzazione: j=d-1;
Iterazione: confronto t[j] con t[j-1] e se t[j-1] è più grande li scambio nel vettore.
Decremento j di 1. Quindi j--.
Terminazione: ho terminato quando j == i
BubbleSort: programma
void bubble_sort(int *t, int dim, int el){
int i,j;
for(i=0;i<dim; i++)
for(j=dim-1; j>i; j--)
if (t[j-1]>t[j]) swap(&t[j-1],&t[j]);
}
void swap(int *p, int *q){
int aux;
aux = *p;
*p = *q;
*q = aux;
Metodo:
Iterazione su Vettori
Importanza delle Ipotesi sui dati
Elementi con somma > 0
Scrivere una algoritmo che dati due vettori t e s conti il numero di coppie (i,j) tali che
t[i]+s[j]>0.
Scrivere una algoritmo più efficiente nel caso in cui i due vettori siano
ordinati.
I Algoritmo. Per ogni elemento del primo array ricorriamo tutto il secondo array e contiamo il
numero di coppie (i,j) tali che t[i]+s[j] >0.
I ciclo
Inizializzazione: i=0
Iterazione: Dato t[i] Ricorriamo tutto per contare quante coppi (i,j) ci sono tali che
t[i]+s[j]
Terminazione: i=d1
Verifica Terminazione: Ad ogni iterazione incrementiamo i.
II Ciclo
Inizializzazione: j=0;
Iterazione: se t[i]+s[j] >0 incrementa il cnt.
Terminazione: j=d2
Elementi con somma > 0
int somma_mag(int *t1, int d1, int *t2, int d2){
/* Pre: d1, d2 >=1 */
/* Post: somma_mag(t1,d1,t2,d2)= # coppie con somma >0 in t ed s */
int i,j,cnt;
i=cnt=0;
while((i<d1){
j=0;
while (j<d2)
if (t[i]+s[j]>0){
++cnt;
++j;
}
++i;
}
return cnt;
}
Intersezione vettori ordinati
Scrivere una algoritmo che dati due vettori di interi ordinati (senza ripetizioni) restituisca un
terzo vettore formato dall’intersezione dei due vettori.
Idea. Ricorriamo i due vettori in parallelo con due indici i e j. Ad ogni passo dell’iterazione
se troviamo due elementi uguali lo aggiungiamo nel nuovo vettore, altrimenti
incrementiamo l’indice del vettore con l’ elemento più piccolo
i
20
40
veGore
traGato
j
23
veGore
traGato
25
Intersezione vettori ordinati:pseudo-codice
Supponiamo i due vettori siano, int t[dim1] e int s[dim2] , int r[dim]
Inizializzazione: i=0, j=0, k=0
Iterazione: se t[i]=s[j] allora scriviamo l’elemento in r , incrementiamo k la sua dimensione
dim3 e incrementiamo sia i che j.
se t[i]<s[j] allora incrementiamo i.
Se t[i]>s[j], allora incrementiamo j
Terminazione: i=dim-1 oppure j = dim2-1
Verifica Terminazione: Ad ogni iterazione, incrementiamo i oppure j. Quindi
uno dei due indici sicuramente raggiungerà l’ultima posizione.
Intersezione vettori ordinati: programma
int intersezione(int *t1, int d1, int *t2, int d2, int *t3){
/* Pre: t,s ordinati e senza ripetizioni. d1, d2 >1 */
/* Post: t3 = t∩s e intersezione(t1,d1,t2,d2,t3)= |t∩s| */
int i,j,cnt;
i=j=cnt=0; /* inzializzo i contatori */
while((i<d1) && (j < d2)){
if (t1[i]==t2[j]){
*(t3+cnt)=t1[i];
++cnt;
++i;
++j;
}
else if (t1[i]<t2[j]) i++;
else j++;
}
return cnt;
}
Fusione ordinata di array ordinati
Input: Due array ordinati
Output: un array ordinato che contiene l’unione degli elementi dei due array
traGato
i
25
t
j
s
20
traGato
Idea. ricorriamo i due vettori contemporaneamente con due indice i e j e copiamo nel
terzo vettore l’elemento minore dei due vettori e avanziamo solo in questo
vettore. Quando raggiungiamo la fine di uno dei due vettori, copiamo il
rimanente dell’altro nel terzo vettore
Fusione ordinata di array ordinati:
Pseudo-codice
Ciclo principale
Inizializzazione: i=0, j=0, k=0
Iterazione: se t[i]<s[j], allora u[k]=t[i] e i++e k++, altrimenti u[k]=s[j] e j++ e k++.
Terminazione: Terminiamo non appena abbiamo terminato di consultare uno dei due vettori.
Cioè i=dim1, oppure j= dim2
Verifica Terminazione: Ad ogni iterazione incrementiamo i o j, quindi prima o poi raggiungiamo
uno dei casi della terminazione.
Algoritmo
Fusione ordinata di array ordinati:
programma
void merge(int *t, int dim1, int *s,
int i,j,k;
i=0,j=0,k=0;
while (i<dim1 && j<dim2)
if (t[i]<s[j]) u[k++]=t[i++];
else u[k++]=s[j++];
while(i<dim1)u[k++]=t[i++];
while(j<dim2)u[k++]=s[j++];
}
int dim2, int *u){
merge sort
ordina
con
merge
sort
ordina
con
merge
sort
ordinato
ordinato
fusione
ordinata
ordinato
merge sort
Algoritmo
void merge_sort(int *t,int inf, int sup){
int i,j,k;
k=(inf+sup)/2;
mergesort(t,inf,k);
mergesort(t,k+1,sup);
merge(t,inf,k,t,k+1,sup,)
}