Chapter 16: Guided Project: Simple Command-Line Calculator

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 atoi or strtol.

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:

  1. Save the code above as calculator.c.
  2. Compile it: gcc calculator.c -o calculator
  3. 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:

  1. Update your calculator.c with the atoi calls.
  2. Compile: gcc calculator.c -o calculator
  3. Test:
    • ./calculator 10 + 5 (should print Parsed numbers: 10 and 5)
    • ./calculator -2 * 7 (should print Parsed numbers: -2 and 7)
    • ./calculator hello + world (should print Parsed numbers: 0 and 0 due to atoi returning 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., with strtol.)

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:

  1. Add the function prototypes and definitions to calculator.c.
  2. 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:

  1. Integrate the if-else if-else logic into your main function.
  2. Compile: gcc calculator.c -o calculator
  3. 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):

  1. Replace the if-else if-else block with the function pointer array approach.
  2. Compile and test thoroughly.
  3. 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.