gl/ripetizioni

C++ classes and objects: the Year 4 OOP test

Classes, constructors, encapsulation and methods in C++: how to tackle the first object-oriented programming test in Year 4 at a technical computing school.

by Gaetano Livornese

Read the original in Italian

It is late September 2026, and in Year 4 (the fourth year of Italian high school) at a technical institute, in the Information Technology and Telecommunications track, the computing year has restarted from one precise point: object-oriented programming. After three years of writing programs as sequences of instructions acting on scattered variables, in Year 4 you have to change the way you think — and the change is not painless. The first test, which follows the typical format of computing in this track (a practical exam at the computer plus a written theory part), arrives five to eight weeks from now, so between the end of October and November.

If your stumbling block in Year 3 was loops and arrays — I talk about it in Nested loops and arrays in Year 3 computing — here the obstacle is different: it is no longer “getting the program to run”, it is organising data and operations inside an object instead of leaving them scattered around.

Why we move to objects

Up to Year 3, a program that managed, say, a bank account probably had a balance in one variable and some functions that modified it by taking it as a parameter. It works, but it has a problem that shows up as soon as the program grows: nothing stops a distant piece of code from changing the balance directly, perhaps taking it below zero with no check, because the variable is “out there” and anyone can reach it.

Object-oriented programming answers this: instead of keeping data and functions separate, it encapsulates them together in a single unit — the class — and decides explicitly what can be touched from outside and what cannot. This is not a syntactic whim: it is a different way of organising responsibility in the code, which is why the OOP test insists so much on where you put public and where you put private, and not only on whether the program “works”.

Class and object: the difference that has to be stated properly

A class is a blueprint, a description: it says which attributes (data) and which methods (functions) every object built from it will have. An object is a concrete instance of that class, with its own specific values. The class BankAccount describes the fact that every account has a balance and a holder; the object c1 is one specific account, with a balance of 500 and holder “Rossi”.

class BankAccount {
  private:
    double balance;
    std::string holder;

  public:
    BankAccount(std::string h, double b) {
      holder = h;
      balance = b;
    }

    void deposit(double amount) {
      balance += amount;
    }

    double getBalance() {
      return balance;
    }
};

BankAccount c1("Rossi", 500.0); creates an object: from here on c1 is a real account with its own data, distinct from any c2.

Attributes and methods, public and private

Attributes are the data of the class (balance, holder); methods are its functions (deposit, getBalance). The keyword private makes a member accessible only from inside the class itself; public makes it accessible from outside too.

Encapsulation is the practice of keeping attributes private and exposing only the methods needed to read or modify them in a controlled way. It is not a style rule: it is what stops main() from writing c1.balance = -1000; directly, bypassing every check. If balance is private, that line simply does not compile.

Getters and setters

A getter is a method that returns the value of a private attribute (getBalance() above); a setter is a method that modifies it, usually with a check:

void setBalance(double b) {
  if (b >= 0) {
    balance = b;
  }
}

The point of a setter is not “to be a getter in reverse”: it is the place where you put validation. A private attribute with a setter that checks is safer than a public attribute with no checks — and this is exactly the kind of theory question an OOP test asks: “why use a setter instead of making the attribute public?”.

The constructor, in all its forms

The constructor is a special method that has the same name as the class, has no return type (not even void), and is called automatically when an object is created. It comes in several forms:

Default constructor (no parameters):

BankAccount() {
  balance = 0.0;
  holder = "";
}

Constructor with parameters (the one above), which lets you initialise the object with specific values at the moment of creation.

Initialiser list, an alternative syntax for initialising attributes, often required for const attributes or for efficiency:

BankAccount(std::string h, double b) : holder(h), balance(b) {
  // empty body, or extra checks here
}

The initialiser list (after the colon, before the braces) initialises the attributes directly, instead of assigning them inside the body of the constructor. For a Year 4 test, knowing that it exists and recognising it in a code fragment is enough; the deeper reason (avoiding a double construction for certain types) usually comes later.

A class can also have a destructor, called automatically when the object is destroyed — useful above all when the class manages resources (dynamically allocated memory, open files):

~BankAccount() {
  // clean-up, if needed
}

It has the same name as the class preceded by ~, no parameters and no return type.

this

Inside a method, this is a pointer to the current object on which the method was called. It is mostly needed when a parameter has the same name as an attribute:

void setHolder(std::string holder) {
  this->holder = holder;
}

Without this->, holder = holder; would assign the parameter to itself, leaving the attribute of the class unchanged — a shadowing error that often produces no compilation error at all.

Arrays of objects

An array can hold objects, not just primitive types:

BankAccount accounts[3] = {
  BankAccount("Rossi", 500.0),
  BankAccount("Bianchi", 1200.0),
  BankAccount("Verdi", 300.0)
};

for (int i = 0; i < 3; i++) {
  std::cout << accounts[i].getBalance() << std::endl;
}

You reach each object in the array through its index, and its public members with the dot, exactly as for a single object: accounts[i].getBalance(). It is a case that combines a topic from last year and one from this year: loops and arrays on one side, classes on the other — which is why it often shows up in the practical exam.

A first look at inheritance

Inheritance lets you define a class starting from another one, inheriting its attributes and methods and adding some of its own or modifying them. For now it is enough for the idea to be clear at a conceptual level: a class SavingsAccount could inherit from BankAccount and add an interest rate. The syntax and the details (protected, method overriding, base-class constructors) belong to the next block of the programme — here all that matters is having understood why it would be needed: to avoid rewriting from scratch a class that resembles one that already exists.

A note on the language

C++ is one of the most widely used object-oriented languages in schools, but not the only one: some classes use a different language to introduce OOP. Syntax and some details change (memory management, for example), but the concepts — class, object, encapsulation, constructor, inheritance — are the same everywhere. If your course uses another language, this post is still useful for the reasoning; the syntax of the examples will need adapting.

Exercises in the format of the test

1. A BankAccount class with validation in the setter. Write a class with private attributes balance and holder, a constructor with parameters, a method deposit(double amount) that rejects negative amounts, and a setter for the balance that does not allow values below zero.

class BankAccount {
  private:
    double balance;
    std::string holder;

  public:
    BankAccount(std::string h, double b) : holder(h), balance(b) {}

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

    void setBalance(double b) {
      if (b >= 0) {
        balance = b;
      }
    }

    double getBalance() {
      return balance;
    }
};

2. A Rectangle class with area and perimeter.

class Rectangle {
  private:
    double base;
    double height;

  public:
    Rectangle(double b, double h) : base(b), height(h) {}

    double area() {
      return base * height;
    }

    double perimeter() {
      return 2 * (base + height);
    }
};

With base = 6.0 and height = 4.0: area , perimeter .

3. Find the error. What is wrong here?

class Point {
  private:
    int x, y;

  public:
    Point(int a, int b) {
      x = a;
      y = b;
    }
};

int main() {
  Point p(3, 4);
  std::cout << p.x << std::endl;
  return 0;
}

Solution: p.x tries to access a private attribute from outside the class — it does not compile. You need a getter, for example int getX() { return x; }, and then call p.getX(). A second, sneakier error, which often turns up in the same kind of exercise: a “constructor” written with a return type (void Point(int a, int b) { ... }) is not a constructor at all as far as the compiler is concerned — it is an ordinary method that is never called automatically.

4. Array of objects with a search for the maximum. Given a class Student with a private attribute grade and a getter getGrade(), write the code that, given an array of 5 Student objects, finds and prints the highest grade.

Student classroom[5] = { /* ... objects already created ... */ };
double maximum = classroom[0].getGrade();

for (int i = 1; i < 5; i++) {
  if (classroom[i].getGrade() > maximum) {
    maximum = classroom[i].getGrade();
  }
}

std::cout << maximum << std::endl;

The loop starts at i = 1 because classroom[0] is already the initial value for comparison — a small detail that a test is happy to check.

Typical mistakes that cost marks

  • Accessing a private member from main() or from outside the class. This is the most common error, and it is also the point where encapsulation has to be really understood, not just remembered as a rule.
  • A constructor without the right form (or with a return type by mistake): the compiler treats it as any ordinary method, and the object is created without proper initialisation.
  • Forgetting this-> when a parameter of a constructor or setter has the same name as an attribute, ending up assigning a variable to itself.
  • Passing an array of objects to a function expecting it to stay unchanged: as with arrays of primitive types, in C++ an array passed as a parameter is not copied by default.

How to tell whether you are ready

Try to write from scratch, without looking at examples, a class with at least one private attribute, a constructor with parameters, a getter and a setter with validation — and to explain out loud why that attribute is private and not public. If the explanation comes naturally, and you can read a code fragment with an array of objects and say exactly what each element contains, you are ready for the practical exam.

There is not yet a dedicated practice test on OOP and C++ in the high school practice tests category (questions in Italian). If you want to revise the basics of the language before getting here, the Year 3 post on nested loops and arrays is the direct prerequisite; if pointers await you next year, Pointers and memory in C, explained properly is written for the university exam, but the mental model of memory applies here too.

After classes, in the same year, parallel processes usually arrive: I talk about them in the post on threads and semaphores.

If the move from procedural code to objects still feels like a change of syntax more than a change of mindset, the computer science page explains how we tackle it in lessons, and the free introductory call is a good starting point before the test.

Keep reading

Want to put what you read into practice?

Let's talk for 30 minutes, free: we look together at where you are stuck and I tell you honestly how I can help.

Or message me on WhatsApp.