Classes in C++
Struct Evolution in C++
In C, struct defines structures containing only data. C++ extends struct to support defining classes with both data members and member functions bundled together.
Class Definition Syntax
class ClassName {
// Member variables (attributes)
// Member functions (methods)
};
The trailing semicolon is mandatory.
Two Definition Approaches
Inline Definition: Both declaration and implementation reside within the class body.
class Rectangle {
int width;
int height;
int calculateArea() {
return width * height;
}
};
External Definition: Declaration inside the class, implementation outside.
class Rectangle {
int width;
int height;
public:
int calculateArea();
};
int Rectangle::calculateArea() {
return width * height;
}
Instantiation
Creating objects from a class template:
class Person {
std::string name;
int age;
};
Person student;
The Three Pillars of OOP: Encapsulation, Inheritance, and Polymorphism
Encapsulation
Encapsulation combines data and operations on thatt data into a single unit, hiding internal complexity while exposing only necessary interfaces.
Consider a smartphone: users interact through the screen and buttons, unaware of the complex circuitry within. This protective barrier prevents unauthorized access and misuse.
Implementation involves:
- Grouping data members and member functions together
- Controlling visibility through access specifiers
Access Specifeirs:
| Specifier | Access Level |
|---|---|
public |
Accessible from anywhere |
protected |
Accessible within class and derived classes |
private |
Accessible only within the class |
Key Rules:
- Access scope begins at the specifier and continues until the next specifier or class end
classdefaults toprivateaccess;structdefaults topublic(for C compatibility)- Members of the same class can access each other's private members
class BankAccount {
private:
double balance;
std::string accountNumber;
public:
void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}
double getBalance() const {
return balance;
}
};
Class Size and Memory Layout
Objects store only member variables, not member functions. All objects of a class share the same function code.
#include <iostream>
class StudentA {
char grade;
int score;
void display() {
std::cout << "Display" << std::endl;
}
};
class StudentB {
bool passed;
void display() {
std::cout << "Display" << std::endl;
}
};
class EmptyClass {
};
int main() {
StudentA a;
StudentB b;
EmptyClass c;
std::cout << sizeof(a) << std::endl; // 8 (with padding)
std::cout << sizeof(b) << std::endl; // 4 (with padding)
std::cout << sizeof(c) << std::endl; // 1 (placeholder byte)
}
Memory Alignment: Class size equals the sum of member variables, aligned to the largest member's boundary. Empty classes receive a 1-byte placeholder to ensure unique object addresses.
The this Pointer
When multiple objects call the same member function, how does it know which object's data to manipulate?
class Counter {
int value;
public:
void setValue(int v) {
this->value = v; // Explicitly uses this pointer
}
};
this Pointer Characteristics:
- Type is
ClassName* const— cannot be reassigned - Available only within member functions
- Acts as an implicit parameter passing the object's address
- Automatically passed by the compiler via the
ecxregister
The Six Special Member Functions
These functions generate automatically if not explicitly defined.
Constructor
Executes during object initialization.
class Employee {
std::string name;
int id;
public:
Employee() {
name = "Unknown";
id = 0;
}
Employee(std::string n, int i) {
name = n;
id = i;
}
};
Employee e1; // Default constructor
Employee e2("Alice", 101); // Parameterized constructor
Constructor Properties:
- Name matches the class name exactly
- No return type
- Invoked automatically during object creation
- Supports overloading
- If any constructor is explicitly defined, the compiler stops generating a default one
Default Constructors: Either no-parameter or all-parameters-have-defaults. Only one default constructor should exist per class.
class Product {
std::string title;
double price;
public:
Product(std::string t = "", double p = 0.0) {
title = t;
price = p;
}
};
Product p1; // OK
Product p2("Widget"); // OK
Compiler-Generated Constructor Behavior: For built-in types (int, char, etc.), it does nothing. For user-defined types, it calls their constructors.
Destructor
Cleans up resources when objects go out of scope.
class DynamicArray {
int* data;
size_t capacity;
public:
DynamicArray(size_t cap = 10) {
data = new int[cap];
capacity = cap;
}
~DynamicArray() {
delete[] data;
data = nullptr;
}
};
Destructor Properties:
- Prefixed with
~ - Takes no parameters, returns nothing
- Cannot be overloaded — only one per class
- Called automatically when object lifetime ends
- Handles heap-allocated memory; stack memory auto-cleans
Copy Constructor
Creates a new object as a duplicate of an existing one.
class Point {
int x;
int y;
public:
Point(int a = 0, int b = 0) : x(a), y(b) {}
// Copy constructor - must use reference!
Point(const Point& p) {
x = p.x;
y = p.y;
}
};
Point p1(10, 20);
Point p2(p1); // Copy constructor called
Why Reference Parameter? Passing by value triggers another copy, causing infinite recursion.
Default Behavior: Shallow copy (byte-by-byte memory copy). Handles built-in types directly and user-defined types via their copy constructors.
Assignment Operator Overloading
class Vector2D {
int x, y;
public:
Vector2D(int a = 0, int b = 0) : x(a), y(b) {}
Vector2D& operator=(const Vector2D& other) {
if (this != &other) {
x = other.x;
y = other.y;
}
return *this;
}
};
Vector2D v1(1, 2);
Vector2D v2;
v2 = v1; // Assignment operator
Operator Overloading Syntax:
ReturnType operator+(Parameters);
Important Restrictions:
- Cannot invent new operators
- Must have at least one class-type operand
- Cannot alter built-in operator semantics
.,::,sizeof,?:, and.*cannot be overloaded
Complete Date Class Example:
class Date {
int year, month, day;
int daysInMonth() {
int mdays[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0))
return 29;
return mdays[month];
}
public:
Date(int y = 0, int m = 1, int d = 1) : year(y), month(m), day(d) {}
Date(const Date& other) : year(other.year), month(other.month), day(other.day) {}
Date& operator=(const Date& other) {
if (this != &other) {
year = other.year;
month = other.month;
day = other.day;
}
return *this;
}
bool operator<(const Date& other) {
if (year != other.year) return year < other.year;
if (month != other.month) return month < other.month;
return day < other.day;
}
Date& operator+=(int days) {
day += days;
while (day > daysInMonth()) {
day -= daysInMonth();
month++;
if (month > 12) {
month = 1;
year++;
}
}
return *this;
}
Date& operator++() {
*this += 1;
return *this;
}
Date operator++(int) {
Date temp(*this);
*this += 1;
return temp;
}
};
const Member Functions
const after the function signature restricts modifications to member variables.
class Circle {
double radius;
public:
Circle(double r) : radius(r) {}
double getRadius() const {
return radius;
}
double getArea() const {
return 3.14159 * radius * radius;
}
};
const Circle c(5.0);
double r = c.getRadius(); // OK - const function called
Access Rules:
constobjects can only callconstmember functionsnon-constobjects can call bothconstandnon-constfunctionsconstmember functions cannot invokenon-constmembersnon-constmembers can safely callconstmembers
When to Use const: Apply to any member functon that doesn't modify state.
Address-of Operators
Typically uses compiler-generated versions. Rarely needs custom implementation.
class Widget {
int data;
public:
Widget* operator&() {
return this;
}
const Widget* operator&() const {
return this;
}
};
Advanced Topics
Initializer Lists
Provide direct initialization for members.
class Coordinate {
int x, y, z;
public:
Coordinate(int a, int b, int c) : x(a), y(b), z(c) {}
};
Required Situations:
constmembers (require initialization at definition)- Reference members (must bind at definition)
- Members without default constructors
class Buffer {
const size_t maxSize;
int& indexRef;
public:
Buffer(size_t size, int& idx) : maxSize(size), indexRef(idx) {}
};
Order Dependency: Initialization occurs in declaration order, not list order. Align both for clarity.
Implicit Type Conversions
Single-parameter constructors enable implicit conversions:
class Distance {
double meters;
public:
Distance(double m = 0) : meters(m) {}
};
void printDistance(Distance d);
printDistance(100.5); // Implicit conversion from double
Prevent with explicit:
class Distance {
double meters;
public:
explicit Distance(double m = 0) : meters(m) {}
};
printDistance(100.5); // Error - conversion disabled
printDistance(Distance(100.5)); // OK - explicit
Static Members
Shared across all instances of a class.
class Counter {
static int instanceCount;
int id;
public:
Counter() {
id = ++instanceCount;
}
static int getCount() {
return instanceCount;
}
};
int Counter::instanceCount = 0;
Static Variables: Declared in class, defined outside. Exist in static storage, shared by all objects.
Static Functions: No this pointer. Call via ClassName::function() syntax.
Access Rules:
- Static functions cannot call non-static functions (no
this) - Non-static functions can call static functions
Friend Declarations
Grant external functions or classes access to private members.
class Teacher;
class Student {
friend class Teacher;
friend void displayRecord(const Student&);
private:
std::string name;
int score;
public:
Student(std::string n, int s) : name(n), score(s) {}
};
void displayRecord(const Student& s) {
std::cout << s.name << ": " << s.score << std::endl;
}
class Teacher {
public:
void viewStudent(const Student& s) {
std::cout << "Name: " << s.name << std::endl;
std::cout << "Score: " << s.score << std::endl;
}
};
Cautions: Friends break encapsulation. Use sparingly and intentionally.