Porque o meu gets não pega a variavel nome?

Criei um programa de ordenação de nomes e para fazer a entrada do nome, eu uso a função gets, mas quando chega na execução do gets passa em branco e não executa nada. Vocês podem me ajudar?

Veja o programa abaixo:

/*

    Ronaldo Rodrigues Godoi
    Ordenação de strings
    
*/

#include <stdio.h>
#include <string.h>

char nome[10][80];
int matricula[10];

void ordena() {
	int x, y, r, mat;
	char aux[80];
	for(x=0; x<=9; x++) {
		for(y=x+1; y<=9; y++) {
			r = strcmp(nome[x], nome[y]);
			if(r > 0) {
				strcpy(aux, nome[x]);
				strcpy(nome[x], nome[y]);
				strcpy(nome[y], aux);
				mat = matricula[x];
				matricula[x] = matricula[y];
				matricula[y] = mat;
			}
		}
	}
}

void imprime_ordem_matricula() {
	printf("\n Nomes ordenados por matricula \n ------------------------------------------------------ \n");
	for(int i = 0; i < 10; i++) {
		printf("Matricula: %d Nome: ", matricula[i]);
		puts(nome[i]);
	}
}

void imprime_ordem_nome() {
	printf("\n Nomes em ordem alfabetica \n ------------------------------------------------------ \n");
	for(int i = 0; i <= 9; i++) {
		printf("Matricula: %d Nome: ", matricula[i]);
		puts(nome[i]);
	}
}

main() {
	printf("Digite 10 nomes e 10 matriculas");
	for(int i = 0; i <= 9; i++) {
		printf("\n Matricula: ");
		scanf("%d", &matricula[i]);
		printf(" Nome: ");
		gets(nome[i]);
	}
	
	imprime_ordem_matricula();
	
	ordena();
	
	imprime_ordem_nome();
}

O problema é que scanf("%d" lê somente o mínimo necessário para obter um número, o que quer dizer que ele não lê a quebra de linha (que equivale ao ENTER).

Então se você digita, por exemplo, 123<ENTER>, somente os caracteres 1, 2 e 3 são lidos por scanf (que por sua vez, já converte para número, conforme indicado pelo formato %d), mas a quebra de linha (o caractere \n) permanece lá no buffer de entrada.

E aí o gets vai lá e já lê esse caractere. E segundo a documentação, gets encerra a leitura quando encontra uma quebra de linha, e a mesma não é colocada na string.

Você pode comprovar fazendo esse teste:

int n;
char s[10];
printf("digite um número:\n");
scanf("%d", &n);
printf("digite um texto:\n");
gets(s);
printf("número: %d\n", n);
printf("texto: [%s]", s);

Se eu digitar um número (por exemplo, 1) e der ENTER, o scanf lê somente o 1 e a quebra de linha permanece no buffer. Depois gets lê a quebra de linha e encerra a leitura, mas como ele não inclui a quebra de linha no resultado, a string s fica vazia. A saída do código é:

digite um número:
1
digite um texto:
número: 1
texto: []

Claro que existem formas de forçar o scanf a ler as quebras de linha, mas na verdade o problema todo é misturar scanf com outras funções (como gets, fgets, etc), já que elas possuem comportamentos distintos (scanf só lê as quebras de linha quando achar necessário, gets e fgets sempre leem, etc).

O melhor seria padronizar a leitura e não misturar. Por exemplo, poderia ser assim:

printf("digite um número:\n");
scanf("%d", &n);
printf("digite um texto:\n");
scanf("%s", s);

Mais ainda, eu recomendo não usar gets, já que esta função é considerada insegura e muitos compiladores modernos dão até um aviso. Por exemplo, compilei seu código com o GCC 9 (que nem é a última versão) e ele deu esse aviso:

warning: the gets function is dangerous and should not be used.


Um detalhe é que scanf("%s" não lê os espaços: se você digitar abc def, a string só vai ter abc. Claro que dá pra fazer coisas bem “bonitas” como scanf(" %10[^\n]s", s); (o espaço antes do % é para ler quebras de linha que eventualmente estiverem no buffer, depois tem o tamanho para não ler caracteres a mais, e por fim uma indicação para só parar quando encontrar uma quebra de linha, assim ele lê os espaços).

Mas aí já começa a complicar demais. Uma alternativa - na minha opinião - melhor é usar fgets. É uma alternativa mais segura que gets, mas você tem que cuidar dos detalhes (em C não tem jeito, é uma linguagem “bruta” e a maioria dessas coisas você tem que fazer na mão mesmo).

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
    char buffer[80]; // buffer temporário para ler a matrícula
    char nome[10][80];
    int matricula[10];
    printf("Digite 10 nomes e 10 matriculas\n");
    for (int i = 0; i < 10; i++) {
        printf("Matricula: ");
        // sempre verifique o retorno das funções de leitura
        if (! fgets(buffer, sizeof buffer, stdin)) {
            printf("erro na leitura da matrícula\n");
            return 1;
        }
        // converte o valor lido para número
        matricula[i] = atoi(buffer);

        printf("Nome: ");
        if (!fgets(nome[i], sizeof nome[i], stdin)) {
            printf("erro na leitura do nome\n");
            return 1;
        }
        // fgets inclui a quebra de linha no final, precisa remover
        nome[i][strcspn(nome[i], "\n")] = 0;
    }

    for (int i = 0; i < 10; i++) {
        printf("Matricula %d = %d \t Nome: %s\n", i, matricula[i], nome[i]);
    }
    return 0;
}

Repare que agora uso fgets para ler tudo. No caso da matrícula, depois tenho que converter o valor lido para inteiro, usando a função atoi.

Já para o nome, tenho que remover a quebra de linha que fgets inclui no final.

E claro que esta é apenas uma alternativa. Existem várias formas de se ler do stdin em C, e a “melhor” depende de cada caso (para um exercício simples, porém, acho que “tanto faz”, desde que se tome esses cuidados para não cair no problema que você teve).


Alternativa para os arrays

Não declare variáveis “soltas” fora do main. Prefira declarar as variáveis perto de onde são usadas, e manter o escopo bem definido. Se precisa acessá-las dentro de outras funções, prefira passá-las como parâmetros. Só que aí eu prefiro um pouco mais de flexibilidade em vez de tamanhos fixos:

void imprime_ordem_matricula(int *matriculas, char **nomes, int tamanho) {
    printf("\n Nomes ordenados por matricula \n ------------------------------------------------------ \n");
    for(int i = 0; i < tamanho; i++) {
        printf("Matricula: %d Nome: %s\n", matriculas[i], nomes[i]);
    }
}

int main() {
    int qtdMatriculas = 10;
    int tamanhoBuffer = 80;
    char buffer[tamanhoBuffer]; // buffer temporário para ler a matrícula

    char **nomes = malloc(qtdMatriculas); // sizeof char sempre é 1, então nem precisa colocar
    int matriculas[qtdMatriculas];
    printf("Digite %d nomes e %d matriculas\n", qtdMatriculas, qtdMatriculas);
    for (int i = 0; i < qtdMatriculas; i++) {
        printf("Matricula: ");
        // sempre verifique o retorno das funções de leitura
        if (! fgets(buffer, sizeof buffer, stdin)) {
            printf("erro na leitura da matrícula\n");
            return 1;
        }
        // converte o valor lido para número
        matriculas[i] = atoi(buffer);

        printf("Nome: ");
        nomes[i] = malloc(tamanhoBuffer);
        if (!fgets(nomes[i], tamanhoBuffer, stdin)) {
            printf("erro na leitura do nome\n");
            return 1;
        }
        // fgets inclui a quebra de linha no final, precisa remover
        nomes[i][strcspn(nomes[i], "\n")] = 0;
    }

    imprime_ordem_matricula(matriculas, nomes, qtdMatriculas);

    for (int i = 0; i < qtdMatriculas; i++) {
        free(nomes[i]);
    }    
    free(nomes);
    return 0;
}

Repare que agora a função recebe as matrículas e nomes como parâmetros. Assim ela fica mais flexível, podendo funcionar com outros arrays, por exemplo (e você pode reusá-la caso tenha outro conjunto de matrículas e nomes). Faça o mesmo para as outras funções.

Também mudei o nome das variáveis para o plural: matriculas e nomes, afinal, elas guardam respectivamente várias matrículas e vários nomes. Pode parecer um detalhe besta, mas dar nomes melhores ajuda na hora de programar.

2 curtidas

Tem uma função que parece que resolve todos os problema com gets e fgets que é a função _fpurge. Mas tentei usar e não funcionou. E sem uma providência anterior aos gets/fgets as vezes eles são pulados da execução do programa, não permitindo que seja feita a entrada de dados.

Você sabe o que está acontecendo com o meu _fpurge? Outra pergunta são dois underscore __fpurge ou um _purge?

Tem que ter um jeito, será que o problema não é no meu compilador c?

Obrigado,
Ronaldo

Coloque o código em que isso acontece, e quais valores você digitou para testar (aliás, isso é o que todos deveriam fazer sempre que dizem que “o programa não funciona:slight_smile:).

Quanto a fpurge, o problema é que esta função não é portável. Na documentação é dito:

These functions are nonstandard and not portable. The function fpurge () was introduced in 4.4BSD and is not available under Linux. The function __fpurge () was introduced in Solaris, and is present in glibc 2.1.95 and later.

Não sei em qual ambiente você está testando, mas enfim, fpurge não é garantido que funcione em todos os ambientes.

Se quer limpar o buffer, pode usar isso:

int c;
while ( (c = getchar()) != '\n' && c != EOF ) { }

Um pouco mais chato, porém mais garantido…

1 curtida

Tudo bem, Hugo. Mas se eu usar a função getchar(), não haverá uma entrada obrigatória de um caracter. Digo, eu não terei que digitar alguma coisa?

Obrigado pelo esclarecimento quanto a função _fpurge(), é uma pena, mas no IDE que estou usando não funciona a função. É o ambiente Embarcadero-Dev-C++. Não tinha C no meu computador, dai eu baixei este aí. Quanto ao programa que está com problema é o que segue: (são problemas de entrada de string, veja os comentários //).

/*

    Ronaldo Rodrigues Godoi
    Ordenação de strings
    
*/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

char nome[10][80];
int matricula[10];

void ordena_nome() {
	int x, y, r, mat;
	char aux[80];
	for(x=0; x<=9; x++) {
		for(y=x+1; y<=9; y++) {
			r = strcmp(nome[x], nome[y]);
			if(r > 0) {
				strcpy(aux, nome[x]);
				strcpy(nome[x], nome[y]);
				strcpy(nome[y], aux);
				mat = matricula[x];
				matricula[x] = matricula[y];
				matricula[y] = mat;
			}
		}
	}
}

void ordena_matricula() {
	int x, y, mat;
	char aux[80];
	for(x = 0; x < 10; x++) {
		for(y = x + 1; y < 10; y++) {
			if (matricula[x] > matricula[y]) {
				strcpy(aux, nome[x]);
				strcpy(nome[x], nome[y]);
				strcpy(nome[y], aux);
				mat = matricula[x];
				matricula[x] = matricula[y];
				matricula[y] = mat;
			}
		}
	}
}

void imprime_ordem_matricula() {
	printf("\n Nomes ordenados por matricula \n ------------------------------------------------------ \n");
	for(int i = 0; i < 10; i++) {
		printf("Matricula: %d Nome: ", matricula[i]);
		puts(nome[i]);
	}
}

void imprime_ordem_nome() {
	printf("\n Nomes em ordem alfabetica \n ------------------------------------------------------ \n");
	for(int i = 0; i <= 9; i++) {
		printf("Matricula: %d Nome: ", matricula[i]);
		puts(nome[i]);
	}
}

main() {
	printf("Digite 10 nomes e 10 matriculas");
	for(int i = 0; i <= 9; i++) {
		printf("\n Matricula: ");
		scanf("%d", &matricula[i]);
		printf(" Nome: ");
		//_fpurge(stdin);
		//fgets(nome[i], 80, stdin);
		//fgets(nome[i], 80, stdin);
		scanf("%s", &nome[i]);
		
	}
	
	ordena_matricula();
	
	imprime_ordem_matricula();
	
	ordena_nome();
	
	imprime_ordem_nome();
}

Olá!

Vejo uma situação em que o uso da ‘gets’ não é recomendado, também vi que chama a ‘scanf’, logo, por que não chamar outra vez? E menos dor de cabeça dando-lhe a indicação que ignore o caractere de formatação no início do fluxo, para isso, use um espaço no início do formato.

Exemplo
escrevo para que a ‘scanf’ em duas situações distintas ignore os caracteres de formatação no início da captura.

#include"stdio.h"
int
main (void) {
  char nome_completo[51] = { "" };
  int matricula = 0;
  for (unsigned long i = 0;  i < 2;  ++i) {
      printf (" Matricula ..: "); scanf (" %d", &matricula);
      printf (" Nome .......: "); scanf (" %50[^\n]", &nome_completo[0]);

      printf ("Matricula----------- | Nome----------------------------------------------\n");
      printf ("%20d | %-50s\n\n", matricula, nome_completo);
  }
  return 0;
}

[ :slight_smile: ]

1 curtida