next up previous contents index
Next: Funzioni di libreria sulle Up: Array, enumerati, stringhe Previous: Enumerati   Indice   Indice analitico

Stringhe di caratteri

In questo paragrafo introduciamo finalmente le stringhe di caratteri, che d'ora in poi indicheremo semplicemente come ``stringhe''. Sin dall'inizio abbiamo presentato il tipo char, il quale ha come dominio l'insieme dei simboli alfanumerici e alcuni segni di punteggiatura; tuttavia tale tipo permette di fare ben poco, perché è piuttosto raro utilizzare un solo carattere per volta: nasce così l'esigenza di avere delle successioni di caratteri, che noi chiamiamo stringhe. Immaginare un programma di una certa consistenza, che non faccia assolutamente uso di stringhe è ben difficile: ogni programma presenta una interfaccia utente, grafica o testuale, che permette all'utilizzatore del programma di interagire con esso; ebbene, essendo l'utente un essere umano, è ovvio che si troverà a suo agio in un programma, solo se esso parlerà la sua lingua: si provi ad immaginare come sarebbe il vostro programma preferito se, al posto di avere delle stringhe nei menu, avesse degli interi, ad esempio al posto di ``file'' il numero 3, al posto di ``stampa'' il numero 7, e così via: un inferno. Le stringhe non servono solo a creare interfacce utente, ma a mille altri scopi: dati riguardanti persone, nomi dei files, e quanto altro si possa immaginare semplicemente aprendo a caso i programmi in un qualsiasi computer. Le stringhe sono dunque importanti, per la conoscenza di un linguaggio di programmazione; a dispetto di tale importanza, esse sono però difficili da gestire, rispetto ai tipi primitivi, in quanto esse presuppongono una struttura dati articolata piuttosto che una semplice variabile. Il motivo risiede nel fatto che, mentre sappiamo esattamente quanta memoria occupa un int o un double, non possiamo dire nulla riguardo una stringa: se chiediamo all'utente di immettere il suo nome, e vogliamo inserirlo in una stringa, come possiamo sapere se tale utente si chiama ``Leo'' (tre caratteri) o ``Pierfrancescopaolo'' (18 caratteri)?

Il C ed il C++ hanno due approcci molto differenti per quanto riguarda la gestione delle stringhe; come sempre, l'approccio del C è a basso livello e consente una gestione minimale delle stringhe, la quale è facilmente instabile, mentre l'approccio del C++ è orientato agli oggetti stringhe, e permette di gestire semplicemente e in maniera affidabile tale struttura dati. Per un programmatore C++ dunque, la scelta tra quale sistema di gestione delle stringhe utilizzare non si pone affatto; tuttavia il livello di conoscenza del C++ da noi raggiunto fino a tale momento, non ci consente di utilizzare strutture troppo raffinate tipiche del C++, come le stringhe; dovremo dunque studiare le stringhe di carattere à la C, il che non è poi tanto male se si tratta di scrivere programmi non troppo estesi, come i nostri.

In C le stringhe si rappresentano come array di caratteri; prima osservazione: abbiamo detto e ripetuto che la lunghezza di un array non si può modificare, come possiamo dunque stabilire a priori la lunghezza di una stringa? Non è necessario in quanto, come vedremo meglio nel prossimo capitolo, è possibile creare degli array dinamici, i quali hanno sì grandezza costante, ma essa è fissata in fase di esecuzione. In questa maniera, se il nostro tale si chiama ``Leo'', il compilatore provvederà a creare un array lungo 4 caratteri, se si chiama ``Pierfrancescopaolo'' ne avremo uno di 19; una volta fissata la lunghezza di una stringa, essa non può essere modificata, come per tutti gli array. Per ora, comunque, ci accontenteremo di creare delle stringhe, la cui lunghezza sia ragionevolemente adeguata al dato che essa deve rappresentare: ad esempio, per un nome, 20 caratteri probabilmente bastano, 13 sono sicuramente sufficienti per un numero di telefono, 5 per un CAP. Torniamo alla frase precedente: come mai ho detto che ``Leo'' occupa un array di 4 caratteri se tale nome ne ha solo 3? Perché ogni stringa deve terminare con il carattere '\0', che indica la fine della stringa stessa; è molto importante ricordare l'esistenza di tale carattere, altrimenti si sperimenteranno misteriosi errori durante l'esecuzione dei programmi. Gli array di caratteri, o stringhe, corrispondono in definitiva nel linguaggio C (e anche nel C++ quindi) a puntatori a char. A proposito dei vettori, non abbiamo fatto notare, per evitare spiacevoli confusioni, che un array è in realtà un puntatore al suo primo elemento; comunque non ci interessa particolarmente il motivo profondo di tale implementazione, l'importante è sapere che il nome di un array è un puntatore al suo primo elemento, e che un puntatore a char è un array di caratteri, dunque una stringa. Vediamo un esempio:


// ex6_5_1
#include <iostream.h>
void main() {
  char* s = "Ciao!\0";
  char* t = "come va?";   // non c'e` '\0'
  cout << "s: " << s << "\n";
  cout << "t: " << t << "\n";
}

output:
s: Ciao!
t: come va?

In C la sintassi per la creazione di stringhe è, come si vede, molto semplice: basta racchiudere tra doppi apici (`"') la stringa che si vuole puntare; tale sintassi è utilizzabile solo in fase di inizializzazione delle stringhe: non possiamo assegnare ad un array di caratteri una stringa racchiusa tra doppi apici. Notiamo inoltre che, nonostante non abbiamo inserito il carattere di fine stringa in "come va?", il programma non crea problemi in fase di esecuzione: perché? Il motivo è che quando creiamo una stringa tramite doppi apici, il compilatore inserisce automaticamente il carattere '\0' se esso non è presente. Se proviamo infatti a costruire una stringa carattere per carattere, e non inseriamo il carattere '\0' a fine stringa, come nel seguente esempio:


// ex6_5_2
// attenzione il programma e` ERRATO
#include <iostream.h>
void main() {
  char s[5];
  // s = "ciao";  // NO: " " solo per l'inizializzazione
  s[0] = 'c';
  s[1] = 'i';
  s[2] = 'a';
  s[3] = 'o';
  // senza carattere '\0' a fine stringa
  cout << s;
}
può succedere di tutto: instabilità del sistema, stampa di sequenze di caratteri a caso, e simili. Vediamo ora il seguente:

// ex6_5_3
#include <iostream.h>
void main() {
  char s[20];
  cout << "parola (min 7, max 20)? "; cin >> s;
  cout << "seconda lettera: " << s[1] << "\n";
  cout << "terza lettera: " << s[2] << "\n";
  cout << "settima lettera: " << s[6] << "\n";
}

esempio di output:
parola (min 7, max 20)? programmazione
seconda lettera: r
terza lettera: o
settima lettera: m

Essendo le stringhe rappresentate tramite degli array, esse hanno la loro stessa modalità di passaggio come argomenti di funzioni: per indirizzo. Vediamo un semplice esempio:


// ex6_5_4
#include <iostream.h>

// torna la lunghezza di una stringa
int lunghezza (char* s) {
  int i = 0;
  while (s[i] != '\0')
    i++;
  return i;
}

void main() {
  char s[50];
  cout << "? "; cin >> s;
  cout << "lunghezza: " << lunghezza(s);
}

esempio di output:
? informatica
lunghezza: 11

Osservazione importante è che se immettiamo più parole, separate da spazi, solo la prima viene assegnata alla stringa; in seguito vedremo dove vanno a finire le altre parole; ad esempio possiamo avere il seguente output:
? Che bella giornata
lunghezza: 3

ove infatti 3 è la lunghezza di ``Che''. Vediamo infine un programma che ``mischia'' le lettere di una stringa data:


// ex6_5_5
#include <iostream.h>
#include <time.h>
#include <stdlib.h>

const int MAX = 50;

int lunghezza (char* s) {
  int i = 0;
  while (s[i] != '\0')
    i++;
  return i;
}

void copia (char* s, char* t) {
  int i = 0;
  while (t[i] != '\0') {
    s[i] = t[i];
    i++;
  }
  s[i] = '\0';
}

void numeriCasualiUnici (int* v, int max) {
  srand ( time(0) );
  // azzeriamo gli elementi del vettore necessari
  for (int i = 0; i < max; i++)
    v[i] = 0;
  for (int i = 0; i < max; i++) {
    bool ok;
    do {
      int n = rand() % max;
      ok = true;
      for (int j = 0; j < i; j++)
        if (v[j] == n) {
          ok = false;
          break;
        }
      v[i] = n;
    } while (!ok);
  }
}

char* mischia (char* s) {
  int lung = lunghezza(s);
  int v[MAX];
  char temp[MAX]; // stringa ausiliare
  copia (temp, s);
  numeriCasualiUnici(v, lung);
  for (int i = 0; i < lung; i++)
    s[i] = temp[v[i]];
  return s;
}

void main() {
  char s[MAX];
  cout << "? "; cin >> s;
  cout << mischia(s) << "\n";
}

esempio di output:
? Napoleone
eaNploone

ex-2
si scriva un programma che richieda l'immissione di una stringa da parte dell'utente, e ne stampi i primi tre caratteri;
ex-3
si scriva un programma che richieda l'immissione di una stringa di 10 caratteri da parte dell'utente, e ne stampi gli ultimi 5 caratteri;
ex-4
si scriva una funzione che conti il numero di ricorrenze di un dato carattere all'interno di una stringa, anch'essa data;
ex-5
si scriva una funzione che accetti due stringhe, e torni true se la seconda è contenuta nella prima almeno una volta, false altrimenti (attenzione: esercizio non banale);
ex-6
si scriva una funzione che inverta una stringa data;


next up previous contents index
Next: Funzioni di libreria sulle Up: Array, enumerati, stringhe Previous: Enumerati   Indice   Indice analitico
Claudio Cicconetti
2000-09-06