Classes and objects

Introduction

A class is a new type that bundles data (data members, or fields) with the functions that work on that data (member functions, or methods). An object is one value of that type, with its own copy of every data member.

Classes are the heart of this course. A class lets you design a new type, such as an account or a student, that knows its own rules, so that the rest of the program cannot put it into a wrong state.

How it works

A first class

#include <iostream>
using namespace std;

class Rectangle {
public:
    double width;
    double height;

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

int main() {
    Rectangle r;
    r.width = 3;
    r.height = 4.5;
    cout << "area " << r.area() << ", perimeter " << r.perimeter() << endl;
    Rectangle s = r;
    s.width = 10;
    cout << r.width << " " << s.width << endl;
    return 0;
}
Open in compiler

r.area() calls area on the object r: inside area, the names width and height mean r's members. In the visualizer, a member function's frame has a hidden first parameter, this, a pointer to the object it was called on. width inside the function is short for this->width.

Rectangle s = r; makes a copy: s has its own width, so changing it leaves r alone.

Syntax

class Name {
private:
    int value;                       // hidden data
public:
    void set(int v) { value = v; }   // public operations
    int get() const { return value; }
};                                   // note the semicolon

Name n;
n.set(5);

Example

#include <iostream>
#include <string>
using namespace std;

class BankAccount {
private:
    string owner;
    double balance;

public:
    void open(string name) {
        owner = name;
        balance = 0;
    }
    bool deposit(double amount) {
        if (amount <= 0) return false;
        balance += amount;
        return true;
    }
    bool withdraw(double amount) {
        if (amount <= 0 || amount > balance) return false;
        balance -= amount;
        return true;
    }
    double getBalance() const {
        return balance;
    }
};

int main() {
    BankAccount acc;
    acc.open("Nadia");
    acc.deposit(500);
    if (!acc.withdraw(800)) cout << "not enough money" << endl;
    acc.withdraw(200);
    cout << "balance: " << acc.getBalance() << endl;
    return 0;
}
Open in compiler

Members under private: can be used only by the class's own member functions. Members under public: can be used by everyone. So no code outside the class can write acc.balance = 1000000;: the compiler refuses. The only way to change the balance is through deposit and withdraw, which check the amount first.

This is encapsulation, the first big idea of object-oriented programming: an object guards its own data, and the rest of the program uses it only through its public functions. If the rules change (a minimum balance, say), only the class changes.

getBalance() const promises not to change the object. class members are private unless marked; struct members are public unless marked. Otherwise the two are the same.

Line by line

  1. BankAccount keeps owner and balance under private:, so code outside the class cannot touch them.
  2. open sets both members, starting the balance at 0.
  3. deposit refuses an amount that is 0 or less, and otherwise adds it.
  4. withdraw refuses an amount that is not positive or is more than the balance.
  5. getBalance() const only reads the balance, and the const promises that.
  6. In main, the withdrawal of 800 fails because only 500 is in the account, so the message is printed. Then 200 is withdrawn, leaving 300.

Expected result

not enough money
balance: 300

In the real world

Banking, stock-control and booking systems are built from classes like this one, where each object enforces its own rules. When the rules change, for example with a daily withdrawal limit, only the class needs to change, and every part of the program that uses it gets the new rule.

Common mistakes

  • Forgetting the ; after the class's closing }.
  • Making every member public: the class then protects nothing.
  • Calling a member function without an object (area() in main): it needs r.area().

Best practices

  • Make data members private by default.
  • Give member functions names that describe actions, such as deposit.
  • Mark every member function that does not change the object as const.

Summary

A class bundles data with the functions that use it, and each object has its own copy of the data. Private members and public member functions give encapsulation: the object protects its own rules.

Quick check

1. Inside r.area(), what does this point to?
2. balance is private. Where can it be used?
3. Rectangle s = r; s.width = 10; What happens to r.width?

Key takeaways

  • Each object has its own copy of every data member.
  • Private members can be used only by the class's own member functions.
  • Public member functions are the only door into a well-designed object.

Practice problems for this lesson

My notes

Write down what you want to remember from this lesson. Notes are saved on this device as you type, and appear on your dashboard.