Informatica appunti dalle lezioni del 14 e 18/10/2010
Numeri binari in memoria In un calcolatore, i numeri binari sono tipicamente memorizzati in sequenze di caselle (note anche come parole) di lunghezza fissa dipendente dalla struttura del calcolatore stesso. Ad esempio, una parola di 4 bit può contenere il numero 0101 2 0 1 0 1
Combinazioni possibili di numeri Una parola di 4 bit può contenere 2 4 = 16 numeri binari diversi: da 0000 a 1111 In generale, una parola di n bit può contenere 2 n numeri binari diversi
Dimensioni delle memorie 8 bit = 1 Byte (1B) 2 10 Byte = 1024 Byte = 1 KiloByte (1KB) 2 20 Byte = 1048576 Byte = 1 MegaByte (1MB) 2 30 Byte = 1073741824 Byte = 1 GigaByte (1GB) 2 40 Byte = 1099511627776 Byte = 1 TeraByte (1TB)
Interpretazioni possibili dei numeri Se non ci preoccupiamo del segno dei numeri, e li consideriamo sempre positivi, la sequenza che va da 0000 2 a 1111 2 corrisponde ai numeri da 0 10 a 15 10 In generale, data una parola da n bit e interpretando i numeri binari come numeri senza segno, solo positivi, i numeri esprimibili con tale parola vanno da 0 a 2 n -1
Numeri con segno Se vogliamo introdurre anche i numeri negativi, una possibililità è di usare il primo bit a sinistra per esprimere il segno del numero: 0 sta per +, 1 sta per meno Con questa convenzione, chiamata modulo e segno, 1010 2 = -2 10, e 0111 2 = 7 10 In generale, con una parola di n bit si possono esprimere i numeri compresi tra (2 n-1 1) e 2 n-1 1
Complemento a due La rappresenazione modulo e segno ha un inconveniente: ci sono due rappresentazioni per 0 10 : ad esempio, se si hanno parole da 4 bit, sia 0000 2 sia 1000 2 corrispondono a 0 10 Con la rappresentazione in complemento a due si ovvia a questo problema: 0 10 si rappresenta solo con 0000 2, +1 10 come al solito con 0001 2 mentre per ottenere la rappresentazione binaria di -1 10, si procede come segue
Da numero positivo a negativo (1) Data la rappresentazione binaria di +1 10 : 0001 2 La rappresentazione di si ottiene così: si invertono tutti i bit si somma 1 Quindi, la rappresentazione in complemento a due di -1 10 è: 1111 2
Da numero positivo a negativo (2) Analogamente per +2 10 : 0010 2 La rappresentazione in complemento a 2 di per -2 10 è: 1110 2 E così via fino a utilizzare tutte le configurazioni di bit possibili della parola Con una parola da n bit, in complemento a due si possono rappresentare i numeri compresi tra tra 2 n-1 e 2 n-1 1 (da notare che cʼè un numero in più grazie al fatto che 0 10 ha unʼunica rappresentazione)
Metodo alternativo Lʼinversione di tutti i bit e la somma di 1 costituiscono un metodo per scoprire, dato un numero positivo +n 2, la codifica binaria del suo opposto: n 2 (o anche viceversa: dato -n 2, scoprire +n 2 ) In alternativa, si può usare la seguente regola: partendo da destra, lascia tutto intatto fino al primo ʻ1ʼ incluso, poi inverti tutto il resto
Somma di numeri binari Sui numeri binari si effettuano le classiche operazioni aritmetiche esattamente come per i numeri in base 10 In particolare, la somma si esegue bit per bit, con le seguenti regole: 0 + 0 = 0 1 + 0 = 0 + 1 = 1 1 + 1 = 0 con carry (o riporto) di 1 a sinistra
Overflow Si ha un overflow quando si sommano 2 numeri contenuti in parole da n bit e il risultato non riesce ad essere rappresentato in n bit Ad esempio: 0111 + 0110 = 1101 Questa somma non è valida perché sommando due numeri posi7vi su 4 bit si o:ene un numero nega7vo, il che è assurdo. Il risultato corre?o sarebbe 01101 ma per poterlo rappresentare servono 5 bit invece che 4. Una tecnica per controllare se c è overflow è di vedere i ripor7 alla posizione più a sinistra e della posizione a sinistra di essa (vedi le stelle): se i ripor7 sono diversi c è overflow. In questo caso c è riporto alla posizione del quarto bit da destra ma non alla sua sinistra, e infa: c è overflow.
Sottrazione Con il complemento a due, la sottrazione non è altro che sommare con un numero negativo 6 4 equivale a calcolare 6 + (-4): 6 10 = 0110 2 4 10 = 0100 2-4 10 = 1100 2 2 10 = 0010 2
Descrizione formale dei circuiti elettronici che realizzano le operazioni logiche sui segnali binari
Circuiti logici (1) Negazione, NOT,! IN OUT 0 1 1 0 IN OUT
Circuiti logici (2) Congiunzione, AND, && IN1 IN2 OUT= IN1 AND IN2 0 0 0 0 1 0 1 0 0 1 1 1 IN1 IN2 OUT
Circuiti logici (3) Disgiunzione, OR, IN1 IN2 OUT= IN1 OR IN2 0 0 0 0 1 1 1 0 1 1 1 1 IN1 IN2 OUT
Circuiti logici (4) Disgiunzione esclusiva, XOR, IN1 IN2 OUT= IN1 XOR IN2 0 0 0 0 1 1 1 0 1 1 1 0 IN1 IN2 OUT
Precedenze degli operatori! precede &&, che precede, che precede!a&&b C è equivalente a ((!A)&&B) C Le parentesi vincono sulle precedenze: in!((a B)&&C) la disgiunzione viene eseguita per prima, poi la congiunzione, da ultimo la negazione
Alcune regole per semplificare!a&&a 0!A A 1!(A&&B)!A!B!(A B)!A&&!B 0&&A 0 0 A A 1 A 1 1&&A A Trasformando le espressioni logiche seguendo queste regole rende i circuiti più semplici: (!A&&A) (!B&&C)!B&&C
Tipi di espressioni Tautologie: espressioni, come!a A, che valgono sempre 1 (sono sempre vere) qualunque sia il valore di A Contraddizioni: espressioni, come!a&&a, che valgono sempre 0 (sono sempre false) qualunque sia il valore di A Tutte le altre espressioni che possono essere sia vere sia false, a seconda dei valori delle loro componenti, si dicono contingenze.
Circuiti sommatori (1) Half-adder (A+B = Somma con Carry)
Circuiti sommatori (2) Full-adder (A+B+Carry in ingresso = Somma con Carry in uscita)
La compilazione: dalla scrittura dei programmi al codice binario
Scrivere programmi Scrivere programmi significa esprimere un algoritmo in un linguaggio comprensibile al calcolatore, ossia utilizzando un linguaggio di programmazione (ad es.: C++) Un programma in C++, prima di essere eseguito dal calcolatore, deve essere compilato, ossia elaborato da un compilatore
Il compilatore Il compilatore è anchʼesso un programma in funzionamento nel calcolatore, ed ha lo scopo di tradurre programmi scritti in un linguaggio di programmazione in eseguibili, ossia programmi scritti nel linguaggio macchina Le istruzioni scritte in linguaggio macchina possono essere lette, decodificate, ed eseguite direttamente dal processore
.cpp e.exe Il programma scritto in C++ prende si chiama anche codice sorgente, e il nome del file ha la tipica estesione.cpp (ad es.: prova.cpp) Il codice sorgente viene compilato e, se tutto va bene, viene creato il corrispondente codice eseguibile, con estensione.exe (ad es.: prova.exe) Il codice eseguibile è pronto per essere eseguito dal calcolatore
codice sorgente int x = 0; cout << x; prova.cpp Compilatore C++ codice eseguibile 000101010101011 101010101111010 prova.exe
La compilazione Il processo di compilazione consiste di 3 tipi diversi di analisi Lʼanalisi lessicale controlla che siano usate parole corrette del linguaggio di programmazione (ad es.: cout, int, ctou ) Lʼanalisi sintattica controlla che le istruzioni siano costruite in maniera corretta (ad es. int x = 0;, = 0 int x; ) Lʼanalisi semantica analizza il significato delle istruzioni (ad es.: x = x+5; vuol dire prendi x e aggiornalo sommando 5 )
Limiti della compilazione Il programmatore deve fare attenzione Il successo delle suddette tre analisi non è garanzia del buon funzionamento del programma Un programma scritto correttamente in C++ può non rispettare le specifiche perché il programmatore ha scritto un codice che differisce da quanto ha intenzione di realizzare
Concetti fondamentali del linguaggio C++
Variabili Le variabili in un programma rappresentano delle zone fisse (fisse almeno durante lʼesecuzione del programma) in cui vengono conservati dati di interesse per un programma Una variabile deve essere dichiarata prima di essere utilizzata: int x; (si dichiara una variabile x destinata a contenere un dato di tipo intero) int y = 3; (in questo caso viene anche specificato il valore iniziale, ossia si ha una inizializzazione)
Istruzioni Le dichiarazioni e le inizializzazioni delle variabili sono casi particolari di istruzioni Un programma è costituito da una sequenza di istruzioni separate da un punto e virgola (;) Le istruzioni possono essere raggruppate in blocchi, racchiusi da parentesi graffe { } Tipi possibili di istruzioni: dichiarazioni, inizializzazioni, assegnamenti, modifiche di variabili istruzioni di input e output (cin, cout) costrutti condizionali (if, if-else) cicli condizionali (while, do-while) cicli iterativi (for)
Assegnamenti e modifiche di variabili x = y; (il valore di y viene copiato in x) x = x + z + 6; (al valore vecchio di x vengono sommati il valore di z e il numero 6) x++; (equivalente a x = x + 1; ) x--; (equivalente a x = x 1; ) tutte le principali operazioni aritmetiche sono disponibili nel linguaggio: somma (+), sottrazione (-), prodotto (*), divisione intera (/), resto della divisione intera o modulo (%) etc.
Istruzioni di input e output Unʼistruzione di input pone il calcolatore in attesa di un valore che lʼutente immette tipicamente tramite la tastiera, e specifica anche la variabile in cui questo valore viene salvato: cin >> x; Unʼistruzione di output visualizza su schermo il valore contenuto in una variabile oppure il valore di una espressione, oppure una stringa di caratteri: cout << y; cout << (x + y)/2; cout << ciao\n \\ \n è il carattere di ʻnewlineʼ per andare a capo
Costrutto If If (condizione) istruzione_1; istruzione_2; Questo codice funziona così: no condizione? sì istruzione_1 istruzione_2
Costrutto If-else If (condizione) istruzione_1; else istruzione_2; Il codice funziona così: no istruzione_2 condizione? sì istruzione_1
Condizioni Una condizione è una descrizione formale di una situazione che può essere vera oppure falsa. Tipicamente si tratta di confronti tra variabili e espressioni: x == y (attenzione ai due ʻ=ʻ, diversi dallʼassegnamento che ne ha uno solo) x > 5 z!= x + y -10 (ʻ!=ʻ vuol dire ʻdiverso daʼ)
Operatori logici Le condizioni si possono comporre per mezzo di operatori logici: && ( and, congiunzione), ( or, disgiunzione),! ( not, negazione) Ad esempio: (x!=y) ((x==y)&&!(x>5)) è una condizione composta che è vera quando x è diverso da y oppure x è uguale a y e non è maggiore di 5. Lʼordine di valutazione degli operatori, se non viene modificato per mezzo delle parentesi, è: prima!, poi &&, e infine. Nellʼesempio, quindi, le parentesi intorno alla congiunzione sono in realtà inutili.
Una nota sugli ʻelseʼ if (condizione_1) if (condizione_2) istruzione_a; else istruzione_b; Quando viene eseguita lʼistruzione_b? A quale ʻifʼ si riferisce lʼʻelseʼ? Al primo disponibile risalendo il codice, quindi lʼistruzione_b viene eseguita quando condizione_1 è vera e condizione_2 è falsa.