paulo1205
(usa Ubuntu)
Enviado em 19/03/2017 - 03:04h
Como eu mencionei na mensagem anterior, sua string de formatação de
scanf() tinha um erro. Eis o que você fez.
scanf(" %[^\n]s", nome);
Vamos olhar cada pedaço da string:
1) O espaço no começo da string server para suprimir qualquer quantidade (inclusive zero) espaços em branco (incluindo marcas de fim de linha remanescentes de leituras anteriores) antes de começar a ler os nomes.
2) A especificação de conversão “
%[^\n]” significa que você vai ler uma quantidade qualquer (inclusive zero, e sem limite máximo) de caracteres diferentes da marca de fim de linha, e vai guardá-los em sucessivas posições de memória a partir do endereço indicado por
nome.
3) O caráter “
s” é procurado isoladamente na entrada, após o fim da leitura da string.
Como a leitura de string só termina em quando chega um caráter de fim de linha, quando se chega ao fim do arquivo ou quando ocorre um erro, o terceiro pedaço da string de formatação vai sempre falhar.
Perceba que a conversão
"%[" é diferente da conversão
"%s". O que vem entre colchetes não é um modificador de uma conversão
"%s", colocado entre o sinal de porcentagem e a letra “
s”.
"%[" é uma conversão de pleno direito, que recebe argumentos pospostos sobre quais caracteres devem ser incluídos (ou excluídos) da conversão, e esses argumentos são encerrados pela presença do caráter “
]” após a especificação de um ou mais elementos do conjunto de caracteres a ser considerado para a extração da string. Por ser uma conversão completa, ela aceita os mesmos modificadores que as outras conversões (e.g. largura máxima, reordenamento de argumentos, supressão de atribuição etc.), dispostos entre o sinal de porcentagem e o caráter que indica a conversão.
-----------
O código abaixo, fartamente comentado, exemplifica como fazer a leitura do nome usando uma conversão correta e segura, e como tratar os possíveis casos de erro sinalizados por
scanf(). Se parecer complicado, é porque é complicado mesmo! Eu costumo dizer que
scanf() é uma das funções mais complicadas da biblioteca padrão do C.
int r, a, b, c;
nome[99]='\0'; // Proteção, pois nem sempre scanf() coloca o terminador na string.
a=b=c=0;
// contador de quantos caracteres de espaçamento foram consumidos
// após a supressão de caracteres em branco
// ||
// || contador do total de caracteres consumidos por scanf()
// || ao final da leitura do nome
// || ||
// || || contador do total de caracteres consumidos
// || || ao final da string de formatação
// || || ||
// VV VV VV
r=scanf(" %n%99[^\n]%n%*1[\n]%n", &a, nome, &b, &c);
// ^ ^^^^^^^^ ^^^^^^^
// | |||||||| |||||||
// | |||||||| converte string composta apenas por caracteres '\n',
// | |||||||| com tamanho máximo de um caráter, mas o asterisco
// | |||||||| indica que não haverá atribuição da string lida a
// | |||||||| nenhum argumento
// | ||||||||
// | converte uma string composta por caracteres diferentes de '\n',
// | com tamanho máximo de 99 bytes, atribuindo-a ao array ‘nome’.
// |
// provoca a supressão de qualquer quantidade de caracteres em branco
// no começo do buffer de entrada
if(r==EOF){
/*
Erro de leitura, possivelmente devido a fim prematuro de dados na
entrada, ou nenhuma parte da string de formatação corresponde à entrada.
Você pode querer testar o resultado de ferror(stdin) e feof(stdin) para
decidir como proceder.
*/
if(ferror(stdin)){
/* Trata erro de leitura. */
}
else{
/* Trata fim de arquivo. */
}
}
else if(r==1){
/*
Compara com o número de conversões com atribuição solicitado. Se
for igual, todas as conversões com atribuição foram bem-sucedidas.
Note que as ocorrências de “%n” fazem atribuições, mas não con-
versões de dados da entrada. Logo não entram na conta. Tampouco
entra a conversão cuja atribuição foi suprimida pela presença do
asterisco.
*/
// Alguns dos testes abaixo ajudam a examinar a consistência dos dados.
if(a==b){
/*
Equivalente a testar se strlen(nome)==0, porém mais rápido.
No seu caso particular, essa condição provavelmente nunca vai
ocorrer, já que '\n' é considerado um espaço em branco, e seria
suprimido da entrada antes de começar a ler o nome, por causa
do espaço em branco no início da string de formatação.
Contudo, se a supressão de espaços em branco não estivesse
presente, ou se fosse outro o conjunto de caracteres a ser
suprimido, a condição aqui testada poderia ser útil para
impedir o uso de um nome vazio.
*/
}
else if(c>b){
/*
Nome tem 99 ou menos carcteres e havia um sinal de fim
de linha na entrada, que foi consumido. Certamente o nome lido é
válido, e você pode usá-lo aqui.
*/
}
else if(ferror(stdin)){
/*
Truncamento do nome por erro de leitura. Talvez seja perigoso
usar o nome truncado nessa situação.
*/
}
else if(feof(stdin)){
/*
Truncamento do nome devido ao fim dos dados na entrada.
Possivelmente menos crítico do que o truncamento por erro,
mas depende da aplicação decidir se convém ou não usar o nome
lido nesse caso.
*/
}
else{
/*
Usuário tentou digitar um nome longo demais, e os caracteres
excedentes não foram consumidos por scanf(), permanecendo no
buffer de entrada.
Além de tratar o truncamento do nome, se você não quiser que
os caracteres excedentes afetem futuras operações de leitura,
terá de tirá-los do buffer.
*/
}
}
else{
/*
Valor retornado por scanf() é diferente da quantidade total solicitada
de conversões com atribuição de valor, de modo que ocorreu algum
problema de formatação ou de erro durante a execução da função.
No seu caso particular, só foi solicitada uma conversão com atribuição,
logo o valor de r neste ponto do programa seria zero. Contudo, se você
tivesse pedido N conversões na mesma string de formatação, qualquer
valor entre 0 e N-1 seria indicativo de falha parcial, e uma aplicação
bem comportada deveria saber tratar qualquer um desses casos.
Mais ainda: sua string de formatação é composta apenas de elementos
se podem indicar sucesso em caso de “zero ou mais” ocorrências (tanto
a supressão de espaços quanto as conversões de strings). Então você
provavelmente nunca terá um caso de sucesso parcial, e a única possi-
bilidade de não sucesso total provavelmente será mesmo o caso em que
r==EOF.
*/
}
Mudar o modo de ler pode ajudar a evitar uma parte da complicação. Eis como conseguir funcionalidade semelhante com
fgets(), ao custo de ter uma posição a menos disponível para guardar o nome no array
nome, uma vez que
fgets() transfere a marca de fim de linha para o array lido, a fim de facilitar justamente o diagnóstico de erros.
if(
// Pula espaços em branco
scanf(" ")==EOF ||
// Lê linha após espaços em branco
fgets(nome, sizeof nome, stdin)==NULL
){
/*
Erro de leitura ou fim de arquivo antes de receber qualquer dado.
Não há nome válido com que se trabalhar.
*/
if(ferror(stdin)){
/* Trata erro de leitura. */
}
else{
/* Trata fim de arquivo. */
}
}
else{
size_t len=strlen(nome);
if(nome[len]!='\n'){
/*
Falta a marca de fim linha.
*/
if(len<sizeof nome-1){
/*
Ocorreu truncamento antes de se consumirem todas as
posições de ‘nome’.
*/
if(ferror(stdin)){
/*
Trata erro de leitura. Pode ser perigoso considerar o
nome lido como válido.
*/
}
else{
/*
Trata fim de arquivo. Tratar o nome como válido pode ser
aceitável ou não, dependendo da aplicação.
*/
}
}
else{
/*
Todas as posições foram ocupadas, mas sem a marca de fim de
linha: o usuário digitou um nome grande demais.
Você possivelmente vai querer descartar os dados lidos e
limpar os caracteres que ainda estiverem no buffer de
entrada, e depois tentar ler o nome novamente.
*/
}
goto saida; // Sim, é goto mesmo!
}
else{
/*
Marca de fim de linha presente. Suprime-a, pois você provavel-
mente não vai querê-la como parte do nome.
*/
nome[len--]='\0';
}
/*
Aqui vai um nome válido e não-nulo, com no máximo
‘sizeof nome-2’ caracteres, que você pode usar à vontade.
*/
}
saida:
Se você não quiser perder um byte do array, terá de usar uma variação que cria um segundo array com espaço para um caráter a mais, e depois copia dado lido (sem o
'\n') de volta para o array original.
char nome_mais_lf[1+sizeof nome]; // buffer com um byte a mais para conter quebra de linha
if(
// Pula espaços em branco
scanf(" ")==EOF ||
// Lê linha após espaços em branco
fgets(nome_mais_lf, sizeof nome_mais_lf, stdin)==NULL
){
/*
Erro de leitura ou fim de arquivo antes de receber qualquer dado.
Não há nome válido com que se trabalhar.
*/
if(ferror(stdin)){
/* Trata erro de leitura. */
}
else{
/* Trata fim de arquivo. */
}
}
else{
char *const ultimo=nome+sizeof nome-1;
char *p, *q;
/*
Copia nome lido de ‘nome_mais_lf’ para ‘nome’, suprimindo eventual
quebra de linha.
*/
*ultimo='\0'; // Garante de antemão o terminador em ‘nome’.
for(p=nome, q=nome_mais_lf; p<ultimo && (*p=*q)!='\0'; p++, q++)
if(*p=='\n'){
*p='\0';
break;
}
/*
Neste ponto, p==nome+strlen(nome) e com certeza *p=='\0'.
Por sua vez, q==nome_mais_lf+strlen(nome), mas não necessariamente
*p==*q.
*/
if(*q=='\n'){
/*
A marca de fim de linha foi encontrada por fgets(), e todos os
caracteres antes dela foram copiados com sucesso para ‘nome’.
Pode usar o nome lido neste ponto à vontade.
*/
}
else if(p<ultimo || *q=='\0'){
/*
Nome truncado por interrupção prematura da leitura em fgets(),
antes de ler ‘sizeof nome’ (que é o mesmo que
‘sizeof nome_mais_lf-1’) caracteres.
*/
if(ferror(stdin)){
/*
Trata erro de leitura. Pode ser perigoso considerar o
nome lido como válido.
*/
}
else{
/*
Trata fim de arquivo. Tratar o nome como válido pode ser
aceitável ou não, dependendo da aplicação.
*/
}
}
else{
/*
Usuário digitou nome grande demais, e um dos caracteres
excedentes acabou sendo consumido por fgets() e guardado
em ‘nome_mais_lf’, mas não há espaço para ele em ‘nome’.
É possível devolver o caráter consumido a mais para o buffer
de entrada, fazendo “ungetc(*q, stdin)”, mas possivelmente
você vai preferir descartá-lo junto com os demais caracteres
excedentes que porventura ainda estejam no buffer de entrada,
e tentar uma nova leitura de um nome não truncado mais tarde.
*/
}
}
Já se você topar uma implementação em mais baixo nível, eis uma função feita sob medida para você, evitando o custo de chamar
strlen() e qualquer cópia entre arrays.
char *const ultimo=nome+sizeof nome-1;
char *p=nome;
int ch;
// Pula espaços em branco no início da linha.
while(isspace(ch=getchar()))
;
if(ch==EOF)
goto trata_erros;
*ultimo='\0';
for(p=nome; p<ultimo; p++)
switch(ch=getchar()){
case EOF:
goto trata_erros;
case '\n':
*p='\0';
goto saida;
default:
*p=ch;
}
/*
Chegou ao fim do loop mas ainda não encontrou marca de fim de linha.
Pode ser, porém, que ela seja o próximo caráter, então tenta ler mais
um, e vê se é fim de linha ou não.
*/
ch=getchar();
if(ch==EOF)
goto trata_erros;
if(ch!='\n'){
/*
Usuário digitou um nome grande demais.
É possível devolver o caráter consumido a mais por este último
getchar() para o buffer de entrada fazendo “ungetc(ch, stdin)”,
mas possivelmente você vai preferir descartá-lo junto com os
demais caracteres excedentes que porventura ainda estejam no
buffer de entrada, e tentar uma nova leitura de um nome não
truncado mais tarde.
*/
}
goto saida;
trata_erros:
if(p>nome){
/*
Conseguiu ler pelo menos alguns caracteres do nome.
*/
if(ferror(stdin)){
/*
Trata erro de leitura. Pode ser perigoso considerar o
nome lido como válido.
*/
}
else{
/*
Trata fim de arquivo. Tratar o nome como válido pode ser
aceitável ou não, dependendo da aplicação.
*/
}
}
else{
/*
O erro de leitura ou o fim dos dados ocorreu antes de ler qualquer
parte do nome.
*/
if(ferror(stdin)){
/* Trata erro de leitura. */
}
else{
/* Trata fim de arquivo. */
}
}
saida: