come funziona void

pippopippoli:
salve a tutti ho scoperto che oltre al void setup e void loop posso creare un void tutto mio tipo void gianluca ,
ma non sono riuscito a capire come farlo partire e chiudere
non ho trovato niente su google come funziona?

Dato il nome deciso da te, dove vuoi eseguirlo, scrivi il nome e poi apri e chiudi la tonda.
Esempio stupido:

const byte pinLed=13;
void setup()
{ delay(1000);
  pinMode(pinLed,OUTPUT);
}

void ledOn()
{ digitalWrite(pinLed,HIGH);
}

void ledOff()
{ digitalWrite(pinLed,LOW);
}

void loop()
{ ledOn();
  delay(1000);
  ledOff();
  delay(1000);
}

Aggiungo a quanto ti hanno già detto: "void" da solo significa "nulla", e messo prima del nome di una funzione indica che tale funzione non restituisce al chiamante nessun valore, vuol dire che vengono chiamate, eseguono il codice che contengono e poi restituiscono il controllo al programma chiamante.

Scusa se mi inserisco, forse e' una domanda stupida, ma poniamo il caso che io voglia farmi delle funzioni tipo quella, ma che devono restituire qualcosa (ad esempio, una funzione che, richiamata solo quando mi serve, mi vada a leggere dei sensori analogici, esempio, NTC o fotocellula o volt, quello che vuoi, e che sia in grado di riportare il valore letto al programma principale sotto forma di variabile) ... quale sarebbe il modo migliore di farlo ? ... intendo, sempre come funzione richiamabile a comando come la void, non come parte del programma che continua a girare ...

Hai vari modi.
Se ti serve un solo parametro dichiari la funzione in base al tipo di parametro da restituire e usi il comando return all'interno della funzione.
Se sono più parametri li puoi passare alla funzione come puntatori e la funzione li manipola poi restituendoli modificati.
Potresti infine usare dei parametri globali singoli o organizzati in una struttura (C) o in una classe (C++).

Etemenanki:
Scusa se mi inserisco, forse e' una domanda stupida, ma poniamo il caso che io voglia farmi delle funzioni tipo quella, ma che devono restituire qualcosa ...

Così :

tipo_da_restituire nome_funzione(tipo_parametri parametri) {
   ....
   ....
   <<implementazione>>
   ....
   ....
   return valore_da_restituire;
}

... o non ho capito la domanda ??? :roll_eyes:

Guglielmo

gpb01: Fondamentalmente mi chiedevo se c'erano funzioni tipo la "void" (che non restituisce nulla), ma gia previste per restituire valori ... oppure se era necessario usare sempre "void", ma trasferendo i valori in un modo diverso, dato che di per se non lo fa (a quanto ho capito)

O in alternativa, se per fare una cosa simile, fosse necessario scriversi una specifica libreria ... cosa molto piu complessa ...

EDIT: PaoloP, potrebbe essere anche un solo parametro, al limite ... ad esempio, se fosse una funzione per leggere una temperatura solo quando serve, il dato sarebbe, alla fine, solo una variabile numerica contenente un singolo valore ...

Etemenanki:
gpb01: Fondamentalmente mi chiedevo se c'erano funzioni tipo la "void" (che non restituisce nulla), ma gia previste per restituire valori ... oppure se era necessario usare sempre "void", ma trasferendo i valori in un modo diverso, dato che di per se non lo fa (a quanto ho capito) ...

Scusa, sarò io che non capisco ... ma quando, ad esempio, usi analogRead() ... quella è una funzione che ti ritorna un valore ... per l'esattezza un int ... quindi ... tu che vuoi sapere esattamente ???

Guglielmo

@Guglielmo, credo @Eteme voglia fare una procedura (funzione con void) a cui passa dei parametri modificabili ma non ritorna nulla.

Si, @Eteme puoi, come detto da @PaoloP

Riassumendo:

  1. se non devi restituire nulla fai una funzione void (che in altri linguaggi si chiamano procedura o sub)
  2. se devi ritornare un solo valore fai una funzione dichiarando davanti al posto del void il tipo del ritorno (e in altri linguaggi le chiamano funzioni)
  3. se devi ritornare più valori, di solito fai una funzione (void oppure no) che manipola i parametri (alcuni o tutti) che passi. Quindi quei parametri saranno dal punto di vista della funzione sia in input che in output. Alcuni parametri però in C vengono passati per valore (sono una copia del valore originale) perciò bisogna usare i puntatori per permettere alla funzione di cambiare i valori della variabile passata.

Se esiste il modo di scriversi una funzione "complessa", diversa da quelle gia esistenti, in grado di eseguire piu comandi, e trasferire anche valori, all'occorrenza ...

Ad esempio, il tuo riferimento all'analogread, quella e' una funzione gia esistente, che legge un valore analogico da un'ingresso (se non sbaglio), e lo mette in una variabile, ma fondamentalmente fa solo quello ... poniamo invece, ad esempio, il caso che tu debba eseguire anche altre funzioni connesse alla tua lettura, funzioni per le quali non hai un comando gia pronto ... se ho capito bene il discorso della "void", questa ti permette di crearti delle funzioni (chiamiamole "pacchetti di comandi", se vuoi), e di richiamarle quando ti servono, ma non restituisce nulla al programma ...

Se invece io volessi, sempre come esempio, eseguire diverse funzioni, il cui risultato finale e' una lettura analogica, e poi avere questo risultato trasferito indietro al corpo principale del programma, ma senza doverle riscrivere ogni volta che servono, e senza che rimangano sempre in esecuzione, il modo piu semplice che mi viene in mente e' di metterle in una "funzione" (o insieme di funzioni), e richiamarla solo nei momenti in cui mi serve ... in questo caso, per restituire il valore ad una variabile nel corpo principale una volta che la "funzione" e' terminata, il sistema migliore quale sarebbe ? ... creare una funzione specifica, o usare un "return" che spedisca il valore ad una variabile "esterna" alla mia "funzione" ?

Faccio un'esempio stupido, metti che debba campionare una serie di temperature dopo aver spostato una sonda ed atteso un tempo di stabilizzazione, ma che il tutto lo debba fare solo quando premi un pulsante (era una cosa che avevo fatto secoli fa in basic, e' il primo esempio che mi e' venuto in mente :P) ... dovrei fare qualcosa di simile:

un "corpo" principale
....
....
.... (istruzioni)
quando il pulsante 1 e' premuto, richiama "funzione1"
elabora "x"
....
....
.... (altre istruzioni)
ecc

ed una sottofunzione tipo:

"funzione1"
sposta la sonda
attendi 10 secondi
(loop) leggi 10 valori e mettili in un'array
fai la media del contenuto dell'array
"x"=risultato
ritira la sonda
torna a principale (ovviamente nel punto da cui e' stata lanciata "funzione1")
fine "funzione1"

che venga richiamata dal corpo principale solo quando serve (potrebbero anche essere piu funzioni differenti, per eventi diversi) ... e cosi via ... (scusa il linguaggio "da profano" ... lo so che magari a voi la domanda sembra stupida, come quando chiedono a me che differenza c'e' fra tensione e corrente :stuck_out_tongue: ... ma sto ancora cercando di impararla, la programmazione delle MCU) ... a livello di programmazione Arduino, o comunque MCU Atmel, e' una cosa possibile, o non e' supportata e bisogna usare una struttura completamente differente ?

Etem,
come fare te l'ho fatto vedere 3 post sopra, inoltre ...
... non hai limiti al numero di funzioni che puoi creare e le chiami solo quando ti servono.

Ogni funzione, può o non può tornare un valore. Se NON torna alcun valore, per farlo capire al compilatore, si usa la parolina magica "void" che, appunto, indica che il compilatore NON deve aspettarsi indietro nessun valore dalla funzione. Se la funzione ritorna un valore, allora devi dire quale è il tipo di valore che ritorna : byte, char, int, long int. .... e così via.

La funzione è quindi un blocchetto che fa sempre una determinata cosa ... la analogRead() legge il valore del convertitore ADC e ti ritorna questo valore in un int che tu assegni ad una variabile, quindi ... nel tuo programma, quando ti serve di leggerlo farai :

mio_valore = analogRead(Ax);

il controllo passerà temporaneamente alla funzione che, fatta la lettura, restituirà il valore che verrà assegnato alla tua variabile.

Allo stesso identico modo tu ti crei la TUA funzione che, nel tuo esempio, campiona la temperatura e ti ritorna il risultato ...
... quando ti serve la chiami, quella va in esecuzione, ritorna indietro il valore e tu assegni questo valore ad una tua variabile e prosegui con il tuo programma. Se ti riserve, la richiami di nuovo e così via ...

Più chiaro adesso ?

Guglielmo

@Eteme non capisco bene.
Quello che fa la tua funzione dipende da te e può essere complessa quanto vuoi. Può richiamare a sua volta altre funzioni da te create.
L'istruzione return si usa solo nel caso di funzione non void che deve ritornare un dato del tipo dichiarato.

Scusa @Guglielmo se mi sovrappongo :slight_smile:

Per incasinare il discorso ]:smiley: diciamo pure che il C++ permette l’overloading delle funzioni.
Ovvero puoi avere più funzioni con lo stesso nome ma parametri in ingresso diversi e il compilatore sceglie quella che corrisponde ai parametri.
Il tipo di funzione (int, float, ecc), il parametro restituito e il codice interno può essere diverso da funzione a funzione.

gpb01:
Se la funzione ritorna un valore, allora devi dire quale è il tipo di valore che ritorna : byte, char, int, long int. .... e così via.

Questo lo devo dichiarare nel "setup", solo all'inizio, se non ho capito male :stuck_out_tongue:

... mio_valore = analogRead(Ax);

il controllo passerà temporaneamente alla funzione che, fatta la lettura, restituirà il valore che verrà assegnato alla tua variabile.
...

Più chiaro adesso ?

Un po piu chiaro, si (abbi pazienza, io sono cresciuto a gwbasic e basta :P) ... era la struttura della chiamata che mi fregava nel ragionamento ... quindi, secondo il tuo esempio, la chiamata della funzione (analogread) e' l'azione stessa di assegnarla come valore alla variabile ... io invece ragionavo (sbagliando) con la sequenza "chiama funzione > esegui funzione > funzione assegna valore a variabile > ritorna > usa variabile", (e li mi incasinavo :P) ... mentre il tuo esempio "mio_valore = analogRead(Ax);", chiama la funzione semplicemente assegnando il valore della funzione alla variabile ... inizia a calare la nebbia :wink:

Solo un dubbio, questo tipo di struttura di richiamo comporta sempre che la funzione restituisce il controllo al punto in cui e' stata chiamata, giusto ? ... se invece dovesse condizionare il punto di rientro al valore restituito, dovrei farlo dopo il rientro usando degli if o dei case, non potrei farlo dall'interno della funzione stessa ...

L'istruzione return si usa solo nel caso di funzione non void che deve ritornare un dato del tipo dichiarato.

Questa devo studiarmela con calma ...

Per incasinare il discorso ]:slight_smile: diciamo pure che il C++ permette l’overloading delle funzioni.
Ovvero puoi avere più funzioni con lo stesso nome ma parametri in ingresso diversi e il compilatore sceglie quella che corrisponde ai parametri.
Il tipo di funzione (int, float, ecc), il parametro restituito e il codice interno può essere diverso da funzione a funzione.

Grazie, mi mancava un'altro po di confusione ... :stuck_out_tongue: XD

Etemenanki:
Questo lo devo dichiarare nel "setup", solo all'inizio, se non ho capito male :stuck_out_tongue:

No, la dovresti dichiarare subito dopo gli #include e i #define come prototipo di funzione ma ci pensa l'IDE di Arduino a farlo, quindi sei esonerato da questo compito.

Etemenanki:
....
Questo lo devo dichiarare nel "setup", solo all'inizio, se non ho capito male :stuck_out_tongue:

Il guaio è che usi l'IDE che, per semplificare la vita a chi inizia, nasconde tutta una parte ... ma va bene ...

Allora, normalmente in C si dovrebbe sempre dichiarare in testa al programma (più o meno dove dichiari le costanti, le variabili globali, ecc) le funzioni che usi, descrivendo i parametri che esse vogliono e il valore che esse ritornano, cioè ... occorrerebbero i "prototipi delle funzioni" es. :

int analogRead(int);

questo è il prototipo, poi, da qualche parte c'è l'implementazione della funzione :

int analogRead(int pinNumber) {
   ....
   ....
   return il_valore
}

tutto questo l'IDE te lo nasconde e, quando dichiari una TUA funzione ... di nascosto il prototipo lo fa lui. Se usi Atmel Studio invece ... o altri ambienti di sviluppo, i prototipi li devi mettere tu.

Etemenanki:
Un po piu chiaro, si (abbi pazienza, io sono cresciuto a gwbasic e basta :P) ... era la struttura della chiamata che mi fregava nel ragionamento ... quindi, secondo il tuo esempio, la chiamata della funzione (analogread) e' l'azione stessa di assegnarla come valore alla variabile ...

Allora .. nessuno ti vieta di chiamare la funzione direttamente

analogRead(pin);

... solo che così ... ti perdi il valore di ritorno, visto che il compilatore non sa a chi dare l'int di ritorno e quindi lo butta via XD

Se la funzione torna un valore, magari quel valore ti serve (o magari no) ... e quindi tu dici al compilatore di prendere il valore di ritorno ed assegnarlo ad una variabile.

Etemenanki:
Solo un dubbio, questo tipo di struttura di richiamo comporta sempre che la funzione restituisce il controllo al punto in cui e' stata chiamata, giusto ? ... se invece dovesse condizionare il punto di rientro al valore restituito, dovrei farlo dopo il rientro usando degli if o dei case, non potrei farlo dall'interno della funzione stessa ...

Si, in pratica, con la solita regola da destra a sinistra, l'espressione viene valutata ... e dato che incontra l'assegnazione ad una variabile di un qualche cosa che non conosce ... prima chiama la funzione (in pratica si salva il punto in cui sta e salta al punto d'ingresso della funzione) e, quando la funzione è terminata torna dove aveva lasciato assegnando il valore che ora ha :wink:

Etemenanki:
Questa devo studiarmela con calma ...

Immagina void come un "tipo vuoto, inesistente" ... se la funzione deve solo fare delle cose ma non ti deve ritornare nulla (es. pulisci LCD ... deve solo pulire la memoria del LCD ma non ti deve tornare nulla) allora la dichiari di tipo void ovvero da cui non ti aspetti nulla in ritorno e che non contiene il ritorno di un valore (l'istruzione return la può contenere, non deve contenere return valore). Negli altri casi, in cui ti aspetti un valore di ritorno, la dichiarerai del tipo del valore che ti aspetti ti ritorna (byte, char, int ....)

Etemenanki:
Grazie, mi mancava un'altro po di confusione ... :stuck_out_tongue: XD

Al momento NON stare a sentire chi ti parla di C++ ... ]:smiley:
... già hai da lavorare parecchio sulle basi ... poi per il C++ c'è sempre tempo :wink:

Guglielmo

Etemenanki:

L'istruzione return si usa solo nel caso di funzione non void che deve ritornare un dato del tipo dichiarato.

Questa devo studiarmela con calma ...

{ ... da qualche parte nel loop
hai dichiarato qui o all'inizio
int x=1; int y=2; int z;
fai
z = somma(x,y);
}

dove sotto costruisci

int somma (int a, int b) {int c = a+b; return c}

una cosa di queste insomma...

Esempio concreto:

void flashLed(int flash, byte ledPin) {
  for (int i = 0; i < flash; i++) {
    digitalWrite(ledPin, 1);
    delay(50);
    digitalWrite(ledPin, 0);
    delay(50);
  }
}

Hai una funzione che accetta dei parametri, fa "qualcosa" e non ti restituisce nulla indietro. Semplice e pulita.
Mettiamo invece ora che tu debba sapere dopo l'attivazione di un relé che accende un dispositivo, la lettura di una tensione che ti indicherà un qualcosa.

boolean accendiDevice() {
  digitalWrite(pin, HIGH);
  delay(500);
  if (analogRead(sensorPin) < 250) { //errore
    return false;
  } else { //tutto OK
    return true;
  }
}

Questa funzione "fa qualcosa" ma non solo, ti dice anche com'è andata l'operazione.
Se tu ad esempio nel codice scrivi qualcosa del tipo:

if (accendiDevice() == FALSE) {
    Serial.println("ERRORE");
}

La "risposta" della funzione diventa l'elemento di test dell'if.

In realtà, per non confondere le idee a Etem, occorre dire che anche nell'ultimo caso spiegato da Leo c'è un valore di ritorno che viene comunque usato ...

if (accendiDevice() == FALSE) {
    Serial.println("ERRORE");
}

... il compilatore esamina da destra a sinistra quell'IF ... FALSE è una costante e quindi ha un valore, poi c'è l'operatore e poi c'è la funzione ... il compilatore non sa il valore della funzione quindi genera un codice che, arrivato a quel punto salta nella funzione, la esegue, prende il valore di ritorno e, tornato indietro, quel valore viene usato come se li ci fosse una variabile che ha assunto quel valore ... quindi può fare il confronto ed agire di conseguenza :wink:

Guglielmo

Sì ma difatti l'esempio era proprio per descrivere l'uso di un valore di ritorno.

Se Etem vuol vedere l'uso di un valore di ritorno non usato prende la classe Print, nel file vedrà che ci sono return a tutte le funzioni. Cioè, quando io faccio Serial.print in realtà la funzione mi restituisce qualcosa, solo che io non la uso e faccio cadere quel valore.

No, non ci credo Etem che non sa programmare.
Pronto, pronto intervento? si dica.
Emet non sa programmare.
Provvediamo subito, una sacca...no meglio due di programmina, 200 cc di C. :grin:

Ok, niente di preoccupante, anche io sono passato per gwbasic, quickbasic e basic-7.1.

Il tipo void è necessario perché nelle vecchie versioni di C una funzione di base ritornava sempre un intero, e credo che ancora adesso sia così. Quindi void si usa per dire che la funzione che di default ritorna un intero non deve ritornare nulla. Il C ANSI, richiede anche "void" nella lista di argomenti di funzione quando questa è vuota.

void myFunc(void); // protoype

void myFunc(void) 
{
     // Questo è un blocco di codice legato ad un nome di funzione richiamabile.
}
int yourFunc(void)
{
      // il puntatore PC e qui, non puoi decidere in anticipo cosa accadrà quando la funzione termina.
      // anche perché qui siamo in contesto locale dove le variabili create qui dentro vengono distrutte
      // al termine della funzione.
      int i = 0;
      return i;  // ritorna i, al chiamante e poi la variabile locale i viene distrutta
}

Occhio che viene creata una copia della variabile i e ritornata al chiamante, la copia rimane nello stack del contesto chiamante e viene distrutta nel contesto della funzione.

Immagina cosa accade se al posto di restituire un tipo "int", restituissi un tipo utente, come ad esempio
un oggetto String. Ci vuole più tempo a creare una copia di un oggetto "String" che un oggetto di tipo "int".
Un ogetto String è instanza oggetto di classe String. Nel caso di "int", un oggetto int è instanza oggetto di tipo predefinito fornito dal linguaggio di programmazione. Questa spiegazione è più orientata alla programmazione
ad oggetti e prima che venisse creato il C++ i programmatori C avrebbero detto che myInt è una variabile di tipo int.

Anche gli argomenti (anche detti parametri) di una funzione sono locali al contesto della funzione, perché di default il passaggio di parametri viene effettuato per valore, per cui viene creata una copia della variabile usata come parametro che ha il valore che nella chiamata hai specificato.

void myFunc(int x) 
{
     // viene creata una variabile "x" locale a cui gli viene assegnato il valore che nella chiamata seguente 
     // vale 5.
     x += 1;
}

// chiamata
myFunc(5);

Questo porta a dire che una variabile ha una lifetime, cioè un periodo di vita più o meno lungo.
Le variabili globali hanno un periodo di vita lungo quanto è lunga la vita del programma.
Le variabili locali hanno un periodo di vita più breve rispetto alle variabili globali. Il periodo di vita è determinato
dal blocco {}. Un code block è una entità che può esistere anche non legata ad una istruzione C o ad una funzione.

void myFunc() 
{
      int i = 0;
      {     //  questo è un code block non legato ad alcuna funzione o istruzione
            int h = 1;
      }
     // int h qui non è più visibile perché non esiste più e posso dichiararla un altra volta.
     int h = 5;
}

Ciao.