480 likes | 628 Views
Caratteri e stringhe di caratteri Una ( costante di ) stringa è una qualsiasi sequenza di caratteri racchiusi tra doppi apici, ad es.: “Buon giorno!” .
E N D
Caratteri e stringhe di caratteri Una (costante di) stringa è una qualsiasi sequenza di caratteri racchiusi tra doppi apici, ad es.: “Buon giorno!”. Una stringa è memorizzata come un vettore di caratteri che termina con uno speciale marcatore di fine di stringa, detto carattereNULL e rappresentato dalla sequenza di escape\0. Ad es., la stringa “Buon giorno!” verrebbe rappresentata in memoria come Essa usa 13 locazioni di memoria, e va pertanto dichiarata come char string[13] per riservare uno spazio anche per il carattere NULL, che funge da sentinella per denotare la fine della stringa.
Inizializzazione. La inizializzazione di un vettore di caratteri può essere semplificata scrivendo una dichiarazione del tipo: char codici[] = “prova”; nella quale si usa la stringa prova per inizializzare il vettore codici, di 6 elementi, come indicato in figura. I primi 5 elementi consistono nelle lettere p r o v a, l’ultimo è la sequenza di escape \0 o carattere NULL, che il compilatore C aggiunge automaticamente alla fine di ogni stringa. Dato che una stringa è memorizzata come un vettore di caratteri, i suoi singoli caratteri possono essere immessi, manipolati o messi in uscita usando le tecniche standard di gestione dei vettori, tramite le notazioni di indice o di puntatore.
Funzioni di ingresso e uscita Sebbene, per elaborare una stringa già in memoria, si possa usare sia una funzione di biblioteca sia una scritta dall’utente, l’immissione di una stringa da tastiera o la sua visualizzazione sullo schermo richiedono qualche supporto da funzioni di biblioteca standard. La tabella seguente elenca le funzioni di biblioteca comunemente disponibili per l’ingresso e l’uscita di una stringa, sia carattere per carattere sia come unità completa. Le definizioni delle funzioni indicate in tabella sono contenute nel file d’intestazionestdio.h, che pertanto va incluso in ogni programma che le utilizzi (con la solita direttiva#include <stdio.h>). Le due funzioni scanf()e printf()sono già state usate ampiamente. Vediamo perciò le altre quattro funzioni.
Funzione getchar(). La funzionegetchar()restituisce il prossimo carattere battuto alla tastiera. Ha come intestazione: int getchar() ed è usata per l’ingresso di singoli caratteri in un’istruzione quale car_in = getchar(); Essa memorizza il prossimo carattere battuto nella variabilecar_in, ed equivale alla più lunga scanf(“%c”, &car_in); getchar()non si aspetta che le vengano passati argomenti, e restituisce un dato di tipo intero. Ciò per consentire che venga restituita la sentinellaEOF(End-Of-File) già vista, che ha un codice intero (2610). getchar()risulta utile per immettere in continuazione stringhe di caratteri da file di dati, come vedremo più avanti.
Funzione putchar(). La funzione di uscita corrispondente agetchar()èputchar(), che si aspetta un argomento costituito da un singolo carattere e lo visualizza sullo schermo. Ad es. l’istruzione putchar(‘a’); visualizza sullo schermo la letteraa, ed è equivalente alla più lunga printf(“%c”, ‘a’); Il programma seguente usa le funzionigetchar()eputchar()per “fare l’eco” sullo schermo dei caratteri battuti sulla tastiera. #include <stdio.h> int main() { int car_in; while ((car_in = getchar()) != EOF) putchar(car_in); }
Funzioni gets(), puts(). Queste funzioni trattano le stringhe come unità complete, e sono scritte usando le routine più elementari getchar() e putchar()(che forniscono l’ingresso e l’uscita di singoli caratteri). Un’istruzione quale: gets(messaggio); accetta in continuazione i caratteri digitati da tastiera e li memorizza nel vettore di caratterimessaggio. La memorizzazione dei caratteri nel vettore termina quando si preme il tasto <Invio>. Esso genera un carattere di linea nuova (\n) chegets()traduce in un carattere NULL (\0), scrivendolo come ultimo carattere nel vettore messaggio.
Un’istruzione quale: puts(messaggio); visualizza sullo schermo il vettore di caratterimessaggio, che contiene la stringa immessa. Come mostra la figura,puts()traduce il carattereNULL(\0) in una sequenza di escape di linea nuova (\n), che invia automaticamente al video dopo che la stringa è stata visualizzata.
#include <stdio.h> main() { char messaggio[81]; printf(“Scrivi una stringa:\n”); gets(messaggio); printf(“La stringa appena scritta è:\n”); puts(messaggio); } Il seguente programma Getsputs usagets()eputs()per “fare l’eco” sullo schermo di una stringa (lunga fino a 80 caratteri) immessa da tastiera.
B u o n g ... \n gets() • In esso osserviamo che: • la funzione gets() accetta in continuazione i caratteri digitati da tastiera e li memorizza nel vettore di caratterimessaggio; • la pressione del tasto <Invio> genera un carattere di linea nuova, \n, che gets()interpreta come un fine-carattere; • tutti i caratteri incontrati da gets(), tranne quello di linea nuova, sono memorizzati nel vettore di caratterimessaggio; • prima di restituire un valore, la funzionegets()aggiunge il carattereNULLal gruppo di caratteri memorizzati, come mostra la figura. • il programma usa quindi la funzioneputs()per visualizzare la stringa.
Equivalenza tra puts() e printf().Si tenga presente che, in generale, una chiamata alla funzioneputs()può sempre essere sostituita con una chiamata alla funzioneprintf() • Ad es., l’istruzione • puts(mesaggio) • usata nel programma precedente può essere sostituita con l’istruzione • printf(“%s\n”, messaggio); • nella quale • la sequenza di controllo di conversione %s avverte la funzione che si accederà a una stringa; • la sequenza di escape di linea nuova \n sostituisce la linea nuova generata automaticamente daputs()dopo che la stringa è stata visualizzata.
Non equivalenza tra gets() e scanf().L’equivalenza tra le funzioni di uscitaputs() eprintf()non è sussiste tra le funzioni di ingressogets()escanf(). Ad es., gets(messaggio); e scanf(“%s”, messaggio); non sono equivalenti. • Infatti • gets() legge un gruppo di caratteri fino a quando incontra un carattere di linea nuova. • scanf() legge un gruppo di caratteri fino a quando incontra un carattere di linea nuova oppure uno spazio vuoto. • Perciò, se si tentasse di immettere nel vettore messaggio i caratteri • Questa è una stringa • usando l’istruzione scanf(“%s”, messaggio); solo la parola Questaverrebbe assegnata al vettore.
Per immettere la linea completa usando una chiamata alla funzione scanf() sarebbe necessaria una istruzione del tipo: scanf(“%s %s %s %s”, mess1, mess2, mess3, mess4); che assegna Il fatto che scanf() accetti anche lo spazio vuoto come delimitatore, la rende poco utile per immettere stringhe di dati.
Osservazione se si usa la funzione scanf() per immettere una stringa di dati, davanti al nome del vettore non si usa il carattere &. Dato che, come sappiamo, un nome di vettore è una costante puntatore, equivalente all’indirizzo della prima locazione di memoria riservata per il vettore, messaggio è equivalente a&messaggio[0]. Perciò abbiamo potuto usare la chiamata di funzione scanf(“%s”, messaggio); in luogo di scanf(“%s”, &messaggio[0]);
Manipolazione di stringhe di caratteri Copia carattere per carattere. Le stringhe si possono manipolare usando o le tecniche di elaborazione standard dei vettori o le funzioni di biblioteca standard. Vediamo dapprima come si elabora una stringa carattere per carattere, il che farà capire come sono costruite le funzioni di biblioteca standard e come creare funzioni di biblioteca proprie. Costruiamo una funzione,strcopia(), che copi lastringa2nellastringa1. void strcopia(char string1[], char string2[]) { int i = 0; while (string2[i] != ‘\0’) { string1[i] = string2[i]; ++i; } string1[i] = ‘\0’; return; }
Sebbene questa funzione possa essere abbreviata e scritta in maniera più compatta, essa illustra le caratteristiche principali della manipolazione di stringhe. Nella linea d’intestazione, le due stringhe sono passate a strcopia() come vettori, quindi ciascun elemento distring2è copiato nell’elemento corrispondente distring1fino a che (while) s’incontra il marcatore di fine-stringa NULL(\0), la cui rilevazione fa terminare il ciclowhileche copia gli elementi. Dato che il carattereNULLnon è copiato dastring2astring1, l’ultima istruzione distrcopia()aggiunge astring1un carattere di fine-stringa. Prima di chiamarestrcopia(), si deve allocare spazio sufficiente per il vettorestring1, in modo che possa accogliere gli elementi distring2. Il programma seguente alloca per ciascuno dei due vettori lo spazio di una linea di schermo completa (80 caratteri), più uno per il fine-stringa, dichiara la funzionestrcopia()che si aspetta due vettori di caratteri e la chiama passandole gli indirizzi dei due vettori.
#include <stdio.h> void main(void) { char messaggio[81]; char nuovo_mess[81]; int i; void strcopia(char [], char []); printf("Scrivi una frase:"); gets(messaggio); strcopia(nuovo_mess, messaggio); puts(nuovo_mess); } void strcopia(char string1[], char string2[]) { int i = 0; while (string2[i] != '\0') { string1[i] = string2[i]; ++i; } string1[i] = '\0'; return; }
Immissione di stringhe carattere per carattere. Con la stessa tecnica con cui può esere copiata carattere per carattere, una stringa può essere anche immessa e visualizzata. Ad es., il programma seguente usa la funzione d’ingresso caratteri getchar()per fare immettere una stringa carattere per carattere. #include <stdio.h> void main(void) { char messaggio[81], c; int i; printf("Scrivi una frase:\n"); printf("La frase appena scritta è:\n"); puts(messaggio); } i = 0; while(i<80 && (c = getchar()) != '\n') { messaggio[i] = c; ++i; } messaggio[i] = '\0';
La parte di programma evidenziata in colore sostituisce l’istruzionegets(messaggio)usata nel programma Getsputs. L’istruzionewhilefa leggere i caratteri immessi purché il loro numero sia inferiore a 81 e il carattere restituito dagetchar()non sia quello di linea nuova (\n). Osservazione.Le parentesi attorno all’espressionec = getchar()sono necessarie per assegnare il carattere restituito dagetchar()alla variabilecprima di confrontarlo con una sequenza di linea nuova (\n). In loro assenza, dato che l’operatore di confronto!=ha la precedenza su quello di assegnazione=, l’espressione sarebbe equivalente a c = (getchar() != ‘\n’) Essa confronta il carattere restituito dagetchar()con ‘\n’, e il valore dell’espressione relazionale(getchar() != ‘\n’)è 0 o 1 a seconda chegetchar()abbia ricevuto o no il carattere di linea nuova. Così anche il valore assegnato acsarebbe 0 o 1, a seconda del risultato del confronto.
Funzione scritta dall’utente. Il programma precedente illustra anche un’utile tecnica per sviluppare le funzioni. Le istruzioni evidenziate in colore costituiscono una unità autoconsistente per immettere una linea completa di caratteri da tastiera. Pertanto esse possono essere scorporate damain()e messe insieme in una nuova funzione, chiamatagetlinea(), come illustrato nel programma seguente.
#include <stdio.h> void main(void) { char messaggio[81]; int i; void getlinea(char []); printf("Scrivi una frase:\n"); getlinea(messaggio); printf("La frase appena scritta è:\n"); puts(messaggio); } void getlinea(char strin[]) { int i = 0; char c; while (i < 80 && (c = getchar()) != '\n') { strin[i] = c; ++i; } strin[i] = '\0'; return; }
Una scrittura compatta. Possiamo anche riscriveregetlinea()in modo più compatto assegnando direttamente alle componenti del vettorestrini caratteri restituiti dagetchar(), eliminando la variabile localec. void getlinea(char strin[]) { int i = 0; while (i < 80 && (strin[i++] = getchar()) != '\n') ; strin[i] = '\0'; return; } • Osservazione. L’istruzione di assegnazione • (strin[i++] = getchar()) • assegna il carattere restituito dagethcar()direttamente al vettorestrin, quindi • incrementa il subscrittoiusando l’operatore postfisso++.
la successiva istruzioneNULL(;) soddisfa l’esigenza che un ciclowhilecontenga almeno una istruzione. • Entrambe le versioni digetlinea()sono sostituti accettabili digets(), e dimostrano la intercambiabilità delle funzioni di biblioteca del C con quelle scritte dall’utente.
Puntatori e funzioni di biblioteca Nel costruire funzioni di gestione delle stringhe risultano particolarmente utili i puntatori: una notazione con puntatori dà luogo a istruzioni più compatte ed efficienti - rispetto agli indici - per accedere ai caratteri individuali di una stringa. Per vedere l’equivalenza tra indici e puntatori quando si accede ai singoli caratteri di una stringa riprendiamo in considerazione la funzionestrcopia(), che copia i caratteri da un vettore a un altro, uno alla volta. void strcopia(char string1[], char string2[]) { int i = 0; while (string2[i] != '\0') { string1[i] = string2[i]; ++i; } string1[i] = '\0'; return; }
Per scriverne una versione con puntatori, apportiamole due modifiche: • Osserviamo che l’espressione • (string2[i] != '\0') • è vera (cioè ha un valore diverso da 0) fin tanto che si leggono i caratteri della stringa diversi da quello finale (\0), mentre • è falsa (cioè vale 0) solo quando si legge il carattere finale. Ma anche l’espressionestring2[i]ha un valore diverso da 0 per tutti i caratteri della stringa tranne l’ultimo, dato che solo\0ha il codice ASCII 0. Perciò si può sostituire l’espressione (string2[i] != '\0') con la più semplice string2[i] e scrivere:
void strcopia(char string1[], char string2[]) { int i = 0; while (string2[i]) { string1[i] = string2[i]; ++i; } string1[i] = '\0'; return; }
2. L’istruzione di assegnazione string1[i] = string2[i]; ha come valore il codice ASCII del carattere via via copiato; esso è uguale a 0 solo dopo che il carattereNULLsia stato assegnato a string1. Perciò possiamo includere l’espressione di assegnazionestring1[i] = string2[i] dentro la parte di test dell’istruzionewhile, scrivendo: void strcopia(char string1[], char string2[]) { int i = 0; while (string1[i] = string2[i]) ++i; return; } In tal modo non è necessario terminare esplicitamente la prima stringa con il carattereNULL, dato che l’assegnazione entro parentesi assicura cheNULLsia copiato dalla seconda stringa nella prima, facendo terminare correttamente il ciclo while.
void strcopia(char *string1, char *string2) { while (*string1 = *string2) { string1++; string2++; } return; } Possiamo adesso convertirestrcopia()dalla notazione con indici a quella con puntatori come segue: Sia nella versione con indici, sia in quella con puntatori, la funzionestrcopia()riceve il nome del vettore passato (ossia l’indirizzo della sua prima locazione). Nella versione con puntatori distrcopia()i due indirizzi passati sono memorizzati rispettivamente negli argomenti puntatorestring1estring2.
La precedente dichiarazione (char *string1, char *string2) indica chestring1estring2sono puntatori che contengono l’indirizzo di un carattere, e fa sì che gli indirizzi passati siano trattati come valori puntatori anziché come nomi di vettori. La dichiarazione è equivalente a: (char string1[], char string2[]) All’interno distrcopia()l’espressione puntatore*string1(”l’elemento il cui indirizzo è instring1”), sostituisce l’espressione con subscritto equivalentestring1[i]. Analogamente per*string2. L’espressione *string1 = *string2 fa sì che l’elemento puntato dastring2sia assegnato all’elemento puntato dastring1.
void strcopia(char *string1, char *string2) { while (*string1++ = *string2++) ; return; } Dato che gli indirizzi di partenza di entrambe le stringhe sono passati astrcopia()e memorizzati rispettivamente instring2estring1, l’espressione*string2si riferisce inizialmente astring2[0], e*string1astring1[0]. Gli incrementi consecutivi ai due puntatori instrcopia()con le istruzionistring1++; estring2++; fanno sì che ciascun puntatore punti al carattere successivo nella rispettiva stringa. Si può effettuare un ultimo cambiamento alla funzione includendo gli incrementi ai puntatori come operatori postfissi nella parte di test dell’istruzionewhile. La forma finale della funzione di copia stringa è allora:
Nell’espressione *string1++ = *string2++non c’è ambiguità, anche se l’operatore di indirezione,*, ha la stessa precedenza di quello di incremento,++, infatti essa fa accedere al carattere puntato prima che il puntatore sia incrementato. Solo dopo il completamento dell’assegnazione*string1++ = *string2++i puntatori sono incrementati per puntare ai caratteri successivi nelle rispettive stringhe. Quasi tutti i compilatori C comprendono nella loro biblioteca standard una funzione di copia stringa, scritta esattamente come la nostra versione con puntatori distrcopia().
Funzioni di biblioteca di stringa. Quasi tutti i compilatori C dispongono di un gruppo di funzioni di biblioteca per: • l’ingresso • il confronto • la manipolazione • l’uscita • di stringhe di caratteri. Un loro elenco è fornito nella tabella seguente:
Esercizio. Inserire gli opportuni commenti nelle righe lasciate vuote del seguente programma, quindi indicare l’uscita prodotta dal programma, senza eseguirlo.
#include <stdio.h> #include <string.h> int main() { char x[] = "Buon compleanno a te"; char y[25]; char z[15]; printf("%s%s\n%s%s\n", "La stringa nel vettore x è: ", x, "La stringa nel vettore y è: ", strcpy(y, x)); strncpy(z, x, 14); z[14] = '\0'; printf("La stringa nel vettore z è: %s\n", z); return(0); }
#include <stdio.h> #include <string.h> int main() { char x[] = "Buon compleanno a te"; /* inizializza il vettore di caratteri x */ char y[25]; /* crea il vettore di caratteri y */ char z[15]; /* crea il vettore di caratteri z */ printf("%s%s\n%s%s\n", "La stringa nel vettore x è: ", x, "La stringa nel vettore y è: ", strcpy(y, x)); strncpy(z, x, 14); /* copia i primi 14 caratteri di x in z (non copia il carattere nullo) */ z[14] = '\0'; printf("La stringa nel vettore z è: %s\n", z); return(0); }
Il programma precedente utilizza le funzioni: • strcpy() per copiare l’intera stringaxnel vettore y • strncpy() per copiare i primi 14 caratteri della stringaxnel vettore z. • Al vettore z viene accodato un carattere NULL (\0) perché la chiamata di strncpy() nel programma non scrive un carattere nullo di terminazione, dato che il 3° argomento è inferiore alla lunghezza della stringa indicata dal 2° argomento.
Esercizio. Inserire gli opportuni commenti nelle righe lasciate vuote del seguente programma, quindi indicare l’uscita prodotta dal programma, senza eseguirlo.
#include <stdio.h> #include <string.h> int main() { char s1[20] = "Happy "; char s2[] = "New Year "; char s3[40] = ""; printf("s1 = %s\ns2 = %s\n", s1, s2); printf("strcat(s1, s2) = %s\n", strcat(s1, s2)); printf("strncat(s3,s1,6) = %s\n", strncat(s3,s1,6)); printf("strcat(s3,s1) = %s\n", strcat(s3,s1)); return(0); }
#include <stdio.h> #include <string.h> int main() { char s1[20] = "Happy "; /* inizializza il vettore di caratteri s1 */ char s2[] = "New Year "; char s3[40] = ""; /* inizializza il vettore di caratteri s3 con la stringa vuota */ printf("s1 = %s\ns2 = %s\n", s1, s2); printf("strcat(s1, s2) = %s\n", strcat(s1, s2)); /* accoda s2 a s1 */ printf("strncat(s3,s1,6) = %s\n", strncat(s3,s1,6)); /* accoda i primi 6 caratteri di s1 a s3; inserisce \0 dopo l’ultimo carattere */ printf("strcat(s3,s1) = %s\n", strcat(s3,s1)); /* accoda s1 a s3 */ return(0); }
Il programma precedente usa due funzioni: strcat() accoda il suo 2° argomento (una stringa) al 1°, che è un vettore di caratteri che contiene una stringa. Il 1° carattere del 2° argomento sostituisce il carattere NULL (\0) che termina la stringa del 1° argomento. Il vettore utilizzato per immagazzinare la prima stringa deve essere sufficientemente grande per contenere: • la prima stringa, • la seconda stringa, • il carattere nullo di terminazione che viene copiato dalla 2^ stringa. strncat() accoda un numero specificato di caratteri dalla 2^ stringa alla 1^. Al risultato viene accodato automaticamente un carattere NULL di terminazione.
Funzioni per la conversione delle stringhe (atof(), atoi(), atol()) La biblioteca di utilità generiche <stdlib.h> del C contiene funzioni che convertono le stringhe formate da numeri in valori interi e in virgola mobile, indicate in Tabella. La funzione atof() restituisce il suo argomento, costituito da una stringa che rappresenta un numero in virgola mobile, convertito in un numero in doppia precisione. Nel caso in cui il valore non possa essere convertito (per esempio, se il primo carattere della stringa non è un numero), il comportamento di atof() sarà indefinito.
Esempio di atof(). Scrivere un programma che converta una stringa in un numero in doppia precisione, e che produca la seguente uscita: La stringa “99.0” convertita in double è 99.00 Il valore convertito, diviso 2 è: 49.500 Un programma che soddisa la richiesta è il seguente: #include <stdio.h> #include <stdlib.h> int main() { double d; d = atof("99.0"); printf("%s%.3f\n%s%.3f\n", "La stringa \"99.0\" convertita in double è ", d, "Il valore convertito, diviso 2 è: ", d/2.0); }
Esempio di atoi(). In modo analogo si comporta la funzione atoi(), che converte in un valore intero il suo argomento costituito da una stringa di cifre che rappresenta un intero. Consideriamo il seguente programma: #include <stdio.h> #include <stdlib.h> int main() { int i; i = atoi("2006"); printf("%s%d\n%s%d\n", "La stringa \"2006\" convertita in int è: ", i, "Il valore convertito, meno 6, è: ", i - 6); }
Esempio di atol(). La funzione atol() restituisce il suo argomento, costituito da una stringa che rappresenta un numero intero lungo, convertito in un valore (intero) lungo. Qualora gli int e i long siano rappresentati entrambi mediante 4 byte, le funzioni atoi() e atol() funzionano in modo identico. Consideriamo il seguente programma: #include <stdio.h> #include <stdlib.h> int main() { long l; l = atol("12345678"); printf("%s%ld\n%s%ld\n", "La stringa \"12345678\" convertita in long è: ", l, "Il valore convertito, diviso 2, è: ", l / 2); }
Funzioni di biblioteca di carattere (ctype.h) Vi sono anche funzioni per operare sui caratteri, alcune delle quali sono elencate nella tabella seguente:
Queste funzioni sono inserite in un file standard di nome ctype.h, per cui per accedere a esse il programma dovrà contenere, prima dimain(), l’istruzione #include <ctype.h> Esempio (toupper(), tolower()). Come primo esempio, consideriamo il programma seguente: #include <stdio.h> int main() { printf("%s%c\n %s%c\n %s%c\n", " u convertito in maiuscolo è ", toupper('u'), "7 convertito in maiuscolo è ", toupper('7'), "L convertito in minuscolo è ", tolower('L')); }
Esso produce la seguente uscita: u convertito in maiuscolo è U 7 convertito in maiuscolo è 7 L convertito in minuscolo è l Esempio (isalpha(), isdigit()). Come altro esempio consideriamo il programma seguente, che chiede in continuazione all’utente di immettere un carattere, quindi stabilisce se esso sia una lettera o una cifra. Il programma contiene un ciclo do-while dal quale si esce quando si digita unaf. Per non costringere l’utente a scrivere lettere minuscole o maiuscole, esso converte tutte le lettere scritte in minuscole.
#include <stdio.h> #include <ctype.h> void main(void) { char car_ingr; do { printf("\nPremi un tasto qualsiasi (f per finire) "); getchar(); /*riceve e ignora il tasto Invio*/ if (isalpha(car_ingr)) /*un valore diverso da 0 è vero*/ printf("Il carattere immesso è una lettera.\n"); else if (isdigit(car_ingr)) printf("Il carattere immesso è una cifra.\n"); else printf("Il carattere non è né lettera né cifra.\n"); } while (car_ingr != 'f'); } car_ingr = getchar(); /*riceve il prossimo carattere*/ car_ingr = tolower(car_ingr); /*converte in minuscolo*/
possono essere fuse nell’unica istruzione: car_ingr = tolower(getchar()); Osserviamo che, poiché le funzioni restituiscono un valore, una funzione può essa stessa essere l’argomento di una funzione (compresa se stessa). Perciò le due istruzioni del programma precedente car_ingr = getchar(); car_ingr = tolower(car_ingr);