O que é conversão de tipos
Converter tipos significa transformar um valor de um tipo em outro: transformar um double em um int, um char em seu código numérico, ou um ponteiro para a classe base em um para a classe derivada. O C++ faz algumas dessas conversões por você automaticamente, mas as que ele faz em silêncio são exatamente onde os bugs se escondem.
Existem dois sabores: conversões implícitas que acontecem sozinhas e casts explícitos que você mesmo escreve. Você já viu um sintoma disso em operadores: a divisão inteira. O cast é como você assume o controle disso.
Conversões implícitas
Quando você mistura tipos numéricos em uma expressão, o C++ promove o tipo "menor" para o "maior" para que os dois lados combinem. Geralmente isso faz o que você quer.
O problema começa quando a conversão vai no sentido oposto: de um tipo mais largo para um mais estreito. Isso é uma conversão de estreitamento, e ela pode perder dados em silêncio.
De float para int, trunca em direção a zero: não arredonda, então 3.99 vira 3. E enfiar 300 em um char causa estouro. Muitos compiladores avisam aqui; alguns não. Quando você realmente quiser estreitar, diga isso explicitamente com um cast para que o próximo leitor saiba que foi de propósito.
Corrigindo a armadilha da divisão inteira
O motivo mais comum para fazer um cast é a divisão. Quando os dois operandos são inteiros, / faz divisão inteira e descarta o resto.
A correção é static_cast<double> em um operando antes da divisão. Um erro frequente é static_cast<double>(got / total): é tarde demais, porque got / total já vale 0 quando o cast roda, então você obtém 0.0. Faça o cast de um operando, não do resultado.
static_cast: seu cast padrão
O C++ oferece quatro casts nomeados. O que você vai usar em 95% das vezes é static_cast<T>(value), que realiza conversões bem definidas entre tipos relacionados: conversões numéricas, de enum para int, de void* de volta para um ponteiro tipado, e subir ou descer em uma hierarquia de classes quando você já conhece o tipo.
Prefira static_cast ao antigo cast no estilo C (int)balance. Um cast no estilo C vai tentar qualquer conversão para fazer o código compilar, incluindo as perigosas abaixo, então ele pode remover o const em silêncio ou reinterpretar bytes brutos. O static_cast só permite conversões que o compilador consegue de fato justificar, e o verboso static_cast<...> é trivial de localizar em uma revisão de código.
// Evite - cast no estilo C, sem rede de segurança:
int dollars = (int) balance;
// Prefira - explícito, verificado, fácil de localizar:
int dollars = static_cast<int>(balance);
Os outros três casts (use com parcimônia)
Os casts restantes existem para tarefas específicas e bem estreitas. Recorra a eles apenas quando o static_cast realmente não der conta.
const_cast remove (ou adiciona) const. Seu único uso legítimo é chamar uma API no estilo C que esqueceu de marcar um parâmetro como const. Modificar, através de um const_cast, um objeto que foi originalmente declarado como const é comportamento indefinido.
void legacyApi(char* msg); // API antiga, não aceita const
const char* text = "hello";
legacyApi(const_cast<char*>(text)); // só é seguro se legacyApi não escrever nele
reinterpret_cast reinterpreta o padrão de bits bruto, por exemplo um ponteiro como um endereço inteiro. Não realiza nenhuma conversão e é extremamente inseguro; quase sempre é um sinal de que você deveria repensar o design.
dynamic_cast converte com segurança um ponteiro ou referência da classe base para um tipo derivado em tempo de execução, usando o tipo real do objeto. Requer uma base polimórfica (uma classe com pelo menos uma função virtual) e retorna nullptr se o cast não se aplicar.
Se a tivesse apontado para outro Animal, dynamic_cast<Dog*> retornaria nullptr e o ramo else rodaria, que é exatamente por que ele é mais seguro do que usar static_cast às cegas para descer em uma hierarquia.
Erros comuns a evitar
- Fazer o cast do resultado em vez de um operando.
static_cast<double>(a / b)descarta sua fração primeiro. Faça o cast deaoub. - Supor que de float para int arredonda. Ele trunca:
static_cast<int>(2.99)é2. Para arredondar, usestd::round,std::lround, etc. - Recorrer a um cast no estilo C. Ele esconde qual conversão acontece. Use
static_caste você receberá um erro de compilação quando a conversão for insegura, em vez de uma surpresa silenciosa. - Estreitar para um tipo pequeno demais. Fazer o cast de
300paracharou de umlongenorme paraintdá a volta ou estoura. Escolha um tipo de destino largo o bastante para o intervalo.
Próximo: If-Else
Agora que você consegue converter e comparar valores com clareza, o próximo passo é tomar decisões com eles. A instrução if-else executa código diferente dependendo de uma condição ser true: a base de todo programa com ramificações.
Perguntas frequentes
Qual é a diferença entre static_cast e um cast no estilo C em C++?
Um cast no estilo C como (int)x tenta cada conversão por vez: pode se tornar silenciosamente um perigoso reinterpret_cast ou remover o const. static_cast<int>(x) só realiza conversões relacionadas que o compilador consegue verificar, então o compilador rejeita absurdos. Em C++ moderno, prefira sempre static_cast aos casts no estilo C; é mais seguro e muito mais fácil de localizar com grep.
Como faço o cast de um int para um double em C++?
Use static_cast<double>(x). Isso importa principalmente na divisão: 5 / 2 é divisão inteira e dá 2, mas static_cast<double>(5) / 2 dá 2.5. Faça o cast de um dos operandos antes de a divisão acontecer: fazer o cast do resultado, static_cast<double>(5 / 2), é tarde demais e ainda dá 2.0.
Por que fazer o cast de um valor grande para um tipo menor dá um número errado em C++?
Converter para um tipo que não consegue armazenar o valor é uma conversão de estreitamento. De float para int, a parte fracionária é truncada (static_cast<int>(3.99) é 3), e um inteiro fora do intervalo ou dá a volta (sem sinal) ou fica definido pela implementação (com sinal). O compilador geralmente não te impede, então faça o cast de forma deliberada e garanta que o tipo de destino seja largo o bastante.