Objektorientert programmering: klasser og objekter forklart

Én personmal er koblet til to ulike personkort.

Objektorientert programmering samler data og funksjoner som hører sammen, i klasser. En klasse beskriver egenskaper og oppførsel, mens et objekt er en konkret instans av klassen. Her lærer du forskjellen med enkle eksempler i C++.

Tenk deg at du vil lagre navn, alder og bosted for flere personer. Med tre separate lister må du passe på at opplysningene på samme plass alltid gjelder samme person. Med en klasse kan du samle opplysningene om hver person i ett objekt.

Hva er forskjellen på en klasse og et objekt?

En klasse er en mal som beskriver hvilke data og funksjoner objektene skal ha. Et objekt er en konkret instans av denne malen. Klassen Person er beskrivelsen, mens otto og grete er to ulike objekter.

Du kan sammenligne klassen med en hustegning og objektene med hus bygget fra tegningen. Husene følger samme mal, men kan ha ulik farge. På samme måte kan to personer ha samme typer opplysninger, men ulike navn og aldre.

I C++ kan vi definere en klassetype med både struct og class. Vi begynner med struct, som gir direkte tilgang til medlemmene som standard.

#include <iostream>
#include <string>

struct Person {
    std::string navn;
    int alder;
    std::string by;
};

int main() {
    Person otto{"Otto", 23, "Oslo"};
    Person grete{"Grete", 41, "Bergen"};

    std::cout << otto.navn << '\n';   // Otto
    std::cout << grete.alder << '\n'; // 41
}

Person samler ulike datatyper: navn og by er tekststrenger av typen std::string, mens alder er et heltall av typen int. Hvert objekt har sine egne verdier. Punktumet i otto.navn gir tilgang til navnet som er lagret i akkurat dette objektet.

Krøllparentesene gir medlemmene startverdier i rekkefølgen navn, alder og by. Dette enkle eksempelet trenger ingen egendefinert konstruktør. I mer avanserte klasser kan en konstruktør sørge for at objektet får gyldige startverdier når det opprettes.

C++-typen Person samler navn, alder og by. Objektene otto og grete har hver sine verdier.
Otto og Grete er to objekter av samme klasse. Medlemsvariablene til hvert objekt lagrer personens egne verdier.

Medlemsvariabler og medlemsfunksjoner

Medlemsvariabler lagrer dataene som hører til objektet, som navn, alder og by. De omtales også som attributter. Medlemsfunksjoner, ofte kalt metoder, beskriver hva objektet kan gjøre.

La oss gi Person en medlemsfunksjon som skriver ut informasjon. Parameteren kort bestemmer om vi vil se bare navnet eller alle opplysningene. Her er et komplett eksempel:

#include <iostream>
#include <string>

struct Person {
    std::string navn;
    int alder;
    std::string by;

    void skriv_info(bool kort = false) const {
        if (kort) {
            std::cout << navn << '\n';
        } else {
            std::cout << navn << ", " << alder
                      << " år, fra " << by << '\n';
        }
    }
};

int main() {
    Person otto{"Otto", 23, "Oslo"};
    otto.skriv_info();     // Otto, 23 år, fra Oslo
    otto.skriv_info(true); // Otto
}

Når du kaller otto.skriv_info(), bruker funksjonen verdiene til otto. Standardverdien false gjør at du kan utelate argumentet når du vil skrive ut alle opplysningene. const etter parentesen betyr at medlemsfunksjonen ikke skal endre objektets vanlige medlemsvariabler.

Hvis du har brukt std::vector, kjenner du allerede denne måten å kalle funksjoner på. Uttrykket tall.push_back(4) bruker en medlemsfunksjon til å legge tallet 4 til i vektoren tall.

Innkapsling med public og private

Innkapsling betyr å samle data og funksjoner i en klasse og styre hvordan resten av programmet får bruke dem. Klassen kan skjule interne detaljer og tilby bestemte funksjoner for å lese og endre data.

Tenk deg en lommebok med en saldo. Vi vil at penger skal legges til gjennom en funksjon som kontrollerer beløpet. Resten av programmet skal ikke kunne sette saldoen direkte til hva som helst. I dette forenklede eksempelet bruker vi hele kroner.

#include <iostream>

class Lommebok {
private:
    int saldo = 0;

public:
    void legg_til(int belop) {
        if (belop > 0) {
            saldo += belop;
        }
    }

    int hent_saldo() const {
        return saldo;
    }
};

int main() {
    Lommebok lb;
    lb.legg_til(250);
    lb.legg_til(-100); // Ignoreres: beløpet er ikke positivt
    std::cout << lb.hent_saldo() << '\n'; // 250
}

saldo er private. Derfor kan du ikke skrive lb.saldo = -100; i main. Det gir en kompileringsfeil. Funksjonene under public er tilgjengelige utenfra, og legg_til slipper bare gjennom positive beløp.

Poenget er å samle kontrollen på ett sted. Hvis reglene endres senere, kan du endre funksjonen uten å lete etter direkte endringer av saldoen rundt i programmet.

Hva er forskjellen på struct og class i C++?

Både struct og class kan ha medlemsvariabler, medlemsfunksjoner og konstruktører. Den viktigste forskjellen i eksemplene våre er standardtilgangen: Medlemmer i en struct er public som standard, mens medlemmer i en class er private. Standardtilgangen ved arv følger samme skille.

Du kan bruke både public: og private: i begge variantene. Det er vanlig å bruke struct for enkle samlinger av data og class når tilgangskontroll og regler for dataene står sentralt.

Hvorfor bruke objektorientert programmering?

Struktur: Data som hører sammen, holdes sammen. Du slipper å holde separate lister med navn, alder og by synkronisert.

Gjenbruk: Du kan opprette mange objekter fra samme klasse uten å skrive beskrivelsen på nytt.

Kontroll: Medlemsfunksjoner kan kontrollere verdier før data endres. Du kan også endre den interne løsningen så lenge funksjonene fortsatt oppfører seg som avtalt.

Modellering: Mange problemer kan beskrives som ting med egenskaper og oppførsel, for eksempel en student, en konto eller en spillfigur.

De fire grunnprinsippene i objektorientert programmering

Fire begreper brukes ofte for å beskrive objektorientert programmering. Eksemplene over viser særlig innkapsling og abstraksjon. Arv og polymorfisme er naturlige temaer å gå videre med.

Innkapsling: Klassen samler data og funksjoner og styrer tilgangen til dataene. I Lommebok er saldoen privat.

Abstraksjon: Du trenger å vite hva en funksjon gjør, uten å kjenne alle detaljene inni den. Du kaller legg_til(250) for å øke saldoen.

Arv: En klasse kan bygge videre på en annen. En Student kan for eksempel arve fra Person og legge til studienummer.

Polymorfisme: Ulike objekttyper kan brukes gjennom et felles grensesnitt og likevel oppføre seg forskjellig. I C++ kan en virtuell medlemsfunksjon i en basisklasse overstyres i avledede klasser. Et kall gjennom en peker eller referanse til basisklassen kan da bruke funksjonen til den faktiske objekttypen.

Kort oppsummert

En klasse beskriver data og funksjoner, mens et objekt er en konkret instans av klassen. Medlemsvariabler lagrer objektets verdier, og medlemsfunksjoner bruker eller endrer dem. I C++ styrer public og private tilgangen. Prøv selv å opprette to Person-objekter og skrive ut opplysningene deres.

Lær mer om C++

Vil du jobbe videre med faget? Se kurset Prosedyre- og objektorientert programmering ved NTNU, eller finn faget som passer til ditt studiested.

Går du TDT4100 i stedet? Det faget bruker Java. Se Objektorientert programmering ved NTNU for klasser og objekter i Java.

Vil du øve på algoritmer, kan du lese artikkelen vår om quicksort.