Introduction to Object-Oriented Programming
C++ supports object-oriented programming (OOP), which centers around the concept of objects—entities that combine data and behavior. This contrasts with C's procedural approach, where focus lies on functions and step-by-step execution.
Consider laundry as an example:
- Procedural: Break the task into steps: open machine → add clothes and detergent → select cycle → wait → remove clothes.
- Object-Oriented: Identify two entities: a person and a washing machine. The person performs actions like opening the machine and adding items; the machine handles the wash cycle internally.
This shift emphasizes collaboration between components rather than linear sequences, leading to better maintainability and scalability.
Defining Classes in C++
In C++, structs can contain both data and functions, effectively beccoming classes. While struct is still compatible with C-style syntax, class is typically preferred for defining new types.
#include <iostream>
#include <cstring>
using namespace std;
class Student {
public:
void Initialize(const char* name, const char* gender, int age) {
strcpy(_name, name);
strcpy(_gender, gender);
_age = age;
}
void Display() const {
cout << _name << " " << _gender << " " << _age << endl;
}
private:
char _name[20];
char _gender[3];
int _age;
};
int main() {
Student s1;
s1.Initialize("Alice", "Female", 25);
s1.Display();
return 0;
}
Member variables are often prefixed with _ or m_ for clarity, though this is not mandatory.
Class Structure and Access Control
A class defines a scope containing members—data (attributes) and functions (methods). Syntax:
class ClassName {
// members
};
Two common approaches:
- Define all members inside the class body (often treated as inline).
- Separate declaration (in
.h) from implementation (in.cpp). The latter is recommended for larger projects.
Access specifiers control visibility:
public: accessible from outside the class.private: inaccessible externally; default forclass.protected: similar to private but relevant in inheritance.
Access scope starts at the specifier and continues until the next one appears. If none follows, it lasts until the closing brace.
Encapsulation: Hiding Complexity
Encapsulation bundles data and methods within a class, exposing only necessary interfaces. It promotes:
- Security by hiding internal logic.
- Reusability through well-defined APIs.
- Simplicity—users interact with public methods without knowing implementation details.
Think of it like a museum: visitors can enter via tickets (public interface), but sensitive artifacts remain protected behind walls (private/protected members).
Class Scope and Member Resolution
Each class creates its own scope. External member definitions must use the scope resolution operator (::):
class Student {
public:
void Display();
private:
char _name[20];
char _gender[3];
int _age;
};
void Student::Display() {
cout << _name << " " << _gender << " " << _age << endl;
}
Object Instantiation
Instantiation means creating real instances from a class blueprint. A class itself doesn’t occupy memory—it’s a template. Only when objects are created do they receive space for member variables.
Like architectural plans: the design exists abstractly, but physical buildings (objects) are built from it and occupy real space.
Size of Class Objects
The size of a class object equals the sum of its member variable sizes, adjusted for alignment.
#include <iostream>
using namespace std;
class A1 {
public:
void func1() {}
private:
int data;
};
class A2 {
public:
void func2() {}
};
class A3 {};
int main() {
cout << sizeof(A1) << endl; // Output: 4 (on most systems)
cout << sizeof(A2) << endl; // Output: 1 (empty class gets 1 byte)
cout << sizeof(A3) << endl; // Output: 1
return 0;
}
Key points:
- Member functions aren’t stored per instance—they reside in code segment.
- Empty classes are given 1 byte by the compiler to ensure uniqueness.
- Alignment rules apply: each member aligns to its natural boundary (min of type size and default alignment, usually 8 bytes).
- Total size must be a multiple of the largest alignment requirement.
The this Pointer
Every non-static member function implicitly receives a this pointer—a constant pointer to the calling object.
class Date {
public:
void Print() const {
cout << _year << "-" << _month << "-" << _day << endl;
}
void SetDate(int y, int m, int d) {
_year = y;
_month = m;
_day = d;
}
private:
int _year, _month, _day;
};
int main() {
Date d1, d2;
d1.SetDate(2024, 7, 14);
d2.SetDate(2024, 7, 13);
d1.Print();
d2.Print();
return 0;
}
Under the hood, SetDate becomes:
void SetDate(Date* const this, int y, int m, int d)
Properties of this:
- Type:
ClassName* const— cannot change the pointer, but can modify contents. - Available only inside member functions.
- Passed automatically by the compiler; no need for explicit argument.
- Stored in register (e.g., ECX) or stack depending on optimization.
Edge Case: Null Pointer
Student* ptr = nullptr;
ptr->Display(); // Works if no member access
This works because Display() accesses only data via this, and the function address is shared across all instances.
But:
void Display() const {
cout << _name << endl; // Accesses member via this
}
Calling ptr->Display() with nullptr causes a crash—dereferencing null leads to undefined behavior.
Thus, while member functions don’t require valid object addresses for their own execution, accessing members through this fails if this is null.