Module 1: Foundations & Paradigms
Programming Paradigms
A paradigm is a model or pattern for how we express computation. The two main branches are Imperative (how to execute) and Declarative (what to execute).
- Imperative/Procedural: The classic approach. You provide a step-by-step sequence of commands that change the program's state. (e.g., C, FORTRAN)
- Object-Oriented (OOP): An extension of imperative programming. Data and the code that operates on that data are bundled into "objects". (e.g., C++, Java, C#)
- Functional/Applicative: Treats computation as the evaluation of mathematical functions. It avoids changing state and mutable data (no side-effects). (e.g., Scheme, LISP)
- Logic/Declarative: You define a set of facts and rules. The computer uses logic to figure out the solution. The focus is on the goal, not the algorithm. (e.g., Prolog)
Program Structure & Errors
Programs are analyzed in layers, from basic symbols to their ultimate meaning. Errors can occur at different stages.
- Lexical: The basic building blocks (tokens) like keywords (
int), identifiers (myVar), and operators (+). - Syntactic: The grammar rules. Does
if (x > 0)follow the language's structure? - Contextual: The rules beyond grammar. Did you declare a variable before using it? Are the types compatible (e.g., you can't add a string to an integer without conversion)? Type checking happens here.
- Semantic: The runtime meaning. A classic semantic error is division by zero. The code might be syntactically and contextually correct, but its behavior is invalid.
Macros vs. Functions
Both macros and functions are ways to reuse code, but they work very differently.
A macro is a preprocessor directive. It's a simple text substitution that happens *before* compilation. It's like a find-and-replace for your code.
// Macro definition
#define MAX(x, y) ((x) > (y) ? (x) : (y))
int a = 5, b = 10;
int m = MAX(a, b); // Preprocessor changes this to: int m = ((a) > (b) ? (a) : (b));
- Pros: Faster execution as there is no function call overhead. Code is "inlined".
- Cons (Side Effects): Arguments are evaluated every time they appear.
MAX(a++, b++)will increment the variables multiple times, leading to unexpected behavior. The programmer is responsible for correctness. Parentheses are crucial to avoid operator precedence issues.
A function involves a proper function call. Execution jumps to the function's code, arguments are pushed to the stack, and a value is returned. It's safer but has a small performance overhead.
Module 2: Data Types & Basic I/O
Data Types & Type Checking
A data type defines a set of allowed values and the operations that can be performed on them. C/C++ have several primitive types:
char: A single character (typically 1 byte).int: An integer. Can be modified withshort,long,signed,unsigned.float,double: Floating-point numbers with different precision.void: Represents the absence of a type.- C++ adds
bool(true/false) andwchar_t(wide character).
Type Checking ensures that operations are valid for the given types.
- Static Typing (C/C++): Types are checked at compile-time. Catches errors early.
- Dynamic Typing (Python, JS): Types are checked at run-time. More flexible, but errors might only appear during execution.
- Strong vs. Weak Typing: Refers to how strictly type rules are enforced. C++ is strongly typed (you can't add an int to a string without explicit conversion), while C is weaker (
charandintcan sometimes be used interchangeably).
Basic I/O in C and C++
Formatted I/O reads/writes data in specific formats.
In C, we use <stdio.h>:
#include <stdio.h>
int main() {
int i;
printf("Enter an integer: ");
// scanf needs the ADDRESS of the variable to store the value
scanf("%d", &i);
printf("You entered: %d\n", i);
return 0;
}
In C++, we use <iostream>:
#include <iostream>
int main() {
int i;
std::cout << "Enter an integer: ";
std::cin >> i; // cin handles the memory automatically
std::cout << "You entered: " << i << std::endl;
return 0;
}
Unformatted I/O reads raw characters or lines. A common issue is mixing formatted and unformatted input, which can leave a newline character (\n) in the input buffer. Use functions like fflush(stdin) (C) or cin.ignore() (C++) to clear it.
Module 3: Pointers, Arrays & Structs
Pointers: The Core Concept
A pointer is a variable that stores the memory address of another variable. The value of a pointer is an integer representing a location in memory.
- Referencing (`&`): The "address-of" operator. It gets the memory address of a variable.
int* p = &myVar; - Dereferencing (`*`): The "value-at-address" operator. It gets the value stored at the memory address the pointer is holding.
int value = *p;
Pointers allow for indirect modification of data, which is essential for dynamic memory, data structures, and efficient function parameter passing.
int x = 10; // A regular integer variable
int* ptr; // A pointer to an integer
ptr = &x; // ptr now holds the memory address of x
*ptr = 20; // Dereference ptr to change the value AT that address.
// This changes x to 20!
printf("Value of x is: %d\n", x); // Prints 20
Arrays and Strings
An array is a collection of elements of the same type stored in contiguous memory locations. In C, the name of an array "decays" into a pointer to its first element.
int arr[5] = {10, 20, 30, 40, 50};
int* p = arr; // Same as p = &arr[0];
// These two lines are equivalent:
printf("%d\n", arr[2]); // Prints 30 (Array notation)
printf("%d\n", *(p + 2)); // Prints 30 (Pointer arithmetic)
A C-style string is simply an array of characters that is terminated by a special null character ('\0'). This terminator is crucial for functions like strcpy or printf("%s", ...) to know where the string ends.
Defining New Types: `struct`, `enum`, `typedef`
You can create your own custom data types.
struct: A composite type that groups together variables of different types under a single name. Similar to a class in Java but traditionally only contains data.
struct Contact {
char name[50];
int phone;
};
struct Contact person1;
strcpy(person1.name, "Ada Lovelace");
person1.phone = 12345;
enum: Creates a type where variables can only hold a specific set of named integer constants.enum Day { MON, TUE, WED, THU, FRI, SAT, SUN }; // MON=0, TUE=1, ...
enum Day today = WED;
typedef: Creates a synonym or alias for an existing type, making code more readable.typedef unsigned long ulong;
ulong bigNumber = 1000000UL;
// Often used with structs to avoid typing 'struct' repeatedly
typedef struct Contact Person;
Person person2;
Module 4: Memory & File I/O
Memory Organization: Static, Stack, Heap
A C/C++ program's memory is divided into three main areas:
- Static/Global: Memory for global and `static` variables. Allocated once when the program starts and lasts for the entire execution.
- Stack: Memory for local variables and function parameters. It's managed automatically. When a function is called, a "stack frame" is created for its variables. When the function returns, the frame is destroyed. This is fast but limited in size.
- Heap: A large pool of memory for dynamic allocation. You manually request memory from the heap using `malloc()` (C) or `new` (C++), and you are responsible for freeing it with `free()` or `delete`. This is where memory leaks happen!
Parameter Passing
How data is passed to functions is crucial.
- Pass-by-Value: The function receives a *copy* of the argument's value. Changes inside the function do not affect the original variable. This is the default in C/C++.
- Pass-by-Address (Pointer): The function receives the memory *address* of the argument. By dereferencing this pointer, the function can directly modify the original variable.
- Pass-by-Alias (Reference - C++ only): The function receives an *alias* for the original variable. Syntactically it looks like pass-by-value, but it behaves like pass-by-address, allowing direct modification of the original.
// Pass-by-Value (y is a copy)
// Pass-by-Address (*x points to original)
void func(int *x, int y) {
*x = *x + y; // Modifies the original variable x points to
y = 2; // Modifies only the local copy of y
}
int main() {
int a = 10, b = 10;
func(&a, b);
// After call: a is 20, b is still 10
printf("a: %d, b: %d\n", a, b);
}
File I/O and Buffering
Reading from/writing to a disk is very slow. To improve performance, file operations use a buffer—a temporary storage area in memory. When you open a file with fopen(), a buffer is created. Data is read from the disk into the buffer in large chunks, or written from your program to the buffer. The buffer is only flushed (written) to the actual disk file when it's full or when you explicitly close the file with fclose().
Linked Lists
A linked list is a fundamental dynamic data structure. It's a sequence of nodes where each node contains data and a pointer to the next node in the sequence. The list is accessed via a pointer to the first node, called the head.
struct Node {
int data;
struct Node* next;
};
Operations like adding or removing nodes involve manipulating these next pointers to correctly link the nodes together.
Module 5: Recursion & Data Structures
Recursion
A function is recursive if it calls itself. A recursive solution must have two parts:
- Base Case: A stopping condition that does not make a recursive call. This prevents infinite loops.
- Recursive Step: The part of the function that calls itself, but on a smaller or simpler version of the problem.
// Recursive factorial function
int factorial(int n) {
// Base Case
if (n == 0) {
return 1;
}
// Recursive Step
else {
return n * factorial(n - 1); // Solves a smaller problem
}
}
While elegant, recursive calls use the stack for each call. Deep recursion can lead to a "stack overflow" error.
Trees and Binary Search Trees (BST)
A tree is a hierarchical data structure. If each node can have at most two children (a left and a right child), it's a binary tree.
A Binary Search Tree (BST) is a special binary tree that maintains a specific ordering property:
- For any given node, all values in its left subtree are less than the node's value.
- For any given node, all values in its right subtree are greater than the node's value.
This property makes searching for an element very efficient, with an average complexity of O(log n).
Tree Traversal
Traversal is the process of visiting each node in the tree exactly once. The three main recursive traversal orders are:
- Pre-order: Visit Root, then traverse Left subtree, then traverse Right subtree.
- In-order: Traverse Left subtree, then visit Root, then traverse Right subtree. (For a BST, this visits nodes in sorted order!)
- Post-order: Traverse Left subtree, then traverse Right subtree, then visit Root. (Useful for deleting a tree).
Module 6: OOP & Memory Management
Object-Oriented Programming (OOP) in C++
OOP is a paradigm centered around objects. A class is a blueprint for creating objects. An object is an instance of a class.
Key Principles:
- Encapsulation: Bundling data (member variables) and the methods (member functions) that operate on the data into a single unit (the class).
- Information Hiding: Restricting access to certain members of an object. This is done with access specifiers:
public,protected, andprivate. Data members are almost always keptprivate. - Abstraction: Hiding the complex implementation details and showing only the essential features of the object.
Constructors and Destructors
These are special member functions that control an object's life cycle.
- Constructor: A function with the same name as the class. It's called automatically when an object is created. Its job is to initialize the object's state.
- Destructor: A function with the same name as the class, preceded by a tilde (
~). It's called automatically when an object is destroyed. Its primary job is to release any resources the object acquired, especially heap memory.
class Queue {
private:
int* buffer;
int size;
public:
// Constructor: allocates heap memory
Queue(int s) {
size = s;
buffer = new int[size]; // Request memory from the heap
}
// Destructor: must free the heap memory
~Queue() {
delete[] buffer; // Release the memory
}
};
Manual Memory Management & Garbage Collection
C++ does not have an automatic garbage collector. You, the programmer, are responsible for memory management.
- Memory Leak: Occurs when you allocate memory on the heap (with
newormalloc) but forget to deallocate it (withdeleteorfree). The memory remains reserved and unusable for the rest of the program's life. - Dangling Pointer: A pointer that points to a memory location that has already been freed. Accessing a dangling pointer leads to undefined behavior.
The Rule of Thumb: For every new, there must be a corresponding delete. For every new[] (array allocation), there must be a corresponding delete[].
The best way to delete a dynamic data structure like a linked list or tree is to traverse it and delete each node one by one. For a tree, a post-order traversal is ideal for deletion, as it deletes children before their parent.