Unit 1: C++ Programming Basics and Functions

CSE202 — Object Oriented Programming 7 min read

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

Object-oriented programming (OOP) organizes software around objects that combine data with operations on that data. C++, developed by Bjarne Stroustrup beginning in 1979, extends procedural C with classes, inheritance, polymorphism, function overloading, references, and other abstraction mechanisms.

  • Object: An identifiable program entity with state and behavior; for example, a BankAccount object stores a balance and provides deposit().
  • Class: A user-defined type that specifies the data members and member functions shared by its objects.
  • Encapsulation: Bundling data and functions inside a class while controlling access through private, protected, and public.
  • Abstraction: Presenting essential operations while hiding implementation details; users may call withdraw() without knowing its internal logic.
  • Inheritance: Constructing a new class from an existing class to support reuse and specialization.
  • Polymorphism: Allowing one interface to represent different behaviors, such as overloaded functions or overridden virtual functions.
  • Message passing: Objects communicate through member-function calls such as account.deposit(500).
  • C++ convention: Program execution begins in main(), statements normally end with ;, and names are case-sensitive.

II. Object-Oriented Programming Paradigm — Concepts and Comparison

A. Introduction to concepts of OOP and OOP languages

OOP languages model a problem as interacting objects rather than only as a sequence of procedures.

  • OOP language support: C++, Java, C#, Python, and Smalltalk provide classes or comparable object mechanisms.
  • Class-to-object relationship: A class is a blueprint, while an object is an instance created from that blueprint.
  • Modularity: A class can isolate one responsibility, such as representing a Date, Student, or Invoice.
  • Reusability: Existing classes can be reused through composition—placing one object inside another—or inheritance.
  • Maintainability: A stable public interface permits internal code to change without forcing changes in client code.
  • C++ character: C++ is multi-paradigm; it supports procedural, object-oriented, generic, and functional styles rather than enforcing OOP exclusively.

B. Differences between procedural and object-oriented programming paradigms

Procedural programming emphasizes functions and stepwise algorithms, whereas OOP emphasizes objects, responsibilities, and controlled data access.

  1. Procedural programming:

    • Primary unit: The function or procedure, such as calculateTax(income).
    • Design direction: Commonly top-down; a large task is decomposed into smaller functions.
    • Data treatment: Data may be passed among many functions or stored globally.
    • Security: Unrestricted global data is vulnerable to unintended modification.
    • Reuse: Primarily achieved through reusable functions.
  2. Object-oriented programming:

    • Primary unit: The class and its objects, such as TaxAccount account.
    • Design direction: Often bottom-up; reusable classes are combined into larger systems.
    • Data treatment: State is enclosed within objects and accessed through defined operations.
    • Security: Private members enforce access restrictions.
    • Reuse: Achieved through classes, composition, inheritance, and polymorphism.

III. Classes and Objects — Defining Encapsulated Types

A. Creating classes

A class declaration defines a new type by listing its members and their access levels.

  • Syntax: The class body is enclosed in braces and followed by a semicolon.
  • Access control: Members are private by default; a public: section exposes the class interface.
  • Example: balance stores state, while deposit() modifies it.
CPP
class Account {
private:
    double balance = 0.0;

public:
    void deposit(double amount) {
        if (amount > 0) balance += amount;
    }

    double getBalance() const {
        return balance;
    }
};
  • Const member: The trailing const on getBalance() promises not to modify the object’s non-mutable data.

B. Class objects

An object is a concrete instance of a class, with storage allocated for its non-static data members.

  • Declaration: Account a; creates an automatic object named a.
  • Independent state: If Account a, b; is declared, a and b possess separate balance values.
  • Lifetime: A local object is destroyed when its scope ends; a dynamically allocated object lives until deleted.
  • Initialization: Constructors initialize objects when they are created, although compiler-generated constructors may be available.

C. Accessing class members

Class members are accessed according to both the object form and the member’s declared access level.

  • Object access: The dot operator is used with an object: a.deposit(500);.
  • Pointer access: The arrow operator is used with a pointer: ptr->deposit(500);.
  • Public restriction: Client code can directly name only accessible members, normally those under public:.
  • Private access: A private member such as balance is accessible inside class member functions and authorized friends, not through a.balance.
  • Scope resolution: A function defined outside its class uses ClassName::member, as in double Account::getBalance() const.

D. Differences between structures, unions, enumerations and classes

These user-defined types differ mainly in storage organization, permitted values, and default access.

  • Structure (struct): Groups members that occupy separate storage; members and base classes are public by default.
  • Class (class): Also groups data and functions, but members and base classes are private by default.
  • Practical equivalence: Apart from defaults, C++ structures and classes support constructors, methods, inheritance, and access specifiers.
  • Union (union): All non-static data members share the same memory region; normally only one member’s value is active at a time.
  • Enumeration (enum): Defines a set of named integral constants, such as enum class Day { Mon, Tue };.
  • Type safety: A scoped enumeration written enum class prevents implicit conversion to integers and requires Day::Mon.

E. Inline and non-inline member functions

Member functions may be expanded at a call site or invoked through ordinary function-call machinery, subject to compiler decisions.

  1. Inline member functions:

    • Definition: A function defined inside the class body is implicitly inline.
    • Purpose: Suitable for short operations such as int getX() const { return x; }.
    • Compilation rule: Inline definitions may appear identically in multiple translation units, commonly through headers.
  2. Non-inline member functions:

    • Definition: Usually declared inside the class and defined outside using ::.
    • Purpose: Better for lengthy implementations that should be separated from the interface.
    • Important limit: “Inline” permits, but does not require, call-site expansion; the compiler makes the optimization decision.

F. Static data members and static member functions

Static class members belong to the class as a whole rather than to each individual object.

  • Static data: One shared static data member exists for all objects; it can count created objects.
  • Definition: A traditional declaration static int count; inside a class requires int ClassName::count = 0; outside it.
  • Modern alternative: Since C++17, inline static int count = 0; may be initialized in the class.
  • Static function: Called as ClassName::getCount() and has no this pointer.
  • Access limit: A static member function can directly access only static members; it needs an object to access non-static state.

G. Friend function and friend class

Friendship grants selected non-members or classes access to another class’s private and protected members.

  • Friend function: Declared with friend inside the class but remains a non-member function.
  • Typical use: A symmetric binary operator may be declared friend Complex operator+(Complex, Complex);.
  • Friend class: friend class Inspector; permits all Inspector member functions to access the granting class’s restricted members.
  • Properties: Friendship is neither inherited, transitive, nor automatically reciprocal.
  • Design caution: Excessive friendship weakens encapsulation; it should represent close implementation cooperation.

IV. Stream-Based Input and Output — Formatted Data Transfer

A. Reading and writing data using cin and cout

C++ uses standard stream objects from <iostream> to exchange formatted data with standard input and output.

  • Input: std::cin >> age; extracts a value and converts it to the type of age.
  • Output: std::cout << age << '\n'; inserts the value into standard output.
  • Chaining: std::cin >> name >> age; works because each operator returns the stream.
  • Whitespace: Formatted extraction into std::string stops at whitespace; std::getline(std::cin, name) reads an entire line.
  • Error check: if (std::cin >> age) succeeds only when extraction leaves the stream usable.

B. Features of input/output streams

Streams provide a device-independent, type-aware abstraction over sequences of characters.

  • Type safety: Overloaded << and >> select conversions based on operand types.
  • Device independence: Similar operations work with console streams, file streams, and string streams.
  • Buffering: Output may be temporarily buffered for efficiency rather than written immediately.
  • State flags: good(), eof(), fail(), and bad() report stream conditions.
  • Recovery: After invalid input, clear() resets flags and ignore() can discard unwanted characters.
  • Standard streams: cin reads input, cout writes normal output, and cerr reports errors.

C. Manipulator functions

Manipulators alter stream formatting or trigger a stream operation.

  • Without arguments: std::endl writes a newline and flushes the stream; std::boolalpha prints true or false.
  • With arguments: <iomanip> provides std::setw(8), std::setprecision(2), and std::setfill('0').
  • Floating-point control: std::fixed << std::setprecision(2) prints 12.5 as 12.50.
  • Persistence: Some settings, such as fixed, persist; setw() generally applies only to the next formatted field.
  • Efficiency: Prefer '\n' when flushing is unnecessary because std::endl performs both newline insertion and flushing.

V. Functions — Interfaces, Binding, and Repeated Computation

A. Functions with default parameters or arguments

Default arguments supply omitted trailing arguments at the call site.

  • Declaration: double interest(double principal, double rate = 0.05);.
  • Use: interest(1000) uses 0.05, while interest(1000, 0.08) overrides it.
  • Ordering rule: After a parameter receives a default, later parameters generally must also have defaults.
  • Placement: Defaults are normally specified once in a visible declaration, often in a header.
  • Binding: The compiler inserts the default argument; it is not selected dynamically at runtime.

B. Inline functions

An inline function is declared with inline to support header-defined functions and potentially reduce call overhead.

  • Syntax: inline int square(int x) { return x * x; }.
  • Benefit: Call-site expansion can avoid function-call overhead for small, frequently called functions.
  • Cost: Repeated expansion may increase executable size.
  • Compiler authority: The compiler may ignore inline for recursion or complex code and may inline functions lacking the keyword.
  • Macro advantage: Unlike #define SQUARE(x), an inline function provides type checking and evaluates each argument once.

C. Function overloading and scope rules

Function overloading allows the same name to denote different functions when their parameter lists differ.

  • Valid overloads: print(int) and print(double) differ by parameter type.
  • Invalid distinction: Return type alone cannot distinguish int f() from double f().
  • Resolution: The compiler selects the best viable overload using argument number, type, and permitted conversions.
  • Ambiguity: Competing conversions of equal rank can produce a compile-time error.
  • Scope rule: A name declared in an inner block hides the same name in an outer scope.
  • Qualification: ::value names a global entity, while ClassName::function names a class member.

D. Reference variables

A reference is an alias bound to an existing object.

  • Declaration: int n = 10; int& ref = n; makes ref another name for n.
  • Modification: Assigning ref = 20; changes n to 20.
  • Initialization: A reference must normally be initialized when declared and cannot later be reseated.
  • Const reference: const int& r = n; prevents modification through r and can bind to temporary values.
  • Use: References support efficient parameter passing without pointer syntax.

E. Differences between call by value, call by address and call by reference

These mechanisms differ in what the function receives and whether it can modify the caller’s object.

  1. Call by value:

    • Parameter: void f(int x).
    • Effect: x is a copy; changing it does not change the caller’s variable.
    • Safety: No aliasing, but copying large objects may be expensive.
  2. Call by address:

    • Parameter: void f(int* x).
    • Effect: The caller passes &n, and the function modifies n through *x.
    • Condition: The pointer may be null, so validation may be necessary.
  3. Call by reference:

    • Parameter: void f(int& x).
    • Effect: Calling f(n) permits direct modification of n.
    • Read-only form: const Type& avoids copying while preventing modification.

F. Recursion using functions and member functions

Recursion occurs when a function calls itself directly or indirectly on a smaller instance of the problem.

  • Base case: A terminating condition stops further calls; factorial uses n <= 1.
  • Recursive case: The function reduces the problem, such as n * factorial(n - 1).
  • Example:
CPP
long long factorial(int n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
}
  • Member recursion: A member function may recursively call itself, for example return sum(node->next); in a linked-list class.
  • Runtime behavior: Each call creates a stack frame containing parameters, local variables, and a return address.
  • Limitations: Missing base cases cause unbounded recursion, while very deep recursion can exhaust stack memory.