[Impariamo il C] Lezione 5

Buongiorno.

Prosegue il corso collaborativo di programmazione.

Riassunto delle puntate precedenti:


  1. installazione dell'IDE, cenni di architettura degli elaboratori

  2. sintassi del C, programmazione strutturata
  3. ancora sulla sintassi, i tipi di dato, le variabili
  4. variabili parte seconda: gli array

In questo capitolo:

  • le funzioni
  • un nuovo sguardo sul main


Le funzioni

Ripartiamo ancora una volta dal nostro hello world.


void main()
{
printf("Hello World!\n");
system("pause");
}
Ormai conosciamo quasi tutto il significato di questo codice: sia come viene dichiarato il main (e il suo scopo), le stringhe, i segni di punteggiatura, le graffe, ecc.

Rimane un solo argomento non trattato: le funzioni.

printf e system (ma anche il main stesso) sono delle funzioni. Vi ricordate cosa sono le funzioni in matematica? Formalmente sono delle regole, delle leggi, delle indicazioni per mettere in relazione elementi di un insieme con quelli di un altro.



In questo esempio, la funzione f mette in relazione 1 con A, 2 con C e 3 con D.

Le funzioni possono essere arbitrarie come in questo caso, oppure avere comportamenti più regolari (come quelle che siamo abituati a trattare). Per fare un esempio, potremmo avere una funzione chiamata successore tale che, preso un qualsiasi numero intero in ingresso, lo mette in relazione con il suo successore:



Come implementare una funzione del genere in C? Una prima strada, ingenua, potrebbe essere quella di fare un programma di questo tipo (pseudocodice):


main {

se ( dato_in_ingresso è 1 ) allora successore = 2
se ( dato_in_ingresso è 2 ) allora successore = 3
se ( dato_in_ingresso è 3 ) allora successore = 4
...

}
Salta subito agli occhi un problemino di fondo di questo approccio: enumerare tutti i casi è improponibile!

In informatica si utilizza un altro approccio, di natura induttiva: si progetta un algoritmo risolvente che funzioni per qualsiasi input e ritorni un output coerente. Il modo più elegante per realizzare questo è creare una funzione (pseudocodice):


crea nuova funzione "successore"
input X: numero intero
output Y: numero intero
{
Y = X + 1
ritorna Y
}
Abbiamo già anticipato nelle scorse lezioni che la sintassi del C traduce questo pseudocodice in maniera leggermente diversa. Ecco come:


int successore( int X ) {
int Y;
Y = X+1;
return Y;
}
Poiché la funzione ritorna un valore (di un qualsiasi tipo di dato che abbiamo visto nelle due lezioni precedenti, anche void) il compilatore vuole che noi gli si riservi un blocco di memoria. Ecco dunque che la funzione inizia dichiarando il tipo del valore di ritorno.

La seconda cosa da dichiarare è il nome delle funzione.

La terza sono i parametri in ingresso: quali dati utilizza la mia funzione per elaborare? In questo caso, sappiamo che vogliamo il successore di un numero intero, quindi dichiareremo la funzione con un solo parametro intero. I parametri si mettono tra parentesi tonde e possono essere in numero arbitrario. Si osservi che non abbiamo scritto


int successore( int ) {
...
ma abbiamo specificato anche il nome di una variabile


int successore( int X ) {
...
Perché? perché noi vogliamo poter utilizzare un qualsiasi valore arbitrario passato alla funzione, senza conoscerlo in anticipo. Dunque, nelle parentesi, avvengono delle vere e proprie dichiarazioni di variabili.

Attenzione ora. Le variabili dichiarate all'interno delle funzioni sono utilizzabili solo dentro il corpo delle funzioni: il main (e tutte le altre funzioni) non possono utilizzarle. Questa "visibilità" delle variabili è detta scopo e va immaginata come delle vere e proprie strutture a matrioska: una variabile dichiarata dentro una matrioska più piccola non è visibile da quella più grande. Vale invece il viceversa: se dichiaro una variabile prima della funzione, la potrò utilizzare.


Abbiamo già anticipato che le funzioni ritornano un valore. Ciò avviene con l'istruzione return, che si occupa anche di interrompere l'esecuzione della funzione e restituire il controllo al chiamante (ad esempio il main).


int successore( int X ) {
int Y;
Y = X+1;
return Y;
}
Dunque, traduciamo in linguaggio naturale questo codice C:


  • dichiariamo una funzione che ritorna un numero intero chiamata successore
  • questa funzione accetta un (e un solo) parametro in ingresso, di tipo intero che, alla chiamata di funzione, verrà inserito nella variabile X
  • dichiariamo un'altra variabile Y
  • mettiamo in Y il valore X+1
  • ritorniamo Y e chiudiamo l'esecuzione della funzione


Riprendendo in mano le funzioni viste nell'hello world, ora intuiamo che printf e system accettano come parametri in ingresso delle stringhe. In realtà, la printf è più potente di così: accetta anche altri parametri, che consentono di formattare l'output a video. Inoltre, entrambe le funzioni ritornano dei valori. Torneremo su di esse in seguito.


Un nuovo sguardo sul main

Chiaramente, in funzioni dichiarate con void non dobbiamo restituire nulla; questo è il caso del nostro main dell'hello world. Tuttavia, la sintassi corretta del main è questa:


int main( int argc, char* argv[] ) {
...
}
Vediamo che il main deve restituire un intero (ormai lo sapevate già ) e riceve due parametri in ingresso. Di questi parametri parleremo più avanti (perché ancora non sapete quell'asterisco cosa significa), ma se avete mai utilizzato una console intuite già che si riferiscono ai parametri che si possono passare ai programmi.

Ora vediamo come scrivere un programma che, letto un numero dall'utente, ne stampa il successore.


int successore( int X ) {
return X+1;
}

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

/* dichiaro le variabili che userò */
int valore;
int ritorno;

/* leggo input */
printf( "Inserire un numero intero: " );
scanf ( "%d" , &valore );

/* stampo output */
ritorno = successore( valore );
printf( "Il suo successore è: " );
printf( "%d", ritorno );

/* fine: inseriamo una pausa e poi ritorniamo zero (=tutto okay) */
system( "pause" );
return 0;
}
Seguono quattro osservazioni.

Osservazione 1: la funzione deve essere dichiarata PRIMA del corpo principale, sennò il main non sa di cosa stiamo parlando ("successore??! che è??") e il compilatore ci da errore.

Osservazione 2: nella funzione successore non ho dichiarato una variabile Y di appoggio. In effetti è inutile, possiamo ritornare direttamente il risultato se la funzione è semplice: risparmiamo memoria.

Osservazione 3: nel main ho inserito dei commenti tramite /* ... */ . E' buona norma commentare sempre il proprio codice, perché qualunque sviluppatore può confermarvi quanto sia difficile rimettere mano al proprio codice (o, peggio, a quello altrui) se non è stato adeguatamente commentato. Altre buone regole sono l'indentazione e l'uso di nomi di variabili significativi (e non cose tipo pippo = pluto + 1).

Osservazione 4: come vedete, sia scanf che printf hanno richiesto l'uso di %d, mentre nell'esempio precedente usavamo %s. Sarà spiegato in futuro il motivo, ma possiamo anticipare che è legato al fatto che prima manipolavamo stringhe e ora dei numeri (decimals); entrambe le funzioni, per lavorare, han bisogno di sapere su che tipi di dato stanno agendo.




L'esercitatore Kaehell vi fornirà ora dei succosi esercizi per testare la vostra comprensione della prima tranche dell'argomento funzioni.
Wow veramente chiaro e puntuale, l'inizio può spaventare ma dal primo esempio in poi scorre tutto liscio^^
Ti volevo chiedere: ma quindi le funzioni vanno scritte prima del main? Perché in java mi sembra si possa fare:

funzione1(){
bla bla;
}

main(){
funzione1();
funzione2();
}

funzione2(){
bau bau;
}
sì, in C vanno scritte prima. come vedremo verso la fine del corso, puoi aggirare il problema tramite l'uso dei prototipi, che iniziano a dichiarare le funzioni prima di specificarne il corpo:


/* prototipo */
void funzione2();

main() {
funzione2();
}

void funzione2() {
bau bau;
}
Ah ecco grazie, li avevo già visti ma non ne avevo compreso l'utilità
La lezione è stata caricata sul wiki
mi iscrivo, anche in object pascal ci sono i metodi "virtuali", non li avevo ben collegati a C. Pascal e C sembran simili (anche se preferisco pascal come primo amore), chissà se gli * (puntatori giusto?) si comportano come i ^ in Pascal.
non so in object pascal cosa siano i metodi virtuali; in C non ci sono perché... non esistono i metodi (niente classi).

In C++ esistono i metodi virtuali e hanno a che fare coi problemi di ereditarietà, o nel caso delle funzioni "virtuali pure" con le classi astratte; non credo ne parleremo mai su questi schermi

edit: comunque Pascal e C sono strettissimi parenti, essendo entrambi linguaggi imperativi.
vedo un'ecatombe di studenti o sbaglio? 6 reply
scusate ma sono infognato nella programmazione e ho poco tempo per seguire. Sono già finiti gli studenti?


ho la stessa sensazione.

pensavo succedesse dopo, tipo alla prima malloc
Potrebbe aiutare una lista dei maggiori utilizzi del C...perché sinceramente sto iniziando a chiedermi anch'io in che ambiti lo potrei usare...app per linux? app per windows? driver? boh
eiei, io ci sarò sempre! tiè!
solo che la settimana scorsa ero in ferie e più tempo per dibattere qui...

anzi, per ora mi sembra tutto abbastanza chiaro....ora non ho voglia di mettermi a sperimentare a caso...
aspetto l'esercizio per casa e poi si vedrà
@Kaehell, se sei in sbattimento mi invento un esercizio io per questa lezione. La calcolatrice la fai al prox giro okay?

@uizz. in realtà non stiamo guardando il C al fine di usarlo sul serio. applicazioni utili le hai già dette: app per qualsiasi sistema operativo (il codice che vedremo sarà interamente cross platform e portabile), driver, piccole applicazioni di servizio.

ad esempio, una volta mi ero programmato un client di posta per ravanare nella posta alla ricerca di messaggi di un certo tipo e, nel caso ci fossero, far effettuare certe azioni al computer (leggi mandarmi sms ).

il punto è che una volta che consoci il C, poi passare a PHP/Javascript è un attimo. inoltre, verrebbe naturale alla fine del corso sfociare a C++ o Java. insomma, è un punto di partenza, non di arrivo.
Piccolo OT:
un esempio o una semplice infarinatura sui driver mi piacerebbe assai!
e finito il corso passare a un'infarinatura generale o anche qualcosa di più avanzato su Java (specie per poi orientarmi alle applicazioni su cellulari...) sarebbe altrettanto bello per me.
propongo un esercizio per gli aficionados.

scrivere un programma che legge dall'utente due numeri (x1 e x2), li passa ad una funzione chiamata somma_dei_prodotti_piu_uno che implementa questa funzione:

y = x1*x1+x2*x2 + 1

Il valore di ritorno dev'essere stampato a video.


Provare questo codice con
x1=16777216
x2=16777216
e provare a spiegare il risultato.
due domande: in cosa il C e differente al C++ nell'inizializzazione/definizione di variabili ? mi riferisco a posizione nel programma ed eventuali vincoli

se non sbaglio il const non c'è in C, come si ovvia a questa mancanza ?
sia per l'uso comune delle variabili che per il passaggio di una variabile che non deve essere modificata a una funzione come quella di stampa a video
Il const c'e' in C e la sua funzione dipende alla posizione nel codice. Dopotutto il codice C è compatibile al 100% con il C++.
Per le differenze, ce ne sono un bel po' e per questo ti rimando a questo link (anche se è un po' di parte )
Nel frattempo mi sto attrezzando per quel che vi ho detto in pm
Ecco bravo che ssh non va


allora, la questione const (sopratutto nelle funzioni/metodi) è un gran bordello in C++. Il punto è (lo ammetto ) che non sono sicuro esista roba equivalente in C.

Ora indago


edit: apparentemente no, in C non c'è praticamente quasi nulla dell'armamentario del C++ per la protezione dei dati in ingresso e uscita dalle funzioni. const lo usi giusto per definire una variabile non modificabile, piuttosto che un #define che non viene analizzato dal preprocessore.