Lezione 2: La Logica dei Numeri e l'Input/Output

Nelle lezioni precedenti abbiamo visto come un programma minimale possa restituire un valore numerico al sistema operativo o visualizzare messaggi statici sullo schermo. In questa lezione faremo un importante passo avanti: introdurremo il concetto di variabile, scopriremo come interagire dinamicamente con l’utente chiedendo dati da tastiera tramite scanf, e come scrivere espressioni logiche ed algebriche per guidare le scelte fondamentali della CPU tramite il costrutto if.
1. Variabili, Tipi di Dato e I/O Standard
Sezione intitolata “1. Variabili, Tipi di Dato e I/O Standard”Che cos’è una Variable sotto il cofano?
Sezione intitolata “Che cos’è una Variable sotto il cofano?”Per un programmatore alle prime armi, una variabile assomiglia a una lettera algebrica (come la o la delle equazioni scolastiche). Sotto il cofano del computer, tuttavia, una variabile è una denominazione simbolica associata a un’area fisica della memoria RAM.
Quando scrivete nel vostro codice C:
int age = 19;State ordinando al compilatore di:
- Riservare una cella fisica di memoria (dello Stack) di dimensione fissa (di norma 4 byte per il tipo
intsui sistemi moderni a 64 bit). - Associare il nome simbolico
ageall’indirizzo di partenza di quella cella. - Scrivere dentro quella cella la sequenza binaria corrispondente al numero intero
19(00000000 00000000 00000000 00010011).
Notiamo che i nomi delle variabili sono un’astrazione creata esclusivamente per agevolare lo sviluppatore umano. Nel momento in cui il compilatore traduce il codice in linguaggio Assembly e in codice macchina, tutti i nomi simbolici (age, raggio, etc.) scompaiono del tutto, venendo sostituiti da indirizzi di memoria fisici o scostamenti relativi a registri di base (es. [rbp-4]).
Il Concetto di Tipo
Sezione intitolata “Il Concetto di Tipo”Il tipo di dato indica al compilatore due informazioni cruciali:
- La dimensione in byte della scatola di memoria da riservare.
- La codifica (la regola di traduzione) con cui interpretare i bit salvati all’interno. Ad esempio, la sequenza di bit che rappresenta l’intero
65è identica a quella che rappresenta il carattere'A'in codifica ASCII; solo il tipo ci dice come interpretarla.
Il linguaggio C impone la dichiarazione esplicita del tipo per ragioni di efficienza compilativa ed esecutiva: conoscendo in anticipo il tipo, il compilatore genera direttamente le istruzioni macchina native più corrette ed efficienti (la somma tra interi add usa circuiti della CPU radicalmente diversi rispetto alla somma in virgola mobile per numeri reali).
Interi vs Reali: int e double
Sezione intitolata “Interi vs Reali: int e double”All’inizio del nostro viaggio ci concentreremo su due soli tipi fondamentali:
int: Rappresenta i numeri interi con segno (es.0,42,-1053). Di norma occupa 4 byte (32 bit), consentendo di rappresentare numeri nell’intervallo tra circa miliardi e miliardi.double: Rappresenta i numeri reali o decimali (es.3.14,-0.005,2.0). Il nome deriva da double precision (doppia precisione): occupa 8 byte (64 bit) e segue lo standard internazionale IEEE 754 per la codifica in virgola mobile. È il tipo di default utilizzato dal C quando scrivete una costante decimale nel codice.
Dichiarazioni con Inizializzazione e Dichiarazioni Disgiunte
Sezione intitolata “Dichiarazioni con Inizializzazione e Dichiarazioni Disgiunte”In C potete decidere di dichiarare e inizializzare una variabile contemporaneamente sulla stessa riga:
int age = 19; // Alloca la memoria e vi scrive subito 19Oppure potete scegliere la strada della dichiarazione disgiunta dall’assegnamento:
int score; // Dichiarazione: lo spazio viene riservato, ma il valore è indeterminatoscore = 100; // Assegnamento successivo: viene scritto 100Identità di Assegnamento: Lvalue vs Rvalue
Sezione intitolata “Identità di Assegnamento: Lvalue vs Rvalue”Quando leggete un errore di compilazione che cita le parole lvalue o rvalue, fate riferimento a questa distinzione:
- Lvalue (Left-value): Identifica una locazione di memoria fisica persistente e modificabile (scrivibile). Deve stare a sinistra dell’operatore di assegnamento
=. Esempio: il nome di una variabile (x). - Rvalue (Right-value): Rappresenta un valore temporaneo, costante o calcolato che non possiede una locazione di memoria persistente a cui fare riferimento. Può stare solo a destra dell’operatore
=. Esempi: un letterale numerico (5), il risultato di un calcolo (x + 3).
Cosa non potete fare:
5 = x; // ERRORE: 5 è un rvalue, non potete assegnargli una cella di memoria!x + y = 10; // ERRORE: l'espressione a sinistra non è un lvalue scrivibile.2. Input/Output e Formattazione
Sezione intitolata “2. Input/Output e Formattazione”La Filosofia di printf
Sezione intitolata “La Filosofia di printf”La funzione printf non serve solo a stampare stringhe statiche. Funziona come una stampante di modelli o template.
Per stampare un numero a schermo, la CPU deve effettuare una conversione: deve prendere la rappresentazione binaria in RAM (es. il numero 5) e tradurla nel carattere testuale corrispondente (il codice ASCII del carattere '5').
Per farlo, forniamo a printf una Stringa di Formato (Format String) che funge da modello con dei segnaposto (specificatori di formato), seguita dall’elenco degli argomenti che andranno a riempire ordinatamente tali segnaposto:
printf("L'utente %s ha ottenuto %d punti.\n", "Alice", 150);In questo esempio, %s (segnaposto per stringhe) riceve "Alice", mentre %d (segnaposto per interi decimali) riceve 150.
Specificatori di Formato Essenziali
Sezione intitolata “Specificatori di Formato Essenziali”Per questa prima fase del corso utilizzeremo quasi esclusivamente questi tre segnaposto fondamentali:
%d: Stampa o legge un intero (int) in base 10 con segno.%f: Stampa un numero reale (doubleofloat) in virgola fissa. Mostra di default 6 cifre decimali (es.3.141593).
L’Input dall’Utente: La Funzione scanf
Sezione intitolata “L’Input dall’Utente: La Funzione scanf”Mentre printf scrive verso l’esterno, la funzione scanf legge i dati digitati dall’utente sulla tastiera (dallo standard input stdin) e li memorizza nelle variabili del programma.
int age;scanf("%d", &age);Per utilizzare correttamente scanf dovete seguire tre regole ferree:
- Fornire il formato corretto:
%dper interi,%lfper i double (attenzione: mentre inprintfpotete usare%fper i double, inscanfdovete usare obbligatoriamente%lf- long float - altrimenti la memoria verrà corrotta scrivendo 4 byte al posto di 8. Parleremo meglio di questi specificatori in un’altra lezione.). - Fornire l’indirizzo con l’operatore
&:scanfdeve scrivere fisicamente nella memoria della variabile. Per farlo, ha bisogno dell’indirizzo fisico della cella RAM, ottenuto anteponendo l’operatore unario&(“indirizzo di”) al nome della variabile. Vedremo questi dettagli meglio in seguito. Per ora, possiamo pensare a&come ad un operatore che ci permette di accedere alla variabile in scrittura, mentre di norma l’accesso sarebbe solo in lettura. - Consumo del Whitespace:
scanfignorerà automaticamente tutti gli spazi, tabulazioni e caratteri di invio (\n) iniziali fino a quando non incontrerà il primo carattere utile del tipo richiesto.
Esempio Pratico Completo: Calcolo dell’Area del Cerchio
Sezione intitolata “Esempio Pratico Completo: Calcolo dell’Area del Cerchio”Ecco un programma C autosufficiente che potete compilare per vedere l’I/O in azione:
#include <stdio.h>
int main(void) { double raggio = 0.0; double area = 0.0; double PI = 3.1415926535;
printf("Inserisci il raggio del cerchio (numero reale): "); scanf("%lf", &raggio); // Chiede il raggio all'utente
area = PI * raggio * raggio;
printf("\n--- Risultati ---\n"); printf("Raggio inserito: %f cm\n", raggio); printf("Area calcolata: %f cm²\n", area);
return 0;}3. Espressioni, Operatori e Logica
Sezione intitolata “3. Espressioni, Operatori e Logica”Espressioni Aritmetiche, Precedenza ed Associatività
Sezione intitolata “Espressioni Aritmetiche, Precedenza ed Associatività”Gli operatori si dividono in:
- Unari: Lavorano su un solo operando (es.
-unario per invertire il segno, come in-x). - Binari: Richiedono due operandi (es.
+,-,*,/,%).
Per valutare espressioni complesse senza ambiguità, il compilatore C applica regole rigide di Precedenza (l’ordine di importanza degli operatori) e Associatività (la direzione in cui valutare gli operatori con la stessa priorità).
Ogni espressione restituisce un valore!
Sezione intitolata “Ogni espressione restituisce un valore!”Un concetto cardine del C, spesso trascurato, è che qualsiasi espressione (inclusa una singola istruzione di assegnamento) produce e restituisce sempre un valore di output.
Ad esempio, l’espressione di assegnamento x = 5 non si limita a scrivere 5 nella memoria della variabile x, ma restituisce essa stessa il valore 5 come output. Questo consente ad esempio di concatenare gli assegnamenti come in y = x = 5 (che valuta x = 5 restituendo 5, che viene poi assegnato a y).
Ecco la tabella completa degli operatori principali con l’indicazione del loro valore restituito:
| Priorità | Operatore | Descrizione | Valore Restituito in Output | Associatività |
|---|---|---|---|---|
| 1 (Massima) | + - ! | Unari (Segno, NOT) | Il valore numerico invertito o lo stato invertito (1/0) | Da destra a sinistra |
| 2 (Media) | * / % | Moltplicazione, Divisione, Modulo | Il risultato matematico dell’operazione | Da sinistra a destra |
| 3 (Bassa) | + - | Somma, Sottrazione | Il risultato matematico dell’operazione | Da sinistra a destra |
| 4 (Minima) | = | Assegnamento semplice | Il valore che è stato scritto nella variabile a sinistra | Da destra a sinistra |
Esempio di Valutazione delle Espressioni
Sezione intitolata “Esempio di Valutazione delle Espressioni”Consideriamo questa espressione complessa:
int x;int y;y = (x = 2 * 3) + 4;Sotto il cofano, la CPU eseguirà le operazioni in questo preciso ordine rispettando le precedenze e le parentesi:
- Risolve l’espressione tra parentesi tonde più interna:
x = 2 * 3. - Esegue la moltiplicazione
2 * 3che restituisce6. - Esegue l’assegnamento
x = 6. Questa espressione scrive6inxe restituisce in output6. - Sostituisce l’intera parentesi
(x = 2 * 3)con il suo valore di output6. L’espressione ora diventa:y = 6 + 4. - Esegue la somma
6 + 4che restituisce10. - Esegue l’assegnamento finale scrivendo
10iny.
Assegnamenti Composti
Sezione intitolata “Assegnamenti Composti”Per scrivere codice più compatto ed efficiente, il C offre degli operatori di assegnamento composto:
x += 5;equivale a scriverex = x + 5;x *= 2;equivale a scriverex = x * 2;x -= 1;equivale a scriverex = x - 1;
Il Trabocchetto della Divisione: Intera vs Reale
Sezione intitolata “Il Trabocchetto della Divisione: Intera vs Reale”In C, il comportamento dell’operatore di divisione / è determinato unicamente dal tipo degli operandi:
- Divisione Intera: Se entrambi gli operandi sono interi (
int), C esegue un calcolo intero e tronca completamente la parte decimale senza effettuare alcun arrotondamento. Esempio:5 / 2restituisce2(la frazione.5viene persa). Possiamo vedere questa divisione anche come il quoziente tra due numeri. - Divisione Reale: Se almeno uno degli operandi è un numero reale (ad esempio
double), C esegue una divisione decimale completa. Esempio:5.0 / 2promuove implicitamente l’intero2a2.0ed esegue5.0 / 2.0, restituendo2.5.
L’operatore Modulo (%) restituisce invece il resto della divisione intera e può essere utilizzato solo tra interi. Esempio: 5 % 2 restituisce 1. Viene comunemente usato per verificare se un numero è pari (n % 2 == 0).
4. Logica Booleana e Decisioni Condizionali
Sezione intitolata “4. Logica Booleana e Decisioni Condizionali”Come il C rappresenta la Verità
Sezione intitolata “Come il C rappresenta la Verità”Nello standard C originale non esiste un tipo booleano puro nativo (sebbene standard moderni abbiano introdotto estensioni). Il C rappresenta i valori logici tramite interi:
0rappresenta Falso (False).- Qualsiasi valore diverso da 0 (solitamente
1) rappresenta Vero (True).
Gli operatori di confronto (relazionali) permettono di stabilire relazioni d’ordine o di uguaglianza tra espressioni aritmetiche, producendo come output 1 se la relazione è verificata (Vero) e 0 in caso contrario (Falso):
| Operatore | Significato | Esempio | Risultato (se a=5, b=10) | Nota di Attenzione |
|---|---|---|---|---|
== | Uguale a | a == b | 0 (Falso) | Da non confondere con l’assegnamento =! |
!= | Diverso da | a != b | 1 (Vero) | Restituisce vero se i valori non coincidono |
> | Maggiore di | a > b | 0 (Falso) | Confronto stretto |
< | Minore di | a < b | 1 (Vero) | Confronto stretto |
>= | Maggiore o uguale a | b >= 10 | 1 (Vero) | Vero se maggiore o esattamente identico |
<= | Minore o uguale a | a <= 5 | 1 (Vero) | Vero se minore o esattamente identico |
Operatori Logici Booleani
Sezione intitolata “Operatori Logici Booleani”Per combinare più relazioni e creare espressioni logiche complesse, si utilizzano gli operatori logici:
| Operatore | Nome Logico | Descrizione | Sintassi C | Regola di Output |
|---|---|---|---|---|
&& | AND (Congiunzione) | Entrambe vere | A && B | Restituisce 1 solo se sia A che B sono diversi da zero. |
| ` | ` | OR (Disgiunzione) | Almeno una vera | |
! | NOT (Negazione) | Inversione logica | !A | Restituisce 1 se A vale zero, e 0 se A è diverso da zero. |
Tabelle di Verità (Truth Tables)
Sezione intitolata “Tabelle di Verità (Truth Tables)”Per comprendere appieno l’output prodotto dagli operatori logici, si fa riferimento alle tabelle di verità (dove 0 è Falso e 1 è Vero):
| Operando A | Operando B | Risultato A && B | Risultato A || B | Risultato !A |
|---|---|---|---|---|
0 (Falso) | 0 (Falso) | 0 | 0 | 1 |
0 (Falso) | 1 (Vero) | 0 | 1 | 1 |
1 (Vero) | 0 (Falso) | 0 | 1 | 0 |
1 (Vero) | 1 (Vero) | 1 | 1 | 0 |
L’Istruzione Condizionale if
Sezione intitolata “L’Istruzione Condizionale if”Per deviare il flusso lineare del programma a seconda delle decisioni logiche, utilizziamo il costrutto if:
if (condizione) { // Questo blocco viene eseguito solo se la condizione restituisce un valore Vero (diverso da 0)}Ecco un esempio pratico in cui usiamo variabili intere per memorizzare stati logici:
#include <stdio.h>
int main(void) { int haPatente = 1; // 1 rappresenta Vero, 0 rappresenta Falso int eta = 20;
// L'operatore logico && combina i due controlli if (eta >= 18 && haPatente) { printf("Autorizzato alla guida dell'autovettura.\n"); }
// Il NOT logico inverte la condizione int pioggia = 1; // 1 rappresenta Vero (Piove) if (!pioggia) { printf("Puoi fare una passeggiata all'aperto.\n"); } else { printf("Prendi l'ombrello!\n"); }
return 0;}La Logica Cortocircuitata (Short-Circuit Evaluation)
Sezione intitolata “La Logica Cortocircuitata (Short-Circuit Evaluation)”Il compilatore C valuta le espressioni logiche composte da sinistra a destra ed arresta immediatamente la valutazione non appena il risultato finale è matematicamente certo:
- Cortocircuito dell’AND (
&&): Nell’espressioneA && B, seAè Falso (0), l’intera espressione sarà sicuramente falsa indipendentemente daB. Pertanto,Bnon viene valutato. - Cortocircuito dell’OR (
||): Nell’espressioneA || B, seAè Vero (1), l’intera espressione sarà sicuramente vera indipendentemente daB. Pertanto,Bnon viene valutato.
Questo comportamento pigro è di vitale importanza pratica per proteggere il programma da crash a runtime. Permette infatti di effettuare un controllo di sicurezza preliminare prima di eseguire operazioni potenzialmente fatali:
int divisore = 0;int dividendo = 10;
// Grazie al cortocircuito, poichè "divisore != 0" è Falso (0),// la divisione per zero non viene eseguita e il programma non crasha.if (divisore != 0 && (dividendo / divisore) > 2) { printf("Condizione superata in sicurezza!\n");}Lo stesso principio si applica per evitare errori usando l’operatore OR (||): se la prima condizione è Vera (1), l’intera espressione è sicuramente vera, quindi la seconda parte non viene valutata. Questo è utile ad esempio per impostare un bypass o una scorciatoia di sicurezza:
int bypassaControlli = 1; // 1 rappresenta Veroint divisore = 0;int dividendo = 10;
// Grazie al cortocircuito, poichè "bypassaControlli" è Vero (1),// l'intera condizione dell'if è già soddisfatta.// La CPU non esegue la seconda parte, evitando la divisione per zero e il crash!if (bypassaControlli || (dividendo / divisore) > 2) { printf("Accesso consentito (controlli bypassati o superati).\n");}Se invertissimo l’ordine della condizione (if ((dividendo / divisore) > 2 || bypassaControlli)), la CPU proverebbe immediatamente ad eseguire la divisione per zero, provocando l’arresto immediato del programma con un errore di tipo Floating point exception.
5. Quiz di Autovalutazione
Sezione intitolata “5. Quiz di Autovalutazione”Mettetevi alla prova con questo test rapido di 10 domande per verificare la vostra comprensione dei concetti di questa lezione. Cliccate su ciascuna domanda per verificare la risposta corretta e la spiegazione.
1. Sotto il cofano del calcolatore, che cos’è fisicamente una variabile?
- A) Una lettera algebrica salvata nel compilatore.
- B) Una denominazione simbolica associata a un indirizzo e ad un’area fisica della memoria RAM.
- C) Un file temporaneo memorizzato sull’hard disk.
- D) Un’istruzione in linguaggio macchina per eseguire una somma.
▶ Mostra Risposta Corretta
Risposta corretta: B
Spiegazione: Le variabili sono astrazioni create per lo sviluppatore umano; durante la compilazione vengono tradotte direttamente in indirizzi fisici o scostamenti della memoria RAM (solitamente nello Stack).
2. Quale delle seguenti affermazioni su una variabile locale non inizializzata è corretta?
- A) C assegna automaticamente il valore
0ad essa. - B) La compilazione fallisce immediatamente.
- C) Contiene un valore spazzatura (garbage value) indeterminato dovuto allo stato precedente dei bit in RAM.
- D) Viene eliminata automaticamente dal sistema operativo all’avvio.
▶ Mostra Risposta Corretta
Risposta corretta: C
Spiegazione: La dichiarazione riserva l’area di memoria ma non la azzera. Leggere una variabile non inizializzata produce comportamenti imprecisati basati sui bit residui in quella cella.
3. Perché il linguaggio C richiede la dichiarazione esplicita del tipo per ogni variabile?
- A) Per consentire al compilatore di allocare la corretta dimensione in byte e generare istruzioni CPU specifiche ed efficienti.
- B) Per rendere il codice leggibile solo da programmatori esperti.
- C) Perché la memoria RAM gestisce solo tipi interi.
- D) Per consentire al linker di unire i file sorgente in un unico file eseguibile.
▶ Mostra Risposta Corretta
Risposta corretta: A
Spiegazione: Conoscere il tipo permette al compilatore di allocare lo spazio esatto (es. 4 byte per int, 8 byte per double) e di usare i registri e le istruzioni CPU appropriate (somma intera vs somma reale).
4. Nell’istruzione x = y + 3;, quali elementi rappresentano rispettivamente un lvalue e un rvalue?
- A)
xè un lvalue;y + 3è un rvalue. - B)
xè un rvalue;y + 3è un lvalue. - C) Entrambi sono lvalue perché contengono variabili.
- D) Entrambi sono rvalue perché fanno parte di un calcolo aritmetico.
▶ Mostra Risposta Corretta
Risposta corretta: A
Spiegazione: Un lvalue indica una locazione di memoria fisica persistente in cui è possibile scrivere (la variabile x), mentre un rvalue è un valore calcolato temporaneo (y + 3) che non ha un indirizzo persistente accessibile.
5. Cosa succede se si usa lo specificatore %f all’interno della funzione scanf per leggere un numero di tipo double?
- A) Il numero viene letto correttamente ma approssimato all’intero più vicino.
- B) Si verifica una corruzione di memoria, poiché
scanfsi aspetta%lfper scrivere 8 byte anziché 4. - C) La compilazione fallisce con un errore di sintassi.
- D) La variabile viene allocata nello Heap invece che sullo Stack.
▶ Mostra Risposta Corretta
Risposta corretta: B
Spiegazione: In scanf, lo specificatore %f è destinato ai dati di tipo float (4 byte). Per memorizzare correttamente un double (8 byte) è obbligatorio usare %lf.
6. Quanto restituisce l’espressione aritmetica 5 / 2 in C?
- A)
2.5 - B)
2 - C)
3 - D)
0
▶ Mostra Risposta Corretta
Risposta corretta: B
Spiegazione: Poiché entrambi gli operandi (5 e 2) sono interi (int), C esegue una divisione intera e tronca completamente la parte frazionaria.
7. Come si può ottenere una divisione decimale corretta (che restituisca 2.5) a partire dai numeri 5 e 2?
- A) Scrivendo
5.0 / 2o5 / 2.0. - B) Utilizzando l’operatore modulo
%. - C) Memorizzando il risultato in una variabile
doublesenza modificare i due operandi interi. - D) Modificando il codice di ritorno del programma
mainconreturn 2.5;.
▶ Mostra Risposta Corretta
Risposta corretta: A
Spiegazione: Se almeno uno degli operandi è un letterale reale (5.0), l’altro intero viene promosso implicitamente a reale prima dell’operazione, forzando una divisione in virgola mobile.
8. In C, come viene rappresentato internamente il valore logico Falso (False)?
- A) Tramite la parola chiave
falseinclusa di default nel compilatore. - B) Esclusivamente dal valore intero
0. - C) Da qualsiasi valore minore di 0.
- D) Dalla presenza di un valore spazzatura (garbage value).
▶ Mostra Risposta Corretta
Risposta corretta: B
Spiegazione: Per il linguaggio C nativo, lo 0 logico rappresenta il Falso, mentre qualsiasi valore diverso da zero (positivo o negativo) è interpretato come Vero.
9. Nel frammento if (a != 0 && b / a > 1), cosa succede se la variabile a è uguale a 0?
- A) Il programma crasha immediatamente a causa di una divisione per zero.
- B) La condizione restituisce Vero perché la seconda parte non è controllata.
- C) La seconda parte della condizione non viene valutata grazie alla logica di cortocircuito dell’operatore
&&. - D) Il valore di
bviene azzerato in automatico dalla CPU.
▶ Mostra Risposta Corretta
Risposta corretta: C
Spiegazione: Poiché il primo termine a != 0 valuta a Falso (0), il risultato globale di un AND (&&) è matematicamente certo di essere Falso. La CPU arresta la valutazione evitando il crash dovuto alla divisione b / a.
10. Qual è il comportamento dell’operatore logico di cortocircuito OR (||)?
- A) Valuta sempre entrambi gli operandi per sicurezza.
- B) Se il primo operando è Vero (
1), non valuta il secondo perché l’espressione è sicuramente Vera. - C) Inverte sempre il risultato finale.
- D) Restituisce errore se uno dei due operandi è uguale a zero.
▶ Mostra Risposta Corretta
Risposta corretta: B
Spiegazione: Per l’operatore || basta che un solo operando sia vero affinché l’espressione totale sia vera. Se il primo termine è non-nullo, la CPU ottimizza saltando la valutazione del secondo.
6. Esercizi Pratici
Sezione intitolata “6. Esercizi Pratici”Mettete in pratica i concetti studiati provando a risolvere questi esercizi. Le soluzioni in codice C sono nascoste all’interno dei relativi pannelli.
Esercizio 1: Calcolo della Percentuale
Sezione intitolata “Esercizio 1: Calcolo della Percentuale”Scrivete un programma in C che richieda all’utente l’importo totale di un conto e la percentuale di mancia da lasciare. Calcolate e stampate l’importo della mancia e il conto totale (conto + mancia) formattati con due cifre decimali.
💻 Mostra Soluzione e Codice C
#include <stdio.h>
int main(void) { double conto = 0.0; double percentualeMancia = 0.0; double mancia = 0.0; double totale = 0.0;
printf("Inserisci l'importo del conto (€): "); scanf("%lf", &conto);
printf("Inserisci la percentuale di mancia (es. 15): "); scanf("%lf", &percentualeMancia);
mancia = conto * (percentualeMancia / 100.0); totale = conto + mancia;
printf("\n--- Riepilogo Spesa ---\n"); printf("Conto originale: %.2f €\n", conto); printf("Mancia calcolata (%.1f%%): %.2f €\n", percentualeMancia, mancia); printf("Conto totale da pagare: %.2f €\n", totale);
return 0;}Spiegazione dell’algoritmo:
- Dichiarazione delle variabili di tipo
doubleper mantenere la precisione decimale dell’importo. - Input tramite
scanfcon il formato%lf. - Calcolo aritmetico dividendo la percentuale per
100.0(forzando calcolo reale) e sommandolo al totale. - Visualizzazione formattata tramite
%.2fper mostrare solo i centesimi.
Esercizio 2: Verifica di Divisibilità
Sezione intitolata “Esercizio 2: Verifica di Divisibilità”Scrivete un programma in C che legga due numeri interi (chiamati numeratore e denominatore) e determini se il primo è divisibile per il secondo (ovvero se il resto è nullo). Stampate a schermo 1 se è divisibile, 0 altrimenti.
Vincolo: non usate l’istruzione if o l’operatore ternario.
💻 Mostra Soluzione e Codice C
#include <stdio.h>
int main(void) { int numeratore = 0; int denominatore = 0;
printf("Inserisci il numeratore (intero): "); scanf("%d", &numeratore);
printf("Inserisci il denominatore (intero): "); scanf("%d", &denominatore);
// Verifichiamo prima se il denominatore è diverso da zero per evitare crash. // Sfruttiamo il cortocircuito logico: se il denominatore è 0, // l'espressione si ferma restituendo 0 (Falso). int divisibile = (denominatore != 0) && (numeratore % denominatore == 0);
printf("Risultato divisibilita': %d\n", divisibile);
return 0;}Spiegazione dell’algoritmo:
- Leggiamo i due interi con
%d. - L’espressione logica
(denominatore != 0) && (numeratore % denominatore == 0)garantisce la sicurezza: sedenominatoreè zero, il programma non valuta l’operatore modulo%(che su divisione per zero provocherebbe crash). - L’operatore logico AND
&&restituisce direttamente1(Vero) se la condizione è rispettata, altrimenti restituisce0(Falso).
Esercizio 3: Calcolo dell’Idoneità (Soglia di Punteggio)
Sezione intitolata “Esercizio 3: Calcolo dell’Idoneità (Soglia di Punteggio)”Scrivete un programma in C che legga due punteggi interi (es. prova scritta e prova pratica, ciascuna con valore da 0 a 15). Il programma deve determinare se il candidato ha ottenuto l’idoneità. L’idoneità si ottiene solo se la somma dei due punteggi è maggiore o uguale a 18. Stampate 1 in caso di successo (idoneo), 0 altrimenti.
Vincolo: non utilizzate il costrutto else o else if (non ancora formalmente introdotti).
💻 Mostra Soluzione e Codice C
#include <stdio.h>
int main(void) { int scritto = 0; int pratico = 0; int somma = 0; int idoneo = 0;
printf("Inserisci il punteggio dello scritto (0-15): "); scanf("%d", &scritto);
printf("Inserisci il punteggio del pratico (0-15): "); scanf("%d", &pratico);
somma = scritto + pratico;
// Utilizziamo solo istruzioni if semplici basate su logica di confronto. // Inizializziamo idoneo a 0, e lo cambiamo a 1 solo se la soglia è raggiunta. if (somma >= 18) { idoneo = 1; }
printf("Stato idoneita': %d\n", idoneo);
return 0;}Spiegazione dell’algoritmo:
- Leggiamo i due punteggi interi tramite
scanf. - Calcoliamo la somma algebrica delle prove.
- Utilizziamo una variabile di stato
idoneoinizializzata a0(Falso). Un singolo controlloiflineare verifica sesomma >= 18: in caso affermativo, la variabile viene aggiornata a1.
Esercizio 4: Scambio di Valori Senza Variabile Temporanea
Sezione intitolata “Esercizio 4: Scambio di Valori Senza Variabile Temporanea”Scrivete un programma in C che legga due numeri interi da tastiera e li memorizzi nelle variabili a e b. Successivamente, scambiate i valori contenuti nelle due variabili senza utilizzare una terza variabile di supporto (temporanea). Stampate a schermo i valori delle variabili prima e dopo lo scambio.
Suggerimento: utilizzate operazioni algebriche di addizione e sottrazione.
💻 Mostra Soluzione e Codice C
#include <stdio.h>
int main(void) { int a = 0; int b = 0;
printf("Inserisci il valore di a: "); scanf("%d", &a);
printf("Inserisci il valore di b: "); scanf("%d", &b);
printf("\nPrima dello scambio: a = %d, b = %d\n", a, b);
// Algoritmo di scambio algebrico senza variabile temporanea a = a + b; // a ora contiene la somma dei due valori b = a - b; // b diventa la differenza (somma - b iniziale) -> assume il valore originale di a a = a - b; // a diventa la differenza (somma - a iniziale) -> assume il valore originale di b
printf("Dopo lo scambio: a = %d, b = %d\n", a, b);
return 0;}Spiegazione dell’algoritmo:
- La prima istruzione memorizza la somma dei due numeri in
a. - Sottraendo il valore iniziale
bdalla nuova sommaa(eseguito cona - b), si ottiene esattamente il valore originario dia, che viene memorizzato inb. - Infine, sottraendo il nuovo valore di
b(che è il vecchioa) dalla sommaa(eseguito cona - b), si isola il valore originario dib, che viene memorizzato ina.
Esercizio 5: Calcolatore di Tariffe Orarie (Bassa/Alta Stagione)
Sezione intitolata “Esercizio 5: Calcolatore di Tariffe Orarie (Bassa/Alta Stagione)”Un parcheggio applica una tariffa base di 2.50 € all’ora. Tuttavia, se l’utente ha diritto ad una tariffa promozionale (rappresentata da una variabile intera haPromozione che vale 1) ed è un giorno feriale (rappresentato da una variabile giornoFeriale che vale 1), la tariffa viene ridotta del 20%. Scrivete un programma che calcoli e stampi il costo totale del parcheggio in base al numero di ore inserite dall’utente.
Vincolo: non utilizzate il costrutto else o else if.
💻 Mostra Soluzione e Codice C
#include <stdio.h>
int main(void) { double ore = 0.0; int haPromozione = 0; int giornoFeriale = 0; double tariffaOraria = 2.50; // Tariffa base di partenza double totale = 0.0;
printf("Inserisci il numero di ore di sosta: "); scanf("%lf", &ore);
printf("Hai diritto alla promozione? (1 per Si, 0 per No): "); scanf("%d", &haPromozione);
printf("E' un giorno feriale? (1 per Si, 0 per No): "); scanf("%d", &giornoFeriale);
// Applichiamo lo sconto del 20% solo se entrambe le condizioni sono vere. // L'operatore logico && valuta la contemporaneità degli eventi. if (haPromozione && giornoFeriale) { tariffaOraria = tariffaOraria * 0.80; // Sconto del 20% }
totale = ore * tariffaOraria;
printf("\nTariffa oraria applicata: %.2f €/h\n", tariffaOraria); printf("Totale da pagare: %.2f €\n", totale);
return 0;}Spiegazione dell’algoritmo:
- Dichiarazione ed inizializzazione delle variabili. La tariffa di base è impostata su
2.50a runtime. - Acquisizione degli stati logicizzati dell’utente tramite interi (
1/0). - L’espressione logica
haPromozione && giornoFerialecontrolla se entrambe le condizioni sono verificate. In caso positivo, il bloccoifaggiorna la tariffa applicando lo sconto (tariffaOraria * 0.80). - Calcolo finale moltiplicando le ore per la tariffa calcolata in precedenza.
Esercizio 6: Operatori di Assegnamento Composto
Sezione intitolata “Esercizio 6: Operatori di Assegnamento Composto”Scrivete un programma in C che legga un numero intero da tastiera, lo moltiplichi per 3, vi sommi 10 ed infine stampi il risultato.
Vincolo: per eseguire le operazioni utilizzate esclusivamente gli operatori di assegnamento composto (*=, +=).
💻 Mostra Soluzione e Codice C
#include <stdio.h>
int main(void) { int numero = 0;
printf("Inserisci un numero intero: "); scanf("%d", &numero);
// Eseguiamo i calcoli usando solo operatori composti numero *= 3; // Equivale a: numero = numero * 3; numero += 10; // Equivale a: numero = numero + 10;
printf("Risultato finale: %d\n", numero);
return 0;}Spiegazione dell’algoritmo:
- Acquisiamo l’input con
%d. - Applichiamo la moltiplicazione per 3 e la memorizziamo direttamente su
numerocon l’operatore composto*=. - Sommiamo
10anumeromemorizzando il risultato su se stessa usando+=.
Esercizio 7: Conversione da Celsius a Fahrenheit
Sezione intitolata “Esercizio 7: Conversione da Celsius a Fahrenheit”Scrivete un programma in C che legga una temperatura espressa in gradi Celsius (numero reale) e la converta in gradi Fahrenheit usando la formula: Vincolo: assicuratevi che la divisione non venga troncata a causa della divisione intera.
💻 Mostra Soluzione e Codice C
#include <stdio.h>
int main(void) { double celsius = 0.0; double fahrenheit = 0.0;
printf("Inserisci la temperatura in gradi Celsius: "); scanf("%lf", &celsius);
// Usiamo 9.0 / 5.0 (divisione reale) anziché 9 / 5 (divisione intera) fahrenheit = celsius * (9.0 / 5.0) + 32.0;
printf("Temperatura in Fahrenheit: %.2f °F\n", fahrenheit);
return 0;}Spiegazione dell’algoritmo:
- Dichiarazione delle variabili di tipo
doubleper gestire i decimali. - Calcolo della conversione. Usare
9.0 / 5.0(o semplicemente1.8) previene l’errore classico di divisione intera9 / 5, che in C verrebbe troncato a1.
Esercizio 8: La trappola dell’assegnamento nel confronto
Sezione intitolata “Esercizio 8: La trappola dell’assegnamento nel confronto”Scrivete un programma che inizializzi una variabile intera stato a 0. Scrivete una istruzione if che controlli se lo stato è attivo (uguale a 1). Nel blocco della condizione inserite erroneamente l’operatore di assegnamento = al posto di == (es. if (stato = 1)). Eseguite il programma e spiegate perché il blocco condizionale viene eseguito nonostante il valore iniziale fosse 0.
💻 Mostra Soluzione e Spiegazione
Codice sorgente assegnamento_errato.c:
#include <stdio.h>
int main(void) { int stato = 0;
// Errore comune: usiamo '=' invece di '==' if (stato = 1) { printf("Il blocco viene eseguito! Stato attuale: %d\n", stato); }
return 0;}Spiegazione:
- L’espressione
stato = 1non effettua un confronto, ma assegna il valore1alla variabilestato. - In C, il risultato di un’operazione di assegnamento corrisponde al valore stesso assegnato (in questo caso
1). - Essendo
1un valore diverso da zero, viene interpretato logicamente come Vero (True). Pertanto, la condizione dell’ifè soddisfatta ed il blocco viene eseguito.
Esercizio 9: Calcolo di Ore, Minuti e Secondi
Sezione intitolata “Esercizio 9: Calcolo di Ore, Minuti e Secondi”Scrivete un programma in C che legga da tastiera un numero intero positivo che rappresenta una quantità di secondi. Calcolate e stampate a schermo il numero equivalente espresso in Ore, Minuti e Secondi.
Suggerimento: utilizzate gli operatori divisione intera / e modulo %.
💻 Mostra Soluzione e Codice C
#include <stdio.h>
int main(void) { int secondiTotali = 0; int ore = 0; int minuti = 0; int secondiResidui = 0;
printf("Inserisci la quantita' di secondi: "); scanf("%d", &secondiTotali);
// Calcoliamo le ore dividendo i secondi per 3600 (secondi in un'ora) ore = secondiTotali / 3600;
// Calcoliamo quanti secondi rimangono dopo aver tolto le ore intere secondiResidui = secondiTotali % 3600;
// Calcoliamo i minuti dai secondi residui dividendo per 60 minuti = secondiResidui / 60;
// I secondi finali sono il resto dell'operazione precedente secondiResidui = secondiResidui % 60;
printf("%d secondi equivalgono a: %d h, %d m, %d s\n", secondiTotali, ore, minuti, secondiResidui);
return 0;}Spiegazione dell’algoritmo:
- Dividiamo per 3600 (numero di secondi in 1 ora) ottenendo le ore intere per troncamento.
- Calcoliamo il resto dei secondi esclusi con
% 3600. - Dividiamo la quantità restante per 60 per ottenere i minuti ed usiamo l’operatore modulo per isolare i secondi finali.
Esercizio 10: Calcolo di Sicurezza del Logaritmo (Bozza Logica)
Sezione intitolata “Esercizio 10: Calcolo di Sicurezza del Logaritmo (Bozza Logica)”Il calcolo del logaritmo naturale di un numero reale (in matematica espresso come ) è definito solo per numeri strettamente maggiori di zero (). Scrivete un programma in C che acquisisca un numero double x da tastiera e, utilizzando gli operatori logici e il cortocircuito, memorizzi in una variabile valido il valore 1 se è possibile calcolare il logaritmo in sicurezza, 0 altrimenti.
Vincolo: non utilizzate il costrutto if o else.
💻 Mostra Soluzione e Codice C
#include <stdio.h>
int main(void) { double x = 0.0;
printf("Inserisci il valore reale x: "); scanf("%lf", &x);
// L'espressione relazionale restituisce 1 se vero, 0 se falso. // Viene valutata in sicurezza. int valido = (x > 0.0);
printf("E' possibile calcolare il logaritmo? (1=Si, 0=No): %d\n", valido);
return 0;}Spiegazione dell’algoritmo:
In C le espressioni di confronto (come x > 0.0) producono un valore numerico pari a 1 se la relazione d’ordine è verificata o 0 in caso contrario. Assegniamo questo valore direttamente alla variabile valido, senza usare blocchi condizionali.