Introduzione alle Stringhe in Linguaggio C

In questa lezione introduttiva inizieremo a studiare il concetto di stringa in linguaggio C. Le stringhe sono una parte fondamentale della programmazione e sono utilizzate per rappresentare sequenze di caratteri.

Ci concentreremo sulle stringhe letterali, e su come il C rappresenti internamente una stringa.

A partire dalla prossima lezione, invece, vedremo come definire e manipolare le variabili stringa.

Stringhe Letterali

Una stringa letterale, in linguaggio C, è una sequenza di caratteri racchiusi tra apici doppi. Ad esempio:

"Salve, come si va?"

Abbiamo già incontrato spesso le stringhe letterali nel corso delle varie lezioni sul linguaggio C. Ad esempio, quando abbiamo usato la funzione printf per stampare a video un messaggio, abbiamo passato alla funzione una stringa letterale come argomento:

printf("Salve, come si va?");

In questa prima lezione sulle stringhe ci concentreremo sulle stringhe letterali, ossia quelle che definiamo direttamente nel nostro codice. A partire dalla prossima lezione vedremo come definire e manipolare stringhe in variabili.

Definizione

Stringa Letterale

Una Stringa Letterale è una sequenza di caratteri racchiusa tra apici doppi.

Una stringa letterale può essere definita direttamente nel codice sorgente. La sintassi per definire una stringa letterale è la seguente:

"stringa"

Caratteri di Escape

Così come per i char, anche le stringhe possono contenere al proprio interno dei caratteri di escape. I caratteri di escape sono sequenze di caratteri che iniziano con il carattere \ (backslash). Ad esempio, il carattere \n rappresenta un carattere di nuova riga. Ecco alcuni esempi di utilizzo di caratteri di escape all'interno di una stringa:

"Questa è una stringa con un carattere di nuova riga\n"
"Questa\nè\nuna\nstringa\nsu\npiù\nrighe\n"
"Questa è una stringa con un carattere di tabulazione\t"

I caratteri di escape sono utili per formattare le stringhe in modo da renderle più leggibili o per inserire caratteri speciali che non possono essere digitati direttamente da tastiera.

Definizione

Le stringhe letterali possono contenere caratteri di escape

Una stringa letterale può contenere al proprio interno dei caratteri di escape. I caratteri di escape sono sequenze di caratteri che iniziano con il carattere \ (backslash).

Stringhe letterali spezzate

Quando si inseriscono delle stringhe letterali nel proprio codice, può sorgere l'esigenza di dover spezzarle per motivi di leggibilità. Ad esempio, potrebbe essere necessario dover scrivere una stringa letterale su più righe.

Un primo metodo, in linguaggio C, per spezzare una stringa letterale su più righe è quello di utilizzare il carattere di escape \ alla fine di ogni riga. Ad esempio:

printf("Nel mezzo del cammin di nostra vita\n\
mi ritrovai per una selva oscura\n\
ché la diritta via era smarrita.");

La regola è che nessun carattere deve seguire il carattere di escape \. In questo modo, il compilatore sa che la stringa continua sulla riga successiva. Non possiamo usare nemmeno lo spazio o il tabulatore dopo il carattere di escape.

Lo svantaggio di questa regola è che la riga successiva deve iniziare necessariamente iniziare con il continuo della stringa. Ciò potrebbe rappresentare un problema. Non possiamo, infatti, scrivere questo codice:

/* ERRORE */
printf("Nel mezzo del cammin di nostra vita\n\ 
        mi ritrovai per una selva oscura\n\ 
        ché la diritta via era smarrita.");

In questo caso abbiamo indentato, per motivi di leggibilità il codice, ma il compilatore non riconoscerà la stringa come spezzata correttamente.

Esiste, tuttavia, una seconda tecnica per spezzare le stringhe letterali. In pratica, il compilatore riconosce come unica stringa più stringhe letterali se esse sono separate esclusivamente da spazi o tabulazioni. Ad esempio le due stringhe seguenti sono considerate come una sola stringa:

"Ciao, "     "come si va?"

In tal caso il compilatore considera le due stringhe come una singola stringa:

"Ciao, come si va?"

Pertanto, possiamo scrivere la printf di sopra sfruttando questa tecnica e indentando il codice:

printf("Nel mezzo del cammin di nostra vita "
       "mi ritrovai per una selva oscura "
       "ché la diritta via era smarrita.");

In questo modo, la stringa è spezzata su più righe, ma il compilatore la considera come una singola stringa.

Definizione

Stringhe letterali spezzate

In linguaggio C è consentito spezzare una stringa letterale in più stringhe in due modi:

  1. Si può spezzare una stringa letterale su più righe utilizzando il carattere di escape \ alla fine di ogni riga.

    Il vincolo è che nessun carattere deve seguire il carattere di escape \ e la riga successiva deve iniziare necessariamente iniziare con il continuo della stringa.

    La sintassi è:

    "Parte 1 della stringa\
    Parte 2 della stringa\
    Parte 3 della stringa"
    
  2. Si può spezzare una stringa letterale in più stringhe se esse sono separate esclusivamente da spazi o tabulazioni.

    La sintassi è:

    "Parte 1 della stringa"     "Parte 2 della stringa"     "Parte 3 della stringa"
    

    In questo caso, il compilatore considera le stringhe come una singola stringa.

Rappresentazione interna delle stringhe

Finora abbiamo adoperato le stringhe, in particolare le stringhe letterali, inconsapevolmente passandole a funzioni come la printf e la scanf. Quello che ci domandiamo, adesso, è capire esattamente cosa significa passare come argomento una stringa ad una funzione.

Per poterlo capire, tuttavia, dobbiamo prima capire come il linguaggio C rappresenta internamente le stringhe.

Nella sua essenza, una stringa in linguaggio C è un array di caratteri char. Quando il compilatore trova una stringa letterale di una certa lunghezza n, riserva un'area di memoria di lunghezza n + 1 byte. Quest'area di memoria conterrà i caratteri della stringa e un carattere speciale chiamato terminatore.

Lo scopo del carattere terminatore è quello di segnalare la fine della stringa. In linguaggio C, il carattere terminatore è uno zero binario che può essere rappresentato con la sequenza di escape \0. Questo carattere è sempre presente alla fine di una stringa e non fa parte del testo della stringa stessa.

Definizione

Rappresentazione interna delle stringhe

In linguaggio C, una stringa è rappresentata internamente come un array di caratteri char.

L'array contiene i caratteri della stringa e un carattere speciale chiamato terminatore. Il terminatore è uno zero binario rappresentato dalla sequenza di escape \0.

Chiariamo con un esempio. Supponiamo di inserire nel nostro codice la stringa letterale "ciao" composta da 4 caratteri. Internamente essa verrà memorizzata come un array di 5 caratteri così composto:

+-----+-----+-----+-----+-----+
| 'c' | 'i' | 'a' | 'o' | '\0'|
+-----+-----+-----+-----+-----+

Detto questo, ne consegue che la stringa vuota "" è composta da un solo carattere, il terminatore \0:

+-----+
| '\0'|
+-----+

Dal momento che le stringhe letterali sono memorizzate come array di caratteri, il compilatore le tratta come puntatori di tipo char *. Pertanto, le funzioni printf e scanf si aspettano come argomenti dei puntatori di tipo char * e non delle stringhe.

Quando invochiamo la printf in questo modo:

printf("Salve, come si va?");

in realtà stiamo passando alla funzione un puntatore al primo carattere della stringa letterale "Salve, come si va?". La printf leggerà i caratteri a partire da quel puntatore fino a trovare il terminatore \0.

Definizione

Le stringhe letterali sono trattate come puntatori

Le stringhe letterali sono memorizzate come array di caratteri e trattate come puntatori di tipo char *. Quando passiamo una stringa letterale come argomento ad una funzione, stiamo passando un puntatore al primo carattere della stringa.

Nota

Non confondere il carattere terminatore '\0' con il carattere '0'

Il carattere terminatore '\0' è uno zero binario, ossia corrisponde al codice ASCII 0.

Non si confonda con il carattere '0' che corrisponde al codice ASCII 48 ed è un carattere del tutto diverso.

Operazioni sulle Stringhe Letterali

In generale, possiamo adoperare le stringhe letterali ovunque il linguaggio C richieda un puntatore a caratteri char *.

Possiamo, oltre a passare le stringhe letterali come argomenti alle funzioni, assegnarle a variabili di tipo char *:

char *saluto = "Salve, come si va?";

L'assegnamento, in questo caso, non copia la stringa letterale nella variabile saluto, ma assegna alla variabile saluto l'indirizzo del primo carattere della stringa letterale.

Dal momento che le stringhe letterali sono array di caratteri possiamo anche usare l'indicizzazione per accedere ai singoli caratteri:

char c;
c = "Salve, come si va?"[3];  // c contiene 'v'

In questo caso, stiamo assegnando alla variabile c il valore del quarto carattere della stringa letterale "Salve, come si va?", ossia il carattere 'v'.

Questa proprietà delle stringhe letterali non viene usata molto spesso. Esistono situazioni in cui può essere utile. Ad esempio, supponiamo di voler scrivere una funzione che, preso in ingresso un intero che rappresenta il giorno della settimana, restituisca un carattere che rappresenta l'iniziale del giorno.

Potremmo scrivere questa funzione usando uno switch, in questo modo:

char giorno_iniziale(int giorno) {
    switch (giorno) {
        case 1: return 'L';
        case 2: return 'M';
        case 3: return 'M';
        case 4: return 'G';
        case 5: return 'V';
        case 6: return 'S';
        case 7: return 'D';
        default: return '?';
    }
}

Oppure, potremmo scrivere una funzione che restituisce l'iniziale del giorno usando, in modo più compatto, una stringa letterale:

char giorno_iniziale(int giorno) {
    if (giorno < 1 || giorno > 7) return '?';
    return "LMMGVSD"[giorno - 1];
}
Nota

Una stringa letterale è in sola lettura

Sebbene le stringhe letterali siano memorizzate come array di caratteri, se si prova a modificarle si può provocare un comportamento indefinito.

Ad esempio, il codice che segue è errato:

char *saluto = "Salve, come si va?";
// ERRORE: tentativo di modifica di una stringa letterale
saluto[0] = 'C';

Un programma che prova a modificare una stringa letterale potrebbe andare in crash o comportarsi in modo imprevedibile.

Nella prossima lezione vedremo come definire e manipolare stringhe in variabili.

Stringhe Letterali e Caratteri Costanti

Concludiamo questa lezione con un'ultima importante osservazione.

Spesso, gli sviluppatori inesperti confondono le stringhe letterali composte da un singolo carattere con i caratteri costanti. Ad esempio, la stringa letterale "A" è diversa dal carattere costante 'A'.

Il fatto è che la stringa letterale "A" è un array di due caratteri: 'A' e '\0'. Si tratta in tutto e per tutto ad un puntatore di tipo char * che punta ad una locazione di memoria.

Il carattere a è, invece, un'intero che rappresenta il codice ASCII del carattere A.

Quindi, bisogna prestare attenzione quando una funzione richiede come parametro una stringa, ossia char *, oppure quando la funzione richiede un singolo carattere, ossia char.

Ad esempio, la seguente invocazione di printf è corretta:

printf("A");

Del resto la printf si aspetta come primo parametro un puntatore a caratteri char *.

Viceversa, il seguente codice è errato:

/* ERRORE */
printf('A');
Nota

Non confondere stringhe composte da un singolo carattere con caratteri costanti

Le stringhe composte da un singolo carattere sono array di due caratteri: il carattere stesso e il terminatore '\0'.

I caratteri costanti sono interi che rappresentano il codice ASCII del carattere.

In Conclusione

Questa lezione rappresenta il punto di partenza per lo studio delle stringhe in linguaggio C.

Abbiamo imparato che una stringa letterale è una sequenza di caratteri racchiusa tra apici doppi. Abbiamo visto che le stringhe letterali possono contenere caratteri di escape e possono essere spezzate su più righe.

Cosa fondamentale, abbiamo capito che le stringhe letterali sono trattate come puntatori di tipo char * e che sono memorizzate come array di caratteri con un carattere terminatore '\0'.

Tuttavia, ci siamo concentrati sulle stringhe letterali che inseriamo direttamente nel codice. Nella prossima lezione vedremo come definire e manipolare le variabili stringa.