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 rawstructand 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
| Feature | Procedural 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/O | printf("%d", x);scanf("%d", &x); | std::cout << x;std::cin >> x; | Type-Safe: No format specifiers (%d, %f). Eliminates format mismatch bugs. |
| Strings | char str[50];strcpy, strcmp, strlen | std::string str;+, ==, .length() | Dynamic & Safe: Automatically manages heap buffers, preventing buffer overflows. |
| Dynamic Memory | malloc() / free() | new / delete, new[] / delete[] | Constructor Aware: new invokes constructors; delete invokes destructors. |
| Argument Passing | Pointers only: swap(&a, &b) | Native references: swap(a, b) | Clean Syntax: No dereferencing (*) in body; no address-of (&) at call site. |
| Booleans | int (1/0) or <stdbool.h> | Built-in bool, true, false | First-class language primitive. |
| Function Overload | ❌ Not permitted | ✅ Fully supported | Multiple functions can share names if signatures differ. |
| Default Arguments | ❌ Not permitted | ✅ Supported (int c = 0) | Reduces boilerplate overloaded functions. |
| Structures | Plain 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 likeobj.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++
classorstructmust terminate with a semicolon after the closing brace:class MyClass { ... };.
Trap 3: Stream Operator Reversal
Writing
cin << age;orcout >> name;. Remember:cin >> varextracts from input;cout << datainserts into output.
Trap 4: Mixing
mallocwith C++ ClassesNever use
malloc()for C++ classes.malloc()allocates raw bytes without executing constructors, leaving internal pointers, strings, and virtual tables uninitialized. Always usenew.
💬 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.