Classes en objecten

Wat is een klasse ?

Een klasse is een constructie om allerlei dingen te groeperen. Zo zou je potloden tot de klasse der schrijfwaren kunnen rekenen, mensen horen bij de klasse der zoogdieren en panne(n)koeken behoren tot de klasse der etenswaren.

Vaak kun je ook een andere groepering toepassen en meestal horen daar dan andere dingen bij. Zo kun je vers geslepen potloden ook onderbrengen in de klasse der scherpe voorwerpen, mensen in de klasse der zuurstof verbruikende organismen en panne(n)koeken in de klasse der bouwmaterialen, maar dan moeten ze wel een tijdje liggen.

Wat is een C++ class ?

Een C++ class is een taalconstructie waarmee je groepen van C++-dingen kunt maken. Dat doe je dan niet achteraf door bestaande dingen in een groep te stoppen, maar vantevoren door een klasse te beschrijven en vervolgens dingen te maken die de eigenschappen van die klasse hebben.

Een class doet niks !!!

Een class op zich doet niks. Pas wanneer je objecten gaat maken volgens de class-beschrijving wordt een class nuttig. Je kunt een class zien als een recept voor het maken van objecten. Een recept voor paella kun je niet eten, de paella zelf (het object) wel.

OOP (object oriented programming)

Waarom gebruiken we classes?

Voorbeeld van een C++ class

Laten we beginnen met een eenvoudige class voor het maken van een database met liedjes. Zet de volgende code in een file song.cpp

#include 
#include 

using namespace std;

class Song
{
public:
  void set_title(string new_title)
  {
    songtitle = new_title;
  }

  string get_title()
  {
    return songtitle;
  }

private:
  string songtitle;
};

Hier zie je een class Song die één variabele songtitle definieert en twee functies: set_title() die songtitle een nieuwe waarde kan geven en get_title() die de huidige waarde opvraagt. De eerste regels met #include en using namespace leg ik later uit.

Wat is een C++ object ?

Om de class Song te gebruiken maken we een main functie waarin we een Song-object maken.

int main()
{
  Song mySong;
  mySong.set_title("Running for a drink");
  cout << mySong.get_title() << endl;
  return 0;
}

mySong is nu een object van het type Song.

Daarna krijgt mySong een titel en om te bewijzen dat dat gelukt is wordt als laatste de titel weer aan mySong gevraagd en met cout zichtbaar gemaakt.

Meerdere objecten van hetzelfde type

Een object Song is misschien wel leuk, maar als we er maar één nodig hebben, waarom maken we dan eerst een klasse van songs ? Een klasse beschrijft de eigenschappen van een groep objecten, dus is vooral zinvol als we meerdere objecten willen maken die tot die klasse horen.

In het volgende voorbeeld zie je dat twee objecten gemaakt worden: song1 en song2, beide van het type Song:

int main()
{
  Song song1,song2;
  song1.set_title("Running for a drink");
  song2.set_title("Baby let's swing");
  cout << song1.get_title() << endl;
  cout << song2.get_title() << endl;
  return 0;
}

Constructoren

Bij wat we tot nu toe gezien hebben zijn objecten van het type Song aanvankelijk allemaal precies hetzelfde. Achteraf kan met de member-functie set_title() een titel gegeven worden waardoor de objecten zich van elkaar gaan onderscheiden.

Dat alle objecten op precies dezelfde manier gemaakt worden komt omdat er impliciet, voor ons verborgen, bij het maken van een object een constructie-functie wordt aangeroepen die het object in een default-toestand brengt. Zo'n functie noemen we een implicit-constructor.

Je kunt zelf ook constructoren maken, maar realiseer je dat er dan geen implicit constructor gemaakt wordt.

Je kunt zelf een constructor zonder argumenten maken als je wilt dat objecten wel altijd op dezelfde manier, maar op een andere manier dan met de impliciete constructor gemaakt worden. Een constructor zonder argumenten wordt een default constructor genoemd.

Je kunt ook constructoren maken die met parameters de nieuwe objecten bij hun creatie al in een bepaalde toestand brengen.

Een constructor heeft altijd dezelfde naam als de class. Bovendien heeft een constructor geen return type.

We breiden de class Song uit met een constructor zonder parameters en een constructor die een String als parameter verwacht.

class Song
{
public:
  Song() // constructor
  {
  }

  Song(string new_title) // constructor
  {
    songtitle = new_title;
  }

  void set_title(string new_title)
  {
    songtitle = new_title;
  }

  string get_title()
  {
    return songtitle;
  }

private:
  string songtitle;
};

Nu kunnen we objecten direct bij het maken al een titel geven.

int main()
{
  Song song1,song2,song3("Beautiful day");
  song1.set_title("Running for a drink");
  song2.set_title("Baby let's swing");
  cout << song1.get_title() << endl;
  cout << song2.get_title() << endl;
  cout << song3.get_title() << endl;
  return 0;
}

Waarom get/set functies ?

We kunnen de variabele songtitle van de Song-objecten gewoon public maken zodat we ze van buiten af kunnen veranderen en lezen. Waar hebben we dan die get- en set-functies voor nodig ?

Net als bij het aanroepen van member-functies kun je variabelen van de class lezen en schrijven. De algemene vorm is <class>.<variabele>

class SongYear
{
public:
  int release_year;

  int set_year(int newyear)
  {
    if(newyear < 1900 || newyear > 2014)
    {
      System.out.println("Year out of range");
      return -1;
    }
    release_year = newyear;
    return 0;
  }

  int get_year()
  {
    return release_year;
  }

};

In de class SongYear kun je het jaar bijhouden waarin een song uitkwam. Stel dat we niet willen dat het jaar in de toekomst ligt of voor 1900, dan kunnen we dat in de functie set_year beveiligen. Maar als je dan buiten die functie om rechtstreeks het jaar op een ongeldige waarde kunt zetten heb je daar weinig aan:

SongYear mySong;

  mySong.set_year(2002);
  cout << mySong.get_year() << endl;
  mySong.set_year(-10);
  cout << mySong.get_year() << endl;
  mySong.release_year = -10;
  cout << mySong.get_year() << endl;

Om dit op te lossen kun je release_year 'private' maken in plaats van 'public'.

private int release_year;

Op die manier kunnen alleen functies die bij de class horen de variabele lezen of veranderen en kun je er niet rechtstreeks bij. Het volgende is dan niet meer mogelijk en leidt tot een fout bij het compileren van je programma:

mySong.release_year = -10;

Voorgebakken classes gebruiken

Bij C++ krijg je al een heleboel classes die je zo kunt gebruiken. Je hoeft dus niet alles zelf te maken. Er zijn complete bibliotheken (libraries) met classes voor het lezen van het toetsenbord, lezen van de muis, maken van knopjes en nagaan of de gebruiker daarop drukt, maken van pop-up window en menu's, lezen en schrijven van files, datastructuren, communicatie en nog veel meer.

Om die classes te gebruiken moet je meestal een library importeren, bijvoorbeeld zo:

#include <iostream>

Zelf libraries maken

Dat kan ook :-P

class versus struct

Classes in C++ zijn afgeleid van structs in C. In essentie zijn ze bijna hetzelfde: beide kunnen functies en variabelen bevatten. Het belangrijkste verschil is:

Waarom moet er een semi-colon ';' ter afsluiting van een struct of class staan ? Volgens mij omdat dit de mogelijkheid geeft om direct na de struct een variabele van die struct te maken. En omdat class een speciaal geval van struct is moet het daarbij ook. Zie ook dit voorbeeld.