Stringhe e allocazione dinamica della memoria
Transcript
Stringhe e allocazione dinamica della memoria
Stringhe e allocazione dinamica Stringhe e allocazione dinamica della memoria Esercizio Scrivere un programma strings.c che legge da standard input una sequenza di parole separate da uno o più spazi, e stampa le parole lette, una per linea, in ordine inverso rispetto all’ordine di lettura. Si assume che: la lunghezza massima di una parola è definita nel programma dalla macro MAX LEN (ad esempio, si può porre MAX LEN uguale a 30); il numero massimo di parole in input è definito dalla macro MAX WORDS (ad esempio, si può porre MAX WORDS uguale a 100). Esempio Supponiamo che lo standard input sia cane ape cavallo Deve essere stampato cavallo ape cane Stringhe e allocazione dinamica Lettura di stringhe con scanf Per leggere una stringa da standard input si può fare la chiamata scanf("%s", w) dove w è l’indirizzo di un array di char sufficientemente grande per contenere la parola lette in input. Poiché stiamo assumendo che le parole in input abbiano al massimo MAX LEN caratteri, si può porre: char w[MAX_LEN + 1]; // array di MAX_LEN+1 elementi Stringhe e allocazione dinamica Lettura di stringhe con scanf In C le stringhe devono terminare con il carattere ’\0’ (carettere nullo il cui codice ASCII è 0). Quindi, se occorre memorizzare una parola di n caratteri in una array di char w , il numero di elementi di w deve essere maggiore o uguale a n + 1. Quando si legge una stringa da standard input usando scanf con specifica di formato %s, i blank (caratteri di spaziatura) che precedono la stringa sono saltati; la lettura della stringa termina quando viene letto un blank. Se viene letto “end-of-file”, scanf restituisce EOF. Stringhe e allocazione dinamica Array di stringhe Poiché le parole lette in input vanno stampate in ordine inverso, devono essere memorizzate. Dato che sappiamo che il numero massimo di parole è MAX WORDS, si può definire un array char* words[MAX_WORDS]; // array di stringhe Per ogni k tale che 0 ≤ k ≤ MAX WORDS words[k] deve puntare all’array che contiene la k-esima stringa letta. Stringhe e allocazione dinamica Array di stringhe Quando la k-esima parola (k ≥ 0) è stata letta, occorre eseguire le seguenti operazioni: 1. Allocare dinamicamente un array di char in cui sia possibile memorizzare w ; 2. Assegnare a words[k] l’indirizzo del nuovo array; 3. Copiare w nel nuovo array. Stringhe e allocazione dinamica Array di stringhe Quindi, dopo la letture delle parole cane, ape e cavallo, l’array words può essere rappresentato come: words[0] words[1] words[2] ......... ’e’ ’\0’ ’c’ ’a’ ’n’ ’a’ ’p’ ’e’ ’\0’ ’c’ ’a’ ’v’ ’a’ ’l’ ’l’ ’o’ ’\0’ Stringhe e allocazione dinamica Array di stringhe Per stampare le parole in ordine inverso, occorre stampare l’array words partendo dall’ultima posizione occupata da una parola. Quindi, se l’ultima parola è word[k-1] si può eseguire il ciclo: // stampa in ordine inverso for( i = k-1 ; i >= 0 ; i-- ) printf("%s\n",words[i]); // stampa stringa words[i] Stringhe e allocazione dinamica Funzioni ausiliarie Occorre definire le seguenti due funzioni: int length(char* w) Restituisce la lunghezza della stringa w, ossia il numero di caratteri presenti in w (senza contare il carattere di fine stringa ’\0’). Ad esempio, se w è la stringa "cane", la funzione deve restituire 4. void copy(char* to, char* from) Copia la stringa from nell’array to. Si assume che to sia sufficientemente grande per poter contenere la stringa from. Ad esempio, se from è la stringa "cane", l’array to deve contenere almeno 5 elementi, in quanto per rappresentare "cane" occorrono 5 caratteri (un carattere per ogni lettera più il carattere di fine stringa). Stringhe e allocazione dinamica Le funzioni length e copy /* Restituisce la lunghezza della stringa w */ int length(char* w){ int k; for ( k=0 ; w[k] != ’\0’ ; k++); // il ciclo termina quando w[k] = ’\0’ return k; } /* Copia la stringa from nell’array to. Si assume che to sia sufficientemente grande per contenere from. */ void copy(char* to, char* from){ int k; for(k=0 ; from[k] != ’\0’ ; k++) // il ciclo termina quando from[k]=’\0’ to[k] = from[k]; to[k] = ’\0’; // carattere fine stringa } Stringhe e allocazione dinamica Allocazione dinamica della memoria Per allocare dinamicamente un array si può usare la funzione calloc (occorre includere lo header file <stdlib.h>). Per creare un nuovo array di char in grado di contenere una stringa di lunghezza len, la chiamata da fare è calloc(len+1, sizeof(char)) Viene restuito l’indirizzo del nuovo array, che andrà assegnato a un elemento words[k] dell’array words. Stringhe e allocazione dinamica La funzione main int main(void){ char w[MAX_LEN+1]; // array usato per la lettura di una parola char* words[MAX_WORDS]; // array che contiene tutte le parole lette int k, read, len,i; k = 0; read = scanf("%s", w); // leggi prima parola while(read != EOF){ len = length(w); words[k] = calloc(len+1, sizeof(char)); copy( words[k], w); k++; read = scanf("%s", w); // leggi prossima parola } // l’ultima parola letta e’ words[k-1] // stampa in ordine inverso le parole lette for( i=k-1 ; i >= 0 ; i--) printf("%s\n",words[i]); // stampa stringa words[i] return 0; } Stringhe e allocazione dinamica Le funzioni strlen e strcpy La libreria C mette a disposizione numerose funzioni per eseguire operazioni su stringhe (occorre includere lo header file <string.h>). Ad esempio: Per calcolare la lunghezza di una stringa si può usare la funzione strlen Per copiare una stringa in una array si può usare la funzione strcpy. Possiamo usare strlen e strcpy invece di length e copy. Si raccomanda di leggere la documentazione sul libro o su un manuale. Va letto con attenzione cosa fa una funzione e le assunzioni sui parametri. Stringhe e allocazione dinamica Le funzioni strlen e strcpy L’istruzione len = length(w); può essere sostituita dall’istruzione len = strlen(w); L’istruzione copy(words[k],w); può essere sostituita da strcpy(words[k],w); Stringhe e allocazione dinamica Esempio di esecuzione Supponiamo che la prima parola sullo standard input sia cane L’istruzione read = scanf("%s", w); legge la stringa (saltando gli spazi iniziali) e la memorizza nell’array w (vengono utilizzati solo i primi 5 elementi di w). Poiché il valore restituito di read è diverso da EOF, vengono eseguite le istruzioni nel ciclo. Stringhe e allocazione dinamica Esempio di esecuzione La memoria può essere schematicamente rappresentata in questo modo: main w[0] w[1] w[2] w[3] w[4] ’c’ ’a’ ’n’ ’e’ ’\0’ ......... words[0] words[1] words[2] ......... Stringhe e allocazione dinamica Esempio di esecuzione Dopo l’istruzione len = length(w); la variabile len vale 4. La chiamata words[k] = calloc(len+1, sizeof(char)); // k vale 0 crea un nuovo array di char di 5 elementi e assegna il suo indirizzo a words[0]. Quindi, words[0] punta al nuovo array (è l’unico modo in cui è possibile accedere ad esso). Stringhe e allocazione dinamica Esempio di esecuzione main w[0] w[1] w[2] w[3] w[4] ’c’ ’a’ ’n’ ’e’ ’\0’ ......... words[0] words[1] words[2] ......... Stringhe e allocazione dinamica Esempio di esecuzione La chiamata copy( words[k], w); // k vale 0 copia la stringa w, ossia la stringa "cane", nel nuovo array. La chiamata si può scrivere anche come: copy( words[k], &w[0] ); Il primo argomento passato a copy è il valore di words[0], che è l’indirizzo dell’array creato dinamicamente. Il secondo argomento passato a copy è l’indirizzo del primo elemento dell’array w (elemento contenente il primo carattere della stringa da copiare). Stringhe e allocazione dinamica Esempio di esecuzione Dopo il passaggio dei parametri si ha: copy from to k main w[0] w[1] w[2] w[3] w[4] ’c’ ’a’ ’n’ ’e’ ’\0’ ......... words[0] words[1] words[2] ......... Stringhe e allocazione dinamica Esempio di esecuzione from[0] è sinonimo di w[0] (from[0] e w[0] denotano la stessa locazione di memoria); from[1] è sinonimo di w[1] ... to[0] denota il primo elemento del nuovo array; to[1] denota il secondo elemento del nuovo array; ... Il ciclo in copy esegue gli assegnamenti: to[0] to[1] to[2] to[3] = = = = w[0] w[1] w[2] w[3] // // // // ’c’ ’a’ ’n’ ’e’ Quando k vale 4, il ciclo termina. La prossima istruzione pone to[4] = ’\0’ Stringhe e allocazione dinamica Esempio di esecuzione copy from to k main w[0] w[1] w[2] w[3] w[4] 4 ’c’ ’a’ ’n’ ’e’ ’\0’ ......... ’c’ words[0] words[1] words[2] ......... ’a’ ’n’ ’e’ ’\0’ Stringhe e allocazione dinamica Esempio di esecuzione Supponiamo che la prossima parola sia "ape". Viene creato dinamicamente un nuovo array di 4 elementi e assegnato a words[1]; nel nuovo array viene copiata la stringa "ape". Dopo tali istruzioni, l’array words è: words[0] words[1] words[2] ’e’ ’\0’ ’c’ ’a’ ’n’ ’a’ ’p’ ’e’ ’\0’ ......... Completare l’esecuzione passo-passo dell’esempio. Stringhe e allocazione dinamica Esercizio 2 Consideriamo il seguente programma: #include <stdio.h> #include <string.h> int main(void){ char w[6]; w[0] = ’c’; w[1] = ’a’; w[2] = ’m’; w[3] = ’p’; w[4] = ’o’; w[5] = ’\0’; printf("(1) %s--->len printf("(2) %s--->len w[2] = ’\0’; printf("(3) %s--->len printf("(4) %s--->len printf("(5) %s--->len printf("(6) %s--->len return 0; } = %d\n", w, strlen(w)); = %d\n", w+3, strlen(w+3)); = = = = %d\n", %d\n", %d\n", %d\n", w, strlen(w)); w+1, strlen(w+1)); w+2, strlen(w+2)); w+3, strlen(w+3)); Stringhe e allocazione dinamica Esercizio 2 1 Eseguire il programma e motivare l’output ottenuto 2 Sostituire la definizione char w[6]; con char w[5]; Cosa succede eseguendo il programma? Perché?