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:
-
Functions: Break code into reusable, manageable pieces
-
Overloading: Same name, different parameters for similar operations
-
Recursion: Elegant for certain problems, but watch for stack overflow
-
Scope: Variables are only accessible within their defined scope
-
Storage Classes: Control variable lifetime and visibility
-
Memory: Understand stack vs heap, automatic vs dynamic allocation
Comments (0)
No comments yet. Be the first to comment!