---
title: "Calculadora CLI em Zig — Tutorial Passo a Passo"
url: "https://ziglang.com.br/projetos/calculadora-cli-em-zig-tutorial-passo-a-passo/"
markdown_url: "https://ziglang.com.br/projetos/calculadora-cli-em-zig-tutorial-passo-a-passo.MD"
description: "Construa uma calculadora interativa de linha de comando em Zig com suporte a operações básicas, histórico e tratamento de erros."
date: "2026-02-21"
author: "Zig Brasil"
---

# Calculadora CLI em Zig — Tutorial Passo a Passo

Construa uma calculadora interativa de linha de comando em Zig com suporte a operações básicas, histórico e tratamento de erros.


# Calculadora CLI em Zig — Tutorial Passo a Passo

Neste tutorial, vamos construir uma **calculadora interativa de linha de comando** em Zig. Este é um excelente primeiro projeto porque cobre conceitos fundamentais: leitura de entrada do usuário, parsing de strings, tratamento de erros e organização de código.

## O Que Vamos Construir

Nossa calculadora vai:

- Aceitar expressões matemáticas como `3 + 4` ou `10 / 2`
- Suportar as quatro operações básicas (+, -, *, /)
- Manter um histórico dos últimos cálculos
- Tratar erros como divisão por zero e entrada inválida
- Rodar em loop até o usuário digitar `sair`

## Por Que Este Projeto?

Uma calculadora CLI é o "Hello World" dos projetos reais. Ela força você a lidar com problemas que não existem em exemplos triviais: parsing de entrada do usuário (que pode conter qualquer coisa), tratamento robusto de erros e loop de interação. Em Zig, isso nos dá a oportunidade de explorar o sistema de erros da linguagem, que é um dos seus diferenciais.

## Pré-requisitos

- Zig 0.13+ instalado ([guia de instalação](/tutoriais/instalacao/))
- Familiaridade com conceitos básicos de Zig ([fundamentos](/tutoriais/fundamentos/))

## Passo 1: Estrutura do Projeto

Crie o diretório do projeto e o arquivo de build:

```bash
mkdir calculadora-cli
cd calculadora-cli
zig init
```

Isso cria a estrutura padrão. Vamos trabalhar no arquivo `src/main.zig`.

## Passo 2: Definindo os Tipos

Começamos definindo os tipos que representam nossa calculadora. Em Zig, é uma boa prática definir tipos explícitos em vez de usar primitivos soltos — isso torna o código mais legível e seguro.

```zig
const std = @import("std");
const io = std.io;
const fmt = std.fmt;
const mem = std.mem;

/// Operações matemáticas suportadas pela calculadora.
/// Usamos um enum porque o conjunto de operações é fixo e conhecido
/// em tempo de compilação — isso permite que o compilador nos avise
/// se esquecermos de tratar algum caso em um switch.
const Operacao = enum {
    soma,
    subtracao,
    multiplicacao,
    divisao,

    /// Converte um caractere para a operação correspondente.
    /// Retorna erro se o caractere não for um operador válido.
    pub fn deCaractere(c: u8) !Operacao {
        return switch (c) {
            '+' => .soma,
            '-' => .subtracao,
            '*' => .multiplicacao,
            '/' => .divisao,
            else => error.OperadorInvalido,
        };
    }

    /// Retorna o símbolo da operação para exibição.
    pub fn simbolo(self: Operacao) u8 {
        return switch (self) {
            .soma => '+',
            .subtracao => '-',
            .multiplicacao => '*',
            .divisao => '/',
        };
    }
};

/// Representa uma expressão matemática parseada.
/// Separamos o parsing da execução para facilitar testes
/// e permitir exibir a expressão formatada.
const Expressao = struct {
    operando_a: f64,
    operacao: Operacao,
    operando_b: f64,
};

/// Erros possíveis durante o parsing e execução.
const CalcError = error{
    OperadorInvalido,
    NumeroInvalido,
    DivisaoPorZero,
    ExpressaoIncompleta,
    FormatoInvalido,
};
```

**Por que usar um `enum` para operações?** Porque o compilador Zig nos obriga a tratar todos os casos em um `switch`. Se adicionarmos uma nova operação no futuro, o compilador vai nos avisar de todos os lugares que precisam ser atualizados. Isso é muito mais seguro do que comparar caracteres soltos.

## Passo 3: Parser de Expressões

Agora implementamos o parser que transforma a string digitada pelo usuário em uma `Expressao` estruturada.

```zig
/// Faz o parsing de uma string de entrada como "3.5 + 2.1"
/// e retorna uma Expressao estruturada.
///
/// Decisão de design: usamos split simples por espaços em vez de
/// um parser mais sofisticado. Para uma calculadora básica, isso é
/// suficiente e mantém o código acessível para iniciantes.
fn parsearExpressao(entrada: []const u8) CalcError!Expressao {
    // Remove espaços em branco nas bordas
    const limpa = mem.trim(u8, entrada, " \t\r\n");

    if (limpa.len == 0) return CalcError.ExpressaoIncompleta;

    // Dividimos por espaço — esperamos o formato "A op B"
    var partes = mem.splitSequence(u8, limpa, " ");

    // Primeiro operando
    const str_a = partes.next() orelse return CalcError.ExpressaoIncompleta;
    const a = fmt.parseFloat(f64, str_a) catch return CalcError.NumeroInvalido;

    // Operador
    const str_op = partes.next() orelse return CalcError.ExpressaoIncompleta;
    if (str_op.len != 1) return CalcError.OperadorInvalido;
    const op = Operacao.deCaractere(str_op[0]) catch return CalcError.OperadorInvalido;

    // Segundo operando
    const str_b = partes.next() orelse return CalcError.ExpressaoIncompleta;
    const b = fmt.parseFloat(f64, str_b) catch return CalcError.NumeroInvalido;

    return Expressao{
        .operando_a = a,
        .operacao = op,
        .operando_b = b,
    };
}
```

**Por que split por espaços?** É a abordagem mais simples e funcional para o nosso caso. Um parser mais robusto (que aceitasse `3+4` sem espaços) exigiria um tokenizer, o que adicionaria complexidade sem benefício proporcional neste projeto introdutório.

## Passo 4: Motor de Cálculo

Com a expressão parseada, o cálculo em si é direto:

```zig
/// Executa o cálculo representado por uma expressão.
/// Retorna erro apenas em caso de divisão por zero.
fn calcular(expr: Expressao) CalcError!f64 {
    return switch (expr.operacao) {
        .soma => expr.operando_a + expr.operando_b,
        .subtracao => expr.operando_a - expr.operando_b,
        .multiplicacao => expr.operando_a * expr.operando_b,
        .divisao => {
            if (expr.operando_b == 0.0) return CalcError.DivisaoPorZero;
            return expr.operando_a / expr.operando_b;
        },
    };
}
```

**Por que verificar divisão por zero explicitamente?** Em muitas linguagens, dividir por zero com floats retorna `Infinity` ou `NaN`. Embora Zig também faça isso com `f64`, preferimos tratar como erro explícito para dar feedback claro ao usuário. Essa é uma decisão de UX, não de correção matemática.

## Passo 5: Histórico de Operações

Vamos adicionar um histórico para que o usuário possa rever cálculos anteriores. Usamos um buffer de tamanho fixo para evitar alocação dinâmica — uma prática comum e idiomática em Zig.

```zig
/// Registro de uma operação realizada.
const RegistroHistorico = struct {
    expressao: [64]u8,
    expressao_len: usize,
    resultado: f64,
};

/// Histórico circular de operações.
/// Usamos um buffer fixo em vez de ArrayList porque:
/// 1. Não precisamos de alocação dinâmica
/// 2. O tamanho máximo é conhecido e pequeno
/// 3. O comportamento circular (sobrescrever antigos) é desejável
const Historico = struct {
    registros: [20]RegistroHistorico,
    quantidade: usize,
    proximo_indice: usize,

    const Self = @This();

    pub fn init() Self {
        return Self{
            .registros = undefined,
            .quantidade = 0,
            .proximo_indice = 0,
        };
    }

    pub fn adicionar(self: *Self, expr: Expressao, resultado: f64) void {
        var buf: [64]u8 = undefined;
        const texto = fmt.bufPrint(&buf, "{d} {c} {d} = {d:.4}", .{
            expr.operando_a,
            expr.operacao.simbolo(),
            expr.operando_b,
            resultado,
        }) catch return;

        self.registros[self.proximo_indice] = .{
            .expressao = buf,
            .expressao_len = texto.len,
            .resultado = resultado,
        };

        self.proximo_indice = (self.proximo_indice + 1) % 20;
        if (self.quantidade < 20) self.quantidade += 1;
    }

    pub fn exibir(self: *const Self, writer: anytype) !void {
        if (self.quantidade == 0) {
            try writer.print("  (nenhum cálculo realizado ainda)\n", .{});
            return;
        }

        var i: usize = 0;
        while (i < self.quantidade) : (i += 1) {
            const idx = if (self.quantidade < 20)
                i
            else
                (self.proximo_indice + i) % 20;

            const reg = self.registros[idx];
            try writer.print("  {s}\n", .{reg.expressao[0..reg.expressao_len]});
        }
    }
};
```

## Passo 6: Formatação de Erros

Uma boa calculadora precisa de mensagens de erro claras:

```zig
/// Retorna uma mensagem amigável para cada tipo de erro.
/// Decisão: mensagens em português e descritivas, porque o público
/// é brasileiro e mensagens técnicas em inglês não ajudam um iniciante.
fn mensagemErro(err: CalcError) []const u8 {
    return switch (err) {
        CalcError.OperadorInvalido => "Operador inválido. Use +, -, * ou /",
        CalcError.NumeroInvalido => "Número inválido. Use números como 3, 3.14 ou -2",
        CalcError.DivisaoPorZero => "Erro: divisão por zero não é permitida",
        CalcError.ExpressaoIncompleta => "Expressão incompleta. Use o formato: 3 + 4",
        CalcError.FormatoInvalido => "Formato inválido. Use o formato: número operador número",
    };
}
```

## Passo 7: Loop Principal

Finalmente, montamos o loop interativo que une tudo:

```zig
pub fn main() !void {
    const stdout = io.getStdOut().writer();
    const stdin = io.getStdIn().reader();

    var historico = Historico.init();

    // Banner inicial
    try stdout.print(
        \\
        \\╔══════════════════════════════════════╗
        \\║     Calculadora Zig v1.0             ║
        \\║  Digite uma expressão: 3 + 4         ║
        \\║  Comandos: historico, limpar, sair    ║
        \\╚══════════════════════════════════════╝
        \\
    , .{});

    // Buffer para leitura de entrada
    var buf: [256]u8 = undefined;

    while (true) {
        try stdout.print("\n> ", .{});

        // Lê uma linha da entrada padrão
        const linha = stdin.readUntilDelimiterOrEof(&buf, '\n') catch {
            try stdout.print("Erro ao ler entrada.\n", .{});
            continue;
        } orelse break; // EOF = sair

        const entrada = mem.trim(u8, linha, " \t\r");

        // Comandos especiais
        if (mem.eql(u8, entrada, "sair") or mem.eql(u8, entrada, "quit")) {
            try stdout.print("Até logo!\n", .{});
            break;
        }

        if (mem.eql(u8, entrada, "historico")) {
            try stdout.print("\n--- Histórico ---\n", .{});
            try historico.exibir(stdout);
            continue;
        }

        if (mem.eql(u8, entrada, "limpar")) {
            historico = Historico.init();
            try stdout.print("Histórico limpo.\n", .{});
            continue;
        }

        if (entrada.len == 0) continue;

        // Parsear e calcular
        const expr = parsearExpressao(entrada) catch |err| {
            try stdout.print("  {s}\n", .{mensagemErro(err)});
            continue;
        };

        const resultado = calcular(expr) catch |err| {
            try stdout.print("  {s}\n", .{mensagemErro(err)});
            continue;
        };

        try stdout.print("  = {d:.6}\n", .{resultado});
        historico.adicionar(expr, resultado);
    }
}
```

## Passo 8: Testes

Uma das grandes vantagens de Zig é o suporte nativo a testes. Vamos adicionar testes para nossas funções:

```zig
test "parsear expressão válida" {
    const expr = try parsearExpressao("3 + 4");
    try std.testing.expectEqual(@as(f64, 3.0), expr.operando_a);
    try std.testing.expectEqual(Operacao.soma, expr.operacao);
    try std.testing.expectEqual(@as(f64, 4.0), expr.operando_b);
}

test "parsear expressão com decimais" {
    const expr = try parsearExpressao("3.14 * 2.0");
    try std.testing.expectApproxEqAbs(@as(f64, 3.14), expr.operando_a, 0.001);
    try std.testing.expectEqual(Operacao.multiplicacao, expr.operacao);
}

test "rejeitar operador inválido" {
    const resultado = parsearExpressao("3 ^ 4");
    try std.testing.expectError(CalcError.OperadorInvalido, resultado);
}

test "rejeitar expressão incompleta" {
    const resultado = parsearExpressao("3 +");
    try std.testing.expectError(CalcError.ExpressaoIncompleta, resultado);
}

test "calcular soma" {
    const expr = Expressao{ .operando_a = 10, .operacao = .soma, .operando_b = 5 };
    const resultado = try calcular(expr);
    try std.testing.expectEqual(@as(f64, 15.0), resultado);
}

test "rejeitar divisão por zero" {
    const expr = Expressao{ .operando_a = 10, .operacao = .divisao, .operando_b = 0 };
    try std.testing.expectError(CalcError.DivisaoPorZero, calcular(expr));
}
```

Execute os testes com:

```bash
zig build test
```

## Compilando e Executando

```bash
# Compilar
zig build

# Executar
./zig-out/bin/calculadora-cli

# Ou compilar e executar diretamente
zig run src/main.zig
```

## Exemplo de Uso

```
╔══════════════════════════════════════╗
║     Calculadora Zig v1.0             ║
║  Digite uma expressão: 3 + 4         ║
║  Comandos: historico, limpar, sair    ║
╚══════════════════════════════════════╝

> 3 + 4
  = 7.000000

> 10 / 3
  = 3.333333

> 5 * 0.5
  = 2.500000

> 10 / 0
  Erro: divisão por zero não é permitida

> historico

--- Histórico ---
  3 + 4 = 7.0000
  10 / 3 = 3.3333
  5 * 0.5 = 2.5000

> sair
Até logo!
```

## Desafios Extras

Quer ir além? Tente estas melhorias:

1. **Suporte a parênteses** — implemente parsing de expressões como `(3 + 4) * 2`
2. **Funções matemáticas** — adicione `sqrt`, `pow`, `sin`, `cos`
3. **Variável `ans`** — use o resultado anterior como operando
4. **Persistência** — salve o histórico em arquivo usando [I/O de arquivos](/receitas/io-arquivos/)

## Conceitos Aprendidos

- Leitura de entrada do usuário com `stdin`
- Parsing de strings com `std.mem` e `std.fmt`
- Tratamento de erros com `catch` e tipos de erro personalizados
- Uso de `enum` e `struct` para modelar domínio
- Buffers de tamanho fixo vs alocação dinâmica
- Testes unitários nativos

## Próximos Passos

- Aprenda mais sobre [manipulação de strings em Zig](/receitas/strings/)
- Explore a [stdlib std.fmt](/stdlib/fmt/) para formatação avançada
- Construa o próximo projeto: [Todo List CLI](/projetos/todo-list-cli/)
