Ambito delle variabili

Slides:



Advertisements
Presentazioni simili
Puntatori Linguaggio C.
Advertisements

INFORMATICA Algoritmi fondamentali
Puntatori in C e C++.
Procedure e funzioni A. Ferrari.
Introduzione alla programmazione A. Ferrari. Il linguaggio C Nel 1972 Dennis Ritchie nei Bell Laboratories progettò il linguaggio C Il linguaggio possiede.
PUNTATORI Introduzione
MATLAB. Scopo della lezione Programmare in Matlab Funzioni Cicli Operatori relazionali Esercizi vari.
Introduzione al linguaggio C
Introduzione al linguaggio C Dr. Francesco Fabozzi Corso di Informatica.
Anno accademico Le classi di memorizzazione.
Indirizzi delle variabili A ogni variabile sono associati tre concetti fondamentali: il valore memorizzato; il tipo dati di appartenenza; lindirizzo. Il.
FUNZIONI DI BIBLIOTECA
Fondamenti di Informatica II Ingegneria Informatica / Automatica (A-I) Meccanica Prof. M.T. PAZIENZA a.a – 3° ciclo.
Caratteri e stringhe di caratteri
Argomenti dalla linea dei comandi Gli argomenti possono essere passati a qualsiasi funzione di un programma, compresa la main(), direttamente dalla linea.
Iterazione enumerativa (for)
Fondamenti di Informatica II Ingegneria Informatica / Automatica (A-I) Meccanica Prof. M.T. PAZIENZA a.a – 3° ciclo.
Funzioni definite dall’utente
PROGRAMMI DI RICERCA E ORDINAMENTO
Prof.ssa Chiara Petrioli -- Fondamenti di programmazione, a.a. 2009/2010 Corso di Fondamenti di programmazione a.a. 2009/2010 Prof.ssa Chiara Petrioli.
DIPARTIMENTO DI ELETTRONICA E INFORMAZIONE Puntatori Marco D. Santambrogio – Ver. aggiornata al 21 Marzo 2013.
DIPARTIMENTO DI ELETTRONICA E INFORMAZIONE Stringhe e Puntatori Marco D. Santambrogio – Ver. aggiornata al 18 Marzo 2013.
Informatica 2. Concetti fondamentali di programmazione Programmare vuol dire scrivere un algoritmo in un linguaggio che faccia funzionare un calcolatore.
Laboratorio di Linguaggi lezione VI: puntatori 2/3 Marco Tarini Università dellInsubria Facoltà di Scienze Matematiche, Fisiche e Naturali di Varese Corso.
Eliana minicozzi linguaggi L1 Lezione3.
Approfondimento delle classi
memoria gestita staticamente:
Le classi Definizione di classe Attributi e metodi di una classe Costruttori e distruttori Private e public Funzioni friend Il puntatore this.
Strutture dei sistemi di calcolo Funzionamento di un sistema di calcolo Struttura di I/O Struttura della memoria Gerarchia delle memorie Architetture di.
Esercizi FUNZIONI Passaggio di parametri per valore, variabili e tipi locali e globali, prototipo.
Esercizi Puntatori, struct con campi puntatore, puntatori a struct, rapporto tra array e puntatori. FUNZIONI Passaggio di parametri per indirizzo, passaggio.
Algebra Lineare Esercizi assegnati.
Le funzioni.
AN FI Un denominatoe comune Comandi u notazioni che esprimono azioni che, una volta eseguite, comportano una modifica permanente dello stato interno.
FUNZIONI... Una funzione permette di dare un nome a una espressione rendendola parametrica float f(){ return * sin(0.75); } float f1(int x) { return.
Corso di PHP.
2000 Prentice Hall, Inc. All rights reserved. Capitolo 10 (Deitel) Strutture, unioni ed enumerazioni Sommario Introduzione Definire le strutture.
Il linguaggio C Le funzioni C Language Il passaggio dei parametri
Un esempio: Calcolo della potenza n-esima di un numero reale
Prof.ssa Chiara Petrioli -- Fondamenti di programmazione, a.a. 2009/2010 Corso di Fondamenti di programmazione a.a. 2009/2010 Prof.ssa Chiara Petrioli.
Unità Didattica 3 Linguaggio C
Passaggio di parametri per indirizzo
Fopndamenti di programmazione. 2 La classe String Una stringa è una sequenza di caratteri La classe String è utilizzata per memorizzare caratteri La classe.
2000 Prentice Hall, Inc. All rights reserved. Capitolo 5 (Deitel) Le funzioni Indice degli argomenti Introduzione Moduli nei programmi C 5.3.
2000 Prentice Hall, Inc. All rights reserved. Attivazione di funzioni La chiamata/attivazione di funzione viene indicata citando il nome della funzione.
Corso JAVA Lezione n° 11 Istituto Statale di Istruzione Superiore “F. Enriques”
Complessità di un algoritmo
Prof.ssa Chiara Petrioli -- corso di programmazione 1, a.a. 2006/2007 Corso di Programmazione 1 a.a.2006/2007 Prof.ssa Chiara Petrioli Corso di Laurea.
1 Fabio Scotti ( ) Laboratorio di programmazione per la sicurezza Valentina Ciriani ( ) Laboratorio di programmazione Lezione 11 e 12 -
- prof. V. Riboldi - SOTTOPROGRAMMI IN TPASCAL METODO TOP DOWN.
Ripetizione La vera potenza dei programmi per computer risiede nella capacità di ripetere lo stesso calcolo o sequenza di istruzioni più volte, ogni volta.
Prof.ssa Chiara Petrioli -- corso di programmazione 1, a.a. 2006/2007 Corso di Programmazione 1 a.a.2006/2007 Prof.ssa Chiara Petrioli Corso di Laurea.
Sistemi e Tecnologie Informatiche Verifica di correttezza di un programma.
Vettori, indirizzi e puntatori Finora abbiamo usato gli indirizzi nel chiamare  la funzione scanf()  le altre funzioni per riferimento Tuttavia la vera.
Vettori (a una dimensione)
Parte 3 Lo stato: variabili, espressioni ed assegnazioni
DIPARTIMENTO DI ELETTRONICA E INFORMAZIONE Puntatori Marco D. Santambrogio – Ver. aggiornata al 11 Ottobre 2014.
Fondamenti di Informatica 2 Ingegneria Informatica Docente: Giovanni Macchia a.a
Fondamenti di Informatica II Ingegneria Informatica (A-I) Prof. M.T. PAZIENZA a.a – 3° ciclo.
Informatica 4 Funzioni. FUNZIONE: definizione MATEMATICA Relazione (o applicazione) binaria tra due insiemi A e B che associa a ogni elemento di A un.
Sottoprogrammi e funzioni
Procedure e funzioni In linguaggio C.
DIPARTIMENTO DI ELETTRONICA E INFORMAZIONE Puntatori Marco D. Santambrogio – Ver. aggiornata al 11 Marzo 2014.
1 Il linguaggio C Precisazioni sull’esperienza in laboratorio.
ALLOCAZIONE STATICA: LIMITI Per quanto sappiamo finora, in C le variabili sono sempre dichiarate staticamente –la loro esistenza deve essere prevista e.
Linguaggio C: Le basi Stefano Cagnoni e Monica Mordonini
Il C `e un linguaggio di programmazione di uso generale, originariamente sviluppato per la scrittura del sistema operativo Unix, ed oggi disponibile su.
“ Pseudocodice ” Un programma per computer lavorerà su in insieme di “ variabili ” contenenti i dati del problema, soluzioni intermedie, soluzioni finali.
1 Metodo I metodi sono uno strumento che i programmatori usano per strutturare i programmi, sia per renderli più facili da capire che per permettere il.
Linguaggio C: Funzioni e Puntatori Laboratorio di Programmazione Gruppo 1.
Transcript della presentazione:

Ambito delle variabili Esaminiamo più a fondo le variabili dichiarate in una funzione e le loro relazioni con quelle dichiarate in altre funzioni. Per loro natura, le funzioni C sono costruite come moduli indipendenti. Dato che le variabili create in una funzione sono disponibili solo alla funzione stessa, si dice che esse sono locali alla funzione. Questo termine si riferisce alla portata o ambito di una variabile, definito come la sezione del programma in cui la variabile è valida o nota. Una variabile può avere un ambito locale o globale. Una variabile (con ambito) locale è una variabile le cui locazioni di memoria sono state riservate da un’istruzione di dichiarazione presente nel corpo della funzione. Le variabili locali sono significative solo se usate in espressioni o istruzioni entro la funzione che le dichiara. Ciò significa che lo stesso nome di variabile può essere dichiarato e usato in più di una funzione: per ogni funzione che dichiara la variabile, viene creata una variabile distinta.

Tutte le variabili usate finora erano locali Tutte le variabili usate finora erano locali. Ciò è un risultato diretto dell’avere situato le istruzioni di dichiarazione entro le funzioni e di averle usate come istruzioni di definizione che fanno riservare al computer la memoria per la variabile dichiarata. Ma le istruzioni di dichiarazione possono essere situate fuori dalle funzioni, e non devono necessariamente agire come definizioni che fanno riservare nuove aree di memoria per la variabile dichiarata. Una variabile (con ambito) globale è una variabile la cui area di memoria è stata creata da un’istruzione di dichiarazione esterna a qualsiasi funzione. Perciò essa è detta anche esterna. Le variabili globali possono essere usate da tutte le funzioni di un programma che siano fisicamente situate dopo le loro dichiarazioni. Ciò è illustrato dal programma seguente, dove abbiamo usato volutamente lo stesso nome di variabile all’interno di entrambe le funzioni contenute nel programma.

int primonum; /* crea la variabile GLOBALE primonum */ #include <stdio.h> void main(void) { int secnum; /* crea una prima variabile LOCALE secnum */ void valfun(void); primonum = 10; secnum = 20; printf("\nDa main(): 1° numero = %d",primonum); printf("\nDa main(): 2° numero = %d\n",secnum); valfun(); printf("\nDa main() ancora: 1° numero = %d",primonum); printf("\nDa main() ancora: 2° numero = %d",secnum); } void valfun(void) { int secnum; /*crea una seconda variabile LOCALE secnum*/ secnum = 30; /*influenza solo questa variabile LOCALE*/ printf("\nDa valfun(): 1° numero = %d",primonum); printf("\nDa valfun(): 2° numero = %d\n",secnum); primonum = 40; /*cambia primonum per entrambe le funzioni*/ return; }

La variabile primonum è globale, perché la sua area di memoria è creata da una istruzione di dichiarazione situata al di fuori di una funzione. Dato che entrambe le funzioni main() e valfun() sono definite dopo la dichiarazione di primonum, entrambe possono usare tale variabile senza bisogno di un’ulteriore dichiarazione. Il programma contiene anche due variabili locali separate, entrambe di nome secnum. La memoria per la variabile secnum nominata in main() è creata dall’istruzione di dichiarazione situata in main(). Una differente area di memoria per la variabile secnum in valfun() è creata dall’istruzione di dichiarazione situata nella funzione valfun().

Ciascuna variabile di nome secnum è locale alla funzione in cui è creata la sua area di memoria, e può essere usata solo dall’interno della relativa funzione.

Così, quando secnum() è usata in main(), si accede alll’area di memoria riservata da main() per la sua variabile secnum; valfun(), si accede all’area di memoria riservata da valfun() per la sua variabile secnum. Quando si esegue il programma, si ottiene la seguente uscita:

Dato che primonum è una variabile globale, il suo valore può essere usato e cambiato sia da main() sia da valfun(). Inizialmente entrambe le funzioni visualizzano il valore 10 che main() ha memorizzato in primonum. Prima di ritornare, valfun() cambia il valore di primonum in 40, che è il valore visualizzato quando la variabile primonum è visualizzata successivamente dall’interno di main(). Dato che ogni funzione “conosce” solo le sue proprie variabili locali, main() può inviare alla funzione printf() solo il valore della sua secnum, e valfun() può inviare a printf() solo il valore della sua secnum. Così, ogni qualvolta secnum è ottenuta da main() viene visualizzato il valore 20 valfun() viene visualizzato 30.

Il C non confonde le due variabili secnum peché in un dato momento può essere eseguita una sola funzione, e si accede solo all’area di memoria per la variabile creata dalla funzione correntemente in esecuzione. Se la funzione usa una variabile non locale a essa, il programma cerca il suo nome nelle aree di memoria globale.

Uso delle variabili globali Uso delle variabili globali. Anziché passare variabili a una funzione, sarebbe possibile rendere tutte le variabili globali. Ciò non va fatto, perché il rendere indiscriminatamente tutte le variabili globali distrugge istantaneamente tutte le precauzioni fornite dal C per rendere le funzioni indipendenti e isolate l’una dall’altra, compresa la necessità di progettare accuratamente il tipo di argomenti necessari a una funzione, le variabili usate in essa e il valore restituito. L’uso di sole variabili globali può essere controproducente, specialmente in programmi di grandi dimensioni che contengono molte funzioni create dall’utente. Dato che tutte le variabili di una funzione devono essere dichiarate, se si creano funzioni che usano variabili globali è necessario scrivere le appropriate dichiarazioni globali all’inizio di ogni programma che usi la funzione, dato che esse non si trovano più nella funzione stessa.

Ancora più lunga e noiosa è la ricerca di un errore in un lungo programma che usi variabili globali, dato che esse possono essere usate e modificate da qualsiasi funzione che segua la dichiarazione globale. Tuttavia, le variabili globali possono anche risultare molto utili: se più funzioni richiedono l’accesso a un gruppo di tabelle, le variabili globali consentono alle funzioni di compiere cambiamenti a una stessa tabella senza la necessità di passaggi multipli di tabelle.

Classi di memorizzazione delle variabili Come abbiamo visto, l’ambito di una variabile si può considerare come lo spazio entro il programma dove la variabile è valida e può essere usata. Dato un programma, si può prendere una matita e tracciare un riquadro intorno alla sezione del programma che rappresenta l’ambito di validità della variabile. In aggiunta alla dimensione spaziale rappresentata dal suo ambito, una variabile possiede anche una dimensione temporale, che si riferisce alla quantità di tempo durante il quale le locazioni di memoria sono riservate per essa. Ad es., tutte le locazioni di memoria di variabili sono rilasciate al computer quando termina l’esecuzione di un programma.

Tuttavia, mentre un programma è ancora in esecuzione, aree di memoria per variabili temporanee sono riservate e successivamente restituite di nuovo al computer. Dove e per quanto tempo le locazioni di memoria per le variabili siano mantenute, prima di essere rilasciate, può essere determinato dalla classe di memorizzazione di una variabile. Le quattro classi di memorizzazione disponibili sono: Se si usa uno di questi nomi di classi, esso deve essere situato in un’istruzione di dichiarazione prima del tipo dati della variabile. Esempi di istruzioni di dichiarazione che comprendono una designazione di classe di memorizzazione sono indicati in tabella.

Per illustrare il significato della classe di memorizzazione di una variabile, consideriamo dapprima le variabili locali (quelle create entro una funzione) e poi quelle globali (create fuori da una funzione).

Classi di memorizzazione di variabili locali Le variabili locali possono essere membri solo delle classi di memorizzazione auto. Se nell’istruzione di dichiarazione non s’inserisce alcuna descrizione di classe, la variabile è assegnata automaticamente alla classe auto (da automatico), che è pertanto la classe preimpostata del C. Perciò tutte le variabili locali finora usate, essendo stata omessa la designazione di classe, erano auto. L’area di memoria per le variabili locali automatiche è riservata o creata automaticamente ogni volta che viene chiamata una funzione che dichiari variabili automatiche. Fin tanto che la funzione non abbia restituito il controllo alla sua funzione chiamante, tutte le variabili automatiche locali alla funzione sono “vive”, cioè è disponibile per esse l’area di memoria.

Quando la funzione chiamata restituisce il controllo alla sua funzione chiamante, le sue variabili automatche locali “muoiono”, cioè la memorizzazione per esse è restituita al computer. Il processo si ripete ogni volta che una funzione viene chiamata.

Come esempio, consideriamo il programma seguente, dove la funzione testauto() è chiamata tre volte dalla main(). #include <stdio.h> void main(void) { int cont; /* cont è variabile AUTO LOCALE */ void testauto(void); /* prototipo di funzione */ for(cont = 1; cont <= 3; ++cont) testauto(); } void testauto(void) int num = 0; /*crea e pone=0 la variabile AUTO num*/ printf("\nLa variabile automatica num vale %d", num); ++num; return;

Esso produce un’uscita in apparenza inaspettata: Il valore della variabile automatica num è 0 Ciò perché, ogni volta che si chiama testauto(), la variabile automatica num è creata e inizializzata a 0. Quando la funzione ritorna il controllo a main(), la variabile num è distrutta insieme a qualsiasi valore memorizzato in essa. Così, l’effetto di incrementare num in testauto(), prima dell’istruzione return della funzione, si perde quando il controllo è restituito a main().

static. L’uso di variabili automatiche è appropriato per la maggior parte delle applicazioni. Tuttavia vi sono casi in cui si vorrebbe che una funzione ricordi i valori tra le chiamate successive, e questo è lo scopo della classe di memorizzazione static. Una variabile locale dichiarata static fa sì che il programma mantenga la variabile e il suo ultimo valore anche quando la funzione che l‘ha dichiarata non è più in esecuzione. Una variabile statica locale non viene creata e distrutta ogni volta che si chiama la funzione che la dichiara ma, una volta creata, rimane in essere per tutta la vita del programma. Perciò l’ultimo valore memorizzato nella variabile quando è terminata l’esecuzione della funzione è disponibile per la funzione la prossima volta che essa sarà chiamata.

Dato che le variabili statiche locali mantengono i loro valori, esse non vengono inizializzate in un’istruzione di dichiarazione nella stessa maniera delle variabili automatiche. Infatti, consideriamo l’istruzione di dichiarazione del programma precedente che crea e imposta a zero la variabile automatica num int num = 0; Essa è detta inizializzazione di run time, dato che l’inizializzazione ha luogo ogni volta che s’incontra l’istruzione. Questo tipo d’inizializzazione resetta il valore della variabile a zero ogni volta che la funzione è chiamata, distruggendone il valore. Invece l’istruzione di dichiarazione static int num = 0; crea una variabile statica (sia locale sia globale) e vi inserisce un valore d’inizializzazione una sola volta, quando il programma è compilato.

Nelle successive chiamate della funzione, il valore nella variabile è mantenuto senza venire ulteriormente inizializzato. Perciò, se nel programma precedente modifichiamo come segue l’istruzione di dichiarazione della variabile num #include <stdio.h> void main(void) { int cont; /* cont è variabile AUTO LOCALE */ void test(void); /* prototipo di funzione */ for(cont = 1; cont <= 3; ++cont) test(); } void test(void) static int num = 0; /*num è variabile STATICA LOCALE*/ printf("\nLa variabile statica num vale ora %d", num); ++num; return;

otteniamo la seguente uscita: Il valore della variabile statica num è ora 0 Il valore della variabile statica num è ora 1 Il valore della variabile statica num è ora 2 Come si vede, la variabile statica num è ipostata a zero solo una volta. Quindi la funzione test() la incrementa subito prima di restituire il controllo a main(). Il valore che num ha quando si lascia la funzione test() è trattenuto e visualizzato quando la funzione è chiamata di nuovo.

Inoltre, a differenza delle variabili automatiche A differenza delle variabili automatiche, che possono essere inizializzate o con costanti o con espressioni che usano sia costanti sia variabili inizializate in precedenza, le variabili statiche possono essere inizializzate solo usando costanti o espressioni di costanti quali 3.2+8.0. Inoltre, a differenza delle variabili automatiche le variabili statiche sono impostate a zero quando non sia fatta una inizializzazione esplicita. Perciò, nel programma precedente, la specifica inizializzazione di num a zero non è necessaria.

register. L’ultima classe di memorizzazione disponibile per le variabili locali è la classe register, che viene usata meno diffusamente delle classi auto o static. Le variabili register hanno la stessa durata temporale delle variabili auto, coè una variabile register locale è creata quando si entra nella funzione che la dichiara, e distrutta quando termina l’esecuzione della funzione. L’unica differenza tra le variabili auto e register è il posto dove è situata la memoria per la variabile. La memoria per tutte le variabili (locali e globali), tranne le register è riservata nell’area di memoria del computer. Ma i computer hanno anche zone aggiuntive di memoria ad alta velocità situate direttamente nell’unità centrale di elaborazione, che possono essere usate anche per la memorizzazione di variabili. Queste speciali aree di memoria ad alta velocità sono dette registri.

Dato che essi sono fisicamente situati nell’unità centrale di elaborazione, si può accedere a essi più velocemente che alle normali aree di memoria situate nell’unità di memoria del computer. Inoltre le istruzioni che si riferiscono ai registri richiedono tipicamente meno spazio di quelle che si riferiscono a locazioni di memoria, dato che vi sono meno registri accessibili che locazioni di memoria. Oltre a diminuire le dimensioni di un programma C compilato, l’uso di variabili register può anche aumentare la velocità di esecuzione di un programma, se il computer in uso supporta questo tipo dati. Se non lo supporta, oppure se le variabili register dichiarate eccedono la capacità in registri del computer, le variabili dichiarate nella classe di memorizzazione register sono convertite automaticamente nella classe di memorizazione auto.

non si può usare l’indirizzo di una variabile L’unica restrizione nell’uso della classe di memorizzazione register è che non si può usare l’indirizzo di una variabile register tramite l’operatore & in quanto i registri non hanno indirizzi di memoria standard.

Classi di memorizzazione di variabili globali Ricordiamo che le variabili esterne o globali sono create da istruzioni di dichiarazione esterne a una funzione, e non vengono create e distrutte con la chiamata a una funzione. Una volta creata, una variabile globale esiste finché termina l’esecuzione del programma in cui è dichiarata. Perciò le variabili globali non possono essere dichiarate come membri di classi di memorizzazione auto o register, che sono create e distrutte mentre il programma è in esecuzione, mentre possono essere dichiarate come membri delle classi di memorizzazione static extern

Le classi static ed extern hanno effetto solo sull’ambito, e non sulla durata temporale delle variabili globali. Come le variabili locali static, tutte le variabili globali sono inizializzate a zero al momento della compilazione se non è presente una esplicita inizializzazione.

extern. Lo scopo della classe di memorizzazione extern è di estendere l’ambito di una variabile globale oltre i suoi confini naturali Per comprendere ciò, osserviamo che tutti i programmi visti finora erano sempre contenuti in un solo file. Ciò non è richiesto dal C. I programmi di grandi dimensioni consistono tipicamente in molte funzioni, memorizzate in più file. Uno schema di esempio è mostrato nella figura seguente, dove le tre funzioni main(), funz1(), funz2() sono memorizzate in un file, e le due funzioni funz3(), funz4()in un altro.

Per i file illustrati in figura, le variabili globali prezzo, ricavo e coupon dichiarate in file1.c possono essere usate solo dalle funzioni main(), funz1() e funz2() in questo file. La singola variabile globale tasso, dichiarata in file2.c può essere usata solo dalle funzioni funz3() e funz4() in file2.c. Tuttavia, sebbene la variabile prezzo sia stata creata in file1.c, potremmo volerla usare in file2.c. Ciò è possibile se s’inserisce in file2.c l’istruzione di dichiarazione extern int prezzo; come indicato in figura.

Dato che questa istruzione è all’inizio di file2 Dato che questa istruzione è all’inizio di file2.c, essa estende a file2.c l’ambito della variabile prezzo, che così può essere usata in funz3() e funz4(). Così, se s’inserisce in funz4() l’istruzione extern float ricavo; si estende a funz4() l’ambito della variabile globale ricavo, creata in file1.c. Analogamente, l’ambito della variabile globale tasso, creata in file2.c, è esteso a funz1() e funz2() se s’inserisce prima di funz1() l’istruzione di dichiarazione extern double tasso; Si osservi che tasso non è disponibile in main(). Un’istruzione di dichiarazione che contenga la parola extern è differente da qualsiasi altra istruzione di dichiarazione, in quanto

non causa la creazione di una nuova variabile riservandole nuova memoria Essa informa semplicemente il computer che la variabile già esiste e può ora essere usata. La memorizzazione effettiva per la variabile deve essere creata in qualche altra parte del programma usando una, e solo una, istruzione di dichiarazione globale, in cui non vi sia la parola extern. L’inizializzazione di una variabile globale può essere fatta con la sua dichiarazione originale, mentre non è consentita all’interno di una dichiarazione extern, e causerebbe un errore di compilazione. L’esistenza della classe di memorizzazione extern è la ragione per cui abbiamo distinto con cura la creazione e la dichiarazione di una variabile. Infatti le istruzioni di dichiarazione che contengono la parola extern non creano nuove aree di memoria, ma estendono solo l’ambito di variabili globali esistenti.

static. L’ultima classe globale, static, è usata per evitare l’estensione di una variabile globale in un secondo file. Le variabili static globali sono dichiarate nella stessa maniera delle static locali, tranne per il fatto che l’istruzione di dichiarazione è situata fuori una funzione. L’ambito di una variabile static globale non si può estendere oltre il file in cui è dichiarata. Ciò fornisce un grado di riservatezza per le variabili static globali. Dato che esse sono “conosciute” e possono essere usate solo nel file in cui sono dichiarate, altri file non possono accedervi o cambianre i valori. Le variabili static globali non possono essere successivamente estese a un secondo file con un’istruzione di dichiarazione extern, dato che ciò causerebbe un errore di compilazione.

Anche i vettori, come le variabili scalari, possono essere dichiarati dentro o fuori una funzione. I vettori dichiarati dentro una funzione sono detti vettori locali, quelli dichiarati fuori sono detti vettori globali. Consideriamo, ad es., la seguente sezione di codice:

Come le variabili scalari, anche i vettori globali e i locali static sono creati una sola volta, in fase di compilazione, e conservano i loro valori finché termina l’esecuzione di main(). I vettori auto sono creati e distrutti ogni volta che viene chiamata la funzione in cui sono locali. Perciò i vettori litri, dist e corse sono creati una sola volta, mentre km è creato e distrutto 10 volte. Vettori a due dimensioni. Come i vettori a una dimensione, anche quelli a due dimensioni possono essere dichiarati dentro o fuori una funzione. Quelli dichiarati all’interno di una funzione sono locali, quelli all’esterno sono globali. Ad es., il seguente segmento di codice:

int bingo[2] [3]; main() { static double lotto[104] [6]; double totip[52] [6]; . } dichiara: bingo come un vettore a due dimensioni globale di 2 righe e 3 colonne lotto come un vettore a due dimensioni statico locale di 104 righe e 6 colonne totip come un vettore automatico locale di 52 righe e 6 colonne.

Chiamate per valore e per riferimento Nel normale corso delle operazioni, una funzione chiamata riceve valori dalla sua funzione chiamante, li memorizza nei suoi propri argomenti locali, manipola questi in modo opportuno ed eventualmente restituisce un singolo valore. Questo metodo di chiamare una funzione e passarle valori è detto chiamata per valore a una funzione. La chiamata per valore è una caratteristica vantaggiosa del C, che consente di scrivere le funzioni come entità indipendenti che possono usare qualsiasi nome di variabile senza preoccuparsi se anche altre funzioni stiano usando lo stesso nome. Nello scrivere una funzione, è opportuno considerare gli argomenti o come variabili inizializzate, o come variabili alle quali saranno assegnati valori quando la funzione sarà eseguita. In nessun momento, tuttavia, la funzione chiamata ha accesso diretto a qualsiasi variabile locale contenuta nella funzione chiamante.

Vi sono però casi in cui conviene dare a una funzione chiamata accesso alle variabili locali della funzione chiamante, in modo che la funzione chiamata possa usare e cambiare i loro valori senza la conoscenza della funzione chiamante, nella quale le variabili locali sono dichiarate. Per fare ciò è necessario che alla funzione chiamata sia passato l’indirizzo della variabile. Una volta che la funzione chiamata abbia tale indirizzo, essa “sa dove la variabile vive”, e può accedere a essa usando l’indirizzo e l’operatore di indirezione *. Il passaggio di indirizzi è detto chiamata per riferimento a una funzione, dato che la funzione chiamata può riferire la, o accedere alla variabile usando l’indirizzo passato. Vediamo le tecniche necessarie per passare indirizzi a una funzione e fare sì che essa li accetti e li usi.

Passaggio, memorizzazione e uso degli indirizzi Passaggio, memorizzazione e uso degli indirizzi. Per passare, memorizzare e usare gli indirizzi si usano: l’operatore di indirizzo & i puntatori l’operatore di indirezione *. Consideriamo, come esempio, la seguente funzione principale: printf("\nIl minore è %lf", primonum); printf("\nIl maggiore è %lf", secnum); } #include <stdio.h> void main(void) { double primonum, secnum; void ordinum(double *, double *); /* prototipo */ printf("Scrivi due numeri: "); scanf("%lf %lf", &primonum, &secnum); ordinum(&primonum, &secnum); /* chiamata */

Essa passa gli indirizzi delle due variabili primonum e secnum alla funzione ordinum() (oltre che, come al solito, a scanf()), la quale confronta i valori contenuti negli indirizzi passati ed eventualmente li scambia, in modo che il valore più piccolo vada nel primo indirizzo. Se invece degli indirizzi di primonum e secnum fossero passati a ordinum() i loro valori (come avveniva in un programma precedente), la funzione non potrebbe scambiare i valori nelle variabili, perché non avrebbe accesso a esse. Nella funzione precedente il prototipo della funzione ordinum() void ordinum(double *, double *); dichiara che essa non restituisce un valore e si aspetta come argomenti due puntatori a (indirizzi di) variabili in doppia precisione. Perciò la successiva chiamata a ordinum() richiede che le siano passati due indirizzi di numeri in doppia precisione.

Nella intestazione di ordinum()vanno dichiarati due argomenti che possano memorizzare gli indirizzi passati, quali, ad es., double *num1_indir; double *num2_indir; (in tal modo sia num1_indir, sia num2_indir puntano a variabili in doppia precisione). Quindi l’intestazione sarà: ordinum(double *num1_indir, double *num_2indir) Prima di scrivere il corpo di ordinum() che confronti (e scambi se necessario) i due valori, controlliamo che i valori cui si accede usando gli indirizzi in num1_indir e num2_indir siano corretti, scrivendo il programma seguente:

#include <stdio.h> void main(void) { double primonum = 20.0, secnum = 5.0; void ordinum(double *, double *); /* prototipo */ ordinum(&primonum, &secnum); /* chiamata */ } void ordinum(double *num1_indir, double *num2_indir) { printf("Il numero il cui indirizzo è in num1_indir è %lf", *num1_indir); printf("\nIl numero il cui indirizzo è in num2_indir è %lf", *num2_indir); } Esso produce la seguente uscita: Il numero il cui indirizzo è in num1_indir è 20.000000 Il numero il cui indirizzo è in num2_indir è 5.000000

if (*num1_indir > *num2_indir) Osservazione. Nelle due chiamate a printf() in ordinum() si usa l’operatore di indirezione * per accedere ai valori memorizzati in primonum e secnum. ordinum() non ha conoscenza di questi nomi di variabile, ma ha l’indirizzo di primonum memorizzato in num1_indir, e quello di secnum in num2_indir. Avendo verificato che ordinum() può accedere alle variabili locali primonum e secnum di main(), possiamo ora scrivere l’effettiva funzione ordinum(). Essa confronta i valori nelle variabili primonum e secnum, controllando se sono nell’ordine desiderato, tramite l’istruzione: if (*num1_indir > *num2_indir) La funzione ordinum() è allora:

void ordinum (double *num1_indir, double *num2_indir) { double temp; if (*num1_indir > *num2_indir) temp = *num1_indir; *num1_indir = *num2_indir; *num2_indir = temp; } return; Si osservi che l’uso dei puntatori ha permesso di scambiare tra loro i due valori se essi non sono nell’ordine desiderato.

Confronto tra chiamata per valore e per riferimento Confronto tra chiamata per valore e per riferimento. Per confrontare il diverso funzionamento della chiamata per valore e della chiamata per riferimento a una funzione, consideriamo i due programmi seguenti, che calcolano il cubo di un numero: il primo passando il valore della base (chiamata per valore), #include <stdio.h> int main() { int numero = 5; printf("Il valore del numero è: %d\n", numero); printf("Il nuovo valore del numero è: %d\n", numero); } return n*n*n; int cuboVal(int); numero = cuboVal(numero); /*passa il valore di numero*/ int cuboVal(int n)

il secondo passando il suo indirizzo (chiamata per riferimento). #include <stdio.h> int main() { int numero = 5; printf("Il valore del numero è: %d\n", numero); printf("Il nuovo valore del numero è: %d\n", numero); } *punt_n = *punt_n * *punt_n * *punt_n; void cuboRif(int *); cuboRif(&numero); /* passa l'indirizzo di numero */ void cuboRif(int *punt_n)

Il primo programma passa il valore della variabile numero alla funzione cuboVal utilizzando una chiamata per valore. La funzione cuboVal eleva al cubo il suo argomento e restituisce a main() il nuovo valore utilizzando un’istruzione return. Il nuovo valore viene assegnato a numero nella funzione main(). Il secondo programma passa l’indirizzo della variabile numero alla funzione cuboRif utilizzando una chiamata per riferimento. La funzione cuboRif riceve come parametro un puntatore a un int chiamato punt_n. La funzione risolve il riferimento del puntatore ed eleva al cubo il valore puntato da punt_n (cioè n), quindi assegna il valore a *punt_n (che corrisponde al numero di main()), modificando quindi il valore di numero in main(). I due programmi producono, ovviamente, la stessa uscita: Il valore del numero è: 5 Il nuovo valore del numero è: 125

Bolle per riferimento. Come altro esempio, modifichiamo il programma dell’ordinamento a bolle, in modo che chiami (per valore) una funzione bolle_rif, la quale chiami a sua volta (per riferimento) una funzione scambia. #include <stdio.h> #define N 10 int main() { int a[N] = {22,5,67,98,45,32,101,99,73,10}; int i, passi; printf("Il vettore ordinato in ordine crescente è:\n"); for (i = 0; i < N; ++i) printf("%d ", a[i]); } int bolle_rif(int [], int); passi = bolle_rif(a, N);

passi++; } i++; while (i < numel - 1 && passi != 0); return(i); int bolle_rif(int *elem, int numel) { void scambia(int *, int *); int i, j, passi; i = 0; do passi = 0; for (j=1; j < numel; j++) if (elem[j] < elem[j-1]) scambia(&elem[j], &elem[j-1]);

void scambia(int *punt_elem1, int *punt_elem2) { int temp = *punt_elem1; *punt_elem1 = *punt_elem2; *punt_elem2 = temp; } bolle_rif esegue l’ordinamento del vettore e chiama scambia per scambiare di posto gli elementi elem[j] ed elem[j-1]. Poiché il C applica l’incapsulamento delle informazioni tra le funzioni, scambia non avrà accesso ai singoli elementi del vettore di bolle_rif. Dato che bolle_rif ha necessità che scambia abbia accesso agli elementi del vettore, in modo da poterli scambare di posto, bolle_rif passa a scambia ognuno di quegli elementi attraverso una chiamata per riferimento: in altri termini, passa in modo esplicito l’indirizzo di ogni elemento del vettore. Come sappiamo, un vettore completo sarebbe passato automaticamente per riferimento, ma i suoi singoli elementi sono scalari, e sono passati normalmente per valore.

scambia(&elem[j], &elem[j-1]); Di conseguenza, nella chiamata a scambia, bolle_rif utilizza l’operatore di indirizzo & con ognuno degli elementi del vettore, con l’istruzione seguente: scambia(&elem[j], &elem[j-1]); che esegue una chiamata per riferimento. La funzione scambia riceve &elem[j] nel puntatore punt_elem1. Sebbene alla funzione scambia non sia consentito conoscere il nome elem[j] a causa dell’incapsulamento delle informazioni, essa può comunque utilizzare *punt_elem1 come sinonimo per elem[j]. Di conseguenza, quando scambia fa riferimento a *punt_elem1, in realtà punta a elem[j] di bolle_rif. (Analogamente, quando fa riferimento a *punt_elem2, in realtà punta a elem[j-1] di bolle_rif).

Per quanto alla funzione scambia non sia consentito un gruppo di istruzioni quali: temp = elem[j]; elem[j] = elem[j-1]; elem[j-1] = temp; lo stesso effetto si ottiene inserendo in essa il seguente gruppo di istruzioni: int temp = *punt_elem1; *punt_elem1 = *punt_elem2; *punt_elem2 = temp;

int *elem anziché come int elem[] Osservazioni. Vediamo alcune caratteristiche della funzione bolle_rif degne di nota. 1. Nella sua intestazione per indicare che riceverà come argomento un vettore unidimensionale di interi, abbiamo dichiarato elem come int *elem anziché come int elem[] dato che queste due notazioni sono completamente intercambiabili. 2. Il prototipo della funzione scambia è stato inserito nel corpo di bolle_rif poiché questa è l’unica funzione che la chiama. L’avere inserito il prototipo in bolle_rif limita le chiamate accettabili di scambia esclusivamente a quelle eseguite all’interno di bolle_rif. Le altre funzioni che tentassero di chiamare scambia non avrebbero acceso al giusto prototipo di funzione. Inserire i prototipi all’interno delle definizioni delle funzioni obbedisce al cosiddetto principio del minimo privilegio. int bolle_rif(int *elem, int numel)

3.La dimensione del vettore viene passata alla funzione bolle_rif in modo esplicito, il che offre due benefici principali: la riusabilità del software e una sua corretta progettazione. Dato che la funzione è stata definita in modo da ricevere la dimensione del vettore per mezzo di un argomento, essa potrà essere utilizzata da un qualsiasi programma che debba riordinare vettori unidimensionali di interi di qualsiasi dimensione. 4.Avremmo potuto memorizzare la dimensione del vettore in una variabile globale, che fosse accessibile all’intero programma. Questo approccio sarebe stato ancora più efficiente, perché non sarebbe stato necessario creare una copia della dimensione durante la chiamata della funzione. Tuttavia, altri programmi che eventualmente richiedessero l’ordinamento di vettori di interi potrebbero non contenere la stessa variabile globale, e quindi non potrebbero usare la funzione.