Gestione delle aree di memoria : le stringhe





La gestione di array di dimensione arbitraria mediante puntatori rappresenta l'unico metodo per poter passare ad una funzione o far restituire da una funzione un array.

In particolare, ci sono funzioni all'interno della libreria standard che sono state espressamente implementate per la gestione di un particolare tipo di array di char: le stringhe

Il file di intestazione da includere prima di usare le funzioni di gestione delle stringhe è : string.h



Come mai sono state implementate funzioni per la gestione di array di char e non per la gestione di array di int, float o double ?























Le stringhe sono un particolare tipo di array di char in quanto, diversamente dagli array convenzionali, possiedono una fine; per convenzione si assume che la stringa termini quando, al suo interno, si incontra il carattere '\0', cioè il carattere avente codice ASCII nullo. Le funzioni della libreria standard, quindi, considerano solo i caratteri dell'intero vettore che vanno dalla prima posizione fino al carattere di terminazione.


NOTA: la fine della stringa non coincide sempre con la fine dell'array di char. La stringa termina quando incontriamo il carattere con codice ASCII nullo mentre l'array termina quando arriviamo all'ultimo elemento.






0
1
2
3
4
5
6
7
8
9
s
t
r
i
n
g
a
'\0'
s
t



Per le funzioni di libreria standard la stringa termina alla posizione 6 nonostante l'array di char sia composto in totale da 10 caratteri.



Le funzioni per la gestione delle stringhe che utilizzeremo oggi sono:



int strlen(char *s) la funzione restituisce un numero intero pari al numero di caratteri che compongono la stringa s (ovvero il numero di caratteri che sta prima di quello nullo)
   
char * strcpy(char*s1,char*s2) la funzione copia la stringa s2 all'interno di s1 e restituisce poi la stringa s1 (s1 deve essere abbastanza grande da contenere s2). Anche il carattere nullo viene copiato in s1.
   
char *strcat(char*s1,char*s2) la funzione concatena la stringa s2 alla stringa s1 e poi restituisce s1
   

int strcmp(char*s1,char*s2)

la funzione confronta la stringa s1 con la stringa s2. il valore restituito è: negativo se, secondo l'ordine alfabetico, s1 viene prima di s2; zero se le due stringhe coincidono; positivo se, secondo l'ordine alfabetico, s1 viene dopo s2.
int strncmp(char*s1,char*s2,int n) la funzione confronta al massimo n caratteri della stringa s1 con quelli della stringa s2. il valore restituito è: negativo se, secondo l'ordine alfabetico, la porzione di s1 viene prima di quella di s2; zero se le due stringhe coincidono; positivo se, secondo l'ordine alfabetico, la porzione di s1 viene dopo quella di s2.



Anche printf() e scanf() riescono a gestire le stringhe; sia per visualizzare una stringa che per inserirci dei caratteri lo specificatore da usare è %s.


Per inserire nel codice una stringa costante, è sufficiente racchiudere il testo che ci interessa tra doppi apici.

"ecco una stringa costante"



// esempio di applicazione delle funzioni



#include <string.h>

int main()
{char stringa1[40], *stringa2;

// nella stringa costante è automaticamente compreso anche il carattere nullo
  strcpy(stringa1,"inserisco dei caratteri nella stringa");


// printf considera finita la stringa quando incontra `\0`
  printf("stringa1 : %s ",stringa1); 


// sfruttanto il valore restituito dalla funzione strcpy si poteva anche scrivere il tutto così:
  printf("stringa1 : %s", strcpy(stringa1,"inserisco dei caratteri nella stringa") );


  printf("la lunghezza di stringa1 e\' : %d", strlen(stringa1) );


// scanf() termina di leggere quando incontra un carattere ' ' (spazio)
  printf("inserisci dei caratteri (senza spazio)");

  scanf("%s",stringa2);      // !! non è stata ancora allocata memoria per stringa2



  stringa2 = (char*) malloc(50*sizeof(char));
  printf("inserisci dei caratteri (senza spazio)");

  scanf("%s",stringa2);      // ok !

  printf("l'utente ha inserito %d caratteri", strlen(stringa2) );



// supponiamo ora di voler aggiungere dei caratteri in fondo ad una delle due stringhe
// se voglio inserire 7 caratteri in fondo alla stringa stringa2, devo verificare che ci sia spazio sufficiente
// per poterlo fare

  if((50 - 1 - strlen(stringa2) ) > 7)
   strcat(stringa2," utente");

// se stringa2 era pari a "inserimento", ora stringa2 vale "inserimento utente"
  printf("la nuova stringa e' : %s",stringa2);

  
// ora modifico le stringhe

  strcpy(stringa1,"include");
  
  strcpy(stringa2,"programma");

  strcmp(stringa1,stringa2);       // confronta "include" e "programma"

  strncmp(stringa1,stringa2,4);    // confronta "incl" e "prog"


 return 0; }



// fine esempio









DOMANDE:

(1) dove inizia la stringa ?
(2) dove termina la stringa ?   -> in corrispondenza del carattere con codice ASCII nullo




char stringa[50];
strcpy(stringa,"prova di scrittura");

printf("\necco il valore della stringa : %s",stringa);

printf("\necco una porzione della stringa originale : %s", stringa + 6);

L'output del codice è:

ecco il valore della stringa : prova di scrittura
ecco una porzione della stringa originale : di scrittura



Perciò, per le funzioni della libreria standard, la stringa inizia in corrispondenza dell'elemento cui fa riferimento il puntatore che passo alle funzioni.


































Costruiamo ora una funzione che svolge questo compito:

(1) la funzione deve chiedere all'utente di inserire una stringa di lunghezza arbitraria
(2) la funzione deve chiedere all'utente di inserire una seconda stringa, più corta
(3) la funzione deve cercare se la seconda stringa è contenuta dentro la prima
(4) la funzione deve restituire un numero che indichi quante occorrenze della seconda stringa ha trovato all'interno della prima

Esempio

prima stringa : "questa è la stringa inserita grazie alla tastiera"
seconda stringa : "la"

dal confronto : "questa è la stringa inserita grazie alla tastiera"

La funzione deve restituire il valore: 2






s
t
r
i
n
g
a
`\0'
?
?
                   
i
n
             













Viste le richieste, la funzione può avere il seguente prototipo (in quanto le stringhe le inserisce direttamente l'utente):



int ricerca_stringa(void);


int ricerca_stringa()
{char *temp;    // stringa temporanea per memorizzare l'input
  char *str1, *str2;   // conterranno l'input dell'utente
  int i = 0, occorrenze = 0;


  temp = (char *) malloc (200 * sizeof(char)) ;    // tanto per stare tranquilli

  printf("inserisci la prima stringa");
  scanf("%s",temp);

  // ora dimensiono correttamente str1

  str1 = (char *) malloc((strlen(temp)+1) * sizeof(char)) ;
  strcpy(str1,temp);

  printf("inserisci la seconda stringa");
  scanf("%s",temp);

  // ora dimensiono correttamente str2

  str2 = (char *) malloc((strlen(temp)+1) * sizeof(char)) ;
  strcpy(str2,temp);

  // a questo punto, temp non mi serve più

  free(temp);

  /*
Ora si può iniziare con la ricerca: essa deve essere effettuata confrontando tutti i caratteri contenuti in str2, ad esempio 3, con un sottogruppo di caratteri di str1 formato da 3 caratteri
   */

  for(i = 0; i <= ( strlen(str1) - strlen(str2) ) ; i++)
    {if( strncmp( str1+i , str2 , strlen(str2) ) == 0)
         occorrenze ++;
     }
// inizio a leggere la stringa str1 a partire da str1 + i, ovvero sposto l'indicatore di posizione
// all'interno della stringa

  return occorrenze;
}













Attenzione: la funzione scanf() interrompe la lettura del buffer di input non appena incontra uno spazio.

se l'utente digita:

prova di inserimento stringa

scanf() preleva solo: prova



Per evitare questo inconveniente, possiamo usare la funzione

char *gets(char*)

dichiarata in stdio.h

La funzione preleva TUTTI i caratteri dal buffer di input e li inserisce nella stringa che riceve come argomento (ammesso che ci sia sufficiente spazio a disposizione ...); la funzione restituisce la medesima stringa. La funzione termina automaticamente la stringa con il carattere '\0' (lo inserisce al posto del newline )

NOTA: non servono specificatori di formato, in quanto la funzione gets() preleva tutti i caratteri dal buffer di input SENZA INTERPRETARLI !

















Con questa modifica, la funzione implementata sopra diventa:



int ricerca_stringa(void);


int ricerca_stringa()
{char *temp;    // stringa temporanea per tenere l'input
  char *str1, *str2;   // conterranno l'input dell'utente
  int i = 0, occorrenze = 0;
  temp = (char *) malloc (200 * sizeof(char)) ;    // tanto per stare tranquilli

  printf("inserisci la prima stringa");
  gets(temp);

  // ora dimensiono correttamente str1

  str1 = (char *) malloc((strlen(temp)+1) * sizeof(char)) ;
  strcpy(str1,temp);

  printf("inserisci la seconda stringa");
  gets(temp);

  // ora dimensiono correttamente str2

  str2 = (char *) malloc((strlen(temp)+1) * sizeof(char)) ;
  strcpy(str2,temp);

  // a questo punto, temp non mi serve più

  free(temp);

  /*
Ora si può iniziare con la ricerca: essa deve essere effettuata confrontando tutti i caratteri contenuti in str2, ad esempio 3, con un sottogruppo di caratteri di str1 formato da 3 caratteri
   */

  for(i = 0; i <= ( strlen(str1) - strlen(str2) ) ; i++)
    {if( strncmp( str1+i , str2 , strlen(str2) ) == 0)
         occorrenze ++;
     }
// inizio a leggere la stringa str1 a partire da str1 + i, ovvero sposto l'indicatore di posizione
// all'interno della stringa

  return occorrenze;
}
















Visto che le stringhe sono comunque dei vettori di char, possiamo anche effettuare le stesse operazioni senza usare le funzioni della libreria standard.






  for(i = 0; i <= ( strlen(str1) - strlen(str2) ) ; i++)
    {if( strncmp( str1+i , str2 , strlen(str2) ) == 0)
         occorrenze ++;
     }


Diventa:




int i = 0;
int j = 0;
int confronto=0;

... // istruzioni

  for(i = 0; i <= ( strlen(str1) - strlen(str2) ) ; i++)
    {confronto = 0;

      for(j = 0 ; j < strlen(str2) ; j++)
       if(str1[i + j] == str2[ j ]) confronto ++;      

      if(confronto == strlen(str2))
        occorrenze ++;
     }
























O ancora, sfruttando il fatto che la stringa ha un carattere di terminazione noto:



int i = 0;
int j = 0;
int confronto=0;
int lung1 = 0, lung2 = 0;
... // istruzioni


 for(i=0;str1[i];i++)
   lung1++;

 for(i=0;str2[i];i++)
   lung2++;

  for(i = 0; i <= ( lung1 - lung2 ) ; i++)
    {confronto = 0;

      for(j = 0 ; str2[j] ; j++)     // quando arrivo in fondo, il codice ASCII vale 0
       if(str1[i + j] == str2[ j ]) confronto ++;      

      if(confronto == lung2)
        occorrenze ++;
     }