Trattamento delle STRINGHE in C(++)

Transcript

Trattamento delle STRINGHE in C(++)
Trattamento delle STRINGHE in C(++)
Le cose che qui vengono dette valgono sia per il linguaggio C che per il C++. Nel linguaggio C non esiste, a differenza di quel che avviene per altri linguaggi di programmazione (ad es. il Pascal), un tipo predefinito per le stringhe. Esiste tuttavia un modo semplice per costruirle e una library (<string.h>) con tante utili funzioni per la loro elaborazione.
In C una stringa viene costruita come un array di caratteri (char); i caratteri della stringa vanno dal primo carattere dell’array (quello di indice 0) fino al primo carattere di Fine Stringa che si incontra andando avanti: il carattere di Fine Stringa che ha come valore 0 e sovente nei programmi lo si indica come '\0'. Tutte le funzioni di <string.h> fanno riferimento alle stringhe come ad un dato che ha la struttura appena descritta e che è illustrata in figura:
Come già sappiamo, quando si dichiara un array, per il linguaggio C il nome dell’array da solo rappresenta un puntatore (indirizzo) al byte dell’area di memoria a partire dal quale l’array stesso viene memorizzato. Nel caso delle stringhe (=array di char) il loro nome dà quindi l’indirizzo del primo carattere della stringa: gli altri caratteri sono tutti quelli successivi fino al primo byte nullo (=0='\0') che s’incontra (Fine Stringa, che non fa parte della stringa). Ovunque in un programma compare una stringa costante (successione di caratteri delimitata dai apici doppi "…") il compilatore, dopo aver memorizzato i caratteri della stringa in una opportuna area di memoria, sostituisce alla stringa costante il puntatore all’area di memoria nella quale i caratteri della stringa sono stati memorizzati, proprio come fa con le stringhe “variabili” dichiarate come array di char. Questo consente, ovunque sia richiesta una stringa come parametro di input, di mettere indifferentemente sia una stringa costante che una “variabile”.
Attenzione! Essendo la fine di una stringa data dal primo carattere di Fine Stringa che s’incontra andando avanti in memoria dopo l’inizio della stringa, per le stringhe variabili, se ci si dimentica di mettere il Fine Stringa, questo potrebbe anche essere trovato fuori dall’area riservata alla stringa con la corrispondente dichiarazione di array di caratteri: come noto il C, per essere efficiente, evita molti controlli tra cui anche quello di andar fuori dall’area riservata ad un array e questo può allora portare ad imprevedibili e non segnalati errori. Il programmatore dovrà comunque preoccuparsi di evitarli.
Adesso verrà fatta una breve descrizione delle funzioni di uso più frequente della library <string.h> e di alcune di queste viene presentata una versione “casalinga” rifatta come esercizio sulle stringhe per cercare possibilmente di riuscire a comprenderle meglio. Queste 118
funzioni rifatte sono state messe in un file stringhe.h e se si vogliono usare in un programma, senza riscriverle, basterà mettere un #include "stringhe.h", purché naturalmente si abbia il file stringhe.h nel direttorio­cartella in cui si sta lavorando.
Alcune funzioni della library sono descrtte negli appunti sull’I/O:
gets(); puts(); per leggere/scrivere stringhe da/su tastiera/schermo;
fgets(); fputs(); per leggere/scrivere stringhe da/su file.
Poi ci sono due funzioni che servono per “leggere”/“scrivere” da/su stringa:
sscanf(ss,"Specificazioni", lista punt. var. input);
sprintf(ss,"Specificazioni", lista valori di output);
Lo scopo di sscanf() è quello di prendere da una stringa ss (che può essere sia costante che variabile) dei dati, ovviamente sotto forma di caratteri, e trasferirli/convertirli secondo le "Specificazioni" nelle aree di memoria individuate dalla lista punt. var. input. Seguono due esempi:
int i,k; float f,g; double d,e;
sscanf("134 7.375 781.97 12500 23861495.123847 1.23E­88", "%i %f %f %lf %lf %d", &i, &f, &g, &k, &d, &e);
trasferisce in i, f, g, k, d, e, secondo la relativa specificazione, i valori numerici: 134 7.375 781.97 12500 23861495.123847 1.23E­88
char tt[35]=" 112 4.75 Z 193.874 Giuseppe 132", w[12], c; int n,m; float f; double d;
sscanf(tt,"%d %f %c %lf %s %i", &m, &f, &c, &d, w, &n);
trasferisce in m, f, c, d, w (notare l’assenza di & perché w è già un puntatore), n, secondo la relativa specificazione, i seguenti oggetti: 112 4.75 'Z' 193.874 "Giuseppe" e 132 (gli apici ' e " sono usati per far capire che in un caso si tratta del carattere Z e nellaltro della stringa Giuseppe, ma non sono ovviamente memorizzati).
Lo scopo della funzione sprintf() è quello mettere su una stringa (ss) i caratteri che con le analoghe funzioni printf() e fprintf() andrebbero rispettivamente sullo schermo o su un file. Ad esempio, col frammento di programma
char riga[81], st[12]="Stringa", ch='Q'; int m=4; float ff=158.376; double d=1.2345678901468E­127;
sprintf(riga,"%3d %8.4f %2c %10s% 20.12le %s",
m,ff,ch,st,d,"Fine");
sulla stringa riga verrebbero memorizzati i caratteri (si è indicato con □ un blank): □□4□158.3760□□Q□□□□Stringa□1.234567890147e­127□Fine
Spesso è utile sapere quanto è lunga una stringa, cioè quanti sono i caratteri che sono compresi tra l’inizio della stringa ed il carattere di Fine Stringa (escluso, vedi figura iniziale). Per 119
questo scopo nella library <string.h> c’è la funzione strlen(char *s) che qui viene rifatta col nome lung():
int lung(char s[]) {
int k=­1;
while (s[++k]); // Si ferma quando s[k]==0 = fine stringa
return k; // Indice fine stringa = lunghezza stringa
} La funzione strcmp(char *s1, char *s2) fa il confronto lessicografico tra le due stringhe s1 ed s2 e restituisce un valore
<0 se s1<s2, =0 se s1=s2 e >0 se s1>s2. Qui viene rifatta col nome confr() (che restituisce rispettivamente –1, 0 e 1):
int confr(char s1[], char s2[])
{
int k=0;
while (s1[k] == s2[k] && s1[k]) k++;
if (s1[k]>s2[k]) return 1;
if (s1[k]<s2[k]) return ­1;
return 0;
}
La funzione strcmpi(char *s1, char *s2) fa il confronto lessicografico tra le due stringhe s1 ed s2 senza considerare significativa la differenza maiuscolo­minuscolo e restituisce un valore ancora
<0 se s1<s2, =0 se s1=s2 e >0 se s1>s2. Qui viene rifatta col nome confr_i() (nella quale la distinzione tra maiuscolo e minuscolo viene eliminata trasformando con la funzione toupper() tutti i caratteri alfabetici in maiuscolo):
int confr_i(char s1[], char s2[])
{
int k=0;
while (toupper(s1[k])==toupper(s2[k]) && s1[k]) k++;
if (toupper(s1[k])>toupper(s2[k])) return 1;
if (toupper(s1[k])<toupper(s2[k])) return ­1;
return 0;
}
La funzione strcpy(char *s1, char *s2) fa sulla stringa s1 una copia della stringa s2. Qui viene rifatta una funzione analoga col nome copia():
void copia(char s1[], char s2[])
120
{
int i=0;
do { s1[i] = s2[i]; } while (s2[i++]);
}
La funzione strcat(char *s1, char *s2) appende alla fine della stringa s1 una copia della stringa s2 (s2 rimane invariata). Qui viene rifatta la funzione col nome concat():
void concat(char s1[], char s2[])
{
int k1=­1,k2=0;
while (s1[++k1]); // si esce quando s1[k1] == fine stringa do { s1[k1++] = s2[k2]; } while (s2[k2++]); }
La funzione char *strstr(char *s1, char *s2) verifica se la stringa s2 è contenuta nella stringa s1 e, in caso affermativo restituisce un puntatore al carattere di s1 a partire dal quale è contenuta la stringa s2; in caso negativo restituisce un puntatore NULL (=0). Qui viene rifatta una funzione analoga, col nome posiz(), ma che restituisce come risultato l’indice del carattere di s1 a partire dal quale è contenuta la stringa s2; in caso negativo restituisce un valore negativo (­1). int posiz(char s1[], char s2[])
{
int i,j,l1=­1,l2=­1;
while (s1[++l1]); while (s2[++l2]); l1 ­= l2;
for (i=0; i<=l1; i++) if (s1[i] == s2[0]) { j=1; while (j<l2 && s1[i+j] == s2[j]) j++;
if (j==l2) return i; } return ­1; }
Seguono ora altre tre funzioni­esercizi sulle stringhe che non fanno parte della library <string.h>.
La funzione cancel() cancella nella stringa s num caratteri a partire da pos (o eventualmente meno se da pos alla fine di s ce ne sono meno)
void cancel(char s[], int pos, int num)
121
{
int k=­1;
while (s[++k]);
num += pos;
k = (num<k) ? num : k;
do { s[pos++] = s[k];
} while (s[k++]);
}
La funzione copy() copia num caratteri di s2 a partire da pos su s1. Se ne copiano meno se da pos alla fine di s2 ce ne sono meno di num.
void copy(char s1[], char s2[], int pos, int num)
{
int i=0;
num += pos; while (s2[pos] && pos<num) s1[i++] = s2[pos++];
s1[i] = '\0';
}
La funzione insert() inserisce nella stringa s1, a partire dalla posizione pos, i caratteri della stringa s2 (senza verifiche di alcun tipo).
void insert(char s1[], char s2[], int pos)
{
int i=­1,k=­1;
while (s1[++i]); while (s2[++k]); for (; i>=pos; i­­) s1[i+k] = s1[i]; for (i=0; i<k; i++) s1[pos++] = s2[i]; }
Esempio di utilizzo delle funzioni precedenti.
Supponiamo di dover fare delle lettere personalizzate per i clienti di una certa ditta. Possiamo preparare la lettera come file di testo mettendo 4 $ nei punti in cui deve andare il nome del cliente. Prendiamo una riga della lettera e mettiamola in una stringa dichiarata come char riga[81]; supponiamo che nome e cognome del cliente siano contenuti nella stringa char nome[36]; se in riga è contenuta la stringa ”$$$$” la cancelliamo e dove si trovava andiamo a inserire la stringa nome. Segue un pezzo di programma che, supponendo di avere già in riga e nome i dati suddetti, se è il caso fa l’operazione suddetta.
char riga[81], nome[36], k;
. . .
k=posiz(riga,”$$$$”);
if (k>=0) // Se k>=0 $$$$ è presente
{ 122
cancel(riga,k,4); // Si cancella la sottostringa ”$$$$”
insert(riga,nome,k); // Si inserisce nome al posto di ”
}
123