mercoledì 24 dicembre 2014

Modern C++ - Prime operazioni con le variabili

Ben ritornati su queste del corso di Modern C++. Spero che il post su CMake non vi abbia particolarmente stravolto.

Oggi vedremo nel dettaglio come giocare con le variabili e le operazioni matematiche di base.

In C++, classicamente, una variabile si definisce nel seguente modo:

tipo_di_dato nome_variabile;

dove tipo_di_dato puó essere uno dei tipi built-in oppure custom (ovvero da noi definito) oppure, novitá assoluta introdotta da C++11, puó essere dedotto in automatico dal compilatore, per mezzo della parola chiave auto.

I tipi di dato built-in base piú utilizzati in C++ sono:
  • char per caratteri singoli (numeri da 0 a 9, una lettera, un segno)
  • int per valori interi
  • float per valori in virgola mobile a singola precisione
  • double per valori in virgola mobile a doppia precisione
A partire da questi questi 4 tipi di dato, sono poi definiti altri tipi custom, per migliorare la leggibilitá del codice sorgente o per creare dei nomi piú intuitivi, a seconda del progetto che si sta sviluppando. Vedremo nelle prossime lezioni, dopo aver parlato di strutture e classi, come creare tipi di dato custom.

Prima di tutto, occorre abilitare GCC per la compilazione di codice C++11: aprite il file project_props.cmake contenuto nella cartella CMakeModules ed incollate il seguente contenuto:
In questo modo, potremo sfruttare alcune interessanti novitá del Modern C++.
Ho preparato per voi un esempio modificato di DemoApp1, il cui listato è il seguente:
Potete notare la sezione dei commenti, delimitata da /* e */. Questi caratteri delimitatori permettono al compilatore di considerare la sezione di testo compresa tra essi come commenti multilinea, ovvero che si estendono su piú linee di codice. In contrasto a questi delimitatori, abbiamo poi il doppio slash // che identifica un commento semplice, ovvero tutti i caratteri successivi a questo simbolo è trattato come commento, fino al termine della riga corrente.

Ho importato la libreria cmath che contiene, tra le svariate funzioni matematiche quali coseno, seno, tangente, radice quadrata, anche la definizione di numeri complessi. Nel listato, come potete notare, utilizzo std::sqrt() e std::pow() per calcolare la radice quadrata e la potenza di un numero.

Nel nostro main(), vedete l'utilizzo di una variabile: in essa viene salvato tipicamente un valore, numerico o letterale, a seconda del tipo di dato specificato tra quelli sopra descritti.

Al rigo 10 vedete la dichiarazione di una variabile, chiamata var_a, che assume valore 3*3, ovvero 9. Questa è la dichiarazione in C++ classico, identica a quanto avviene in C. Una alternativa a questa forma puó essere la seguente:
int var_a(3*3);

Al rigo 11, invece, trovate un nuovo concetto di dichiarazione di variabile, inizializzata con il valore tra parentesi graffe {}, simile a quanto si puó vedere per linguaggi come Python e Javascript, per inizializzare array di elementi. Vedremo come anche in Modern C++ è possibile fare la medesima cosa.
La differenza tra la dichiarazione al rigo 10 e la dichiarazione al rigo 11 sta nel controllo sul tipo di dato effettuato con la seconda modalitá. Il compilatore effettua controlli ed avvisa, mediante warnings, se ci sono problemi con la conversione o inizializzazione di variabili, mentre nel primo caso ció non avviene e la conversione avviene implicitamente.
E´ sempre meglio preferire l'inizializzazione mediante {} ove possibile, poiché è sintatticamente piú corretta.

Al rigo 12, potete vedere come è possibile effettuare piú operazioni: C++ eseguirá le operazioni per precedenza, se non vi sono specificate le parentesi tonde, ovvero, ad esempio, 45.2/3.8*12 è diverso da 45.2/(3.8*12).

E' possibile inizializzare una variabile con un valore numerico o letterale, oppure con il valore di un'altra variabile: infatti, al rigo 15 notate come venga inizializzata var_c con il valore di var_b.

Al rigo 16 viene eseguita la stessa cosa, ma in questo caso, anziché specificare il tipo di dato di var_d, lasciamo al compilatore la libertá di utilizzare il tipo di dato piú appropriato, mediante la parola chiave auto. Questa parola chiave ci permette di alleggerire di molto la leggibilitá e la complessitá del codice, sapendo che il compilatore fará la scelta giusta per quanto riguarda il tipo di dato. Unica accortezza, è sconsigliato utilizzare le {} per inizializzare una variabile, poichè nessuna conversione di dato è implicata (poichè il tipo di dato sará scelto in automatico), ma è preferibile sempre il simbolo =.

Al rigo 23 viene effettuata la conversione esplicita, da double ad int e in questo caso, al compilatore viene un piccolo attacco di panico:
Il Compilatore è allarmato
Perchè avviene questo? Il tipo di dato double utilizza 8 byte per rappresentare un numero in virgola mobile con mantissa ed esponente: è detto a doppia precisione, in contrasto a float, che utilizza solamente 4 byte. Il tipo di dato int usa anch'esso (in generale, ma non sempre) 4 byte per rappresentare peró numeri interi. In questo caso, la conversione double->int fa perdere di precisione (perdiamo i valori decimali, idem per float->int) e quindi si ottiene questo warning. Se provate ad utilizzare il simbolo per inizializzare var_c, vedrete che il compilatore non visualizzerá alcun warning, perchè la conversione avviene implicitamente, agendo alle vostre spalle. Si racconta che in missioni critiche in cui il linguaggio C e il linguaggio C++ siano stati usati senza accorgimenti sulle conversioni di dato e abbiano causato notevoli danni.

Al rigo 23, invece, avviene la conversione da char ad int. In questo caso, non riceviamo alcun warning: char utilizza 1 byte mentre int ne usa 4, quindi non ci sono perdite di precisione, anzi si utilizza fin troppo spazio. Ció che accade invece è la conversione del simbolo letterale nel corrispondente valore numerico in formato ASCII, poichè int tratta solo valori numerici interi non letterali.

Infine, notate l'uso di std::sqrt() e di std::pow(), specificando il numero ed utilizzando auto per definire automaticamente il tipo di dato.

Eseguendo il programma, si ottiene il seguente output:

Output del codice sorgente proposto
Per gli amanti del rischio, la divisione per 0 permette di ottenere effetti indesiderati:

Dividendo per 0 si ottengono effetti particolari
Alla prossima e Buone Feste :-)

P.S. L'indice del corso Modern C++ lo potete trovare qui

2 commenti:

  1. Una domanda da niubbo. Quando definisci una funzione la { puoi metterla sulla stessa riga, come in Go?
    Per il resto bravissimissimo.

    RispondiElimina
    Risposte
    1. Si la { puó esser nella stessa riga (all'egizia, come si suol dire) oppure a capo. Io preferisco a capo, solo perché mi sembra piú facile leggere il codice dopo.

      Elimina