Go Down

Topic: come funziona void (Read 15104 times) previous topic - next topic

PaoloP


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


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.

gpb01

#16
Dec 20, 2013, 06:17 pm Last Edit: Dec 20, 2013, 06:21 pm by gpb01 Reason: 1

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


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. :

Code: [Select]
int analogRead(int);

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

Code: [Select]
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.



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

Code: [Select]
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.



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 ;)



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 ....)



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


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

Guglielmo
Search is Your friend ... or I am Your enemy !

paulus1969





Quote
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...

leo72

Esempio concreto:
Code: [Select]
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.
Code: [Select]

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:
Code: [Select]

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


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

gpb01

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 ...

Code: [Select]
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 ;)

Guglielmo

Search is Your friend ... or I am Your enemy !

leo72

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.

Maurotec

#21
Dec 20, 2013, 10:53 pm Last Edit: Dec 20, 2013, 11:00 pm by MauroTec Reason: 1
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.  :smiley-mr-green:

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.
Code: [Select]

void myFunc(void); // protoype

void myFunc(void)
{
    // Questo è un blocco di codice legato ad un nome di funzione richiamabile.
}


Code: [Select]
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.

Code: [Select]
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.

Code: [Select]
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.

Etemenanki


No, non ci credo Etem che non sa programmare.
...


Credici :P ... gli unici "linguaggi" che avevo imparato un po, erano il gwbasic e l'html, il primo per divertimento ed il secondo per scrivere qualche pagina web (all'epoca usavo il notepad, i programmi per composizione costavano troppo :P) ... poi vuoi per lavoro vuoi per mancanza di tempo, la programmazione non l'ho mai veramente approfondita ... ed ora che mi piacerebbe smanettare un po con qualche MCU semplice, mi tocca imparare tutto da zero.

L'unico "problema", e' che gwbasic ed html li avevo imparati senza nessun corso, semplicemente leggendo il manuale, perche' in fondo sono semplici, mentre C e C++ oltre ad essere "leggermente" piu complessi (:P) hanno anche una struttura completamente differente ... quindi prima di riuscire ad imparare i linguaggi, devo "disimparare" le strutture logiche che usavo prima (ed a 51 anni e con poco tempo a disposizione, non e' facile :P)

Ma non mi arrendo ... o con le buone, o con le cattive, prima o poi ci arrivo  :smiley-roll-blue:
"Sopravvivere" e' attualmente l'unico lusso che la maggior parte dei Cittadini italiani,
sia pure a costo di enormi sacrifici, riesce ancora a permettersi.

nid69ita

#23
Dec 21, 2013, 12:52 pm Last Edit: Dec 21, 2013, 12:56 pm by nid69ita Reason: 1
@etemenaki,  parallelismo con GWBasic:

Una funzione void è come il GOSUB del gwbasic con la differenza che ha un nome e non un numero di linea.
Poi il return del GWbasic indica solo la fine della sottofunzione mentre in C la usi solo se devi ritornare un valore (quindi non lo usi per una void funzione che non ritorna nulla)

Una funzione che ritorna un valore in C la puoi paragonare a "DEF FN Statement" del GWbasic anche se in C può essere molto complessa.
my name is IGOR, not AIGOR

Maurotec

@nid69ita
Quanti ricordi che tornano alla mente, chissà perché le funzioni C mi sembrano molto più naturali rispetto a quelle del GWBasic.

@Etem
Con la tua competenza in elettronica, se imparassi quello che so io di informatica e programmazione ecc non avresti limiti.

Con il C può aiutare sapere cosa accade quando viene chiamata una funzione. Visto che il C è un linguaggio
compilato il compilatore si deve inventare un modo per realizzare il concetto di funzione.
Lo stak è un area di memoria dove ogni dato è simile ad un piatto, più piatti una pila di piatti.
Il primo piatto posato sul tavolo, il secondo sul primo, il terzo sul secondo e così via.
Questa pila è nota anche con il nome di "lifo" Last input, first output appunto come una pila di piatti, dove
l'ultimo piatto posato prima o poi e destinato a essere preso prima di tutti gli altri.

Una funzione non esiste in assembly, si risolve mettendo i parametri nello stack e saltando ad eseguire codice all'indirizzo in cui c'è il codice della funzione, per cui a quel codice ci deve essere anche una porzione di codice aggiunta dal compilatore per leggere i parametri prendendoli appunto dallo stack. Occhio che prenderli deve essere inteso letteralmente, per cui preso un dato dallo stack verrà messo da qualche parte (es registro CPU) e il dato nello stack non c'è più. Se la funzione deve restituire un valore, il codice allora deve spingere il dato di ritorno nello stack e poi saltare all'indirizzo seguente a quello in cui è avvenuta la chiamata.

Ora non ricordo l'ordine dei dati messi nello stack, ma anche l'indirizzo di ritorno deve stare nello stack, diversamente la funzione non saprebbe l'indirizzo da eseguire al termine della funzione stessa.

Per cui una funzione void, costa meno di una non void che deve spingere il dato nello stack.
Il compilatore per fare la traduzione del codice C in assembly ha di bisogno di queste informazioni.

PS: L'istruzione "return" in una funzione void può essere usato per uscire da una funzione prematuramente, cioè
cose del tipo: if (pointer == NULL) return; e pointer potrebbe essere un argomento a cui la funzione accede, per cui se pointer non punta a nulla è inutile (pericoloso) continuare l'esecuzione.

Ciao.

leo72


Ora non ricordo l'ordine dei dati messi nello stack, ma anche l'indirizzo di ritorno deve stare nello stack, diversamente la funzione non saprebbe l'indirizzo da eseguire al termine della funzione stessa.

Come primo dato nello stack, quindi quello che si trova "sotto" a tutti quelli che poi saranno salvati dalla funzione stessa, c'è l'indirizzo dell'istruzione successiva a quella del salto. Una volta che la CPU trova l'istruzione RET, automaticamente preleva dallo stack il suddetto indirizzo, lo mette nel PR e poi prosegue ad eseguire il codice da quel punto.

nid69ita


...chissà perché le funzioni C mi sembrano molto più naturali rispetto a quelle del GWBasic.

Perchè il GWBasic è una porcheria, il Basic già sul PDP11 era meglio di quel dialetto schifoso della M$.   $)
Per fortuna poi in Visual Studio han fatto un dialetto Basic un pò più serio e strutturato.    :D

P.S. l'avete visto lo SmallBasic della MS per insegnare ai bambini? E' carino, ricorda un pò il GWBasic.
http://smallbasic.com/
my name is IGOR, not AIGOR

leo72

Il GWBASIC, all'epoca, non era un bruttissimo BASIC. Era leeeento, ma nel complesso semplificava tante cose, come la gesione del suono e della grafica. Poi era molto organizzato per l'accesso ai file.

Aveva le pecche tipiche dei BASIC, insomma. Certo, se lo paragoni a linguaggi tipo il Pascal, era osceno. Ma devi pensare che il BASIC era nato per insegnare la programmazione anche ai principianti: "Beginner's All-purpose Symbolic Instruction Code"

Etemenanki

Sara' per quello che persino un "coccio" come il sottoscritto era riuscito ad impararlo usando solo gli help ed il manuale :P

E' vero, linguaggi come il C e seguenti sono piu evoluti e permettono molto di piu in termini di funzionalita', ma non sono altrettanto "facili" da capire a livello intuitivo ... poi certo dipende da quanto uno lo usa, se ci stai tutto il giorno, e' piu semplice alla fine che ti rimanga in mente di piu, se ti capita di usarlo una volta al mese, un po meno :P XD
"Sopravvivere" e' attualmente l'unico lusso che la maggior parte dei Cittadini italiani,
sia pure a costo di enormi sacrifici, riesce ancora a permettersi.

paulus1969

Anche il Quick Basic era carino, c'era una versione leggera nel MS-DOS e poi la versione per sviluppare, ho fatto tantissime cose con il QB45.
Pochi giorni fa ho installato il QB6... creato da sviluppatori indipendenti, riprende il QB45.

Go Up