Chapter 16: Guided Project: Simple Command-Line Calculator
Welcome to your first guided project! The best way to solidify your understanding of C programming is by building something practical. In this project, we will create a simple command-line calculator. This will allow you to apply many of the concepts we’ve covered so far:
- Functions: To encapsulate arithmetic operations.
- Control Flow: For handling different operations and error conditions.
- Data Types and Operators: For performing calculations.
- Command-Line Arguments (
argc,argv): For accepting user input. - String to Number Conversion: Using
atoiorstrtol.
The goal is to create a program that can be run from the terminal like this:
./calculator 10 + 5
And it should output:
Result: 15
Let’s break this project down into manageable steps.
Project Objective
Create a C program named calculator that takes three command-line arguments: two numbers and one operator, then performs the calculation and prints the result.
Supported Operations:
- Addition (
+) - Subtraction (
-) - Multiplication (
*) - Division (
/)
Error Handling:
- Insufficient or incorrect number of arguments.
- Invalid operator.
- Division by zero.
Step 1: Project Setup and main Function Skeleton
First, let’s create our project file calculator.c and set up the basic main function to handle command-line arguments.
calculator.c (Initial Skeleton):
#include <stdio.h> // For printf, fprintf
#include <stdlib.h> // For atoi, exit
#include <string.h> // For strcmp
// Function declarations (prototypes) will go here later
// int add(int num1, int num2);
// ... etc.
int main(int argc, char *argv[]) {
// 1. Check for correct number of arguments
// Expected format: ./calculator <num1> <operator> <num2>
// So, argc should be 4 (program name + 3 arguments)
if (argc != 4) {
fprintf(stderr, "Usage: %s <number1> <operator> <number2>\n", argv[0]);
fprintf(stderr, "Supported operators: +, -, *, /\n");
exit(EXIT_FAILURE); // Exit with an error status
}
// Arguments are stored in argv[1], argv[2], argv[3]
char *num1_str = argv[1];
char *operator_str = argv[2];
char *num2_str = argv[3];
// Placeholder for actual logic
printf("Parsing arguments...\n");
printf("Number 1 string: %s\n", num1_str);
printf("Operator string: %s\n", operator_str);
printf("Number 2 string: %s\n", num2_str);
// TODO: Implement parsing and calculation
return 0; // Indicate successful execution
}
// Function definitions will go here later
Task for Step 1:
- Save the code above as
calculator.c. - Compile it:
gcc calculator.c -o calculator - Test it with different numbers of arguments:
./calculator(should print usage and exit with error)./calculator 10(should print usage and exit with error)./calculator 10 + 5(should print parsing info)./calculator 10 + 5 extra(should print usage and exit with error)
Step 2: Parsing Numerical Arguments
Next, we need to convert the string arguments for the numbers (num1_str, num2_str) into actual integer types. We’ll use atoi() for simplicity, but remember that strtol() is more robust for production-grade code.
Update calculator.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Function declarations (prototypes)
// ...
int main(int argc, char *argv[]) {
// ... (previous argument check remains) ...
char *num1_str = argv[1];
char *operator_str = argv[2];
char *num2_str = argv[3];
// Convert string numbers to integers
int num1 = atoi(num1_str);
int num2 = atoi(num2_str);
printf("Parsed numbers: %d and %d\n", num1, num2);
// TODO: Implement calculation based on operator
return 0;
}
// Function definitions
// ...
Task for Step 2:
- Update your
calculator.cwith theatoicalls. - Compile:
gcc calculator.c -o calculator - Test:
./calculator 10 + 5(should printParsed numbers: 10 and 5)./calculator -2 * 7(should printParsed numbers: -2 and 7)./calculator hello + world(should printParsed numbers: 0 and 0due toatoireturning 0 for invalid numbers. We’ll consider this acceptable for this simple project, but a real-world app would need better error checking, e.g., withstrtol.)
Step 3: Implementing Arithmetic Functions
Let’s create separate functions for each arithmetic operation. This makes our code modular and easy to read.
Update calculator.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Function declarations
int add(int a, int b);
int subtract(int a, int b);
int multiply(int a, int b);
// For division, we'll return a double to handle potential fractional results
double divide(int a, int b); // Division can result in a float/double
int main(int argc, char *argv[]) {
// ... (previous code) ...
int num1 = atoi(num1_str);
int num2 = atoi(num2_str);
printf("Parsed numbers: %d and %d\n", num1, num2);
// TODO: Implement calculation based on operator
return 0;
}
// Function Definitions
int add(int a, int b) {
return a + b;
}
int subtract(int a, int b) {
return a - b;
}
int multiply(int a, int b) {
return a * b;
}
double divide(int a, int b) {
// Crucial: check for division by zero before performing the operation
if (b == 0) {
fprintf(stderr, "Error: Division by zero is not allowed.\n");
exit(EXIT_FAILURE);
}
// Perform floating-point division by casting one of the integers to double
return (double)a / b;
}
Task for Step 3:
- Add the function prototypes and definitions to
calculator.c. - Compile to ensure no syntax errors:
gcc calculator.c -o calculator
Step 4: Operator Handling and Calculation
Now we’ll use conditional statements (if-else if-else or switch) to determine which operation to perform based on operator_str and then call the corresponding function. We’ll also print the result.
Update calculator.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Function declarations
int add(int a, int b);
int subtract(int a, int b);
int multiply(int a, int b);
double divide(int a, int b);
int main(int argc, char *argv[]) {
// ... (previous argument check remains) ...
char *num1_str = argv[1];
char *operator_str = argv[2];
char *num2_str = argv[3];
int num1 = atoi(num1_str);
int num2 = atoi(num2_str);
double result; // Use double to hold potential float results from division
int int_result; // For integer results
// Determine operation based on operator_str
if (strcmp(operator_str, "+") == 0) {
int_result = add(num1, num2);
printf("Result: %d\n", int_result);
} else if (strcmp(operator_str, "-") == 0) {
int_result = subtract(num1, num2);
printf("Result: %d\n", int_result);
} else if (strcmp(operator_str, "*") == 0) {
int_result = multiply(num1, num2);
printf("Result: %d\n", int_result);
} else if (strcmp(operator_str, "/") == 0) {
result = divide(num1, num2); // divide function handles exit on zero
printf("Result: %.2lf\n", result); // Print double with 2 decimal places
} else {
fprintf(stderr, "Error: Invalid operator '%s'.\n", operator_str);
fprintf(stderr, "Supported operators: +, -, *, /\n");
exit(EXIT_FAILURE);
}
return 0;
}
// Function Definitions (add, subtract, multiply, divide - remain the same)
// ...
Task for Step 4:
- Integrate the
if-else if-elselogic into yourmainfunction. - Compile:
gcc calculator.c -o calculator - Test comprehensively:
./calculator 10 + 5./calculator 20 - 7./calculator 4 * 6./calculator 10 / 3(should be 3.33)./calculator 10 / 0(should print error and exit)./calculator 10 ^ 3(should print invalid operator error and exit)
Step 5: (Optional) Using Function Pointers for Operations
For a more advanced and extensible solution, we can use an array of function pointers. This makes it easier to add new operations without changing the core if-else if-else structure.
Update calculator.c (Optional Advanced Version):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Function declarations
int add(int a, int b);
int subtract(int a, int b);
int multiply(int a, int b);
double divide(int a, int b);
// Structure to hold operator symbol and its corresponding function pointer
typedef struct {
char *op_symbol;
int (*int_op_func)(int, int); // For integer operations
double (*double_op_func)(int, int); // For double operations (like division)
int is_double_op; // Flag: 1 if operation returns double, 0 if int
} Operation;
// Array of available operations
Operation operations[] = {
{"+", add, NULL, 0},
{"-", subtract, NULL, 0},
{"*", multiply, NULL, 0},
{"/", NULL, divide, 1} // Division uses double_op_func
};
// Calculate the number of operations in the array
const int NUM_OPERATIONS = sizeof(operations) / sizeof(operations[0]);
int main(int argc, char *argv[]) {
// ... (argument check and atoi calls remain the same) ...
char *operator_str = argv[2];
int num1 = atoi(argv[1]);
int num2 = atoi(argv[3]);
int found_op = 0;
double result;
for (int i = 0; i < NUM_OPERATIONS; i++) {
if (strcmp(operator_str, operations[i].op_symbol) == 0) {
found_op = 1;
if (operations[i].is_double_op) {
result = operations[i].double_op_func(num1, num2);
printf("Result: %.2lf\n", result);
} else {
result = operations[i].int_op_func(num1, num2); // Store int result in double
printf("Result: %d\n", (int)result); // Print as int
}
break; // Operator found and executed, exit loop
}
}
if (!found_op) {
fprintf(stderr, "Error: Invalid operator '%s'.\n", operator_str);
fprintf(stderr, "Supported operators: ");
for (int i = 0; i < NUM_OPERATIONS; i++) {
fprintf(stderr, "%s ", operations[i].op_symbol);
}
fprintf(stderr, "\n");
exit(EXIT_FAILURE);
}
return 0;
}
// Function Definitions (add, subtract, multiply, divide - remain the same)
// ...
Task for Step 5 (Optional):
- Replace the
if-else if-elseblock with the function pointer array approach. - Compile and test thoroughly.
- Consider what it would take to add a new operation, like modulo (
%). How easy would it be with this structure?
Final calculator.c Code (Comprehensive)
Here’s the complete code, including the function pointer approach.
#include <stdio.h> // For printf, fprintf
#include <stdlib.h> // For atoi, exit, EXIT_FAILURE
#include <string.h> // For strcmp
// --- Function Declarations (Prototypes) ---
int add(int a, int b);
int subtract(int a, int b);
int multiply(int a, int b);
double divide(int a, int b);
// --- Structure for Operations (using function pointers) ---
typedef struct {
char *op_symbol;
int (*int_op_func)(int, int); // For integer operations
double (*double_op_func)(int, int); // For double operations (like division)
int is_double_op; // Flag: 1 if operation returns double, 0 if int
} Operation;
// --- Array of supported operations ---
Operation operations[] = {
{"+", add, NULL, 0},
{"-", subtract, NULL, 0},
{"*", multiply, NULL, 0},
{"/", NULL, divide, 1} // Division uses double_op_func, as it returns double
};
// Calculate the number of operations in the array
const int NUM_OPERATIONS = sizeof(operations) / sizeof(operations[0]);
// --- Main Function ---
int main(int argc, char *argv[]) {
// 1. Check for correct number of arguments
// Expected format: ./calculator <num1> <operator> <num2>
if (argc != 4) {
fprintf(stderr, "Usage: %s <number1> <operator> <number2>\n", argv[0]);
fprintf(stderr, "Supported operators: ");
for (int i = 0; i < NUM_OPERATIONS; i++) {
fprintf(stderr, "%s ", operations[i].op_symbol);
}
fprintf(stderr, "\n");
exit(EXIT_FAILURE);
}
// 2. Extract arguments
char *num1_str = argv[1];
char *operator_str = argv[2];
char *num2_str = argv[3];
// 3. Convert string numbers to integers
// For simplicity, using atoi. In a real-world app, consider strtol for error handling.
int num1 = atoi(num1_str);
int num2 = atoi(num2_str);
// 4. Perform calculation based on operator using function pointers
int found_op = 0;
double result;
for (int i = 0; i < NUM_OPERATIONS; i++) {
if (strcmp(operator_str, operations[i].op_symbol) == 0) {
found_op = 1;
if (operations[i].is_double_op) {
result = operations[i].double_op_func(num1, num2);
printf("Result: %.2lf\n", result);
} else {
result = operations[i].int_op_func(num1, num2);
printf("Result: %d\n", (int)result); // Cast back to int for printing integer results
}
break; // Operator found and executed, exit loop
}
}
// 5. Handle invalid operator
if (!found_op) {
fprintf(stderr, "Error: Invalid operator '%s'.\n", operator_str);
fprintf(stderr, "Supported operators: ");
for (int i = 0; i < NUM_OPERATIONS; i++) {
fprintf(stderr, "%s ", operations[i].op_symbol);
}
fprintf(stderr, "\n");
exit(EXIT_FAILURE);
}
return EXIT_SUCCESS; // Indicate successful execution
}
// --- Function Definitions ---
int add(int a, int b) {
return a + b;
}
int subtract(int a, int b) {
return a - b;
}
int multiply(int a, int b) {
return a * b;
}
double divide(int a, int b) {
if (b == 0) {
fprintf(stderr, "Error: Division by zero is not allowed.\n");
exit(EXIT_FAILURE); // Exit if division by zero
}
return (double)a / b; // Perform floating-point division
}
Compilation:
gcc calculator.c -o calculator
Testing:
./calculator 10 + 5 # Result: 15
./calculator 20 - 7 # Result: 13
./calculator 4 * 6 # Result: 24
./calculator 10 / 3 # Result: 3.33
./calculator 10 / 0 # Error: Division by zero is not allowed.
./calculator 10 ^ 3 # Error: Invalid operator '^'.
./calculator # Usage: ./calculator <number1> <operator> <number2>
Congratulations! You’ve built a functional command-line calculator, applying many core C programming concepts. This project demonstrates how to create structured, modular, and robust C applications.
This guided project should have cemented your understanding of C’s fundamental building blocks and how they work together in a practical scenario. In the next guided project, we’ll dive deeper into memory management by building a custom memory allocator.