Skip to Content
Course content

171: Building a Command-Line Calculator

Click on the "Edit" button in the top corner of the screen to edit your slide content.

When you first sit down to build a calculator, you'll likely feel the urge to put everything inside a single switch statement. I've seen this countless times: a learner writes a switch that not only decides which operation to perform but also executes the math, handles the output, and manages the errors all within one massive block of cases. It feels efficient at first because it's "all in one place," but it's a trap.

The "Switch Statement is the Logic" Trap

If you put your calculation logic directly inside the case labels, your main function quickly becomes a bloated mess. Imagine you want to add support for advanced functions like square roots, exponents, or logarithms. Your switch block will grow to hundreds of lines, making it nearly impossible to unit test a single operation without running the entire program. Here is what that "wrong" approach looks like:

// Avoid doing this!
switch (op) {
    case '+': 
        std::cout << "Result: " << num1 + num2 << std::endl; 
        break;
    case '/': 
        if (num2 == 0) std::cout << "Error!"; 
        else std::cout << "Result: " << num1 / num2 << std::endl; 
        break;
    // ... 20 more cases of logic mixed with I/O ...
}

The problem here is that your "decision logic" (which operation to use) is fused to your "business logic" (how the math actually works). If you ever want to move this calculator to a GUI or a web interface, you have to rewrite everything because the math is glued to std::cout.

Decoupling Dispatch from Calculation

The professional way to handle this is to treat the switch statement as a dispatcher. Its only job is to look at the operator and call the appropriate function. The function does the work and returns a value; it doesn't care where the input came from or how the result is printed. This keeps your code clean and, more importantly, testable.

double add(double a, double b) { return a + b; }
double divide(double a, double b) {
    if (b == 0) throw std::runtime_error("Division by zero!");
    return a / b;
}

// In main:
double result = 0;
try {
    switch (op) {
        case '+': result = add(num1, num2); break;
        case '/': result = divide(num1, num2); break;
        default: throw std::invalid_argument("Unknown operator");
    }
    std::cout << "Result: " << result << "\n";
} catch (const std::exception& e) {
    std::cerr << "Error: " << e.what() << "\n";
}

Now, adding a new feature is trivial. You write a new function, add one line to the switch, and you're done. You've separated the "how" from the "when."

Stopping the "Infinite Loop" Input Crash

Since we are building a command-line tool, you'll probably want a loop so the user can perform multiple calculations. But there's a nasty quirk with std::cin: if you expect a double but the user types "apple", std::cin enters a "fail state." If you don't handle this, your loop will spin infinitely, printing your prompt a thousand times a second.

To fix this, you need to clear the error flag and discard the bad input before the next iteration. I usually wrap the input process in a small validation check. You can't just call cin.clear(); you also have to use cin.ignore() to flush the buffer of the characters that caused the failure.

  • std::cin.clear(): Resets the internal state of the stream so it can be used again.
  • std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'): Throws away everything currently in the input buffer until the end of the line.

By combining a dispatcher pattern with a robust input loop, you move from writing a "script" to building a "system."




📋 Practical Task

Building a Robust Math Engine with Power and Root Functions

Extend the calculator logic discussed in the lesson. Your goal is to implement a program that runs in a continuous loop until the user types 'q' as the operator. You must implement the following requirements:

  • Function Decoupling: Create separate functions for add, subtract, multiply, divide, power (using std::pow), and square_root (using std::sqrt).
  • The Dispatcher: Use a switch statement in main to call these functions based on the operator entered (+, -, *, /, ^, s).
  • Input Hardening: Implement a check that detects when a user enters a non-numeric value instead of a number. The program should print "Invalid numeric input, please try again" and clear the std::cin buffer so the loop can continue without crashing.
  • Error Handling: Ensure that division by zero and square roots of negative numbers are handled gracefully using try-catch blocks or explicit checks, printing a clear error message to the user without exiting the program.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.