Sei sulla pagina 1di 32

Programmazione I Orale

HW (hardware) e SW (software)
HW

un computer è una macchina in grado di imparare per risolvere problemi (se


programmabili)

ciò che si collega ai pc prende il nome di periferica è può essere interna (come
la scheda di rete) o esterna (come mouse o tastiera)
esistono periferiche:
-input (immettono dati sul pc)
-output (producono info verso l'esterno)
-I/O (input e output come la scheda di rete)
-memorie di massa (USC, CD, hard disk, SSD, ecc...)

in un PC si trovano:
-alimentatore (corrente alternata → corrente continua) per alimentare il pc
-scheda madre che contiene numerosi chip, memoria centrale, CPU e scheda
grafica
-scheda grafica
-CPU o processore
-memoria centrale (RAM) che fornisce alla CPU i dati presi dalla memoria di
massa

SW

“Modello a cipolla” (partendo dal centro)


-HW (bare machine), firmware (collocato nella ROM, interviene durante
l'accensione; al suo interno si trova il BIOS che contiene la boot sequence), OS,
SW di utilità (che contiene il SW di sviluppo), infine le applicazioni
Memorie Digitali

I dati sono scritti in bit (binary digit) e hanno solo 2 stati disponibili 1 e 0
8 bit = 1 byte
ogni byte ha un indirizzo
b = bit B = byte KB = kilobyte
MB = 2^20B GB = 2^30B TB = 2^40B

(sarebbero MiB, GiB, TiB perchè M G e T sono multipli di 10)

Codifica di un'immagine: si divide l'immagine in una griglia, più è fine più sarà
dettagliata (risoluzione), aumentando il numero di bit si possono aggiungere i
colori. Nei formati più utilizzati, se ci sono varie celle della griglia con stesso
colore o molto simili, vengono raggruppati senza predita di informazioni
(lossless) oppure togliendo piccoli oggetti che ostacolano la compressione
(lossy)

Macchina di Von Neumann

* = registri ordinari
PC = program counter
IR = instruction register
La CPU vuole sapere dalla memoria cosa c'è in una cella di memoria, userà il
bus comandi per chiederlo, la memoria comunicherà con la CPU fornendo i dati
con il bus dati

FETCH DECODE EXECUTE


FETCH: istruzione iniziale nel PC, CPU legge l'istruzione fornita dalla
memoria e la carica nell' IR
DECODE: il PC aumenta di 4 byte (in base all'istruzione) e l'istruzione viene
decodificata
EXECUTE: l'istruzione viene eseguita

Ciclo di vita del SW

Esigenze del cliente → studio di fattibilità e analisi delle specifiche (cliente +


analisti) → progettazione e sviluppo (analisti e programmatori) → testing
(tester) → deployment → manutenzione/aggiornamenti

Translation

compilazione: sorgente (pippo.go) → compilatore (go run pippo.go) →


eseguibile (pippo.exe)

differenza tra ide e editor: ide permette anche di compilare il programma

Aspetti sintattici

un programma è formato da token, di solito separati da whitespaces (“ ”, tab o a


capo) TRANNE se uno dei token non è alfanumerico (si può omettere il
whitespace)

token:
-Keyword (package, import, for, func)
-identificatori (nomi dati a funzioni e variabili, alcuni sono obbligati [tipo
main])
-separatori (, . ; : () [] {})
-operatori (+ - * / ++ += && || )
-letterali o costanti

GO è battery included (librerie di base sono incluse)


Variabili

le variabili sono “astrazioni di spazi di memoria centrale”


e hanno: un nome (identificatore), un tipo, uno scope (dove è visibile nel
programma) e un valore (unica caratteristica dinamica)

2 variabili non possono avere lo stesso nome a meno che la seconda variabile
sia in un sottoblocco (tipo un for) si dice “shadowing” e la seconda variabile
verrà usata solo nel blocco in questione (da evitare)

tipi di variabili: di base, composte, interfacce

Assegnamento

variabile = ESPRESSIONE

Assegnamenti multipli → a, b, c = 1, 2, 3 (a b c sono int)


ciò permette di fare: x, y = y, x (scambiare i valori)
si analizzano x e y e poi si assegnano i valori (non si passa quindi per una terza
variabile)

short assignment → a := 2 (senza il tipo che viene dedotto da go)


è come dire var a int e poi a = 2 (dichiarazione + assegnamento)

Blank identifier (_)

_ o blank identifier può essere usato per dire al programma di ignorare il fatto
che un qualcosa non viene mai usato (rendendo il programma compilabile)
esempio:
import "fmt"
func dummy() (int,int) {
val1 := 10
val2 := 12
return val1,val2
}

func main(){
rVal, _ := dummy()
fmt.Println(rVal)
}

si può anche mettere davanti alle funzioni se non vengono usate


Strutture di controllo del flusso

-sequenza/blocco: {}
-selezione: binaria (if), multiaria (switch), canali (select)
-iterazione (for): 0-ario, 1-ario, 3-ario
-altri costrutti (break, continue, defer, return...)

Operatori Logici

&& (and o congiunzione logica)


|| (or o disgiunzione logica)
! (not o negazione logica)

LEGGI DI ASSORBIMENTO:
- a && (a || b) == a
- a || (a && b) == a
non importano i valori di a e b, queste espressioni avranno sempre il valore di a

LEGGI DI DE MORGAN
- !(a && b) == (!a) || (!b)
- !(a || b) == (!a) && (!b)

Rappresentazione dell'informazione

I tipi di base si dividono in:

-interi
-senza segno (uint, uint8, uint16, uint32, uint64)
-con segno (int, int8, int16, int32, int64)
-floating point
-reali (float32, float64)
-complessi (complex64, complex128)
-altri
-bool
-byte
-rune
-string
-error
-uintptr
Interi senza segno

rappresentano numeri interi naturali

utilizzano n bit quanti quelli specificati dal tipo (uint8 = 8bit)

sempre per uint8 (2^8 -1)


00000000 = 0
11111111 = 255

se si sorpassa il limite, sì ricomincia dal valore minimo quindi in uint 8 non


esiste il 256, si ritorna a 0

uint16 (2^16 -1) ecc

Interi con segno

Rappresentazione in complemento a 2

quindi il max in int8 sarà 127 (0 1 1 1 1 1 1 1)

il minimo invece -128 (1 0 0 0 0 0 0 0)

(1 1 1 1 1 1 1 1) sarà quindi -1 (-128 + 127)

0 1 1 1 1 1 1 1 = 127
0 1 1 1 1 1 1 0 = 126
00000000=0
1 1 1 1 1 1 1 1 = -1
1 1 1 1 1 1 1 0 = -2
1 0 0 0 0 0 0 0 = -128

in generale il range sarà da -(2^7) a (2^7)-1 per int8


Floating point reali

1,35 e-7 = 1.35*10^-7


1,35 si chiama mantissa
-7 invece è l'esponente
-in float32 il primo bit è per il segno, i seguenti 8 per l'esponente e i restanti 23
per la mantissa
-in float64 il primo bit è per il segno, i seguenti 11 per l'esponente e i restanti 52
per la mantissa

MAI USARE == CON I FLOAT


|x-y| < n

(if math.Abs(x-y) < 1e-7)


non if x == y

Standard IEEE-754

Floating point complessi

complessi di tipo a + bi
sono il doppio degli altri perchè sono formati da 2 parti: il coefficiente e la i

Byte = uint8
Tipo Rune

-standard US-ASCII (primi 128 caratteru di Unicode)

ogni paese aveva la sua versione aggiuntiva, per esempio in Italia era usata la
US-ASCII + ISO-88591 per caratteri non inclusi in US-ASCII
il risultato era che in paesi diversi si leggeva solo la prima parte (lo standard
ASCII)

-UNICODE (1.114.112 caratteri)

rune = int32 (il primo tipo a contenere quel numero di caratteri)

Costanti int
se un numero inizia con 0x allora è in notazione esadecimale, se inizia con 0 è
in base ottale

Sequenze di escape
Iniziano con \
esempi:
“\n” = newline, a capo
“\t” = tab
“\'” = rappresenta '
“\\” = rappresenta \
\uxxxx = con xxxx caratteri hexa rappresentano i primi 60k caratteri
\uxxxxxxxx = per caratteri oltre i 60k

UTF-8

go utilizza UTF-32 per le rune (4 byte), che risulta in un problema per le


stringhe in quanto si utilizzerebbero troppi byte; perciò go usa UTF-8 per le
stringhe

(RAPPRESENTAZIONE A LUNGHEZZA VARIABILE)

1) ogni carattere occupa da 1 a 4 byte


2) il primo bit:
è 0 per i caratteri ASCII (che occupano solo un byte)
sono 1110 per i caratteri di k > 1 byte
3) i byte successivi al primo
iniziano con 10
quindi:

ricapitolando, per gli ASCII go utilizza un byte, per gli altri caratteri siccome
non può prevederne la dimensione, da 2 a 4 byte (in questo caso 3)

LE STRINGHE SONO SEQUENZE DI BYTE (UTF-8)

IL FOR RANGE SCANDISCE LE STRINGHE RUNA PER RUNA, NON


BYTE PER BYTE

'a' = runa a “a” = stringa di lunghezza 1

libreria strings

SUBSTRINGS
stringa[carattere iniziale : carattere finale (escluso)]

nel leggere in input le stringhe FMT.SCAN SI FERMA al PRIMO SPAZIO

Negli Switch una virgola tra le condizioni del case (case


cond1,cond2: ) è come un or (||)
Funzioni

un file sorgente è una collezione di funzioni (di cui una obbligatoria: il main)

si possono distribuire le funzioni di un programma in più sorgenti, per


compilare si userà quindi il comando

go build -o nome.exe file1.go file2.go ecc

oppure

go build -o nome.exe *.go (tutti i file go della directory)

-i valori passati in una funzione (funzione(2, 3.8)) si chiamano parametri attuali


e sono dei veri e propri valori
-i valori passati nell'header della funzione si chiamano parametri formali
(funzione(x int, y float)) e sono delle variabili che prendono i valori passati
nella chiamata della funzione

si possono dare nomi alle variabili da restituire già nella function header:
func funzione() (x int, y float){
corpo della funzione

return (VUOTO)
}

in go le funzioni vengono chiamate “by value” passando i parametri attuali,


altrimenti si usano le chiamate “by reference” utilizzando i puntatori, in go però
si usano molto meno

Pacchetto bufio

si usa per leggere delle righe di testo anche se separate da spazi

libreria: bufio
scanner := NewScanner(os.Stdin) (per lo standard input, altrimenti un tipo file)
for scanner.Scan(){ (continua finché non trova token)
riga := scanner.Text() (riga = token appena letto)
}

il tutto termina con ctrl+D che chiude il terminale

aggiungendo sotto la dichiarazione dello scanner:


scanner.Split(bufio.bufio.ScanWords) si riduce la dimensione dei token alle
semplici parole separate da spazio (con ScanRunes si tokenizzano le rune)

Tipi composti

si dividono in:
-puntatori
-struct
-array
-slice
-mappe
-tipi funzione

Puntatori

un puntatore è la locazione di memoria dove si trova un dato

dato → & → puntatore → * → dato


& = operatore d'indirizzo
* = operatore d'indirezione

i puntatori sono di tipo *x (x è un tipo: può essere int o string quindi *int,
puntatore a int e *string puntatore a string)

**int è puntatore a puntatore a int

il valore 0 dei puntatori è “nil”

esempio di utilizzo dei puntatori:


var x int
var p *int
var q **int

x = 15
p = &x
q = &p

(**q)++

x è ora 16

con new(punt) si alloca uno spazio nello heap (un tipo di memoria) per il tipo
punt e ne restituisce la locazione

var p *int
p = new(int)
*p = 7
fmt.Println(*p) (printerà 7)

a proposito di Heap, le variabili si allocano in 2 spazi di memoria:


-stack (come una pila, i valori allocati sono i primi ad uscire)
-heap organizzato in modo più anarchico e meno ordinato

Call by reference

oltre a call by value, le funzioni possono essere chiamate con la call by


reference e l'utilizzo dei puntatori: al posto di passare i valori, modificarli e
ritornarli con return, si modificano direttamente attraverso il valore associato
all'indirizzo di memoria
esempio:

func f(p *int){


(*p)++
}
func main(){
x := 7
f(&x)
fmt.Println(x)
}
Garbage collector

a causa dell'anarchia dello heap, è possibile che alcune variabili diventino


orfane perché ormai irraggiungibili (memory leaks)

in alcuni linguaggi più moderni (come RUST) si è eliminato del tutto il


problema, in altri (come go) un algoritmo chiamato garbage collector individua
queste variabili orfane e le libera, in altri linguaggi ancora (come C) queste
variabili richiedono una deallocazione manuale (in C si usa il comando free)

Type
Definizione di tipo:
type puntPuntInt **int (da ora i tipi **int si chiameranno puntPuntInt)
Alias di tipo:
type puntPuntInt = **int (puntPuntInt è solo un alias ora)

gli alias sono interscambiabili, le definizioni no:

type intero = int (è un alias)


var x intero
var y int
x=y+5 OK

type intero int (è una dichiarazione di tipo)


var x intero
var y int
x=y+5 NO SONO TIPI DIVERSI devi fare
x = intero(y) + 5

Struct

sintassi:

type nomestruct struct{


x, y, z int
}

accesso ai valori: si usa la dot notation


nomestruct.variabile (nomestruct.x y o z)
NOTA BENE
per passare i valori di una struct in una funzione si usano i puntatori perchè
passare tutta la struct è piuttosto dispendioso
esempio:
*p.campo = p.campo

Letterale struct:

d = data{29, 11, 1968}

Numeri Pseudocasuali

seme → Generatore → numero pseudocasuale

import(
“fmt”
“math/rand”
)

func main(){
fmt.Println(rand.Intn(10))
}

stampa un numero casuale tra 0 e 10 (intervallo [0,n))

Vettori (Array e Slice)

si usano per immagazzinare più valori dello stesso tipo


ad esempio in un programma che calcola la media di diverse altezze date in
input, se vogliamo che queste altezze vengano salvate, possiamo salvarle in un
vettore, come se fosse una lista

Array
[dimensione costante]tipo

[100]int (array di int di dimensione 100, da 0 a 99 inclusi)


LETTERALI ARRAY
const x [8]int{0,1,2,3,4,5,6,7}

2 array dello stesso tipo ma con dimensioni diverse NON sono uguali

[7]int != [8]int

quindi:
var x,y [8]int
var z [7]int
x=y OK stesso tipo stessa dimensione
x=z NO entrambi int ma con dimensione diversa,

Scansione di Array
3 metodi:
1- for i := 0; i < len(array); i++
2- for i := range array{
a[i] = ….
3- for i,x := range array{
x...

Slice

simili agli array, ma con dimensione dinamica

var x []int (nessuna dimensione perchè si possono aggiungere o togliere


elementi liberamente)

LETTERALE SLICE
x = []int{0, 1, 2, 3, 4, 5...}

CREAZIONE

si possono creare usando la funzione make:

x = make([]int, 100) slice di int di dimensione 100

con la make la slice x avrà una dimensione pari a 100, questo non significa che
non possa essere ampliata.
La make può essere omessa in quanto può essere rimpiazzata dalla funzione
append
APPEND

x = append(x, valore)
x = append(x, valore1, valore2, ecc...)

semplicemente si aggiunge in coda alla slice il/i valore/i dopo la virgola


(secondo, terzo, ecc parametro/i) alla slice specificata prima della virgola
(primo parametro)

Dietro le quinte
Per garantire la dinamicità della slice, quest'ultima è immagazzinata (tramite un
puntatore) in un array statico più grande.
Se con un append la slice supera le dimensioni dell'array in cui è contenuta,
verrà creato un array ancora più grande (sempre nello heap) e il contenuto di
quello vecchio verrà copiato in quello nuovo

NOTA BENE
se si fa un append in una slice dichiarata con una make, anche se è ancora
vuota, l'append appenderà in cima alla slicce il valore richiesto.
Questo significa che:

x = make([]int, 7)
x = append(x, 15)

la slice da 0 0 0 0 0 0 0 diventerà 0 0 0 0 0 0 0 15

Alias con slice


2 slice che condividono totalmente (o in parte) l'array in cui sono contenute

var x,y []int


x = make([]int, 10)
y = x ← alias
così facendo se poi si modifica la slice x, di conseguenza verrà modificata
anche la slice y; questo perchè la slice è come un puntatore all'array più grande
contenuto nello heap, facendo y = x risulta che ora y è lo stesso puntatore
contenuto in x, perciò le modifiche in x saranno ereditate da y.
Per questo è fortemente sconsigliato usare gli alias prima di modificare le slice
(per esempio con l'append)
per copiare i contenuti di una slice in un altra si usa la funzione built-in copy
Accesso alla Slice
3 modi anche qui:

1- for i:= 0; i < len(slice); i++{


slice[i]...
2- for i := range slice{
slice[i]...
3- for i,x := range slice{
slice[i] ← lettura/scrittura
t ← solo lettura

Argomenti da riga di comando


La riga di comando si può considerare come una slice, quindi con l'esempio

./pippo.go Giovanni e Carlo sono morti

os.Args (questa slice sopraccitata) conterrà il comando ./pippo.go e (da


posizione 1 in poi) valori aggiuntivi che verranno analizzati dal programma
per esempio con: s := os.Args[1:] (che è una subslice, uguale alle substring)

Generazione numeri pseudocasuali


r := rand.New(rand.NewSource(time.Now().UnixNano()))
r.Intn(50) → numero casuale intervallo [0,50) (50 escluso)

Printf
fmt.Printf(stringa di formato, argomenti)
stringa di formato composta da stringhe e verbi (%qualcosa)

%d intero %f float %s string %% stampa %


%t bool %q “string” %e float in notazione scientifica

Prima dei verbi ci possono essere degli “avverbi”

%4d = padding a sx con 4 spazi prima dell'int → __numero


%02d = padding a sx con 2 0 al posto degli spazi → 00numero
x = 1347.43162945
%9.2f = __1347.43 → 2 dopo la virgola e in totale lunghezza + padding = 9
%[n]s (in questo esempio si usano le stringhe) si possono riutilizzare gli
argomenti più volte, la n rappresenta l'argomento in questione:
fmt.Printf(“%d %[1]d %[1]d”, 3) stamperà 3 volte 3

VARIANTE SPRINTF
al posto di stampare come Printf, crea una stringa

Slice multidimensionali (matrici)


va immaginata come una griglia
var m [][]int
m = make([][]int, 10) matrice di 10 righe
for r := 0; r < 10; r++{ per ogni riga:
m[r] = make([]int, 15) riga di 15 elementi
}
il risultato sarà una matrice di 10 righe e 15 colonne

Mappe
ad ogni chiave è associato un valore (come una funzione iniettiva)

map[tipo chiave]tipo valore

anche qui si possono aggiungere mano a mano dei valori, come nelle slice con
l'append, ma a differenza delle slice, nelle mappe la make è obbligatoria

var mappa map[string]int a una stringa corrisponde un valore int


mappa = make(map[string]int)

POPOLAZIONE MAPPA:
mappa[“Milano”] = 6991 (città chiave e numero crimini il valore)
mappa[“Rimini”] = 6246
mappa[“RomaLadrona”] = 5485
-se la chiave non esiste, viene creata una nuova coppia chiave-valore
-se la chiave esiste già, si aggiungerà il valore inserito (non bisogna quindi
controllare se la chiave è già presente)

CANCELLAZIONE ELEMENTI
delete(mappa, chiave)
CONTROLLO SE MAPPA CONTIENE UN VALORE/CHIAVE
controlla paragrafo Comma OK in fondo
ITERA COPPIE CHIAVI-VALORI
for k,v := range mappa{
...
}

Gestione della memoria


1 – MEMORIA STATICA
2 – STACK (DI ESECUZIONE)
3 – HEAP (MUCCHIO)

1)
vivono gli oggetti che devono esistere per tutta l'esecuzione del programma
esempio: variabili globali (dichiarate prima del main e con scope tutto il
programma)

3)
tutto ciò che si crea con new/make; e qui che agisce il garbage collector
sostanzialmente contiene tutte le variabili “dinamiche” come mappe o slice

2) Stack

nello stack sono contenute le variabili con valore fisso, come interi, float o i
puntatori.

Lo stack si dice LIFO (last in first out) perchè esattamente come in una pila di
piatti, l'ultimo oggetto depositato (azione chiamata push) sarà il primo a uscire
(azione chiamata pop)

una coda alle poste invece è un esempio di FIFO


(first in (enqueue) first out (dequeue))

quindi:
func main(){
var x int

for ...{
y := 2
}
}
y sarà la prima variabile a essere eliminata, mentre x sarà l'ultima
Record Di Attivazione
concetto inerente allo stack.

Alla base dello stack vengono memorizzate tutte le variabili del main, e ogni
volta che viene creata una funzione, viene allocato lo spazio immediatamente
superiore che conterrà le variabili locali della funzione (+ i parametri), che si
libererà quando la funzione sarà finita.
Lo spazio di memoria allocato per una funzione si chiama record di attivazione
Lo stack è quindi un insieme di record di attivazione.

Oltre alle variabili locali, il record contiene il punto di rientro (dove riprendere
nel main o nella funzione in cui la funzione attuale è stata chiamata) e il valore
restituito dalla funzione.

Nel caso della ricorsione, lo stack di attivazione creerà più istanze della stessa
funzione con le stesse variabili (ma con valori diversi), procede seguendo le
solite regole dello stack

Ricorsione (24-28 Nov)


Prima di tutto si tende ad usare la ricorsione come ultima spiaggia, solo se il
problema che si tenta di risolvere non si può risolvere in altro modo, questo
perchè oltre ad essere più complicata della normale iterazione, la ricorsione è
meno efficiente.

La ricorsione consiste nel chiamare dentro una funzione la funzione stessa:


func funzione(x, y){

funzione(val1, val2)
}
Come ragionare con la ricorsione

Iniziando dal classico esempio usato per la ricorsione: il calcolo del fattoriali
1) trovare il caso base
in questo caso il caso base è n == 0 in cui appunto la ricorsione non serve
2) analizzare i casi ricorsivi
se n != 0 allora bisogna affidarsi alla ricorsione, usando l'esempio n = 7 e
scrivendo solo gli output indentati per numero di esecuzione:
7*
6*
5*
4*
3*
2*
1*
0 quindi ritorna 1
1*1=1
2*1=2
3*2=6
4 * 6 = 24
5 * 24 = 120
6 * 120 = 720
7 * 720 = 5040

Internet e applicazioni client/server (non so se serva)*


internet si divide in
-applicazioni client/server ← come i siti www
-applicazioni peer 2 peer (tra pari) ← come uTorrent

programma sito boldi 28 Nov (server)

Turtlegraphics (programma fiocchi di Koch)*


Codice 29 Nov

Metodi
func (receiver/target) nome (param. formali) return{ ← definizione
...
}

receiver.nome(param. Attuali) ← invocazione


sostanzialmente i metodi sono delle funzioni applicate a delle variabili di tipo
struct (come gli oggetti nella programmazione a oggetti)
esempio di uso dei metodi

type Point struct{


x, y float64
}

func main(){
var p1, p2 Point
d := p1.Dist(p2)
}

func (p1 Point) Dist(p2 Point) float64{ ← dove p1 è il receiver


...
}

Interfacce
oltre ai tipi di base e composti ci sono le interfacce
le interfacce sono delle collezione di metodi
esempio

type geometry interface{


perim() float64
}

type circle struct{


radius float64
}

func measure(g geometry){


fmt.Println(g.perim())
}

func (c circle) perim() float64{


return 2*math.Pi * c.radius
}

func main(){
r := circle{radius: 5}
measure(c)
}
nel codice, fmt.Println(g.perim()) stampa il perimetro, fmt.Println(g) invece
stamperebbe solo la struct circle (il valore della variabile di tipo circle)

I/O Avanzato
APERTURA FILE (pack os)
lettura os.Open(nomefile) ← restituisce un tipo *File e un errore (o nil)
creazione os.Create(nomefile) ← restituisce un tipo *File e un errore (o nil)

APERTURA + LETTURA

f,err := os.Open(“pippo.txt”) ← f è di tipo *File


if err != nil{
gestione errore
}
defer f.Close() ← defer vuoi dire chiudi a fine funzione

var buffer []byte


buffer = make([]byte, 1)
for{
n, err := f.Read(buffer) ← n è il numero di byte letti
if n == 0{
break
}
if err != nil{
break
}
fmt.Printf(“%c”, buffer[0])
}

l'idea è (dopo aver aperto il file) leggere il file tot byte alla volta (buffer di tipo
[]byte), quando non c'è più niente da leggere finisce con break

NON aprire troppi file, risulterà in un errore, chiudere sempre i file quando non
si usano più
SCRITTURA

f,err := os.Create(“pippo.txt”)
if err != nil{
gestione errore
}
defer f.Close()

var b []byte
b = []byte(“Paolo Boldi”) ← cast in slice di byte

n,err := f.Write(b) ← n è il numero di byte scritti


if err != nil{
gestione errore
}

CON BUFIO SCANNER


basta mettere il tipo *File nel NewScanner:
scanner := bufio.NewScanner(file)

Funzioni Variadiche (con num argomenti non fissi)


un esempio sono i Print, posso fare Print(1, 2) come Print(1, 2, 3, 4)
infatti nella documentazione di go → func Println(... any)
l'argomento variadico viene considerato come slice

esempio:

func Sum(...x int) int{ ← tra i parametri formali int in realtà è una slice di int
s := 0
for _,v := range x{
s += v
}
return s
}
nel main:
var s []int{1, 2, 3, 4, 5}
x := sum(5, 7, 8) ← risultato 20
y := sum(1, 3, 5, 7, 9) ← 25
z := sum(s...) ← con i puntini, non senza
func Sum2(x []int) int{
s := 0
for _,v := range x{
s += v
}
return s
}

x := sum2(5, 6, 7) NO → x := sum2([]int{5, 6, 7}) SI

USO SU APPEND
var s, t []string
s = append(s, “ciao”)
s = append(s, “x”, “y”, “z”)
t = append(t, “pippo”, “pluto”)
t = append(t, “topolino”)
s = append(s, t...)

altro esempio
y = append(x[: i], x[i+1 :]...)

Testing
test si dividono in unitari, di integrazione e funzionali (o end-to-end)
i test funzionali sono i classici test manuali
i test unitari invece testano solo singole funzioni o metodi
per testare si usano file di test chiamati “nomefile_test.go” (libreria “testing”)

func IntMin(a, b int) int{


if a < b{
return a
}
return b
}

func TestIntMinBasic(t *testing.T){


ans := IntMin(2, -2)
if ans != -2{
t.Errorf(“IntMin(2, -2) = %d; want -2”, ans) ← Errorf o Error
}
}
se non trova un t.Error allora il test è un PASS
Tipi Funzione
func Pippo(x int, y float) bool
func Pluto(a int, y float) bool
sono entrambe 2 funzioni dello stesso tipo: func(int, float) bool
si può quindi fare un programma:

var f func(int, float) bool variabile di tipo funzione


if ...{
f = pippo
}else{
f = Pluto
}
f(3, 2.5) quindi f avrà il corpo della funzione Pippo o Pluto (dipende dall'if)

Linguaggio C
C è un linguaggio di programmazione nato negli anni 70, creato dallo stesso
creatore di Go, possiamo dire che Go è un upgrade di C
-Standard C
ansi C = C89 (1989)
C99 (1999)
C11 ( 2011)

differenze tra Go e C, in c non c'è package main e import è sostituito da:


#include <stdio.h> (stdio.h è lo standard input/audio)
non esistono Print o Println ma solo printf, non esiste Scan ma solo scanf
PRINTF E SCANF SENZA MAIUSCOLA
tutte le linee di codice finiscono con ; in go invece si omettono e il compilatore
le aggiunge autonomamente.
In Go gli if non richiedono la condizione tra parentesi, in C si, invece mentre
Go vuole per forza le parentesi {} dopo un if, for, ecc... C non le richiede se il
contenuto del blocco è di solo una riga.

per compilare si usa gcc (il miglior compilatore per c gratuito)

gcc – o nomefile.exe nomefile.c ← crea un file.exe


./nomefile.exe lo esegue (sia qui che nel gcc si può omettere .exe)
LA COMPILAZIONE CON GCC
sorgente → pre compilazione → compilazione → assemblaggio → linking
hello.c → hello.i → hello.s → hello.o → hello
gcc -e hello.c → gcc -s hello.c → gcc -c hello.c → gcc classico

gcc -wall = warning all (restituisce i warning per un codice migliore)


pre compilazione:
la pre compilazione trasforma le include negli effettivi file aggiungendo codice
al file, fino a che non ci saranno più include e il sorgente diventerà puro
linguaggio C

DICHIARAZIONE VARIABILI IN C
non var i int come in Go, ma semplicemente int i;
non esistono short assignment o assignment multipli

FUNZIONI:
-dichiarate
-definite (o implementate)

in Go:
func Sqrt(x float64) float64
in C:
double sqrt(double x)

-la dichiarazione di una funzione è la stringa double sqrt(double x) (come negli


header file nel processo di compilazione)
-la definizione è invece il body della funzione (come i contenuti degli header
file)

Tipi di base in C
short int long
unsigned short unsigned int unsigned long
float double char (dim. Fissa = 1 byte usa ASCII)

il valore di default delle variabili non è 0 come in Go, ma è random (tranne


nelle variabili globali in cui è 0)

BOOL = INTERI
0 = false
!= 0 = true

Espressioni

si dividono in:
-con effetti collaterali → y = x (cambia il valore di y)
-senza effetti collaterali → x + 1 (nessun valore è cambiato permanentemente)

esempio uso di espressioni:


y=2
z=3
x = (y = z) + 1
y prenderà il valore di z (3) e x diventerà: y (che prende il val di z, quindi 3) + 1

INCREMENTI PRE E POST (considerando y = 2)


x = (y++) - 3 → x = 2 – 3 incremento POST operazione
x = 2 – 3 = -1 DOPO y++ → x = -1 e y = 3
x = (++y) - 3 → x = 3 – 3 incremento PRE operazione
x=3–3=0 PRIMA y++ → x = 0 e y = 3

quindi se per caso x = 2

x = x++ non è la tessa cosa di x = ++x

x = x++ → x = ancora 2
x = ++x → x = 3

Operatore Ternario
x = y > 3? z++ : x – 3
è la stessa cosa di
if (x = y > 3)
z++
else
x–3

If in C
gli if sono identici agli if in Go, cambia solo che la condizione deve essere tra
parentesi e le graffe si possono omettere se l'if (o else) contiene solo
un'istruzione

mettere il punto e virgole dopo l'if è sbagliato, è come se l'if terminasse


all'istante

Cicli in C
1) equivalente for unario
while (condizione){
corpo while
}

2) equivalente for ternario (identico tranne condizione tra parentesi)


for (a; b; c){
corpo for
}
3) simile al for zerario, ma con la condizione in fondo
do{
corpo
}while(condizione); ← PUNTO E VIRGOLA DOPO LA CONDIZIONE

esistono anche in C i break, continue e return

Cicli infiniti
while(1){
corpo
}
do{
corpo
}while(1);
for(;;){
corpo
}

Tipi composti
1) Puntatori:
Go: var p *int
C: int *p;
2) Struct :
Go: type nomeStruct struct{
C: typedef struct{ corpo struct }data;
3) Union
(non se ne è parlato)
4) Array
Go: var x [10]int
C: int x[n];
in C89 la dim doveva essere statica, dal 99 in poi è semidinamica

esempio programma con array e struct 09-01-24

Puntatori in C
int i
int *p
p= &i ← i = *p (*davanti a un puntatore ne indica il valore)

Allocazione Dinamica
#include <stlib.h> ← stdlib non stdio
voi *malloc(num_byte)

Go: p = new(int)
C: p = (int*)malloc(sizeof(int))

per allocare memoria nello heap (dinamicamente) si usa la funzione malloc


esempio di utilizzo della malloc:
y = (int*)malloc(sizeof(int)); così facendo si alloca lo spazio di memoria
con free(y); si dealloca la memoria allocata con la malloc

ricapitolando malloc restituisce un puntatore di tipo void (o un altro tipo se


castato) alla cella di memoria in cui inizia il blocco allocato
esempio uso puntatori: clicca qui
Aritmetica dei Puntatori
I puntatori possono essere incrementati con degli int o con altri puntatori,
quindi y+1 = 1 posizione dopo la cella y

String in C
le stringhe sono array di byte, in cui l'ultimo byte è uno \0 (fine stringa non un
normale 0)
strlen(stringa) ← lunghezza stringa
strcpy(x, “Paolo”); ← copia Paolo in x
strcat(stringa, stringa da aggiungere) ← concatena
strstr(str da cercare , stringa in cui cercare) ← cerca stringa in stringa
strrstr ← cerca stringa in stringa partendo dal fondo

BUFFER OVERFLOW
la printf stampa fino al carattere \0, quindi se non c'è il carattere, andrà avanti
fino al prossimo \0 (di un'altra stringa)

ARRAY IN C É UN PUNTATORE, QUINDI UN ARRAY DI STRINGHE É


UN ARRAY AD ARRAY

Cast
Go: x = int(f)
C: x = (int)(f)

Argomenti da linea di comando in C


int main(int argc, char* argv[]){
codice...
}

dove:
-argv[0] contiene il nome del programma (./nomeProgramma)
-argv[1] è il primo valore e argv[argc-1]
-argc è il numero di parametri passati
-gli argomenti vengono passati al main

Comma OK

è un modo di gestire gli errori, e può essere usato:


1- per testare le funzioni
2- per controllare se una mappa contiene una chiave
1)
func main(){
if ok, err := isEven(3); ok{
fmt.Println(“è un numero pari”)
}else{
fmt.Println(err)
}
}
func isEven(n int) (bool, error){
if n%2 != 0{
return false, fmt.Errorf(”è un numero dispari”)
}
return true, nil
}

quindi ok conterrà il bool di controllo, errore il messaggio d'errore (o nil se la


funzione è giusta)

2)
value, ok = mappa[key]
se la mappa contiene la key, allora value sarà il valore associato e ok sarà true
se la mappa non contiene key, allora value sarà lo 0 value del tipo del value e ok
sarà false

Potrebbero piacerti anche