Programma in C

Pierlu11
Salve a tutti! Sto cercando di scrivere un programma in linguaggio C per riconoscere se un nome proprio è maschile o femminile in base all'ultima lettera, ma non riesco a capire cosa c'è che non va...

#include
#include

int main() {
int x;
char a[100];
printf("Scrivere il nome: ");
gets(a);

for(int i=0; a!='\0';i++) {
x++;
}
if((a[x-1]=='a')||(a[x-1]=='e')) printf("Femminile\n");
if((a[x-1]=='i')||(a[x-1]=='o')) printf("Maschile\n");
else printf("Non so\n");
return 0;
}

L'errore che mi da è "Segmentation fault" dopo che ho inserito il nome...
Qualcuno può aiutarmi?

Risposte
vict85
gets è stato DEPRECATO nei penultimi standard di C e C++ e definitivamente eliminato dagli stessi nella loro ultima versione. Non andrebbe usato. Il codice con cui va sostituito è
fgets(a, sizeof(a), stdin);
anche se fgets ti scrive il '\n' nella stringa mentre gets non lo fa. Richiede insomma una piccola modifica al codice per tenerne conto.


Il vero bug però è avere usato x non inizializzato a 0. Il mio compilatore mi ha informato subito della cosa con un warning. Dovresti cercare di leggere quello che ti dice il compilatore.

Dovresti inoltre mettere un else prima del secondo if. Non è proprio necessario ma è meglio metterlo.

Pierlu11
Ho provato a fare la modifica ma continua a darmi lo stesso errore...

Sherlock.h
Oltre a ciò che ti ha detto vict85 ti consiglio di stampare a[x-1], mettendo un printf, ad esempio, tra il ciclo for (che poteva essere omesso tranquillamente sfruttando la funzione strlen) e la serie di if. Fatto ciò dovresti renderti conto, con un po' di attenzione, di quale sia il problema.

Pierlu11
Ho sostituito il ciclo for con la funzione strlen e adesso il programma funziona, ma ancora non capisco cosa non andava in quello che avevo fatto... l'unica cosa che mi viene in mente è che non era corretta la condizione di arresto del ciclo (a!='\0') ma non so il motivo.

Sherlock.h
Scusa, sono di fretta. Il problema è che non hai considerato lo \n (newline), ovvero l'invio che hai utilizzato per comunicare al programma di aver finito di inserire il tuo input oppure non hai considerato il fatto che in C si inizia a contare da zero. Io personalmente avrei inizializzato x a -1 prima di iniziare il for. Se scrivi "pollo" nel tuo programma la x, partendo da zero ti restituirà 6 come valore dopo essere stata data in pasto al for, perché verrà contato anche lo \n. In effetti la stringa "pollo" è formata da 6 caratteri, le 5 lettere più lo \n.

Come ho già detto il primo elemento di un array in C è in realtà lo zeresimo, il secondo il primo, il terzo il secondo...
Quando accedi al valore a[x-1], nel nostro esempio a[6-1], quindi a[5], stai considerando lo \n.

'p' è lo zeresimo, 'o' il primo... '\n' il quinto.

Qui sorge il problema. Tu, nell'esempio della parola "pollo", vuoi accedere all'ultima lettera, la 5a per noi umani la 4a per il C.
Una soluzione al problema è quella da me proposta (inizializzare x a -1), ma si tratta di meccanismi semplici che possono essere gestiti nella maniera che ritieni più opportuna.

vict85
Il problema, come detto da sherlock.h e come ti avevo accennato già nel mio primo messaggio, è che passando da gets a fgets aggiungi alla stringa il carattere \n (uno lo inserisce nella stringa mentre l'altro no, alla faccia della coerenza).

Comunque il tuo metodo non è certo ottimale, se aggiungessi uno spazio prima di andare a capo avresti lo stesso problema.

[edit] Pensavo a qualcosa come questo:

#include <stdio.h>
#include <string.h>
#include <ctype.h> // per isalpha()

int main()
{
    char a[100];

    printf("Scrivere il nome: ");
    fgets(a,sizeof(a), stdin);

    char c = 'n';
    for(int i = 0; isalpha(a[i]); c= a[i++]);
    switch (c) {
    case 'a':
    case 'e':
        puts("Femminile");
        break;
    case 'o':
    case 'i':
        puts("Maschile");
        break;
    default:
        puts("Non so");
    }
}

Sherlock.h
Vict85, chiedo scusa, non avevo letto con la dovuta attenzione il tuo messaggio... e oltretutto avevo dimenticato che gets non prende lo '\n' perché, in quanto deprecata, non la uso praticamente mai. Grazie per avermi rinfrescato la memoria e scusami ancora.

Pierlu11
Grazie mille per le risposte! Mi pare che adesso è tutto chiaro...
Un'ultima curiosità: la funzione fgets() mette come carattere finale '\n' mentre la funzione gets() mette '\0'? Quindi mettere a!='\n' (nel caso non usassi strlen) sarebbe appropriato?

vict85
@ Sherlock : non preoccuparti, è normale. La differenza sono andata a vederla su cppreference, non ricordo neanche io tutte le sottigliezza dello standard.

@ Perlu11 : Non è così semplice. gets legge un riga, ovvero arriva a leggere finché non incontra un \n o qualche altro carattere di fine lettura. Alla fine inserisce tutto dentro la stringa (tranne il \n o altro carattere simile) e ci mette un '\0'. Il problema di gets è che non fa attenzione alla dimensione dell stringa. Cioè se io ho una riga lunga 1000 caratteri e una stringa lunga 10 lui mi scrive 1000 caratteri nella stringa lunga 10. E ci sono buonissime ragioni per ritenere questa cosa molto brutta.
fgets invece è la versione per gets relativa ai file. Si vede che per il file si sono posti il problema (un file potrebbe non andare mai a capo) e quindi fgets ha un numero massimo di caratteri che legge (il secondo parametro ridotto di 1 per permettere l'inserimento del terminatore di stringa). Nel caso incontri un carattere "a capo" prima, lui smette di legge e scrive tutto ciò che ha letto (compreso il \n) all'interno della stringa e lo fa seguire dal terminatore di stringa.
Quindi fgets potrebbe inserire un \n come non farlo, certamente inserisce il terminatore di stringa. A rigore quindi dovresti testare sia per \n che per \0. D'altra parte se tu scrivi la stringa "Vittorio \n" (nota lo spazio) allora il programma fallisce. Un soluzione potrebbe essere la mia (ti prende la prima parola inserita, ma fallisce se il primo carattere inserito non è alfabetico) oppure prendere l'ultimo elemento alfabetico della stringa (modificando il mio ciclo oppure tornando indietro dal \0 finché non incontri un carattere).

apatriarca
Invece di cercare la fine della stringa e poi tornare indietro potresti anche iterare su tutta la stringa e memorizzare l'ultimo carattere alfabetico trovato. Qualcosa del genere:
char last_alpha = '\0';
for (int i = 0; a[i] != '\0'; ++i) {
    last_alpha = isalpha(a[i]) ? a[i] : last_alpha;
}

Alla fine del codice avrai l'ultima lettera dell'alfabeto trovata nella stringa. Questo elimina tutti gli spazi o caratteri non voluti, ma ovviamente non verifica molte altre cose che potrebbero interessarti.

Rispondi
Per rispondere a questa discussione devi prima effettuare il login.