C – Lendo de e para strings com sscanf/sprintf

As vezes precisamos, em linguagem C, quebrar uma cadeia de caracteres (string) em pequenas partes para que estes dados sejam manipulados. A solução que conhecia era ler toda a string, caracter a caracter, e quando achar espaço ou outro delimitador ir separando os dados em variáveis, o que dificulta a leitura e manutenção do código.

Isso era antes de conhecer a sscanf e sprintf, e o post é sobre essas funções.

Sscanf e sprintf, são semelhantes ao scanf/printf exceto que a entrada/saída de dados é a partir de uma string.
Além de existirem em linguagem C, também estão presentes em C++ e PHP (com algumas diferenças).

SSCANF

Sintaxe:

sscanf(char *str, char *frmt, var1, ...)

str: string de onde os dados serão lidos.
frmt: formato usado na string lida.

Algumas informações sobre o formato:
– Pode-se usar palavras no sscanf, elas serão lidas e comparadas com a entrada:
Exemplo: aceita entrada no formato “v1 10 op + v2 9”

sscanf(calculo, "v1 %f op %c v2 %f", &operando1, &operacao, &operando2);

– Pode-se usar o %* para ignorar um dado. Ele não será armazenado no argumento correspondente.

sscanf(calculo, "%f %*c %f", &operando1, &operando2);

– Pode especificar uma lista de caracteres permitidos.
No exemplo, se a operação for diferente de “+, -, *, /”, ocorre erro.

sscanf(calculo, "%f %[+-/*] %f", &operando1, &operacao, &operando2);

– Pode-se especificar a quantidade máxima de caracteres que pode ser lida.
No exemplo, aceita somente um caracter da lista: +, -, /, ou *.

sscanf(calculo, "%f %1[+-/*] %f", &operando1, &operacao, &operando2);

var1: Ponteiros ou endereços para os elementos a serem lidos.

Em caso de sucesso, a função retorna o número de itens lidos com sucesso, convertidos e armazenados. O valor retornado não inclui campos que não foram armazenados (%*). Se uma falha de correspondência acontecer, este número pode variar entre o valor esperado até zero. retorna EOF se a função tentar ler um EOF (entrada vazia, por exemplo).

Para um correto funcionamento da função, recomendo usá-la juntamente com um if (como no exemplo abaixo), para testar se a entrada foi associada às variáveis corretamente. Caso ocorra algum erro na correspondência (entrada exceder ao tamanho do dado, tipo do dado diferente, etc), o erro é mostrado.

Bom, vamos ao primeiro exemplo:

/*
Exemplo do uso de sscanf

Arquivo: sscanf.c
Compilar: gcc sscanf.c -o sscanf.o
Executar: ./sscanf.o
*/

#include <stdio.h>

int main()
{
	/* máximo de 20 caracteres + \0 */
	char calculo[21];
	float operando1;
	float operando2;
	char operacao;
	
	/* número máximo de caracteres é 20 */
	fgets(calculo, 21, stdin);
	printf("Calculo é: %s", calculo);

	if(sscanf(calculo, "%f %1[+-/*] %f", &operando1, &operacao, &operando2) == 3)
	{
		printf("-> Operando 1: %.2f\n", operando1);
		printf("-> Operando 2: %.2f\n", operando2);
		printf("-> Operação: %c\n", operacao);
	}
	else
		printf("Erro no sscanf!\n");
	
	return 0;
}

Download do código
O código acima lê os dados do teclado para a string calculo, quebra esta string separando os dados em variáveis, e estas são mostradas no final do programa.

– Ok, mas porque não ler os dados separados e sequencialmente, e então realizar o cálculo?
– Esta forma pode ser útil, por exemplo, quando precisamos enviar uma mensagem entre cliente/servidor utilizando sockets.

O servidor recebe os dados, realiza o cálculo e retorna o resultado para o cliente, enquanto que o cliente somente envia os dados e aguarda o resultado.

Voltando ao exemplo, o fgets lê a string da entrada padrão (teclado – stdin), armazena em uma variável (calculo). A quantidade de caracteres fornecidas pela entrada não pode exceder a um determinado tamanho (20) para não estourar o buffer da variável “calculo”. Se exceder, o fgets desconsidera os caracteres a mais.

...
fgets(calculo, 21, stdin);
...

No código o valor está “21” porque é necessário reservar uma posição para o caracter que determina o final da string (\0), logo, se quero ler no máximo 5 letras da entrada, a função deve ser fgets(variavel, 6, stdin);.

Continuando…
Aqui é onde a mágica acontece !

...
sscanf(calculo, "%f %1[+-/*] %f", &operando1, &operacao, &operando2);
...

No código acima, o sscanf quebra a string “calculo” usando a máscara “%f %1[+-/*] %f”. Isto quer dizer que os dados fornecidos são: um float, somente um char (que pode ser +,-,/ ou *) e outro float, nesta ordem e separados por espaços. O primeiro float ficará armezanado na variável “&operando1”, o caracter ficará armazenado na variável “&operacao” e o último float ficará armazenado na variável “&operando2”. Como foi dito anteriormente, o uso do if diminuirá muito a possibilidade de erros na associação dos dados no processo de leitura.

FGETS

Como visto, usamos a função “fgets” para leitura da string através do teclado. A função fgets é semelhante à função gets(), porém, além de poder fazer a leitura a partir de um arquivo de dados (ou stdin) também armazena a quebra de linha. O caracter de nova linha (\n) faz o fgets parar de ler, mas é considerado um caractere válido e, portanto, é incluído na string. Ainda é possível especificar o tamanho máximo da string de entrada. Funções como gets e scanf não tem este controle, o que poderia acarretar erros de “estouro de buffer”.

– Recomenda-se a utilização da fgets seguida de sscanf no lugar de scanf para leitura de dados do teclado.

Sintaxe:

char *fgets(char *str, int tamanho, FILE *fluxo);

str: string onde os dados lidos do teclado serão armazenados.
tamanho: quantidade máxima de caracteres que podem ser lidos, contando com o caracter nulo que é inserido no final da string. Um caractere nulo (\0) é automaticamente acrescentado na string após a leitura de caracteres, para sinalizar o fim da seqüência.
fluxo: arquivo ou entrada padrão (teclado – stdin).

Em caso de sucesso, a função retorna o parâmetro str (string lida). Se o EOF for encontrado, EOF é definido para o fluxo e fgets retorna um ponteiro nulo. Se ocorrer um erro de leitura, o indicador de erro é definido no fluxo e fgets retorna um ponteiro nulo, e define errno para indicar o erro.

SPRINTF

A função sprintf é semelhante ao sscanf, a diferença é que invés de quebrar a string em variáveis, ela junta variáveis e monta tudo em uma string.

Sintaxe:

int sprintf(char *str, const char *frmt, arg1, ...);

str: é a variável string que receberá os dados formatados.
frmt: Pode ser usado qualquer caractere de formatação da função printf, como: %c, %s, %d, %f, etc, ou qualquer texto que desejar acrescentar à formatação.

– O formato das tags que podem ser usadas: %[flags][largura][.precisão]especificador.

Flags:

-: Pode-se justificar o dado à esquerda, usando %-. O padrão é justificado à direita.
Exemplo:

sprintf(resposta, "\n%-8.2f %c %.2f = %.2f", operando1, operacao, operando2, resultado);

+: Pode-se forçar a exibição do sinal do número (-/+, negativo/positivo) usando %+. Por padrão, somente o sinal de negativo é exibido. Usando a tag “+”, o sinal será exibido tanto para números negativos quanto positivos.

espaço: Ao usar o (% ), se o sinal do número não for exibido antes do número será mostrado um espaço.

0: Pode-se usar %0 para completar a largura do número com zeros à esquerda.
Exemplo:

sprintf(resposta, "\n%08.2f %c %.2f = %.2f", operando1, operacao, operando2, resultado);

#: Ao usar %# antes dos especificadores “o”, “x” ou “X” o valor será precedido com “0”, “0x” ou “0X”, respectivamente, para valores diferentes de zero. Ao usar antes dos especificadores “e”, “E” e “f”, força que a saída contenha ponto decimal, mesmo que não seja um número decimal. Por padrão, se não há números após a vírgula o ponto decimal não é exibido. Ao usar antes de “g” ou “G”, o resultado é o mesmo que com “e ou “E”, mas os zeros à direita não são removidos.
Exemplo:

sprintf(resposta, "\n%#f %c %.2f = %.2f", operando1, operacao, operando2, resultado);

Largura:

número: Número mínimo de caracteres a ser impresso. Se o valor a ser impresso é menor do que este número, o resultado será preenchido com espaços em branco. O valor não é truncado, mesmo se o resultado for maior.
Exemplo:

sprintf(resposta, "\n%8.2f %c %.2f = %.2f", operando1, operacao, operando2, resultado);

*: A largura não é especificada no formato, mas como um argumento adicional (valor inteiro) que precede o argumento que será formatado. Neste exemplo e no acima, ambos terão o número com 8 casas preenchidas com espaços.
Exemplo:

sprintf(resposta, "\n%*.2f %c %.2f = %.2f", 8, operando1, operacao, operando2, resultado;

Precisão:

.número: Pode-se especificar a precisão do número. Ao usar %.2f estamos definindo que o valor é um float e terá dois dígitos após a vírgula.

.*: A precisão não é especificada no formato, mas como um argumento adicional (valor inteiro) que precede o argumento que será formatado. O exemplo abaixo mostrará 3 casas após a vírgula.
Exemplo:

sprintf(resposta, "\n%.*f %c %.2f = %.2f", 3, operando1, operacao, operando2, resultado);

Especificadores:

Os mesmos usados no printf, como %c, %s, %d, %f, %x, %E, etc.

arg1: são as variáveis que irão compor a string final.

Em caso de sucesso, o retorno é igual ao número total de caracteres escritos. Essa contagem não inclui os caracteres \0 adicionados ao final da string. Em caso de falha, um número negativo é retornado.

Vamos ao segundo exemplo, incrementamos o código anterior com o sprintf:

/*
Exemplo do uso de sprintf

Arquivo: sprintf.c
Compilar: gcc sprintf.c -o sprintf.o
Executar: ./sprintf.o
*/

#include <stdio.h>

float fResultado(float *op1, float *op2, char *oper)
{
	switch(*oper)
	{
		case '+':
			{
				return (*op1 + *op2);
				break;
			}
		case '-':
			{
				return (*op1 - *op2);
				break;
			}
		case '*':
			{
				return (*op1 * *op2);
				break;
			}
		case '/':
			{
				return (*op1 / *op2);
				break;
			}
		default: 
			{
				printf("Operação não reconhecida\n !");
				break;
			}
	}
}

int main()
{
	/* máximo de 20 caracteres + \0 */
	char calculo[21];
	float operando1;
	float operando2;
	char operacao;
	float resultado;
	char resposta[60];
	
	/* número máximo de caracteres é 20 */
	fgets(calculo, 21, stdin);
	printf("Calculo é: %s", calculo);

	if(sscanf(calculo, "%f %1[+-/*] %f", &operando1, &operacao, &operando2) == 3)
	{
		printf("-> Operando 1: %.2f\n", operando1);
		printf("-> Operando 2: %.2f\n", operando2);
		printf("-> Operação: %c\n", operacao);

		/* recebe o cálculo de outra função */
		resultado = fResultado(&operando1, &operando2, &operacao);

		/* escreve o resultado na string resposta */
		sprintf(resposta, "\n--> Resposta: %.2f %c %.2f = %.2f", operando1, operacao, operando2, resultado);

		printf("%s\n", resposta);
	}
	else
		printf("Erro no sscanf!\n");
	
	return 0;
}

Download do código
No código acima, após ler a entrada, quebrar a string em outras variáveis, passamos estas informações para uma função que, dependendo da operação, irá realizar uma soma, subtração, multiplicação ou divisão. Logo, este resultado é retornado e será usado para compor uma nova string.

...
sprintf(resposta, "\n--> Resposta: %.2f %c %.2f = %.2f", operando1, operacao, operando2, resultado);

printf("%s\n", resposta);
...

A string “resposta” será composta de: uma quebra de linha, de --> Resposta:, dos operandos (1 e 2) lidos no início do programa, = resultado do cálculo. Depois de formada, a string é mostrada na tela com o uso do printf.

Referências:

sscanf:

http://docs.roxen.com/pike/7.0/tutorial/strings/sscanf.xml
http://www.ime.usp.br/~mms/mac2121s2003/mac2121s2003aula14.pdf
http://equipe.nce.ufrj.br/adriano/c/apostila/funcoes/entrada.htm
http://www.cplusplus.com/reference/clibrary/cstdio/sscanf/

fgets:
http://mtm.ufsc.br/~azeredo/cursoC/aulas/c970.html
http://www.cplusplus.com/reference/clibrary/cstdio/fgets/
http://www.linhadecodigo.com.br/Dica.aspx?id=1141

sprintf:
http://www.cplusplus.com/reference/clibrary/cstdio/sprintf/
http://zz2kzq.site90.com/CursoHtml/Modulo04/Modulo004Aula007.htm

Anúncios
Marcado com: , , ,
Publicado em programação, windows/linux
2 comentários em “C – Lendo de e para strings com sscanf/sprintf
  1. blocoitp disse:

    Tem erro no trecho seguinte e na tua forma de trabalhar com strings terminado por zero: deve-se reservar o espaço que vai guardar esse zero. Assim no trecho abaixo, o vetor calculo não possui espaço suficiente e você tem um estouro de buffer potencial aí…

    char calculo[20];

    /* número máximo de caracteres é 20 */
    fgets(calculo, 21, stdin);

    • eddye disse:

      Obrigado blocoitp, realmente havia erro.

      Quando escrevi o post pensei em reservar a posição para o \0 na hora da leitura do teclado (máximo de 20 caracteres + \0), mas não reservei o mesmo espaço na variável calculo (o que resultaria somente os 20 caracteres, sem o terminador \0). Muito bem observado !

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s

%d blogueiros gostam disto: