Exercises — Chapter 16: Introduction to Object-Oriented Programming


Part A: Conceptual Review (Short Answer)

Exercise 16.1 — Explain, in your own words, the difference between a class and an object. Use an analogy that does not appear in this chapter.

Exercise 16.2 — List three specific problems with procedural code organization that OOP addresses. For each, explain how classes solve the problem.

Exercise 16.3 — Why does Object Pascal require you to call inherited Create at the beginning of a constructor and inherited Destroy at the end of a destructor? What could go wrong if you reversed the order or omitted these calls?

Exercise 16.4 — Explain reference semantics vs. value semantics using a concrete example. Why is it important to understand the difference when working with classes?

Exercise 16.5 — What is the purpose of the Self keyword? Give two situations where you would need to use it explicitly rather than relying on the compiler's implicit resolution.

Exercise 16.6 — A colleague argues: "Properties are unnecessary — you can just make fields public and access them directly." Write a paragraph explaining why this argument is wrong, using the concept of invariant protection.


Part B: Reading Code (Predict the Output)

Exercise 16.7 — What does this program output? Trace through carefully.

{$mode objfpc}{$H+}
uses SysUtils;

type
  TCounter = class
  private
    FValue: Integer;
  public
    constructor Create(AInitial: Integer);
    procedure Increment;
    procedure Display;
    property Value: Integer read FValue;
  end;

constructor TCounter.Create(AInitial: Integer);
begin
  inherited Create;
  FValue := AInitial;
end;

procedure TCounter.Increment;
begin
  FValue := FValue + 1;
end;

procedure TCounter.Display;
begin
  WriteLn('Counter: ', FValue);
end;

var
  A, B: TCounter;
begin
  A := TCounter.Create(10);
  B := A;
  B.Increment;
  B.Increment;
  A.Display;
  A.Free;
end.

Exercise 16.8 — What does this program output? Pay close attention to value semantics vs. reference semantics.

{$mode objfpc}{$H+}

type
  TPointRec = record
    X, Y: Integer;
  end;

  TPointObj = class
  public
    X, Y: Integer;
    constructor Create(AX, AY: Integer);
  end;

constructor TPointObj.Create(AX, AY: Integer);
begin
  inherited Create;
  X := AX;
  Y := AY;
end;

var
  R1, R2: TPointRec;
  O1, O2: TPointObj;
begin
  R1.X := 5; R1.Y := 10;
  R2 := R1;
  R2.X := 99;
  WriteLn('R1.X = ', R1.X);

  O1 := TPointObj.Create(5, 10);
  O2 := O1;
  O2.X := 99;
  WriteLn('O1.X = ', O1.X);
  O1.Free;
end.

Exercise 16.9 — Will this code compile? If not, identify the error(s) and explain why.

{$mode objfpc}{$H+}

type
  TSecret = class
  private
    FCode: Integer;
  public
    constructor Create(ACode: Integer);
  end;

constructor TSecret.Create(ACode: Integer);
begin
  inherited Create;
  FCode := ACode;
end;

var
  S: TSecret;
begin
  S := TSecret.Create(42);
  WriteLn('Code: ', S.FCode);
  S.Free;
end.

Exercise 16.10 — What happens when this program runs? Identify the bug.

{$mode objfpc}{$H+}

type
  TGreeter = class
  private
    FName: string;
  public
    constructor Create(const AName: string);
    procedure Greet;
  end;

constructor TGreeter.Create(const AName: string);
begin
  inherited Create;
  FName := AName;
end;

procedure TGreeter.Greet;
begin
  WriteLn('Hello, ', FName, '!');
end;

var
  G: TGreeter;
begin
  G.Greet;
  G.Free;
end.

Part C: Writing Code (Short Programs)

Exercise 16.11 — Write a TTemperature class with: - A private field FCelsius of type Double - A constructor Create(ACelsius: Double) - A read-only property Celsius - A read-only property Fahrenheit that computes the conversion (C * 9/5 + 32) - A read-only property Kelvin that computes the conversion (C + 273.15) - A Display procedure that prints all three scales

Write a short main block that creates a TTemperature object for boiling water (100 C), displays it, and frees it.

Exercise 16.12 — Write a TRectangle class with: - Private fields FWidth and FHeight (type Double) - A constructor that takes width and height, rejecting non-positive values with an exception - Properties Width and Height (read-write, with validation in setters) - Methods Area and Perimeter that return Double - A method IsSquare that returns Boolean - A Display procedure

Exercise 16.13 — Write a TStopwatch class that simulates a simple stopwatch: - Private fields for start time and elapsed time - Methods: Start, Stop, Reset, ElapsedSeconds (returns Double) - Use Now from SysUtils for timing

Hint: Store TDateTime values and compute the difference.

Exercise 16.14 — Write a TStack class that implements an integer stack using a dynamic array: - Private fields: FItems: array of Integer; FTop: Integer - Constructor Create (initializes empty stack) - Methods: Push(AValue: Integer), Pop: Integer, Peek: Integer, IsEmpty: Boolean - Property: Count: Integer (read-only) - Pop and Peek should raise exceptions if the stack is empty

Write a test program that pushes 5, 10, 15, then pops all values and prints them.

Exercise 16.15 — Write a TContact class and a TContactList class: - TContact has Name, Email, Phone (all strings, validated: none can be empty) - TContactList manages a dynamic array of TContact objects - TContactList has Add, Remove, FindByName (returns TContact or nil), DisplayAll - TContactList owns its contacts and frees them in its destructor

Exercise 16.16 — Write a TDice class that simulates a die: - Constructor takes the number of sides (default 6; must be at least 2) - Method Roll: Integer returns a random value from 1 to the number of sides - Property Sides: Integer (read-only) - Property LastRoll: Integer (read-only, returns the most recent roll result)

Write a test program that creates a 6-sided die and a 20-sided die, rolls each 10 times, and prints the results.


Part D: Analytical and Design Exercises

Exercise 16.17 — Consider this procedural code for a simple bank account:

type
  TAccountRecord = record
    Owner: string;
    Balance: Currency;
    AccountNumber: string;
  end;

procedure Deposit(var Acc: TAccountRecord; Amount: Currency);
procedure Withdraw(var Acc: TAccountRecord; Amount: Currency);
procedure PrintStatement(const Acc: TAccountRecord);
function GetBalance(const Acc: TAccountRecord): Currency;

Redesign this as a TBankAccount class. Include: - Appropriate visibility (private fields, public interface) - Validation (no negative deposits, no overdrafts) - A constructor and destructor - Properties where appropriate

Write only the class declaration (interface), not the implementation.

Exercise 16.18 — You are designing a TPlaylist class for a music application. A playlist has a name, a list of songs (each song has a title, artist, and duration in seconds), and a current position (which song is playing).

Design two classes: TSong and TPlaylist. For each, specify: - All fields (with appropriate visibility) - All methods (with signatures) - All properties - Constructor and destructor - Which class owns which objects

Write the class declarations and explain your design decisions in comments.

Exercise 16.19 — Analyze the following code and identify all the problems (there are at least five):

type
  TEmployee = class
    Name: string;
    Salary: Currency;
    Department: string;
    constructor Create;
    procedure GiveRaise(Amount: Currency);
  end;

constructor TEmployee.Create;
begin
  { nothing here }
end;

procedure TEmployee.GiveRaise(Amount: Currency);
begin
  Salary := Salary + Amount;
end;

var
  E1, E2: TEmployee;
begin
  E1 := TEmployee.Create;
  E1.Name := 'Alice';
  E1.Salary := 50000;
  E1.Department := 'Engineering';

  E2 := E1;
  E2.Name := 'Bob';
  E2.Salary := 60000;

  WriteLn(E1.Name, ': ', E1.Salary:0:2);
  WriteLn(E2.Name, ': ', E2.Salary:0:2);

  E1.Free;
  E2.Free;
end.

Exercise 16.20 — A student writes the following code and is confused when it does not work as expected:

procedure ResetExpense(Expense: TExpense);
begin
  Expense := TExpense.Create('None', 0.00, 'Uncategorized', Now);
end;

They call ResetExpense(MyExpense) expecting MyExpense to be replaced with a fresh object. Explain: 1. Why this does not work (what happens to the parameter inside the procedure?) 2. What memory problem this code creates 3. How to fix it properly

Exercise 16.21 — Compare and contrast the following two approaches to "resetting" an object. Which is better and why?

{ Approach A: Destroy and recreate }
Student.Free;
Student := TStudent.Create('', 0);

{ Approach B: Add a Reset method }
Student.Reset;  { Sets fields back to defaults internally }

Part E: Challenge Problems

Exercise 16.22 — Write a TMatrix class for 2D matrices of real numbers: - Constructor takes rows and columns, allocates internal storage - Methods: SetValue(Row, Col, Value), GetValue(Row, Col), Display - Method Add(Other: TMatrix): TMatrix — returns a new TMatrix that is the sum (raises exception if dimensions do not match) - Method Multiply(Scalar: Double): TMatrix — returns a new TMatrix scaled by the given value - Properties: Rows, Cols (read-only) - Proper destructor

Exercise 16.23 — Write a TStringList class (do not use the built-in one) that: - Stores strings in a dynamic array - Has methods: Add, Insert(Index, Value), Delete(Index), IndexOf(Value): Integer, Clear, Sort - Has properties: Count, Items[Index] (read-write, using a default property) - Grows the internal array efficiently (double capacity when full) - Frees properly

Write a test program that adds 20 strings, sorts them, deletes three, and displays the result.

Exercise 16.24 — Write a TFraction class that represents a fraction (numerator/denominator): - Private fields FNum and FDen (both Integer) - Constructor validates that denominator is not zero; stores the fraction in reduced form (use GCD) - Methods: Add(Other: TFraction): TFraction, Subtract, Multiply, Divide — each returns a new TFraction - Method ToReal: Double - Method ToString: string (returns e.g., '3/4') - Property Numerator and Denominator (read-only)

Write a test program that computes 1/2 + 1/3 and displays the result as a fraction and as a decimal.

Exercise 16.25 — Design and implement a TCircularBuffer class (a fixed-size FIFO queue): - Constructor takes the buffer capacity - Methods: Enqueue(AValue: Integer), Dequeue: Integer, Peek: Integer - Properties: Count, Capacity, IsEmpty, IsFull - When the buffer is full, Enqueue raises an exception (or optionally overwrites the oldest item — implement both behaviors as a constructor parameter) - Write a test program that demonstrates the circular behavior

Exercise 16.26 — Implement a TLinkedList class using objects for nodes:

type
  TNode = class
  public
    Value: Integer;
    Next: TNode;
    constructor Create(AValue: Integer);
  end;

  TLinkedList = class
  private
    FHead: TNode;
    FCount: Integer;
  public
    constructor Create;
    destructor Destroy; override;
    procedure Prepend(AValue: Integer);
    procedure Append(AValue: Integer);
    procedure DeleteFirst;
    function Contains(AValue: Integer): Boolean;
    procedure Display;
    property Count: Integer read FCount;
  end;

Implement all methods. The destructor must properly free all nodes.


Part M: PennyWise Milestone

Exercise 16.M1 — Implement the full TExpense class as described in Section 16.11. Test it by creating at least five expense objects, displaying them, and verifying that invalid values (negative amounts, empty categories) are properly rejected.

Exercise 16.M2 — Implement the full TExpenseList class. Test it by adding ten expenses, computing totals by category and overall, removing an expense by index, and displaying all expenses.

Exercise 16.M3 — Implement the full TBudget class. Write a complete test program that: 1. Creates a budget with a $500 limit 2. Adds expenses from multiple categories 3. Prints a summary showing expenses by category, total spent, budget limit, and remaining budget 4. Tests the over-budget warning

Exercise 16.M4 (Extension) — Add a TReport class to PennyWise: - It takes a TBudget object (it does not own it — it is a viewer, not an owner) - Methods: PrintSummary, PrintByCategory, PrintTopExpenses(N: Integer) - PrintTopExpenses shows the N largest expenses in descending order

This introduces the concept of an object that uses another object without owning it — an important design distinction.

Exercise 16.M5 (Extension) — Add an AddExpenseInteractive method to TBudget that prompts the user for description, amount, and category from the console, validates the input, creates the expense, and adds it. Handle invalid input gracefully (loop until valid input is provided).


For solutions and discussion of selected exercises, see Appendix E.