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é?