Menu

System Programming Topic

C++ Functions, Memory Basics, and Scope

1. Functions: Declaration, Definition, and Call

Basic Function Structure

cpp

#include <iostream>
using namespace std;

// 1. FUNCTION DECLARATION (Prototype) - tells compiler function exists
int add(int a, int b);           // Return type, name, parameters
void printMessage();            // void = no return value
double calculateArea(double radius);

// 2. MAIN FUNCTION - Program entry point
int main() {
    // 3. FUNCTION CALLS
    int result = add(5, 3);      // Call with arguments
    cout << "Sum: " << result << endl;
    
    printMessage();              // Call without arguments
    
    double area = calculateArea(7.5);
    cout << "Area: " << area << endl;
    
    return 0;
}

// 4. FUNCTION DEFINITIONS - Implementation
int add(int a, int b) {          // Parameters receive arguments
    return a + b;               // Return statement
}

void printMessage() {
    cout << "Hello from function!" << endl;
    // No return needed for void functions
}

double calculateArea(double radius) {
    const double PI = 3.14159;
    return PI * radius * radius;
}

Different Parameter Types

cpp

#include <iostream>
using namespace std;

// Value parameters (copy of argument)
void modifyValue(int x) {
    x = 100;                    // Only changes local copy
    cout << "Inside function: " << x << endl;
}

// Reference parameters (alias to argument)
void modifyReference(int &x) {
    x = 100;                    // Changes original variable
    cout << "Inside function: " << x << endl;
}

// Pointer parameters (address of argument)
void modifyPointer(int *x) {
    *x = 100;                   // Changes value at address
    cout << "Inside function: " << *x << endl;
}

// Default parameters
void greet(string name = "Guest", int times = 1) {
    for (int i = 0; i < times; i++) {
        cout << "Hello, " << name << "!" << endl;
    }
}

// Multiple return types using references
bool divide(double a, double b, double &result) {
    if (b == 0) return false;   // Error case
    result = a / b;
    return true;                // Success
}

int main() {
    int num = 5;
    
    cout << "Original: " << num << endl;
    modifyValue(num);
    cout << "After modifyValue: " << num << endl;  // Still 5
    
    modifyReference(num);
    cout << "After modifyReference: " << num << endl;  // Now 100
    
    num = 5;
    modifyPointer(&num);
    cout << "After modifyPointer: " << num << endl;    // Now 100
    
    // Using default parameters
    greet();                     // Uses both defaults
    greet("Alice");              // Uses default for times
    greet("Bob", 3);             // Uses both provided values
    
    // Multiple returns via reference parameter
    double quotient;
    if (divide(10, 3, quotient)) {
        cout << "Quotient: " << quotient << endl;
    } else {
        cout << "Division by zero!" << endl;
    }
    
    return 0;
}

2. Inline Functions

cpp

#include <iostream>
using namespace std;

// Regular function
int square(int x) {
    return x * x;
}

// Inline function - Suggestion to compiler to insert code directly
inline int cube(int x) {
    return x * x * x;
}

// Multiple line inline function
inline int max(int a, int b) {
    return (a > b) ? a : b;     // Ternary operator
}

// When to use inline:
// 1. Small functions (1-3 lines)
// 2. Frequently called functions
// 3. Performance-critical sections

int main() {
    int num = 5;
    
    cout << "Square of " << num << ": " << square(num) << endl;
    cout << "Cube of " << num << ": " << cube(num) << endl;
    cout << "Max of 10 and 20: " << max(10, 20) << endl;
    
    // Inline function call gets expanded by compiler:
    // cout << "Cube of " << num << ": " << (num * num * num) << endl;
    
    return 0;
}

3. Function Overloading (Compile-time Polymorphism)

cpp

#include <iostream>
#include <string>
using namespace std;

// OVERLOADING RULES:
// 1. Functions must have same name
// 2. Must differ in parameter list:
//    - Number of parameters
//    - Type of parameters
//    - Order of parameters
// 3. Return type alone CANNOT differentiate overloaded functions

// Different number of parameters
int add(int a, int b) {
    cout << "add(int, int): ";
    return a + b;
}

int add(int a, int b, int c) {
    cout << "add(int, int, int): ";
    return a + b + c;
}

// Different types of parameters
double add(double a, double b) {
    cout << "add(double, double): ";
    return a + b;
}

string add(string a, string b) {
    cout << "add(string, string): ";
    return a + b;  // String concatenation
}

// Different order of parameters
void display(int id, string name) {
    cout << "ID: " << id << ", Name: " << name << endl;
}

void display(string name, int id) {
    cout << "Name: " << name << ", ID: " << id << endl;
}

// Default parameters vs Overloading
int multiply(int a, int b) {
    return a * b;
}

int multiply(int a, int b, int c = 1) {  // Default parameter
    return a * b * c;
}

// Ambiguity example
void process(int x) {
    cout << "Processing int: " << x << endl;
}

void process(double x) {
    cout << "Processing double: " << x << endl;
}

int main() {
    // Compiler chooses based on arguments
    cout << add(5, 3) << endl;           // Calls int version
    cout << add(5.2, 3.1) << endl;       // Calls double version
    cout << add(5, 3, 2) << endl;        // Calls three-parameter version
    cout << add("Hello, ", "World!") << endl; // Calls string version
    
    display(101, "Alice");
    display("Bob", 102);
    
    // Default parameter ambiguity
    // multiply(2, 3);  // Error: ambiguous call
    
    // Type promotion in overloading
    process(10);      // int version
    process(10.5);    // double version
    process('A');     // char promoted to int
    
    return 0;
}

4. Recursion

cpp

#include <iostream>
using namespace std;

// RECURSION: Function calling itself
// Components:
// 1. Base Case - stopping condition
// 2. Recursive Case - function calls itself
// 3. Progress toward base case

// 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);
}

// Print numbers from n to 1
void countDown(int n) {
    // Base case
    if (n < 1) return;
    
    cout << n << " ";
    
    // Recursive case
    countDown(n - 1);
}

// Print numbers from 1 to n
void countUp(int n, int current = 1) {
    // Base case
    if (current > n) return;
    
    cout << current << " ";
    
    // Recursive case
    countUp(n, current + 1);
}

// Power function: x^n
double power(double x, int n) {
    // Base cases
    if (n == 0) return 1;
    if (n == 1) return x;
    
    // Recursive case
    return x * power(x, n - 1);
}

// Binary search recursively
int binarySearch(int arr[], int left, int right, int target) {
    // Base case
    if (left > right) return -1;
    
    int mid = left + (right - left) / 2;
    
    if (arr[mid] == target) return mid;           // Found
    else if (arr[mid] > target)                   // Search left
        return binarySearch(arr, left, mid - 1, target);
    else                                          // Search right
        return binarySearch(arr, mid + 1, right, target);
}

// Stack memory visualization for recursion
void recursiveStack(int depth) {
    cout << "Entering depth: " << depth << endl;
    
    if (depth == 0) {
        cout << "Base case reached!" << endl;
        return;
    }
    
    recursiveStack(depth - 1);
    
    cout << "Exiting depth: " << depth << endl;
}

int main() {
    // Factorial example
    cout << "Factorial of 5: " << factorial(5) << endl;
    
    // Fibonacci example
    cout << "Fibonacci(6): ";
    for (int i = 0; i <= 6; i++) {
        cout << fibonacci(i) << " ";
    }
    cout << endl;
    
    // Count examples
    cout << "Count down from 5: ";
    countDown(5);
    cout << endl;
    
    cout << "Count up to 5: ";
    countUp(5);
    cout << endl;
    
    // Power example
    cout << "2^5 = " << power(2, 5) << endl;
    
    // Binary search example
    int arr[] = {2, 4, 6, 8, 10, 12, 14, 16, 18, 20};
    int size = sizeof(arr) / sizeof(arr[0]);
    int target = 12;
    
    int index = binarySearch(arr, 0, size - 1, target);
    if (index != -1)
        cout << "Found " << target << " at index " << index << endl;
    else
        cout << target << " not found" << endl;
    
    // Stack visualization
    cout << "\nRecursion stack trace:" << endl;
    recursiveStack(3);
    
    return 0;
}

Recursion vs Iteration Comparison

cpp

#include <iostream>
using namespace std;

// Factorial - Iterative version
int factorialIterative(int n) {
    int result = 1;
    for (int i = 1; i <= n; i++) {
        result *= i;
    }
    return result;
}

// Fibonacci - Iterative version (more efficient)
int fibonacciIterative(int n) {
    if (n <= 1) return n;
    
    int prev = 0, current = 1;
    for (int i = 2; i <= n; i++) {
        int next = prev + current;
        prev = current;
        current = next;
    }
    return current;
}

// When to use recursion vs iteration:
// RECURSION is good for:
// 1. Problems with recursive structure (trees, graphs)
// 2. Divide and conquer algorithms
// 3. When code clarity is important
//
// ITERATION is good for:
// 1. Performance-critical code
// 2. Deep recursion (avoid stack overflow)
// 3. Simple loops

int main() {
    int n = 10;
    
    cout << "Factorial(10):" << endl;
    cout << "Recursive: " << factorial(n) << endl;
    cout << "Iterative: " << factorialIterative(n) << endl;
    
    cout << "\nFibonacci(10):" << endl;
    cout << "Recursive: " << fibonacci(n) << endl;
    cout << "Iterative: " << fibonacciIterative(n) << endl;
    
    // Performance comparison
    cout << "\nNote: Fibonacci recursive is O(2^n), iterative is O(n)!" << endl;
    
    return 0;
}

5. Scope & Storage Classes

cpp

#include <iostream>
using namespace std;

// GLOBAL VARIABLES - File scope
int globalVar = 100;                    // Available throughout file
const double PI = 3.14159;              // Global constant

// Function prototype
void demonstrateScope();
void staticExample();
void externExample();

// EXTERN declaration (defined in another file)
extern int externalVar;                 // Declared here, defined elsewhere

int main() {
    // LOCAL VARIABLES - Block scope
    int localVar = 10;                  // Only available in main()
    
    cout << "In main():" << endl;
    cout << "Global variable: " << globalVar << endl;
    cout << "Local variable: " << localVar << endl;
    // cout << innerVar;  // Error: not in scope
    
    {
        // NESTED SCOPE
        int innerVar = 20;              // Only in this block
        cout << "In inner block:" << endl;
        cout << "Accessing localVar from outer scope: " << localVar << endl;
        cout << "Inner variable: " << innerVar << endl;
        
        // Shadowing
        int globalVar = 50;             // Shadows global variable
        cout << "Shadowed globalVar (local): " << globalVar << endl;
        cout << "Original globalVar (using scope resolution): " << ::globalVar << endl;
    }
    
    // cout << innerVar;  // Error: out of scope
    
    demonstrateScope();
    staticExample();
    staticExample();
    staticExample();
    
    return 0;
}

void demonstrateScope() {
    // Different function, different scope
    int localVar = 30;                  // Different from main()'s localVar
    
    cout << "\nIn demonstrateScope():" << endl;
    cout << "Global variable: " << globalVar << endl;
    cout << "Local variable: " << localVar << endl;
    
    // Can modify global variables (avoid when possible)
    globalVar = 200;
    cout << "Modified globalVar: " << globalVar << endl;
}

// STATIC variables
void staticExample() {
    int normalVar = 0;                  // Re-initialized each call
    static int staticVar = 0;           // Initialized once, persists
    
    normalVar++;
    staticVar++;
    
    cout << "\nIn staticExample():" << endl;
    cout << "normalVar: " << normalVar << " (resets each call)" << endl;
    cout << "staticVar: " << staticVar << " (persists between calls)" << endl;
}

// REGISTER keyword (deprecated in C++17, hint to store in register)
void registerExample() {
    register int counter;               // Suggestion to compiler
    for (counter = 0; counter < 10; counter++) {
        // Fast access if in register
    }
}

// AUTO type inference (C++11+)
void autoExample() {
    auto x = 10;                        // int
    auto y = 3.14;                      // double
    auto name = "Alice";                // const char*
    
    cout << "\nType inference with auto:" << endl;
    cout << "x type: " << typeid(x).name() << ", value: " << x << endl;
    cout << "y type: " << typeid(y).name() << ", value: " << y << endl;
    cout << "name type: " << typeid(name).name() << ", value: " << name << endl;
    
    // auto in functions (C++14+)
    auto add = [](auto a, auto b) {     // Lambda with auto parameters
        return a + b;
    };
    
    cout << "add(5, 3): " << add(5, 3) << endl;
    cout << "add(2.5, 3.7): " << add(2.5, 3.7) << endl;
}

Complete Scope Example

cpp

#include <iostream>
using namespace std;

// Storage Classes Summary:

// auto (C++11): Automatic type inference
// register: Hint to store in CPU register (deprecated)
// static: 
//   - Local: Persists between function calls
//   - Global: File scope only (not accessible from other files)
// extern: Declares variable defined in another file
// mutable: Allows modification in const objects (for class members)

// Global static (file scope only)
static int fileScopedVar = 500;  // Only accessible in this file

// Global extern (defined here, declared elsewhere)
int externalVar = 1000;

void scopeDemonstration() {
    // Automatic (default) - created on stack, destroyed when out of scope
    auto automaticVar = 42;
    
    // Static local - persists between calls
    static int persistentCounter = 0;
    persistentCounter++;
    
    // Register (compiler hint)
    register int fastCounter;
    for (fastCounter = 0; fastCounter < 5; fastCounter++) {
        // Loop code
    }
    
    cout << "automaticVar: " << automaticVar << endl;
    cout << "persistentCounter: " << persistentCounter << endl;
    cout << "fileScopedVar: " << fileScopedVar << endl;
    cout << "externalVar: " << externalVar << endl;
}

int main() {
    cout << "=== Storage Classes Demo ===" << endl;
    
    scopeDemonstration();
    scopeDemonstration();
    scopeDemonstration();
    
    // Modern C++ auto usage
    autoExample();
    
    return 0;
}

Memory Layout Visualization

cpp

#include <iostream>
using namespace std;

/*
Memory Layout of a C++ Program:

1. Code/Text Segment: 
   - Stores executable instructions
   - Read-only

2. Data Segment:
   - Global & static variables
   - Initialized data (.data)
   - Uninitialized/Zero-initialized (.bss)

3. Heap:
   - Dynamic memory allocation (new, malloc)
   - Grows upward
   - Manual management required

4. Stack:
   - Local variables
   - Function parameters, return addresses
   - Grows downward
   - Automatic management

Example Layout:
High Addresses
|----------------|
|     Stack      | ← Grows downward
|----------------|
|      ↓         |
|      (gap)     |
|      ↑         |
|----------------|
|      Heap      | ← Grows upward
|----------------|
|   Data Segment |
|----------------|
|   Code Segment |
|----------------|
Low Addresses
*/

int globalVar;            // Data segment (BSS - zero-initialized)
int initGlobal = 10;      // Data segment (initialized data)
static int staticGlobal;  // Data segment

void memoryExample() {
    int localVar = 5;     // Stack
    static int staticLocal = 0;  // Data segment
    int* heapVar = new int(20);  // Heap (pointer on stack, value on heap)
    
    cout << "Stack variable address: " << &localVar << endl;
    cout << "Heap variable address: " << heapVar << endl;
    cout << "Global variable address: " << &globalVar << endl;
    cout << "Static local address: " << &staticLocal << endl;
    
    delete heapVar;  // Must free heap memory
}

int main() {
    memoryExample();
    return 0;
}

Key Takeaways:

  1. Functions: Break code into reusable, manageable pieces

  2. Overloading: Same name, different parameters for similar operations

  3. Recursion: Elegant for certain problems, but watch for stack overflow

  4. Scope: Variables are only accessible within their defined scope

  5. Storage Classes: Control variable lifetime and visibility

  6. Memory: Understand stack vs heap, automatic vs dynamic allocation

Comments (0)

No comments yet. Be the first to comment!

LecturreAI Assistant

Hello! I'm LecturreAI

What would you like to learn today?