A. Veneziani - Alcune considerazioni su elaborazione con stringhe in C#

Pagina creata da Filippo Fazio
 
CONTINUA A LEGGERE
A. Veneziani - Alcune considerazioni su elaborazione con stringhe in C#
Importanza delle stringhe
Nella elaborazione con il calcolatore due sono le grandi famiglie di tipi di dato: i numerici e gli alfanumerici.
Necessità vuole quindi che, se un dato non sia opportunamente rappresentabile in modo adeguato con tipi
numerici, si debba pensare per esso ad una rappresentazione alfanumerica.
Quando si parla di dato alfanumerico esso può essere rappresentato come singoli caratteri (poco pratici per
poter rappresentare serie di dati), vettori di char (ossia sequenze di caratteri), o stringhe.
I vettori di caratteri possono essere considerati una soluzione per rappresentare alcuni dati alfanumerici, ma
in realtà anch’essi soffrono talora di alcune limitazioni operative.
Spesso quando si considera dati di tipo alfanumerico che non abbiano lunghezza unitaria, la soluzione più
frequente ed ovvia è utilizzare un dato di tipo stringa, tipologia di variabile di cui C# è dotato (come del resto
tutti i linguaggi più moderni, quali Basic, C++, Object Pascal).

Stringhe
La stringa in C#, come del resto in altri linguaggi, non è altro che una serie di lunghezza variabile di caratteri,
eventualmente contenente spazi o persino caratteri di ritorno a capo o altri caratteri speciali.
Su tali stringhe è possibile compiere alcune operazioni atte a rendere facile e immediato il processare e
rielaborare dati alfanumerici. Una delle caratteristiche peculiari delle stringhe è di non avere una ampiezza
predefinita, quindi la loro lunghezza è definita al momento e del tutto dinamica. Si può dire che esse siano
variabili che adattano la loro ampiezza al loro contenuto.

Dichiarazione di variabili stringa
Le variabili stringa in C# sono dichiarate tramite la dichiarazione string:
         string s;
essa dichiara una stringa di nome s.
Una variabile stringa non essendo considerata una variabile di tipo base (quali ad esempio bool, char, int,
ecc.) si comporta a tutti gli effetti una variabile oggetto, e all’inizio della sua esistenza essa ha il valore null
(ossia è non istanziata).
Più intuitivamente si può dire che alla variabile s non è stato mai assegnato alcun contenuto e quindi essa
risulta non assegnata (unassigned). Per utilizzare la variabile s, è necessario in C#, come per gli altri tipi di
variabili, darle un valore.
Una variabile stringa può essere inizializzata, come altre al momento della sua dichiarazione:
         string s = “paperino”;
Tipicamente per assegnare qualcosa alla variabile s, si deve utilizzare l’operazione di assegnazione. Si può
assegnare un qualche valore ad s assegnando una costante stringa o passando il valore contenuto in un'altra
variabile stringa oppure concatenando due stringhe.
         s = “pippo”;
oppure
         s = s2; // ove s2 è a sua volta una variabile string
oppure
         s = “pippo” + s2;
che concatena (antepone) la stringa “pippo” al contenuto della stringa s2.
Anche per questo tipo di variabili è possibile effettuare una assegnazione nel momento stesso che la variabile
sia dichiarata, ossia:
         string s = “pluto”;
in analogia con quanto è possibile con variabili di altro tipo.

                                                     Pagina 1
Stringa nulla
Per stringa nulla si intende una stringa senza alcun carattere. Come logico il motivo della presenza di un
simile valore stringa sta nel fatto di ottenere una stringa priva di ogni contenuto.
Per imporre che s sia una stringa nulla si assegna:
         s = “”;
Attenzione di non confondere questo valore (“”) con quello null precedentemente considerato. Il valore null
è tipico di alcuni tipi di variabili, per indicare che non vi è stata ancora alcuna assegnazione oppure nessuna
istanza riguardante la variabile.
La lunghezza di una stringa nulla è pari a 0. La stringa nulla viene usata come valore di “azzeramento” per le
variabili string.

Operatore di concatenazione (+)
L’operatore più usato ed importante sulle stringhe (e già utilizzato sopra) è quello detto di concatenazione,
presente infatti in molti linguaggi oltre a C#. In C# esso è definito dal simbolo +. In realtà questo simbolo
non indica in questo caso somma, ma semmai accostamento e quindi “somma” in senso lato di stringhe, che
vanno a formare un'unica stringa più grande.
E’ possibile quindi avere espressioni del tipo:
        s = “pippo” + “ e “ + “pluto”; // concatenazione di (tre) costanti
oppure
        s = s2 + “pippo”; // concatenazione di un valore costante e di una var. stringa
oppure
        s = s1 + s2; // concatenazione di due variabili stringa
Come ovvio la stringa risultante dalla concatenazione viene assegnata nel nostro caso ad s.

Assegnazione dovuta a lettura su console
Una stringa può essere anche valorizzata tramite una lettura da console. In questo caso si utilizza ancora il
famoso comando Console.ReadLine(), che in realtà rende una una riga di testo, ossia una stringa (la stringa
digitata sulla console, fino alla pressione del tasto Invio).
In questo caso, data la natura del comando, non è necessaria alcuna conversione del dato che può essere
direttamente utilizzato:
         s = Console.ReadLine(); // lettura da console di una stringa
Con la stessa istruzione sarà possibile avere i casi particolari di un input di un solo carattere (stringa di
lunghezza 1), e di input di nessun carattere (stringa di lunghezza 0), in cui quindi s contiene una stringa nulla
(“”).

Lunghezza di una stringa
Una delle caratteristiche che più spesso vengono richieste ad una stringa è conoscere la sua lunghezza.
Questo dato è una delle proprietà dell’oggetto string considerato.
Qualunque variabile stringa, quindi, come oggetto ha una proprietà che rende la sua lunghezza, detta Length:
         l = s.Length;
dove l è una variabile int e s una var. string.
Si noti che le proprietà vanno scritte senza parentesi, in quanto esse rappresentano direttamente un valore
associato all’oggetto (in questo caso la lunghezza della stringa s).

Istruzioni ToUpper e ToLower
Data una stringa è possibile facilmente rendere tutti i suoi contenuti maiuscoli o minuscoli. Ovviamente ciò
per i comuni caratteri alfabetici (A-Z). Tutti gli altri simboli e caratteri rimangono invariati e tali comandi non
hanno effetto su loro.

                                                    Pagina 2
Ad esempio:
         s = “Pippo”;
         s = s.ToUpper();
         Console.WriteLine(s);
rende la stampa
         PIPPO
tutta in maiuscolo.
Il metodo ToLower(), invece converte tutte i caratteri (A-Z) in minuscolo.

switch con valori string
L’istruzione switch in C# può operare anche con valori di tipo stringa. Ciò può dare una maggior flessibilità
alla codifica in alcuni casi.
Ad esempio quindi è possibile effettuare una selezione con casi multipli in base al contenuto di una variabile
stringa:
         switch(s)
         {
                  case “abc”: …..
                          break;

                case “def”: …..
                        break;
                ….
                default: …..
                        break;
        }

La selezione del caso sarà data dall’uguaglianza del valore di s con una delle stringhe riportate come
riferimento (nel nostro caso “abc” e “def”, ecc.).

Confronto fra stringhe
Sebbene potremmo non rendercene conto, operando in informatica, l’insieme delle stringhe è un insieme
ordinato. Ciò vuol dire che è possibile confrontare stringhe e definire un ordine fra loro in base a certi criteri.
C# esegue confronti tra stringhe (e quindi tra contenuti di variabili string) in base a tali criteri, in modo del
tutto automatico.
Gli operatori di confronto applicabili a stringhe sono quelli usuali validi anche per valori numerici:
           ==     uguaglianza
          !=      diversità
Per i confronti riguardanti le operazioni di ordinamento di stringhe vere e proprie, C# non usa i tradizionali
operatori >, < , >=,
Quindi per l’utilizzo in condizioni, bisognerà aggiungere, oltre all’istruzione sopra, operatori di confronto fra
interi:
        if (s1.CompareTo(s2) > 0)
ad esempio, la condizione qui sopra, se vera, comporta che s1 sia maggiore di s2.
Criteri del confronto
Per il confronto tra stringhe, per le stringhe che siano composte solo da caratteri alfabetici tradizionali (a-z,
A-Z), valgono in realtà applicate le regole di ordinamento alfabetico classico:
Quindi ad esempio:
         “albero” == “albero” risulta vero
mentre
         “albero” > “casa”        risulta falso
In quanto albero è alfabeticamente inferiore a casa (in un comune dizionario la parola “casa” è nelle pagine
successive ad “albero”, quindi ha un “valore più alto”).
In modo analogo vanno intesi ed applicati tutti gli altri confronti.
Ci sono però due complicazioni a questa semplice e chiara regola di ordinamento:
      Il computer non considera (a ragione) uguali i caratteri alfabetici maiuscoli e minuscoli
      Le stringhe devono essere tutte confrontabili da qualunque serie di caratteri esse siano costituite
         (quindi non solo quelle scritte in caratteri alfabetici (a-z, A-Z))
In realtà come detto più volte all’inizio dell’anno, il computer trasforma qualunque dato in numero e tratta
come tale lo stesso. Ciò vuol dire che anche i singoli caratteri che compongono una stringa sono codificati
in forma numerica.
Ciò spiega meglio perché ‘a’ non sia uguale ad ‘A’, e così via per gli altri caratteri maiuscoli / minuscoli. ‘A’
ed ‘a’ hanno in effetti due codici ASCII distinti (si veda tabella ASCII a seguire).
Si noti anche che considerata la tabella ASCII (valida anche nel linguaggio C#), mantiene l’ordinamento
alfabetico sia sulle lettere maiuscole, che sulle minuscole, in ambiti separati.
In sostanza i codici delle lettere alfabetiche (A-Z) maiuscole sono progressivi a salire da 65 corrispondente ad
‘A’ a codici subito seguenti corrispondenti a ‘B’, ‘C’ e così via. Analoga cosa per le minuscole con ‘a’
corrispondente al codice 97 e ‘b’, ‘c’ ecc. con codici contigui a seguire.
In realtà alfabeticamente ‘B’ > ‘A’ e così è anche sotto forma di codice. Una considerazione del tutto simile
vale anche sulle lettere minuscole, confrontabili tramite i codici ASCII.
Considerata la codifica ASCII infatti tutte le minuscole sono “maggiori” delle corrispondenti maiuscole, ed
anzi di tutte le maiuscole (si osservi con attenzione la tabella per convincersene).
Ciò comporta una serie di disuguaglianze fra stringhe non del tutto logica secondo l’ordinamento alfabetico
tradizionale (che non distingue tra maiuscole e minuscole):
         “Albero” < “albero”
oppure
         “CASA” < “casa”
oppure
         “casA” < “casa”
oppure
         “ZAPPA” < “albero”
Queste disuguaglianze danno tutte il valore vero (ossia sono tutte vere).
Per la prima disuguaglianza si può considerare solo la prima lettera, che poi è l’unica diversa nelle due
stringhe. In realtà, per procedere al confronto, come nell’ordinamento alfabetico classico, si considera
lettera per lettera, da sinistra a destra, individuando un ordine sul primo carattere, e se risulta uguale, si
procede sul secondo, e se uguale, sul terzo e così via.
E quindi già sul primo carattere di “Albero” confrontato con “albero”, la ‘A’ ha codice 65, mentre ‘a’ ha codice
97, quindi il calcolatore considerando i codici dei caratteri conclude che “Albero” sia già inferiore ad “albero”.
La stessa cosa accade con “CASA” e “casa”, già alla prima lettera ‘C’ (67 ASCII) e ‘c’ (99 ASCII).
                                                       Pagina 4
Se, come nell’esempio successivo, la parola fosse coincidente, e la differenza maiuscolo / minuscolo di una
lettera coinvolgesse una lettera seguente (qui l’ultima), si dovrebbe procedere confrontando fino ad essa
(qui fino all’ultimo carattere), e concludere che la parola “casA” è inferiore a “casa”, sempre per il fatto che
‘A’ < ‘a’.
Ovviamente vi sono dei casi ancora meno chiari per un ordinamento basato su criteri usuali o “umani”:
Ad esempio ci si può chiedere se “$?/+” < “#@*-“
A questo problema non è possibile rispondere se non con i criteri di ordinamento prospettati prima, ossia
basati esclusivamente sui codici ASCII.
Riepiloghiamo quindi i passi della metodica di confronto fra stringhe:
     1. Si parte con il confrontare il primo carattere delle due stringhe
     2. Il criterio del confronto si basa esclusivamente sul codice ASCII del carattere stesso. Il carattere con
           codice ASCII maggiore è considerato superiore a quello con codice ASCII inferiore.
     3. In base a questo è possibile confrontare non solo caratteri dell’alfabeto (a-z, A-Z), ma qualunque
           carattere o simbolo
     4. Se il primo carattere è diverso => una delle stringhe viene considerata superiore all’altra (quella
           avente codice ASCII più alto sul primo carattere) e il confronto termina.
     5. Se il primo carattere coincide, itero il confronto sui caratteri successivi corrispondenti (secondo col
           secondo, terzo col terzo…), ad uno ad uno, finchè non individuo caratteri diversi in base ai quali
           possa individuare la stringa maggiore o minore (vd. punto 4)
     6. Se le due stringhe hanno lunghezza diversa e se tutti i caratteri fino alla fine della stringa più corta
           sono uguali, risulta maggiore quella avente ulteriori altri caratteri (ossia la più lunga)
     7. Se tutti i caratteri sono uguali e le stringa hanno stessa lunghezza concludo che sono effettivamente
           uguali.

                                                   Pagina 5
Caratteri e stringhe di lunghezza 1
Sebbene ci possa essere una iniziale confusione in realtà è bene convincersi che il carattere ‘A’ è diverso
operativamente dalla stringa contenente “A”.
Questo se ci si fa attenzione è già evidenziato dalla differente delimitazione del dato alfanumerico:
     Apice singolo nel caso dei char (un solo carattere)
     Apici doppi nel caso di un valore string (anche se è lungo un solo carattere)
In realtà C#, giustamente, non permette il passaggio di dati da un tipo all’altro in modo implicito o
sottointeso.

Stringhe come vettori
Sebbene formalmente le stringhe siano diverse dai vettori di char, esse sono assimilabili in certi momenti ad
essi. Infatti data una stringa è possibile accedere ai singoli caratteri della stessa in sola lettura, e leggere
carattere per carattere la stringa stessa:
         s =”Natale”;
il codice C#:
         for (i = 0; i < s.Length; i++) Console.Write(s[i]);
stamperà anch’esso la scritta “Natale”, ma leggendo carattere per carattere il contenuto di s.
Si tenga presente che le regole dell’indice per l’accesso ai singoli caratteri di una stringa seguono le regole
già definite per i vettori, vale a dire:
      L’indice corrispondente al primo carattere della stringa è 0
      Per i caratteri intermedi gli indici sono contigui
      L’indice dell’ultimo carattere, dipende dalla lunghezza della stringa stessa e sarà s.Length - 1
In realtà una prima differenza tra stringhe e vettori di char si nota dal fatto che è possibile leggere i singoli
caratteri, ma non è possibile modificarli.
Ad esempio l’operazione:
         s[0] = ‘n’;
non è consentita in C#.
Bisogna anche ricordare ed avere presente che quando si accede ai singoli caratteri di una stringa, con la
tecnica sopra descritta, il tipo di dato che viene estratto è di tipo char, ossia eventuali confronti devono essere
fatti con costanti o variabili di tipo carattere (char) e non string.
Ad esempio se devo controllare che il carattere 2 della precedente stringa non sia ‘X’:
           if ((s[2] != 'x') && (s[2] != 'X'))
               Console.WriteLine("Il carattere di indice 2 non è x o X");

Conversioni da char a string e da string a char
Consideriamo il problema di convertire un singolo carattere in una stringa.
Se:
         string s;
         char ch = ‘A’;
In realtà non è possibile l’assegnazione diretta, in quanto ch è di tipo differente da string:
         s = ch; // è errato
Un modo possibile per inserire ch in s è:
         s = “” + ch;
ossia fare si che ch appaia come elemento concatenato ad una stringa nulla1.
Un altro modo per ottenere ciò è una conversione esplicita del carattere a stringa con il comando (metodo)
ToString():
         s = ch.ToString();

1   Da ciò si deduce che l’operatore + opera anche su dati di tipo char (convertendoli automaticamente in string).
                                                               Pagina 6
In tal caso s conterrà alla fine il singolo carattere ch, ma ovviamente sarà vista come una stringa.
Viceversa per far diventare una stringa di un solo carattere un char, basterà scrivere l’operazione:
         ch = s[0];
che permette di passare il primo (ed ipotizzato unico) carattere della stringa in ch.
Si consideri anche che anche i tipi di dato int, float, double, bool possono essere convertiti al loro equivalente
alfanumerico usando il metodo ToString(). Ad esempio:
         double d;
         …
         d = 134.59;
         s = d.ToString();
ora s contiene la stringa “134.59”2.
Si noti anche che questo tipo di operazioni sono il simmetrico di quelle effettuate con il comando (metodo)
Parse(…), su varie stringhe, in cui da stringa si portava il dato verso int, double, bool ecc.

2Su sistemi Windows italiani la conversione sarà effettuata come “134,59” in quanto il punto di separazione tra interi e cifre
decimali, viene convertito, coerentemente alla lingua corrente, in virgola.
                                                             Pagina 7
Puoi anche leggere