Cpp luokat ja oliot

Mureakuha

Loikkaa: valikkoon, hakuun

Sisällysluettelo

Konstruktori- ja destruktorifunktiot

On tavallista, että ohjelmassa tarvitaan joitain alustuksia, esimerkiksi edellisessä luvussa olleessa pino-esimerkissä "pino-osoitin" piti asettaa nollaksi. Olioiden yhteydessä alustukset ovat lähes välttämättömiä. C++:ssa on tuki alustuksille, siten että jokaisen olion luonnin (= esitellään muuttuja, joka on tietyn luokan tyyppinen) jälkeen ei ensimmäiseksi tarvitse kutsua alustusrutiinia vaan alustusrutiinia kutsutaan automaattisesti olion luonnin jälkeen. Tällainen alustusrutiini on nimeltään konstruktori, ja sen sijaitsee luokan muiden funktioiden yhteydessä. Konstruktorilla on aina tietty nimi = luokan nimi. Konstruktori ei voi palauttaa mitään.

Esimerkki:

// pino2
#include <iostream>
 
#define SIZE  10
 
class pino {
  char  puskuri[ SIZE ];
  int  next_free;
public:
  pino();  // konstruktori
  void push( char ch );
  char pop();
};
 
pino::pino()
{
  next_free = 0;
}
 
void pino::push( char  ch )
{
  if (next_free == SIZE)
    cout << "\nPino on täynnä";
  else {
    puskuri[ next_free ] = ch;
    next_free++;
  }
}
 
char pino::pop()
{
  if (next_free==0){
    cout << "\nPino on tyhjä";
    return 0;
  }
  next_free--;
  return puskuri[ next_free ];
}
 
main()
{
  pino  s1, s2;
 
  s1.push('a');
  s2.push('1');
  ...
 
 
  return 0;
}

Kyseessä on sama pino-esimerkki kuin aiemminkin, paitsi että init-rutiini on korvattu konstruktorilla pino. Nyt pino-konstruktoria kutsutaan olion luonnin yhteydessä eli pääohjelman kohdassa, jossa määritellään muuttujat s1 ja s2 pino tyyppisiksi (konstruktoria kutsutaan molemmille erikseen). Kokeile edellistä siten, että laitat testitulostukset pääohjelmaan muuttujien esittelyn jälkeen sekä konstruktorifunktioon. Huomannet, kuinka konstruktoria kutsutaan automaattisesti.

Globaalien muuttujien yhteydessä konstruktoria kutsutaan yhden kerran, ohjelman käynnistyksen yhteydessä. Paikallisille muuttujille konstruktoria kutsutaan aina, kun tullaan kohtaan, jossa on muuttujamäärittely.

Vastaavasti C++:aan liittyy myös destruktori-funktio, jota kutsutaan aina, kun muuttuja tuhotaan. Muuttuja tuhotaan automaattisesti, kun ohjelman kontrolli siirtyy takaisinpäin muuttujan voimassaoloalueelta. Siis destruktoria kutsutaan esimerkiksi funktiossa määritetyille paikallisille muuttujille, kun funktiosta palataan takaisin kutsukohtaan. Destruktorillakin on vakionimi = tilde-merkki + luokan nimi ( ~luokka ).

Tyypillisesti konstruktoria käytetään alustuksiin ja destruktoria lopetustoimiin kuten varatun muistin vapautukseen. Funktiot voivat sisältää mitä tahansa koodia, mutta hyvän ohjelmointitavan mukaisesti ne sisältävät vain aloitus- ja lopetustoimia.

Esimerkki: Kokeile ja ymmärrä seuraava ohjelma. Ohjelmassa luodaan aluksi pino 2-alkioiseksi. Mikäli pinoon viedään uusi alkio eikä pinoon mahdu enempää, pinon kokoa kasvatetaan yhdellä alkiolla. Lopputuloksena pinon koko on sama kuin suurin määrä merkkejä, mitä pinossa on koskaan ollut. Tarkastele testituloksia ja kiinnitä huomiota siihen, missä vaiheessa käydään konstruktorissa ja missä vaiheessa destruktorissa.


// pino3, dynaaminen pino
#include <iostream>
#include <malloc.h>
 
#define SIZE  2
 
class pino {
  char  *puskuri ;
  int  next_free;
  int  koko;
public:
  pino();    // konstruktori
  ~pino();  // destruktori
  void push( char ch );
  char pop();
};
 
pino::pino()
{
  cout << "Konstruktorissa\n";
  next_free = 0;
  koko = SIZE;
  puskuri = (char *)malloc( koko * sizeof( char ) );
}
 
pino::~pino()
{
  cout << "Destruktorissa\n";
  free( puskuri );
  koko = 0;
}
 
void pino::push( char  ch )
{
 
  if (next_free == koko) {
    cout << "Pino on täynnä\n";
    char  *temp = puskuri;
    koko = koko + 1;
    puskuri = (char *)malloc( koko * sizeof( char ) );
    for ( int i = 0; i < koko-1; i++ )
      puskuri[ i ] = temp[ i ];
    free( temp );
  }
  puskuri[ next_free ] = ch;
  next_free++;
}
 
char pino::pop()
{
  if (next_free==0){
    cout << "Pino on tyhjä\n";
    return 0;
  }
  next_free--;
  return puskuri[ next_free ];
}
 
main()
{
  void  hoidaPino( );
 
  cout << "Pääohjelman alku\n";
  hoidaPino();
  cout << "Pääohjelman loppu\n";
  return 0;
}
 
void hoidaPino( )
{
 
  cout << "hoidaPino-alku\n";
  pino  s1;
  int   i;
  cout << "hoidaPino-muuttujamäärittelyiden jälkeen\n";
 
  s1.push('a');
  s1.push('b');
  s1.push('c');
  for ( i = 0; i < 3; i++ )
    cout << s1.pop( ) << " ";
  cout << "\n";
  s1.push('d');
  s1.push('e');
  s1.push('f');
  s1.push('g');
  for ( i = 0; i < 4; i++ )
    cout << s1.pop( ) << " ";
  cout << "\n";
  s1.push('h');
  s1.push('i');
  s1.push('j');
  s1.push('k');
  s1.push('l');
  for ( i = 0; i < 5; i++ )
    cout << s1.pop( ) << " ";
  cout << "\n";
  cout << "hoidaPino-loppu\n";
}

Merkkijono esimerkki.

// merkkijonoluokka, joka ylläpitää merkkijonoa ja sen pituutta
 
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
 
#define SIZE  255
 
class mjono {
  char  *puskuri ;
  int  len;
public:
  mjono();  // konstruktori
  ~mjono();  // destruktori
  void set( char *ptr );
  void show();
};
 
mjono::mjono()
{
  puskuri = (char *)malloc( SIZE );
  printf("Varataan puskuri: %p\n", puskuri );
  if (!puskuri) {
    cout << "Varausvirhe\n";
    exit(1);
  }
  *puskuri = '\0';
  len = 0;
}
 
mjono::~mjono()
{
  printf("Vapautetaan puskuri: %p\n", puskuri );
  free( puskuri );
}
 
void mjono::set( char  *ptr )
{
  if (strlen(ptr) >= SIZE) {
    cout << "Liian pitkä merkkijono\n";
    return;
  }
  strcpy( puskuri, ptr );
  len = strlen( ptr );
}
 
void mjono::show()
{
  cout << len << ":" << puskuri << "\n" ;
}
 
main()
{
  cout << "main-alku\n";
  void mjonoTest();
 
  mjonoTest();
  cout << "main-loppu\n";
  return 0;
}
 
void mjonoTest()
{
  mjono  s1, s2;
 
  s1.set( "Testi" );
  s2.set( "C++ on kivaa");
  s1.show();
  s2.show();
}

Olemassa olo aika esimerkki. Ohjelma kertoo ajan, kuinka kauan olio on ollut olemassa. Ajastin asetetaan konstruktorissa ja tulostetaan destruktorissa.

#include <iostream>
#include <time.h>
#include <conio.h>
 
class ajastin {
  clock_t  alku;
public:
  ajastin();
  ~ajastin();
};
 
ajastin::ajastin()
{
  alku = clock();
}
 
ajastin::~ajastin()
{
  clock_t  loppu;
 
  loppu = clock();
  cout << "\nAikaa kului " << (loppu-alku)/CLK_TCK << " s\n";
}
 
main()
{
  ajastin  x;
 
  // viive
  cout << "Paina jotain näppäintä";
  getch();
  return 0;
}

Konstruktorin argumentit

Konstruktoriin voidaan välittää argumentteja. Tämä tapahtuu siten, että konstruktorin esittelyyn ja määrittelyyn lisätään argumentit normaalin funktion tapaan. Oliomäärittelyssä muuttujaa käsitellään kuten funktiota, annetaan argumentille arvo:

luokka muuttuja( arg1, arg2, ... );

Esimerkki:

#include <iostream>
 
class  oma {
  int  a;
public:
  oma( int  x );  // konstruktori, jossa argumentti
  void show();
};
 
oma::oma( int x )
{
  a = x;
}
 
oma::show()
{
  cout << a << "\n";
}
 
main()
{
  oma  olio( 4 );
 
  olio.show();
  
  return 0;
}

Itseasiassa muuttujamäärittely oma olio( 4 ); on lyhennetty merkintätapa pidemmästä merkinnästä: oma olio = oma( 4 ); Useimmat C++ ohjelmoijat käyttävät lyhempää merkintätapaa.

Konstruktorille voidaan välittää tarvittaessa myös useita argumentteja. Destruktorille ei voi välittää argumentteja, koska ei ole edes mekanismia, miten kutsuttaisiin destruktoria.

Esimerkki: pino, jolle annetaan nimi.

// pino4, nimetty dynaaminen pino
#include <iostream>
#include <malloc.h>
 
#define SIZE  2
 
class pino {
  char  *puskuri ;
  int  next_free;
  int  koko;
  char  nimi;
public:
  pino(char n);  // konstruktori
  ~pino();  // destruktori
  void push( char ch );
  char pop();
};
 
pino::pino(char n)
{
  cout << "Pinon " << n << " Konstruktorissa\n";
  next_free = 0;
  koko = SIZE;
  puskuri = (char *)malloc( koko * sizeof( char ) );
  nimi = n;
}
 
pino::~pino()
{
  cout << "Pinon " << nimi << " Destruktorissa\n";
  free( puskuri );
  koko = 0;
}
 
void pino::push( char  ch )
{
 
  if (next_free == koko) {
    cout << "Pino " << nimi << " on täynnä\n";
    char  *temp = puskuri;
    koko = koko + 1;
    puskuri = (char *)malloc( koko * sizeof( char ) );
    for ( int i = 0; i < koko-1; i++ )
      puskuri[ i ] = temp[ i ];
    free( temp );
  }
  puskuri[ next_free ] = ch;
  next_free++;
}
 
char pino::pop()
{
  if (next_free==0){
    cout << "Pino " << nimi << " on tyhjä\n";
    return 0;
  }
  next_free--;
  return puskuri[ next_free ];
}
 
main()
{
  pino  s1( 'A');
  int   i;
 
  s1.push('a');
  s1.push('b');
  s1.push('c');
  for ( i = 0; i < 3; i++ )
    cout << s1.pop( ) << " ";
  cout << "\n";
  s1.push('d');
  s1.push('e');
  s1.push('f');
  s1.push('g');
  for ( i = 0; i < 4; i++ )
    cout << s1.pop( ) << " ";
  cout << "\n";
  s1.push('h');
  s1.push('i');
  s1.push('j');
  s1.push('k');
  s1.push('l');
  for ( i = 0; i < 5; i++ )
    cout << s1.pop( ) << " ";
  cout << "\n";
}

Esimerkki: merkkijono, joka voidaan alustaa määrittelyn yhteydessä:

// merkkijonoluokka, joka ylläpitää merkkijonoa ja sen pituutta
// lisäksi merkkijonon esittelyssä, merkkijonolle on annettava alkuarvo
 
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
 
class mjono {
  char  *puskuri ;
  int  len;
public:
  mjono(char *ptr);  // konstruktori
  ~mjono();  // destruktori
  void set( char *ptr );
  void show();
};
 
mjono::mjono(char *ptr)
{
  len = strlen( ptr );
  puskuri = (char *)malloc( (len+1)*sizeof(char) );
  printf("Varataan puskuri, %d: %p\n", len+1, puskuri );
  if (!puskuri) {
    cout << "Varausvirhe\n";
    exit(1);
  }
  strcpy( puskuri, ptr );
}
 
mjono::~mjono()
{
  printf("Vapautetaan puskuri, %d: %p\n", len+1, puskuri );
  free( puskuri );
}
 
void mjono::set( char  *ptr )
{
  if (strlen(ptr) != len) {
    printf("Vapautetaan puskuri, %d: %p\n", len+1, puskuri);
    free( puskuri );
    len = strlen( ptr );
    puskuri = (char *)malloc( (len+1)*sizeof(char) );
    printf("Varataan puskuri, %d: %p\n", len+1, puskuri);
    if (!puskuri) {
      cout << "Varausvirhe\n";
      exit(1);
    }
  }
  strcpy( puskuri, ptr );
}
 
void mjono::show()
{
  cout << len << ":" << puskuri << "\n" ;
}
 
main()
{
  mjono  s1("Koe1"), s2("");
 
  s1.show();
  s2.show();
  s1.set( "Testi" );
  s2.set( "C++ on kivaa");
  s1.show();
  s2.show();
 
  char  luettu[80];
  cout << "Anna merkkijono: ";
  cin >> luettu;
  mjono  s3( luettu );  // luodaan s3 vasta nyt tarvittaessa
  s3.show();
}

Perintä, alkeet

Perinnän avulla luokka voi periä toisen luokan ominaisuuksia. Perinnän avulla voidaan toteuttaa hierarkisia järjestelmiä.

Peritty luokka on nimeltään isäluokka, kantaluokka (base class). Perivä luokka on nimeltään johdettu luokka, lapsiluokka ( derived class )

Yleisesti perintäprosessi alkaa isäluokan määrittelyllä. Isäluokka määrittelee kaikki ne ominaisuudet, jotka ovat yhteisiä kaikille tämän isän lapsille (public-osa isän määrittelyssä) sekä isän omat henkilökohtaiset ominaisuudet (private-osa isän määrittelyssä).

Aloitetaan yksinkertaisella esimerkillä:

class isa {
  int  i;  // tätä voidaan käsitellä vain...
public:
  void   set_i( int n );  // ...näillä funktioilla
  int     get_i( );
};
 
class johdettu : public  isa {
      // johdettu-luokan olioita voidaan käsitellä myös 
  int  j;  // isan public-funktioiden avulla. j:tä voidaan 
      // käsitellä vain johdettu public-funktioilla
public:
  void   set_j( int n );
  int  kerro( );
};
 
void isa::set_i( int n )
{
  i = n;
}
int isa::get_i( )
{
  return i;
}
 
void johdettu::set_j( int n )
{
  j = n;
}
int johdettu::kerro( )
{
  return ( j * get_i() ); // suoraan i:hin ei voida viitata
}
 
main(  )
{
  johdettu  olio;   // oliolle on käytettävissä metodit:
        // set_i, get_i, set_j ja kerro
  olio.set_i( 5 );  // johdettu luokan oma funktio
  olio.set_j( 3 );  // isa luokan funktio
  cout << "Tulos = " << olio.kerro( );
  return 0;
}

Isän määrittelyssä rivillä:

class johdettu : public isa {

sana public tarkoittaa, että johdettu luokka perii isa-luokan siten, että kaikki isa-luokan public-ominaisuudet ovat public-ominaisuuksia myös johdettu-luokassa sekä, että isa-luokan private-ominaisuudet eivät ole käytettävissä suoraan johdettu-luokassa.

Esimerkki.

// Perintä esimerkki, isäluokkana on tasapaksu kappale, jolla
// on korkeus ja pohjan pinta-ala sekä nimi.
// Lapsina ovat kappaleet, joiden pohjat ovat kolmion, suorakaiteen ja
// ympyrän muotoiset
 
#include <iostream>
#include <string.h>
 
class  kappale {
  double  h;      // korkeus
  double  A;      // pohjan pinta-ala
  char  nimi[20];    // kappaleen nimi
public:
  void tyyppi(char *ptr);  // asetetaan kappaleelle nimi
  void korkeus(double kork); // asetetaan korkeus
  void ala(double a);  // asetetaan pohjan ala
  void tilavuus();  // tulostetaan kappaleen tilavuus
};
 
class katto : public kappale {
public:
  katto( double kanta, double kork, double syvyys );
  void muuta( double kanta, double kork, double syvyys );
};
 
class laatikko : public kappale {
public:
  laatikko( double kanta, double kork, double syvyys );
  void muuta( double kanta, double kork, double syvyys );
};
 
class sylinteri : public kappale {
public:
  sylinteri( double sade, double syvyys );
  void muuta( double sade, double syvyys );
};
 
void kappale::tyyppi(char  *ptr)
{
  strcpy( nimi, ptr );
}
 
void kappale::korkeus( double  kork )
{
  h = kork;
}
 
void kappale::ala( double a )
{
  A = a;
}
 
void kappale::tilavuus()
{
  cout << "Kappaleen " << nimi << " tilavuus on " << A*h << "\n";
}
 
void katto::muuta( double kanta, double kork, double syvyys )
{
  tyyppi("Katto");
  ala( kanta*kork/2 );
  korkeus( syvyys );
}
katto::katto( double kanta, double kork, double syvyys )
{
  muuta( kanta, kork, syvyys );
}
 
void laatikko::muuta( double kanta, double kork, double syvyys )
{
  korkeus( syvyys );
  ala( kanta*kork );
  tyyppi("Laatikko");
}
laatikko::laatikko( double kanta, double kork, double syvyys )
{
  muuta( kanta, kork, syvyys );
}
 
void sylinteri::muuta( double sade, double syvyys )
{
  korkeus( syvyys );
  ala( 3.1416*sade*sade );
  tyyppi("Sylinteri");
}
sylinteri::sylinteri( double sade, double syvyys )
{
  muuta( sade, syvyys );
}
 
main()
{
  katto    x(1,2,3);
  laatikko  y(1,2,3);
  sylinteri  z(1,3);
 
 
  x.tilavuus();
  y.tilavuus();
  z.tilavuus();
 
  x.muuta(2,3,4);
  y.muuta(4,3,4);
  z.muuta(2,4);
 
  x.tilavuus();
  y.tilavuus();
  z.tilavuus();
 
  return 0;
}

Oliopointterit

Olion jäsentä on tähän mennessä käsitelty aina piste-operaattorin avulla ( olio.get_i() ). Olion jäsentä voidaan käsitellä myös olion osoittimen avulla, tällöin käytetään nuoli-operaattoria ( ptr->get_i() ). Käytännössä toimitaan samalla tavalla kuin struktuureiden yhteydessä. Osoitin määritellään normaalisti tähti-operaattorin kanssa ( luokka *ptr ) ja olion osoitin saadaan lisäämällä oliomuuttujan eteen et-merkki ( &olio ).

#include <iostream>
 
class oma {
  int  a;
public:
  oma( int x );
  int   get();
};
 
oma::oma( int x ) { a = x; }
 
int oma::get() { return a; }
 
main()
{
  oma  olio(120);
  oma  *p;
 
  p = &olio;
 
  cout << "Olion arvo on: " << olio.get() << "\n";
  cout << "Olion arvo osoittimen avulla: " << p->get() << "\n";
 
  return 0;
}

Huomaa, että määrittelyssä oma *p; luodaan vain osoite, varsinaista oliota ei luoda. Olion osoittimelle on annettava järkevä arvo ( p = &olio ), ennen kuin sitä voidaan käyttää. Luokat, struktuurit ja unionit ovat sukulaisia keskenään C++:ssa struktuurin määrittelyä on laajennettu, siten että struktuurilla ja luokalla on lähes samat ominaisuudet. Myös struktuuri voi sisältää jäsenfunktioita sisältäen myös konstruktorin ja destruktorin, struktuurimäärittely on suoraan tyyppimäärittely, ei tarvita erillistä typedef-määrittelyä. Itseasiassa ainoa ero struktuurin ja luokan välillä on se, että struktuurin jäsenet ovat oletusarvoisesti public-tyyppisiä ja luokan jäsenet ovat oletusarvoisesti private-tyyppisiä.

struct  struktuuri{
  // public osa
private:
  // privaatti osa
} muuttujalista;

Miksi tarvitaan molemmat luokka ja struktuuri? Standardi C:n struktuuri on hyväksytty rakenne C++:ssa ja sen on pysyttävä aina hyväksyttynä. Koska C.ssä struktuurin jäsenet ovat public-tyyppisiä, ominaisuus on säilytetty C++:ssa. Koska class-käsite on erillinen struct-käsitteestä, class-käsite on vapaa kehittymään vaikkapa suuntaan, jossa se ei ole yhteensopiva C:n struktuuri-käsitteen kanssa. Kun edelliset ovat eri asioita, C++:n kehitys ei rajoitu edellisiin yhteensopivuus ongelmiin. Toisaalta struktuurikäsitteen laajentamisesta ei ole mitään haittaakaan.

Useimmat ohjelmoijat käyttävät struktuuria C:n struktuurina ja luokkaa, mikäli on tarvetta funktioihin, jotka käsittelevät rakenteeseen liittyvää dataa.

Unioneista: Unioni on samanlainen kuin struktuuri, paitsi että unionin jäsenet jakavat saman data-alueen.

union  koe {
  char  nimi[10];
  int  henk_nro;
};

Oletetaan, että int vie 2 tavua muistia ja char vie 1 tavun. Koe-tyyppinen muuttuja vie 10 tavua muistia, siten että muistissa on joko 10-tavuinen nimi tai 2-tavuinen henk_nro. Kumpi muistissa on milläkin hetkellä, on ohjelman asia.

koe  x;
...
if ( valinta == 'N')
  strcpy( x.nimi, annettu_nimi );
else
  x.henk_nro = annettu_numero;
...

C++:ssa unionikin voi sisältää sekä dataa että funktioita. Kuten struktuureilla unioneillakin jäsenet ovat public-tyyppisiä oletusarvoisesti. Unioneihin liittyy rajoituksia:

  • Unioni ei voi periä mitään.
  • Unionia ei voi periä.
  • Unionin jäsenet eivät voi olla static-muistiluokassa
  • Unioni ei voi sisältää jäsentä, jolla on konstruktori tai destruktori (Unionilla itsellään voi toki olla konstruktori ja destruktori.)
  • Kaikki kääntäjät eivät salli unionille private-tyyppisiä jäseniä.

Seuraavassa rinnakkain struktuurin ja luokan avulla toteutetut ohjelmat.

#include <iostream>                 #include <iostream>
#include <string.h>                 #include <string.h>
 
struct st_type {                    class cl_type {
  st_type(double b, char *n);          double balance;
  void show();                        char  name[40];
private:                            public:
  double balance;                     cl_type(double b, char *n);
  char name[40];                      void show();
};                                  };
 
st_type::st_type(double b, char *n) cl_type::cl_type(double b, char *n)
{                                   {
  balance = b;                        balance = b;
  strcpy( name, n );                  strcpy( name, n );
}                                   }
 
void st_type::show()                void cl_type::show()
{                                   {
  cout << "Name: " << name;           cout << "Name: " << name;
  cout << " :$" << balance;           cout << " :$" << balance;
  if (balance <0.0) cout << "***";    if (balance <0.0) cout << "***"; 
  cout << "\n";                       cout << "\n";
}                                   }
 
main()                              main()  
{                                   {
  st_type acc1(100.12, "Johnson");    cl_type acc1(100.12, "Johnson");
  st_type acc2(-12.34, "Hedricks");   cl_type acc2(-12.34, "Hedricks");
 
  acc1.show();                        acc1.show();
  acc2.show();                        acc2.show();
  return 0;                           return 0;
}                                   }

Esimerkki unionista: Ohjelma tulostaa double-tyyppisen luvun bitti kerrallaan: (Tässä esityksessä double tyyppisessä luvussa on 8 tavua, jokainen tavu esitetään erikseen)

Bittiesitys tavussa 7:  01000000
Bittiesitys tavussa 6:  10011111
...
Bittiesitys tavussa 0:   10001001
#include <iostream>
 
union bits {
  bits( double n );
  void   show_bits();
  double  d;
  unsigned char c[ sizeof( double ) ];
};
 
bits::bits( double n ) { d = n; }
 
void bits::show_bits()
{
  int  i, j;
 
  for ( j = sizeof( double ) - 1; j >= 0; j-- ) {
    cout << "Bittiesitys tavussa " << j  << ": ";
    for ( i = 128; i; i >>= 1 )
      if ( i & c[ j ] ) cout << "1";
      else cout << "0";
    cout << "\n";
  }
}
 
main()
{
  bits  olio( 1991.829 );
 
  olio.show_bits( );
  return 0;
}


Selitys: 128 = 1000 0000

 x>>=1  x:ssä siirretään bitti kerrallaan oikealle yhden pykälän, 
   lopputulos sijoitetaan x:ään, täytetään 0:lla vasemmalta.
 &  AND bittikerrallaan

Esim:

ic[j]i & c[j]
1000000011010011True
0100000011010011True
0010000011010011False
0001000011010011True
0000100011010011False
0000010011010011False
0000001011010011True
0000000111010011True

In-line funktiot

C++:ssa voidaan määritellä funktioita, joita ei itseasiassa kutsuta vaan kutsukohtaan kopioidaan funktion koodi (kuten C:n parametrisoidut makrot). Tällöin puhutaan ns. in-line funktioista. In-line funktion etuna on se, että funktion kutsumiseen ja palautusarvon palautukseen liittyvä lisätyö (overhead) jää pois, jolloin in-line funktioiden suoritus on paljon nopeampaa kuin tavallisten funktioiden. In-line funktion haittana on se, että jos in-line funktiot ovat pitkiä ja jos niitä kutsutaan useissa kohdissa ohjelmaa, ohjelman koko kasvaa. Tästä syystä in-line funktiot on varattu lyhyille funktioille.

In-line funktio määritellään siten, että funktion määrittelyssä funktion nimen eteen laitetaan avainsana inline.


#include <iostream>
 
inline int parillinen( int x )
{
  return !(x%2);
}
 
main()
{
  if ( parillinen( 10 ) ) cout << "10 on parillinen\n";
  if ( parillinen( 11 ) ) cout << "11 on parillinen\n";
  return 0;
}

Koska parillinen on in-line funktio, lause if ( parillinen( 10 ) ) cout << "10 on parillinen\n"; on toiminnallisesti sama kuin if ( !(10%2) ) cout << "10 on parillinen\n";

In-line funktion määrittelyn (koodin) on oltava ennen funktion kutsua! Muuten ei kopiointia pystyttäisi suorittamaan.

Miksi käytetään in-line funktiota eikä parametrisoitua makroa:

  • in-line on yhtenäisempi tapa kuin markot. "inline"- määrettä lukuunottamatta funktio on määritelty aivan samalla tavalla kuin tavalliset funktiot. Makrojen tapauksessa voidaan esimerkiksi tarvita ylimääräisiä sulkeita, jotta makro toimisi kaikissa tapauksissa jne.
  • Kääntäjä pystyy optimoimaan inline-funktion täydellisemmin kuin makron.

C++ ohjelmoijat käyttävät käytännöllisesti katsoen aina inline-funktioita makrojen asemasta. "inline"-määre on pyyntö kääntäjälle ei käsky. Aina funktiota ei pystytä toteuttamaan inline-tyyppisenä, tällöin funktiota käsitellään tavallisena funktiona.

Rajoituksia:

  • Inline funktiossa ei voi olla staattisia muuttujia.
  • Inline funktiossa ei voi olla silmukkarakenteita
  • Inline funktiossa ei voi olla switch-rakennetta tai goto käskyä.
  • Inline funktio ei voi olla rekursiivinen.
  • Inline funktiossa ei voi olla paikallisia taulukko-muuttujia
  • void-tyyppisessä inline-funktiossa ei voi olla return; lausetta.

min-funktion kuormitus ja inlinen käyttöä:

#include <iostream>
 
inline int min( int a, int b )
{
  return a < b ? a : b;
}
 
inline long min( long a, long b )
{
  return a < b ? a : b;
}
 
inline double min( double a, double b )
{
  return a < b ? a : b;
}
 
main()
{
  cout << min( -10, 10 ) << "\n";
  cout << min( -20L, 20L ) << "\n";
  cout << min( -100.0, 100.0 ) << "\n";
 
  return 0;
}

Automaattinen inlining

Jos luokan jäsenfunktion määrittely on riittävän lyhyt, sen määrittely (=koodi) voi sisältyä itse luokkamäärittelyn sisään. Tällöin funktiosta tulee automaattisesti inline-tyyppinen (mikäli mahdollista) eikä avainsanaa inline tarvita.

Ohjelma, jossa luokka ylläpitää kahta kokonaislukua ja jäsenfunktion "jaollinen" avulla saadaan tietää ovatko luvut keskenään jaollisia, suuntaan tai toiseen. Esim. 6 ja 3 ovat jaollisia samoin 4 ja 12.

#include <iostream>
 
class luvut {
  int  i, j;
public:
  luvut( int a, int b ) { i = a; j = b; } // aaltosulkujen välissä 
            // inline-funktion koodi
  int jaollinen( ) 
  {
    if ( i > j ) return !( i % j ) ;
    else  return !( j % i ) ;
  }
};
 
main()
{
  luvut  olio1( 10, 2 ), olio2( 3, 9 ), olio3( 10, 3 );
 
  if (olio1.jaollinen()) cout << "10 ja 2 jaollisia\n";
  if (olio2.jaollinen()) cout << "3 ja 9 jaollisia\n";
  if (olio3.jaollinen()) cout << "10 ja 3 jaollisia\n";
 
  return 0;
}

Usein lyhyet jäsenfunktiot laitetaan inline-funktioiksi vaikka nopeuden suhteen ei saavutettaisikaan mitään etuja. Esimerkiksi tulostus on hidasta, mutta jos tulostavan jäsenfunktion koodi on lyhyt, se on mukava laittaa inline-muotoon. Mitään vahinkoa ei tapahdu:

class oma {
  int  i;
public:
  oma( int n ) { i = n; }
  void show( ) { cout << i << "\n"; }
};
Tämän dokumentin kopiointi, levittäminen sekä muokkaaminen on sallittua GNU Free Documentation Licensen version 1.2 tai uudemman Free Software Foundationin julkaiseman version mukaisesti, ilman muuttumattomuuslauseketta tai kansitekstejä. Tätä koskee vastuuvapaus.
Kopio lisenssistä (englanniksi) löytyy täältä.

Alkuperäinen (c) Petteri Hämäläinen

Henkilökohtaiset työkalut