Le funzioni sono il modo in cui in C++ (come in quasi ogni linguaggio) si organizza il codice: un blocco con un nome, che può ricevere dei dati, fare qualcosa e restituire un risultato. Servono a non ripetere le stesse righe cento volte e a tenere il programma leggibile. In questa puntata vediamo come si dichiarano e si usano, e soprattutto i tre modi di passare i dati: per valore, per puntatore e per riferimento. Alla fine spezziamo il programma in più file e lo compiliamo con un Makefile.
Una funzione somma
Partiamo da un programma che somma due numeri. Dichiariamo due variabili intere, a e b, e chiamiamo la funzione somma, che restituisce un intero. Prima del main scriviamo il prototipo della funzione: dice al compilatore che esiste una funzione chiamata somma, che prende due int e restituisce un int. La definizione vera e propria, con il corpo, può stare anche dopo il main:
#include <iostream>
int somma(int a, int b);
int main() {
int a = 5;
int b = 10;
int risultato = somma(a, b);
std::cout << "Il risultato e' " << risultato << std::endl;
return 0;
}
int somma(int a, int b) {
return a + b;
}
Compilando e lanciando otteniamo:
Il risultato e' 15
Questo è il passaggio per valore: alla funzione arrivano delle copie di a e b. Se dentro somma le modificassimo, le variabili originali del main resterebbero identiche. Il risultato torna indietro grazie al return.
Passaggio per puntatore
A volte vogliamo che la funzione modifichi direttamente una variabile nostra, oppure che restituisca più di un valore. Un modo è passare l’indirizzo di memoria della variabile, cioè un puntatore. Nel prototipo il parametro diventa int *s (un puntatore a intero), nella chiamata passiamo l’indirizzo con &s, e dentro la funzione scriviamo *s per accedere al valore puntato. La funzione non restituisce più niente, quindi è void:
#include <iostream>
void somma(int *s, int a, int b);
int main() {
int a = 5;
int b = 10;
int s = 0;
somma(&s, a, b);
std::cout << "Il risultato e' " << s << std::endl;
return 0;
}
void somma(int *s, int a, int b) {
*s = a + b;
}
L’output è lo stesso:
Il risultato e' 15
ma questa volta è la funzione a scrivere direttamente dentro la variabile s del main.
Passaggio per riferimento
Il terzo modo è il riferimento, una particolarità del C++ che il C non ha. Nel prototipo si scrive int &s: la funzione riceve un alias della variabile originale, non una copia. Il vantaggio è che la sintassi si semplifica parecchio: nella chiamata passiamo s così com’è, e nel corpo la usiamo come una variabile normale, senza asterischi e senza e commerciali da ricordare.
#include <iostream>
void somma(int &s, int a, int b);
int main() {
int a = 5;
int b = 10;
int s = 0;
somma(s, a, b);
std::cout << "Il risultato e' " << s << std::endl;
return 0;
}
void somma(int &s, int a, int b) {
s = a + b;
}
L’output, ancora una volta:
Il risultato e' 15
In sintesi: per valore quando basta restituire un risultato, per riferimento (o per puntatore) quando la funzione deve modificare le variabili del chiamante.
Dividere il programma in più file
Man mano che il programma cresce, tenere tutto in un file solo diventa scomodo. La pratica normale è mettere le dichiarazioni in un file di intestazione (.h) e le definizioni in un file .cpp. Organizziamo il progetto con tre cartelle: src per i sorgenti, obj per i file oggetto e bin per l’eseguibile.
In src/matematica.h mettiamo il prototipo, protetto dalle guardie di inclusione (ifndef, define, endif) che evitano di includere lo stesso file due volte:
#ifndef MATEMATICA_H
#define MATEMATICA_H
int somma(int a, int b);
#endif
In src/matematica.cpp mettiamo la definizione, includendo il nostro header fra virgolette, perché è un file locale e non di sistema:
#include "matematica.h"
int somma(int a, int b) {
return a + b;
}
E in src/main.cpp il programma principale, che ora si limita a usare la funzione:
#include <iostream>
#include "matematica.h"
int main() {
int a = 5;
int b = 10;
int risultato = somma(a, b);
std::cout << "Il risultato e' " << risultato << std::endl;
return 0;
}
Il makefile per più file
Con più file sorgente il Makefile dà il meglio. Le novità rispetto a quelli che abbiamo visto sono tre. Prima di tutto le tre variabili con i nomi delle cartelle. Poi la funzione wildcard, che raccoglie da sola tutti i .cpp della cartella src, così se aggiungete un file non dovete toccare il Makefile. Infine patsubst, che trasforma l’elenco dei sorgenti nell’elenco dei file oggetto corrispondenti, cambiando cartella ed estensione. La regola con il percento descrive come costruire un qualunque .o a partire dal suo .cpp: $< è il primo prerequisito e $@ è il target. Ricordate che le righe dei comandi cominciano con un TAB.
# cartelle
BINDIR = bin
OBJDIR = obj
SRCDIR = src
# nome dell'eseguibile
PROG = main
# tutti i sorgenti e i relativi file oggetto
SOURCES = $(wildcard $(SRCDIR)/*.cpp)
OBJECTS = $(patsubst $(SRCDIR)/%.cpp,$(OBJDIR)/%.o,$(SOURCES))
# flag e compilatore
CFLAGS = -g
CC = g++
all: $(BINDIR)/$(PROG)
$(BINDIR)/$(PROG): $(OBJECTS)
$(CC) $(CFLAGS) $(OBJECTS) -o $(BINDIR)/$(PROG)
$(OBJDIR)/%.o: $(SRCDIR)/%.cpp
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f $(OBJDIR)/*.o $(BINDIR)/$(PROG)
Creiamo le cartelle bin e obj, lanciamo make e poi il programma:
mkdir -p bin obj
make
./bin/main
Ecco l’output:
Il risultato e' 15
Con make clean si ripulisce tutto. La comodità è che make ricompila solo i file cambiati: se toccate un solo .cpp, gli altri non vengono ricostruiti.
Per concludere
Abbiamo visto prototipi, i tre passaggi di parametri e un progetto diviso in più file con il suo Makefile. Nella prossima puntata arriveremo a fare un progetto vero, con una struttura a più livelli.
Link e risorse
- il video su YouTube
- esempi e comandi della puntata su GitHub
- Funzioni, cppreference — dichiarazione e chiamata
- Riferimenti, cppreference — il passaggio per riferimento
- Funzione (informatica) — Wikipedia in italiano
- g++, opzioni di invocazione — la doc del compilatore
Sostieni il canale
PER SOSTENERE IL CANALE BASTANO UN LIKE E UN’ISCRIZIONE!
ma se vuoi offrirmi un caffè, mi aiuterà a rimanere sveglio mentre registro i video 😁☕
clicca qui per scoprire come https://buymeacoffee.com/thelinuxnerd
questi link sono affiliati Amazon, acquistando i libri che ti consiglio sostieni il canale!
