C++ Functions Modular Programming
Code Reusability

C++ Functions: Complete Guide with Examples

Master C++ functions: function declaration, definition, parameters, return values, call by value/reference, passing arrays, recursion, and best practices for modular programming.

Function Basics

Declaration & Definition

Parameters

Value vs Reference

Arrays

1D & 2D Passing

Recursion

Self-calling Functions

Introduction to Functions

Functions are fundamental building blocks in C++ that allow you to organize code into reusable, modular units. They enable code reuse, improve readability, and make debugging easier.

Why Use Functions?
  • Code Reusability
  • Modular Programming
  • Improved Readability
  • Easier Debugging
  • Code Organization
  • Team Collaboration
Function Components
  • Return Type: Data type returned
  • Function Name: Identifier
  • Parameters: Input values
  • Function Body: Implementation
  • Prototype: Declaration
Function Execution Flow
1
Function Call: Call function with arguments
2
Parameter Passing: Arguments copied to parameters
3
Function Execution: Execute function body
4
Return Value: Return result to caller
5
Resume Execution: Continue after function call

C++ Functions Overview

The following table compares different function types and parameter passing methods in C++:

Function Type Syntax Example When to Use Characteristics
Basic Function int add(int a, int b) { } Simple operations, calculations Returns value, has parameters
Void Function void printMsg() { } Actions without return value No return value, may have parameters
Call by Value void func(int x) { } Don't modify original values Creates copy, original unchanged
Call by Reference void func(int &x) { } Modify original values Works on original, no copy
Array Parameter void func(int arr[], int size) { } Process array elements Passes address, modifies original
Recursive Function int factorial(int n) { } Problems with recursive nature Calls itself, base case needed

1. Function Basics: Declaration & Definition

A function consists of a declaration (prototype) and definition. The declaration specifies the function's interface, while the definition provides the implementation.

Function Components
// FUNCTION DECLARATION (Prototype)
return_type function_name(parameter_list);

// FUNCTION DEFINITION
return_type function_name(parameter_list) {
    // Function body
    // Statements
    return value; // if return_type is not void
}
Basic Function Examples
#include <iostream>
using namespace std;

// Function prototypes (declarations)
int add(int a, int b);
void printMessage(string message);
int getMax(int x, int y);
double calculateAverage(double num1, double num2);
bool isEven(int number);

int main() {
    // Example 1: Simple addition function
    cout << "=== Example 1: Addition Function ===" << endl;
    int result = add(10, 20);
    cout << "10 + 20 = " << result << endl << endl;
    
    // Example 2: Void function
    cout << "=== Example 2: Void Function ===" << endl;
    printMessage("Hello from C++ Functions!");
    cout << endl;
    
    // Example 3: Maximum function
    cout << "=== Example 3: Maximum Function ===" << endl;
    int a = 15, b = 25;
    cout << "Maximum of " << a << " and " << b << " is: " 
         << getMax(a, b) << endl << endl;
    
    // Example 4: Average function
    cout << "=== Example 4: Average Function ===" << endl;
    double avg = calculateAverage(85.5, 92.0);
    cout << "Average of 85.5 and 92.0 is: " << avg << endl << endl;
    
    // Example 5: Boolean function
    cout << "=== Example 5: Boolean Function ===" << endl;
    int num = 7;
    if (isEven(num)) {
        cout << num << " is even" << endl;
    } else {
        cout << num << " is odd" << endl;
    }
    
    return 0;
}

// Function definitions
int add(int a, int b) {
    return a + b;
}

void printMessage(string message) {
    cout << "Message: " << message << endl;
}

int getMax(int x, int y) {
    if (x > y) {
        return x;
    } else {
        return y;
    }
}

double calculateAverage(double num1, double num2) {
    return (num1 + num2) / 2.0;
}

bool isEven(int number) {
    return (number % 2 == 0);
}
Function Naming Conventions:
  • Use descriptive names (calculateAverage, isValidInput)
  • CamelCase or snake_case (calculateAverage or calculate_average)
  • Start with verb for actions (print, calculate, validate)
  • Boolean functions start with "is", "has", "can" (isValid, hasPermission)
Common Mistakes:
  • Missing return statement in non-void function
  • Function prototype mismatch with definition
  • Forgetting semicolon after prototype
  • Redeclaring parameters in function body

2. Parameter Passing: Call by Value vs Reference

C++ supports two main ways to pass parameters: call by value (creates copy) and call by reference (works on original).

Parameter Passing Syntax
// CALL BY VALUE (creates copy)
void func(int x) {
    x = 100; // Changes local copy only
}

// CALL BY REFERENCE (works on original)
void func(int &x) {
    x = 100; // Changes original variable
}

// CALL BY CONST REFERENCE (read-only, efficient)
void func(const int &x) {
    // Can read x but not modify
}
Parameter Passing Examples
#include <iostream>
using namespace std;

// Function prototypes
void callByValue(int x);
void callByReference(int &x);
void swapValues(int &a, int &b);
void printInfo(const string &name, int age); // const reference
void modifyArray(int arr[], int size);

int main() {
    cout << "=== PARAMETER PASSING DEMONSTRATION ===" << endl << endl;
    
    // Example 1: Call by Value
    cout << "Example 1: Call by Value" << endl;
    int num1 = 10;
    cout << "Before callByValue: num1 = " << num1 << endl;
    callByValue(num1);
    cout << "After callByValue:  num1 = " << num1 << endl;
    cout << "(Original unchanged - value was copied)" << endl << endl;
    
    // Example 2: Call by Reference
    cout << "Example 2: Call by Reference" << endl;
    int num2 = 20;
    cout << "Before callByReference: num2 = " << num2 << endl;
    callByReference(num2);
    cout << "After callByReference:  num2 = " << num2 << endl;
    cout << "(Original changed - reference used)" << endl << endl;
    
    // Example 3: Swap values using reference
    cout << "Example 3: Swapping Values" << endl;
    int x = 5, y = 10;
    cout << "Before swap: x = " << x << ", y = " << y << endl;
    swapValues(x, y);
    cout << "After swap:  x = " << x << ", y = " << y << endl << endl;
    
    // Example 4: Const reference for efficiency
    cout << "Example 4: Const Reference" << endl;
    string studentName = "Alice";
    int studentAge = 21;
    printInfo(studentName, studentAge);
    cout << "(String passed by const reference to avoid copying)" << endl << endl;
    
    // Example 5: Default arguments
    cout << "Example 5: Default Arguments" << endl;
    cout << "Area of rectangle 5x3: " << calculateArea(5, 3) << endl;
    cout << "Area of square side 4: " << calculateArea(4) << endl;
    cout << "Area with no arguments: " << calculateArea() << endl << endl;
    
    // Example 6: Array passing (always by reference)
    cout << "Example 6: Array Parameter" << endl;
    int numbers[] = {1, 2, 3, 4, 5};
    int size = 5;
    
    cout << "Original array: ";
    for (int i = 0; i < size; i++) {
        cout << numbers[i] << " ";
    }
    cout << endl;
    
    modifyArray(numbers, size);
    
    cout << "Modified array: ";
    for (int i = 0; i < size; i++) {
        cout << numbers[i] << " ";
    }
    cout << endl;
    
    return 0;
}

// Function definitions
void callByValue(int x) {
    x = 100; // Changes local copy only
    cout << "Inside callByValue: x = " << x << endl;
}

void callByReference(int &x) {
    x = 100; // Changes original variable
    cout << "Inside callByReference: x = " << x << endl;
}

void swapValues(int &a, int &b) {
    int temp = a;
    a = b;
    b = temp;
}

void printInfo(const string &name, int age) {
    // name is const reference - can't modify, but efficient for strings
    cout << "Student: " << name << ", Age: " << age << endl;
}

// Function with default arguments
double calculateArea(double length = 1.0, double width = 1.0) {
    return length * width;
}

void modifyArray(int arr[], int size) {
    for (int i = 0; i < size; i++) {
        arr[i] *= 2; // Double each element
    }
}
Call by Value
  • Creates copy of argument
  • Original unchanged
  • Good for small data types
  • Inefficient for large objects
  • Use when don't need to modify
Call by Reference
  • Works on original data
  • Can modify original
  • Efficient for large objects
  • Use & symbol in parameters
  • Use when need to modify
Const Reference
  • Read-only access
  • Efficient, no copying
  • Cannot modify data
  • Best for large read-only data
  • Use const keyword

3. Passing Arrays to Functions: 1D & 2D Arrays

Arrays are always passed by reference to functions. For 1D arrays, you need to pass the size separately. For 2D arrays, you must specify column size.

Array Parameter Syntax
// 1D ARRAY PARAMETER
void processArray(int arr[], int size) {
    // arr is pointer to first element
}

// 2D ARRAY PARAMETER - MUST SPECIFY COLUMNS
void processMatrix(int matrix[][COLS], int rows) {
    // Column size must be specified
}

// USING POINTER NOTATION
void processArray(int *arr, int size) {
    // Same as arr[]
}
Array Passing Examples
#include <iostream>
#include <iomanip>
using namespace std;

// Function prototypes for 1D arrays
void printArray(int arr[], int size);
int findMax(int arr[], int size);
int findSum(int arr[], int size);
void reverseArray(int arr[], int size);
void sortArray(int arr[], int size);

// Function prototypes for 2D arrays
const int COLS = 4; // Must be constant for 2D array parameters
void printMatrix(int matrix[][COLS], int rows);
int sumMatrix(int matrix[][COLS], int rows);
void transposeMatrix(int matrix[][COLS], int rows);

int main() {
    cout << "=== PASSING ARRAYS TO FUNCTIONS ===" << endl << endl;
    
    // 1D ARRAY EXAMPLES
    cout << "=== 1D ARRAY OPERATIONS ===" << endl;
    
    int numbers[] = {45, 12, 89, 34, 67, 23, 90, 11};
    int size = 8;
    
    // Example 1: Print array
    cout << "Original array: ";
    printArray(numbers, size);
    cout << endl;
    
    // Example 2: Find maximum
    cout << "Maximum value: " << findMax(numbers, size) << endl;
    
    // Example 3: Calculate sum
    cout << "Sum of elements: " << findSum(numbers, size) << endl;
    
    // Example 4: Reverse array
    cout << "\nReversing array..." << endl;
    reverseArray(numbers, size);
    cout << "Reversed array: ";
    printArray(numbers, size);
    
    // Reverse back to original
    reverseArray(numbers, size);
    
    // Example 5: Sort array
    cout << "\nSorting array..." << endl;
    sortArray(numbers, size);
    cout << "Sorted array: ";
    printArray(numbers, size);
    
    cout << endl << "=================================" << endl << endl;
    
    // 2D ARRAY EXAMPLES
    cout << "=== 2D ARRAY (MATRIX) OPERATIONS ===" << endl;
    
    const int ROWS = 3;
    int matrix[ROWS][COLS] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    };
    
    // Example 6: Print 2D array
    cout << "\nOriginal matrix:" << endl;
    printMatrix(matrix, ROWS);
    
    // Example 7: Sum of all elements
    cout << "Sum of all matrix elements: " << sumMatrix(matrix, ROWS) << endl;
    
    // Example 8: Transpose matrix (for square matrix)
    cout << "\nSquare matrix for transpose:" << endl;
    int squareMatrix[3][3] = {
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9}
    };
    
    // Note: For transpose, we need same rows and columns
    cout << "Original square matrix:" << endl;
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            cout << setw(4) << squareMatrix[i][j];
        }
        cout << endl;
    }
    
    // Example 9: Dynamic array passing using pointers
    cout << "\n=== DYNAMIC ARRAYS ===" << endl;
    int* dynamicArray = new int[5];
    for (int i = 0; i < 5; i++) {
        dynamicArray[i] = (i + 1) * 10;
    }
    
    cout << "Dynamic array: ";
    printArray(dynamicArray, 5); // Same function works!
    
    delete[] dynamicArray; // Don't forget to free memory
    
    return 0;
}

// 1D ARRAY FUNCTION DEFINITIONS
void printArray(int arr[], int size) {
    for (int i = 0; i < size; i++) {
        cout << arr[i] << " ";
    }
}

int findMax(int arr[], int size) {
    int max = arr[0];
    for (int i = 1; i < size; i++) {
        if (arr[i] > max) {
            max = arr[i];
        }
    }
    return max;
}

int findSum(int arr[], int size) {
    int sum = 0;
    for (int i = 0; i < size; i++) {
        sum += arr[i];
    }
    return sum;
}

void reverseArray(int arr[], int size) {
    for (int i = 0; i < size / 2; i++) {
        // Swap arr[i] with arr[size-1-i]
        int temp = arr[i];
        arr[i] = arr[size - 1 - i];
        arr[size - 1 - i] = temp;
    }
}

void sortArray(int arr[], int size) {
    // Simple bubble sort
    for (int i = 0; i < size - 1; i++) {
        for (int j = 0; j < size - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                // Swap
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}

// 2D ARRAY FUNCTION DEFINITIONS
void printMatrix(int matrix[][COLS], int rows) {
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < COLS; j++) {
            cout << setw(4) << matrix[i][j];
        }
        cout << endl;
    }
}

int sumMatrix(int matrix[][COLS], int rows) {
    int total = 0;
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < COLS; j++) {
            total += matrix[i][j];
        }
    }
    return total;
}
Array Passing Rules:
  • Arrays are always passed by reference (address)
  • For 1D arrays: Pass size as separate parameter
  • For 2D arrays: Must specify column size in parameter
  • int arr[] and int *arr are equivalent in parameters
  • Changes made to array in function affect original
  • Use const keyword for read-only array parameters

4. Function Overloading

Function overloading allows multiple functions with the same name but different parameters. The compiler distinguishes them based on parameter count or types.

Overloading Syntax
// DIFFERENT PARAMETER COUNT
int add(int a, int b);
int add(int a, int b, int c);

// DIFFERENT PARAMETER TYPES
int add(int a, int b);
double add(double a, double b);

// DIFFERENT PARAMETER ORDER
void print(int id, string name);
void print(string name, int id);
Function Overloading Examples
#include <iostream>
#include <string>
#include <cmath>
using namespace std;

// Overloaded function prototypes
int add(int a, int b);
double add(double a, double b);
int add(int a, int b, int c);
string add(string a, string b);

// Overloaded area functions
double area(double radius);               // Circle
double area(double length, double width); // Rectangle
double area(double base, double height, char); // Triangle

// Overloaded display functions
void display(int value);
void display(double value);
void display(string value);
void display(int value, int precision);

// Overloaded max functions
int max(int a, int b);
double max(double a, double b);
int max(int a, int b, int c);

int main() {
    cout << "=== FUNCTION OVERLOADING DEMONSTRATION ===" << endl << endl;
    
    // Example 1: Overloaded add functions
    cout << "=== Example 1: Overloaded Add Functions ===" << endl;
    cout << "add(5, 10) = " << add(5, 10) << endl;
    cout << "add(2.5, 3.7) = " << add(2.5, 3.7) << endl;
    cout << "add(1, 2, 3) = " << add(1, 2, 3) << endl;
    cout << "add(\"Hello, \", \"World!\") = " << add("Hello, ", "World!") << endl;
    cout << endl;
    
    // Example 2: Overloaded area functions
    cout << "=== Example 2: Overloaded Area Functions ===" << endl;
    cout << "Area of circle (radius 5.0) = " << area(5.0) << endl;
    cout << "Area of rectangle (4.0 x 6.0) = " << area(4.0, 6.0) << endl;
    cout << "Area of triangle (base 3.0, height 4.0) = " 
         << area(3.0, 4.0, 'T') << endl;
    cout << endl;
    
    // Example 3: Overloaded display functions
    cout << "=== Example 3: Overloaded Display Functions ===" << endl;
    display(42);
    display(3.14159);
    display("C++ Programming");
    display(3.14159, 3);
    cout << endl;
    
    // Example 4: Overloaded max functions
    cout << "=== Example 4: Overloaded Max Functions ===" << endl;
    cout << "max(10, 20) = " << max(10, 20) << endl;
    cout << "max(5.5, 2.3) = " << max(5.5, 2.3) << endl;
    cout << "max(15, 30, 10) = " << max(15, 30, 10) << endl;
    
    // Example 5: Ambiguity demonstration
    cout << "\n=== Example 5: Potential Ambiguity ===" << endl;
    // Uncommenting the next line would cause ambiguity error
    // display(10); // Which display? int or double?
    // Solution: Use explicit cast
    display(static_cast<double>(10));
    
    return 0;
}

// Overloaded add function definitions
int add(int a, int b) {
    return a + b;
}

double add(double a, double b) {
    return a + b;
}

int add(int a, int b, int c) {
    return a + b + c;
}

string add(string a, string b) {
    return a + b;
}

// Overloaded area function definitions
double area(double radius) {
    return 3.14159 * radius * radius;
}

double area(double length, double width) {
    return length * width;
}

double area(double base, double height, char) {
    return 0.5 * base * height;
}

// Overloaded display function definitions
void display(int value) {
    cout << "Integer: " << value << endl;
}

void display(double value) {
    cout << "Double: " << value << endl;
}

void display(string value) {
    cout << "String: " << value << endl;
}

void display(double value, int precision) {
    cout.precision(precision);
    cout << "Double with precision " << precision << ": " 
         << fixed << value << endl;
}

// Overloaded max function definitions
int max(int a, int b) {
    return (a > b) ? a : b;
}

double max(double a, double b) {
    return (a > b) ? a : b;
}

int max(int a, int b, int c) {
    return max(max(a, b), c);
}
Overloading Best Practices:
  • Overload based on parameter types/count, not return type
  • Maintain consistent functionality across overloads
  • Avoid ambiguous overloads that confuse compiler
  • Use default parameters as alternative to overloading
  • Document each overload's purpose clearly

5. Recursive Functions

A recursive function calls itself to solve a problem. It must have a base case (stopping condition) and a recursive case (calls itself).

Recursion Structure
return_type function(parameters) {
    // BASE CASE (stopping condition)
    if (stopping_condition) {
        return base_value;
    }
    
    // RECURSIVE CASE (call itself)
    return function(modified_parameters);
}
Recursive Function Examples
#include <iostream>
using namespace std;

// Recursive function prototypes
int factorial(int n);
int fibonacci(int n);
int power(int base, int exponent);
int sumOfDigits(int n);
int gcd(int a, int b);
void printNumbers(int n);
void towerOfHanoi(int n, char from, char to, char aux);
int binarySearch(int arr[], int left, int right, int target);

int main() {
    cout << "=== RECURSIVE FUNCTIONS DEMONSTRATION ===" << endl << endl;
    
    // Example 1: Factorial
    cout << "=== Example 1: Factorial ===" << endl;
    for (int i = 0; i <= 5; i++) {
        cout << i << "! = " << factorial(i) << endl;
    }
    cout << endl;
    
    // Example 2: Fibonacci sequence
    cout << "=== Example 2: Fibonacci Sequence ===" << endl;
    cout << "First 10 Fibonacci numbers: ";
    for (int i = 0; i < 10; i++) {
        cout << fibonacci(i) << " ";
    }
    cout << endl << endl;
    
    // Example 3: Power calculation
    cout << "=== Example 3: Power Calculation ===" << endl;
    int base = 2, exponent = 5;
    cout << base << "^" << exponent << " = " << power(base, exponent) << endl;
    cout << endl;
    
    // Example 4: Sum of digits
    cout << "=== Example 4: Sum of Digits ===" << endl;
    int number = 12345;
    cout << "Sum of digits of " << number << " = " << sumOfDigits(number) << endl;
    cout << endl;
    
    // Example 5: Greatest Common Divisor (GCD)
    cout << "=== Example 5: GCD Calculation ===" << endl;
    int a = 56, b = 98;
    cout << "GCD of " << a << " and " << b << " = " << gcd(a, b) << endl;
    cout << endl;
    
    // Example 6: Print numbers recursively
    cout << "=== Example 6: Print Numbers 1 to 5 ===" << endl;
    printNumbers(5);
    cout << endl << endl;
    
    // Example 7: Binary Search (recursive)
    cout << "=== Example 7: Recursive Binary Search ===" << endl;
    int sortedArray[] = {2, 5, 8, 12, 16, 23, 38, 45, 56, 72};
    int size = 10;
    int target = 23;
    
    int index = binarySearch(sortedArray, 0, size - 1, target);
    if (index != -1) {
        cout << "Element " << target << " found at index " << index << endl;
    } else {
        cout << "Element " << target << " not found" << endl;
    }
    cout << endl;
    
    // Example 8: Tower of Hanoi
    cout << "=== Example 8: Tower of Hanoi (3 disks) ===" << endl;
    towerOfHanoi(3, 'A', 'C', 'B');
    
    return 0;
}

// Recursive function definitions

// Factorial: n! = n * (n-1)!
int factorial(int n) {
    // Base case
    if (n <= 1) {
        return 1;
    }
    // Recursive case
    return n * factorial(n - 1);
}

// Fibonacci: fib(n) = fib(n-1) + fib(n-2)
int fibonacci(int n) {
    // Base cases
    if (n == 0) return 0;
    if (n == 1) return 1;
    
    // Recursive case
    return fibonacci(n - 1) + fibonacci(n - 2);
}

// Power: base^exponent = base * base^(exponent-1)
int power(int base, int exponent) {
    // Base case
    if (exponent == 0) {
        return 1;
    }
    // Recursive case
    return base * power(base, exponent - 1);
}

// Sum of digits: sum(123) = 3 + sum(12)
int sumOfDigits(int n) {
    // Base case
    if (n == 0) {
        return 0;
    }
    // Recursive case: last digit + sum of remaining digits
    return (n % 10) + sumOfDigits(n / 10);
}

// GCD using Euclidean algorithm
int gcd(int a, int b) {
    // Base case
    if (b == 0) {
        return a;
    }
    // Recursive case
    return gcd(b, a % b);
}

// Print numbers from 1 to n
void printNumbers(int n) {
    // Base case
    if (n <= 0) {
        return;
    }
    // Recursive call first (to print 1 to n-1)
    printNumbers(n - 1);
    // Then print n
    cout << n << " ";
}

// Recursive binary search
int binarySearch(int arr[], int left, int right, int target) {
    // Base case: element not found
    if (left > right) {
        return -1;
    }
    
    int mid = left + (right - left) / 2;
    
    // Base case: element found
    if (arr[mid] == target) {
        return mid;
    }
    
    // Recursive cases
    if (arr[mid] > target) {
        // Search left half
        return binarySearch(arr, left, mid - 1, target);
    } else {
        // Search right half
        return binarySearch(arr, mid + 1, right, target);
    }
}

// Tower of Hanoi
void towerOfHanoi(int n, char from, char to, char aux) {
    // Base case: only one disk
    if (n == 1) {
        cout << "Move disk 1 from " << from << " to " << to << endl;
        return;
    }
    
    // Move n-1 disks from 'from' to 'aux' using 'to'
    towerOfHanoi(n - 1, from, aux, to);
    
    // Move nth disk from 'from' to 'to'
    cout << "Move disk " << n << " from " << from << " to " << to << endl;
    
    // Move n-1 disks from 'aux' to 'to' using 'from'
    towerOfHanoi(n - 1, aux, to, from);
}
Recursion Warnings:
  • Always have a base case to prevent infinite recursion
  • Recursion can be inefficient (stack overhead, repeated calculations)
  • May cause stack overflow for deep recursion
  • Use iteration when possible for better performance
  • Consider memoization (caching) for optimization

6. Best Practices and Common Mistakes

Function Best Practices
  • One function = One responsibility
  • Use descriptive function names
  • Keep functions small (< 50 lines)
  • Use const references for large read-only parameters
  • Document function purpose, parameters, return value
  • Validate parameters at function entry
  • Use function prototypes for better organization
Common Mistakes
  • Forgetting return statement in non-void function
  • Mismatched function prototype and definition
  • Passing wrong array size to functions
  • Infinite recursion without base case
  • Global variables instead of parameters
  • Functions that are too long and complex
  • Not handling edge cases in parameters
Good vs Bad Function Design
#include <iostream>
#include <string>
#include <vector>
using namespace std;

// ===== BAD PRACTICES =====
void processEverything(int data[], int size) { // Too broad responsibility
    // 100+ lines of code doing multiple things
    // Hard to read, debug, and maintain
}

int calculate(int a, int b) { // Vague name
    return a + b; // But what if we want subtraction later?
}

void printData(int arr[], int n) { // No parameter validation
    for (int i = 0; i < n; i++) { // What if n is negative or too large?
        cout << arr[i] << " ";
    }
}

// Global variable abuse
int counter = 0; // Bad: Global variable
void increment() {
    counter++; // Function depends on global state
}

// ===== GOOD PRACTICES =====

// Good: Single responsibility
double calculateAverage(const vector<double>& numbers) {
    if (numbers.empty()) {
        return 0.0; // Handle edge case
    }
    
    double sum = 0.0;
    for (double num : numbers) {
        sum += num;
    }
    return sum / numbers.size();
}

// Good: Descriptive name, const reference for efficiency
void printStudentInfo(const string& name, int age, double gpa) {
    cout << "Name: " << name << endl;
    cout << "Age: " << age << endl;
    cout << "GPA: " << gpa << endl;
}

// Good: Parameter validation
bool isValidIndex(int index, int arraySize) {
    return (index >= 0 && index < arraySize);
}

// Good: Small, focused function
int findMaxValue(const int arr[], int size) {
    if (size <= 0) return INT_MIN; // Validate input
    
    int maxVal = arr[0];
    for (int i = 1; i < size; i++) {
        if (arr[i] > maxVal) {
            maxVal = arr[i];
        }
    }
    return maxVal;
}

// Good: Function with clear documentation
/**
 * Calculates the factorial of a non-negative integer.
 * 
 * @param n The number to calculate factorial for (must be >= 0)
 * @return The factorial of n
 * @throws invalid_argument if n is negative
 */
long long factorial(int n) {
    if (n < 0) {
        throw invalid_argument("Factorial not defined for negative numbers");
    }
    
    long long result = 1;
    for (int i = 2; i <= n; i++) {
        result *= i;
    }
    return result;
}

// Good: Function returning multiple values (using references)
bool solveQuadratic(double a, double b, double c, 
                    double& root1, double& root2) {
    double discriminant = b * b - 4 * a * c;
    
    if (discriminant < 0) {
        return false; // No real roots
    }
    
    root1 = (-b + sqrt(discriminant)) / (2 * a);
    root2 = (-b - sqrt(discriminant)) / (2 * a);
    return true;
}

int main() {
    cout << "=== FUNCTION DESIGN COMPARISON ===" << endl << endl;
    
    // Good practice examples
    vector<double> scores = {85.5, 92.0, 78.5, 88.0};
    cout << "Average score: " << calculateAverage(scores) << endl;
    
    printStudentInfo("Alice Johnson", 21, 3.75);
    
    int numbers[] = {45, 12, 89, 34, 67};
    cout << "Maximum value: " << findMaxValue(numbers, 5) << endl;
    
    // Using function with multiple return values
    double root1, root2;
    if (solveQuadratic(1, -3, 2, root1, root2)) {
        cout << "Quadratic roots: " << root1 << ", " << root2 << endl;
    }
    
    return 0;
}