Desafio C

Tagged union para formas geométricas

Enunciado

Crie uma tagged union chamada Forma que pode representar um círculo (com raio float), um retângulo (com base e altura float) ou um triângulo (com base e altura float). Use um enum para a etiqueta. Implemente funções para calcular a área de cada forma e uma função area que recebe uma Forma e retorna a área correta. No main, crie uma forma de cada tipo e imprima suas áreas.

Requisitos

  • Definir um enum TipoForma com CIRCULO, RETANGULO, TRIANGULO.
  • Definir uma union com os dados de cada forma (círculo: raio; retângulo: base e altura; triângulo: base e altura).
  • Criar uma struct Forma que contenha o tipo e a union.
  • Implementar a função area que usa switch na etiqueta para calcular a área correta.
  • A saída deve mostrar as áreas com duas casas decimais.

Código inicial

#include <stdio.h>
#include <math.h>

// Defina o enum, a union e a struct aqui

// Implemente a função area

int main() {
    // Crie e teste as formas
    return 0;
}

Saída esperada

Área do círculo: 78.54
Área do retângulo: 12.00
Área do triângulo: 6.00
Ver dica

Use M_PI para o valor de π (inclua <math.h>). Na union, você pode ter uma struct para retângulo e triângulo, ou membros separados. Lembre-se de inicializar a etiqueta corretamente.

Mostrar solução
#include <stdio.h>
#include <math.h>

// Enum com os tipos de forma
typedef enum {
    CIRCULO,
    RETANGULO,
    TRIANGULO
} TipoForma;

// Union com os dados específicos de cada forma
typedef union {
    float raio;                     // para círculo
    struct { float base, altura; } ret; // para retângulo
    struct { float base, altura; } tri; // para triângulo
} DadosForma;

// Struct que combina a etiqueta e os dados
typedef struct {
    TipoForma tipo;
    DadosForma dados;
} Forma;

// Calcula a área com base no tipo
float area(Forma f) {
    switch (f.tipo) {
        case CIRCULO:
            return M_PI * f.dados.raio * f.dados.raio;
        case RETANGULO:
            return f.dados.ret.base * f.dados.ret.altura;
        case TRIANGULO:
            return (f.dados.tri.base * f.dados.tri.altura) / 2.0f;
        default:
            return 0.0f;
    }
}

int main() {
    // Círculo de raio 5
    Forma c = { .tipo = CIRCULO, .dados.raio = 5.0f };
    // Retângulo 4x3
    Forma r = { .tipo = RETANGULO, .dados.ret = { 4.0f, 3.0f } };
    // Triângulo base 4, altura 3
    Forma t = { .tipo = TRIANGULO, .dados.tri = { 4.0f, 3.0f } };

    printf("Área do círculo: %.2f\n", area(c));
    printf("Área do retângulo: %.2f\n", area(r));
    printf("Área do triângulo: %.2f\n", area(t));

    return 0;
}

Passo a passo

  1. Definimos o enum TipoForma com as constantes CIRCULO, RETANGULO, TRIANGULO.
  2. A union DadosForma contém um float raio para círculo e duas structs anônimas para retângulo e triângulo, cada uma com base e altura. Assim, cada forma usa apenas a memória necessária.
  3. A struct Forma agrupa a etiqueta tipo e os dados da union.
  4. A função area usa switch na etiqueta para acessar o membro correto da union e calcular a área. Para o círculo, usamos M_PI da <math.h>.
  5. No main, inicializamos três variáveis Forma usando inicializadores designados (.tipo = ..., .dados.raio = ..., etc.).
  6. Imprimimos as áreas com %.2f para duas casas decimais.

Por que funciona

A tagged union garante que sempre acessamos o membro correto da union, pois a etiqueta indica qual está ativo. O switch direciona para o cálculo apropriado. Como a union compartilha memória, a struct Forma ocupa o espaço do maior membro mais o espaço da etiqueta, economizando memória em comparação com uma struct que tivesse todos os campos.

Erros comuns

  • Acessar membro errado da union: se você criar uma Forma com tipo = CIRCULO mas tentar acessar f.dados.ret.base, o resultado é lixo. Sempre verifique a etiqueta antes de acessar.
  • Esquecer de incluir <math.h>: sem ela, M_PI não está definido e o compilador pode emitir erro. Alguns compiladores exigem #define _USE_MATH_DEFINES antes de <math.h> no Windows.
  • Não inicializar a union corretamente: ao usar inicializadores designados, certifique-se de que a etiqueta corresponda ao membro inicializado. Exemplo:
    Forma f = { .tipo = RETANGULO, .dados.raio = 5.0f }; // ERRO: raio não é retângulo
    
    O correto seria .dados.ret = { ... }.
  • Divisão inteira: na área do triângulo, usar / 2 em vez de / 2.0f pode truncar o resultado se base e altura forem inteiros. Como são float, / 2.0f garante divisão real.

Outra forma de resolver

Poderia usar uma struct com todos os campos e uma flag booleana para indicar qual usar, mas isso desperdiça memória. A tagged union é mais eficiente. Outra alternativa é usar void* e casts, mas perde a segurança de tipos. A tagged union é o padrão em C para tipos variantes.

Saída esperada

Área do círculo: 78.54
Área do retângulo: 12.00
Área do triângulo: 6.00