• Non ci sono risultati.

Scope (visibilità) Scope di un identificatore

N/A
N/A
Protected

Academic year: 2021

Condividi "Scope (visibilità) Scope di un identificatore"

Copied!
30
0
0

Testo completo

(1)

Scope (visibilità)

Scope di un identificatore

¨ 

Lo scope (visibilità) di un identificatore è la porzione di codice in cui quell’identificatore è definito e ha senso

¤ 

Se l’identificatore si riferisce a una variabile, lo scope è la porzione di codice che inizia dal punto in cui viene definita la variabile e termina alla fine del blocco che la contiene (chiusura della parentesi graffa) Non è possibile utilizzare una variabile al di fuori del suo scope

Si può pensare che la variabile, raggiunto il termine

dello scope, venga distrutta

(2)

Scope di un identificatore

Scope di a nel ramo-if

Scope di a nel ramo-else Scope di b int b = 4;

if (b == 3) { int a = 5;

printf("b è uguale a 3\n e a è uguale a %d", a);

} else {

int a = 1;

printf("b non è uguale a 3 e a è uguale a %d\n", a);

}

Il codice è corretto, però bisogna prestare attenzione:

¨ 

La variabile a del ramo-if non corrisponde alla variabile a del ramo-else

¨ 

Entrambe le variabili a vengono distrutte al termine del blocco, quindi qualsiasi riferimento a queste variabili dopo questo blocco di istruzioni sarebbe un errore

Scope di un identificatore

¤ 

Se l’identificatore si riferisce a una funzione, lo

scope è la porzione di codice che inizia dal punto in

cui viene definita e termina alla fine del file sorgente,

cioè la zona in cui il compilatore è in grado di

recuperare la definizione della funzione (il body) e

il tipo e il numero di argomenti che la funzione

stessa richiede, nonché il tipo del valore di ritorno

(3)

Esempio

¨ 

Non si può invocare una funzione non dichiarata (è fuori dallo scope) Invocazione senza dichiarazione

int main(void) { int a=f(3,5);

printf("%d",a);

return 0;

}

int f(int x, int y){

return 2*x + 3*y;

}

ERRORE!

¨ 

Anche le funzioni di libreria (printf, strlen, …) sottostanno a questa regola: i file header (stdio.h, string.h, …) contengono le dichiarazioni delle funzioni

Esempio

¨ 

La definizione di una funzione è implicitamente una dichiarazione:

Dichiarazione, invocazione,

definizione

int f(int x, int y);

int main(void) { int a = f(3,5);

printf("%d", a);

return 0;

}

int f(int x, int y) { return 2*x + 3*y;

Definizione (con dichiarazione implicita) e invocazione

int f(int x, int y) { return 2*x + 3*y;

}

int main(void) { int a = f(3,5);

printf("%d", a);

return 0;

}

(4)

Tipi derivati: struct

Strutture

¨ 

Gli array sono utili quando dobbiamo aggregare informazione omogenea

¨ 

Quando invece dobbiamo gestire informazione eterogenea è necessario utilizzare una struttura: una collezione finita di

“variabili” (chiamati membri) non necessariamente dello stesso tipo, ognuna identificata da un nome

¨ 

Sintassi

struct <etichetta> {

<definizione-di-variabili>

};

¨ 

L’etichetta è opzionale e rappresenta il nome della struttura

(5)

Dichiarazione di Strutture

¨ 

Esempio di dichiarazione di una struttura

struct studente { char nome[20];

int eta;

char sesso;

float media;

};

studente è il nome della struttura composta da tre membri: nome, eta , sesso e media

¨ 

N.B.: La definizione della struttura non provoca allocazione di memoria, ma introduce un nuovo tipo di dato

¨ 

La memoria viene allocata solo al momento della

dichiarazione di una variabile del tipo struct studente

Definizione di variabili di tipo struttura

¨ 

Dichiarazione di due variabili di tipo struct studente:

struct studente p1, p2;

¨ 

Dichiarazione di un array di 50 elementi ciascuno dei quali è di tipo struct studente:

struct studente elenco[50];

(6)

Definizione di variabili di tipo struttura

¨ 

In memoria i membri di una struttura vengono allocati nello stesso ordine di dichiarazione

¨ 

Tra un membro e l’altro, a seconda dell’architettura, possono esserci spazi intermedi di allineamento di memoria inutilizzabili e di

contenuto indefinito (sono utili per rendere più efficiente la copia dei dati)

¨ 

Es. su una macchina a 64 bit con char da 1 byte, int da 4 byte e float da 4 byte, sizeof(struct studente) == 32 invece di 29

Regole per nominare i membri

¨ 

I membri devono avere nomi univoci all’interno di una struttura, ma strutture diverse possono avere membri con lo stesso nome

¨ 

I nomi dei membri possono coincidere con nomi di variabili o funzioni

int x;

struct a { char x; int y; }; //corretto

struct b { int w; float x; }; //corretto

(7)

Accesso ai membri di una struttura

¨ 

Si accede ai membri mediante la notazione con punto:

<nome-variabile>.<nome-mebro>

¨ 

Ogni membro si usa come una normale variabile del tipo corrispondente a quello del membro

struct punto { int x, y;

};

struct punto p1, p2;

p1.x = 10;

p1.y = -2;

p2.x = 5;

p2.y = 7;

struct carta { int valore;

char seme;

};

struct carta c1, c2;

c1.valore = 5;

c1.seme = 'Q';

Puntatori a strutture

¨ 

Se ho un puntatore a una struct, per accedere ai suoi membri posso usare l’operatore -> (freccia, scritta con meno e maggiore)

struct data {

int giorno, mese, anno;

};

struct data d;

struct data *pd = &d; //pd è un puntatore alla

struttura d

pd->giorno = 7; //equivalente a (*p).giorno

pd->mese = 1; //equivalente a (*p).mese

pd->anno = 2009; //equivalente a (*p).anno

(8)

Operazioni su Strutture

¨ 

Si possono assegnare variabili di un tipo struttura a variabili dello stesso tipo struttura.

struct data d1, d2;

d1 = d2;

¨ 

Non è possibile effettuare il confronto tra due variabili di tipo struttura.

struct data d1, d2;

if(d1 == d2) … //Errore!

ma è necessario confrontare ogni singolo membro:

if(d1.giorno==d2.giorno && d1.mese==d2.mese &&

d1.anno==d2.anno)…

Il motivo è legato all’allineamento in memoria (dato che lo spazio intermedio tra i membri ha contenuto indefinito e il C non può confrontare efficientemente le due zone di memoria, delega il compito all’utente)

Esempio

#include <stdio.h>

struct complesso { float im;

float re;

};

int main(void) {

struct complesso c1,c2,somma;

c1.re = 3.; c1.im = 4.;

c2.re = 2.; c2.im = 3.;

somma.im = c1.im + c2.im;

somma.re = c1.re + c2.re;

printf("la somma di %f+i%f e %f+i%f è %f+i%f\n", c1.re,c1.im,c2.re,c2.im,somma.re,somma.im);

return 0;

}

(9)

Attenzione

struct pippo { int codice;

char nome[20];

float dato;

};

struct pippo vrb1, vrb2;

...

vrb2 = vrb1;

CORRETTO

struct pippo { int codice;

char nome[20];

float dato;

};

struct pluto { int codice;

char nome[20];

float dato;

};

struct pippo vrb1;

struct pluto vrb2;

...

vrb2 = vrb1;

ERRATO

Perché sia ammesso l’assegnamento, non è sufficiente che le due strutture abbiano gli stessi campi, ma devono essere dichiarate con lo stesso tipo

(il motivo è ancora legato al fatto che l’allineamento dei dati in memoria potrebbe essere diverso)

Definizione di nuovi tipi

(10)

Definizione nuovo tipo

Con la parola chiave typedef si definisce un “nuovo tipo” (in realtà un’abbreviazione per un tipo esistente):

typedef <tipo_esistente> <nuovo_tipo>;

Ad esempio

typedef int votoesame;

definisce un nuovo tipo, votoesame , che corrisponde al tipo

int

Quando dichiarerò una variabile potrò scrivere

votoesame esame1, esame2;

Definizione nuovo tipo

Definire un nuovo tipo è utile per rendere più leggibile il

codice o per “nascondere” al programmatore che usa un

nostro modulo come realizziamo internamente al modulo

una certa funzionalità

(11)

Definizione nuovo tipo

Posso usare typedef anche sulle strutture:

typedef struct {

int codice;

char nome[20];

float dato;

} nuovotipo;

nuovotipo var1, var2;

nuovotipo var3;

Abbiamo definito un nuovo tipo derivato

nuovotipo

Grazie a typedef possiamo evitare di scrivere la parola chiave struct quando dichiariamo le variabili

Esempio: Vettori parzialmente riempiti

(12)

Vettori parzialmente riempiti

¨ 

Definiamo una struttura dati in cui usiamo un vettore a cui aggiungiamo via via degli interi fino a

riempire interamente il vettore

Vettori parzialmente riempiti

¨ 

Il vettore occupa in memoria N celle (es. N interi)

¨ 

Ma contiene dati solo per una parte

¨ 

ff (first free) è l’indice della prima posizione libera, dove si dovrà eventualmente inserire il prossimo elemento del vettore

0 ff N-1

(13)

Vettori parzialmente riempiti

#define N 20 typedef struct {

int vettore[N];

int ff;

} vpr_int;

int main(void) {

vpr_int miovettore;

}

Vettori parzialmente riempiti

¨ 

Devo però preoccuparmi di inizializzare la struttura

¨ 

Quanto vale all’inizio ff?

¨ 

Se non la inizializzassi, potrebbe avere

“casualmente” qualsiasi valore, anche negativo

¨ 

È necessario, quindi, definire una funzione di

inizializzazione da invocare prima di utilizzare un

vettore parzialmente riempito

(14)

Vettori parzialmente riempiti

¨ 

Inizializzazione:

vpr_int init(vpr_int vpr) { vpr.ff = 0;

return vpr;

}

¨ 

La funzione verrà invocata in questo modo:

int main(void) {

vpr_int miovettore;

miovettore = init(miovettore);

}

Vettori parzialmente riempiti

¨ 

Stampa del vettore:

void print(vpr_int vpr) { int i;

for(i=0; i<vpr.ff; i++)

printf("elemento %d: %d\n", i, vpr.vettore[i]);

}

(15)

Vettori parzialmente riempiti

¨ 

Funzione che determina se il vettore è pieno o se c’è ancora spazio per inserire elementi:

int pieno(vpr_int vpr) { if (vpr.ff >= N)

return 1;

else

return 0;

}

Vettori parzialmente riempiti

¨ 

Inserimento di un elemento:

vpr_int inserisci(vpr_int vpr, int n) { if (pieno(vpr))

return vpr;

vpr.vettore[vpr.ff] = n;

vpr.ff++;

return vpr;

}

(16)

Vettori parzialmente riempiti

¨ 

Ricerca dell’indice in cui è memorizzato un elemento:

int cerca(vpr_int vpr, int n) { int i;

for (i = 0; i < vpr.ff; i++) if (vpr.vettore[i] == n) return i;

return -1;

}

Vettori parzialmente riempiti

¨ 

Esempio di un main in cui testiamo il codice

int main(void) { vpr_int miovettore;

miovettore = init(miovettore);

int i;

for (i = 0; i <= N; i++) { printf("inserisco %d.\n", i);

miovettore = inserisci(miovettore, i);

}

(17)

Vettori parzialmente riempiti

i = cerca(miovettore, N);

if (i == -1)

printf("%d non trovato.\n", N);

else

printf("%d trovato all'indice %d.\n", N, i);

i = cerca(miovettore, 0);

if (i == -1)

printf("%d non trovato.\n", 0);

else

printf("%d trovato all'indice %d.\n", 0, i);

return 0;

}

Strutture come parametri

¨ 

Attenzione, quando si passa come parametro una struttura, questa viene copiata interamente anche quando un membro della struttura è un array

¨ 

Passare come parametro una struttura può quindi risultare inefficiente

¨ 

Usando i puntatori, si possono passare i parametri in modo più

efficiente: per “riferimento”

(18)

Strutture come parametri

¨ 

Esempio di funzione stampa modificata con il passaggio per riferimento:

void stampa(vpr_int *pv) { int i;

for(i=0; i < pv->ff; i++)

printf("elemento %d: %d\n", i, pv->vettore[i]);

}

int main(void) { vpr_int miovettore;

stampa(&miovettore);

}

Conversione stringhe

(19)

Conversione degli argomenti

Per convertire gli argomenti da stringa in tipi primitivi si possono usare le funzioni di stdlib.h:

¨ 

int atoi(char *s) : da stringa a intero

¨ 

long atol(char *s) : da stringa a long

¨ 

double atof(char *s) : da stringa a double Esempio:

¨ 

int ivalue = atoi(argv[1]);

Conversione degli argomenti

¨ 

Purtroppo atoi, atol e atod non effettuano controlli: se il parametro s non può essere convertito, viene comunque restituito un valore senza segnalare errori

¨ 

Quindi sono da usare solo quando si è certi che l’argomento sia una stringa convertibile in intero

¨ 

In generale bisognerebbe invece usare le più

complesse strtol e strtod (consultare il man per il

loro uso)

(20)

Argomenti dalla linea di comando

Argomenti dalla linea di comando

¨ 

Permettono di passare a un programma parametri da linea di comando. Ad esempio:

$myprog par1 par2

¨ 

Bisogna definire il main in questo modo:

int main(int argc, char *argv[])

¨ 

Il C mette a disposizione del programma un vettore di stringhe

(cioè un vettore di vettori di char); ogni stringa corrisponde a

un parametro

(21)

Argomenti dalla linea di comando

¨ 

Più in dettaglio:

¤ 

argc è il numero di parametri, incluso l’eseguibile stesso (quindi argc >= 1)

¤ 

argv è un vettore di stringhe: ogni suo elemento argv[i]

è una stringa che corrisponde a un parametro (casi particolari:

n 

argv[0] è il nome dell’eseguibile stesso,

n 

argv[argc] è NULL)

Argomenti dalla linea di comando

¨ 

Ad esempio, con $myprog par1 par2 il C crea le seguenti variabili:

NULL

argv

m y p r o g

p a r 1

\0 p a r 2

\0 argc 3

(22)

Argomenti dalla linea di comando

¨ 

Esempio di un programma che stampa gli argomenti dalla linea di comando:

#include <stdio.h>

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

int i;

printf("argc = %d\n", argc);

for(i=0; i<argc; i++)

printf("argv[%d] = %s\n", i, argv[i]);

return 0;

}

Controllo degli argomenti

Esempio di programma che calcola la somma dei suoi argomenti interi

#include <stdio.h>

#include <stdlib.h>

int main (int argc, char* argv[]) { int sum = 0, i;

if (argc < 3) { //controllo della cardinalità degli argomenti

printf("Sono richiesti almeno due parametri.\nUso: %s num1 num2 ... numN\n", argv[0]);

exit(EXIT_FAILURE);

}

for (i=0; i<argc; i++) sum += atoi(argv[i]);

printf("La somma totale è %d.\n", sum);

return EXIT_SUCCESS;

}

(23)

v 

I/O su canali standard

v 

I/O su file

v 

I/O su stringhe

I/O

Funzioni di I/O

¨ 

Abbiamo già visto l’uso di scanf e printf per fare input/output sui canali standard

¨ 

Esistono altre funzioni che permettono di fare I/O

Scrittura:

con uso implicito di file (stdout) int putchar(int c);

int puts(char *s);

int printf(const char

*format,...);

con uso esplicito di file int fputc(int c, FILE

*stream);

int fputs(char *s, FILE

*stream);

int putc(int c, FILE

Lettura:

con uso implicito di file (stdin) char* gets(char* s);

int getchar(void);

int scanf(const char *format,…);

con uso esplicito di file

int fgetc(FILE *stream);

char *fgets(char *s, int size, FILE *stream);

int ungetc(int c, FILE *stream);

(24)

Scrivere sullo standard output

Oltre alla printf ci sono:

int putchar(int c);

¤ 

scrive un carattere sullo standard output (file speciale corrispondente al terminale)

¤ 

restituisce un numero intero corrispondente alla codifica ASCII del carattere scritto, EOF in caso di errore

int puts(char *s);

¤ 

scrive una stringa sullo standard output

¤ 

restituisce un numero non negativo in caso di successo, EOF in caso di errore

Leggere dallo standard input

Oltre alla scanf ci sono:

¨ 

int getchar(void);

¤ 

legge un carattere dallo standard input (tastiera) e lo restituisce come

carattere, EOF in caso di errore.

(25)

Esempi d’uso

#include <stdio.h>

int main(void) {

char c = getchar(); //legge un carattere da

terminale (anche "a capo" è un carattere: è '\n')

putchar(c); //stampa a video il carattere in c return 0;

}

Molto spesso getchar è usata per sospendere l’esecuzione di un programma in modo che l’utente possa visionare risultati intermedi

I/O su FILE

¨ 

Ogni funzione che opera su file utilizza una struttura chiamata FILE definita in stdio.h

¨ 

Senza entrare nei dettagli, è sufficiente sapere che FILE contiene informazioni riguardanti un file e che ci permette di operare su di esso. Per esempio, tiene traccia dell’offset, cioè il punto del file a cui si è arrivati a leggere o scrivere. FILE viene allocata quando si apre un file e deallocata quando il file viene chiuso

¨ 

Prima di operare su un file, occorre aprirlo con la funzione fopen

¨ 

Si ottiene un puntatore alla struttura FILE che bisognerà poi passare come parametro alle funzioni di lettura/scrittura

¨ 

Quando si termina, occorre chiudere il file con fclose

(26)

Apertura di FILE

FILE *fopen(const char *path, const char *mode);

¤  argomenti:

n il percorso (nome compreso) del file che si vuole aprire

n il modo:

n "r" à lettura (il file deve esistere) [esistono anche "rb", "r+", "rb+"]

n "w" à scrittura (crea un file vuoto ed elimina un eventuale file già esistente) [esistono anche "wb", "w+", "wb+"]

n "a" à accoda (append; se il file esiste, scrive in coda al file; se non esiste lo crea vuoto) [esistono anche "ab", "a+", "ab+"]

n Consultare il man per saperne di più

¤  restituisce un puntatore a una struttura di tipo FILE se l’istruzione va a buon fine. Lo stream è posizionato:

n al primo byte del file (read/write)

n all’ultimo byte del file (append)

n NULL se si verifica qualche errore (ad es. non si hanno i diritti per leggere o scrivere il file o il file da leggere non esiste)

I/O su FILE

¨ 

fscanf e fprintf sono le versioni relative ai file di scanf e printf

¨ 

int fscanf(FILE *fp, formato, argomenti)

¨ 

int fprintf(FILE *fp, formato, argomenti)

¨ 

I parametri e il funzionamento sono identici a quelli delle funzioni scanf e printf tranne per la presenza del parametro di tipo FILE* che identifica il file su cui le funzioni devono operare

¨ 

Abbiamo già visto fgets:

¨ 

char* fgets(char *str, int size, FILE

*stream);

¨ 

Ricordate: In UNIX un file è un flusso (stream) di caratteri

terminato dal carattere speciale EOF

(27)

Chiusura di FILE

int fclose(FILE *fp);

¤ 

Richiede un puntatore a un file (aperto)

¤ 

Restituisce 0 se la chiusura avviene con successo, EOF se con fallimento.

In entrambi i casi, successivi tentativi di accedere al file chiuso falliranno

¨ 

È molto importante usare sempre fclose quando si è terminato di usare un file:

¤ 

l'accesso a un file (qualunque linguaggio di programmazione si usi) è gestito dal sistema operativo, che deve allocare apposite strutture per lo specifico processo richiedente

¤ 

fopen effettua una richiesta di allocazione al sistema operativo

¤ 

fclose rilascia al sistema operativo le strutture usate fino a quel momento; poiché le risorse di un computer sono sempre limitate, se non si dealloca mai si rischia di terminare le risorse

Accesso ai FILE

¨ 

fscanf/fprintf successive fanno avanzare lo stream legato al file

#include <stdio.h>

#include <stdlib.h>

int main(void) {

FILE *fp = fopen("dati.txt", "r");

if(fp == NULL) {

perror("Errore nell’apertura del file");

exit(EXIT_FAILURE);

} int x;

while(fscanf(fp, "%d", &x) != EOF) printf("\n x= %d", x);

fclose(fp);

return EXIT_SUCCESS;

void perror(char* s) è una funzione il cui prototipo è in stdio.h che, invocata dopo un errore in una system call, stampa la stringa passata come argomento seguita da un messaggio sulle cause dell’errore

EXIT_FAILURE e EXIT_SUCCESSS sono costanti simboliche definite in stdlib.h

(28)

Esempio alternativo

#include <stdio.h>

#include <stdlib.h>

int main(void) {

FILE *fp = fopen("dati.txt", "r");

if (fp == NULL){…} //come nella slide precedente int x;

while(!feof(fp)) { fscanf(fp, "%d", &x);

printf("\n x=%d", x);

}

fclose(fp);

return EXIT_SUCCESS;

}

int feof(FILE *f)è una funzione il cui prototipo è in stdio.h.

Restituisce un numero diverso da zero (e quindi corrispondente a vero per il C) quando siamo arrivati alla fine del file *f

Flussi standard

¨ 

Ogni processo in esecuzione è associato a tre FILE* speciali, che risultano già aperti quando si lancia il programma:

¤ 

FILE *stdin //standard input, di solito la

tastiera

¤ 

FILE *stdout //standard output, di solito lo

schermo

¤ 

FILE *stderr //standard error, di solito lo

schermo

¨ 

scanf è quindi un caso particolare di fscanf , in cui si usa

stdin come parametro FILE *

¤ 

scanf("…", …) equivale a fscanf(stdin, "…", …)

¨ 

Analogamente printf è un caso particolare di fprintf :

¤ 

printf("…",…) equivale a fprintf(stdout, "…", …)

(29)

I/O su stringa

¨ 

Tramite le funzioni sscanf e sprintf è possibile operare (cioè leggere e scrivere dati) su una stringa anziché su file

¨ 

int sscanf(char *str, formato, argomenti);

¤ 

lettura da stringa; utile per parsificare dati con formato

¨ 

int sprintf(char *str, formato, argomenti);

¤ 

scrittura su stringa; utile per creare stringhe concatenando dati con formato

I/O su stringa: sprintf

char linea[128];

int x, y;

float val;

x = 25; y=7; val = 7.5F;

sprintf(linea, "%d %d :: %f", x, y, val);

Per effetto della sprintf linea corrisponde alla stringa

"25 7 :: 7.5"

che può essere mandata a video o scritta su file

(30)

I/O su stringa: sscanf

char linea[128] = "25 7 :: 7.5";

int x,y;

float val;

sscanf(linea, "%d %d :: %f", &x, &y, &val);

Per effetto della

sscanf

le variabili

x, y

e

val

saranno inizializzate come:

X ß 25, y ß 7, val ß 7.5F

¨ 

Attenzione: affinché la

sscanf

abbia successo la sottostringa " :: "

indicata nella stringa formato deve essere presente in

linea

¨ 

Se i dati rispettano un certo formato in ingresso la

sscanf

è in grado di

parsificare

linea

e compiere la conversione da stringa a tipo primitivo

Riferimenti

Documenti correlati

• Gli argomenti della linea di comando sono accessibili all’interno della funzione main. – il SO li inserisce sullo stack prima di attivare

•  Unnaming: distruzione di associazione fra nome e oggetto denotato (esempio: ambiente locale all’uscita di un blocco).

At the present time consultant posts are often advertised as requiring some special expertise, such as paediatric ophthalmology or

La funzione malloc restituisce un puntatore alla prima regione di memoria libera allocata nella heap; se non esiste memoria sufficiente, ritorna un null pointer... Notare

 nella funzione stessa, a cui viene passato il puntatore al primo elemento della matrice perché la funzione possa creare e inizializzare il vettore dinamico, il puntatore

ESEMPIO: alloca spazio per un intero e memorizza in p l’indirizzo della memoria allocata int *p;.. p = (int*)

prisons 4. Where electronic monitoring is used as a modality of execution of a prison sentence, in some jurisdictions those under electronic monitoring are

The total proportion of contacts who have symptoms of COVID-19 at initiation of tracing and are confirmed to have COVID-19 later is important to understand whether public