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.