Unit 1: Principles of OOP and C++ Essentials

CAP455 — Object Oriented Programming Using C++ 3 min read

I. Orientation — Foundations of Object-Oriented C++

Object-oriented programming (OOP) organizes software around objects that combine data with operations, while C++ (developed by Bjarne Stroustrup from 1979 onward) supports both procedural and object-oriented styles.

  • Object: A runtime entity with identity, state, and behavior; for example, a BankAccount object may store balance and provide deposit().
  • Class: A programmer-defined blueprint specifying the data members and member functions shared by objects.
  • Encapsulation: Bundles data and functions inside a class and controls access through private, protected, and public.
  • Abstraction: Exposes essential operations while hiding implementation details; users may call withdraw() without knowing its internal checks.
  • Inheritance: Creates a new class from an existing class, supporting reuse and hierarchical classification.
  • Polymorphism: Permits one interface to represent different behaviors, such as overloaded functions or overridden virtual functions.
  • C++ conventions:
    • A declaration normally ends with ;.
    • Program execution begins with main().
    • Names are case-sensitive: total and Total differ.
    • Automatic objects usually follow scope-based lifetime and deterministic destruction.

II. Programming Paradigms — Procedure-Centred and Object-Centred Design

A. Procedural vs object oriented programming paradigms

Procedural programming decomposes a problem into functions, whereas OOP decomposes it into interacting objects.

  1. Procedural paradigm:

    • Primary focus: Algorithms and step-by-step procedures operating on data.
    • Organization: Functions are generally separated from shared data; a C-style program might use deposit(Account*, double).
    • Design direction: Commonly top-down—divide the main task into smaller functions.
    • Advantages: Direct and efficient for small, computation-oriented programs.
    • Limitations: Shared data may be modified unpredictably, and large systems can become difficult to maintain.
  2. Object-oriented paradigm:

    • Primary focus: Objects that own state and provide controlled operations.
    • Organization: A class combines related members, such as balance and deposit().
    • Design direction: Often bottom-up—build reusable classes and combine their objects.
    • Advantages: Encapsulation, extensibility, reuse, and closer modelling of real entities.
    • Trade-off: Class hierarchies and object interactions can add unnecessary complexity to very small programs.
  • Concrete contrast: Instead of directly changing account.balance, an encapsulated design calls account.deposit(500.0), allowing validation within the class.

III. Stream-Based Interaction — Reading and Writing Data

A. Input/output streams

C++ treats input and output as streams, meaning ordered flows of characters or bytes between a program and an external source or destination.

  • Standard streams: <iostream> defines std::cin for input, std::cout for normal output, and std::cerr for errors.
  • Operators: >> extracts formatted data; << inserts data into an output stream.
  • Chaining: Each operator returns the stream, enabling cout << x << '\n';.
  • Line input: std::getline(std::cin, name) reads spaces as part of a string, unlike cin >> name.
  • Formatting: <iomanip> supplies manipulators such as std::fixed, std::setprecision(2), and std::setw(8).
  • Example:
CPP
#include <iomanip>
#include <iostream>

int main() {
    double price{};                         // price: entered monetary value
    std::cin >> price;
    std::cout << std::fixed
              << std::setprecision(2) << price << '\n';
}
  • Failure state: If nonnumeric input is supplied for price, cin.fail() becomes true and subsequent extractions stop until the state is cleared.

IV. Object Construction — Defining Types and Instances

A. Classes and objects

A class defines a type, while an object is a particular instance of that type occupying storage during execution.

  • Members: Data members represent state; member functions define behavior.
  • Access control: Class members are private by default, so a public interface protects internal representation.
  • Constructor: A specially named function initializes an object and has no return type.
  • Object access: Use . with an object and -> with a pointer to an object.
  • Example:
CPP
class Counter {
    int value;                 // state
public:
    Counter() : value(0) {}    // constructor
    void increment() { ++value; }
    int get() const { return value; }
};

Counter c;
c.increment();
  • Const member function: get() const promises not to modify the observable state of c.
  • Separation: A class definition allocates no storage for ordinary instance data until objects such as c are created.

V. Aggregate Storage — Alternative Data Layouts

A. Structure vs union

A structure stores all members simultaneously, while a union overlays its members in the same storage region.

  1. Structure (struct):

    • Storage: Every member has distinct storage, subject to alignment and padding.
    • Size: At least the combined member storage plus possible padding.
    • Validity: All initialized members can hold meaningful values at the same time.
    • Default access: Members and base classes are public.
  2. Union (union):

    • Storage: All non-static data members begin in shared storage.
    • Size: Sufficient for the largest member, plus alignment requirements.
    • Validity: Normally only the active member should be read.
    • Use: Memory-sensitive variant representation, usually accompanied by a tag indicating the active type.
CPP
struct Point { int x; int y; };
union Number { int i; float f; };
  • Caution: Assigning Number::f after Number::i changes the active member; reading the inactive member is generally invalid. Modern C++ often prefers std::variant for type-safe alternatives.

VI. Named Values and User-Defined Types — Controlled Symbolic Modelling

A. Enumerations and classes

Enumerations define a fixed set of named integral constants, whereas classes define complete abstractions containing both state and operations.

  • Unscoped enumeration: enum Color { red, green }; places enumerator names in the surrounding scope and permits implicit integral conversion.
  • Scoped enumeration: enum class Color { red, green }; requires names such as Color::red and prevents accidental implicit conversion to int.
  • Underlying type: It may be stated explicitly, as in enum class Status : unsigned char.
  • Class relationship: An enumeration can be nested inside a class to model a restricted property.
CPP
class TrafficLight {
public:
    enum class State { red, amber, green };
private:
    State state{State::red};
};
  • Distinction: State represents one choice from a finite set; TrafficLight can additionally enforce transitions and retain changing state.

VII. Class-Wide State — Members Shared by All Objects

A. Static data members and functions

A static class member belongs to the class as a whole rather than separately to each object.

  • Static data member: One shared variable exists regardless of the number of objects; a counter can track live instances.
  • Definition: A non-inline static data member traditionally requires one namespace-scope definition.
  • Static member function: Can be called as Tracker::count() without an object.
  • Restriction: It has no this pointer and therefore cannot directly access non-static members.
CPP
class Tracker {
    inline static int objects = 0;  // one class-wide variable
public:
    Tracker() { ++objects; }
    ~Tracker() { --objects; }
    static int count() { return objects; }
};
  • Concrete result: If two Tracker objects are alive, Tracker::count() returns 2.
  • Modern form: Since C++17, inline static permits initialization inside the class definition.

VIII. Functional Decomposition — Reusable Operations

A. User defined functions

A user-defined function is a named block written by the programmer to perform a specific task, optionally receiving parameters and returning a value.

  • Declaration: Introduces the name, return type, and parameter types: double area(double);.
  • Definition: Supplies the body implementing the operation.
  • Call: Transfers control to the function and evaluates supplied arguments.
  • Return type: Specifies the result type; void indicates no returned value.
  • Parameters and arguments: Parameters are local names in the definition; arguments are expressions supplied at the call site.
  • Overloading: Functions may share a name when parameter lists differ sufficiently for overload resolution.
CPP
double square(double x) {   // x: number to be squared
    return x * x;
}
  • Scope: The parameter x and local variables cease to exist when the call ends, unless storage-duration rules specify otherwise.

IX. Call Optimization — Small Function Expansion

A. Inline function

An inline function permits identical definitions in multiple translation units and suggests, but does not require, call-site expansion.

  • Syntax: inline int cube(int x) { return x * x * x; }.
  • Compiler decision: The compiler may still generate an ordinary call, and it may inline functions lacking the keyword.
  • Class members: A function defined inside a class definition is implicitly inline.
  • Benefit: Expansion can eliminate call overhead for small, frequently executed functions.
  • Costs: Repeated expansion may increase executable size; large or recursive functions are poor candidates.
  • Correctness role: The essential language effect is compliance with the one-definition rule when the same inline definition appears across translation units.

X. Controlled Privilege — Access Beyond the Public Interface

A. Friend function and friend class

Friendship grants a specified non-member function or another class access to private and protected members.

  • Friend function: Declared with friend inside the granting class but remains a non-member.
  • Friend class: Every member function of the named friend class receives access.
  • Use case: Symmetric operators such as operator<< often need access to an object’s private representation.
  • Properties: Friendship is explicitly granted, not inherited, not automatically mutual, and not transitive.
CPP
class Box {
    int width{10};
    friend int readWidth(const Box&);
};

int readWidth(const Box& b) {
    return b.width;
}
  • Design limitation: Excessive friendship weakens encapsulation because privileged code can bypass the public interface.

XI. Aliases and Parameter-Passing — Accessing Existing Objects

A. Reference variables

A reference variable is an alias bound to an existing object rather than an independent object.

  • Declaration: int& ref = value; makes ref another name for value.
  • Initialization: A reference must normally be initialized when declared and cannot later be reseated.
  • Modification: Assigning through a non-const reference changes the original object.
  • Const reference: const int& prevents modification through the alias and can bind to temporary values.
  • Efficiency: const LargeObject& avoids copying while retaining read-only access.
  • Distinction from pointers: References use ordinary expression syntax and are intended to denote valid objects; pointers explicitly store addresses and may be null.

B. Differentiate among call by value, call by address and call by reference

The three techniques differ in what is passed and whether the called function can directly alter the caller’s object.

  1. Call by value:

    • Parameter: Receives a copy, as in void f(int x).
    • Effect: Changes to x do not affect the caller.
    • Cost: Copying may be expensive for large objects.
  2. Call by address:

    • Parameter: Receives a pointer, as in void f(int* p).
    • Effect: *p = 5; modifies the pointed-to object.
    • Safety: The function must account for a possible null pointer.
  3. Call by reference:

    • Parameter: Receives an alias, as in void f(int& x).
    • Effect: x = 5; modifies the caller’s object directly.
    • Syntax: No explicit dereference is required.
CPP
void byValue(int x)   { ++x; }
void byAddress(int* x){ if (x) ++(*x); }
void byReference(int& x) { ++x; }
  • Selection: Use value for small independent inputs, const T& for large read-only inputs, references for required aliases, and pointers when absence or address arithmetic is meaningful.

XII. Self-Referential Computation — Solving Reduced Instances

A. Recursion

Recursion occurs when a function invokes itself directly or indirectly to solve a smaller instance of the same problem.

  • Base case: Stops further calls; without it, recursion continues until resources are exhausted.
  • Recursive case: Reduces the problem toward the base case.
  • Call stack: Each call stores its parameters, local variables, and return location in a separate stack frame.
  • Factorial relation: For a nonnegative integer (n), (n! = n(n-1)!), with (0! = 1).
CPP
unsigned long long factorial(unsigned int n) {
    if (n == 0) return 1;          // base case
    return n * factorial(n - 1);   // recursive case
}
  • Worked result: factorial(4) evaluates as 4 × 3 × 2 × 1 = 24.
  • Applications: Tree traversal, divide-and-conquer algorithms, directory processing, and naturally recursive definitions.
  • Limitations: Deep recursion risks stack overflow and may repeat work; iteration or memoization can be more efficient when subproblems recur.