Docsity
Docsity

Prepara i tuoi esami
Prepara i tuoi esami

Studia grazie alle numerose risorse presenti su Docsity


Ottieni i punti per scaricare
Ottieni i punti per scaricare

Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium


Guide e consigli
Guide e consigli


Esercizi di Programmazione in C: Funzioni, Puntatori e Gestione della Memoria, Dispense di Fondamenti di informatica

Una serie di esercizi di programmazione in c, focalizzandosi su concetti fondamentali come le funzioni, i puntatori e la gestione della memoria. Gli esercizi illustrano come definire e utilizzare funzioni, come passare parametri per riferimento e per valore, come allocare e deallocare memoria dinamicamente, e come gestire gli array di puntatori. Utile per studenti che desiderano approfondire la loro conoscenza della programmazione in c e per sviluppatori che desiderano migliorare le loro competenze in questi ambiti.

Tipologia: Dispense

2023/2024

In vendita dal 04/02/2025

francesco-12u
francesco-12u 🇮🇹

34 documenti

1 / 161

Toggle sidebar

Questa pagina non è visibile nell’anteprima

Non perderti parti importanti!

bg1
Esercizi
Esercizi su Funzioni Ricorsive
Esercizio 1: Stampa decrescente
Scrivi una funzione ricorsiva che stampi i numeri da un intero positivo n fino a 1.
Specifica
Suggerimento
La funzione deve:
Prototipo
Esercizio 2: Somma dei numeri da 1 a n
Scrivi una funzione ricorsiva che calcoli la somma dei numeri da 1 a n.
Specifica
Suggerimento
La funzione deve:
Input: un numero intero positivo n
Output: stampa a schermo i numeri da n a 1 in ordine decrescente.
1. Stampare il numero n.
2. Chiamare sé stessa con n - 1 .
3. Fermarsi quando n raggiunge 0.
void stampa_decrescente(int n);
Input: un numero intero positivo n
Output: restituisce la somma di tutti i numeri da 1 a n.
1. Restituire n + somma(n - 1) .
2. Fermarsi quando n è uguale a 0.
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff
pf12
pf13
pf14
pf15
pf16
pf17
pf18
pf19
pf1a
pf1b
pf1c
pf1d
pf1e
pf1f
pf20
pf21
pf22
pf23
pf24
pf25
pf26
pf27
pf28
pf29
pf2a
pf2b
pf2c
pf2d
pf2e
pf2f
pf30
pf31
pf32
pf33
pf34
pf35
pf36
pf37
pf38
pf39
pf3a
pf3b
pf3c
pf3d
pf3e
pf3f
pf40
pf41
pf42
pf43
pf44
pf45
pf46
pf47
pf48
pf49
pf4a
pf4b
pf4c
pf4d
pf4e
pf4f
pf50
pf51
pf52
pf53
pf54
pf55
pf56
pf57
pf58
pf59
pf5a
pf5b
pf5c
pf5d
pf5e
pf5f
pf60
pf61
pf62
pf63
pf64

Anteprima parziale del testo

Scarica Esercizi di Programmazione in C: Funzioni, Puntatori e Gestione della Memoria e più Dispense in PDF di Fondamenti di informatica solo su Docsity!

Esercizi

Esercizi su Funzioni Ricorsive

Esercizio 1: Stampa decrescente

Scrivi una funzione ricorsiva che stampi i numeri da un intero positivo n fino a 1.

Specifica

Suggerimento

La funzione deve:

Prototipo

Esercizio 2: Somma dei numeri da 1 a n

Scrivi una funzione ricorsiva che calcoli la somma dei numeri da 1 a n.

Specifica

Suggerimento

La funzione deve:

Input: un numero intero positivo n

Output: stampa a schermo i numeri da n a 1 in ordine decrescente.

1. Stampare il numero n.

2. Chiamare sé stessa con n - 1.

3. Fermarsi quando n raggiunge 0.

void stampa_decrescente(int n);

Input: un numero intero positivo n

Output: restituisce la somma di tutti i numeri da 1 a n^.

1. Restituire n^ +^ somma(n^ -^ 1)^.

2. Fermarsi quando n è uguale a 0.

Prototipo

Esercizio 3: Fattoriale

Scrivi una funzione ricorsiva che calcoli il fattoriale di un numero n. Il fattoriale di n è

definito come:

n!=n×(n−1)×(n−2)×…×1n! = n \times (n - 1) \times (n - 2) \times \ldots \times 1

Specifica

Suggerimento

La funzione deve:

Prototipo

Esercizio 4: Fibonacci

Scrivi una funzione ricorsiva che calcoli il termine n -esimo della serie di Fibonacci. La serie

di Fibonacci è definita come:

Specifica

int somma(int n);

Input: un numero intero positivo n

Output: restituisce il valore di n!.

1. Restituire n * fattoriale(n - 1).

2. Fermarsi quando n è uguale a 1 o 0 (in entrambi i casi il risultato è 1).

int fattoriale(int n);

F(0)=0F(0) = 0

F(1)=1F(1) = 1

F(n)=F(n−1)+F(n−2)F(n) = F(n - 1) + F(n - 2) per n≥2n \geq 2

Input: un numero intero non negativo n

Output: restituisce il termine n^ -esimo della serie di Fibonacci.

Esercizi sulle Funzioni

Esercizi Base

Esercizi Intermedi

1. Somma di due numeri

Scrivi una funzione int^ somma(int^ a,^ int^ b)^ che accetta due interi e

restituisce la loro somma.

Nel main , chiedi all'utente di inserire due numeri e utilizza la funzione per

calcolarne la somma.

2. Stampa di un messaggio

Scrivi una funzione void stampaMessaggio() che stampa il messaggio: "Ciao,

benvenuto alle funzioni in C!".

Chiamala nel main.

3. Calcolo del massimo

Scrivi una funzione int massimo(int a, int b) che restituisce il maggiore tra

due numeri interi.

Nel main , usa la funzione per trovare il massimo tra due numeri forniti dall'utente.

4. Funzione senza parametri

Scrivi una funzione int valoreFisso() che restituisce sempre il valore 42.

Stampa il valore restituito dalla funzione nel main.

5. Media di tre numeri

Scrivi una funzione float^ media(int^ a,^ int^ b,^ int^ c)^ che calcola la media di

tre numeri interi.

Usa la funzione nel main per stampare la media di tre numeri inseriti dall’utente.

#include <stdio.h> float media(int a, int b, int c) { return (a+b+c)/3; } int main() { int num1, num2, num3; printf("Scrivi il primo numero: "); scanf("%d", &num1); printf("Scrivi il secondo numero: "); scanf("%d", &num2); printf("Scrivi il terzo numero: "); scanf("%d", &num3); int risultato = media(num1, num2, num3);

printf("La media di questi tre numeri è: %d\n", risultato); }

6. Area di un rettangolo

Scrivi una funzione int calcolaArea(int base, int altezza) che calcola

l'area di un rettangolo.

Nel main^ , chiedi all'utente di inserire base e altezza, quindi utilizza la funzione per

calcolarne l'area.

#include <stdio.h> int calcolaArea(int base, int altezza) { return base*altezza; } int main() { int base, altezza; printf("Inserisci la base: "); scanf("%d", &base); printf("Inserisci l'altezza: "); scanf("%d", &altezza); int area = calcolaArea(base, altezza); printf("L'area del rettangolo è: %d\n", area); }

7. Verifica di un numero pari

Scrivi una funzione int pari(int numero) che restituisce 1 (vero) se il numero

è pari, altrimenti restituisce 0 (falso).

Nel main^ , chiedi all’utente un numero, verifica se è pari e stampa un messaggio

adeguato

#include <stdio.h> int pari(int numero) { if(numero % 2 == 0) { return 1; }else { return 0; } } int main() { int num; printf("Inserisci un numero: "); scanf("%d", &num); int risultato = pari(num); if (risultato == 1) { printf("Il numero è pari"); }else { printf("Il numero è dispari");

t2 = nextTerm; // Aggiorna t2 al valore successivo }

printf("\n"); // Stampa una nuova linea alla fine }

int main() { int n;

// Chiedi all'utente di inserire il numero di termini printf("Inserisci il numero di termini della serie di Fibonacci da stampare: "); scanf("%d", &n);

// Controlla se l'input è valido if (n <= 0) { printf("Inserisci un numero positivo!\n"); } else { fibonacci(n); // Chiama la funzione fibonacci }

return 0; }

10. Verifica se un numero è primo

Scrivi una funzione int primo(int numero) , un numero è primo se è maggiore

di 1 ed è divisibile solo per 1 e per se stesso, che restituisce 1 (vero) se il numero

è primo, altrimenti 0 (falso).

Usa la funzione nel main per verificare se un numero dato dall’utente è primo.

11. Ricerca in un array

Scrivi una funzione int cerca(int array[], int n, int valore) che verifica

se un valore è presente in un array.

La funzione deve restituire l’indice del valore se trovato, altrimenti -^.

Testa la funzione passando un array e un valore nel main.

12. Conversione Celsius-Fahrenheit

Scrivi una funzione float celsiusToFahrenheit(float celsius) che converte

una temperatura da Celsius a Fahrenheit.

Nel main , chiedi all'utente una temperatura in gradi Celsius e usa la funzione per

stampare il valore convertito.

13. Operazioni su array

Scrivi una funzione int sommaArray(int array[], int n) che calcola la

somma di tutti gli elementi di un array.

Nel main , crea un array di almeno 5 numeri, passa l'array alla funzione e stampa

la somma.

Esercizi di Sintesi

Esercizi sui Puntatori

Esercizio 1: Puntatore su variabile intera

Scrivi un programma che:

Suggerimento : Usa l'operatore di dereferenzazione ( * ) per accedere e modificare il valore.

Esercizio 2: Puntatore su array

Scrivi un programma che:

14. Rovescia una stringa

Scrivi una funzione void rovescia(char str[]) che rovescia una stringa.

Nel main , chiedi una stringa all’utente, passa la stringa alla funzione e stampa il

risultato.

15. Calcolatrice

Scrivi una funzione int calcolatrice(int a, int b, char operatore) che

esegue un’operazione matematica ( + , - , * , / ) tra due numeri e restituisce il

risultato.

Nel main , chiedi all’utente due numeri e un operatore, quindi usa la funzione per

calcolare e stampare il risultato.

16. Tabellina di un numero

Scrivi una funzione void stampaTabellina(int n) che stampa la tabellina del

numero n fino a 10.

Chiedi all’utente un numero nel main^.

17. Crittografia semplice

Scrivi una funzione void crittografia(char str[], int chiave) che applica

una crittografia semplice sostituendo ogni carattere della stringa con il carattere

successivo nella tabella ASCII (sommando chiave ).

Chiedi una stringa e una chiave nel main , poi utilizza la funzione per crittografarla

e stampare il risultato.

1. Dichiara una variabile intera.

2. Crea un puntatore che punti a questa variabile.

3. Modifica il valore della variabile tramite il puntatore e stampalo.

Esercizio 8: Puntatore a funzione

Suggerimento : Crea un tipo di puntatore a funzione e usalo per chiamare la funzione di

confronto.

Questi esercizi coprono vari concetti fondamentali sui puntatori e ti aiuteranno a sviluppare

una comprensione più approfondita del loro utilizzo in C. Se hai bisogno di aiuto con uno

degli esercizi, fammi sapere!

1. Scrivi una funzione che prenda due numeri interi e restituisca il maggiore.

2. Crea un puntatore a funzione che punti a questa funzione.

3. Usa il puntatore per chiamare la funzione e stampare il risultato.

Tail Recursion

La tail recursion (ricorsione di coda) è un tipo di ricorsione in cui la chiamata ricorsiva è

l'ultima operazione eseguita dalla funzione. In altre parole, in una funzione ricorsiva, se

l'ultimo passo consiste semplicemente nel chiamare se stessa (o un'altra funzione) con i

suoi parametri, quella funzione è detta in tail recursion.

In C, la tail recursion è importante per un concetto chiamato ottimizzazione della

ricorsione di coda (tail call optimization, TCO), che può migliorare significativamente

l'efficienza del programma evitando l'uso aggiuntivo di memoria per ogni chiamata ricorsiva.

Cos'è la Ricorsione di Coda

Una funzione ricorsiva in C si dice essere in tail recursion se, alla fine della funzione, non

vengono eseguite operazioni ulteriori dopo il ritorno del valore della chiamata ricorsiva. Se

ciò accade, il compilatore può ottimizzare la ricorsione, trasformandola in un ciclo iterativo,

evitando quindi l'accumulo di chiamate nella call stack. Questo riduce il rischio di stack

overflow e migliora l'efficienza, in quanto il compilatore può riutilizzare lo stesso frame di

stack per ogni chiamata successiva.

Esempio di Tail Recursion in C

Un esempio classico di tail recursion è il calcolo di un fattoriale.

Esempio non ottimizzato (non in tail recursion):

In questo caso, la funzione factorial non è in tail recursion perché dopo la chiamata

ricorsiva factorial(n - 1) , la funzione esegue ancora una moltiplicazione, mantenendo

quindi una serie di chiamate in stack per ogni livello della ricorsione.

#include <stdio.h>

int factorial(int n) { if (n == 0) return 1; return n * factorial(n - 1); // La chiamata ricorsiva non è l'ultima operazione }

int main() { int result = factorial(5); printf("Fattoriale di 5 è: %d\\n", result); return 0; }

quindi il compilatore può semplicemente "trasformare" la chiamata ricorsiva in un ciclo

iterativo, evitando l'allocazione di nuovi stack frames.

Passi dell'ottimizzazione:

Limiti della Tail Recursion in C

Conclusione

La tail recursion è un potente strumento per scrivere funzioni ricorsive efficienti in C. Quando

implementata correttamente, può migliorare l'efficienza del programma evitando l'uso di

memoria addizionale nello stack. Tuttavia, la sua efficacia dipende dal supporto del

compilatore per l'ottimizzazione della ricorsione di coda, che trasforma la ricorsione in un

ciclo iterativo per risparmiare risorse.

1. Il compilatore identifica che la funzione sta chiamando se stessa come ultima

operazione.

2. La funzione non ha bisogno di mantenere il proprio stato, poiché non ci sono operazioni

post-chiamata.

3. Il compilatore elimina il frame corrente e lo sostituisce con il nuovo frame per la

chiamata successiva, trasformando la ricorsione in un ciclo.

Non tutti i compilatori ottimizzano la ricorsione di coda , quindi dipende dal

compilatore e dalle sue impostazioni di ottimizzazione.

In C, la ricorsione non è sempre la soluzione migliore per alcuni problemi,

specialmente se il compilatore non supporta l'ottimizzazione della ricorsione di coda o

se la profondità della ricorsione è troppo alta per essere gestita efficientemente anche

con l'ottimizzazione.

Stato dello Stack

Stato dello Stack

Stato dello Stack: Come Funziona e Come Viene Gestito

Lo stack è una parte della memoria utilizzata per gestire le chiamate di funzione in un

programma C. È una struttura LIFO (Last In, First Out) , cioè l'ultima funzione chiamata è la

prima a essere rimossa dallo stack. Ogni volta che una funzione viene chiamata, viene

creato un activation record o stack frame , che contiene tutte le informazioni necessarie

per l'esecuzione di quella funzione. Quando la funzione termina, il suo activation record

viene rimosso dallo stack.

Cos'è uno Stack Frame?

Uno stack frame è una sezione di memoria nello stack creata ogni volta che viene chiamata

una funzione. Contiene:

Esempio di Stato dello Stack: Funzione Ricorsiva

Vediamo un esempio pratico per capire meglio come viene gestito lo stack durante

l'esecuzione di un programma ricorsivo. Useremo la funzione per calcolare il fattoriale:

1. Indirizzo di ritorno: L'indirizzo di memoria a cui tornare dopo che la funzione ha

terminato la sua esecuzione.

2. Parametri della funzione: I valori passati alla funzione (se presenti).

3. Variabili locali: Le variabili dichiarate all'interno della funzione.

4. Registri salvati: Alcuni registri del processore che potrebbero essere necessari dopo

la chiamata.

#include <stdio.h>

int fattoriale(int n) { if (n == 0) { return 1; // Caso base } return n * fattoriale(n - 1); // Passo ricorsivo }

int main() { int risultato = fattoriale(3); printf("Il fattoriale di 3 è: %d\\n", risultato); return 0;

Diagramma dello Stack

Quando lo stack si svuota, il processo è simile a questo:

| indirizzo ritorno| | n = 2 | +------------------+ | indirizzo ritorno| | n = 3 | +------------------+

4. Chiamata a fattoriale(0)^ da fattoriale(1)^ :

Viene creato un nuovo stack frame per fattoriale(0).

Parametro n = 0.

Lo stack ora contiene:

| indirizzo ritorno| | n = 0 | +------------------+ | indirizzo ritorno| | n = 1 | +------------------+ | indirizzo ritorno| | n = 2 | +------------------+ | indirizzo ritorno| | n = 3 | +------------------+

5. Raggiunto il Caso Base ( n == 0 ):

La funzione fattoriale(0) ritorna 1 e il suo stack frame viene rimosso.

Ora lo stack si svuota gradualmente:

fattoriale(1) calcola ( 1 \times 1 = 1 ) e ritorna 1.

fattoriale(2) calcola ( 2 \times 1 = 2 ) e ritorna 2.

fattoriale(3) calcola ( 3 \times 2 = 6 ) e ritorna 6.

| indirizzo ritorno| | n = 1 | <- rimosso dopo ritorno di fattoriale(0)

Stack Overflow

Uno stack overflow avviene quando lo stack si riempie e non c'è più spazio per creare

nuovi stack frame. Questo può accadere se:

Esempio di codice che può causare stack overflow:

In questo esempio, la funzione funzioneInfinita^ continua a chiamarsi senza mai

fermarsi, esaurendo lo spazio dello stack.

Differenza tra Stack e Heap

Caratteristica Stack Heap

Allocazione Automatica Manuale ( malloc() , free() )

Velocità Molto veloce Più lento

Dimensione Limitata Dipende dalla memoria disponibile

Rischio di overflow Stack overflow possibile Possibilità di memory leak

| indirizzo ritorno| | n = 2 | <- rimosso dopo ritorno di fattoriale(1) +------------------+ | indirizzo ritorno| | n = 3 | <- rimosso dopo ritorno di fattoriale(2) +------------------+

Una funzione ricorsiva non ha un caso base corretto.

Ci sono troppi livelli di ricorsione.

Vengono dichiarate molte variabili locali o variabili di grandi dimensioni all'interno di una

funzione.

#include <stdio.h>

void funzioneInfinita() { funzioneInfinita(); // Chiamata ricorsiva senza caso base }

int main() { funzioneInfinita(); return 0; }

Funzione Ricorsiva

Una funzione ricorsiva è una funzione che chiama sé stessa direttamente o indirettamente

durante l'esecuzione. Ogni volta che una funzione ricorsiva viene chiamata, viene creata una

nuova istanza di quella funzione con i propri parametri, variabili locali e indirizzo di ritorno.

Definizione Formale

In C, una funzione ricorsiva è una funzione che si richiama da sola per risolvere un

problema, suddividendolo in sottoproblemi più semplici fino a raggiungere una condizione

base che interrompe la ricorsione.

Elementi Chiave di una Funzione Ricorsiva

Esempio 1: Calcolo del Fattoriale di un Numero

Il fattoriale di un numero n (indicato come n! ) è il prodotto di tutti i numeri interi da 1 a n.

La formula è:

n! = n ×( n − 1)×( n − 2)× … ×1 n! = n × ( n − 1) × ( n − 2) × … × 1

In termini ricorsivi:

n! = {

Codice in C:

1. Caso base (o condizione di arresto) : è la condizione che interrompe la ricorsione

evitando chiamate infinite. Senza un caso base, la funzione continuerebbe a chiamarsi

indefinitamente, causando un stack overflow.

2. Passo ricorsivo : è la chiamata alla funzione stessa con un input ridotto o modificato in

modo da avvicinarsi progressivamente al caso base.

1 se n = 0

n × ( n − 1)! se n > 0

#include <stdio.h>

// Funzione ricorsiva per calcolare il fattoriale di un numero int fattoriale(int n) { if (n == 0) // Caso base: 0! = 1 return 1; else // Passo ricorsivo: n! = n * (n - 1)! return n * fattoriale(n - 1); }

Spiegazione:

Esempio 2: Calcolo della Serie di Fibonacci

La serie di Fibonacci è definita come:

F ( n ) =

Codice in C:

Spiegazione:

int main() { int numero = 5; printf("Il fattoriale di %d è %d\n", numero, fattoriale(numero)); return 0; }

Caso base: n == 0 , il fattoriale di 0 è definito come 1.

Passo ricorsivo: n * fattoriale(n - 1) riduce progressivamente il problema fino a

raggiungere il caso base.

0 se n = 0

1 se n = 1

F ( n − 1) + F ( n − 2) se n > 1

#include <stdio.h>

// Funzione ricorsiva per calcolare il n-esimo numero di Fibonacci int fibonacci(int n) { if (n == 0) // Caso base: Fibonacci(0) = 0 return 0; else if (n == 1) // Caso base: Fibonacci(1) = 1 return 1; else // Passo ricorsivo: F(n) = F(n-1) + F(n-2) return fibonacci(n - 1) + fibonacci(n - 2); }

int main() { int numero = 6; printf("Il %d-esimo numero di Fibonacci è %d\n", numero, fibonacci(numero)); return 0; }

Caso base: n == 0 e n == 1.

Passo ricorsivo: fibonacci(n - 1) + fibonacci(n - 2).