INFORMATICA
Il linguaggio C
Premessa
• Fu creato agli inizi degli anni ’70 quale strumento per lo
sviluppo del Sistema Operativo UNIX.
• Si è diffuso molto rapidamente e nel 1989 l’American
National Standards Institute (ANSI) completava la
definizione del linguaggio producendo il documento noto
come ANSI C, al quale fanno riferimento ormai tutti i
compilatori per quel linguaggio.
• A differenza degli altri linguaggi ad alto livello consente
un agevole accesso alla struttura hardware del sistema di
elaborazione.
© Piero Demichelis
2
Caratteristiche Generali
• Il C è un linguaggio:
-
ad alto livello
• ... ma anche poco astratto
-
strutturato
• ... ma con eccezioni
-
fortemente tipizzato
• ogni oggetto ha un tipo
-
semplice (!?)
• poche keyword
-
case sensitive
• maiuscolo diverso da minuscolo negli identificatori!
-
portabile
standardizzato (ANSI)
© Piero Demichelis
3
Alfabeto
Il vocabolario base del C è costituito dai seguenti simboli:
• tutte le lettere dell’alfabeto inglese maiuscole e minuscole:
A÷Z, a÷z
• le dieci cifre decimali
0÷9
• un insieme di caratteri speciali, tra cui:
+ */=<>()[]{}.,;: ”?!%#&˜ˆ
© Piero Demichelis
4
Identificatori
• Si riferiscono ad una delle entità del linguaggio:
-
costanti
variabili
funzioni
-
ecc.
-
•
Iniziano con un carattere alfabetico oppure con “_”
(underscore) e possono contenere solamente caratteri
alfabetici, cifre e “_”
•
Il C standard prevede che solo i primi 31 caratteri
dell'identificatore sono significativi, anche se possono
essere usati nomi più lunghi.
© Piero Demichelis
5
Commenti
• Sono testi liberi inseriti all’interno del programma dal
programmatore per descrivere cosa fa il programma.
• Non sono processati dal compilatore: servono al
programmatore, non al sistema!
• Formato:
-
Racchiuso tra i simboli:
Non è possibile annidarli.
/*
*/
• Esempi:
/* Questo è un commento corretto! */
/* Questo /*
risulterà un
© Piero Demichelis
*/ errore */
6
Istruzioni
• Le istruzioni devono essere scritte rispettando alcune
regole sintattiche e di punteggiatura.
• L’istruzione deve sempre essere conclusa con un ; (punto
e virgola).
• Si può scrivere più di un’istruzione per riga purché
ognuna sia conclusa col ;.
• Un’istruzione può occupare più di una riga.
© Piero Demichelis
7
Parole chiave
• Riservate!
• Nel C standard sono 32:
auto
break
case
char
const
continue
default
do
double
else
enum
extern
float
for
goto
if
int
long
register
return
short
signed
sizeof
static
© Piero Demichelis
struct
switch
typedef
union
unsigned
void
volatile
while
8
Struttura di un programma C
• Struttura generale:
Direttive e parte dichiarativa globale
main ()
{
Parte dichiarativa locale
Parte esecutiva
}
© Piero Demichelis
9
Struttura di un programma C
•
Tutti gli oggetti, con le loro caratteristiche, che
compongono il programma devono essere
preventivamente dichiarati.
• main
- è la parola chiave che indica il punto di “ingresso” del
programma quando viene eseguito dal S.O.;
-
il suo contenuto è delimitato da parentesi graffe { … }
© Piero Demichelis
10
Struttura di un programma C
• Parte dichiarativa locale:
- elenco degli oggetti che compongono il main ognuno
con le proprie caratteristiche.
• Parte esecutiva:
- sequenza di istruzioni, ovvero ciò che descriviamo con
il diagramma di flusso oppure con lo pseudocodice!
© Piero Demichelis
11
Struttura di un programma C
• Programma minimo:
main()
{
}
START
STOP
file prova.c
© Piero Demichelis
12
I dati
Definizione dei dati
•
In C, tutti i dati devono essere dichiarati e definiti
prima di essere usati!
•
Definizione di un dato:
1.
2.
3.
•
riserva spazio in memoria;
assegna un nome.
identifica gli operatori leciti su quel dato
Richiede l’indicazione di:
-
nome (identificatore);
tipo;
modalità di accesso (variabile/costante).
© Piero Demichelis
14
Tipi
• Il tipo definisce l'insieme dei valori che possono essere
assunti, la rappresentazione interna e l'insieme degli
operatori che possono agire su quel dato.
• Il linguaggio C richiede di definire il tipo dei dati e
possiede regole rigide per la loro manipolazione
(tipizzazione forte ). Permette inoltre al programmatore di
definire nuovi tipi astratti.
• Contemporaneamente permette di “vedere” gli oggetti
interni al calcolatore: i registri, la memoria, gli indirizzi
(puntatori), ecc.
© Piero Demichelis
15
Tipi base (primitivi)
• Sono quelli forniti direttamente dal C.
• Sono identificati da parole chiave!
-
char
int
float
double
caratteri ASCII;
interi (complemento a 2);
reali (floating point singola precisione);
reali (floating point doppia precisione).
• La dimensione precisa di questi tipi dipende
dall’architettura (non definita dal linguaggio).
-
•
char = 8 bit sempre
Attenzione: le parole chiave dei tipi base vanno scritte in
minuscolo!
© Piero Demichelis
16
char
• Il tipo char (character) definisce un carattere (attenzione:
un solo carattere!) espresso su 8 bit (1 byte) in codice
ASCII.
• I valori sono interpretati come numeri interi con segno su
8 bit (-128 ÷ +127).
• Un carattere deve essere indicato tra apici, così:
‘a’
© Piero Demichelis
17
int
• Il tipo int (integer) definisce i numeri interi con segno.
• La rappresentazione interna e l'intervallo dei valori assunti
dipende dal compilatore e dalla macchina usata.
• Generalmente si tratta del complemento a 2 e i valori
assunti sono compresi nell’intervallo -32768 ÷ 32767 su
16 bit oppure, per le macchine a 32 bit, nell’intervallo
-2.147.483.648 ÷ 2.147.483.647.
• Vanno indicati semplicemente così come siamo abituati a
scriverli sulla carta, col loro valore (senza il punto
decimale):
-2453
© Piero Demichelis
18
float e double
• Sia il tipo float che il tipo double sono rappresentazioni di
numeri reali (frazionari).
• Sono rappresentati secondo la notazione floating-point,
rispettivamente in singola (32 bit) e doppia (64 bit)
precisione.
• I valori assunti (rappresentabili) sono:
float
± 3.4E+38
(7 cifre decimali)
double
± 1.7E+308 (16 cifre decimali)
• La rappresentazione è normalmente riferita allo standard
IEEE P754.
© Piero Demichelis
19
float e double
• I valori di tipo float o double vanno indicati con il punto
decimale, ad esempio:
14.8743
• E’ ammessa anche una notazione simile alla notazione
scientifica con il carattere E al posto di 10, così:
0.148743E-02
• In alternativa, si può ancora scrivere il numero senza
punto decimale ma seguito dal suffisso F oppure f (ad
esempio, 10F, 10f e 10.0 sono equivalenti). Il compilatore
concepisce questi valori sempre come di tipo double.
© Piero Demichelis
20
Modificatori dei tipi base
• Sono previsti dei modificatori, identificati da parole
chiave, da premettere ai tipi base:
•
•
•
•
short
long
signed
unsigned
© Piero Demichelis
21
short / long
• Il qualificatore short si applica al tipo int e impone che la
rappresentazione degli interi sia su 16 bit (valori assunti:
-32768 ÷ 32767);
• il qualificatore long si applica sia al tipo int che al tipo
double;
- long int impone la rappresentazione degli interi su 32 bit (valori
assunti: -2.147.483.648 ÷ 2.147.483.647);
- long double forza la rappresentazione dei reali su 80 bit (±
1.7E+308 aumentando la precisione a 19 cifre decimali).
© Piero Demichelis
22
signed / unsigned
• I qualificatori signed e unsigned si applicano ai tipi char e int.
• signed è ridondante e serve solo a ricordare che un valore è inteso
con segno (ma per int e char è già così!);
• unsigned permette di estendere l'intervallo dei valori non-negativi.
- Il tipo unsigned char può assumere valori nell'intervallo 0 ÷ 255 e
il tipo unsigned int valori nell'intervallo 0 ÷ 65535.
• I qualificatori possono apparire da soli: nel qual caso si assume che
sia sottinteso il tipo int. E’ lecito, ad esempio, il tipo unsigned short
che viene interpretato come unsigned short int, ecc.
© Piero Demichelis
23
Altri valori interi
• Per un dato intero, i valori di variabili e costanti possono
anche essere espressi in esadecimale (specificati col
prefisso 0x oppure 0X, ad esempio, 0xFF = 25510) oppure
ottale (prefisso 0 (zero), ad esempio 0377 = 25510);
• per il formato long si usa il suffisso L oppure l (255L o
255l esprimono entrambi un long int con valore 25510);
• per l'unsigned si usa il suffisso U oppure u (255U e 255u
esprimono un unsigned int = 25510 ). I suffissi u ed l
possono apparire entrambi, con ovvio significato.
© Piero Demichelis
24
Valori speciali
• Il codice ASCII comprende alcuni caratteri non stampabili.
Per rappresentarli viene utilizzata la sequenza di escape
(backslash seguito da un altro carattere).
Sequenza
Carattere
Sequenza
Carattere
\a
allarme
\’
apostrofo
\b
backspace
\”
doppio apice
\f
pagina nuova
\\
backslash
\n
new line
\ddd
ottale
\r
return
\xddd
esadecimale
\t
tabulatore orizz.
\0
null
\v
tabulatore vert.
© Piero Demichelis
25
Tabella riassuntiva
Tipo
Intervallo dei valori
-128 ÷ 127
char, signed char
int, signed, signed int
-32768 ÷ 32.767
short, short int, signed short
-32768 ÷ 32.767
signed short, signed short int
-32768 ÷ 32.767
long, long int,
2.147.483.648 ÷ 2.147.483.648
signed long, signed long int
2.147.483.648 ÷ 2.147.483.648
unsigned, unsigned char, unsigned short
0 ÷ 255
unsigned int, unsigned short int
0 ÷ 65535
unsigned long, unsigned long int
0 ÷ 4.294.967.295
float
3.4E±38 (7 cifre)
double
1.7E±308 (16 cifre)
long double
1.7E±308 (20 cifre)
© Piero Demichelis
26
Direttive
• Il C prevede che nel programma, oltre alle istruzioni,
possano esserci anche delle direttive che devono essere
interpretate ed eseguite dal compilatore stesso
(inserzione di macro, compilazioni condizionate,
inclusione di altri sorgenti, ecc.);
• il compilatore C esegue una preelaborazione del
programma, detta Preprocessing, per riconoscere ed
eventualmente eseguire le direttive;
• le direttive si distinguono dalle istruzioni perché sono
inserite sempre in righe individuali e iniziano con il
carattere # seguito da una parola chiave; ad esempio:
#include
#define
© Piero Demichelis
27
Definizione di variabili
• Sintassi:
<tipo> <nome della variabile >;
• Più in generale (definizioni multiple):
<tipo> <lista dei nomi delle variabili >;
-
<nome> : l’identificatore che rappresenta il nome della
variabile;
<lista dei nomi delle variabili> : lista di identificatori
separati da , (virgola).
© Piero Demichelis
28
Definizione di variabili
• Esempi:
int x;
char ch;
long int x1 ,x2, x3;
double pi;
short int stipendio;
long y,z;
• Usiamo nomi significativi!
Esempi:
•
int RitenuteOperate, StipendioBase;
float OreLavorate;
Esempi errati:
float Ore Lavorate;
int Stip?base;
/* c’è uno spazio */
/* c’è un carattere speciale */
© Piero Demichelis
29
Definizione di variabili
• E’ possibile “inizializzare” una variabile, ovvero attribuirgli
un valore prima che venga utilizzata per la prima volta, in
fase di dichiarazione della stessa.
• Esempio:
int x = 24;
char ch = ‘m’;
double pi = 124.654;
© Piero Demichelis
30
Definizione di costanti
• Sintassi:
const <tipo> <nome della costante> = <valore>;
• Esempi:
const double pigreco = 3.14159;
const char separatore = ‘$’;
const float aliquota = 0.2;
• Convenzione:
-
Identificatori delle constanti tipicamente in MAIUSCOLO
(ma è una convenzione!).
const double PIGRECO = 3.14159;
© Piero Demichelis
31
La direttiva “define”
• E’ un'altra possibilità di definire valori costanti : si
introduce un identificatore come sinonimo di una
costante:
#define identificatore testo
• Deve comparire sempre in testa al programma, prima di
main(), e all'inizio della riga.
• E’ elaborata dal preprocessore del compilatore, che
sostituirà in tutto il programma, ovunque appare
l'identificatore, il testo di cui è sinonimo.
© Piero Demichelis
32
La direttiva “define”
• Non essendo un'istruzione non termina con il punto e
virgola.
Esempi:
#define
#define
#define
#define
PIGRECO 3.1415
DOMENICA 7
VERO 1
Carattere ‘p’
© Piero Demichelis
33
Stringa
• Definizione:
-
sequenza di caratteri terminata dal carattere NULL (‘\0’);
• non è un tipo di base del C.
• memorizzata in posizioni adiacenti di memoria.
• Formato:
“<sequenza di caratteri>“
-
Esempi:
• “Ciao!”
• “abcdefg\n”
© Piero Demichelis
34
INFORMATICA
Cenni sulla
Rappresentazione dei dati
Rappresentazione di
dati numerici
Sistemi numerici
• Si suddividono in:
- Non posizionali : quali ad esempio il sistema di numerazione
romano (i cui simboli sono: I, II, III, IV, V, X, L, C, D, M) oppure
quello egiziano
- Posizionali : quali ad esempio il sistema arabo (decimale) e il
sistema maya (ventesimale).
• Nei sistemi posizionali le operazioni aritmetiche risultano
molto agevoli mentre in quelli non posizionali sono
alquanto complicate.
© Piero Demichelis
37
Sistema posizionale a base fissa
• Nei sistemi numerici a base fissa, un numero N può
essere rappresentato in uno del seguenti modi:
N = dn-1; dn-2 ........ d1; d0; d-1 ........ d-m
N = dn-1· rn-1 + ..... + d0· r0 + d-1· r-1 + ..... + d-m· r-m
N 
n 1

i m
© Piero Demichelis
di  r
i
38
Sistemi numerici
• Proprietà di un sistema numerico a base fissa
- è a rango illimitato : ogni numero intero vi può essere
rappresentato;
- è a rappresentazione unica : ad ogni numero intero
corrisponde un solo insieme ordinato di cifre;
- è irridondante : ad ogni insieme ordinato di cifre
corrisponde un solo numero non rappresentato da altri
insiemi ordinati.
© Piero Demichelis
39
Sistema decimale
• r = 10
• cifre: { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }
- Esempio:
10110 = 1 × 102 + 0 × 101 + 1 × 100
= 100 + 0 + 1
= 10110
© Piero Demichelis
40
Sistema binario
•r=2
• cifre: { 0, 1 }
- Esempio:
1012 = 1 × 22 + 0 × 21 + 1 × 20
= 4 + 0 + 1 = 510
© Piero Demichelis
41
Sistema ottale
•r=8
• cifre: { 0, 1, 2, 3, 4, 5, 6, 7 }
- Esempio:
1018 = 1 × 82 + 0 × 81 + 1 × 80
= 64 + 0 + 1 = 6510
• molto utile per scrivere in modo compatto i numeri binari
(ad ogni 3 cifre binarie corrisponde una cifra ottale)
( 1 1 0 1 1 0 0 0 1) 2 =
( 6
6
1 )8
© Piero Demichelis
42
Sistema esadecimale
• r = 16
• cifre: { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F }
- Esempio:
101H = 1 × 162 + 0 × 161 + 1 × 160
= 256 + 0 + 1 = 25710
• anch’esso utile per scrivere in modo compatto i numeri
binari (ad ogni 4 cifre binarie corrisponde 1 cifra
esadecimale)
( 1 1 0 1 1 0 0 0 1) 2 =
(1
B
1 ) 16
© Piero Demichelis
43
Sistema base 5
•r=5
• cifre: { 0, 1, 2, 3, 4 }
- Esempio:
1015 = 1 × 52 + 0 × 51 + 1 × 50
= 25 + 0 + 1 = 2610
© Piero Demichelis
44
Sistema binario
• Caratteristiche
- su n cifre si rappresentano 2n numeri; ad esempio su 4 cifre:
0
1
10
11
100
101
110
111
...
...
...
...
...
...
...
...
0
1
2
3
4
5
6
7
1000
1001
1010
1011
1100
1101
1110
1111
...
...
...
...
...
...
...
...
8
9
10
11
12
13
14
15
- Prime 16 potenze del 2:
20
21
22
23
24
25
26
27
28
...
...
...
...
...
...
...
...
...
1
2
4
8
16
32
64
128
256
© Piero Demichelis
29
210
211
212
213
214
215
216
...
...
...
...
...
...
...
...
512
1024
2048
4096
8192
16384
32768
65536
45
Sistema binario
• La cifra binaria è detta bit
parola che deriva dall’unione di due elisioni:
binary digit
• I bit estremi di un numero binario si chiamano:
1 0 1 1 1 0 0 1 0 0
MSB
LSB
(Most Significant Bit)
(Least Significant Bit)
© Piero Demichelis
46
Limiti del sistema binario
• Poiché su n bit si rappresentano 2n numeri, per
rappresentare la stessa grandezza occorrono molte più
cifre rispetto al sistema numerico decimale.
bit
simboli
4
8
16
32
16
256
65,536
4,294,967,296
val. minimo val. massimo
0
0
0
0
© Piero Demichelis
15
255
65,535
4,294,967,295
47
Conversione da binario a decimale
• Si applica direttamente la definizione effettuando la
somma pesata delle cifre binarie:
1012 = 1 × 22 + 0 × 21 + 1 × 20
=
4
=
5
+
0
+
1
1101.12 = 1  23 + 1  22 + 0  21 + 1  20 + 1  2-1
=
8 + 4
+ 0
+ 1 + 0.5
= 13.510
© Piero Demichelis
48
Conversione da decimale a binario
N = dn-1· rn-1 + ..... + d0· r0 + d-1· r-1 + ..... + d-m· r-m
• Consideriamo la sola parte intera e riscriviamo il numero binario nel
modo seguente:
N = d0 + 2 · (d1 + 2 · (d2 + ...... + dn-1))
• Si può osservare che dividendo N per la base 2, si ottiene un
quoziente (d1 + r · (d2 + ...... + dn-1)) e un resto d0, che costituisce
proprio la cifra meno significativa del numero nella base 2.
• Dividendo successivamente il quoziente per la base 2 si trova ancora
un quoziente e un resto d1, che è la cifra di peso uno cercata, e così
via.
© Piero Demichelis
49
Esempio
• Esempio:
13
1
d0
6
0
d1
3
1
d2
1
1
d3
0
quozienti
resti
1310 = 11012
© Piero Demichelis
50
Conversione da decimale a binario
• Dato un numero frazionario:
N = a-1 2-1 + a-2 2-2 + ...... + a-m 2-m
• moltiplicando N per la base 2, si ricava come parte intera
la cifra a-1, cioè la prima cifra binaria.
• Eliminata questa parte intera, moltiplicando quanto resta
ancora per 2, si ricava come parte intera a-2, ecc.
• Le parti intere, scritte nel medesimo ordine con cui sono
state ricavate, rappresentano il numero frazionario binario
cercato.
© Piero Demichelis
51
Esempio
• Regola: si moltiplica per due la parte frazionaria e si
prende la cifra intera prodotta dal risultato proseguendo
fino alla precisione richiesta.
- Esempio:
0.34 x
2
0.3410 = 0.01012
0.68 x
2
1.36 x
2
0.72 x
2
1.44 ecc.
© Piero Demichelis
13.3410 = 1101.01012
52
Conversioni tra sistemi in base qualsiasi
• E’ ovvio che le regole di conversione decimale-binario
sono del tutto generali e valgono qualsiasi siano i sistemi
numerici coinvolti.
• Ad esempio per convertire il numero decimale 365 in
base 7 si divide per 7:
365
1
52
3
7
0
1
1
0
36510 = 10317
© Piero Demichelis
53
Operazioni aritmetiche
• Le operazioni aritmetiche in un qualsiasi sistema
numerico si possono eseguire nello stesso identico modo
che conosciamo così bene per il sistema numerico
decimale.
• L’avvertenza è solo quella di costruire la “tabellina”
opportuna per quel particolare sistema numerico: si
ricordi che la tabellina per il sistema numerico decimale
ce la siamo studiata a memoria sin dall’infanzia!!!!
• Il nostro interesse è però particolarmente concentrato sul
sistema numerico binario e sono proprio le operazioni
aritmetiche in binario che affronteremo ora.
© Piero Demichelis
54
Somma in binario
• Regole
0 +
0 +
1 +
1 +
base:
0 =
1 =
0 =
1 =
0
1
1
0
con riporto (carry) di 1
• Si effettuano le somme parziali tra i bit dello stesso peso,
propagando gli eventuali riporti:
1 1
0 1 1 0 +
0 1 1 1 =
1 1 0 1
6+
7=
13
© Piero Demichelis
55
Somma completa
• La somma completa (full addition) tiene conto del riporto
per cui si sommano due bit ed un carry ottenendo come
risultato un bit di somma e un bit di riporto
A
B
Carry
S
Rip
0
0
0
0
0
0
1
0
1
0
1
0
0
1
0
1
1
0
0
1
0
0
1
1
0
0
1
1
0
1
1
0
1
0
1
1
1
1
1
1
© Piero Demichelis
56
Sottrazione in binario
• Regole base:
0–0=0
0–1=1
1–0=1
1–1=0
con prestito (borrow) di 1
• Si eseguono le sottrazioni bit a bit tenendo conto dei
prestiti:
1
1 1 0 0 1 0 1 0 =
0 0 1 0
12 10 =
2
© Piero Demichelis
57
Sottrazione completa
• Analogamente alla somma, è possibile definire la
sottrazione completa (sottrazione tra due bit ed un
borrow )
A
B
Borrow
S
Prest
0
0
0
0
0
0
1
0
1
1
1
0
0
1
0
1
1
0
0
0
0
0
1
1
1
0
1
1
0
1
1
0
1
0
0
1
1
1
1
1
© Piero Demichelis
58
Moltiplicazione in binario
• Il prodotto tra due numeri binari si può calcolare con la
tecnica già nota per i numeri in base 10, detta della
somma e scorrimento.
- Esempio:
1011 x
101 =
1011
0000
1011
110111
11 x
5=
55
• Nella pratica si usano accorgimenti particolari basati
sull’operazione di scorrimento (shift ).
© Piero Demichelis
59
Divisione in binario
• Come per le altre operazioni applichiamo le stesse regole
che usiamo col sistema decimale:
- Esempio:
1
0
1
0
0101
0
01
11
100
11
0011
11
00
11
0111
© Piero Demichelis
21 / 3 = 7
60
L’operazione di shift
• Equivale ad una moltiplicazione o divisione per la base.
• Consiste nel “far scorrere ” i bit (a sinistra o a destra)
inserendo opportuni valori nei posti lasciati liberi.
• In decimale equivale a moltiplicare (shift a sinistra) o
dividere (shift a destra) per 10.
• In binario equivale a moltiplicare (shift a sinistra) o
dividere (shift a destra) per 2.
© Piero Demichelis
61
Shift a sinistra
• Si inserisce come LSB un bit a zero
• Equivale ad una moltiplicazione per due
0011 « 1 = 0110 ( 3  2 = 6 )
0011 « 2 = 1100 ( 3  22 = 12 )
0011 « 3 = 11000 ( 3  23 = 24 )
0
0
0
1
1
0
1
1
0
« 1 (shift a sinistra di 1 posizione)
© Piero Demichelis
62
Shift a destra
• Si inserisce come MSB un bit a zero
• Equivale ad una divisione per due
0110 » 1 = 0011
0110 » 2 = 0001
(6:2=3)
( 6 : 4 = 1 ) troncamento!
0
1
1
0
0
1
1
0
» 1 (shift a destra di 1 posizione)
0
© Piero Demichelis
63
Moltiplicazioni
• Una qualsiasi moltiplicazione tra due numeri può essere
trasformata in una serie di shift e di somme, operazioni
che vengono eseguite molto velocemente dai
microprocessori.
• Ad esempio il prodotto 14 x 13 diventa:
14 · 13 = 14 · (8 + 4 + 1) = 14 · 8 + 14 · 4 + 14 · 1
1410 = 11102
1111
1110000 +
1110 « 3 + 1110 « 2 + 1110
111000 +
1110 =
10110110
© Piero Demichelis
64
Limiti della rappresentazione
• Quando scriviamo sulla carta non ci preoccupiamo quasi
mai della grandezza dei numeri (a meno di particolari
necessità).
• Nelle macchine numeriche un numero deve essere
rappresentato in un particolare dispositivo elettronico
interno che si chiama registro ed è paragonabile ad una
cella di memoria.
• Caratteristica fondamentale di questo dispositivo è la sua
dimensione (numero di bit) stabilita in sede di progetto:
ovvero in un elaboratore potremo rappresentare solo una
quantità limitata di numeri.
© Piero Demichelis
65
Limiti della rappresentazione
• Ad esempio se il nostro contenitore (registro) è lungo 5
bit:
potremo rappresentare solamente i numeri binari
compresi tra 0
0
0
0
0
0
e 31
1
1
1
1
1
• Inoltre dovremo in qualche modo introdurre il segno dei
numeri!
© Piero Demichelis
66
I numeri con segno
• Oltre al problema relativo al valore del numero bisogna
trovare il modo di rappresentare il segno.
• Il segno dei numeri può essere solo di due tipi:
positivo ( + )
negativo ( - )
• Sembrerebbe quindi facile rappresentarlo in binario,
tuttavia la soluzione più semplice (1 bit riservato al
segno) non è sempre conveniente.
• Per tener conto del segno anziché il sistema numerico
binario si utilizzano dei codici binari che hanno tuttavia
come base, ovviamente, il sistema numerico binario.
© Piero Demichelis
67
Modulo e segno
• Su N bit, un bit è destinato al segno (in binario 0 = +,
1 = -) e N-1 bit al valore assoluto (anche detto modulo)
S modulo
• E’ un codice che ricorda molto il nostro modo di
rappresentare i numeri sulla carta.
• Presenta però gravi svantaggi dovuti alla doppia
rappresentazione dello zero (esistono e sono leciti infatti
sia + 0, che - 0) e alla complessità delle operazioni
aritmetiche.
© Piero Demichelis
68
Modulo e segno
• Esempi - usando una codifica su quattro bit:
+ 310
 0011MS
 310
 1011MS
• Si ha una doppia rappresentazione dello zero:
0000MS + 010
1000MS  010
• In generale su N bit sono rappresentabili i valori:
- ( 2N-1 - 1 ) x + ( 2N-1 - 1 )
8 bit
16 bit
=>
=>
[ -127 ÷ +127 ]
[ -32.767 ÷ +32.767 ]
© Piero Demichelis
69
Complemento a 1
• Considerando numeri binari di n bit, si definisce
complemento a uno di un numero A la quantità:
A = 2n - 1 – A
• Viene anche detto semplicemente complemento.
• Regola pratica:
il complemento a uno di un numero binario A si ottiene cambiando il
valore di tutti i suoi bit (complementando ogni bit)
- Esempio:
A = 1011  A 0100
© Piero Demichelis
70
Complemento a 2
• Considerando numeri binari di n bit, si definisce
complemento a due di un numero A la quantità:
A = 2n – A
Regola pratica:
il complemento a due di un numero binario A si ottiene sommando
uno al suo complemento (a uno)
- Esempio:
A = 1011

A = 0100  A 0101
© Piero Demichelis
71
Complemento a 2
• E’ usato per rappresentare numeri relativi:
(A0)
(A<0)
0 A2 (= AMS)
complemento a 2 di A
• In questo modo l’MSB indica il segno: 0 = +, 1 = • Regola alternativa per la determimazione del
complemento a due:
si parte da destra, si lasciano inalterati tutti gli zeri fino al primo uno
che si lascia inalterato, si complementano tutti gli altri bit
• Esempio:
A = 001101001000;
A = 110010111000
© Piero Demichelis
72
Complemento a 2
• Esempio - usando una codifica su 4 bit:
+ 310  0 ( 32 )
- 310  - 0011


0011CA2
1100 + 1  1101CA2
• In generale su N bit sono rappresentabili i valori:
- ( 2N-1 ) x + ( 2N-1 - 1 )
8 bit
16 bit
=>
=>
[ -128 ÷ +127 ]
[ -32.768 ÷ +32.767 ]
© Piero Demichelis
73
Somma e sottrazione in complemento a 2
• La somma si effettua direttamente, senza badare ai segni
degli operandi, come fossero due normali numeri binari.
• La sottrazione si effettua sommando al minuendo il
complemento a 2 del sottraendo:
A – B  A + (- B)
ovvero: A + B
• Esempio:
01010 +
10100 =
11110
10 +
- 12 =
- 2
© Piero Demichelis
74
Overflow
• Si usa il termine overflow per indicare l’errore che si
verifica in un sistema di calcolo automatico quando il
risultato di un’operazione non è rappresentabile con la
medesima codifica e numero di bit degli operandi.
• Nella somma in binario puro si ha overflow quando si
opera con un numero fisso di bit e si genera un riporto
(carry) sul bit più significativo (MSB, quello più a sinistra).
Esempio: somma tra numeri di 4 bit in binario puro
0101 +
1110 =
overflow!  1 0 0 1 1
© Piero Demichelis
75
Overflow in complemento a 2
1. Operandi con segno discorde:
- non si può mai verificare overflow!!!!!
2. Operandi con segno concorde:
- c’è overflow quando il risultato ha segno discorde da quello dei
due operandi
3. In ogni caso, si trascura sempre il carry (riporto) oltre il MSB
Esempi:
0101 +
0100 =
1001
overflow!
1110+
1101=
11011=
carry, risultato OK
© Piero Demichelis
76
Fixed-point
• Si usa un numero fisso di bit per la parte intera e per
quella frazionaria (e non si rappresenta la virgola!)
• Ad esempio (4 + 4 bit, binario puro):
15.9375 =
0.0625 =
11111111
00000001
virgola sottintesa
© Piero Demichelis
77
Fixed-point
• Vantaggi:
- gli operandi sono allineati per cui le operazioni aritmetiche
risultano facili ed immediate;
- la precisione assoluta è fissa
• Svantaggi:
- l’intervallo di valori rappresentati è assai modesto
- la precisione dei numeri frazionari rappresentati molto scarsa
• Utilizzo tipico:
- DSP (Digital Signal Processor)
- Sistemi digitali per applicazioni specifiche (special-purpose)
- Numeri interi nei calcolatori
© Piero Demichelis
78
Rappresentazione di numeri interi
• A causa dell’estrema semplicità che presentano le
operazioni aritmetiche in complemento a 2, in tutte le
macchine numeriche i numeri interi vengono rappresentati
in questo codice.
• Il numero di bit utilizzati dipende dalla macchina: si tratta
generalmente di 16 bit (interi corti) o 32 bit (interi lunghi).
• La rappresentazione è nota col nome di fixed-point e il
punto frazionario è supposto all’estrema destra della
sequenza di bit (parte frazionaria nulla).
© Piero Demichelis
79
Rappresentazione di numeri reali
• Le rappresentazioni fin qui considerate hanno il pregio di
rappresentare esattamente i numeri (almeno quelli interi)
ma richiedono un numero di bit esorbitante quando il
numero da rappresentare ha valore elevato.
• La rappresentazione dei numeri frazionari che deriva dai
codici precedenti, ovvero in fixed point, a causa delle forti
approssimazioni che impone è usata raramente.
• Generalmente viene utilizzato un apposito codice noto
come floating point che consente di rappresentare in un
numero limitato di bit grandezze di qualsiasi valore anche
se condizionate da approssimazioni più o meno elevate.
© Piero Demichelis
80
Floating-point
• E’ basata sul formato esponenziale (notazione scientifica)
N = mantissa  base esponente
- Ricorda le notazioni:
standard
scientifico
3.5 × 104
0.35 × 105
3.5E+4
0.35E+5
• Nei sistemi di elaborazione
- Base = 2
- Mantissa ed esponente sono rappresentati in binario
© Piero Demichelis
81
Floating-point
• Vantaggi:
- grande intervallo di valori rappresentabili
- errore relativo fisso
• Svantaggi:
- operandi non allineati per cui le operazioni aritmetiche risultano
molto complesse
- errore assoluto variabile e dipendente dal valore del numero
• E’ la rappresentazione utilizzata da tutti i calcolatori
elettronici per rappresentare i numeri frazionari ed è stata
standardizzata dall’IEEE.
© Piero Demichelis
82
Formato IEEE-P754
• Standard IEEE per il floating-point:
- Rappresentazione binaria di
mantissa
esponente
segno
• Singola precisione: 32 bit (float)
segno
1 bit
esponente
8 bit
mantissa
precisione: circa 7 cifre decimali
23 bit
• Doppia precisione: 64 bit (double)
segno
esponente
mantissa
1 bit
11 bit
52 bit
precisione: circa 17 cifre decimali
© Piero Demichelis
83
Overflow e Underflow
• A causa della precisione variabile è possibile avere errori
di rappresentazione:
- numeri troppo grandi: overflow
- numeri troppo piccoli: underflow
Esempio: IEEE P754
underflow
-1038
-10-38 0 10-38
1038
overflow
© Piero Demichelis
84
Rappresentazioni di dati non numerici
• Qualunque insieme finito di oggetti può essere codificato
tramite valori numerici associando ad ogni oggetto un
codice (ad esempio un numero intero).
• Nel sistema numerico binario per rappresentare K oggetti
distinti occorre un numero minimo di bit pari a:
N = log2 K 
© Piero Demichelis
85
Caratteri
• E’ sicuramente il tipo di informazione più scambiata:
occorre pertanto una codifica standard.
- la più usata fa riferimento al codice ASCII (American Standard
Code for Information Interchange)
- in passato era molto diffuso il codice EBCDIC (Extended BCD
Interchange Code)
- codice UNICODE
© Piero Demichelis
86
Codice ASCII
• E’ usato anche nelle telecomunicazioni.
• Usa 8 bit per rappresentare:
- i 52 caratteri alfabetici (a ÷ z , A ÷ Z)
- le 10 cifre (0 ÷ 9)
- i segni di interpunzione (,;:!?&%=+-/ ecc.)
- un gruppo di caratteri di controllo tra cui:
CR
LF,NL
FF,NP
HT
VT
NUL
BEL
EOT
(
(
(
(
(
(
(
(
13 )
10 )
12 )
9 )
11 )
0 )
7 )
4 )
Carriage Return
New Line, Line Feed
New Page, Form Feed
Horizontal Tab
Vertical Tab
Null
Bell
End-Of-Transmission
© Piero Demichelis
87
Codice ASCII
• Ad esempio per rappresentare il messaggio “Auguri a
tutti!” è necessaria la seguente sequenza:
01000001
01110101
01100111
01110101
01110010
01101001
00100000
01100001
A
u
g
u
r
i
spazio
a
00100000
01110100
01110101
01110100
01110100
01101001
00100001
© Piero Demichelis
spazio
t
u
t
t
i
!
88
Fine Rappresentazione dei dati
Visibilità delle variabili
• Ogni variabile è definita all’interno di un preciso ambiente
di visibilità (scope).
• Variabili globali
- definite all’esterno al main sono visibili da tutti i
moduli.
• Variabili locali
- definite all’interno del main (sono visibili solo all’interno
del main);
- più in generale, definite all’interno di un blocco (sono
visibili solo all’interno del blocco).
© Piero Demichelis
90
Struttura a blocchi
• In C è possibile aggregare gruppi di istruzioni in blocchi
racchiudendole tra parentesi graffe;
• significato: delimitazione di un ambiente di visibilità di
“oggetti” (variabili).
• Esempio:
{
int a=2;
int b;
a e b sono definite
solo all’interno del blocco!
b=2*a;
}
© Piero Demichelis
91
Visibilità delle variabili - Esempio
int n;
double x;
main()
{
int a,b,c;
double y;
{
int d;
double z;
}
}
-
n, x:
visibili in tutto il file
a, b, c ,y: visibili in tutto il main
d, z:
visibili solo nel blocco
© Piero Demichelis
92
Le istruzioni
Assegnazioni
• Sintassi:
<variabile> = <espressione>;
• Non è un’uguaglianza!
-
Significato: il risultato di <espressione> viene assegnato a
<variabile>;
<variabile> e <espressione> devono essere “compatibili” (ovvero
dello stesso tipo);
<variabile> deve essere stata precedentemente definita!
• Esempio:
int x;
float y;
x = 3;
y = -323.9498;
© Piero Demichelis
94
Assegnazioni
• In realtà l’assegnazione (o assegnamento) non è
un’istruzione (come accade in tutti gli altri linguaggi);
• il simbolo = è un operatore che assegna alla variabile che
si trova a sinistra il valore calcolato sull’espressione di
destra;
• nel caso più semplice l’espressione di destra è un
semplice valore.
• Sono pertanto lecite assegnazioni “multiple”:
<var1> = <var2> = <espressione>;
© Piero Demichelis
95
Istruzioni di I/O
• Per ora consideriamo solo l’I/O interattivo, quello cioè che
si realizza con tastiera e monitor.
• Sono disponibili diverse forme in base al tipo di
informazione letta o scritta:
-
I/O formattato
I/O di caratteri
I/O “per righe”
© Piero Demichelis
96
I/O formattato
• Standard output (scrittura su monitor)
istruzione printf
• Standard input (lettura da tastiera)
istruzione scanf
• L’utilizzo di queste funzioni richiede l’inserimento di una
direttiva
#include <stdio.h>
all’inizio del file sorgente il cui significato è: “includi il file
stdio.h ”
© Piero Demichelis
97
L’istruzione printf
• Visualizza sul monitor.
• La printf opera utilizzando una stringa, detta <format>,
nella quale si devono inserire i comandi che descrivono
come devono apparire i dati sul monitor.
• Al <format> deve seguire la lista di variabili che si vuol
visualizzare.
• Sintassi:
printf (<format>,<arg1>,...,<argn>);
© Piero Demichelis
98
L’istruzione printf
•
<format>: stringa che determina il formato di
visualizzazione per ognuno dei vari argomenti.
•
<arg1>,...,<argn>: lista degli argomenti da visualizzare.
Gli argomenti (opzionali) possono essere costanti,
variabili o espressioni.
•
Se non ci sono argomenti (come quando si vuole
visualizzare solo un messaggio) la funzione trasferisce sul
video il testo della stringa di <format> e il cursore si
posiziona subito dopo l'ultimo carattere.
© Piero Demichelis
99
L’istruzione printf
• Affinché il cursore vada a capo, occorre inserire nella
stringa di format il carattere new-line (\n).
• Esempio:
#include <stdio.h>
main()
{
printf ("Stampa di una riga\n");
printf ("Seconda riga\n");
}
© Piero Demichelis
100
Specificatori di formato
• Generalmente nel format sono indicati gli specificatori di
formato per le variabili della lista. I principali specificatori
di formato sono:
-
%d o %i per il tipo int, stampa in notazione decimale;
%o per il tipo int, stampa in ottale senza segno;
%x per il tipo int, stampa in esadecimale senza segno;
%u per il tipo int, stampa in decimale senza segno;
%c per il tipo char, stampa un carattere;
%f per il tipo float, stampa nella notazione virgola mobile nel
formato -d.dddddd (6 cifre dopo la virgola);
- %e o %E per il tipo float, stampa nella notazione virgola mobile
nel formato esponenziale -d.dddddde(E)±dd;
- %s per le sequenze di caratteri (stringhe).
© Piero Demichelis
101
Specificatori di formato
• Gli specificatori di formato sono costituiti dal carattere %
seguito da un altro carattere che indica il formato da utilizzare
per la stampa dell'argomento corrispondente (carattere,
numero intero o reale, stringa, ecc.).
• Quando incontra il primo specificatore di formato il C preleva il
primo argomento, effettua la conversione dal formato interno
del dato ad una sequenza di caratteri ASCII seguendo le
indicazioni del descrittore ed esegue infine l'ouput dei caratteri
sul video.
• Prosegue poi con la stringa del format, ripetendo le azioni
prima descritte per ogni specificatore incontrato e così fino ad
esaurire l'intero format: il numero di specificatori di formato
deve essere quindi pari al numero di argomenti.
© Piero Demichelis
102
Esempio
• L’associazione tra variabili e specificatori di formato è di
tipo ordinale: 1° specificatore
1ª variabile;
2° specificatore
2ª variabile, ecc.
• Esempi:
int x = 2;
float z = 0.5;
Char c = ‘a’;
printf (“%d %f %c\n”, x, z, c);
2 0.500000 a
_
printf (“%f***%c***%d\n”, z, c, x);
0.500000***a***2
_
© Piero Demichelis
103
Specificatori di formato
• Tra il carattere % e quello di specificazione può esserci
uno o più elementi aggiuntivi:
- un intero, che fissa la larghezza minima (numero di caratteri) del
campo su cui il dato è stampato;
- un punto seguito da un intero, che stabilisce la precisione con cui
visualizzare il dato (numero di cifre frazionarie);
- uno di questi modificatori:
• h (per indicare che si tratta di un tipo short),
• l (per indicare che si tratta di un tipo long),
• L (per indicare che si tratta di un tipo long double).
© Piero Demichelis
104
Esempio (printf)
#include <stdio.h>
int a=-57, b=2, c=450, d=33;
float e=1.22E7, f=-0.1234567, g=98765.4321, h=1.0;
main()
{
printf ("a=%4d b=%3d c=%8d d=%1d\n", a, b, c, d);
printf (“e=%9.3f f=%9.3f g=%9.3f h=%9.3f", e, f, g, h);
}
• Sul video apparirà:
a= -57 b= 2 c=
450 d=33
e=12200000.000 f= -0.123 g=98765.432 h=
© Piero Demichelis
1.00
105
L’istruzione scanf
• Permette la lettura di dati da tastiera.
• La scanf, come la printf, opera utilizzando un format, cioè
un descrittore del formato che devono avere i dati in
ingresso e accetta vari tipi di argomenti: interi, reali,
caratteri, stringhe, ecc.
• Sintassi:
scanf (<format>,<arg1>,...,<argn>);
© Piero Demichelis
106
L’istruzione scanf
•
<format> : è una stringa di caratteri dove compaiono
•
<arg1>,...,<argn> : possono essere solo variabili il cui
solo specificatori di formato (è bene evitare di inserire
degli spazi tra gli specificatori), gli stessi utilizzati per la
printf;
nome deve essere preceduto dal carattere &.
ATTENZIONE!!!MPORTANTE
i nomi delle variabili vanno sempre precedute dall’operatore &
che indica l’indirizzo della variabile
• Esempio:
int x;
float z;
scanf(“%d%f“, &x, &z);
© Piero Demichelis
107
L’istruzione scanf
• Per comprendere il funzionamento della scanf si
immagini che, man mano che vengono introdotti i
caratteri dalla tastiera, il codice di ognuno venga
accodato in un contenitore (un flusso).
• Si possono pensare così a delle sequenze di caratteri sulle
quali il programma in esecuzione dispone di un cursore.
• Le modalità con cui opera la scanf sono alquanto
complesse e dipendono dagli specificatori che compaiono
nel <format>.
© Piero Demichelis
108
L’istruzione scanf
• Quando deve leggere un numero intero o reale, il cursore
avanza fino al primo carattere diverso da spazio.
• Vengono poi letti tutti i caratteri successivi (cifre) fino a
raggiungere un carattere di spazio oppure un delimitatore
di riga (comunque un carattere non numerico), sul quale
il cursore si ferma.
• Se la sequenza di caratteri così isolata è corretta, viene
convertita nella rappresentazione interna e il valore è
attribuito alla variabile, altrimenti le operazioni della scanf
si bloccano.
© Piero Demichelis
109
L’istruzione scanf
• Nel caso che la stringa contenga più di un descrittore, per
ognuno viene attivato l'opportuno meccanismo di lettura
e conversione: affinché la scanf si comporti
correttamente ci devono essere tanti descrittori quanti
argomenti.
• Tra un numero e il successivo possono essere inseriti
quanti caratteri di spazio si desidera: vengono
automaticamente ignorati (come ce ne fosse uno solo!).
• Se invece si leggono dati di tipo char viene letto un solo
carattere, quello su cui è posizionato il cursore, il quale
avanza di una sola posizione!
© Piero Demichelis
110
L’istruzione scanf
• Per i dati più comuni (numeri e stringhe di caratteri) i
valori devono essere introdotti separandoli tra loro da
almeno un separatore (spazio, invio, ecc.).
x
• Lo specificatore %*
provoca il salto della prossima
conversione: viene effettuata la lettura ma il valore non
viene assegnato all'argomento.
• Lo specificatore %* può risultare utile in pratica solo nel
caso di lettura di dati da file (come si vedrà più avanti).
© Piero Demichelis
111
Esempio
#include <stdio.h>
int dato1, dato2, dato3;
main()
{
printf(“\nIntroduci tre numeri interi: ");
scanf("%d%d%d", &dato1, &dato2, &dato3);
}
© Piero Demichelis
112
L’istruzione scanf
•
I tre dati interi (da tastiera) possono essere introdotti
indifferentemente separandoli tra loro con un semplice
spazio oppure con il tasto di invio oppure ancora con il
carattere tab, ecc.
•
Ad esempio sono lecite ed equivalenti:
14 674 99000
14
(su una sola riga)
674 99000
14
674
99000
(su una sola riga)
(su tre righe distinte)
© Piero Demichelis
113
I/O
• scanf e printf non sono le uniche possibilità che abbiamo
per introdurre o visualizzare dati.
• Esistono numerose altre istruzioni di input sia per la
lettura da tastiera, sia per leggere dati da altri periferici
(come, ad es., il disco).
• Sono naturalmente diponibili le rispettive istruzioni di
output.
• Il loro uso è però più complesso e verrà proposto più
avanti.
© Piero Demichelis
114
Espressioni
• Sono combinazioni di variabili e operatori.
• Esistono varie categorie di operatori, applicabili a tipi di
dati diversi:
-
operatori aritmetici;
operatori relazionali;
operatori logici;
operatori sui bit:
ecc.
© Piero Demichelis
115
Operatori aritmetici
• Quattro operatori comuni a tutti (numeri reali e interi):
+
-
*
/
• Per i numeri interi, esiste anche l’operatore % che ritorna il resto
della divisione intera.
• Stesse regole di precedenza dell’aritmetica ordinaria:
- ( *, / ) > ( + , - )
- le parentesi tonde alterano la gerarchia.
• Esempio:
int x = 5, y = 2, q, r;
q = x / y;
/*
(q = 2, variabili intere! troncamento) */
r = x % y;
/*
(r = 1) , resto di 5 / 2
q = x + (y * (x – r));
/*
5 + (2 * (5 – 1)) = 13
© Piero Demichelis
*/
*/
116
Operatori aritmetici: esempi
const double ENEPER = 2.718281;
/*
sezione variabili
int dato, divintero;
double risul, inizio;
main()
{
dato = 12 * 3 - 4 * 5;
dato = dato + 1;
divintero = dato % 10;
inizio = dato;
risul = inizio / ENEPER;
}
/*
/*
/*
*/
equivale a (12*3)-(4*5) = 16 */
aggiunge 1 a dato: dato = 17
*/
/*
resto di 17 / 10 (= 7)
*/
conversione di tipo
*/
/*
divisione tra numeri reali
*/
© Piero Demichelis
117
Conversione forzata di tipo (casting)
• Si può forzare la conversione di tipo anteponendo al dato
che si vuole convertire il tipo posto tra parentesi, secondo
questo schema:
(tipo) espressione
• Esempio:
int dato_1, dato_2;
float dato_real;
dato_real = (float)dato_1 / (float)dato_2;
• L'effetto della conversione mediante l'operazione cast è
limitato all'espressione in cui appare: il dato su cui opera
resta immutato.
© Piero Demichelis
118
Operatori di assegnamento composti
• E’ possibile combinare l’istruzione di assegnazione con gli
operatori aritmetici.
• Sintassi:
<variabile> <operatore>= <espressione>;
• Operatori:
+=
-
-=
*=
/=
%=
Significato: assegnazione + operazione.
• Esempi:
x += 5;
y -= x;
/* equivalente a x = x + 5 */
/* equivalente a y = y – x */
© Piero Demichelis
119
Operatori di incremento e decremento
• Per le assegnazioni composte più comuni sono previsti
degli operatori espliciti:
++
--
• Significato:
++
--


+=1
-=1
• Esempi:
x++;
valore--;
/* equivale a x = x + 1 */
/* equivale a valore = valore – 1 */
© Piero Demichelis
120
Operatori di incremento e decremento
• Possono essere utilizzati sia in notazione prefissa che in
notazione postfissa
• Prefissa: la variabile viene modificata prima di essere
utilizzata nell’espressione
• Postfissa: la variabile viene modificata solo dopo averla
utilizzata nell’espressione
• Esempio: assumendo x = 4:
- Se si esegue y = x++, si otterrà come risultato x = 5 e y = 4;
- Se si esegue y = ++x, si otterrà come risultato x = 5 e y = 5;
© Piero Demichelis
121
Operatori relazionali
• Operano su quantità numeriche o di tipo char e
forniscono un risultato logico o “booleano”.
<
<=
>
>=
==
!=
• Il risultato è sempre di tipo int:
-
risultato = 0
significa
FALSO
-
risultato  0
significa
VERO
• In C (C89) non esiste un tipo logico o “booleano” !
© Piero Demichelis
122
Esempio
main()
{
float dato1, dato2;
int inter1, inter2;
short int switch1, flag;
dato1 = 10.5;
dato2 = 3.7;
inter1 = 2;
inter2 = 1;
switch1 = dato1 < (dato2 * 0.5);
/*
switch1 = 10.5 < 1.85 = FALSO (= 0) */
flag = inter1 != inter2;
/*
flag = VERO
( 0) */
}
© Piero Demichelis
123
Operatori logici
• Operano su espressioni “booleane” e forniscono un
risultato logico o “booleano”.
!
NOT
&&
||
AND
OR
• Equivalenti agli operatori Booleani di base.
-
Stesse regole di precedenza
• NOT > AND > OR
• Esempi:
-
(x > 0) && (x < 10)
(x1 > x2) || (x1 == 3)
© Piero Demichelis
(x compreso tra 1 e 9)
124
Esempio
#define FALSO 0
#define VERO 1
main()
{
int dato1, dato2;
short int test, flag, condiz;
dato1 = 5;
dato2 = 3;
flag = VERO;
test = !flag;
/*
test = NOT flag cioè FALSO (= 0) */
condiz = (dato1 >= dato2) && test;
/*condiz =
VERO
AND FALSO cioè FALSO (= 0) */
}
© Piero Demichelis
125
Operatori di manipolazione dei bit
• Il C possiede una serie di operatori che possono agire
direttamente sui bit delle variabili e costanti di tipo intero
o carattere, dichiarate nel programma.
• Si tratta di 4 operatori derivati direttamente dalle
operazioni booleane di base e di altri 2 che eseguono
l’operazione di shift (destra e sinistra) di un certo numero
di bit.
• I primi si prestano a “mascherare” o “commutare” i bit, i
secondi possono essere utili nelle operazioni di divisione e
moltiplicazione per 2.
• Tranne uno, sono tutti operatori binari, che agiscono cioè
su due espressioni.
© Piero Demichelis
126
Richiamo: operatori Booleani
A
0
1
0
0
0
1
0
1
B
A
0
1
0
0
1
1
1
1
B
A OR B
A AND B
A
0
1
1
0
A
0
1
0
0
1
1
1
0
B
A XOR B
NOT A
© Piero Demichelis
127
Operatori su bit
• Operatori di manipolazione dei bit:
Operazione
Operatore
tipo
AND bit a bit
&
Binario
OR bit a bit
|
Binario
XOR bit a bit
^
Binario
NOT bit a bit
(complemento a 1)
~
Unario
Shift a sinistra
<<
Binario
Shift a destra
>>
Binario
© Piero Demichelis
128
Operatori su bit: esempi
unsigned char z, x = 3, y = 13;
x
0
0
0
0
0
0
1
1
y
0
0
0
0
1
1
0
1
z = x & y
AND
z = ~ x
NOT
x
0
0
0
0
0
0
1
1
y
z
0
0
0
0
1
1
0
1
0
0
0
0
0
0
0
1
x
0
0
0
0
0
0
1
1
z
1
1
1
1
1
1
0
0
© Piero Demichelis
129
Operatori su bit: esempi
z = x | y
OR
z = x ^ y
EXOR
0
0
0
0
0
0
1
1
0
0
0
0
1
1
0
1
0
0
0
0
1
1
1
1
0
0
0
0
0
0
1
1
0
0
0
0
1
1
0
1
0
0
0
0
1
1
1
0
NOTA: Il significato decimale non è rilevante!
© Piero Demichelis
130
Operatori di shift
• Equivalenti alla divisione/moltiplicazione per le potenze di
2.
• Sintassi:
<operando>
<operando>
>>
<<
<num. posizioni>
<num. posizioni>
• Significato: fai scorrere <operando> a destra/sinistra di
un numero di bit pari a <num. posizioni>
• Sia <operando> che <num. posizioni> devono essere
valori interi.
© Piero Demichelis
131
Operatori di shift
• I due operatori operano diversamente a seconda del tipo
numerico
• Dati unsigned: equivale allo shift logico;
- << inserisce degli ‘0’ nelle posizioni meno significative
- >> inserisce degli ‘0’ nelle posizioni più significative
• Dati con segno: equivale allo shift aritmetico;
- << aggiunge degli ‘0’ nelle posizioni meno significative, e
mantiene inalterato il bit più significativo (segno)
- >> inserisce un valore uguale al bit più significativo (bit di segno)
mantenendo pertanto inalterato il segno;
© Piero Demichelis
132
Operatori di shift
• Esempio:
unsigned char x = 15;
x = x << 2
x = x >> 2
char x = -15
x = x << 2
x = x >> 2
/*
/*
/*
/*
/*
/*
x = 00001111 */
x = 00111100 (15 x 22 = 60)
x = 00000011 (15 / 22 = 3)
x = 11110001
x = 11000100
x = 11111100
*/
(-15 x 22 = - 60)
(-15 / 22 = - 4)
*/
*/
*/
*/
• Anche per questi operatori è consentita la scrittura
abbreviata:
x <<= 2 equivale a x = x << 2
© Piero Demichelis
133
Operatori di manipolazione dei bit
• Con questi operatori si possono eseguire operazioni
complesse sui singoli bit delle variabili dei programmi.
• Esempio:
unsigned char car = ‘m’, val = 1;
....................
car = car &
~ (val << 5 );
AND NOT
left shift
provoca la trasformazione del carattere ‘m’ (il cui codice
ASCII è 109) nel valore intero 77 corrispondente, in
codice ASCII al carattere ‘M’.
© Piero Demichelis
134
Operatori bit-a-bit: esempio riassuntivo
car = car & ~ ( val << 5 )
0
0
7
6
0
0
5 4
0
0
0
1
3 2
1
0
Codice binario del numero 1 su 8 bit (variabile val)
© Piero Demichelis
135
Operatori bit-a-bit: esempio
car = car & ~ (val << 5 )
0
0
0
0
0
0
0
1
0
0
1
0
0
0
0
0
Operazione di shift a sinistra di 5 posizioni sulla variabile
val (risultato = 32).
© Piero Demichelis
136
Operatori bit-a-bit: esempio
car = car & ~ (val << 5 )
0
0
0
0
0
0
0
1
0
0
1
0
0
0
0
0
1
1
0
1
1
1
1
1
Operazione NOT sul risultato del precedente shift
(risultato = 223 in binario puro).
© Piero Demichelis
137
Operatori bit-a-bit: esempio
car = car & ~ (val << 5 )
0
1
1
0
1
1
0
1
1
1
0
1
1
1
1
1
In car vi è il codice di ‘m’, cioè 109.
© Piero Demichelis
138
Operatori bit-a-bit: esempio
car = car & ~ (val << 5 )
car
(val << 5)
0
1
1
0
1
1
0
1
1
1
0
1
1
1
1
1
bit 0
1
Operatore AND (&): esegue l’operazione logica AND tra i
bit corrispondenti delle due variabili interessate.
© Piero Demichelis
139
Operatori bit-a-bit: esempio
car = car & ~ (val << 5 )
car
(val << 5)
0
1
1
0
1
1
0
1
1
1
0
1
1
1
1
1
bit 1
0
1
Operatore AND (&): esegue l’operazione logica AND tra i
bit corrispondenti delle due variabili interessate.
© Piero Demichelis
140
Operatori bit-a-bit: esempio
car = car & ~ (val << 5 )
car
(val << 5)
0
1
1
0
1
1
0
1
1
1
0
1
1
1
1
1
bit 2
1
0
1
Operatore AND (&): esegue l’operazione logica AND tra i
bit corrispondenti delle due variabili interessate.
© Piero Demichelis
141
Operatori bit-a-bit: esempio
car = car & ~ (val << 5 )
car
(val << 5)
0
1
1
0
1
1
0
1
1
1
0
1
1
1
1
1
bit 3
1
1
0
1
Operatore AND (&): esegue l’operazione logica AND tra i
bit corrispondenti delle due variabili interessate.
© Piero Demichelis
142
Operatori bit-a-bit: esempio
car = car & ~ (val << 5 )
car
(val << 5)
0
1
1
0
1
1
0
1
1
1
0
1
1
1
1
1
bit 4
0
1
1
0
1
Operatore AND (&): esegue l’operazione logica AND tra i
bit corrispondenti delle due variabili interessate.
© Piero Demichelis
143
Operatori bit-a-bit: esempio
car = car & ~ (val << 5 )
car
(val << 5)
0
1
1
0
1
1
0
1
1
1
0
1
1
1
1
1
bit 5
0
0
1
1
0
1
Operatore AND (&): esegue l’operazione logica AND tra i
bit corrispondenti delle due variabili interessate.
© Piero Demichelis
144
Operatori bit-a-bit: esempio
car = car & ~ (val << 5 )
car
(val << 5)
0
1
1
0
1
1
0
1
1
1
0
1
1
1
1
1
bit 6
1
0
0
1
1
0
1
Operatore AND (&): esegue l’operazione logica AND tra i
bit corrispondenti delle due variabili interessate.
© Piero Demichelis
145
Operatori bit-a-bit: esempio
car = car & ~ (val << 5 )
car
(val << 5)
0
1
1
0
1
1
0
1
1
1
0
1
1
1
1
1
bit 7
0
1
0
0
1
1
0
1
Operatore AND (&): esegue l’operazione logica AND tra i
bit corrispondenti delle due variabili interessate.
© Piero Demichelis
146
Operatori bit-a-bit: esempio
car = car & ~ (val << 5 )
car
(val << 5)
0
1
1
0
1
1
0
1
1
1
0
1
1
1
1
1
0
1
0
0
1
1
0
1
7
6
5 4
3 2
1
0
Valore finale di car
In pratica si è “spento” il bit in posizione 5 di car
© Piero Demichelis
147
Istruzione composta
• Una sequenza di istruzioni può essere racchiusa tra le parentesi
graffe per costituire quella che è nota come
istruzione composta
• Il C considera il blocco di istruzioni che costituiscono l'istruzione
composta come se si trattasse di una sola istruzione.
..........................
printf (“\nIntroduci due dati interi da elaborare:");
scanf ( "%d%d", &dato1, &dato2);
{
coeff = 10.5;
scala = 3;
}
• Lo stesso corpo di un programma può essere considerato come
un'unica istruzione composta.
© Piero Demichelis
149
Operatore sizeof
• Quando si realizzano programmi per i quali “deve” essere garantita la
trasportabilità, può sorgere la necessità di conoscere quanti byte
occupa un certo tipo di dato su una particolare macchina: ad
esempio, il tipo int può impegnare 2, 4 o 8 byte.
• L'operatore sizeof può agire o su un tipo, ad esempio,
sizeof (double)
o su una variabile, un vettore, un’espressione, una struct, ad
esempio,
sizeof (dato_real)
in ogni caso restituisce un intero corrispondente al numero di byte
occupato dall‘argomento.
• In seguito vedremo numerose e importanti applicazioni di questo
operatore.
© Piero Demichelis
150
Operatore sizeof
•
Esempio: programma che visualizza il numero di bit sui quali è
rappresentato un carattere, un numero intero, un numero reale, un
numero double.
#include <stdio.h>
int num_bit
main()
{
num_bit = sizeof ( char ) * 8;
printf (“\nSu questa macchina un char e' su %d bit", num_bit);
printf (“\n un intero su %d bit", (sizeof (int) * 8));
printf (“\n un float su %d bit", (sizeof (float) * 8));
printf (“\n un double su %d bit", (sizeof (double) * 8));
}
© Piero Demichelis
151
Scarica

Introduzione al linguaggio C