Ferramentas do usuário

Ferramentas do site


prog2:conversao_de_tipos

Conversão de tipos

Muitas vezes, operações lógicas/aritméticas envolvem valores de tipos distintos, como char, int, float e double. Algumas conversões são efetuadas implicitamente pelo compilador, sem riscos para a integridade dos dados manipulados. Todavia, algumas conversões podem levar à perda de informação e precisam ser “forçadas” pelo programador, através de conversões explícitas.

Conversão implícita

Quando uma operação lógica/aritmética envolve dois operandos de tipos distintos, o compilador primeiro os converte para um único tipo, antes de avaliá-la. Nos caso de tipos numéricos, essa conversão é definida pela regra de promoção automática, segundo a qual o tipo de menor tamanho (com menos bytes) é automaticamente convertido (“promovido”) para o tipo de maior tamanho (com mais bytes).

A hierarquia de tipos considerada para a promoção automática é a seguinte (os tipos unsigned estão no mesmo nível de hierarquia que seus equivalentes signed):

char < short < int < long < long long < float < double < long double

O código a seguir mostra alguns exemplos de promoção automática:

char   c = 'A' ;
int    i = 3, j = 54;
short  k = 31 ;
float  x = 31.8 ;
double y ;
 
j = c + 30 ;	//  o valor de c (65) é convertido de char para int
 
y = i * x + k ;	// ao avaliar i*x,       i é promovido de int a float
                // ao avaliar float + k, k é promovido de short a float

A conversão implícita (ou automática) é realizada para as operações aritméticas (+ - * / %), relacionais (< <= > >= == !=), com bits (& | ^) e para o operador condicional (? :).

Os operadores de atribuição (=, =+, …) também recebem uma conversão implícita de tipo, para compatibilizar o valor atribuído (lado direito da atribuição) à variável que está sendo atribuída (lado esquerdo).

Erros de conversão

A regra de promoção automática é aplicada A CADA OPERAÇÃO da expressão lógica/aritmética. Por isso, deve ser tomado cuidado em expressões mais complexas. O código abaixo traz um exemplo dos riscos envolvidos:

erro-conversao.c
#include <stdio.h>
 
int main ()
{
  int i   = 100 ;
  int j   = 6 ;
  float x = 6 ;
  float y ;
 
  y = i / j * x ;              // (int / int) * float  -> int * float
  printf ("y vale %f\n", y) ;  //  ^^^^^^^^^ erro de arredondamento!
 
  y = x * i / j ;              // (float * int) / int
  printf ("y vale %f\n", y) ;  // resultado correto (vide abaixo)
}

Ao avaliar primeiro a operação i/j, ocorre um erro de arredondamento:

$ ./a.out 
y vale 96.000000
y vale 100.000000

As expressões são usualmente avaliadas da esquerda para a direita, mas isso depende do compilador e do nível de otimização usado. Por isso, a ordem de avaliação das operações em uma expressão envolvendo tipos distintos deve ser explicitada usando parênteses:

y = (x * i) / j ;            // (float * int) / int
printf ("y vale %f\n", y) ;  // resultado correto sempre

A conversão implícita na atribuição também pode provocar perda de informação. Eis alguns exemplos:

char c = 'A' ;
int i, j = 34 ;
long long k = 12345677890 ;
float x, y = 451.28 ;
double pi = 3.141592653589793264 ;
 
i = c ;		// ok, pois int > char
x = i ;		// geralmente ok, pois float > int (* vide abaixo)
i = y ;		// problema, parte fracionária será truncada
x = pi ;	// problema, perda de precisão
i = k ;		// problema, perderá os bits mais significativos

Observe que a precisão de um float é menor que a de um int a partir de 223+1, pois a mantissa do float tem apenas 23 bits (padrão IEEE 754). Assim, no caso de um valor inteiro muito grande (> 223), a conversão do mesmo em float implica em perda de precisão.

Conversão explícita

Em alguns casos, é necessário forçar a conversão de tipos de dados, para que uma expressão seja avaliada da forma correta.

Por exemplo:

int soma, num ;
float media ;
 
soma = 10 ;
num  = 4 ;
 
media = soma / num ;		// media = 2.0 (por que?)

No caso acima, a expressão soma / num será avaliada como int / int, resultando em uma divisão inteira e consequente perda de precisão. Para gerar o resultado correto, a expressão deve ser avaliada como float. Isso pode ser obtido de duas formas:

  • adicionando um elemento neutro de tipo float à expressão;
  • forçando a avaliação de soma ou num como float.
int soma, num ;
float media ;
 
soma = 10 ;
num  = 4 ;
 
media = soma / num ;		// errado, perda de precisão
media = 1.0 * soma / num ;	// soma é "promovida" a float
media = (float) soma / num ;	// soma é avaliada como float (casting)
media = soma / (float) num ;	// num é avaliado como float (casting)

A avaliação forçada de um valor ou variável para um tipo específico usando o prefixo (type), como no exemplo acima, é chamada type casting. Essa operação é muito usada, sobretudo na avaliação de ponteiros.

Conversão de/para strings

No caso específico de strings, a conversão destas para outros tipos é efetuada através de funções específicas:

#include <stdlib.h>
 
double    atof  (const char *str);	// string to float (double)
 
int       atoi  (const char *str);	// string to integer
 
long      atol  (const char *str);	// string to long
 
long long atoll (const char *str);	// string to long long 

No outro sentido, a forma mais simples de converter um dado de qualquer tipo para string é usando a função sprintf, que formata e “imprime” o dado em uma string, de forma similar ao que a função printf realiza na saída padrão:

conversion.c
#include <stdio.h>
 
int main ()
{
  char buffer[256] ;
  float x ;
 
  x = 32.4 / 7 ;
 
  sprintf (buffer, "%5.4f", x) ; // "imprime" x na string buffer
 
  printf ("%s\n", buffer) ;
 
  return 0 ;
}

Conversão de ponteiros

Uma operação frequente em C é a conversão de tipos de ponteiros. Muitas funções importantes, como qsort e bsearch, usam ponteiros genéricos void* como parâmetros de entrada, para poder receber dados de diversos tipos.

Como ponteiros para void não apontam para nenhum tipo válido de dado, eles não podem ser desreferenciados (ou seja, não é possível acessar diretamente os dados que eles apontam). Por isso, ponteiros para void precisam ser convertidos em ponteiros para algum tipo válido antes de serem desreferenciados.

O exemplo a seguir mostra como ponteiros void* são convertidos em int*, dentro da função compara_int(a,b):

qsort.c
#include <stdio.h>
#include <stdlib.h>
 
int vetor[1000] ;
int vsize ;
 
// compara dois inteiros apontados por "a" e "b"
int compara_int (const void* a, const void* b)
{
  int *pa, *pb ;
 
  pa = (int*) a ;  // "vê" a como int*
  pb = (int*) b ;  // idem, b
 
  if (*pa > *pb) return  1 ;
  if (*pa < *pb) return -1 ;
  return 0 ;
}
 
int main ()
{
  int i ;
 
  // preenche o vetor de inteiros com aleatórios
  vsize = 1000 ;
  for (i = 0; i < vsize; i++)
    vetor[i] = random() % 1000 ;
 
  // ordena o vetor (man qsort)
  qsort (vetor, vsize, sizeof (int), compara_int) ;
 
  // escreve o vetor
  for (i = 0; i < vsize; i++)
    printf ("%d ", vetor[i]) ;
  printf ("\n") ;
}

Leitura complementar: Type Conversions, C in a Nutshell.

prog2/conversao_de_tipos.txt · Última modificação: 2019/10/14 13:27 por maziero