Traccia della lezione Lez05: Lucido 2: Spesso si usa scrivere il costrutto di selezione nella forma: if (espressione) istruzione Ovviamente, non cambia nulla. L'abbiamo scritta andando a capo solo per distinguere meglio l'attività di gestione (valutare l'espressione logica) dall'attività di processo (eseguire l'istruzione). In termini più concreti, se si scrive: if (b != 0) r = a/b; - "if (b != 0)" è il costrutto di selezione (attività di gestione); al suo interno, (b != 0) è la condizione logica. - "r = a/b;" è l'istruzione (attività di processo) Infatti, la condizione non è seguita da punto e virgola, l'istruzione sì. Se per errore si scrivesse: if (b != 0); r = a/b; il compilatore non segnalerebbe errori. Infatti, questo brano di codice ha il seguente significato: - se b != 0, esegui l'istruzione vuota (;) - dividi a per b e assegna il risultato a r Il compilatore non è in grado di intuire dall'indentazione le nostre intenzioni, e si limita ad applicare il filtro che distingue ciò che rispetta le regole del linguaggio da ciò che non le rispetta. Per questo motivo, disseminare ; nel codice è una tipica causa di errore. L'esempio IF1.C assegna valori a l1, l2 e l3. Poi pone una serie di domande su espressioni logiche ottenute combinando valori con operatori aritmetici e chiamate a funzione. Per ogni domanda, se l'espressione è vera, stampa la stringa "Vero". a) (l1 - NUMERO1) == 0 diventa (l1 - 5) == 0 ed è un'espressione vera. Le parentesi non sono necessarie, ma aumentano la leggibilità. Se si scrivesse "(l1 - NUMERO3) == 0", avremmo "(l1 - -5 ) == 0". Il secondo "-" è un operatore unario, e prevale sul primo (differenza). Questo prevale sull'operatore di uguaglianza (relazione). Il risultato è falso, e la stringa non viene stampata. b) CalcolaSomma(l1,NUMERO3) == 0 diventa CalcolaSomma(l1,5) == 0 e vale falso. Scrivendo "l1 + NUMERO3 == 0" si otterrebbe lo stesso risultato. c) l2 > 0 vale vero d) l3 * NUMERO2 >= 0 diventa l3 * 0 >= 0 e vale vero Aggiungendo le parentesi, non cambierebbe nulla: (l3 * NUMERO2) >= 0. Il codice IF2.C ripete gli esempi del codice IF1.C, ma stampa sempre il valore dell'espressione logica, anziché stampare la stringa "Vero" solo nel caso vero. Lucido 3: Il codice IF3.C illustra alcune varianti, che possono derivare da errori di scrittura: a) l1 + NUMERO3 = 0 dà un errore di compilazione alla riga 50 ("invalid l-value in assignment"). Usando notepad, possiamo raggiungere l'errore direttamente con la voce del menù "Modifica/Vai a" (oppure CTRL-G): l1 + NUMERO3 non è un "l-value" valido, cioè non rappresenta una cella di memoria nella quale si può scrivere un valore. b) CalcolaSomma(l1,NUMERO3) = 0 dà lo stesso errore alla riga 54, per lo stesso motivo. c) (l3 * NUMERO2) >= 0 vale 1, e viene interpretata come vera (con o senza parentesi). d) l3 * (NUMERO2 >= 0) vale 5, e viene interpretata come vera (espressione bislacca, comunque). e) l1 = 1 illustra il classico errore di usare l'assegnamento anziché un'uguaglianza: l1 valeva 5, ma gli viene assegnato il valore 1 e il valore dell'espressione di assegnamento è 1. f) (10 < 8 < 6) vale 1, cioè vero, Infatti si valuta prima 10 < 8 (che è falsa, cioè vale 0) e poi 0 < 6 (che è vera, cioè vale 1). E' l'altro tipico errore: usare gli operatori di relazione dimenticando che sono binari. g) (10 < 8 && 8 < 6) è l'espressione precedente scritta in maniera corretta, e vale 0, cioè falso. Lucido 5: Il codice IF4.C illustra: - la definizione di costanti simboliche TRUE e FALSE per i valori logici e di un tipo simbolico "bool", che in realtà è "int", ma rende più chiara la natura delle variabili usate (durante la precompilazione, la direttiva #define fa sostituire "bool" con "int"). In futuro vedremo un modo migliore di procedere, usando i così detti "tipi enumerativi". Nel C99 esiste un tipo bool nativo per le variabili logiche. - l'uso della clausola "else" per proporre istruzioni alternative - l'uso di blocchi condizionali al posto delle singole istruzioni; il blocco sarebbe ovviamente evitabile scrivendo una sola istruzione "ACapo();" alla fine. Allo stesso modo, potremmo invece duplicare la scritta iniziale spostandola in entrambi i blocchi alternativi. Come semplice esercizio, si sostituiscano i doppi if associati a situazioni esattamente alternative con una sola espressione condizionale che usi la clausola else. Lucido 7: Due semplici esercizi: che cosa calcolano queste due istruzioni? 1) k = (i >= 0 ? i : 0); Risposta: max(i,0) 2) k = (i > j ? i : j); Risposta: max(i,j) Si provi a ragionare per casi particolari (per es., i = 1, j = 2). Lucido 8: Per un approfondimento su vantaggi e svantaggi di vari stili di parentesi, si veda la voce "indent style" di Wikipedia o la pag. 94 del King. Lucido 10: Nel codice sulla sinistra, l'else viene aggregato al secondo if, mentre il primo if rimane semplice. Perciò, quando y = 0, non si esegue nessuna istruzione. Quando y != 0 e x = 0, d'altra parte, si esegue l'else, cioè si segnala un errore che non esiste. Nel codice sulla destra, le parentesi graffe indicano correttamente che l'else va aggregato al primo if. Di conseguenza, il secondo if rimane semplice. Quando y = 0, si segnala l'errore e quando y != 0 e x = 0 non si fa nulla. Come norma di stile, quando si ha un costrutto if...then...else conviene tenere per primo, nella parte "then" il caso più degenere e semplice (tipicamente quello con l'errore o con la soluzione banale), perché altrimenti rischia di venire spinto molto in basso e non essere più chiaro. Quindi nell'esempio converrebbe scrivere if (y == 0) printf("Errore!"); else { if (x != 0) r = y/x; } che si può ulteriormente chiarire scrivendo: if (y == 0) printf("Errore!"); else if (x != 0) r = y/x; Lucido 12: Da un punto di vista formale, il costrutto switch è molto irregolare: - è un unico blocco (c'è solo una coppia di parentesi graffe obbligatorie), - ma ha diversi punti di ingresso e diversi punti di uscita. In effetti, la parola chiave default e ciascuna parola chiave case sono tutti punti di entrata diversi. L'istruzione break usata al termine di ogni caso fa uscire dall'intero blocco, quindi ciascuna è un punto di uscita aggiuntivo, oltre alla fine del blocco stesso. In pratica, si stanno simulando molti blocchi alternativi con un solo blocco. Questa non è programmazione strutturata, e sarebbe da evitare. Però il costrutto switch viene usato in casi talmente specifici e chiari da risultare accettabile. Lucido 13: Si noti che le costanti devono essere, appunto, costanti. Pare ovvio, ma capita di dimenticarlo. Il codice MONTH1.C stampa la data 29/03/2011 in forma testuale (29 marzo 2011) convertendo il numero del mese nel nome corretto attraverso una sequenza di costrutti "else if" in cascata. Esercizio: si sostituisca la cascata di "else if" con un solo costrutto "switch". Il codice MONTH2.C mostra che cosa succede se non ci sono i break in corrispondenza a ciascun caso: si eseguono tutte le istruzioni successive al caso valido. Il codice MONTH3.C e' quello corretto, con i break che fanno eseguire solo le istruzioni del caso valido.