C++-manual
Förutgående |  | Nästa

# Kapitel 17. Mera om klasser

Detta kapitel behandlar olika aspekter på hur klasser fungerar i C++. Många små ämnen har här kombinerats ihop till ett kapitel.

# Dynamiskt allokera objekt

Förutom normala primitiva datatyper, sammansatta datatyper och vektorer kan man även allokera objekt dynamiskt. Dynamiskt allokerade objekt skiljer sig väldigt lite från automatiska objekt, d.v.s. objekt som skapas automatiskt av kompilatorn i någon viss funktion eller metod. Fördelarna med att dynamiskt allokera objekt när de behövs är desamma som för andra typer av minne:

- mindre slöseri med minne.

- programmet är mera dynamiskt i olika situationer.

Allmänt ser en allokering av en klass ut på följande sätt:

    klasstyp * variabel1 = new klasstyp;
    klasstyp * variabel1 = new klasstyp (parametrar);

Vi antar att vi har en klass dom heter `Coordinate` definierad (se Kapitel 14). Vi kan allokera en dylik genom följande anrop:

    // använd ursprunsgvärden
    Coordinate * C1 = new Coordinate;

    // använd egna värden
    Coordinate * C2 = new Coordinate ( 1, 2 );

Vilken metod som används beror på lklassen ifråga och vilka olika konstruktorer denna har. Vår klass `Coordinate` har både en tom konstruktor och en konstruktor som tar två parametrar. Allokerade objekt måste även förstöras för att inte läcka minne. Det görs som normalt med operatorn `delete`. För att deallokera ovan allokerade objekt använder vi:

    delete C1;
    delete C2;

Man bör minnas att det endas sätt att referera till ett allokerat objekt är via en pekare som pekar till objektet. Finns det ingen pekare längre som pekar på objektet är det förlorat för all framtid och kan inte igen användas. Vi har då läckt minne.

För att allokera en vektor av objekt används samma syntax som för primitiva datatyper:

    Coordinate * Vector = new Coordinate [42];

Man adresserar sedan denna på samma sätt som normala vektorer, d.v.s. via `[]`. En vektor av objekt raderas även på samma sätt som en normal vektor:

    delete [] Vector;

Då objekt allokeras med `new` och förstörs med `delete` exekveras deras konstruktorer och destruktorer på normalt sätt.

## Exempel på dynamisk minneshantering

Ett exempel på dynamisk minneshanterin följer i programmet nedan. Det använder sig även av interna klasser (se avsnittet Klasser i klasser i Kapitel 14) och illustrerar hur dessa kan användas. Programmet implementerar ett mycket enkelt binärt träd som lagrar heltal. Man kan sätta in tal i trädet samt skriva ut trädet. Trädet använder sig av rekursiva hjälpfunktioner för att skriva ut trädet samt lägga in element.

    #include <iostream>
    #include <stdlib.h>
    #include <time.h>

    class Tree {
    public:
      // konstruktor
      Tree () : m_Root (0) {};
      ~Tree ();

      // lägg till ett tal
      void add (const int Value);

      // skriv ut trädet
      void print () { print ( m_Root ); }

    private:
      // intern klass för att hålla data om en nod
      class Node {
      public:
        Node (const int Value) : m_Value(Value), m_Left(0), m_Right(0) {};
        ~Node ();

        // värdet för noden
        int m_Value;

        // vänster och höger subträdt
        Node * m_Left;
        Node * m_Right;
      };

      // rekursiv metod för att lägga till ett värde och skriva ut
      void add (const int Value, Node * Root);
      void print (Node * Root);

      // roten för trädet
      Node * m_Root;
    };

    Tree::~Tree () {
      // har vi en rot?
      if ( m_Root )
        delete m_Root;
    }

    void Tree::add (const int Value) {
      // har vi en rot redan?
      if ( ! m_Root )
        // nej, så skapa en rot och gå iväg
        m_Root = new Node ( Value );

      else
        // lägg till värdet till roten
        add (Value, m_Root);
    }

    void Tree::add (const int Value, Node * Root) {
      // kolla värdet för denna nod
      if ( Value < Root->m_Value ) {
        // har vi en gren till vänster?
        if ( Root->m_Left ) {
          // jep, rekursera vidare
          add (Value, Root->m_Left);
        }
        else {
          // nej, så skapa en nod till vänster
          Root->m_Left = new Node (Value);
        }
      }

      // kolla värdet för denna nod
      else if ( Value > Root->m_Value ) {
        // har vi en gren till höger?
        if ( Root->m_Right ) {
          // jep, rekursera vidare
          add (Value, Root->m_Right);
        }
        else {
          // nej, så skapa en nod till vänster
          Root->m_Right = new Node (Value);
        }
      }
    }

    void Tree::print (Node * Root) {
      // har vi ett värde i noden?
      if ( ! Root )
        // nej, gå bort
        return;

      // skriv ut vänster subträd
      print ( Root->m_Left );
      cout << Root->m_Value << " ";

      // skriv ut höger subträd
      print ( Root->m_Right );
    }

    Tree::Node::~Node () {
      // har vi ett vänster subträd?
      if ( m_Left )
        delete m_Left;

      // har vi ett höger subträd?
      if ( m_Right )
        delete m_Right;
    }

    int main () {
      Tree T;

      // initiera slumptalen
      srand ( time (0) );

      // sätt in 20 slumptal i trädet
      for ( int Index = 0; Index < 20; Index++ ) {
        T.add ( (int)((float)rand () / (float)RAND_MAX * 100.0) );
      }

      // skriv ut trädet
      T.print ();
    }

Logiken i programmet är relativt enkel och kräver inga närmare beskrivningar. Notera dock hur destruktorn för den interna klassen `Node` implementerats. Eftersom den är intern för klassen `Tree` räcker det inte med enbart:

    Node::~Node () {
      ...
    }

utan man måste även berätta att det är en metod för en klass som tillhör `Tree` genom att även skriva `Tree::` enligt:

    Tree::Node::~Node () {
      ...
    }

Förutgående | Hem | Nästa
Multipel ärvning |  | Friends