C++
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C++
-
Section 4: Memory Management
-
Section 5: Templates and Generic Programming
-
Section 6: The Standard Template Library
-
Section 7: Modern C++ Features
-
Section 8: Error Handling
-
Section 9: Multithreading
-
Section 10: Operator Overloading and Type Conversion
-
Section 11: Advanced Topics
-
Section 12: Tooling and Build Systems
-
Section 13: Design Patterns in C++
-
Section 14: Interfacing with C and Systems Programming
-
Section 15: Networking and IPC Basics
-
Section 16: Graphics and Game Programming Basics
-
Section 17: The Boost Libraries
-
Section 18: Data Structures and Algorithms in C++
-
Section 19: Practical Projects
-
Section 20: More Concurrency Patterns
-
Section 21: More OOP and Design Practice
-
Section 22: File I/O and Streams
-
Section 23: More Standard Library
-
Section 24: Practice Exercises
-
Section 25: Interview and Algorithm Practice
-
Section 26: Compiler and Language Internals
-
Section 27: GUI and Application Frameworks Overview
-
Section 28: Testing and Quality Practices
-
Section 29: Numerics Library
-
Section 30: Concepts Library (C++20)
-
Section 31: Ranges Library (C++20) In Depth
-
Section 32: More Utility Library
171: Building a Command-Line Calculator
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(usingstd::pow), andsquare_root(usingstd::sqrt). - The Dispatcher: Use a
switchstatement inmainto 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::cinbuffer 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-catchblocks or explicit checks, printing a clear error message to the user without exiting the program.
There are no comments for now.