Le stringhe in C

  1. Dichiarare una stringa
  2. Mostrare in output una stringa
  3. Leggere in input una stringa
  4. Leggere in input più stringhe
  5. Domande & risposte
  6. Accedere ai caratteri di una stringa
  7. Argomenti da riga di comando
  8. La libreria <ctype.h>
    1. Funzioni per la classificazione dei caratteri
    2. Le funzioni di case mapping
  9. La libreria <string.h>

Nei linguaggi di programmazione, i testi, le sequenze di caratteri, sono comunemente chiamati stringhe.

Dichiarare una stringa

Vediamo un primo esempio di programma, in cui semplicemente, dichiariamo una variabile stringa:

#include <stdio.h>
#define STR_LEN 20

int main()
{
    char nome[STR_LEN + 1] = "Angelo";
    return 0;
}

Quello qui sopra è una soluzione corretta.

Alcune osservazioni:

char nome[STR_LEN + 1] = "Angelo";

equivale a:

char nome[STR_LEN + 1] = {'A', 'n', 'g', 'e', 'l', 'o', '\0'};

Solo che, per noi esseri umani, la prima istruzione è un po’ più semplice da scrivere.

char nome[STR_LEN + 1] = 'Angelo'; // SBAGLIATO!
// Di queste 50 's', solo le prime STR_LEN saranno salvate in `nome'
char nome[STR_LEN + 1] = "ssssssssssssssssssssssssssssssssssssssssssssssssss";

Mostrare in output una stringa

Per mostrare in output una stringa si usa la solita funzione printf(), con lo specificatore di conversione %s:

#include <stdio.h>
#define STR_LEN 20

int main()
{
    char nome[STR_LEN + 1] = "Angelo";
    printf("Ciao, %s!\n", nome);
    return 0;
}

Leggere in input una stringa

Per leggere in input una stringa, la tentazione è di usare la funzione scanf():

#include <stdio.h>
#define STR_LEN 20

int main()
{
    char nome[STR_LEN + 1];
    printf("Come ti chiami? ");
    scanf("%s", &nome);
    printf("Ciao, %s!\n", nome);
    return 0;
}

Il codice qui sopra è corretto, ma ha due importanti limitazioni, per le quali l’utilizzo della funzione scanf() per la lettura di stringhe è fortemente sconsigliato!

Come ti chiami? Angelo Sala
Ciao, Angelo!

In sintesi: la funzione scanf non è adatta a leggere stringhe. Dobbiamo ricorrere a un’altra soluzione. Eccola qui:

#include <stdio.h>
#define STR_LEN 20

int main()
{
    char nome[STR_LEN + 1];

    printf("Come ti chiami? ");

    int len = 0;
    char c = getchar();

    while(len < STR_LEN && c != '\n')
    {
        nome[len] = c;
        len += 1;
        c = getchar();
    }

    nome[len] = '\0';

    printf("Ciao, %s!\n", nome);
    return 0;
}

Focalizziamoci sul codice che, effettivamente, si occupa di leggere una stringa in input e di salvarla nella variabile nome:

    int len = 0;
    char c = getchar();

    while(len < STR_LEN && c != '\n')
    {
        nome[len] = c;
        len += 1;
        c = getchar();
    }

    nome[len] = '\0';

Per leggere correttamente, in maniera sicura, senza sorprese, l’input, si utilizza la funzione getchar().

getchar() legge un singolo carattere dall’input: per questo è spesso usata in combinazione con un ciclo while. Di solito, il ciclo è interrotto se:

Nota bene: quando si usa getchar() il carattere speciale di terminazione di una stringa \0 non è scritto automaticamente nella stringa: esso deve essere aggiunto manualmente.

Confrontiamo i risultati di getchar() e scanf(), a seconda dell’input fornito. Si assume una lunghezza massima di 20 caratteri per la stringa:

Stringa di input Stringa salvata usando getchar() Stringa salvata usando scanf()
Angelo Angelo Angelo
Angelo Sala Angelo Sala Angelo
Angelo Alessandro Sala Angelo Alessandro Sa Angelo Alessandro Sala

Leggere in input più stringhe

Sembra tutto chiaro, ma ci sono degli accorgimenti da considerare. Soprattutto quando il nostro programma deve leggere più stringhe! Consideriamo il seguente programma:

#include <stdio.h>
#define STR_LEN 10

int main()
{
    char nome[STR_LEN + 1], città[STR_LEN + 1];

    printf("Ciao! Come ti chiami? ");

    int len_nome = 0;
    int c = getchar();

    while (len_nome < STR_LEN && c != '\n')
    {
        nome[len_nome] = c;
        len_nome += 1;
        c = getchar();
    }

    nome[len_nome] = '\0';

    printf("Dove vivi? ");

    int len_città = 0;
    c = getchar();

    while (len_città < STR_LEN && c != '\n')
    {
        città[len_città] = c;
        len_città += 1;
        c = getchar();
    }

    città[len_città] = '\0';

    printf("\nCiao %s!\n", nome);
    printf("Vivi a %s\n", città);

    return 0;
}

Il programma fa questo:

  1. chiede all’utente di inserire il suo nome
  2. chiede all’utente di inserire il nome del paese in cui vive
  3. mostra in output il nome dell’utente e la città in cui vive.

Insomma, ci piacerebbe avere un input/output di questo tipo:

Ciao! Come ti chiami? Angelo
Dove vivi? Osnago

Ciao Angelo!
Vivi a Osnago

Ed effettivamente, inserendo in input le stringhe Angelo e Osnago, entrambe più corte della massima lunghezza accettata dal programma, 10. Tutto funzionerebbe a meraviglia.

E se… inserissi un nome più lungo di 10 caratteri? Ops!:

Ciao! Come ti chiami? Angelo Alessandro
Dove vivi?
Ciao Angelo Ale!
Vivi a ssandro

Angelo Alessandro è lungo 17 caratteri, e questo rovina tutto: il programma neanche lascia il tempo di rispondere alla domanda: Dove vivi?, e mostra subito in output Vivi a ssandro.

Che cosa è successo? Cosa abbiamo sbagliato? 🙁

Quando abbiamo riempito la stringa nome, abbiamo lasciato i rimanenti caratteri inseriti dall’utente prima di \n. Perciò, la funzione getchar(), quando utilizzata per leggere il nome del paese in cui vivo, trovando già dei caratteri in input (quelli rimasti d’avanzo prima!) ha letto quelli, e finito, dal suo punto di vista, il suo compito.

Okay, tutto chiaro: come risolvere però?

Ogni volta che si finisce di leggere una stringa in input, perché è stata raggiunta la lungheza massima, bisogna consumare tutti gli eventuali caratteri rimasti in input.

In sintesi, basta aggiungere queste due righe di codice dopo ogni lettura dell’input:

if (c != '\n')
    while (getchar() != '\n');

Il nostro programma, corretto, diventerebbe questo:

#include <stdio.h>
#define STR_LEN 10

int main()
{
    char nome[STR_LEN + 1], città[STR_LEN + 1];

    printf("Ciao! Come ti chiami? ");

    int len_nome = 0;
    int c = getchar();

    while (len_nome < STR_LEN && c != '\n')
    {
        nome[len_nome] = c;
        len_nome += 1;
        c = getchar();
    }

    nome[len_nome] = '\0';

    if (c != '\n')
        while (getchar() != '\n');

    printf("Dove vivi? ");

    int len_città = 0;
    c = getchar();

    while (len_città < STR_LEN && c != '\n')
    {
        città[len_città] = c;
        len_città += 1;
        c = getchar();
    }

    città[len_città] = '\0';

    if (c != '\n')
        while (getchar() != '\n');

    printf("\nCiao %s!\n", nome);
    printf("Vivi a %s\n", città);

    return 0;
}

Domande & risposte

Prof., cosa succede se dichiaro una stringa lunga, ad esempio, 20 caratteri, ma poi in essa ne salvo solo 6? Cosa c’è scritto nelle rimanenti 14 posizioni?

Ottima domanda. Se dichiaro una variabile stringa lunga 20 caratteri (STR_LEN + 1 == 20), e poi in essa salvo la parola Angelo, lunga soli 6 caratteri, la stringa sarà così formata:


Prof., coi suoi metodi può capitare che, ad esempio, come nome di una persona si salvi Angelo Ass, che non è bellissimo come nome da salvare. Posso fare in modo che, se l’utente prova a inserire un nome, ma questo è troppo lungo, gli si mostra un avviso, o altro?

Certamente. Un ottimo esercizio! Qui sotto ti mostro un programma che chiede all’utente di insierire il suo nome. Se il nome è troppo lungo per stare nella variabile stringa, si mostra all’utente un avviso, e si ri-chiede di inserire il nome.

#include <stdio.h>
#include <stdbool.h>
#define STR_LEN 10

int main()
{
    char nome[STR_LEN + 1];
    bool nome_letto_correttamente = false;

    while (nome_letto_correttamente == false)
    {
        printf("Ciao! Come ti chiami? ");

        int len = 0;
        char c = getchar();

        while (len < STR_LEN && c != '\n')
        {
            nome[len] = c;
            len += 1;
            c = getchar();
        }

        nome[len] = '\0';

        if (c == '\n') // il nome è stato letto per intero: abbiamo raggiunto la fine della riga!
            nome_letto_correttamente = true;
        else // rimangono dei caratteri in input, quindi il nome non è stato letto per intero!
        {
            // 1. consumiamo i caratteri rimasti
            while (getchar() != '\n')
                ;

            // 2. avvisiamo l'utente che qualcosa è andato storto
            printf("ATTENZIONE! Posso leggere nomi lunghi al massimo %d caratteri. Riprova.\n", STR_LEN);

            // 3. facciamo in modo che tutto il codice sia ri-eseguito!
            nome_letto_correttamente = false;
        }
    }

    printf("\nCiao, %s!\n", nome);

    return 0;
}

Un esempio dell’input/outut di questo programma:

Ciao! Come ti chiami? Angelo Alessandro
ATTENZIONE! Posso leggere nomi lunghi al massimo 10 caratteri. Riprova.
Ciao! Come ti chiami? Angelo

Ciao, Angelo!

Accedere ai caratteri di una stringa

Una stringa è un vettore di caratteri. Perciò, come per qualsiasi altro vettore, possiamo accedere ai singoli caratteri di una stringa usando l’indicizzazione.

Supponiamo di voler contare quanti spazi vuoti (caratteri ' ') sono presenti in una stringa s, di lunghezza len. Il nostro algoritmo apparirà così:

#include <stdio.h>
#define MAX_LEN 100

int main()
{
    char s[MAX_LEN + 1];
    int len = 0;

    /*
    Qui va il codice che legge dei caratteri in input e li salva in 's',
    aumentando opportunamente il valore di 'len'
    */

    int conta = 0;

    for(int i = 0; i < len; i++)
        if(s[i] == ' ') // <-- QUI accediamo al signolo carattere di 's' in posizone 'i'
            conta += 1;

    printf("Numero di spazi salvati nella stringa: %d\n", conta);

    return 0;
}

Argomenti da riga di comando

Ho deciso di rinviare questo argomento a data da destinarsi!

La libreria <ctype.h>

La libreria <ctype.h> fornisce due tipi di funzioni:

Funzioni per la classificazione dei caratteri

Alcune funzioni della libreria <ctype.h> ricevono come parametro un singolo carattere e restituiscono un valore di verità (true oppure false) a seconda che quel carattere abbia la caratteristica richiesta dalla funzione, oppure no. Tra tali funzioni, è bene conoscere queste:

Funzione Utilizzo
isalnum(c) Restituisce true se c è un carattere alfanumerico, altrimenti false
isalpha(c) Restituisce true se c è un carattere alfabetico, altrimenti false
isdigit(c) Restituisce true se c è un carattere che rappresenta una cifra decimale, altrimenti false
ispunct(c) Restituisce true se c è un segno di punteggiatura, altrimenti false
isupper(c) Restituisce true se c è una lettera maiuscola, altrimenti false
islower(c) Restituisce true se c è una lettera minuscola, altrimenti false

Le funzioni isupper(c) e islower(c) danno sicuramente come risultato false se il carattere non è una lettera.

Le funzioni appena riportate sono ottime da usare come condizioni, ad esempio nei blocchi if/else: queste funzioni, infatti, restituiscono solo true oppure false: esattamente ciò che i blocchi if/else si aspettano!

Ad esempio, se vogliamo sapere quante sono le lettere maiuscole e quante sono le lettere minuscole presenti in una stringa, possiamo procedere così:

#include <stdio.h>
#include <ctype.h> // RICORDATI DI INCLUDERE QUESTO!
#define MAX_LEN 100

int main()
{
    char s[MAX_LEN + 1];
    int len = 0;

    /*
    Qui va il codice che legge dei caratteri in input e li salva in 's',
    aumentando opportunamente il valore di 'len'
    */

    int maiuscole = 0, minuscole = 0;

    for(int i = 0; i < len; i++)
        if(isupper(s[i])) // 's[i]', se ci pensi, è un singolo carattere ✅
            maiuscole += 1;
        else if (islower(s[i]))
            minuscole += 1;
        // Se 's[i]' non  è una lettera, non incrementiamo niente

    printf("Numero di maiuscole: %d\n", maiuscole);
    printf("Numero di minuscole: %d\n", minuscole);

    return 0;
}

Le funzioni di case mapping

La funzione toupper(c) restituisce la versione maiuscola della lettera c ricevuta come parametro; la funzione tolower(c) restituisce la versione minuscola della lettera c ricevuta come parametro. Se c non è una lettera, queste due funzioni restituiscono c così com’è, senza modificare nulla.

La libreria <string.h>

La libreria <string.h> contiene molte funzioni utili per lavorare con le stringhe. Di tutte queste, per ora, a noi ne interessa una soltanto: strlen(s).

La funzione strlen(s) riceve come parametro una stringa s e ne restituisce la lunghezza, ovvero il numero di caratteri presenti in s fino al primo carattere \0.

La funzione strlen(s) è utile, ad esempio, quando si riceve una stringa da riga di comando: per ottenere la lunghezza di tale stringa, occorre strlen.