Dispense su:
Linguaggio di programmazione C
a cura di
Angela Scaringella e Sofia Bochicchio
1
Prefazione
Questa dispensa costituisce il testo di riferimento, in aggiunta agli argomenti trattati nel libro di
testo, per la preparazione all’esame di base di Informatica, sia per gli studenti di Sociologia Nuovo
Ordinamento, sia per gli studenti di Goru, sia per gli studenti di Sociologia Vecchio Ordinamento.
Si tratta di una introduzione elementare al linguaggio C che riporta le lezioni e le esercitazioni
tenute durante il corso. E’ destinata soprattutto agli studenti che non hanno potuto frequentare il
corso e permette di acquisire gli elementi iniziali della programmazione che sono richiesti per
sostenere l’esame.
Introduzione
I linguaggi di programmazione sono alla base dell’Informatica, perché consentono di generare il
software, sia quello di base (sistemi operativi), sia quello applicativo utilizzato nei vari aspetti
dell’elaborazione di dati, come ad esempio operazioni eseguite da un programma, sequenze di passi
eseguiti da una macchina per l’automazione di processi produttivi, controlli e protocolli di
telecomunicazione, le interfacce utente, raccolta di dati da Internet, ecc.
I linguaggi di programmazione occupano una posizione intermedia tra il linguaggio naturale
(tipicamente inglese), comprensibile dalle persone, e quello binario (composto da sequenze delle
sole cifre 0 e 1), comprensibili solo dal computer, l'unico linguaggio che pilota direttamente le unità
fisiche dell'elaboratore (memoria centrale e cpu).
Il linguaggio C, come qualsiasi altro linguaggio di programmazione (confrontare il Cap. 10 del libro
di testo), si compone di parole e regole; le prime generate da un dato alfabeto o simboli secondo un
metodo prefissato, le seconde costituiscono la portante grammaticale e sintattica del linguaggio che
consente di aggregare le parole per formare frasi di senso compiuto, cioè ammissibili.
I linguaggi di programmazione sono costituiti da un alfabeto, da una sintassi e da una semantica. La
sintassi riguarda l’insieme di regole che devono essere rispettate per scrivere programmi corretti.
Il linguaggio dei programmi C è costituito da tutte le stringhe (insiemi di caratteri o simboli) che
soddisfano la proprietà di poter essere analizzate da un compilatore C, senza che venga rilevato
alcun errore sintattico.
La costruzione delle stringhe avviene secondo una metodologia che, in informatica è nota con il
nome “Grammatiche formali”. Una grammatica formale (come introdotta da Chomsky) è un
sistema che comprende:
• variabili terminali,
• variabili,
• un insieme di regole di produzione e
• un insieme di assiomi.
La grammatica permette di operare su stringhe di simboli attraverso le regole di produzione che
fanno passare da una stringa ad un’altra a partire dagli assiomi. Il linguaggio generato dalla
grammatica è l’insieme di stringhe composte solo da variabili terminali che si possono ottenere a
partire dagli assiomi operando con le regole di produzione.
In seguito, ogni argomento introdotto è supportato da esempi e da esercizi svolti.
2
Relazione tra i linguaggi di programmazione e il concetto di algoritmo
Si richiama il concetto di algoritmo e dalla sua rappresentazione mediante i diagrammi di flusso,
questo ci permetterà di gettare una buona base sulla logica sottostante ai linguaggi di
programmazione (confrontare il Cap. 1 del libro di testo).
I computer, conosciuti anche con il nome di elaboratori elettronici o calcolatori o sistemi di
elaborazione dati, sono strumenti d’ausilio alla risoluzione di una vasta gamma di problemi (non
tutti!!, cfr cap.1, par. 1.4 del libro di testo) ed hanno la caratteristica di svolgere un gran numero di
operazioni in maniera rapida e corretta.
Questa capacità deriva dal fatto che sono macchine programmabili e cioè che accettano istruzioni e
comandi imposti dall’uomo secondo modalità ben precise.
I linguaggi di programmazione permettono di tradurre algoritmi interpretabili da un sistema di
elaborazione. Un algoritmo scritto in un linguaggio di programmazione viene chiamato programma
e il processo di scrittura del programma, a partire dall’algoritmo, viene chiamato codifica.
Svolgere un’attività di programmazione (o, brevemente, programmare) significa quindi scrivere
programmi che risolvano problemi destinati ad essere eseguiti in maniera automatica su un
calcolatore.
In generale, è necessario definire il problema nel modo più preciso possibile: tanto più precisa sarà
la sua descrizione, tanto più facile risulterà scrivere il programma che lo risolve. E’ necessario
quindi specificare in maniera rigorosa i dati noti e i risultati attesi.
Esempio:
Dato un importo, denominato imponibile, si deve calcolare un secondo importo, denominato lordo,
derivante dall’applicazione dell’IVA al 20%.
Di seguito è riportata la soluzione:
1. Si esegue la moltiplicazione imponibile*20;
2. La cifra ottenuta al passo 1 si divide per 100;
3. Il risultato cercato è dato dalla somma della cifra ottenuta al passo 2 con il valore
imponibile.
Dati iniziali: imponibile e aliquota IVA; risultato atteso: importo lordo. Si osservi che, dato un
qualsiasi problema, i dati iniziali vengono sempre trasformati (o meglio,elaborati) al fine di
produrre il risultato.
Inoltre, con l’elencazione dei 3 passi precedenti, abbiamo dato origine ad un algoritmo.
Si richiama la definizione di algoritmo:
a) E’ una successione finita di azioni, o passi, che determinano la risoluzione di un problema.
Si intuisce che, dati due algoritmi che risolvono lo stesso problema, viene preferito quello
col minor numero di passi.
b) Le azioni specificate devono essere univocamente interpretabili ovvero devono essere
comprensibili senza ambiguità da colui che le esegue (solitamente un computer).
c) Se la successione di azioni viene ripetuta in momenti diversi ma con gli stessi dati iniziali,
allora deve essere restituito sempre lo stesso risultato; si dice che l’algoritmo deve essere
non casuale.
d) In ogni istante, la prossima azione da intraprendere deve essere univocamente determinabile
a partire dall’analisi del valore di particolari dati.
Esistono vari modi di rappresentare un algoritmo e precisamente:
- in forma discorsiva per punti (elenco di passi);
- mediante uno pseudo-linguaggio di programmazione;
- in forma grafica (diagramma a blocchi ) utilizzando simboli standard.
3
Esempio_1 :
Consideriamo un gioco con un dado: “una volta lanciato un dado, il giocatore vince solo se è uscito un
numero n maggiore di 3, altrimenti perde. In caso di vincita verrà pagato un premio di euro 5*n ”.
- In termini di algoritmo si ha la seguente successione di passi elementari:
1. Calcola un numero naturale n tra 1 e 6;
2. Se n appartiene all’insieme {1,2,3} allora vai al passo 4;
3. n appartiene all’insieme {4,5,6} ed è stata vinta la somma di euro 5*n;
4. Il gioco è terminato.
- In pseudo-codice (simile al linguaggio Pascal) :
program Gioco Con Dado:
begin {inizio dell’algoritmo}
n:=CalcolaUnNumeroNaturaleTra1e6:
if n>=4 then {è uscito un numero maggiore di 3}
begin
premio:=n*5{calcola il premio vinto}
end
end {fine dell’algoritmo}
- Per la forma grafica dobbiamo introdurre i simboli fondamentali dell’algoritmo.
La struttura logica della soluzione di un problema può essere visualizzata mediante un diagramma
a blocchi (diagramma di flusso).
Simbolo terminale (Inizio e Fine)
Simbolo di ingresso (Input)
*
Simbolo di annotazione (Commento)
Simbolo di collegamento
4
Simbolo di elaborazione (Processo)
Simbolo di decisione (il computer può decidere solo
se vero o falso)
Tornando all’ esempio_1, possiamo così graficamente rappresentare lo sviluppo dell’algoritmo:
Inizio
Estrai
n
n >= 4 ?
si
Hai vinto
n*5 euro!
n
o
Fine
gioco
Vediamo subito un altro esempio per chiarire alcuni punti.
5
Esempio_2:
“Uno studente immette il suo voto: se il voto è uguale o superiore a 18 lo studente viene promosso,
se è minore di 18 viene bocciato”.
Inizio
Leggi
voto
voto
>=18
Si
Promosso
n
o
Bocciato
Fine
Da notare che non abbiamo dovuto specificare, con un altro controllo, se il voto fosse minore di 18.
Se volessimo inserire più voti ? Mettiamo il caso che: “uno studente inserisca 3 voti e che voglia
trovare la media.”
Esempio_3:
6
Inizio
somma=0;
voto;
voti ins=0;
Leggi
voto
si
somma=
somma +
voto
voti_ins =
voti_ins +
1
voti_ins
< 3?
no
Media =
somma / 3
Fine
Questa è la logica lineare da seguire se si vuole sviluppare un algoritmo, ora siamo pronti per
introdurre la storia del linguaggio C.
LE BASI DELL’AMBIENTE C
Il C e' stato progettato da Dennis Ritchie su UNIX, e discende dal B, ideato nel 1970 da Ken
Thompson, e il B a sua volta discende dal BCPL, sviluppato da Martin Richards.
Nel 1983 l'Istituto Nazionale Americano per gli Standard (ANSI) ha iniziato una standardizzazione
del C, terminata nel 1989 con la definizione dell'ANSI C, che differisce di poco dal C di K&R
(Kernigan & Ritchie).
In seguito e' stato aggiornato secondo la filosofia della programmazione orientata agli oggetti, in
questo caso si parla di C++, e il suffisso dei file solitamente e'.CPP.
Il C consente la gestione di bit, byte e indirizzi di memoria, a differenza di altri linguaggi di alto
livello come basic e pascal, per questo alle volte e' definito un linguaggio di medio livello, ossia piu'
vicino al basso livello, ossia all'assembler.
Tra l'altro ha solo 32 parole chiave, ben poche al confronto del basic, che ne ha solitamente piu' di
150.
Inoltre il C e' un linguaggio portabile, ossia un listato scritto in ANSI C puo' essere compilato su
ogni compilatore standard di ogni sistema operativo.
I compilatori C richiedono un listato scritto in formato testo, che si puo' scrivere con un editor
qualsiasi o con quello integrato nel compilatore. Il file di testo del programma, che di solito ha
come suffisso “.c” e viene chiamato “programma sorgente”, viene compilato, cioè tradotto e
7
trasformato in un nuovo file chiamato “programma oggetto” (o modulo), per distinguerli l’uno
dall’altro, il programma trasformato e reso comprensibile solo alla macchina assume un nuovo
nome cui bisogna assegnare il suffisso .OBJ.
Tutti i sistemi C consistono generalmente di tre parti: l’ambiente, il linguaggio e la libreria standard
del C.
I programmi scritti in C passano tipicamente attraverso 6 fasi, prima di essere eseguiti. Queste sono:
editare, preelaborare, compilare, linkare (collegare), caricare ed eseguire.
La prima fase consiste nella scrittura del codice (editing) in un file: questa si esegue con un
programma chiamato editor. Il programmatore scrive un programma in C con l’editor e, se
necessario, eseguirà delle correzioni. Il programma sarà quindi immagazzinato in un dispositivo di
memoria di massa, come un disco. Il nome del file del programma C dovrà terminare con
l’estensione .c. Ad esempio il blocco note, il “vi” e l’emacs sono editor largamente utilizzati sui
sistemi windows e UNIX.
In seguito, il programmatore immetterà il comando di compilazione del programma. Il compilatore
tradurrà il programma C nel codice di un linguaggio macchina (detto anche codice oggetto). In un
sistema C, prima che incominci la fase di traduzione, sarà eseguito automaticamente il programma
preprocessore. Il preprocessore del C obbedisce a comandi speciali, chiamati direttive del
preprocessore, con le quali si indica che sul programma dovranno essere eseguite determinate
manipolazioni,prima della compilazione vera e propria. Tali manipolazioni consistono
generalmente nella inclusione di altri file in quello da compilare,e nella sostituzione di simboli
speciali con un testo del programma.
La quarta fase è chiamata linking (collegamento). I programmi scritti in C contengono tipicamente
dei riferimenti a funzioni definite altrove,per esempi nelle librerie standard o in quelle di un gruppo
di programmatori che lavorano su un particolare progetto. Di conseguenza, il codice oggetto
prodotto dal compilatore conterrà tipicamente dei “buchi” dovuti a queste parti mancanti.
Il linker collega il codice oggetto con quello delle funzioni mancanti per produrre una immagine
eseguibile (senza pezzi mancanti).
La fase quinta è chiamata caricamento. Prima che possa essere eseguito,un programma dovrà essere
caricato nella memoria. Questa operazione sarà eseguita dal loader (caricatore) che prenderà
l’immagine eseguibile dal disco e la trasferirà nella memoria.
Finalmente, sotto il controllo della sua CPU, il computer eseguirà il programma, una istruzione per
volta.
Quando si scrive il codice è buona abitudine indentarlo, ovvero, ad ogni istruzione si va a capo e si
lascia una tabulazione orizzontale, questo permette al programmatore una più chiara lettura di
quello che si sta facendo; per commentare più righe si usano i simboli: /* (Slash asterisco), per
aprire il commento, e */ (asterisco slash) per chiudere; // doppio slash per commentare una sola riga.
Vediamo subito un programma che visualizzi "Buon giorno!" sullo schermo:
#include <stdio.h>
/* Include la libreria standard, che non e' altro che un
file di testo con delle strutture e macro predefinite.
Questi file sono detti Header, infatti terminano con il
suffisso .h */
main( )
/* Definiamo la funzione main, che non riceve nessun valore
come argomento. Il programma iniziera' l'esecuzione a partire
da questa funzione, che deve essere presente in ogni
programma */
{
/* Le istruzioni della funzione sono racchiuse tra aperta e
chiusa parentesi graffa */
8
printf(“Buon giorno!\n”);
/* Chiamiamo la funzione di libreria printf che si
occupa di ,visualizzare sullo schermo.
Da notare che \n indica il newLine, ossia serve
per andare a capo.
Fondamentale: ogni istruzione termina con ;
punto e virgola/
/* chiudiamo la funzione principale main( ) */
}
In questo programma si notano già degli accorgimenti fondamentali. Innanzitutto la funzione
main(), è la funzione principale, viene eseguita per prima ed è sempre presente in ogni programma.
Inoltre si nota che gli argomenti da passare alle funzioni vanno messi tra le parentesi ( ) che si
trovano dopo il nome della funzione stessa. Nel caso di main( ), non viene messo alcun argomento,
infatti non richiede argomenti.
La parentesi graffa aperta { apre il corpo di ogni funzione , la parentesi graffa chiusa } la chiude. La
porzione di programma racchiusa in esse è anche detta blocco.
Quando si chiama la funzione printf( ) della libreria <stdio.h> immettiamo come argomento il testo
da stampare tra “ ” (doppi apici ), come vuole la sua sintassi.
All'interno della stringa di caratteri abbiamo la sequenza di escape \n, che equivale al carattere
newline, posiziona il cursore all’inizio della riga successiva. Sono disponibili altri caratteri non
editabili, ad esempio \t , tabulazione orizzontale, muove il cursore alla tabulazione successiva; \r,
ritorno carrello, posiziona il cursore all’inizio della riga corrente; \a, allarme, fa suonare il cicalino
del sistema; \\ backslash,visualizza un carattere backslash in una istruzione printf; \’, apice singolo;
\” virgolette.
Dichiarazione di variabili
Gli elementi di base di un programma sono le variabili e le costanti.
In C le variabili devono essere dichiarate prima di essere usate e all’inizio del programma.
Si possono definire molti tipi di variabili numeriche, ad esempio con int si indicano variabili che
possono contenere solo numeri interi, con float variabili che possono contenere numeri con la
virgola.
Ecco una tabella riassuntiva dei tipi di dati definibili:
Tipo
Lungh.
unsigned char
char
unsigned int
short int
int
unsigned long
long
float
double
long double
8 bits
8 bits
16 bits
16 bits
16 bits
32 bits
32 bits
32 bits
64 bits
80 bits
Range (valori minimi e massimi)
0 fino 255
-128 "" 127
0 "" 65,535
-32,768 "" 32,767
-32,768 "" 32,767
0 "" 4,294,967,295
-2,147,483,648 "" 2,147,483,647
3.4 * (10**-38) "" 3.4 * (10**+38)
1.7 * (10**-308) "" 1.7 * (10**+308)
3.4 * (10**-4932) "" 1.1 * (10**+4932)
9
Come si vede i tipi interi (char, int, long) possono essere unsigned, ossia solo positivi, in questo
modo contengono il doppio di valori positivi, risparmiandosi di supportare quelli negativi. I tipi a
virgola mobile, ossia float, double e long double, sono comunque signed.
Se si va fuori del raggio di azione di una variabile, ad esempio se si aggiunge 100 ad una variabile
int che vale gia' 32700, il risultato e' -32736! Questo per l'aritmetica in complemento a 2, che per
semplificare fa ricominciare dal numero più basso quando si supera quello più alto, dato che non e'
segnalato alcun errore e il risultato erroneo può essere usato, attenzione a non far eccedere mai i
tipi!
Per dichiarare una variabile basta scrivere il tipo seguito dal nome che decidiamo per la variabile
stessa, ad esempio:
int numero;
float numero;
/*nel caso in cui volessi inserire un numero intero */
/*nel caso in cui volessi inserire un numero con la
virgola */
Nel primo caso abbiamo dichiarato una variabile di tipo intero e di nome numero. Da notare che
abbiamo terminato la dichiarazione con un ;. Il nome da dare alla variabile e' a piacere, le uniche
limitazioni sono: non può iniziare con un numero, ne con simboli come &,/,!,... , non si possono
dare come nomi le parole chiave di comandi del C, come if, else, int, float...
Il C è case sensitive, ovvero, le lettere maiuscole e quelle minuscole sono differenti in C, per cui
NUMERO e' diverso da Numero o da NumeRO. Le parole chiave come printf() devono essere
scritte in minuscolo, e solitamente anche le variabili sono scritte con
caratteri minuscoli, nonostante si possano usare anche caratteri maiuscoli.
La regola quindi sarebbe di scrivere le variabili a lettere minuscole, e se si vuole si può usare il
carattere “_”. Per esempio, si può fare:
int numero_1;
int prova1_prova;
Come assegnare un valore ad una variabile:
numero = 1000;
abbiamo introdotto l'operatore di assegnamento “ = ”, che assegna il valore ad una variabile. In
questo caso abbiamo dato a numero il valore 1000.
Da notare che scrivere:
numero = 500+500;
numero = (250+250)+500;
è equivalente, infatti si può mettere anche un’espressione, che sarà risolta in fase di
precompilazione, dato che si assegna sempre 1000.
A questo punto ci manca di sapere come stampare il valore di una variabile a video con la funzione
printf( ). Abbiamo già visto che per stampare un testo normale basta includerlo tra “ ” ,virgolette.
Per inserire una variabile nella stringa occorre aggiungere un %d nel punto che ci interessa, e
mettere il nome della variabile dopo la chiusura delle “ ”, in questo modo:
printf (“Il valore di Numero e' %d.”,numero);
10
Al momento della stampa il %d sarà sostituito dal valore della variabile numero. Da notare la
virgola che separa i doppi apici “ ” dalla variabile.
La specifica di conversione %d è usata in una stringa di controllo del formato di una funzione
printf per indicare che sarà visualizzato un intero, mentre in una stringa di controllo del formato di
una funzione scanf per indicare che sarà immesso un intero.
La funzione scanf della libreria standard stdio.h è utilizzata per leggere i dati della tastiera.
Andiamo ora a semplificare le cose con un esempio:
#include<stdio.h>
main()
{
int num1;
//dichiarazioni variabili
int num2;
int somma;
num1=5;
//inizializzazione
num2=0;
somma=0;
printf(“Inserisci un numero:\n”);
//prompt
scanf(“%d”, &num2);
/* legge un intero, il valore digitato sarà immesso
nella posizione di memoria alla quale il nome
num2 è stato assegnato */
somma=num1+num2;
//assegnamento dell’addizione alla somma
printf(“La somma del numero inserito da tastiera e del numero dato dal
computer è : %d”,somma);
//visualizza la somma
return ;
/* indica che il programma è terminato con successo */
}
L’istruzione
scanf (“%d”,&num2);
utilizza scanf per ottenere un valore dall’utente. La funzione scanf prende i dati in ingresso dallo
standard input che è di solito la tastiera. Questa scanf ha due argomenti: “%d” e &num2. Il primo
argomento, la stringa di controllo del formato, indica il tipo di dato che dovrà essere immesso
dall’utente. La specifica di conversione %d indica che il dato dovrà essere un intero ( d sta per
intero decimale). Il secondo argomento incomincia con una E commerciale &, detta in C operatore
di indirizzo, seguita dal nome della variabile. La E commerciale, o ampersand, quando è combinata
con il nome della variabile , indica alla scanf la locazione di memoria in cui è immagazzinata la
variabile num2. Il computer quindi immagazzinerà il valore della variabile num2 in quella
locazione.
Ogni qualvolta un valore è sistemato in una posizione di memoria, esso si sostituisce al valore
contenuto in precedenza in quella locazione. Dato che questa informazione precedente sarà
distrutta, il processo di scrittura in una locazione di memoria è detto scrittura distruttiva.
Così come abbiamo visto per la variabile somma, prima inizializzata a 0 e poi sostituita dal valore
dato dalla somma di num1 e num2.
Quando il valore sarà letto da una locazione di memoria, il valore in quella locazione sarà
preservato, questo processo è detto lettura non distruttiva.
Per semplicità, immaginate la locazione della memoria come un cassettone, ad ogni cassetto (la
dimensione dipende dal tipo della variabile) corrisponde un nome, all’interno troveremo il nostro
valore. E’ buona abitudine inizializzare a 0 la variabile (come se volessimo ripulire il cassetto) per
evitare risultati poco probabili durante l’esecuzione.
11
L’istruzione
return 0;
restituisce il valore 0 all’ambiente del sistema operativo in cui il programma è stato eseguito.
In questa tabella sono riportate le specifiche di conversione per printf e scanf:
Specifiche di conversione
per printf
per scanf
Tipi di dato
long double
double
float
unsigned long int
long int
unsigned int
int
short
char
%Lf
%f
%f
%lu
%ld
%u
%d
%hd
%c
%Lf
%lf
%f
%lu
%ld
%u
%d
%hd
%c
L’aritmetica del C
Gli operatori aritmetici sono:
Operazione
Operatore
Parentesi
Moltiplicazione
Divisione
Modulo
()
*
/
%
Addizione
Sottrazione
+
-
Ordine di valutazione (priorità)
Sono valutate per prime
Sono valutate per seconde
Sono valutate per seconde
Resto di una divisione intera.
Sono valutate per seconde
Sono valutate per ultime
Sono valutate per ultime
Gli operatori di uguaglianza e relazionali:
Operatori di uguaglianza
==
!=
uguale
diverso (not uguale)
Operatori relazionali
>
<
>=
<=
maggiore
minore
maggiore o uguale
minore o uguale
12
Tutti questi operatori, a eccezione di quello di assegnamento =, associano da sinistra a destra.
L’operatore di assegnamento associa da destra a sinistra.
Es.:
somma = num1 + num2 ;
Gli operatori di assegnamento
Il C fornisce diversi operatori di assegnamento per abbreviare le relative espressioni. Per esempio
l’istruzione a=a+b; può essere abbreviata con l’operazione di assegnamento addizione += come in:
a+=b; L’operatore += aggiunge il valore dell’espressione alla destra dell’operatore alla variabile
alla sua sinistra, immagazzinando il risultato in quest’ultima.
+=
-=
*=
/=
%=
Addiziona
Sottrae
Moltiplica
Divide
Da il resto
Gli operatori logici
Gli operatori logici dell’algebra booleana (confronta Cap. 3 del libro di testo, par. 3.2) possono
essere utilizzati per formare condizioni più complesse, combinando quelle semplici. Questi sono:
&& ( AND logico), || (OR logico) e ! (NOT logico detto anche negazione logica).
(condizione 1) && (condizione 2) , la condizione sarà vera se e solo se entrambe le condizioni
semplici saranno vere.
(condizione 1) || (condizione 2) , la condizione sarà vera se una o entrambe le condizioni semplici
saranno vere.
Gli operatori di incremento ++ e di decremento - Il C fornisce anche l’operatore di incremento ++ unario e l’operatore di decremento – unario.
Quindi, qualora volessi incrementare di 1 una variabile, anziché scrivere: a= a+1 o a+=1
Si può scrivere a++
++a
Incrementa la variabile a di 1 e quindi utilizza il nuovo valore di a
nell’espressione in cui essa occorre.
a++ Utilizza il valore corrente di a nell’espressione in cui essa occorre e quindi
incrementa a di 1.
--a
Decrementa a di 1 e quindi utilizza il nuovo valore di a nell’espressione in cui essa
occorre.
a-Utilizza il valore corrente di a nell’espressione in cui essa occorre e quindi
decrementa a di 1.
Quando gli operatori di incremento o di decremento sono sistemati prima di una variabile, si dicono
rispettivamente operatori di preincremento o di predecremento. Quando sono sistemanti dopo una
variabile, si dicono rispettivamente operatori di postincremento o di postdecremento.
13
STRUTTURE DI CONTROLLO
La maggior parte dei programmi richiede delle iterazioni o cicli. Un ciclo (loop) è un gruppo di
istruzioni che il computer eseguirà ripetutamente, finché una certa condizione di continuazione del
ciclo rimarrà vera.
Tutti i programmi possono essere scritti in termini di tre sole strutture di controllo: la struttura di
sequenza, la struttura di selezione, e la struttura di iterazione. Queste strutture di controllo con un
ingresso e una uscita singoli semplificano la costruzione dei programmi. Le strutture di controllo
possono essere attaccate l’una all’altra, collegando il punto di uscita di una di esse con quello di
entrata con una successiva: accatastamento delle strutture di controllo; oppure possono essere
innestate: nidificazione delle strutture di controllo.
Strutture di sequenza: le istruzioni sono eseguite nell’ordine in cui sono state scritte.
Strutture di selezione: sono di tre tipi:
- IF , nel caso in cui una condizione sia vera eseguirà una certa azione, mentre la ignorerà
nel caso sia falsa. Chiamata struttura di selezione singola.
- IF/ELSE , nel caso in cui una condizione sia vera eseguirà una certa azione, nel caso in
cui sia falsa eseguirà un’altra differente azione. Detta struttura di selezione doppia.
- SWITCH , sceglierà ed eseguirà una tra tante differenti azioni secondo il valore assunto
da una data espressione. Consiste di una serie di etichette case (caso) e di un caso
opzionale default. Detta struttura di selezione multipla.
Strutture di iterazione: sono di tre tipi:
- WHILE , consente al programmatore di specificare che una azione dovrà essere
ripetuta finchè alcune condizioni rimarranno vere.
- DO/WHILE , stesso controllo del WHILE fatto però dopo aver eseguito almeno una
volta il corpo del loop (ciclo).
- FOR , gestisce automaticamente tutti i dettagli di una iterazione controllata da un
contatore.
Utilizziamo ora i diagrammi di flusso per visualizzare il funzionamento delle strutture di sequenza,
selezione e ripetizione con un ingresso e una uscita singoli .
Sequenza
Selezione Struttura if (selezione singola)
vero
fa
ls
o
14
Struttura if/else (selezione doppia)
falso
vero
Struttura switch (selezione multipla)
vero
f
vero
f
vero
f
15
Iterazione
Struttura do/while
vero
f
Struttura while
vero
f
16
Struttura for
vero
f
Vediamo due tipi di iterazione:
• L’iterazione controllata da un contatore;
• L’iterazione controllata da un valore sentinella.
L’iterazione controllata da un contatore è detta a volte iterazione definita, perchè conosciamo
esattamente in anticipo il numero di volte che il ciclo sarà eseguito. L’iterazione controllata da un
valore sentinella è detta a volte iterazione indefinita, perché non è noto in anticipo il numero di
volte che il ciclo sarà eseguito.
I valori sentinella sono utilizzati per controllare una iterazione quando il numero preciso delle
iterazioni non è noto in anticipo e il ciclo include delle istruzioni che otterranno dei dati ogni volta
che quest’ultimo sarà eseguito. Il valore sentinella indica la “fine dei dati” e sarà immesso
dall’utente dopo che tutti i veri elementi informativi saranno stati forniti dal programma.
Nella iterazione controllata da un contatore, è utilizzata una variabile di controllo per contare il
numero delle iterazioni. La variabile di controllo sarà incrementata (di solito di 1) ogni volta che
sarà eseguito il gruppo di istruzioni. Il ciclo terminerà quando il valore della variabile di controllo
indicherà che sarà stato eseguito il numero corretto di iterazioni; a quel punto, il computer potrà
continuare con l’istruzione successiva alla struttura di controllo.
Una iterazione controllata da un contatore richiede: il nome di una variabile di controllo; il valore
iniziale della stessa; l’incremento (o decremento) con cui la variabile di controllo sarà modificata
ogni volta nel corso del ciclo; la condizione che verificherà il valore finale della variabile di
controllo (ovverosia, quella che determinerà se il ciclo dovrà continuare).
17
Esempio_4 Iterazione controllata da un contatore (while):
#include<stdio.h>
main( )
{
int contatore = 1;
/* dichiarazione e inizializzazione, posso dargli un qualsiasi
nome e valore */
while ( contatore < = 10 )
// condizione di iterazione
{
printf (“%d Buon giorno!\n”,contatore);
contatore++;
// incremento
}
return ;
}
Questo programma conta quante volte ( 10 nel nostro esempio) ho richiamato la funzione printf e
ripete la frase : Buon giorno! ogni volta.
La condizione di iterazione la posso prendere dall’esterno del programma e quindi richiederla
all’utente e memorizzarla in una variabile d’appoggio. Vedremo come nel prossimo esempio.
La struttura di iterazione for gestisce automaticamente tutti i dettagli di una iterazione controllata da
un contatore. Riscriviamo il programma dell’esempio_4 con il for aggiungendo anche una variabile
d’appoggio:
Esempio_5 (for):
include<stdio.h>
main( )
{
int i ;
// contatore
int appoggio = 0; // variabile d’appoggio
printf (“Digita quante volte vuoi ripetere la frase Buon giorno: ”);
scanf (“%d”,&appoggio);
/* inizializazione, condizione di iterazione e incremento sono tutti
inclusi nella intestazione della struttura for */
for (i=1; i <= appoggio; i++)
{
printf (“%d Buon giorno\n”,i );
}
return ;
}
18
Proseguiamo con altri esempi sulle strutture di controllo.
Esempio_6 (if/else):
“Inserire un numero e vedere se è pari o dispari.”
#include<stdio.h>
void main(void)
/* void sta a significare che la funzione main
non riceve e non restituisce alcun valore,
come abbiamo già visto, si può omettere */
{
int numero;
// dichiaro variabile
printf("Inserisci un numero:\n");
// visualizzo su schermo
scanf("%d",&numero);
// leggo numero
if(numero%2==0)
// condizione
printf("Il numero inserito è pari");
else
printf("Il numero inserito è dispari");
}
Esempio_7 (if/else if/else):
“Inserire un voto. Se il voto dello studente è >= 90 visualizza "a", altrimenti
se il voto è >= 80 visualizza "b", altrimenti se il voto è >= 70 visualizza "c",
altrimenti visualizza "d".”
#include<stdio.h>
void main(void)
{
int voto;
printf("Inserisci voto: ");
scanf("%d",&voto);
if(voto>=90)
printf("\n a");
/* if appeso */
/* costruzione annidata */
else if (voto>=80)
printf("\n b");
/* avendo una sola istruzione
non ha bisogno delle parentesi graffe*/
else if(voto>=70)
printf("\n c");
else
printf("\n d");
}
19
Esempio_8 (while/if/else):
“ Inserisci un numero e vedere se è un numero primo.”
#include<stdio.h>
void main (void)
{
int num, i=2, div=0; // posso dichiarare ed inizializzare più variabili insieme
printf("Inserisci un numero:\n");
scanf("%d",&num);
while(i<num)
// due strutture di controllo l’una dentro l’altra
{
if(num%i==0)
{
div=div+1; //div++;
i=i+1;
}
else
{
i++;
// uguale a i=i+1
}
}
if(div==0)
printf("\n%d è un numero primo",num);
else
printf("\n%d non è un numero primo",num);
}
Esempio_9 (if/while/while):
“Scrivere un programma C che legga il lato di un quadrato e lo disegni
sullo schermo utilizzando degli "*".Il programma dovrà funzionare con
tutti i quadrati con lati compresi tra 1 e 20”.
esempio
lato: 3
***
***
***
se lato < 1 o lato > 20 visualizzare "errore"
#include<stdio.h>
void main(void)
{
int lato,contatore1,contatore2;
printf("Inserisci il lato del quadrato: ");
scanf("%d",&lato);
if((lato>=1) && (lato<=20))
/* && sta per l’operatore and,
se entrambe le condizioni sono vere il programma
seguita la lettura nella funzione */
{
contatore1=0;
while(contatore1<lato)
{
printf("\n*");
20
contatore1++;
contatore2=1;
while(contatore2<lato)
{
printf("*");
contatore2++;
}
}
}
else
printf("\nErrore!");
}
Esempio_10 (switch)
“Scrivere un programma che calcoli l'area del quadrato, del rettangolo,
del triangolo e del cerchio.”
#include<stdio.h>
void main (void)
{
int figura;
float base, altezza, area, raggio, p=3.14159;
printf("Per calcolare l'area del:\n quadrato digita 1\n rettangolo digita 2\n
triangolo digita 3\n cerchio digita 4");
printf("\n\n Digita numero: ");
scanf("%d",&figura);
switch(figura)
{
case 1:
printf("Inserisci base: ");
scanf("%f",&base);
area=base*base;
break;
//esce dallo switch
case 2:
printf("Inserisci base e altezza separati da spazio: ");
scanf("%f %f",&base,&altezza);
area=base*altezza;
break;
case 3:
printf("Inserisci base e altezza separati da spazio: ");
scanf("%f %f",&base,&altezza);
area=(base*altezza)/2;
break;
case 4:
printf("Inserisci il raggio: ");
scanf("%f",&raggio);
area=raggio*raggio*p;
break;
21
default:
printf("Che ho detto? o 1 o 2 o 3 o 4...Guarda che hai inserito!");
}
printf("L'area della figura geometrica che hai scelto è: %.4f ",area);
// %.4f mi visualizza un numero float con solo 4 cifre dopo la virgola
}
Le istruzioni break e continue sono utilizzate per alterare il flusso di controllo.
L’istruzione break, qualora sia eseguita in una struttura while, for, do/while o switch, provocherà
l’uscita immediata da quella struttura. L’esecuzione del programma continuerà con la prima
istruzione successiva alla struttura. L’utilizzo tipico dell’istruzione break è per anticipare l’uscita
da un ciclo, oppure per ignorare la parte rimanente di una struttura switch. In assenza del break
sarebbero eseguiti insieme tutti i case di una istruzione switch. Qualora non sia stata riscontrata
nessuna occorrenza, sarà eseguito il caso default e sarà visualizzato un messaggio di errore.
Ogni case può contenere una o più azioni. La struttura switch è differente da tutte le altre strutture,
poiché in un case di switch non sono necessarie le parentesi graffe intorno alle azioni multiple.
Qualora sia eseguita in una struttura while, for o do/while, l’istruzione continue farà in modo che
quelle rimanenti nel corpo della struttura siano ignorate e che sia eseguita l’iterazione successiva del
ciclo.
Esempio:
for ( x=1; x<=10; x++) {
if ( x ==5)
continue;
/* senza parentesi per la if perché contiene
un’unica istruzione */
printf (“%d ”,x);
}
Il risultato sarà: 1 2 3 4 6 7 8 9 10
Se avessi messo break al posto di continue il risultato sarebbe stato:
1234
Il C fornisce l’operatore condizionale ( ? : ) che è strettamente correlato con la struttura if/else.
Quello condizionale è l’unico operatore ternario del C, in altre parole, accetta tre operandi. Gli
operandi, insieme all’operatore condizionale, formano una espressione condizionale. Il primo
operando è una condizione, il secondo operando è il valore che assumerà l’intera espressione
condizionale, qualora la condizione sia vera, mentre il terzo operando è il valore che assumerà
l’intera espressione condizionale, qualora la condizione sia falsa. Per esempio l’istruzione printf
printf ( “%s”, voto >= 18 ? “Promosso” : “Bocciato”);
contiene un’espressione condizionale che restituirà la stringa letterale Promosso, qualora la
condizione voto >= 18 risulti vera, mentre restituirà la stringa letterale Bocciato, qualora la
condizione risulti falsa. La specifica di conversione %s serve per visualizzare una stringa di
caratteri.
I valori in una espressione condizionale possono anche essere delle azioni da eseguire.
Otteniamo lo stesso risultato se scriviamo:
voto >= 18 ? printf (“Promosso”) : printf (“Bocciato”);
22
LE FUNZIONI
Il modo migliore per sviluppare e amministrare un programma corposo è di costruirlo partendo da
pezzi più piccoli o moduli ognuno dei quali sia più maneggevole del programma originale. Questa
tecnica è detta dividi e conquista (divide et impera).
I moduli in C sono chiamati funzioni. Le funzioni possono essere: predefinite, quelle
“preconfezionate” disponibili nella libreria standard del C (es. #include<stdio.h> , file già
compilato, #include<math.h>,…); o definite dall’utente.
Le funzioni sono invocate da una chiamata di funzione. Questa specifica il nome della funzione e
fornisce delle informazioni (gli argomenti ) di cui la funzione chiamata ha bisogno. Un capo (la
funzione chiamante) chiede a un operaio (la funzione chiamata) di eseguire un compito e tornare
indietro a riferire, quando il lavoro sarà stato eseguito.
Il prototipo di funzione indica al compilatore il tipo del dato restituito dalla funzione, il numero dei
parametri che quella funzione si aspetta di ricevere, il tipo dei parametri e l’ordine in cui questi sono
attesi. Il compilatore utilizzerà i prototipi per convalidare le chiamate di funzione.
Il formato di una definizione di funzione è:
tipo_del_valore_di_ritorno nome_della_funzione (lista_dei_parametri)
{
dichiarazioni;
istruzioni;
return;
}
Esempio_11:
“Trovare la radice quadrata di un numero chiesto all’utente. Utilizzare una funzione predefinita
contenuta nella libreria matematica.”
#include<stdio.h>
#include<math.h> /* includo il file di intestazione matematico, utilizzando la direttiva del
preprocessore #include<math.h>, quando utilizzo le funzioni della libreria matematica. */
void main (void)
{
double x=0;
printf("Inserisci il valore: ");
scanf("%lf",&x);
printf("La radice quadrata di %.2f è %.2f",x , sqrt(x)); /* chiamo la funzione
all'interno della printf , in alternativa avrei potuto dichiarare un’altra variabile,
assegnarle il risultato della radice quadrata e poi visualizzarla a video. */
}
23
Esempio_12:
“Trovare il quadrato di un numero chiesto all’utente. Utilizzare una funzione personalizzata.”
#include<stdio.h>
int funzione_1 (int valore); /* prototipo di funzione. Nel prototipo posso omettere il nome della
variabile che vado a passare, avrei potuto scrivere: int funzione_1 (int);*/
main( )
{
int numero=0;
/* numero e risultato sono variabili locali.
int risultato=0;
printf("Inserisci un numero:\n");
scanf("%d",&numero);
risultato=funzione_1(numero); /*chiamo la funzione passandogli un argomento, la variabile
“numero”, il valore intero di ritorno lo assegno alla variabile
d’appoggio “risultato” */
printf(" %d elevato al quadrato è %d", numero, risultato);
return;
}
int funzione_1 (int valore)
{
int num;
/* Se chiudessi la funzione con ; il programma non
leggerebbe il blocco di istruzioni. */
/* Nota: non ho bisogno di inizializzare la varibile “valore”
Potrei utilizzare lo stesso nome della variabile numero,
ma è bene capire che sono due variabili distinte! */
num=valore*valore;
/* avrei potuto ottimizzare la scrittura del codice con
un’unica istruzione:
return valore * valore;
senza così utilizzare la variabile d’appoggio “num”. */
return num;
}
Posso scegliere un qualsiasi nome per la funzione. Il prototipo di funzione:
int funzione_1 (int valore);
sta ad indicare che la funzione_1 riceverà come argomento una variabile di tipo int e che mi
restituirà un’altra variabile di tipo int. Se alla funzione non passassi alcun argomento e se non mi
restituisse alcun valore scriverei:
void funzione_1 (void);
Tutte le variabili dichiarate nelle definizioni di funzione sono variabili locali : esse sono note
soltanto in quella in cui sono state definite. Quelle dichiarate all’esterno delle funzioni, anche dal
main, sono dette variabili globali : sono note a tutto il programma.
24
Esempio_13:
Vedo come varia il valore di una variabile utilizzando due diverse funzioni.
#include<stdio.h>
void funz1 (int);
//prototipo di funzione
int funz2 (int, int);
//prototipo di funzione
void main (void)
//funzione principale
{
int num;
num=999;
printf("Sono nel main.num vale %d\n",num);
funz1(num);
//chiamata di funz1, passo un solo argomento
num=funz2(10,12);
// chiamata di funz2, passo 2 argomenti (valori) da me scelti.
printf ("Sono di nuovo nella funzione main! num vale %d",num);
}
int funz2 (int a, int b)
//funz2
{
printf("Sono nella funzione 2. a=%d e b=%d\n",a,b);
return a+b;
}
void funz1 (int x)
//funz1
{
printf("Sono nella funzione 1. num vale %d\n",x);
}
Invocare le funzioni:
chiamata per valore e per riferimento
Esistono due modi per invocare le funzioni: la chiamata per valore e la chiamata per riferimento.
Quando gli argomenti saranno passati in una chiamata per valore, sarà preparata una copia dei loro
valori e questa sarà passata alla funzione chiamata. Le modifiche effettuate alla copia non
interessano il valore originale della variabile definita nel chiamante.
Quando un argomento sarà passato in una chiamata per riferimento, il chiamante consentirà
effettivamente alla funzione chiamata di modificare il valore originale della variabile.
Le classi di memoria
Per dichiarare una variabile abbiamo sempre utilizzato il nome, il tipo e il valore, questi sono detti
attributi.
Una variabile è una locazione (posizione) della memoria in cui un valore può essere immagazzinato
perché possa essere utilizzato da un programma .
Un nome di variabile in C è qualsiasi identificatore valido.
Un identificatore è una serie di caratteri,comprendente lettere,numeri e caratteri di sottolineatura
(_),che non comincia con un numero. Un identificatore può avere qualsiasi lunghezza, ma soltanto i
primi 31 caratteri devono essere riconosciuti dal compilatore C, in accordo con lo standard dell’
25
ANSI C. Il C è case sensitive,ovvero, le lettere maiuscole e quelle minuscole sono differenti in C,
perciò a1 e A1 sono identificatori distinti.
Ora utilizzeremo gli identificatori anche come nomi per le funzioni definite dall’utente. In realtà,
ogni identificatore in un programma ha anche altri attributi che includono: la classe di memoria, la
permanenza (o durata) in memoria, la visibilità e il collegamento.
Il C fornisce quattro classi di memoria indicate dalle specifiche di classe di memoria: auto, register,
extern e static. La permanenza in memoria di un identificatore è il periodo durante il quale
quell’identificatore esiste in memoria. Alcuni identificatori esistono per un tempo limitato, altri
sono creati e distrutti ripetutamente, mentre altri ancora esistono durante l’intera esecuzione del
programma. La visibilità di un identificatore è il punto del programma in cui si può far riferimento
all’identificatore. Il collegamento di un identificatore determina se un identificatore sia noto solo in
quello corrente o in qualsiasi altro file sorgente che abbia le dichiarazioni appropriate.
Le quattro specifiche di classe di memoria possono essere suddivise in due tipi di permanenza: la
permanenza automatica in memoria e la permanenza statica in memoria. Le parole chiave auto e
register sono utilizzate per dichiarare variabili con permanenza automatica in memoria, queste
ultime saranno create ogniqualvolta si entrerà nel blocco in cui sono state dichiarate,esisteranno
finchè quello resterà attivo e saranno distrutte quando si uscirà dallo stesso. Le variabili locali
hanno per default una permanenza automatica nella memoria.
I dati per i calcoli e le altre elaborazioni,nella versione in linguaggio macchina di un programma,
sono caricati normalmente nei registri. La specifica di classe di memoria register potrà essere
inserita prima della dichiarazione di una variabile automatica,per suggerire al compilatore di
conservare la variabile in uno dei registri hardware ad alta velocità del computer.
Esistono due tipi di identificatori con permanenza statica nella memoria: gli identificatori esterni
(come le variabili e i nomi di funzione globali) e le variabili locali dichiarate con la specifica di
classe di memoria static. Le variabili e i nomi di funzioni globali sono definiti per default con la
classe di memoria extern.
La visibilità di un identificatore è la porzione del programma in cui quello potrà essere oggetto di
riferimenti. I quattro tipi di visibilità possibili per un identificatore sono: prototipo di funzione,
visibilità nella funzione, visibilità nel file, visibilità nel blocco e visibilità nel prototipo di funzione.
Generazione di numeri casuali
L’elemento della casualità può essere introdotto nelle applicazioni per computer utilizzando la
funzione rand della libreria standard del C.
L’istruzione:
i = rand ( ) ;
genera un numero compreso tra 0 e RAND_MAX (costante simbolica definita nel file di
intestazione <stdlib.h> ). Lo standard dell’ANSI stabilisce che il valore di RAND_MAX debba
essere almeno 32767,che è anche il valore massimo per un intero di due byte.
Il prototipo per la funzione rand può essere ritrovato in <stdlib.h>. Utilizzeremo l’operatore
modulo (%) in congiunzione con rand nel modo seguente:
rand ( ) % 6 ;
per generare degli interi compresi nell’intervallo da 0 a 5. Questa operazione si chiama riduzione in
scala,mentre il numero 6 è detto fattore di scala.
26
La funzione rand genera in realtà dei numeri pseudocasuali. Richiamando ripetutamente rand, si
produrrebbe una sequenza di numeri che sembra essere casuale. In realtà, la sequenza si ripeterà
ogni volta che il programma sarà eseguito. Una volta che il programma sarà stato completamente
messo a punto,potrà essere condizionato in modo da produrre una sequenza diversa di numeri
casuali ad ogni sua esecuzione. Questa operazione è detta in gergo randomizzazione, ed è eseguita
con la funzione srand della libreria standard. La funzione srand riceve un argomento intero
unsigned e insemina la funzione rand, in modo da indurla a generare una diversa sequenza di
numeri casuali ad ogni esecuzione del programma. ( Un unsigned int di due byte può contenere
soltanto valori positivi compresi nell’intervallo da 0 a 65535.)
L’istruzione:
srand ( time ( NULL ) ) ;
porta il computer a leggere il suo orologio interno per ottenere automaticamente un valore per il
seme. La funzione time restituisce l’ora corrente del giorno espressa in secondi. Questo valore sarà
convertito in un intero senza segno e utilizzato come seme per il generatore di numeri casuali. La
funzione time riceverà un argomento NULL (normalmente time è in grado di fornire al
programmatore una stringa che rappresenta l’ora del giorno; il NULL disabilita appunto questa
capacità per una chiamata specifica di time). Il prototipo di funzione per time è in <time.h>.
(esempio: dado.c)
E se volessimo finalmente scrivere il nostro esempio_1 in linguaggio C?
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
main( )
{
// librerie standard del C
// funzione principale
// apro blocco istruzioni
int numero;
srand(time(NULL));
numero=1+rand( )%6;
// dichiaro la variabile int
/* Con l’aggiunta di +1 estraggo un numero
casuale da 1 a 6 */
printf(“E’ uscito il numero %d\n”,numero);
if(numero>=4)
/* struttura di controllo. Tra parentesi c’è la
condizione da rispettare. if, simbolo di decisione: se
il numero che io utente ho inserito è > o = 4 la
lettura del codice seguìta all’interno del blocco
istruzioni e salta la else */
{
printf(“Hai vinto %d*5 euro”,numero); /*In questo caso abbiamo
una sola istruzione*/
}
else
/* Altrimenti, implica quindi che il numero è < di 4,
salta la if e seguìta la lettura dell’else */
{
printf(“Non hai vinto!”);
}
27
}
printf(“Fine gioco!”);
/*A fine programma visualizzerà la
scritta: Fine gioco! */
return ;
// Fine programma
I VETTORI
Un vettore è un gruppo di posizioni (o locazioni) di memoria correlate dal fatto che tutte hanno lo
stesso nome e tipo di dato.Il primo elemento di ogni vettore è l’elemento zero.
I vettori sono entità statiche, giacchè manterranno le proprie dimensioni durante l’esecuzione del
programma. Il numero di posizione contenuto all’interno delle parentesi quadre è più formalmente
noto come indice. Un indice deve essere un intero o un’espressione intera.
DICHIARAZIONE DEI VETTORI
int vett[10];
questa riga di istruzioni specifica il tipo elementare di ogni elemento della
struttura dati di tipo vettore e il numero richiesto per la grandezza del vettore in modo tale che il
computer possa riservare l’appropriata quantità di memoria.
Invece la sequente riga di istruzioni:
int vett[ ]={2,4,7,4,8}; creerà un vettore di tipo int di nome vett di 5 elementi.
int vett[SIZE]; Nella dichiarazione posso utilizzare la costante simbolica SIZE , direttiva del
preprocessore #define. Una costante simbolica è un identificatore che sarà sostituito con il testo di
sostituzione dal preprocessore C, prima che il programma sia compilato.Utilizzare le costanti
simboliche per specificare le dimensioni dei vettori renderà i programmi più scalabili. Una costante
simbolica non è una variabile, il compilatore non le riserva nessuno spazio di memoria.
int vett[5]={1,2,3,4,5,6}; provocherà un errore di sintassi.
INIZIALIZZARE A ZERO GLI ELEMENTI DI UN VETTORE
Tre modi:
int v[10]={0,0,0,0,0,0,0,0,0,0};
int v[10]={0};
for(i=0;i<10;i++) v[i]=0;
Esempio_14:
“Chiedere all'utente 10 valori interi, il programma deve visualizzare il max.”
#include <stdio.h>
void main (void)
{
int i, vettore[10], max = 0;
for(i=0;i<10;i++)
28
{
printf("Inserisci un numero: ");
scanf("%d",&vettore[i]);
if(vettore[i]>max)
max=vettore[i];
}
printf("Il maggiore è %d",max);
}
Esempio_15:
“Tabellina del 2.”
#include<stdio.h>
#define N 10
main()
{
int v[N]={0},i;
for(i=0;i<N;i++)
v[i]=2+2*i;
printf("%s%13s\n","Elemento","Valore");
for(i=0;i<N;i++)
printf("%7d%13d\n",i,v[i]);
return;
}
Come esercizio, si potrebbe chiedere all’utente di introdurre un qualsiasi numero e poi visualizzare
la tabellina.
Esempio_16:
“Scrivere un programma che chieda all’utente di introdurre 10 valori. Visualizzare prima tutti i
valori pari e poi tutti i valori dispari.”
#include<stdio.h>
main()
{
int v[10]={0},i;
printf("Digita 10 numeri:\n");
for(i=0;i<10;i++)
scanf("%d",&v[i]);
printf("Valori pari:\n");
for(i=0;i<10;i++)
if(v[i]%2==0)
printf("%d ",v[i]);
29
printf("\nValori dispari:\n");
for(i=0;i<10;i++)
if(v[i]%2!=0)
printf("%d ",v[i]);
return;
}
Esempio_17:
“Scrivere un programma che chieda all’utente di introdurre 10 valori. Visualizzare prima tutti i
valori di posizione pari e poi tutti i valori di posizione dispari.”
#include<stdio.h>
main()
{
int v[10]={0},i;
printf("Digita 10 numeri:\n");
for(i=0;i<10;i++)
scanf("%d",&v[i]);
printf("Valori di posizione pari:\n");
for(i=0;i<10;i+=2)
printf("%d ",v[i]);
printf("\nValori di posizione dispari:\n");
for(i=1;i<10;i+=2)
printf("%d ",v[i]);
return;
}
PASSARE I VETTORI ALLE FUNZIONI
Indicherete il nome del vettore senza parentesi quadre per passare ad una funzione un argomento di
quel tipo. Per esempio, se il vettore vett fosse stato dichiarato come
int vett[10];
l’istruzione per la chiamata della funzione
somma(vett,10);
passerebbe il vettore vett, il nome, e la sua dimensione alla funzione somma. Quando si passa a una
funzione un vettore, spesso sarà passata anche la sua dimensione, in modo che la funzione possa
elaborare il numero specifico degli elementi inclusi nel vettore. Il C passa i vettori alle funzioni
utilizzando automaticamente una chiamata per riferimento simulata: la funzione chiamata potrà
modificare i valori degli elementi inclusi nel vettore originale del chiamante. Il nome del vettore è
in realtà l’indirizzo del suo primo elemento! vett e &vett[0] hanno l’indirizzo equivalente.
30
Di conseguenza, quando all’interno del suo corpo la funzione chiamata modificherà gli elementi del
vettore, essa starà modificando effettivamente quelli del chiamante direttamente nelle loro locazioni
di memoria originarie.
funz( vett[3]);
così ho passato un solo elemento del vettore ad una funzione.
Il C fornisce lo speciale qualificatore di tipo const per prevenire, all’interno di una funzione, la
modifica dei valori contenuti in un vettore. Quando un parametro di tipo vettore sarà preceduto dal
qualificatore const, i suoi elementi diventeranno delle costanti nel corpo della funzione e ogni
tentativo di modificarli, in quel contesto, provocherà un errore in fase di compilazione.
Esempio_18:
“Inserisco 10 numeri e trovo il massimo.” Stesso esercizio dell’esempio_14 ma utilizzando una
funzione per trovare il max.
#include<stdio.h>
#define N 10
int maxvet (int v[ ]);
main()
{
int i,vet[N];
printf("Inserisci %d numeri:\n",N);
for(i=0;i<N;i++)
scanf("%d",&vet[ i ]);
printf("Il massimo è : %d",maxvet(vet));
return;
}
int maxvet (int v[ ])
{
int j, max =0;
for(j=0;j<N;j++)
{
if(v[ j ]>max)
max=v[ j ];
}
return max;
// restituisco un intero
}
Esempio_19:
“Cerco un valore all’interno di un vettore di 10 elementi.”
#include<stdio.h>
#define N 10
void inserimento (int vet[ ]);
void cerca (int vettore[ ], int numero);
void stampa (int vettoreee[ ]);
main()
{
int v[N],num=0;
inserimento(v);
31
printf("\nDigita un numero:");
scanf("%d",&num);
cerca(v,num);
stampa(v);
return;
}
void inserimento (int vet[ ])
{
int i;
printf("Inserisci %d numeri\n",N);
for(i=0;i<N;i++)
scanf("%d",&vet[i]);
}
void cerca (int vettore[ ], int numero)
{
int i,f=0;
for(i=0;i<N;i++)
if(vettore[i]==numero)
{
f=1;
printf("Il numero %d è nella posizione %d\n",numero,i+1);
}
if(f==0)
printf("Il numero %d non è nel vettore\n",numero);
}
void stampa (int vettoreee [ ])
{
int i;
for(i=0;i<N;i++)
printf("%d\n",vettoreee[i]);
}
La Struttura Dati MATRICE
I vettori in C possono anche avere più di un indice. Un utilizzo tipico dei vettori multidimensionali è
la rappresentazione di tabelle di valori formate da informazioni organizzate in righe e colonne. Per
identificare un particolare elemento della tabella, dovremo specificare due indici: il primo
identificherà per convenzione la riga dell’elemento, mentre il secondo ne identificherà la colonna.
Le tabelle o i vettori che per identificare un particolare elemento richiedono due indici sono detti
vettori bidimensionali o matrici. Osservate che i vettori multidimensionali possono avere più di due
indici. Lo standard ANSI stabilisce che i sistemi ANSI C debbano supportare per i vettori un
minimo di 12 indici.
Colonna 0
Riga 0 M[0][0]
Riga 1 M[1][0]
Riga 2 M[2][0]
Colonna 1
M[0][1]
M[1][1]
M[2][1]
Colonna 2
M[0][2]
M[1][2]
M[2][2]
Colonna 3
M[0][3]
M[1][3]
M[2][3]
32
Matrice M con 3 righe e 4 colonne, matrice 3 per 4.
Esempio_20:
/* MATRICE */
#include<stdio.h>
#define R 4
#define C 6
/* righe */
/* colonne */
void riempiMx (int Mat[R][C]);
/* utilizzo le funzioni */
void stampaMx (int Matrice[R][C]);
void main (void)
{
int Mx[R][C]={0};
riempiMx(Mx);
stampaMx(Mx);
}
/* dichiaro e inizializzo matrice */
/* chiamata funzione */
void riempiMx (int Mat[R][C])
/* inserisco i valori nella matrice */
{
int i,j;
printf("Inserisci valori:\n");
for(i=0;i<R;i++)
/* utilizzo un doppio ciclo for */
for(j=0;j<C;j++)
scanf("%d",&Mat[i][j]);
}
void stampaMx (int Matrice[R][C]) /* stampo i valori */
{
int i,j;
for(i=0;i<R;i++)
{
printf("\n");
/* va a capo dopo aver visualizzato una riga */
for(j=0;j<C;j++)
printf("%4d",Matrice[i][j]);
}
}
La Struttura Dati PUNTATORE
I puntatori sono delle variabili che come valore contengono degli indirizzi di memoria.
Una variabile contiene direttamente un valore specifico; un puntatore, invece, contiene l’indirizzo
di una variabile nella quale è immagazzinato quel valore specifico.
33
Es.:
num
7
num, nome variabile, fa direttamente riferimento ad una variabile il cui valore è 7.
numPtr
X
numPtr fa indirettamente riferimento ad una variabile il cui valore è 7.
Per questo motivo il riferimento a un valore per mezzo di un puntatore è detto
deriferimento.
DICHIARAZIONE
int num, *numPtr ;
* (asterisco) indica che la variabile dichiarata sarà un puntatore.
Possono essere dichiarati in modo da far riferimento a oggetti di qualsiasi tipo di dato.
Posso inizializzare il puntatore nella dichiarazione o con una istruzione di assegnamento, come una
variabile normale.
& operatore di indirizzo, restituisce l’indirizzo del suo operando
int y = 5 ;
int *yPtr ;
yPtr = &y ; assegnerà al puntatore yPtr l’indirizzo della variabile y
*
operatore di deriferimento o operatore di risoluzione del riferimento, restituisce il valore
dell’oggetto puntato dal suo operando ( cioè dal puntatore )
printf ( “ %d “, *yPtr ) ; visualizzerà il valore della variabile y, 5.
Esempio_21:
“Inserisco un numero e lo modifico mediante un passaggio alla funzione del suo puntatore:”
#include<stdio.h>
void modifico (int *); // riceve come argomento un puntatore
main()
{
int a;
printf("Inserisci un valore intero:\n");
scanf("%d",&a);
modifico(&a);
// chiamo la funzione e le passo un puntatore
printf("Il valore di a è = %d\n ",a); /* ho modificato la variabile a senza che
la funzione mi restituisse un valore */
return;
}
void modifico (int *x)
{
if(*x>10)
(*x)-=5;
/* se il valore è > di 10 gli sottraggo 5 (numero preso a
34
else
caso */
// se il valore è < di 10 gli addiziono 5
(*x)+=5;
}
La Struttura Dati STRINGA
Nel linguaggio C una stringa è in realtà un vettore di caratteri che termina con il carattere nullo
(‘\0’). Per accedere a una stringa si utilizzerà un puntatore che farà riferimento al suo primo
carattere. Il valore di una stringa corrisponde all’indirizzo del suo primo carattere. Ne consegue che
nel linguaggio C è corretto affermare che una stringa è un puntatore: in effetti, è un puntatore al
primo carattere della stringa. In questo senso le stringhe sono come i vettori, poiché anche questi
sono puntatori al loro primo elemento.
Una stringa potrà essere utilizzata in una dichiarazione per inizializzare un vettore di caratteri:
char colore [ ] = “rosso”;
uguale è scrivere:
char colore [ ] = {‘r’,’o’,’s’,’s’,’o’,’\0’};
// i caratteri sono tra apici singoli
Assicurarsi che il vettore sia grande a sufficienza per immagazzinare la stringa e il carattere NULL
di terminazione. La dichiarazione creerà il vettore colore di 6 elementi.
char colore [6];
FUNZIONI PER LA MANIPOLAZIONE DELLE STRINGHE TRATTE DALLA LIBRERIA
PER LA GESTIONE DELLE STRINGHE
Libreria: <stdlib.h>
strcpy ( s1 , s2 )
strcat ( s1 , s2 )
strcmp ( s1 , s2 )
copia la stringa s2 nella stringa s1
accoda s2 a s1
confronta le stringhe s1 e s2
La funzione restituirà 0 se sono uguali
< 0 se s1 è minore di s2
> 0 se s1 è maggiore di s2
N.b. Per comprendere < o > si consideri il processo di
sistemazione in ordine alfabetico (es. per cognomi ).
strlen ( s )
determina la lunghezza della stringa s.
Restituisce il numero di caratteri che precedono
il NULL di terminazione.
strerror ( nomestringa )
traduce in una stringa di testo dipendente dal sistema
printf ( “ %s \n “ , strerror ( Sbagliato! ) ;
visualizzerà: Error Sbagliato!
35
Nel caso di strncpy, strncat, strncmp posso copiare, accodare, comparare un massimo di ‘ n ‘
caratteri dalla stringa s2 nel vettore s1.
Es.: strncpy ( s1 , s2 , 4 ) ;
Per strcpy s1 deve avere uguale o maggiore dimensione di memoria di s2.
Dopo aver copiato una stringa bisogna aggiungere il carattere nullo di terminazione
s1 [ strlen ( s1 ) ] = ‘\0’ ;
Esempio_22:
“Chiedo all’utente di inserire delle parole, il programma termina se si inserisce la parola "fine" e
visualizza le stringhe fino ad ora inserite.”
#include<stdio.h>
#include<string.h>
main()
{
char s[5]="fine";
char s1[20]=" ",s2[20]=" ",s3[200]=" "; //dichiaro ed inizializzo a 0 tre stringhe
printf("Digita basta per terminare\n\n");
printf("Inserisci una parola:\n");
gets(s1);
//stessa funzione dello scanf, legge in input.
while(strcmp(s,s1)<0 || strcmp(s,s1)>0)
{
strcat(s3,s1);
strcat(s3," "); /*inserisco uno spazio tra una parola e l'altra*/
printf("Inserisci una parola:\n");
gets(s2);
strcpy(s1,s2);
s1[strlen(s1)]='\0';
}
printf("%s",s3);
return;
}
36
Scarica

Linguaggio di programmazione C