Lesson 1: Transitioning from C to C++ & Core Class Structure

Lesson Overview

This lesson bridges your procedural C programming knowledge directly into modern C++ and Object-Oriented Programming (OOP). Every theoretical concept is paired with direct, side-by-side C vs. C++ code examples covering standard I/O, string manipulation, memory allocation, references vs. pointers, function overloading, inline functions, and classes.

💡 New to OOP terms? Check out the Master Concept Glossary & Dictionary for quick ELI5 definitions.


🚀 1. The Paradigm Shift: Procedural (C) vs. Object-Oriented (C++)

  • Procedural Programming (C): Focuses on procedures or functions operating on separate, passive data structures. Data moves freely across the application, making large codebases vulnerable to unintended state modifications.
  • Object-Oriented Programming (C++): Organizes code into objects {self-contained entities bundling state and behavior}. This guarantees encapsulation {bundling data and methods into a single protective capsule} and data hiding {keeping internal variables private so outside code cannot alter them directly}.
graph LR
    subgraph Procedural ["Procedural Paradigm (C)"]
        F1[Function A] --> D1[(Global / Passive Data)]
        F2[Function B] --> D1
    end

    subgraph OOP ["Object-Oriented Paradigm (C++ / Python)"]
        O1["Object 1<br>(Data + Methods)"] <--> O2["Object 2<br>(Data + Methods)"]
    end

⚡ Core Shift: C Python C++

  • Encapsulation: Bundles data (private) and methods (public) into a single type. Replaces C’s raw struct and Python’s _ convention with compiler-enforced access protection.
  • Variables & Typing: Statically typed like C, but encapsulated into object blueprints like Python. Memory is managed deterministically on the stack/heap.

🔄 2. The C-to-C++ Syntax Rosetta Stone

FeatureProcedural C (C89 / C99)Modern C++ (C++11/14/17)Why C++ is Superior / Exam Note
Headers<stdio.h>, <stdlib.h>, <string.h><iostream>, <string>, <vector>Standard library wrapped in namespace std.
Console I/Oprintf("%d", x);
scanf("%d", &x);
std::cout << x;
std::cin >> x;
Type-Safe: No format specifiers (%d, %f). Eliminates format mismatch bugs.
Stringschar str[50];
strcpy, strcmp, strlen
std::string str;
+, ==, .length()
Dynamic & Safe: Automatically manages heap buffers, preventing buffer overflows.
Dynamic Memorymalloc() / free()new / delete, new[] / delete[]Constructor Aware: new invokes constructors; delete invokes destructors.
Argument PassingPointers only: swap(&a, &b)Native references: swap(a, b)Clean Syntax: No dereferencing (*) in body; no address-of (&) at call site.
Booleansint (1/0) or <stdbool.h>Built-in bool, true, falseFirst-class language primitive.
Function Overload❌ Not permitted✅ Fully supportedMultiple functions can share names if signatures differ.
Default Arguments❌ Not permitted✅ Supported (int c = 0)Reduces boilerplate overloaded functions.
StructuresPlain Old Data (typedef struct)Full class counterpart (public default)Can contain methods, access specifiers, and constructors.
Macros vs Inlines#define SQUARE(x) ((x)*(x))inline int square(int x)Type-safe, avoids multiple-evaluation side effects.

💻 3. Direct Side-by-Side Code Examples: C vs. C++


Example 1: Console Input, Output & Buffer Management

/* ======================== PROCEDURAL C ======================== */
#include <stdio.h>
 
int main() {
    int roll;
    char name[50];
 
    printf("Enter Roll: ");
    scanf("%d", &roll); // Requires format specifier %d and address-of &
 
    printf("Enter Name: ");
    // Problematic: leaves newline in buffer, needs special format specifier
    scanf(" %[^\n]", name); 
 
    printf("Student: %s | Roll: %d\n", name, roll);
    return 0;
}
// ========================= MODERN C++ =========================
#include <iostream>
#include <string>
using namespace std;
 
int main() {
    int roll;
    string name;
 
    cout << "Enter Roll: ";
    cin >> roll; // Type-safe: automatically detects int
    
    cin.ignore(); // Flush the trailing newline '\n' left by cin >> roll
 
    cout << "Enter Name: ";
    getline(cin, name); // Safely reads full line with spaces into dynamic string
 
    cout << "Student: " << name << " | Roll: " << roll << endl;
    return 0;
}

Example 2: String Handling & Manipulation

/* ======================== PROCEDURAL C ======================== */
#include <stdio.h>
#include <string.h>
 
int main() {
    char s1[50] = "KUET ";
    char s2[] = "CSE";
 
    // String Concatenation: Risk of buffer overflow if s1 is too small
    strcat(s1, s2);
 
    // String Length
    int len = strlen(s1);
 
    // String Comparison
    if (strcmp(s1, "KUET CSE") == 0) {
        printf("Equal! String: %s (Length: %d)\n", s1, len);
    }
    return 0;
}
// ========================= MODERN C++ =========================
#include <iostream>
#include <string>
using namespace std;
 
int main() {
    string s1 = "KUET ";
    string s2 = "CSE";
 
    // String Concatenation: Natural '+' operator with automatic memory resizing
    string result = s1 + s2; 
 
    // String Length & Direct Comparison
    if (result == "KUET CSE") { // Natural '==' comparison
        cout << "Equal! String: " << result << " (Length: " << result.length() << ")" << endl;
    }
    return 0;
}

Example 3: Dynamic Memory Allocation (malloc/free vs. new/delete)

/* ======================== PROCEDURAL C ======================== */
#include <stdio.h>
#include <stdlib.h>
 
int main() {
    int n = 3;
 
    // Must calculate byte size manually and cast (int*)
    int *arr = (int*)malloc(n * sizeof(int));
    if (arr == NULL) return 1;
 
    for (int i = 0; i < n; i++) arr[i] = (i + 1) * 10;
    for (int i = 0; i < n; i++) printf("%d ", arr[i]);
    printf("\n");
 
    free(arr); // Deallocates memory (no destructor calls)
    return 0;
}
// ========================= MODERN C++ =========================
#include <iostream>
using namespace std;
 
int main() {
    int n = 3;
 
    // Type-safe allocation: calculates bytes automatically
    int *arr = new int[n]; 
 
    for (int i = 0; i < n; i++) arr[i] = (i + 1) * 10;
    for (int i = 0; i < n; i++) cout << arr[i] << " ";
    cout << endl;
 
    delete[] arr; // Releases memory array (invokes destructors if objects)
    return 0;
}

Example 4: References vs. Pointers (Pass-by-Reference)

/* ======================== PROCEDURAL C ======================== */
#include <stdio.h>
 
// Must pass addresses and dereference explicitly using '*'
void swapPointers(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}
 
int main() {
    int x = 10, y = 20;
    swapPointers(&x, &y); // Must pass explicit addresses '&'
    printf("x: %d, y: %d\n", x, y); // x: 20, y: 10
    return 0;
}
// ========================= MODERN C++ =========================
#include <iostream>
using namespace std;
 
// Native reference alias ('&'): no pointer dereferencing needed inside!
void swapReferences(int &a, int &b) {
    int temp = a;
    a = b;
    b = temp;
}
 
int main() {
    int x = 10, y = 20;
    swapReferences(x, y); // Clean call site: no '&' required!
    cout << "x: " << x << ", y: " << y << endl; // x: 20, y: 10
    return 0;
}

Example 5: Function Overloading & Default Arguments

/* ======================== PROCEDURAL C ======================== */
#include <stdio.h>
 
// In C, functions CANNOT share the same name!
int addInt(int a, int b) { return a + b; }
double addDouble(double a, double b) { return a + b; }
int addThree(int a, int b, int c) { return a + b + c; }
 
int main() {
    printf("%d\n", addInt(2, 3));
    printf("%.2f\n", addDouble(2.5, 3.5));
    printf("%d\n", addThree(1, 2, 3));
    return 0;
}
// ========================= MODERN C++ =========================
#include <iostream>
using namespace std;
 
// Overload 1: Two integers
int add(int a, int b) { 
    return a + b; 
}
 
// Overload 2: Two doubles
double add(double a, double b) { 
    return a + b; 
}
 
// Overload 3: Three integers with a DEFAULT ARGUMENT for 'c'
int add(int a, int b, int c = 10) { 
    return a + b + c; 
}
 
int main() {
    cout << add(5, 5) << endl;       // Calls Overload 1 -> 10
    cout << add(2.5, 3.5) << endl;   // Calls Overload 2 -> 6.0
    cout << add(1, 2, 3) << endl;    // Calls Overload 3 -> 6
    return 0;
}

Example 6: Preprocessor Macros vs. inline Functions

/* ======================== PROCEDURAL C ======================== */
#include <stdio.h>
 
// Macro Hazard: Multiple evaluations cause dangerous side effects!
#define SQUARE(x) ((x) * (x))
 
int main() {
    int a = 5;
    // Expands to: ((a++) * (a++)) -> Undefined behavior, increments twice!
    int res = SQUARE(a++); 
    printf("Result: %d, a: %d\n", res, a); // Result: 30 or 25, a: 7 (Bug!)
    return 0;
}
// ========================= MODERN C++ =========================
#include <iostream>
using namespace std;
 
// Type-safe inline function: expands at compile time without side effects
inline int square(int x) {
    return x * x;
}
 
int main() {
    int a = 5;
    int res = square(a++); // Safe: 'a' evaluated once, then passed
    cout << "Result: " << res << ", a: " << a << endl; // Result: 25, a: 6 (Correct!)
    return 0;
}

Example 7: From C struct (Plain Data) to C++ class (Encapsulation)

/* ======================== PROCEDURAL C ======================== */
#include <stdio.h>
 
// In C, structs only contain passive data; functions exist separately
typedef struct {
    int accNo;
    float balance;
} BankAccount;
 
void deposit(BankAccount *acc, float amount) {
    acc->balance += amount; // No access control; anyone can modify balance directly!
}
 
int main() {
    BankAccount myAcc = {101, 500.0f};
    deposit(&myAcc, 200.0f);
    myAcc.balance = -99999.0f; // DANGER: Direct corruption of state allowed in C
    printf("Acc: %d | Balance: $%.2f\n", myAcc.accNo, myAcc.balance);
    return 0;
}
// ========================= MODERN C++ =========================
#include <iostream>
using namespace std;
 
class BankAccount {
private: // Data Hiding: External code cannot corrupt balance!
    int accNo;
    float balance;
 
public:
    // Methods bundled directly inside the class
    void initialize(int no, float initialBal) {
        accNo = no;
        balance = (initialBal >= 0) ? initialBal : 0.0f;
    }
 
    void deposit(float amount) {
        if (amount > 0) {
            balance += amount;
            cout << "Deposited: $" << amount << endl;
        }
    }
 
    void display() const {
        cout << "Acc: " << accNo << " | Balance: $" << balance << endl;
    }
};
 
int main() {
    BankAccount myAcc;
    myAcc.initialize(101, 500.0f);
    myAcc.deposit(200.0f);
 
    // myAcc.balance = -99999.0f; // ❌ COMPILE ERROR: 'balance' is private!
    myAcc.display(); // Acc: 101 | Balance: $700
    return 0;
}

🧠 4. Under-the-Hood Memory Layout

[ RAM Memory Layout during Execution ]

   Stack Memory Segment (Unique allocation for every object)
  +-----------------------------------------------------------+
  |  Object: myAcc                                            |
  |  - int accNo:     101      (4 bytes)                      |
  |  - float balance: 700.00   (4 bytes)                      |
  +-----------------------------------------------------------+

   Code / Text Segment (Shared - Loaded into memory ONCE)
  +-----------------------------------------------------------+
  |  BankAccount Member Functions:                            |
  |  - void initialize(int, float) { ... }                    |
  |  - void deposit(float) { ... }                            |
  |  - void display() const { ... }                           |
  +-----------------------------------------------------------+

⚠️ 5. The Debugger’s Guide (Common Traps for C Developers)

Trap 1: Accidental Private Access

Struct members in C are always public. In C++ classes, members default to private. Direct access like obj.balance = 500; triggers 'var' is private within this context. Wrap access in public setters/getters.

Trap 2: Missing Class Semicolon

Unlike C function bodies, a C++ class or struct must terminate with a semicolon after the closing brace: class MyClass { ... };.

Trap 3: Stream Operator Reversal

Writing cin << age; or cout >> name;. Remember: cin >> var extracts from input; cout << data inserts into output.

Trap 4: Mixing malloc with C++ Classes

Never use malloc() for C++ classes. malloc() allocates raw bytes without executing constructors, leaving internal pointers, strings, and virtual tables uninitialized. Always use new.


💬 6. Viva Quick-Prep

Q1: What is the fundamental difference between a class and a struct in C++?

Answer: Default access specifiers. Members and base inheritance of a class default to private, whereas members and base inheritance of a struct default to public.

Q2: What is the difference between a pointer and a reference in C++?

Answer: A pointer is a variable holding a memory address (can be reassigned, can be nullptr, requires dereferencing *). A reference (&) is an immutable alias for an existing variable, cannot be null, and is used with standard variable syntax.

Q3: Why is new preferred over malloc() in C++?

Answer: new is type-safe, automatically calculates allocation sizes without sizeof, returns the correct type without casting, and automatically calls constructors. malloc() only allocates raw, uninitialized memory bytes.

Q4: Why is cin.ignore() necessary before calling getline()?

Answer: Because cin >> var leaves the trailing newline character \n in the input stream buffer. Calling cin.ignore() discards this newline so that getline() can read the next actual line of user input.