Le stringhe - Soluzione
In questo tutorial non verrà presentata la possibile soluzione nella sua interezza, ma soltano le parti interessanti, che dovranno essere opportunamente integrate per fornire la soluzione completa e compilabile.
Per ottenere un programma completo e funzionante, si tratta pertanto di scrivere la funzione main che dichiara le variabili necessarie e che chiama opportunamente le funzioni descritte.
Estrazione del basename
La funzione che determina il basename può essere la seguente:
void basename(const char *percorso, char *b) {
int pos = trova_ultimo_separatore(percorso);
strcpy(b, percorso + pos + 1);
}
che opera come segue:
- prima viene trovato l'indice dell'ultimo separatore (
pos) usandotrova_ultimo_separatore - con
strcpyviene copiata la porzione di stringa che va dapos + 1fino alla fine della stringapercorsoall'interno della stringab- questa istruzione funziona perché
percorsoè un indirizzo, ovvero l'indirizzo del primo carattere della stringa (percorsoè un puntatore achare viene usato per effettuare un passaggio di parametri per riferimento, vedi S6.6) - quindi spostandosi di
pos + 1caratteri si ottiene l'indirizzo del primo carattere del basename - il tutto è reso possibile dal fatto che i caratteri sono memorizzati nella stringa in posizioni contigue della memoria (si ricordi che si tratta di un vettore, vedi S5.9.1)
- questa istruzione funziona perché
La funzione trova_ultimo_separatore può essere definita come segue:
int trova_ultimo_separatore(const char *percorso)
{
int pos = strlen(percorso) - 1;
while (pos >= 0 && percorso[pos] != '/')
pos--;
return pos;
}
e può operare come segue:
posviene inizializzato pari all'indice dell'ultimo carattere della stringapercorso, usando lastrlenla quale restituisce la lunghezza della stringa in numero di caratteri (S6.11)- il ciclo
while(S4.7) continua a decrementare di uno (pos--) l'indice fintanto che non viene incontrato il primo separatore (partendo da destra) oppure si arriva al valore zero (si sono verificati tutti i caratteri della stringa) - viene restituito il valore di
pos, che varrà0se non ci sono separatori, oppure l'indicepos > 0corrispondente al primo carattere'/'incontrato (il più a destra)
Ricorda che
- l'operatore
&&è l'AND logico (S11.5.1) - l'operatore
!=significa "diverso da" (S11.4.1) - la costante
'/'è una costante intera di tipocharche corrisponde al carattere ASCII/(S5.5)
Estrazione del dirname
Per l'estrazione del dirname si può usare la seguente funzione:
void dirname(const char *percorso, char *b) {
int i;
int n = trova_ultimo_separatore(percorso);
/* Scrittura del terminatore tenendo anche conto del caso n == -1 */
if (n >= 0) {
/* Copia in b i primi n caratteri del percorso */
for (i = 0; i < n; i++)
b[i] = percorso[i];
b[n] = '\0';
} else
b[0] = '\0';
}
Prima viene trovato l'indice n dell'ultimo separatore usando la funzione trova_ultimo_separatore illustrata nel punto precedente.
Se il valore di n è positivo, si svolgono le seguenti operazioni:
- con il ciclo
for(S4.6) si copiano i primincaratteri dapercorsoab - con
b[n] = 0si pone il carattere successivo all'ultimo copiato pari a zero, terminando la stringa
Altrimenti di pone a zero il primo carattere della stringa con b[0] = '\0', che corrisponde a impostare la stringa b pari alla stringa vuota.
Questo significa che il percorso non contiene directory ma solo un nome di file.
ATTENZIONE: Ricorda che la costante '\0'è una costante intera di tipo char che corrisponde al carattere ASCII di valore numerico zero (S5.5).
Non è il carattere '0', che invece ha valore numerico 48 (T5.3).
Estrazione del tipo di percorso
La funzione può essere realizzata come segue:
int assoluto(const char *percorso)
{
if (percorso[0] == '/') return 1;
return 0;
}
Essa restituisce 1 (e quindi "vero") se il primo carattere della stringa percorso, che è percorso[0], corrisponde al carattere '/', altrimenti restituisce 0.
La return 1 termina la funzione, quindi la return 0 viene eseguita solo se il primo carattere percorso[0] è diverso da '/'.
Di conseguenza, in questo caso l'else non serve.
Un esempio simile è riportato in T5.3.
Nel main bisognerà stampare il valore corretto in output a seconda del valore restituito da assoluto.
Estrazione dell'estensione
Per determinare l'estensione, conviene riformulare la funzione trova_ultimo_separatore generalizzandola per cercare l'ultimo carattere, che specificato come argomento:
int trova_ultimo_carattere(const char *percorso, char c)
{
int pos = strlen(percorso) - 1;
while (pos >= 0 && percorso[pos] != c)
pos--;
return pos;
}
Questa operazione di "riformulazione" di una funzinalità esistente si chiama in gergo refactoring. Si notino le modifiche minime alla funzione originale.
Modifica il programma per usare la nuova funzione trova_ultimo_carattere anche nelle altre funzioni già sviluppate.
La funzione trova_ultimo_carattere può essere usata nella seguente funzione estensione:
int estensione(char *percorso, char *ext) {
int pos_punto = trova_ultimo_carattere(percorso, '.');
int pos_sep = trova_ultimo_carattere(percorso, '/');
int len = strlen(percorso);
/* il punto più a destra è a sinistra del separatore più a destra */
if (pos_punto < pos_sep)
return 0;
/* il punto più a destra è l'ultimo carattere della stringa (no estensione) */
if (pos_punto + 1 == len)
return 0;
/* c'è almeno un punto nel percorso */
if (pos_punto > 0) {
strcpy(ext, percorso + pos_punto + 1);
return 1;
}
return 0;
}
la quale opera nel modo seguente:
- usando la funzione
trova_ultimo_carattere, trova la posizione dell'ultimo punto (pos_punto) e dell'ultimo separatore (pos_sep) - il primo
ifcausa la terminazione dalla funzione segnalando che non c'è estensione - restituendo0conreturn 0- se il carattere punto più a destra rimane a sinistra del separatore più a destra (es./home/dir.con.punto/file); altrimenti... - il secondo
ifesce dalla funzione segnalando che non c'è estensione - restituendo0- se il punto più a destra è l'ultimo carattere della stringa (es./home/utente/nomefile., dove ovviamente non c'è estensione); altrimenti... - il terzo
ifcopia l'estensione della stringa di uscitaextse c'è un punto nel percorso e restituisce1(attenzione che si arriva a questo punto quando le condizioni dei precedenti dueifsono false) - altrimenti termina senza trovare un'estensione, con l'ultimo
return 0
Analisi completa
Nel calcolo di basename e dirname bisogna fare attenzione ad alcuni casi particolari:
- se il percorso indica un nome di file "puro", cioè senza alcun separatore, allora il suo dirname è la directory corrente
.(punto) - se il percorso termina in coda con uno o più separatori, essi devono essere eliminati sia dal dirname che dal basename (attenzione che in questo caso il basename è corriponde al nome di una directory, non di un file; questo però non cambia nulla nella suddivisione tra dirname e basename)
- se il percorso coincide con la directory speciale
/, allora sia il basename che il dirname sono pari a/ - se c'è un unico separatore, e si trova all'inizio del percorso, allora il dirname è la directory radice
/
Si modifichi il programma in modo che basename e dirname tengano in considerazione questi casi particolari. I nuovi elementi calcolati dovranno essere stampati dopo i marcatori:
[BASENAME_COMPLETO] e [DIRNAME_COMPLETO]
in modo da consentire la verifica tramite il comando
./pvcheck -f percorsi2.test ./percorsi
utilizzando il file di test percorsi2.test