Linguaggio C
Il C è un linguaggio di programmazione compilato. Ciò vuol dire che è necessario utilizzare un "compilatore" per trasformare il codice sorgente in un programma eseguibile dal sistema operativo. Il livello di programmazione offerto dal C è relativamente basso, pertanto è possibile creare programmi molto performanti e per questo motivo viene utilizzato in molte applicazioni che necessitano di prestazioni elevate (sistemi operativi, giochi per PC, applicazioni embedded, ecc.). La sintassi è molto semplice, ma per diventare esperti programmatori, è necessaria moltissima pratica. Uno dei punti forti del C è sicuramente la gestione della memoria: è il programmatore stesso infatti che alloca determinati blocchi di memoria e si occupa della loro deallocazione. Inoltre è un linguaggio molto flessibile, con tipi di dati che permettono di costruire strutture dati anche molto complesse. Il C è un linguaggio procedurale. Ciò vuol dire che non esistono classi, oggetti, eventi, ecc. come per esempio nel C++, Java e molti altri linguaggi di più alto livello. L'elemento "portante" dei programmi C è costituito infatti dalle funzioni (spiegheremo in seguito cosa sono) o procedure.
A questo punto iniziamo subito a parlare del linguaggio.
Gli strumenti necessari
modificaCome già specificato, per poter eseguire un programma scritto in C, è necessario compilare i sorgenti. Per far questo sono necessari un compilatore e un linker. Ci sono molti compilatori C, molti dei quali gratuiti (per esempio gcc). Inoltre vi consiglio di utilizzare degli ambienti IDE, poiché offrono molte funzioni utili durante la scrittura dei programmi. Degli ottimi ambienti sono Dev-C++ (per Windows) Xcode (per Mac Os X) e Anjuta (per Linux). Link diretto per il download del compilatore Dev-C++. Link diretto per il download di Anjuta
La compilazione di un sorgente C è eseguita in 3 fasi:
- Precompilazione
- Compilazione
- Linking
La Precompilazione è eseguita dal precompilatore, che si occupa di includere i files header delle librerie utilizzate, sostituire i valori delle costanti e delle macro definite tramite istruzioni #define
, ecc.
La Compilazione è eseguita dal compilatore, che si occupa di trasformare i files sorgenti ottenuti dal precompilatori in files oggetto. Un file oggetto è un file che contiene tutte le informazioni necessare al linker per creare il file eseguibile (tabelle dei simboli, ecc.).
Il Linking è eseguito dal linker, che, come dice il nome stesso, si occupa di unire i vari files oggetti e di collegare tutte le variabili e le funzioni.
Le variabili
modificaConcettualmente, le variabili si possono definire come contenitori di dati. Esse infatti corrispondono a locazioni di memoria di determinate dimensioni, contenenti un certo dato. Il tipo di dato e la dimensione della locazione di memoria sono definiti durante la definizione della variabile.
Ogni variabile ha un tipo che indica appunto il tipo di dato che la variabile può contenere. Questo può essere uno dei seguenti
- Char
- Int
- Float
- Double
Il tipo di dato char contiene un valore numerico corrispondente al codice ascii di un carattere. Ha dimensione 1 byte e viene utilizzato per salvare caratteri. Il range di valori che può contenere, va da 0 a 255 se senza segno, oppure da -128 a 127 se con segno.
Il tipo di dato int è utilizzato per contenere numeri interi. La dimensione di questa variabile dipende dall'implementazione: nei moderni compilatori, il dato di tipo int ha dimensione 4 byte, ovvero 32 bit. Il range di valori per questo tipo va da 0 a (2^32)-1 se senza segno, altrimenti da -2^31 a +(2^31) - 1.
Il tipo di dato float è utilizzato per contenere valori in virgola mobile, a singola precisione. La dimensione di una variabile float è di 32 bit (ovvero 4 byte).
Il tipo di dato double è utilizzato per contenere valori in virgola mobile a doppia precisione. La dimensione di una variabile double è di 64 bit (ovvero 8 byte).
In aggiunta esistono alcuni qualificatori, che possono essere applicati a questi tipi. Per esempio, short e long sono associabili agli interi:
short int nome_variabile; long int nome_variabile;
Lo scopo è quello di fornire, dove necessario, delle lunghezze diverse per gli interi: short indica, nel caso di prima, un intero di 16 bit, mentre un long uno di 32.
Oppure i qualificatori signed ed unsigned, per esempio, possono essere associati sia ai char che agli int.
N.B. I numeri unsigned sono sempre positivi o nulli, ed obbediscono alle leggi dell'aritmetica modulo 2^n, dove n è il numero di bit del tipo.
I nomi delle variabili in C devono seguire delle regole ben precise: il primo carattere del nome deve essere una lettera maiuscola o minuscola oppure un 'underscore' ('_'); non possono iniziare con un numero. Inoltre il C è case-sensitive: in poche parole, il C, contrariamente al Basic per esempio, distingue le lettere minuscole da quelle maiuscole. Questo vuol dire che se chiamate una variabile per esempio Prova, non potete poi chiamarla prova perché sono due nomi diversi.
La definizione di variabili in C, si effettua in questo modo:
tipo nome_variabile; tipo nome_variabile = valore;
tipo corrisponde ovviamente ad uno dei tipi elencati sopra. Mentre valore è un valore del tipo tipo. In queste due righe di codice, abbiamo dichiarato una variabile di nome nome_variabile in due modi differenti. Nella prima riga abbiamo solo dichiarato una variabile, ma non le è stato assegnato alcun valore. Ciò non ci permette di utilizzarla in modo sicuro, poiché potrebbe contenere un valore qualunque. Il secondo metodo ci permette invece di inizializzare la variabile al momento della dichiarazione; questo è il metodo che si dovrebbe sempre utilizzare per dichiarare una variabile.
Da quanto detto fino ad ora, è evidente che il C non definisce un tipo per il dato stringa. Questa però non vuol dire che non si possono gestire stringhe: le stringhe vengono infatti definite come vettori (o array) di caratteri, terminati da un carattere nullo. In seguito parleremo dei vettori e ci soffermeremo particolarmente sulle stringhe e la loro manipolazione.
Operatori
modificaIl C fornisce vari tipi di operatori: aritmetici, logici, ecc.
Operatori di assegnazione e confronto
= Assegnamento == Confronto tra due valori > Maggiore >= Maggiore o uguale < Minore <= Minore o uguale != Diverso o non è
La differenza tra l'operatore = e l'operatore == è molto importante, poiché se si vogliono confrontare due variabili e si usa l'operatore =, il risultato sarà solo l'assegnazione alla variabile a sinistra del valore della variabile a destra e verrà restituito tale valore.
Operatori aritmetici
* Prodotto / Rapporto % Modulo. Calcola il resto di una divisione intera. Es. 9 % 5 = 4 + Somma - Differenza *= Moltiplica il valore della variabile a sinistra per il valore a destra e lo riassegna alla variabile /= Come il precedente, ma con il rapporto %= Come il precedente, ma con l'operatore modulo += Aggiunge alla variabile a sinistra il valore a destra -= Sottrae alla variabile a sinistra il valore a destra
Come è possibile notare da quest'elenco, alcuni operatori "includono" l'operatore uguale. Tali operatori sono in realtà delle scorciatoie ad alcune operazioni come mostrato negli esempi seguenti:
var1 += var2; corrisponde all'istruzione var1 = var1 + (var2); var1 -= var2; corrisponde all'istruzione var1 = var1 - (var2); var1 *= var2; corrisponde all'istruzione var1 = var1 * (var2); var1 /= var2; corrisponde all'istruzione var1 = var1 / (var2); var1 %= var2; corrisponde all'istruzione var1 = var1 % (var2);
L'inserimento delle parentesi negli esempi precedenti non ha molto valore in questo caso, ma hanno un significato particolare. Quando si utilizzano questi operatori infatti, è necessario porgere molta attenzione a come vengono strutturate le istruzioni, poiché acquisiscono come 'secondo parametro' tutto ciò che è a destra dell'operatore come se fosse racchiuso tra parentesi. Di seguito sono infatti riportati alcuni esempi per spiegare meglio quanto descritto:
var1 *= var2 + var3; non corrisponde a var1 = var1 * var2 + var3 ma a var1 = var1 * (var2 + var3)
Questo esempio dovrebbe essere più che sufficiente per comprendere il modo corretto di utilizzare questi operatori.
Operatori logici
&& AND logico || OR logico ! NOT logico
Operatori bit a bit
& AND | OR &= Come per gli operatori aritmetici |= Anche questo come per gli operatori aritmetici
Gli operatori &= e |= funzionano nello stesso modo degli operatori +=, -=, *=, /=.
Primo programma
modificaI files sorgenti dei programmi scritti in C hanno estensione .c. Per creare un programma, è necessario definire una funzione particolare chiamata main. Questa funzione rappresenta l'entry point del programma, ovvero il punto di partenza. Questa funzione restituirà un valore intero che indicherà al sistema operativo o all'eventuale programma chiamante un codice di uscita, che servirà a capire se il programma è stato chiuso per un errore oppure se tutto è andato a buon fine. Le funzioni verranno comunque spiegate in dettaglio nel capitolo dedicato all'argomento.
Iniziamo creando un file prova.c. A questo punto è necessario includere un file di intestazione, in cui sono dichiarate le funzioni standard di input/output del C, di cui parleremo durante il resto del documento. Per far questo, iniziamo il programma in questo modo:
#include <stdio.h>
A questo punto dobbiamo dichiarare la funzione main, che come spiegato prima, ci permetterà di eseguire il programma. Ciò va fatto aggiungendo il seguente codice:
int main () {
}
In questo modo abbiamo dichiarato la funzione main che per la semplicità estrema di questo programma di esempio, conterrà tutto il codice necessario. Se tentassimo di compilare il file prova.c così ottenuto
#include <stdio.h>
int main () {
}
otterremmo un programma perfettamente funzionante ma che non esegue nessuna operazione (provare per credere). Per creare un primo programma che stampi la classica frase "Hello world!" sul video, utilizzeremo la funzione di libreria printf, di cui parleremo in seguito, in questo modo:
printf ("Hello world!\n");
Questo codice va inserito tra le due parentesi graffe della funzione main in questo modo:
#include <stdio.h>
int main () {
printf ("Hello world!\n");
}
La sequenza di caratteri \n, è una sequenza di escape che consente di inserire un carattere new line in una stringa.
Se siete in ambiente linux, potete compilare ed eseguire questo file prova.c in questo modo:
$ gcc -o prova prova.c $ ./prova Hello world! $
dove $ indica la shell. In questo caso è stato utilizzato il compilatore gcc: il parametro -o prova indica che il file eseguibile deve chiamarsi prova; mentre prova.c è il file sorgente che deve essere compilato. In questo esempio ho mostrato anche l'output del programma.
Ora proviamo a modificare questo programma per eseguire qualche operazione in più: dichiariamo tre variabili, assegnamo loro alcuni valori, facciamone la somma e stampiamola:
#include <stdio.h>
int main () {
int a = 5, b = 10, risultato;
risultato = a + b;
printf ("La somma di %d e %d è uguale a %d\n", a, b, risultato);
return 0;
}
In questo esempio abbiamo introdotto alcune novità: dichiarazione di più variabili in un'unica riga, novità sull'utilizzo della funzione printf, l'aggiunta dell'istruzione return 0. Andiamo per ordine. Precedentemente avevamo dichiarato sempre una sola variabile per volta, è possibile però dichiarare più variabili separandole con una virgola, purché siano tutte dello stesso tipo.
La variabile risultato non è stata inizializzata durante la dichiarazione perché viene comunque inizializzata alla riga 6, quando le viene assegnata la somma dei valori delle variabili a e b.
La funzione printf è una funzione che permette di stampare a video testo formattato. Essa riceve infatti almeno un parametro (secondo esempio) che definisce il formato dell'output. Come primo parametro infatti bisogna passare una stringa con eventualmente alcuni "segnaposto" che elencheremo prossimamente, poi un ulteriore parametro per ogni segnaposto inserito nella stringa e nello stesso ordine. Il risultato di questo programma sarà:
La somma di 5 e 10 è uguale a 15
Strutture di controllo
modificaLe strutture di controllo sono delle particolari istruzioni, tipiche dei linguaggi imperativi, che permettono di eseguire delle istruzioni secondo determinate condizioni.
Teorema di Bohm-Jacopini Gli informatici Corrado Bohm e Giuseppe Jacopini, nel 1966 enunciarono il seguente teorema: Ogni algoritmo può essere implementato utilizzando tre sole strutture
- Sequenziali: blocchi di codice procedurale le quali istruzioni vengono eseguite nella stessa sequenza con le quali sono state scritte nel codice sorgente. Queste possono contenere strutture di controllo condizionali e/o iterative
- Condizionali: permettono di specificare due rami o blocchi (uno del vero ed uno del falso) di codice, di cui solo uno verrà eseguito in base al risultato booleano dell'espressione condizionale (vero/falso)
- Iterative: eseguono lo stesso blocco di codice, in modo ripetitivo, finquando l'espressione condizionale è vera
Blocchi di istruzioni sequenziali
modificaQuesto tipo di struttura permette di eseguire una serie di istruzioni in sequenza ognuna terminata dal carattere ; (punto e virgola). Questa sequenza è quella dettata dal codice sorgente. Abbiamo visto gia' alcuni esempi precedenti di codice C ma la cosa importante da capire è che queste istruzioni verranno eseguite solo nel momento in cui il programma entrerà nel blocco di istruzioni, e non in altri momenti dell'esecuzione del programma o in sequenza diversa.
istruzione1;
istruzione2;
istruzione3;
Pertanto, non potrà mai succedere che l'istruzione2 venga eseguita dopo l'istruzione3. Questo discorso vale anche per le istruzioni di assegnazione e di lettura delle variabili, per le quali bisogna fare attenzione all'irreversibilità dell'operazione di scrittura:
int var = 2;
printf("%d\n", var);
var = 3;
printf("%d\n", var);
Come si può notare, con il primo printf si visualizzerà il numero 2 contenuto in var, ma con l'istruzione seguente il numero visualizzato è 3. Ciò significa che il valore 2 inserito in var è stato perso: l'operazione di assegnazione non mantiene in memoria i valori precedentemente scritti nelle variabili.
Strutture condizionali
modificaIF
modificaSintassi
if (condizione){ /*se la "condizione" è vera*/
istruzione 1; /*esegue le istruzioni altrimenti (se falsa) esce dal blocco dell'if*/
.
.
.
istruzione n;
}
Questa struttura funziona nel seguente modo: viene verificata la condizione dell'istruzione if, se essa risulta vera, vengono eseguite le istruzioni immediatamente successive, altrimenti, se risulta falsa, viene saltato il blocco dell'if (quindi non vengono eseguite le istruzioni interne all'if) e vengono eseguite le istruzioni immediatamente successive
Esempio
if (numero == 7){ /*se il numero è uguale a 7*/
numero = numero +1; /*allora lo incrementa di 1*/
}
IF... ELSE
modificaSintassi
if (condizione1) /*se la condizione1 è vera*/
{
istruzioni1; /*allora esegue le istruzioni1*/
}
else
{
if (condizione2) /*altrimenti se la condizione2 è vera*/
{
istruzioni2; /*allora esegue le istruzioni2*/
}
else /*altrimenti*/
{
istruzioni3; /*esegue le istruzioni3*/
}
}
Esiste anche un modo molto più sintetico per scrivere l'istruzione if... else, ed è il seguente
condizione ? istruzioni1 : istruzioni2;
dove:
if = ? else = :
esempio: (numero == 7) ? numero++ : numero--;
Esempio
if (voto >= 18) /*se il voto è maggiore o uguale a 18*/
{
printf("Hai superato l'esame!\n"); /*allora stampa a video "Hai superato l'esame"*/
}
else /*altrimenti*/
{
printf("Non hai superato l'esame\n");/*stampa a video "Non hai superato l'esame"*/
}
Esempio completo
#include<stdio.h>
int main()
{
int voto; /*dichiara la variabile voto intera*/
int promossi=0, bocciati=0; /*dichiara le variabili promossi e bocciati e le azzera*/
printf("Inserisci il voto\n");
scanf("%d", &voto);/*acquisisce il valore e lo memorizza nella variabile voto*/
if (voto >= 18) /*se il voto è maggiore o uguale a 18*/
{
promossi = promossi + 1; /*conta i promossi*/
}
else /*altrimenti*/
{
bocciati = bocciati + 1; /*conta i bocciati*/
}
printf("Promossi = %d\n" , promossi); /*stampa il numero dei promossi a video*/
printf("Bocciati = %d\n" , bocciati); /*stampa il numero dei bocciati a video*/
return 0;
}
In questo codice non ha molto senso contare i promossi e i bocciati (visto che viene acquisito un solo voto, ma tuttavia è a titolo di esempio). Vedremo invece come modificarlo, e quindi renderlo effettivamente funzionale, quando verranno introdotte le strutture iterative.
SWITCH (selezione multipla)
modificaSintassi
switch(valore){/*seleziona il valore*/
case 1: /*confronta con 1*/
istruzione1; /* esegue l'istruzione1*/
break;
case 2:
istruzioni2;
break;
.
.
case n:
istruzioni n
default: /*acquisisce tutti gli altri valori diversi da quelli della selezione (case)*/
istruzioni;
}
Molto simile alla logica dell'istruzione if... else, lo switch viene usato quando bisogna confrontare i diversi valori (case1, case2... casen) che una variabile o un'espressione (switch (val)) può assumere e quindi in base a essi eseguire azioni distinte.L'istruzione break serve per uscire dal blocco dello switch, infatti non avrebbe senso (in molti casi) continuare a confrontare ( ma questo non vuol dire che, in alcune situazioni, il break ci debba sempre essere).
Esempio
switch (voto){/*seleziona la variabile voto*/
case 18: /*osserva se il voto è uguale a 18*/
studenti18++; /*studenti18 = studenti che hanno preso 18. Conta gli studenti che hanno un voto uguale a 18*/
break; /*esce dal blocco dello switch*/
case 19:
studenti19++;
break;
.
.
case 30:
studenti30++;
default:/*controlla se viene immesso un valore errato*/
printf("Inserisci un voto maggiore uguale a 18 o minore uguale a 30\n");
}
Strutture iterative
modificaWHILE
modificaSintassi
I comandi di iterazione consentono di eseguire ripetutamente determinate istruzioni finché una certa condizione rimane vera.
while (condizione){/*esegui le istruzioni finché(mentre) la condizione risulta vera*/
istruzioni;
}
Esempio-Sommare i numeri da 1 a 100
int i=1; /*dichiara e inizializza la variabile contatore intera a 1*/
int somma=0; /*dichiara e inizializza la variabile intera somma a 0*/
while (i <= 100){ /*esegui finché i è minore o uguale a 100*/
somma = somma + i; /*aggiunge il valore della variabile i alla variabile somma*/
i++; /*incrementa la variabile i fino a 101 dopodiché esce dal while*/
}
Questo esempio mostra come avviene la somma dei numeri interi da 1 a 100 mediante l'uso del comando di iterazione while. In questo caso, per permettere l'uscita dal while, viene usata una variabile contatore; la variabile i, che deve essere prima di tutto inizializzata, poi incrementata (o decrementata a seconda dello scopo) per permettere che essa raggiunga il valore che non verificherà più la condizione del while
Esempio completo 1-Scrivere un programma che trovi il massimo tra 15 valori inseriti
#include<stdio.h>
int main()
{
int i=2, max, numero; /*dichiara e inizializza i a 2, dichiara le variabili max e numero*/
printf("Inserisci il primo numero\n");
scanf("%d", &numero); /*acquisisce il primo numero*/
max = numero; /*ipotizza che il primo numero inserito sia il massimo*/
while (i <= 15){ /*esegui finché i diventa uguale a 15*/
printf("Inserisci il %d° numero\n" , i);
scanf("%d", &numero); /*acquisisce i successivi numeri*/
if (numero > max ){ /*se l'i-esimo numero è maggiore di max*/
max = numero; /*allora memorizza l'i-esimo numero nella variabile max*/
}
i++; /*incrementa i fino a 16*/
}
printf ("Il massimo tra i %d valori inseriti vale %d\n", i-1, max);
return 0;
}
Esempio completo 2-Scrivere un programma che prenda in input il lato di un quadrato (valori tra 1 e 15) e quindi lo disegni utilizzando asterischi
#include<stdio.h>
int main()
{
unsigned int j, i=1, lato; /*dichiara e inizializza i a 1 , dichiara la variabile lato*/
printf("Inserisci il valore del lato\n");
scanf("%u", &lato); /*acquisisce il valore del lato*/
while(lato < 1 || lato > 15){/*esegui finché il valore inserito non è corretto, e cioè <1 o >15*/
scanf("%u", &lato); /*acquisisci il valore corretto*/
}
while (i <= lato){/*se osserviamo la figura come una tabella diciamo che scorre le righe */
j = 1 ; /*porta j sulla prima colonna*/
while(j <= lato){/*scorre le colonne*/
printf("*"); /*stampa gli asterischi su tutta la riga*/
j++; /*Incrementa j fino alla fine della colonna (valore del lato)*/
}
printf("\n"); /*va a capo e inizia una nuova riga*/
i++; /*Incrementa i fino alla fine delle righe (valore lato)*/
}
return 0;
}
OUTPUT
Inserisci il valore del lato
6
******
******
******
******
******
******
DO... WHILE
modificaIl do... while è molto simile al while e ha la seguente sintassi:
do{
istruzione/i;
}while(condizione);
Osservando la struttura del do... while si può notare che l'istruzione interna al blocco viene eseguita almeno una volta, senza tener conto se la condizione nel while sia vera o falsa.
Esempio
do{ /*fai*/
printf("Premi C per continuare\n");
scanf("%c", &continua);
}while(continua != 'C');
Riprendiamo ora l'esempio del conteggio dei bocciati e promossi visto nella sezione delle strutture condizionali e rendiamolo funzionale.
Esempio completo: scrivere un programma che legge da tastiera i voti di 10 studenti e quindi conta quanti promossi e bocciati ci sono stati
#include<stdio.h>
int main()
{
int i=0; /*Dichiara e inizializza la variabile i a 0*/
int promossi = 0; /*Dichiara e inizializza la variabile promossi a 0*/
int bocciati = 0; /*Dichiara e inizializza la variabile bocciati a 0*/
int voto; /*Dichiara la variabile voto*/
do{ /*Fai*/
printf("Inserisci un voto\n");
scanf("%d", &voto); /*Acquisisce il voto*/
if (voto >= 18 && voto <= 30)/* Se il voto è maggiore uguale a 18 o minore uguale a 30*/
{
promossi ++; /*allora conta i promossi*/
i++; /* incrementa i (solo se il voto inserito è corretto in questo caso è corretto)*/
}
else
{
if (voto < 18)/*altrimenti se il voto è minore di 18*/
{
bocciati++; /*Conta i bocciati*/
i++; /* incrementa i (solo se il voto inserito è corretto, in questo caso è corretto)*/
}
else
{
if( voto > 30)/*Altrimenti se il voto è maggiore di 30*/
{
printf("Voto errato.\n");/*Visualizza un messaggio di errore e i non viene incrementata*/
}
}
}
} while(i < 10);/* fino a che hai inserito 10 voti corretti*/
printf("Promossi = %d\n", promossi); /*Visualizza i promossi*/
printf("Bocciati = %d\n", bocciati); /*Visualizza i bocciati*/
return 0;
}
OUTPUT
Inserisci un voto
26
Inserisci un voto
35
Voto errato
Inserisci un voto
12
.
.
(e cosi via fino ad aver inserito 10 voti corretti)
promossi = 8
bocciati = 2
FOR
modificaLa sintassi del for è la seguente:
for (inizializzazione; condizione; incremento){
istruzione/i;
}
La logica del comando for è molto simile al while, infatti potrebbero essere benissimo usati per gli stessi scopi. Rimane il fatto che il for è molto più sintetico del while e viene usato quando si conosce già il numero di iterazioni che bisogna fare
Esempio1-Stampare a video una successione di cifre da 2 a 20
for(i=2; i<=20; i++){/*per i che va da 2 a 20*/
printf("%d\n", i);/*stampa i valori*/
}
Esempio completo1-Sommare tutti gli interi pari da 2 a 100
#include<stdio.h>
int main()
{
int i, totale=0; /*dichiara e inizializza i, dichiara la variabile totale*/
for (i=2; i<=100; i=i+2){ /*per i che va da 2 a 100 con un passo di 2*/
totale = totale + i; /*Aggiungi a totale il valore di i*/
}
printf("Totale=%d\n", totale); /*visualizza il risultato*/
return 0;
}
Esempio completo2-Acquisire da tastiera 2 numeri, x e y, tra 1 e 20 e stampare in output una matrice di "?" x * y
#include<stdio.h>
int main()
{
int i, j, x, y;/*dichiarazioni*/
printf("Inserisci i valori di x e y\n");
scanf("%d%d", &x, &y);/*acquisisce i 2 valori x e y*/
while ((x<1) || (x>20) || (y<1) || (y>20)){ /*fai finché i valori sono corretti*/
printf("Valori errati. Inserisci valori corretti\n");
scanf("%d%d", &x, &y);/*acquisisce i valori di x e y*/
}
for (i=1; i<=y; i++){/*Scorre le righe*/
for(j=1; j<=x; j++){/*scorre le clonne*/
printf("?");/*stampa su tutta la i-esima riga*/
}
printf("\n");/* va a capo a fine riga*/
}
return 0;
}
OUTPUT
Inserisci i valori di x e y
3
4
???
???
???
???
Esempio3-Scrivere un programma che calcoli e quindi visualizzi i primi 10000 numeri primi
#include<stdio.h>
int main()
{
int i, j, primo;
printf("I numeri primi da 1 a 10000 sono:\n");
for (i=2; i<=10000; i++){
primo = 0; /*inizializza la variabile primo, cha ha il compito di una variabile sentinella*/
for(j=2; j<i; j++){ /* scorre tutti i divisori del numero i(ovviamente partendo dal numero 2)*/
if(i % j != 0){
primo++;
}
}
if (primo == i-2){/* se tutti i numeri precedenti(all'i-esimo numero) divisi per l'i-esimo numero hanno dato resto diverso da 0*/
printf("%d\t", i); /* allora l'i-esimo numero è un numero primo*/
}
}
return 0;
}
Istruzione break in un comando for
for (i=1; i<=10; i++){/*per i che va da 1 a 10*/
if (i == 6){/*se i è uguale a 6*/
break;/*allora esci dal ciclo*/
}
printf("%d\t", i);
}
OUTPUT
1 2 3 4 5
L'istruzione break (come si può intuire dall'esempio) provoca l'uscita immediata dal ciclo appena si verifica una specifica condizione.
Istruzione continue in un comando for
for (i=1; i<=10; i++){ /*per i che va da 1 a 10*/
if (i==6){ /*se i è uguale a 6*/
continue; /*allora non eseguire le istruzioni successive*/
}
printf("%d\t", i);
}
OUTPUT
1 2 3 4 5 7 8 9 10
L'istruzione continue salta un blocco di istruzioni al verificarsi di una certa condizione, continuando però con l'iterazione.
Funzioni
modificaQuando si deve sviluppare un programma complesso , oppure di grandi dimensioni, un'ottima tecnica è quella di utilizzare delle funzioni. Le funzioni non sono altro che "pezzi"(meglio moduli) più piccoli del programma. È come se prendessimo il programma principale e lo dividessimo in piccole parti. Quindi un lavoro svolto in questo modo risulta sicuramente più lineare e agevole.
Le variabili dichiarate nella definizione di una funzione sono chiamate variabili locali La maggior parte delle funzioni contengono una lista di parametri
Dichiarazione di una funzione
tipo_del_valore_di_ ritorno nome_funzione (lista_parametri)
{
dichiarazioni;
istruzioni;
}
Esempio completo1-Scrivere un programma che calcoli il quadrato dei numeri da 1 a 5 utilizzando una funzione "quadrato"
#include<stdio.h>
int quadrato (int y); /* prototipo di funzione*/
int main()
{
int x; /*dichiarazioni*/
for (x=1; x<=5; x++){/*per x che va da 1 a 5*/
printf("%d\t", quadrato(x)); /*chiamata alla funzione*/
}
return 0;
}
int quadrato (int y)
{
return y*y; /*ritorna il quadrato della variabile x passata al parametro della funzione "quadrato"*/
}
OUTPUT
1 4 9 16 25
Quando viene chiamata (invocata) la funzione (in questo caso printf ("% d\t", quadrato (x))) è come se il valore di x viene copiato nel parametro y della funzione quadrato, quindi viene calcolato e ritornato come un intero (int quadrato...)al chiamante (programma principale)
Prototipi di funzione La logica è simile a una dichiarazione di variabile, solo che questa volta è una funzione. Il prototipo serve, quindi, per indicare al compilatore il tipo di dato che la funzione deve restituire , il numero di parametri che si aspetta di ricevere , l'ordine in cui sono attesi e il tipo di parametri. Se viene omesso il prototipo, il compilatore non riuscirà a riconoscere la funzione.
Esempio completo2-Scrivere un programma che somma 2 numeri a e b utilizzando una funzione "somma"
#include<stdio.h>
int somma (int x, int y); /*prototipo*/
int main()
{
int a, b, totale; /*dichiarazioni*/
printf("Inserisci due numeri\n");
scanf("%d%d", &a, &b); /*acquisisce due numeri da tastiera*/
totale = somma (a, b); /*chiamata alla funzione*/
printf("Totale = %d\n", totale);
return 0;
}
int somma (int x, int y)
{
return x + y; /*ritorna il risultato al chiamante*/
}
OPPURE
int somma (int x, int y)
{
int tot = 0;
tot = x + y;
return tot;
}
Esempio completo3-Trovare il maggiore tra 3 numeri utilizzando la funzione "maggiore"
#include<stdio.h>
int maggiore (int, int, int); /*prototipo (nel prototipo il nome del parametro può anche essere omesso)*/
int main()
{
int n1, n2, n3, max; /*dichiarazioni*/
printf("Inserisci 3 numeri\n");
scanf("%d%d%d", &n1, &n2, &n3); /*acquisisce 3 numeri da tastiera*/
max = maggiore (n1, n2, n3); /*chiamata alla funzione "maggiore"*/
printf("Maggiore = %d\n", max); /*Visualizza il massimo*/
return 0;
}
int maggiore (int a, int b, int c)
{
int M;
M = a; /*ipotizza che il numero a (n1) sia il massimo*/
if (b > M){ /*se b(n2) è maggiore di M(a)*/
M = b; /*allora memorizza b nella variabile M*/
}
else if (c > M){ /*altrimenti se c(n3) è maggiore di M(b)*/
M = c; /*allora memorizza c nella variabile M*/
}
return M; /*ritorna il numero massimo*/
}
FUNZIONE RAND()
La funzione rand() genera dei numeri casuali compresi tra 0 e RAND_MAX (costante simbolica che viene definita nel file d'intestazione stdlib.h)
1°Caso Caso in cui la funzione rand genera un numero compreso tra 0 e rand_max
{
int i, x; /*dichiarazioni*/
for(i=0; i<=100; i++){ /* per i che va da 0 a 100*/
x = rand(); /*genera un numero casuale compreso tra 0 e rand_max*/
printf("%d\t", x); /*visualizza il numero*/
}
}
OUTPUT
34 456 1 5 987 23 45 10 99...
2°Caso
Utilizziamo un seme
{
int i, x; /*dichiarazioni*/
unsigned seme; /*dichiara il seme; in pratica sostituisce la costante rand_max*/
printf("Inserisci il seme\n");
scanf("%u", &seme);
srand(seme); /*definisce il seme*/
for(i=0; i<=100; i++){ /* per i che va da 0 a 100*/
x = rand(); /*genera un numero casuale compreso tra 0 e seme*/
printf("%d\t", x); /*visualizza il numero*/
}
}
OUTPUT
*Seme = 10
31 62 10 249 1 93 4 46 6 700 1...
*Seme = 456
412 13 36 138 273 452 10 23 65 8...
*Seme = 10
31 62 10 249 1 93 4 46 6 700 1...
Possiamo notare che quando il seme è uguale a 10la sequenza di numeri è la stessa. Lo steesso avviene quando il seme è rand_max (essendo questa una costante).
Per ovviare al problema possiamo utilizzare una istruzione di questo tipo
srand(time(NULL))
che permette la lettura dell'orologio interno al sistema, ottenendo cosi automaticamente un valore per il seme. La funzione time restituisce l'ora espressa in secondi. Il prototipo di questa funzione si trova nel file d'intestazione time.h.
Esempio1-Simulare il lancio di un dado 10 volte
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
int main()
{
int i, faccia; /*dichiarazioni*/
srand(time(NULL)); /*seme*/
for (i=1; i<=10; i++){ /*per i che va da 1 a 10*/
faccia = 1 + rand()%6; /*genera un numero compreso uguale tra 1 e 6*/
printf("%d\t", faccia);
/*ogni 5 numeri stampati vai a capo*/
if (i%5==0){
printf("\n");
}
}
return 0;
}
OUTPUT
1 2 6 4 6
3 3 5 1 6
L'istruzione faccia = 1 + rand()%6 genera un numero compreso uguale tra 1 e 6. Una divisione intera per 6 può generare i resti (0, 1, 2, 3, 4, 5), per questo motivo viene aggiunto 1.
Esempio3-Simulare il lancio di un dado 500 volte e verificare quante volte esce 1, 2, 3, 4, 5, 6
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
int main()
{
int i, faccia; /*dichiarazioni*/
int freq1=0, freq2=0, freq3=0, freq4=0, freq5=0, freq6=0; /*dichiara e inizializza le variabili frequenza*/
srand(time(NULL)); /*seme*/
for (i=1; i<=500; i++){ /*per i che va da 1 a 500*/
faccia = 1 + rand()%6; /*genera un numero compreso uguale tra 1 e 6*/
switch(faccia){ /*seleziona il valore della faccia*/
case 1: /*se è uguale a 1*/
freq1++; /*incrementa la variabile che conta la faccia 1 */
break;
case 2:
freq2++;
break;
case 3:
freq3++;
break;
case 4:
freq4++;
break;
case 5:
freq5++;
break;
case 6:
freq6++;
break;
}
}
/*visualizza i risultati*/
printf("%d%15d\n", 1, freq1);
printf("%d%15d\n", 2, freq2);
printf("%d%15d\n", 3, freq3);
printf("%d%15d\n", 4, freq4);
printf("%d%15d\n", 5, freq5);
printf("%d%15d\n", 6, freq6);
return 0;
}
OUTPUT
1 93
2 87
3 82
4 78
5 84
6 76
Ricorsione
modificaPer approfondire questo argomento, consulta la pagina Algoritmo Ricorsivo. |
Prendiamo come esempio la successione
{2n}
essa è definita ricorsivamente in questo modo
Partendo da si ha che
Il termine successivo è
Quello ancora successivo è e cosi via
È importante individuare
- La clausola base che serve per indicare il primo termine della successione
- La clausola induttiva che serve ad indicare le operazioni da compiere per calcolare i valori successivi
La serie di Fibonacci
modifica0, 1, 1, 2, 3, 5, 8, 13, 21...
Può essere definita in modo ricorsivo come segue
fibonacci(0) = 0
fibonacci(1) = 1
fibonacci(n) = fibonacci(n-1) + fibonacci(n-2)
Vale a dire che l'n-esimo numero di fibonacci viene calcolato sommando i due termini precedenti (fibonacci (n-1) + fibonacci (n-2)
Ad esempio se volessimo calcolare fibonacci (3) la sequenza sarebbe la seguente
f(3) = f(2) + f(1) ritorna 1 (f(1))
f(2) = f(1) + f(0) ritorna 1 (f(1)) e ritorna 0 (f(0))
quindi
f (3) = 2 infatti il quarto (la successione parte da n = 0) numero di fibonacci è proprio il 2
- Versione ricorsiva
#include<stdio.h>
long fibonacci(long n);
int main()
{
long val; /* Valore di Fibonacci*/
long num; /* n-esimo numero di fibonacci*/
printf("Inserire un intero\n");
scanf("%ld", &num);
val = fibonacci(num);
printf("Fibonacci(%ld) = %ld\n", num, val);
return 0;
}
long fibonacci (long n)
{
if(n==0 || n==1){ /* Clausola base*/
return n;
}
else{
return fibonacci(n-1) + fibonacci(n-2); /*Chiamata ricorsiva*/
}
}
OUTPUT
Inserire un intero
Fibonacci(0) = 0
Inserire un intero
Fibonacci(1) = 1
Inserire un intero
Fibonacci(4) = 3
Inserire un intero
Fibonacci(10) = 55
- Versione Iterativa
#include<stdio.h>
int main()
{
int n,i;
int a = 1;
int b = 1;
int c = 1;
printf("Inserisci l'n-esimo numero di fibonacci da calcolare\n");
scanf("%d", &n);
for(i=2; i<n; i++){
a = b;
b = c;
c = a + b;
}
printf ("Fibonacci(%d) = %d\n", n, c);
return 0;
}
Arrays
modificaUn array non è altro che un puntatore. Il linguaggio C consente di "vedere" i puntatori in due modi diversi:
- Appunto, come puntatori; in questo caso sono disponibili gli operatori "indirizzo di", "valore di", gli operatori di incremento, di decremento, ecc.
Esempio: int i; int* ptr = &i;
.
- Arrays. In questo caso il linguaggio rende disponibili gli operatori di accesso agli array.
Esempio: int[] array = ...; int value = array[...];
.
Nota: è sempre scorretto utilizzare il termine "vettore" per indicare un array. I vettori sono entità definiti nelle librerie standard (#include <vector.h>
).
La dimensione dello spazio di memoria allocato da un array, se espresso in bytes, è pari al prodotto tra il numero di elementi dell'array stesso e la dimensione, in bytes, del tipo di dato a cui esso fa riferimento.
Ad esempio:
- int array[4];
Se un valore di tipo 'int' occupa 4 bytes in memoria (per i processori a 32 bits è, in generale, così), la memoria allocata da array
è pari a 16 bytes.
Esempi di utilizzo
modificaDichiarazione di arrays
Sintassi
int V[5]={3, 4, 78, 0, -3}; /*Dichiara e inizializza un array di 5 elementi interi di nome V*/
Esempio1-Definire un array e inizializzarlo con un ciclo for
#include<stdio.h>
int main()
{
int V[5]; /*Dichiara un array V di 5 elementi di tipo int*/
int i; /*Dichiara la variabile i*/
for (i=0; i<5; i++){ /*scorre in modo sequenziale gli elementi del vettore V*/
V[i] = 2 * i; /*Inizializza l'i-esimo elemento di V */
}
/*Visualizza il vettore in formato tabulare*/
printf("%s%15s\n", "Elemento", "Valore");
for (i=0; i<5; i++){
printf("%8d%15d\n", i, V[i]);
}
return 0;
}
OUTPUT
Elemento Valore
0 0
1 2
2 4
3 6
4 8
Bisogna notare la differenza tra il terzo elemento del vettore (V[2] = 4) e l'elemento 3 del vettore (V[3] = 6), visto che l'indice parte da 0
Esempio2-Definire (con una costante simbolica), inizializzare da tastiera e visualizzare un vettore V di 5 elementi interi
#include<stdio.h>
#define dim 5 /*costante simbolica*/
int main()
{
int V[dim]; /*Dichiara il vettore tramite la costante simbolica*/
int i;
printf("Inserisci i valori nel vettore\n");
for(i=0; i<dim; i++){ /*scorre il vettore*/
scanf("%d", &V[i]); /*acquisisce da tastiera l'i-esimo elemento del vettore*/
}
/*visualizza il vettore*/
printf("%s%15s\n","Elemento","Valore");
for(i=0; i<5; i++){
printf("%8d%15d\n", i, V[i]);
}
return 0;
}
OUTPUT
Inserisci i valori nel vettore
4
76
9
-24
23
Elemento Valore
0 4
1 76
2 9
3 -24
4 23
Esempio3-Sommare gli elementi di un vettore V di 5 interi, calcolare la media e visualizzare il vettore
#inclu<stdio.h>
#define dim 5
int main()
{
int V[dim]= {2, 6, 8, 2, 10}; /*dichiarazione e inizializzazione*/
int i, totale=0; /*dichiara la variabile i, dichiara e inizializza a 0 la variabile totale*/
double media; /*dichiara la variabile media di tipo double*/
for (i=0; i<dim; i++){ /*scorre il vettore*/
totale += V[i]; /*somma l'i-esimo elemento del vettore alla variabile totale*/
}
media = (double)totale / dim; /*calcola la media*/
/*visualizza il vettore*/
printf("%s%15s\n","Elemento", "Valore");
for(i=0; i<5; i++){
printf("%8d%15d\n", i, V[i]);
}
printf("Totale = %d\n", totale); /*visualizza il totale*/
printf("Media = %.2f\n", media); /*visualizza la media*/
return 0;
}
OUTPUT
Elemento Valore
0 2
1 6
2 8
3 2
4 10
Totale = 28
Media = 5.60
Esempio4-Rappresentare graficamente gli elementi di un vettore con dei "+"
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define dim 5
int main()
{
int V[dim], i, j, val;
srand(time(NULL));
printf("%s%15s%18s\n", "Elemento","Valore", "Frequenza");
for(i=0; i<dim; i++){
val = 1 + rand() % 10;
V[i] = val;
}
for(i=0; i<dim; i++){
printf("%8d%15d ", i, V[i]);
for(j=1; j<=V[i]; j++){
printf("%c",'+');
}
printf("\n");
}
return 0;
}
OUTPUT
Elemento Valore Frequenza
0 2 ++
1 4 ++++
2 1 +
3 8 ++++++++
4 7 +++++++
Esempio5-Lanciare un dado 500 volte e riassumere i risultati in un vettore
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define dim 7
int main()
{
int VF[dim]={0}, i, faccia; /*dichiara e inizializza il vettore frequenza, la variabile i e faccia*/
srand(time(NULL)); /*seme*/
for(i=1; i<=500; i++){ /*per i che va da 1 a 500*/
faccia = 1 + rand()%6; /*genera i numeri casuali compresi uguali tra 1 e 6*/
VF[faccia] += 1; /*incrementa il vettore frequenza in base alla faccia(indice del vettore) che è uscita*/
}
/*visualizza il vettore*/
printf("%s%15s\n","Faccia","Frequenza");
for(i=1; i<dim; i++){
printf("%6d%15d\n", i, VF[i]);
}
return 0;
}
OUTPUT
Faccia Frequenza
1 89
2 70
3 91
4 86
5 79
6 85
Esempi di operazioni su numeri primi
modificaEsempio6-Il crivello di Eratostene
/*Per comprendere il codice si faccia riferimento al link riportato sopra*/
#include <stdio.h>
#include <math.h> /*Libreria necessaria per poter usare la funzione sqrt()*/
#define DIM 1000
int main (void)
{
printf ("Crivello di Eratostene\n\n");
int v[DIM+1];
int p[DIM+1];
int i, j, k = 0;
for (i = 2; i <= DIM; i++)
{
v[i] = i; /*Inizializzo il vettore v con i valori del suo indice; quindi contiene in modo sequenziale valori da 2 a 1000 */
}
for (i = 2; i <= sqrt(DIM); i++) /*la funzione sqrt(DIM) da come valore la radice quadrata di DIM*/
{
for (j = i + 1; j <= DIM; j++)
{
if (v[j] % i == 0)
{ /*Verifico se il valore i-esimo del vettore v possiede dei multipli, scorrendo tutti i suoi i-esimi valori successivi */
v[j] = 0; /*Se è vero allora "cancello" i suoi multipli inserendo uno 0 */
}
}
}
/*Ora il vettore contiene tutti i numeri primi più gli zeri, quindi occorre "compattare" il vettore eliminando gli zeri */
for (i = 2; i <= DIM; i++)
{
if (v[i] != 0)
{ /*Se l'elemento i-esimo del vettore v non è uno 0 */
p[k] = v[i]; /*Inserisco il valore nel vettore p che conterrà solo i numeri (primi) senza gli 0 */
k++; /*Incremento la variabile k che servirà per determinare la dimensione del vettore p di primi */
}
}
/*Stampo a video*/
for (i = 0; i < k; i++)
{
printf ("%d\t", p[i]);
}
return 0;
}
Passare i vettori alle funzioni
modificaI vettori vengono passati alle funzioni per riferimento (viene passato l'indirizzo di memoria e non solo il valore), infatti il nome del vettore è l'indirizzo del primo elemento. Questo significa che, quando all'interno della funzione avvengono delle modifiche al vettore, la funzione starà modificando gli elementi originali del vettore e non una loro copia, quindi la modifica avrà effetto anche sul chiamante (programma principale) e non solo all'interno della funzione.
Esempio1-Inizializzare e visualizzare un vettore con delle funzioni
#include<stdio.h>
#define dim 5
/*prototipi*/
void crea (int cv[], int DIM); /*funzione per creare il vettore*/
void visualizza (int vs[], int DIM); /*funzione per visualizzare il vettore*/
int main()
{
int V[dim]; /*dichiarazione del vettore di dimensione dim*/
crea (V, dim); /*chiamata alla funzione crea*/
visualizza (V, dim); /*chiamata alla funzione visualizza*/
return 0;
}
void crea (int cv[], int DIM)
{
int i;
printf("Inserisci %d valori nel vettore\n", DIM);
for (i=0; i<DIM; i++){
scanf("%d", &cv[i]);
}
}
void visualizza (int vs[], int DIM)
{
int i;
printf("%s%15s\n","Elemento","Valore");
for(i=0; i<DIM; i++){
printf("%8d%15d\n", i, vs[i]);
}
}
La parola VOID indica alla funzione che non deve ritornare alcun valore.
Esempio2-Passare alle funzioni un vettore , modificarlo e modificare un singolo elemento del vettore
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define dim 5
void crea (int vc[], int DIM);
void modifica (int vm[], int DIM);
void modifica_elem (int x);
void visualizza (int vs[], int DIM);
int main()
{
int V[dim];
crea (V, dim);
printf ("Vettore originale\n");
visualizza (V, dim);
modifica (V, dim);
printf("\n");
printf ("Vettore modificato \n");
visualizza (V, dim);
printf("\n");
printf ("Il valore di V[4] è %d\n", V[4]);
modifica_elem(V[4]);
printf ("Il valore di V[4] è %d\n", V[4]);
system("pause");
return 0;
}
void crea (int vc[], int DIM)
{
int i, val;
srand(time(NULL));
for (i=0; i<DIM; i++){
val = 1 + rand()%20;
vc[i] = val;
}
}
void modifica (int vm[], int DIM)
{
int i;
for (i=0; i<DIM; i++){
vm[i] *= 2;
}
}
void modifica_elem (int x)
{
x *= 10;
printf("Il valore di V[4] modificato vale %d\n", x);
}
void visualizza (int vs[], int DIM)
{
int i;
printf("%s%15s\n", "Elemento", "Valore");
for (i=0; i<DIM; i++){
printf("%8d%15d\n", i, vs[i]);
}
}
OUTPUT
Vettore Originale
Elemento Valore
0 3
1 12
2 13
3 9
4 7
Vettore Modificato
Elemento Valore
0 6
1 24
2 26
3 18
4 14
Il valore di V[4] è 14
Il valore di V[4] modificato vale 140
il valore di V[4] è 14
In questo esempio bisogna notare come gli elementi del vettore vengono modificati. Quando viene chiamata la funzione "modifica" viene passato l'intero vettore, e quindi i suoi elementi sono passati per riferimento (viene modificato non solo all'interno della funzione "modifica" ma anche nel programma principale) modificando il vettore originale. Mentre quando viene chiamata la funzione "modifica_elem" viene passato un singolo elemento del vettore. Questo provocherà un passaggio per valore e non per riferimento (viene passato un singolo elemento e non l'intero array) , quindi la modifica avrà effetto solo all'interno della funzione "modifica_elem" e non all'interno della funzione main (principale)
Ordinamento
modificaPer approfondire questo argomento, consulta la pagina Algoritmo di ordinamento. |
In questo esempio utilizzeremo la tecnica del Bubble Sort
Esempio- Scrivere un programma che ordini dal più piccolo al più grande gli elementi di un array (implementare il programma con delle funzioni)
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define dim 10
/*prototipi*/
void crea (int vc[], int DIM);
void ordina (int vo[], int DIM);
void visualizza (int vs[], int DIM);
int main()
{
int V[dim]; /*dichiarazione del vettore V di diemnsione dim*/
/*chiamate alle funzioni crea, ordina e visualizza*/
crea (V, dim);
ordina (V, dim);
visualizza (V, dim);
return 0;
}
void crea (int vc[], int DIM)
{
int i, val;
srand(time(NULL));
for (i=0; i<DIM; i++){
val = 1 + rand()%100;
vc[i] = val;
}
}
void ordina (int vo[], int DIM)
{
int i, j, tmp; /*dichiarazioni, la variabile tmp(temporanea) serve per lo scambio*/
for (j=DIM-1; j>0; j--){ /*effettua j-1 passaggi sul vettore*/
for (i=0; i<=j; i++){ /*scorre il vettore*/
if(vo[i]>vo[i+1]){/* se nella i-esima posizione c'è un valore maggiore della i-esima posizione successiva*/
/*allora scambia l'i-esimo valore con l'i-esimo valore successivo*/
tmp = vo[i];
vo[i] = vo[i+1];
vo[i+1] = tmp;
}
}
}
}
void visualizza (int vs[], int DIM)
{
int i;
printf("%s%15s\n", "Elemento", "Valore");
for (i=0; i<DIM; i++){
printf("%8d%15d\n", i, vs[i]);
}
}
OUTPUT
Elemento Valore
0 10
1 13
2 13
3 26
4 35
5 65
6 71
7 86
8 89
9 94
Ricerca
modificaPer approfondire questo argomento, consulta la pagina [[Algoritmo di ricerca]]. |
Ricerca lineare
modificaIn questo esempio utilizzeremo la tecnica della ricerca lineare
Esempio-Ricerca lineare
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define dim 5
/*prototipi*/
void crea (int vc[], int DIM);
int ricerca (int vr[], int key, int DIM);
void visualizza (int vs[], int DIM);
int main()
{
int V[dim], chiave, elemento;
crea (V, dim);
printf("Inserisci il valore da cercare\n");
scanf("%d", &chiave); /*acquisisce il valore(chiave) da trovare*/
elemento = ricerca (V, chiave, dim); /*chiamata alla funzione ricerca*/
if (elemento != -1){ /*se il valore ritornato è diverso da -1*/
printf("Valore trovato nell'elemento %d del vettore\n", elemento);/*allora lo ha trovato*/
}
else{
printf("Valore non trovato\n");/*altrimenti non lo ha trovato*/
}
visualizza (V, dim);
return 0;
}
void crea (int vc[], int DIM)
{
int i, val;
srand(time(NULL));
for(i=0; i<DIM; i++){
val = 1 + rand()%100;
vc[i] = val;
}
}
int ricerca (int vr[], int key, int DIM)
{
int i;
for( i=0; i<DIM; i++ ) { /*scorre il vettore*/
if( vr[i] == key ) { /*se il valore della posizione i-esima del vettore è uguale a key(chiave)*/
return i; /*allora ritorna la posizione i-esima dove lo ha trovato*/
}
}
return -1; /*se non ci sono corrispondenze ritorna -1*/
}
void visualizza (int vs[], int DIM)
{
int i;
printf("%s%15s\n", "Elemento", "Valore");
for(i=0; i<DIM; i++){
printf("%8d%15d\n", i, vs[i]);
}
}
OUTPUT
Inserisci il valore da cercare
65
Valore trovato nella posizione 3 del vettore
Elemento Valore
0 23
1 12
2 36
3 65
4 5
Arrays multidimensionali (matrici)
modificaPer rappresentare tabelle (dette anche "matrici") vengono usati degli arrays con più di un indice.
es. m 3 X 4
m[i][j] colonna 0 colonna 1 colonna 2 colonna 3
riga 0 m[0][0] m[0][1] m[0][2] m[0][3]
riga 1 m[1][0] m[1][1] m[1][2] m[1][3]
riga 2 m[2][0] m[2][1] m[2][2] m[2][3]
Una matrice può essere vista come un array i cui elementi siano a loro volta altri arrays (e così via), quindi un puntatore a puntatore (a puntatore...)
Nel caso particolare di una matrice del tipo m[i][j], i due indici i e j vengono detti rispettivamente "indice di riga" e "indice di colonna" (ma, ovviamente, il fatto di adottare il primo indice per le righe ed il secondo per le colonne è puramente convenzionale).
- DICHIARAZIONE
int m[4][4]= {{3, 5, 6, 9},{4, 8, 6, 5},{5,6},{2,6,9}};
Viene dichiarata una matrice 4 X 4 di nome m.
I valori nel primo insieme di parentesi inizializzano la riga 0; quelli del secondo insieme, la riga 1... e cosi via
Esempio1-Dichiarazione e inizializzazione
#include<stdio.h>
void visualizza (int [][3], int);
int main()
{
int nm; /*numero della matrice*/
int M1[2][3]={{1,2},{3,5,6}};
int M2[2][3]={{3,6,5}};
int M3[2][3]={{1,2,3},{6,4,3}};
nm = 1;
visualizza (M1, nm);
nm = 2;
visualizza (M2, nm);
nm = 3;
visualizza (M3, nm);
return 0;
}
void visualizza (int matrice[][3], int k)
{
int i, j;
for(i=0; i<2; i++){ /*scorre le righe*/
for(j=0; j<3; j++){ /*scorre le colonne*/
printf("Matrice%d [%d][%d] = %d\n", k, i, j, matrice[i][j]);
}
}
printf("\n");
}
OUTPUT
Matrice1 [0][0] = 1
Matrice1 [0][1] = 2
Matrice1 [0][3] = 0
Matrice1 [1][0] = 3
Matrice1 [1][1] = 5
Matrice1 [1][3] = 6
Matrice2 [0][0] = 3
Matrice2 [0][1] = 6
Matrice2 [0][3] = 5
Matrice2 [1][0] = 0
Matrice2 [1][1] = 0
Matrice2 [1][3] = 0
Matrice3 [0][0] = 1
Matrice3 [0][1] = 2
Matrice3 [0][3] = 3
Matrice3 [1][0] = 6
Matrice3 [1][1] = 4
Matrice3 [1][3] = 3
Esempio2-Scrivere un programma che: inizializza, visualizza, trova il massimo, la media e la media delle righe di una matrice. Il tutto deve essere implementato con delle funzioni
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define riga 4
#define colonna 4
void crea (int mc[][colonna], int RIGA);
void visualizza (int ms[][colonna], int RIGA);
void massimo (int mm[][colonna], int RIGA);
void media (int mmed[][colonna], int RIGA);
double media_righe (int mr[], int COLONNA);
int main()
{
int M[riga][colonna], i;
crea(M, riga);
visualizza (M, riga);
printf("\n");
printf("\n");
massimo (M, riga);
media (M, riga);
printf("\n");
for(i=0; i<riga; i++){
printf("La media della riga %d vale %.2f\n", i, media_righe(M[i], colonna));
}
return 0;
}
void crea (int mc[][colonna], int RIGA)
{
int i, j, val;
srand(time(NULL));
for(i=0; i<RIGA; i++){
for(j=0; j<colonna; j++){
val = 1 + rand()%100;
mc[i][j] = val;
}
}
}
void visualizza (int ms[][colonna], int RIGA)
{
int i, j;
/*visualizza in formato tabulare*/
printf(" \t[0] [1] [2] [3]\n");
for(i=0; i<RIGA; i++){
printf("\nMatrice[%d]\t", i);
for (j=0; j<colonna; j++){
printf("%-5d", ms[i][j]);
}
}
}
void massimo (int mm[][colonna], int RIGA)
{
int i, j, max;
max = mm[0][0];
for(i=0; i<RIGA; i++){
for(j=0; j<colonna; j++){
if (mm[i][j] > max){
max = mm[i][j];
}
}
}
printf("Massimo = %d\n", max);
}
void media (int mmed[][colonna], int RIGA)
{
int i, j, tot=0;
double media;
for(i=0; i<RIGA; i++){
for(j=0; j<colonna; j++){
tot += mmed[i][j];
}
}
media = (double)tot / (i*j);
printf("Media = %.2f\n", media);
}
double media_righe (int mr[], int COLONNA)
{
int j, tot=0;
for(j=0; j<COLONNA; j++){
tot += mr[j];
}
return (double)tot/COLONNA;
}
OUTPUT
[0] [1] [2] [3]
Matrice[0] 11 19 68 60
Matrice[1] 11 46 78 93
Matrice[2] 94 65 98 16
Matrice[3] 83 45 13 2
Massimo = 98
Media = 50.13
La media della riga 0 vale 39.50
La media della riga 1 vale 57.00
La media della riga 2 vale 68.25
La media della riga 3 vale 35.75
Esempio3-Scrivere un programma che inizializzi con dei valori casuali una matrice 4*4, visualizzi la matrice in formato tabulare, calcoli la matrice trasposta e visualizzi quest'ultima sempre in formato tabulare. Il programma deve essere implementato mediante funzioni
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define riga 4
#define colonna 4
void crea (int mc[][colonna], int RIGA);
void trasposta (int t[][colonna], int m[][colonna], int RIGA);
void visualizza (int ms[][colonna], int RIGA);
void visualizza_trasposta (int mv[][colonna], int RIGA);
int main()
{
int mat[riga][colonna], trasp[riga][colonna];
crea (mat, riga);
visualizza (mat, riga);
trasposta (trasp, mat, riga);
printf("\n\n\n");
visualizza_trasposta (trasp, riga);
return 0;
}
void crea (int mc[][colonna], int RIGA)
{
int i,j,val;
srand(time(NULL));
for (i=0; i<RIGA; i++){
for(j=0; j<colonna; j++){
val = 1 + rand()%100;
mc[i][j] = val;
}
}
}
void trasposta (int t[][colonna], int m[][colonna], int RIGA)
{
int i,j;
int tmp; /*Dichiaro la variabile temporanea per lo scambio*/
for (i=0; i<RIGA; i++){
for(j=0; j<colonna; j++){
tmp = t[i][j];
t[i][j] = m[j][i];
m[j][i] = tmp;
}
}
}
void visualizza (int ms[][colonna], int RIGA)
{
int i,j;
printf (" [0] [1] [2] [3]");
printf("\n");
for (i=0; i<RIGA; i++){
printf("\nMatrice[%d] ",i);
for (j=0; j<colonna; j++){
printf("%-5d", ms[i][j]);
}
printf("\n");
}
}
void visualizza_trasposta (int mv[][colonna], int RIGA)
{
int i,j;
printf (" [0] [1] [2] [3]");
printf("\n");
for (i=0; i<RIGA; i++){
printf("\nTrasposta[%d] ",i);
for (j=0; j<colonna; j++){
printf("%-5d", mv[i][j]);
}
printf("\n");
}
}
OUTPUT
[0] [1] [2] [3]
Matrice[0] 11 19 68 60
Matrice[1] 11 46 78 93
Matrice[2] 94 65 98 16
Matrice[3] 83 45 13 2
[0] [1] [2] [3]
Trasposta[0] 11 11 94 83
Trasposta[1] 19 46 65 45
Trasposta[2] 68 78 98 13
Trasposta[3] 60 93 16 2
Esempio4-Scrivere un programma che ricerca il valore massimo e il valore minimo in una matrice di interi 4*4. Il programma deve tenere conto anche se il valore del minimo e del massimo si è presentato più volte. Bisogna implementare il tutto mediante funzioni
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define righe 4
#define colonne 4
void crea (int mc[][colonne], int RIGHE);
void massimo (int mmax[][colonne], int RIGHE);
void minimo (int mmin[][colonne], int RIGHE);
void visualizza (int ms[][colonne], int RIGHE);
int main()
{
int m[righe][colonne];
crea (m, righe);
visualizza (m, righe);
printf("\n\n");
massimo (m, righe);
printf("\n\n");
minimo (m, righe);
return 0;
}
void crea (int mc[][colonne], int RIGHE)
{
int i, j, val;
srand(time(NULL));
for (i=0; i<RIGHE; i++){
for (j=0; j<colonne; j++){
val = 1 + rand()%20;
mc[i][j] = val;
}
}
}
void massimo (int mmax[][colonne], int RIGHE)
{
int i, j;
int max = mmax[0][0];
for (i=0; i<RIGHE; i++){
for (j=0; j<colonne; j++){
if (mmax[i][j] > max){
max = mmax[i][j];
}
}
}
printf("Massimo = %d\n", max);
printf("Si trova nella/e posizione/i:\n");
for (i=0; i<RIGHE; i++){
for (j=0; j<colonne; j++){
if (max == mmax[i][j]){
printf("M[%d][%d]\n",i,j);
}
}
}
}
void minimo (int mmin[][colonne], int RIGHE)
{
int i, j;
int min = mmin[0][0];
for (i=0; i<RIGHE; i++){
for (j=0; j<colonne; j++){
if (mmin[i][j] < min){
min = mmin[i][j];
}
}
}
printf("Minimo = %d \n", min);
printf("Si trova nella/e posizione/i:\n");
for (i=0; i<RIGHE; i++){
for (j=0; j<colonne; j++){
if (min == mmin[i][j]){
printf("M[%d][%d]\n",i,j);
}
}
}
}
void visualizza (int ms[][colonne], int RIGHE)
{
int i,j;
printf (" [0] [1] [2] [3]");
printf("\n");
for (i=0; i<RIGHE; i++){
printf("\nMatrice[%d] ",i);
for (j=0; j<colonne; j++){
printf("%-5d", ms[i][j]);
}
printf("\n");
}
}
OUTPUT
[0] [1] [2] [3]
Matrice[0] 13 11 16 4
Matrice[1] 14 3 3 18
Matrice[2] 18 11 17 7
Matrice[3] 4 17 20 14
Massimo = 20
Si trova nella/e posizione/i:
M[3][2]
Minimo = 3
Si trova nella/e posizione/i:
M[1][1]
M[1][2]
Esempio5- Inizializzare una matrice di interi 4*4 e ordinarla in modo ascendente
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define righe 4
#define colonne 4
#define dim righe * colonne
void crea (int m[][colonne], int RIGHE);
void ordina (int mo[][colonne], int RIGHE, int DIM);
void visualizza (int ms[][colonne], int RIGHE);
int main()
{
int a[righe][colonne];
crea (a, righe);
visualizza (a, righe);
ordina (a, righe, dim);
visualizza (a, righe);
system("pause");
return 0;
}
void crea (int m[][colonne], int RIGHE)
{
int i, j, val;
srand(time(NULL));
for(i=0; i<RIGHE; i++){
for(j=0; j<colonne; j++){
val = 1 + rand()%20;
m[i][j] = val;
}
}
}
void ordina (int mo[][colonne], int RIGHE, int DIM)
{
int i, j, k=0;
int v[DIM], tmp;
for(i=0; i<RIGHE; i++){
for(j=0; j<colonne; j++){
v[k] = mo[i][j];
k++;
}
}
for(i=0; i<DIM-1; i++){
for(j=i+1; j<DIM; j++){
if(v[i] > v[j]){
tmp = v[i];
v[i] = v[j];
v[j] = tmp;
}
}
}
k = 0;
for(i=0; i<RIGHE; i++){
for(j=0; j<colonne; j++){
mo[i][j] = v[k];
k++;
}
}
}
void visualizza (int ms[][colonne], int RIGHE)
{
int i,j;
printf (" [0] [1] [2] [3]");
printf("\n");
for (i=0; i<RIGHE; i++){
printf("\nMatrice[%d] ",i);
for (j=0; j<colonne; j++){
printf("%-5d", ms[i][j]);
}
printf("\n");
}
}
OUTPUT
[0] [1] [2] [3]
Matrice[0] 13 11 16 4
Matrice[1] 14 3 3 18
Matrice[2] 18 11 17 7
Matrice[3] 4 17 20 14
[0] [1] [2] [3]
Matrice[0] 3 3 4 4
Matrice[1] 7 11 11 13
Matrice[2] 14 14 16 17
Matrice[3] 17 18 18 20
Introduzione ai puntatori
modificaI puntatori sono un particolare tipo di dato presente nel C.
Una variabile di tipo puntatore contiene un indirizzo di memoria nel quale è salvato un particolare tipo di dato.
Quando noi definiamo una variabile:
int n;
a questa variabile al momento dell'esecuzione del programma viene assegnato uno spazio in memoria (4 byte).
Noi per modificare il valore di questa variabile scriviamo semplicemente n = 5...
Un altro modo per modificarne il valore è però quello di passare per il suo indirizzo in memoria.
I puntatori ci permettono appunto di fare questo.
Nel mio esempio la cosa ha poco senso, ma può essere fondamentale quando si usano le funzioni.
I puntatori si possono considerare croce e delizia della programmazione : talvolta possono essere un po' difficili da comprendere, per questa natura di far riferimento ad un dato indirettamente anziché direttamente.Però mano a mano che un programma diventa complesso , il puntatore diventa utilissimo per consentire il risparmio della memoria.
ESEMPIO 1. Ci sono delle situazioni in cui bisogna manipolare uno stesso dato più volte contemporaneamente. Possiamo usare i puntatori per far più volte riferimento allo stesso dato anziché fare tante copie di quel dato e affollare la memoria del computer.
Utilizzo dei puntatori
modificaVediamo ora come si usa questo nuovo tipo di dato.
Un puntatore va dichiarato specificando anche il tipo di dato a cui si riferisce:
int *punt;
In fase di dichiarazione * specifica che si tratta di un puntatore, poi avrà un altro significato.
int indica che si riferisce a un'area di memoria in cui è contenuto un valore int.
Altro comando per usare i puntatori è &.
Questo comando restituisce l'indirizzo in memoria di una variabile.
Ad esempio &n da come risultato l'indirizzo in memoria di n:
punt=&n; /*Assegna al puntatore l'indirizzo in memoria di n*/
Ora sappiamo dove è salvata n in memoria, come facciamo a cambiarne il valore?
Semplice, ci viene in aiuto un altro comando, l'operatore di indirezione * .
*punt equivale a n.(questa operazione si chiama dereferenziazione).
Come n può essere usato sia a destra che a sinistra del segno di uguale.
E se è posta a sinistra l'assegnamento modifica il valore effettivo di n in memoria:
Puntatori e funzioni
modificaPassaggio dei parametri per riferimento
modificaI parametri in C vengono passati alle funzioni per valore. Questo significa che durante l'esecuzione di un programma ogni chiamata ad una funzione dovrà ricevere una copia di tali parametri. Inoltre, ogni volta che la funzione tenterà di modificare un parametro ricevuto, questa avrà effetto solo all'interno della funzione stessa; possiamo dire che avrà effetto sulla copia del parametro e non sull'originale. Esistono ovviamente casi in cui c'è la necessità di dover modificare una variabile passata a una funzione e altri casi in cui la copia di un parametro passato per valore sarebbe troppo dispendioso l'utilizzo in termini di tempo e memoria nel caso di grandi quantità di dati. Il C permette di simulare un passaggio per riferimento alle funzioni attraverso l'uso dei puntatori. La funzione non riceverà più una copia della variabile ma l'indirizzo di tale variabile. In questo modo ogni modifica sul parametro passato avrà effetto anche sulla variabile originale. Consideriamo il classico esempio della funzione swap che scambia due variabili. Nella prima versione i parametri sono passati per valore, nella seconda vengono passati per riferimento.
Swap V 1 - Passaggio per valore
void swap (int, int);
int main(){
int a = 10;
int b = 5;
printf("a=%d\nb=%d\n", a, b);
printf("Swap..\n");
swap (a, b);
printf("a=%d\nb=%d\n", a, b);
return 0;
}
void swap (int x, int y){
int tmp;
tmp = x;
x = y;
y = tmp;
}
OUTPUT
a = 10
b = 5
Swap..
a = 10
b = 5
Swap V 2 - Passaggio per riferimento
void swap (int *, int *);
int main(){
int a = 10;
int b = 5;
printf("a=%d\nb=%d\n", a, b);
printf("Swap..\n");
swap (&a, &b);
printf("a=%d\nb=%d\n", a, b);
return 0;
}
void swap (int *x, int *y){
int tmp;
tmp = *x;
*x = *y;
*y = tmp;
}
OUTPUT
a = 10
b = 5
Swap..
a = 5
b = 10
Array e puntatori
modificaArray e puntatori in C sono strettamente legati. È importante comprendere questa relazione per due ragioni principalmente: avere un'idea di come il C è stato progettato e capire i programmi esistenti. Questo aiuterà a possedere una buona conoscenza di questo linguaggio. Il nome di un array in realtà altro non è che il puntatore al suo primo elemento. Quindi nello scrivere char a[100], verrà definito un array a di 100 caratteri dove a sarà il puntatore al suo primo carattere a[0]. Consideriamo il seguente frammento di codice:
#define DIM 100
.
.
{
.
.
char a[DIM]; //Dichiara un array di char
char *pa; //Dichiara un puntatore a char
pa = a; //pa sta puntando ad a
pa = &a[0]; //pa sta puntando ad a
*pa = 'C'; //inserisce il carattere C in a[0]
.
.
}
Per le considerazioni fatte si noti come scrivere pa = a e pa = &a[0] equivale alla stessa cosa. Entrambe le assegnazioni permettono di far puntare pa all'array a e, più precisamente, al primo elemento dell'array a. Si può quindi accedere ad a attraverso pa. Scrivendo *pa = 'C'; inseriamo il carattere C nel primo elemento dell'array a, a[0] ora contiene C.
Aritmetica dei puntatori
modificaNon è molto interessante osservare che un puntatore punti ad un elemento di un array. È interessante invece capire come, attraverso l'aritmetica dei puntatori (o aritmetica degli indirizzi), si possono elaborare vettori in modo alternativo. I puntatori prendono il posto degli indici di un array. In C le uniche forme di aritmetica dei puntatori sono le seguenti tre:
- Somma intero con puntatore
- Sottrazione intero con puntatore
- Sottrazione puntatore con puntatore
Somma di un intero a un puntatore
modificaLa somma di un intero j a un puntatore p che punti a un elemento del vettore a, sposterà quel puntatore di j posizioni avanti rispetto a dove stava puntando p in precedenza. In altre parole se consideriamo il seguente frammento di codice:
{
char a[10];
char *pa;
int j;
j=4;
pa = &a[3]; //Punta al quarto elemento dell'array a, a[3]
pa = pa + j; //pa si sposta di j posizioni in avanti
}
pa inizialmente sta puntando al quarto elemento dell'array a, pa = &a[3]. L'istruzione pa = pa + j farà in modo che pa punti all'ottavo elemento a[7].
In generale diciamo che se p punta ad a[i], allora p + j punterà ad a[i + j], sempre se a[i+j] esista.
Sottrazione di un intero a un puntatore
modificaAnalogo discorso si fa per quanto riguarda la sottrazione di un intero a un puntatore. Consideriamo ancora il seguente frammento:
{
char a[10];
char *pa;
int j = 6;
pa = &a[7]; //pa punta all'elemento a[7]
pa -= j; //pa si sposta di j posizioni indietro
}
pa inizialmente punta ad a[7]. In questo caso l'istruzione pa -= j farà in modo che pa punti all'elemento a[1] dell'array. In generale, se un puntatore p punta ad un elemento di un array a[i], allora l'operazione p - j farà puntare p ad a[i-j], sempre se a[i-j] esista.
Sottrazione di un puntatore a un puntatore
modificaL'operazione di sottrarre un puntatore ad un altro puntatore restituirà un intero come risultato che rappresenta la distanza tra i due puntatori, cioè mi dice quanti elementi del vettore ci sono tra i due puntatatori. Consideriamo il seguente frammento di codice:
{
char a[10];
int d;
char *pi, *pj;
pi = &a[8]; //pi punta all'elemento a[8]
pj = &a[2]; //pj punta all'elemento a[2]
d = pi - pj; //d conterrà il numero intero 6. Quindi 6 elementi di a sono presenti tra a[2] ed a[8]
}
Attenzione! Eseguire questo tipo di operazioni richiede che i puntatori puntino ad elementi di un array. In particolar modo eseguire una sottrazione tra puntatori richiede che entrambi puntino allo stesso array.
Puntatori a puntatori
modificaIl discorso fatto per la variabile di tipo int vale in generale per qualsiasi tipo di dato, anche per gli stessi puntatori.
La cosa può sembrare strana, ma si possono dichiarare anche puntatori a puntatori.
int **punt;
Il loro utilizzo si basa comunque sulle regole scritte sopra e verrà specificato nel capitolo sull'allocazione della memoria dove dovranno essere usati per alcune funzioni.
Stampare il contenuto di un puntatore
modificaInfine può essere utile poter stampare il valore di una variabile di tipo puntatore.
Il suo contenuto è un indirizzo di memoria in formato esadecimale.
La direttiva da inserire nella printf per stampare un indirizzo è %p:
printf("%p",&n); /* Stampa l'indirizzo in memoria di n */
In alternativa è anche possibile stamparlo nella forma esadecimale con % x:
printf("%x",&n); /* Stampa l'indirizzo in memoria come un numero esadecimale */
Esempio-Stampare la locazione e il valore di una variabile intera
#include<stdio.h>
int main()
{
int var = 26, *p;
p = &var; /*nella variabile puntatore p viene memorizzato l'indirizzo di var (quindi p "punta" a var)*/
printf("Valore = %d\nIndirizzo = %p\n", *p, p); /*Visualizza "Valore = 26 Indirizzo = 40ffbcde1"(Fittizio)*/
return 0;
}
Scrivendo *p, la variabile puntatore p viene dereferenziata, quindi assume il valore effettivo a cui punta (in questo caso il valore di var, che nell'esempio è 26). Essendo un intero viene usata la specifica % d, mentre p contiene l'indirizzo di memoria di var, e quindi viene passata la specifica %p.
Allocazione dinamica della memoria
modificaL'allocazione dinamica della memoria è gestita dalle funzioni seguenti:
void * calloc (size_t n, size_t size)
La funzione calloc alloca memoria per un vettore di n elementi di dimensione size. La memoria è inizializzata a zero.La funzione restituisce il puntatore ad una zona di memoria allocata in caso di successo e NULL in caso di fallimento.
Per esempio:
int * ip; ip = (int *) calloc (n, sizeof (int));
void * malloc (size_t size)
Alloca size byte senza inizializzare la memoria. La funzione restituisce il puntatore ad una zona di memoria allocata in caso di successo e NULL in caso di fallimento.
Per esempio (utilizzando una struct):
struct stud * s; s = (struct stud *) malloc(sizeof(struct stud)); if (*s==NULL) { fprintf(stderr, "Errore nell'allocazione di memoria"); ... } else { ... }
void * realloc (void *ptr, size_t size)
Cambia la dimensione del blocco allocato all'indirizzo *ptr portandola a size. La funzione restituisce il puntatore ad una zona di memoria allocata in caso di successo e NULL in caso di fallimento.
void free(void *ptr)
Libera lo spazio di memoria puntato da * ptr. Se *ptr è NULL non fa niente.
Per esempio:
for (p = head; p!= NULL; p = q) { q = p->next; free(p); }
Le Librerie
modificaUna libreria è un insieme di tools che realizzano specifiche funzioni. Senza librerie il linguaggio c si dimostrerebbe inutile, dato che non possiede funzioni native a differenza di altri linguaggi.
Le librerie standard contengono materialmente queste funzioni, e quindi sono una sorta di "collezione" delle funzioni scritte per un certo sistema operativo. Ma è possibile anche creare nuove librerie che contengano al loro interno determinate funzioni da richiamare nel programma principale o relativi sottoprogrammi.
Per creare delle librerie, per poi andarle ad includerle nel programma principale, occorre dapprima creare un file .c e un file .h, che racchiudono le eventuali operazioni che deve svolgere la nuova libreria, e la chiamata della libreria nel programma.
Esempio:
Creando un file chiamato prova.c si vanno ad includere tutte le librerie che si trovano nel progetto creato.
#include "mialib.h" /* libreria creata*/ #include "mialib2.h" /* libreria creata*/ #include /* libreria standard*/ #include /* libreria standard*/
int main () { /* ... */ }
mialib.c
---------------------
#include "mialib.h"
#include
int calcola(void){
/* ... */
}
/* ... */
---------------------
Creando un file
mialib.h --------------------- #define PARAMETRO 5
int calcola (void);
/* ... */