Linguaggio D

Le stringhe

Le stringhe in questo linguaggio sono uno dei pezzi forti, soprattutto se paragonate  quanto vediamo in altri linguaggi, segnatamente il C. La seclta progettuale è stata quella di abbracciare in pieno la codifica UTF, si tratti di UTF 8, 16 o 32 e senza impedire, come specificato da Alexandrescu nel suo testo, l'uso di altre codifiche. Ovviamente i numeri 8, 16, e 32 si riferiscono ai bit utilizzati nella rappresentazione dei singoli caratteri componenti la stringa ed altrettanto ovvio è che una maggior potenza rappresentativa, che si ha con UTF 32, si paga con un maggior uso di memoria. Uno dei punti forti di questo approccio è certamente la conseguente forte propensione alla internazionalizzazione, proprio per la possibilità di rappresentare ogni tipo di carattere. L'alta considerazione per le stringhe in D è forte proprio del fatto che si è deciso di riconoscere un "tipo" per ciascuna delle 3 codifiche presentate. Ovvero si è provveduto a definire tre diversi tipi di carattere per comporre stringhe diverse magari non come aspetto esteriore ma come rappresentazione interna (ovvero: una "a" è sempre quella dal nostro punto di vista che si rappresenti come usando UTF8 o UTF 32 ma nel secondo caso ci sarà un certo spreco di memoria) Abbiamo pertanto la seguente tabella:

Tipo codifica valore iniziale
char UTF 8 0xFF
wchar UTF 16 0xFFFF
dchar UTF 32 0xFFFFFFFF

Tecnicamente parlando ognuno degli elementi di cui parliamo è un unsigned integer. Va detto, il solito Alexandrescu è l'unico che lo sottolinea, è che possono essere memorizzati anche valori inadeguati a rappresentare un carattere. Il nostro compilatore conferma un certo spirito libertino e non fa nulla per forzare una codifica corretta....

Da un punto di vista pratico una stringa è una semplice sequenza di caratteri (anche nulla)  racchiusa in coppia di apici

"ciao"
"123 456"
""


va beh, avete capito....
In pratica quando il compilatore si trova davanti a sequenza di questo tipo internamente dal suo punto di vista si tratta di array di caratteri. Dal momento che "si accorge" che sta lavorando su sequenze Unicode rende disponibili ulteriori bonus che facilitano la vita al programmatore. Quindi char[], wchar[] e dchar[] godono di un trattamento speciale. E da questa integrazione nascono i 3 tipi stringa per eccellenza in D:

- string
- wstring
- dstring


La particolarità più evidente si deve al fatto che in una stringa, diversamente da quanto avviene in un array di char, ogni singolo elemento è dotato di un attributo particolare e precisamente immutable, che rende impossibile la sua modifica a qualunque livello. Insomma ogni stringa è nel complesso composta da elementi immutabili.

  Esempio 8-1
1
2
3
4
5
6
7
8
import std.stdio;
void main()
{
    string s1 = "ciao";
    char[] a1 = ['c''i''a''o'];
    a1[1] = 't'//OK
    s1[1] = 't'//errore
}

La riga 7 non può essere compilata, per quanto detto. Per cambiare un carattere in una stringa è necessario crearne una nuova, in una nuova locazione di memoria attraverso la concatenazione:

string s1 = "ciao";
s1 = s1[0..1] ~ 't' ~ s1[2 .. $];


Questo crea una nuova stringa completamente diversa dalla precedente anche fisicamente, cioè, come detto, crea una nuova entità in un diverso spazio di memoria.

Per ciclare in maniera sicura ed efficiente sugli elementi di una stringa è possibile usare l'istruzione foreach, valida anche sugli array di caratteri.

  Esempio 8-2
1
2
3
4
5
6
7
8
9
import std.stdio;
void main()
{
    auto a = "abcde";
    foreach(c; a)
    {
        writeln(c);
    }
}

Naturalmente sulle stringhe è possibile fare tante belle operazioni, come in ogni linguaggio che si conviene. A questo scopo segnaliamo l'importante libreria std.string che, appunto, reca una imponente collezione di strumenti di lavoro sulle stringhe. Ne vediamo alcune qui di seguito pronte per l'uso:

  Esempio 8-3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import std.stdio, std.cstream, std.string;

void main(string[] args)
{
    string s1 = "ABCD";
    string s2 = "efgh";
    writeln(toLower(s1));
    writeln(toUpper(s2));
    writeln(indexOf(s2, "ef"));
    writeln(indexOf(s2, "ww"));
    writeln(capitalize(s2));
    writeln(chomp(s2,"gh"));
    writeln(isNumeric(s2));
    writeln(countchars(s1,"B"));
    writeln(removechars(s1,"A"));
    string s3 = readln();
    writeln(chop(s3));
    writeln("finito");
    readln();    
}


Riga 7 toLower converte l'intera stringa in minuscolo
Riga 8: toUpper converte l'intera stringa in maiuscola
Riga 9: indexOf restituisce l'indice della prima occorrenza della sottostringa indicata
Riga 10: indexOf restituisce -1 se la sottostringa cercata non è presente nella stringa
Riga 11: capitalize resituisce la stringa col primo carattere maiuscolo e gli altri minuscoli
Riga 12: chomp "mangia" una sottostringa in coda alla stringa. Vedi più avanti chop.
Riga 13: isnumeric indica se la stringa è composta da caratteri numerici
Riga 14: countchars conta le occorrenza di un carattere in una stringa
Riga 15: removechars elimina tutte le occorrenze di un certo carattere in un stringa
Riga 17: chop elimina l'ultimo carattere. In particolare quando si immette una stringa dallo standard input elimina i caratteri carriage return che vengono immessi premendo il tasto invio. Può coincidere con chomp se per quet'ultimo non viene indicata alcuna sottostringa

Sul sito ufficiale troverete vari approfondimenti e ulteriori utili comandi.