wiki:staattiset
Last modified 16 months ago Last modified on 2016-09-29 21:44:14

Dokumentti siirretty TIMiin


Luentomonisteen täydennykset » Asiaa static-sanasta

Käyttämästämme ohjelmointikielestä johtuen opiskelija joutuu heti ensimmäistä ohjelmaa tehdessään kohtaamaan monta outoa sanaa. Näistä yksi sana on static, johon on pelkän "intuition" avulla vaikea saada otetta ilman aikaisempaa kokemusta olio-ohjelmoinnista.

Staattisuudella tarkoitetaan eri kielten välillä hieman eri asioita. Aihe on myöskin olio-ohjelmoinnin käsite, eikä valitettavasti kovin aloittelijaystävällinen sellainen, varsinkin kun Ohjelmointi 1 -kurssi ei varsinaisesti ole olio-ohjelmointikurssi. Alla on muutama esimerkki, jonka avulla avaamme staattisen muuttujan ja staattisen metodin käsitteitä C#:ssa. Jos tekstistä ei jää mitään muuta mieleen, niin toivottavasti edes seuraavat kaksi pääasiaa.

  1. Staattiseksi julistettu luokan jäsen (esimerkiksi muuttuja tai metodi) kuuluu tyyppiin itseensä, eikä mihinkään yksittäiseen olioinstanssiin.
  2. Staattinen jäsen jakaa arvonsa kaikkien kyseisestä luokasta tehtyjen olioinstanssien välillä.

Seuraavaksi oletamme, että olet tähän mennessä ymmärtänyt muuttujan käsitteen (M: 7. Muuttujat), ja harjoitellut muuttujien käyttöä omassa ohjelmassasi. Lisäksi oletamme, että ymmärrät, miten luokkia tehdään, ja kuinka luokasta tehdään oliomuuttujia.

Staattinen muuttuja

Staattinen muuttuja jakaa arvonsa kaikkien luokasta tehtyjen instanssien kanssa.

Toisin sanoen, kun teet samasta luokasta monta oliota, niin staattisen muuttujan arvo on sama kaikkien näiden olioiden välillä. Erityisesti, kun arvo muuttuu, niin muutos näkyy kaikissa olioissa.

Käytännössä static-muuttujia on mielekästä käyttää vain tilanteissa, missä on varmaa, että koko ohjelmassa on selvää, että ko. muuttujaa käytetään vain yhdessä merkityksessä, eikä ole vaaraa siitä, että muuttujan arvo muuttuu odottamattomasti.

Esimerkki 1 (ei-staattinen)

Tässä esimerkissä OmaLuokka-luokassa on yksityinen, ei-staattinen muuttuja, jota kasvatetaan KasvataArvoa-metodin avulla. KasvataArvoa-metodi on myöskin ei-staattinen.

using System;

/// <summary> Luokka jossa yksi attribuutti,
/// joka EI ole staattinen. </summary>
public class OmaLuokka
{
    private int i = 2;

    /// <summary>Kasvattaa i:n arvoa
    /// ja palauttaa uuden arvon. </summary>
    /// <returns>Uusi i:n arvo.</returns>
    public int KasvataArvoa()
    {
        i += 3;
        return i;
    }
}

/// <summary>Luokka, jossa pääohjelma 
/// sijaitsee.</summary>
public class Esimerkki
{
    /// <summary>Pääohjelma.</summary>
    public static void Main()
    {
        OmaLuokka muuttuja = new OmaLuokka();
        Console.WriteLine(muuttuja.KasvataArvoa());
        OmaLuokka muuttuja2 = new OmaLuokka();
        Console.WriteLine(muuttuja2.KasvataArvoa());
    }
}

Käydään tämä esimerkki paloittain läpi. Dokumentaatiot on jätetty alla pois tilan säästämiseksi.

public class OmaLuokka
{
    private int i = 2;

    public int KasvataArvoa()
    {
        i += 3;
        return i;
    }
}

Yllä meillä on luokka, josta voidaan tehdä olioita (sitähän varten luokkia tehdään!) Luokassa on yksityinen (private) muuttuja (luokkamuuttujia kutsutaan englanniksi sanalla field), jota muutetaan olion KasvataArvoa-metodin avulla. Toisin sanoen, jokaisella OmaLuokka-luokasta tehdyllä oliolla on oma (erillinen) tila i-muuttujalle, eikä kukaan muu olio voi muuttaa i:n arvoa. KasvataArvoa-metodi on julistettu julkiseksi public-määreellä, joten sitä voidaan kutsua siellä olio on luotu, ja kutsuminen toki muuttaa i:n arvoa. Varsinaisen arvon muutoksen tekee kukin olio itse.

public class Esimerkki
{
    public static void Main()
    {
        OmaLuokka muuttuja = new OmaLuokka();
        Console.WriteLine(muuttuja.KasvataArvoa());  // tulostaa 5
        OmaLuokka muuttuja2 = new OmaLuokka();
        Console.WriteLine(muuttuja2.KasvataArvoa()); // tulostaa 5
    }
}

Pääohjelmassa (Main) tehdään juuri tekemästämme OmaLuokka-luokasta kaksi oliomuuttujaa, muuttuja ja muuttuja2. Tämän jälkeen kutsutaan olioiden KasvataArvoa-metodia. Metodin kutsumisen jälkeen tulostetaan paluuarvona tullut arvo (joka oli int-luku, kuten äsken näimme), ja huomaamme, että kummassakin tapauksessa tulostuu luku 5.

Vielä kertauksena: KasvataArvoa-metodin kutsuminen muuttaa kummankin olion kohdalla sen omaa, "henkilökohtaista" i-muuttujaa, eikä muualta (esimerkiksi pääohjelmasta käsin) ole oikeutta mennä muuttamaan i:n arvoa. Esimerkiksi tämä ei onnistuisi.

public class Esimerkki
{
    public static void Main()
    {
        OmaLuokka muuttuja = new OmaLuokka();
        muuttuja.i = 5; // virhe!
    }
}

Esimerkki 2 (staattinen)

using System;

/// <summary> Luokka jossa yksi attribuutti,
/// joka ON staattinen. </summary>
public class OmaLuokka
{
    private static int i = 2;

    /// <summary>Kasvattaa i:n arvoa
    /// ja palauttaa uuden arvon. </summary>
    /// <returns>Uusi i:n arvo.</returns>
    public int KasvataArvoa()
    {
        i += 3;
        return i;
    }
}

/// <summary>Luokka, jossa pääohjelma 
/// sijaitsee.</summary>
public class Esimerkki
{
    /// <summary>Pääohjelma.</summary>
    public static void Main()
    {
        OmaLuokka muuttuja = new OmaLuokka();
        Console.WriteLine(muuttuja.KasvataArvoa()); // tulostaa 5
        OmaLuokka muuttuja2 = new OmaLuokka();
        Console.WriteLine(muuttuja2.KasvataArvoa()); // tulostaa 8
    }
}

Tämä ohjelma on lähes samanlainen, kuin Esimerkki 1:ssä. Pieni, mutta tärkeä ero on siinä, että i-muuttuja on tällä kertaa merkitty staattiseksi. Tästä johtuen i-muuttujan arvo on kaikille OmaLuokka-luokasta tehdyille olioille yhteinen. Mikä tahansa OmaLuokka-olio voi muuttaa i:n arvoa, ja muutos vaikuttaa kaikkiin kyseisen luokan olioihin. Tästä johtuen ensimmäinen tulostuslause pääohjelmassa tulostaa 5, ja seuraava 8. Tästä johtuen staattisten muuttujien käyttö on varattu vain hyvin harvinaisia erikoistilanteita varten. Seuraavassa esimerkissä esitellään muutamia tällaisia erikoistilanteita.

Esimerkki 3 (staattinen)

Jypelin Game-luokassa on muutamia staattisia muuttujia. Näitä ovat muun muassa nimi, Exiting-tapahtuma ja kontrollit. Nämä ovat intuitiivisesti ajateltuna sellaisia asioita, jotka tapahtuvat joko kerran koko pelin "elämän" aikana (esim. Exiting), tai sitten ovat koko pelin laajuisesti käytössä riippumatta siitä missä tilassa peli muuten on.

Esimerkiksi fyysisiä näppäimistöjä voi tietokoneessa olla vain yksi (no, voi oikeasti olla kiinnitettynä useampikin mutta ne kaikki painelevat samoja "kirjaimia" tietokoneen näkökulmasta), joten on ihan luonnollista että kontrollit ovat yhteiset läpi pelin ajan.

Muistinvaraus

Oliolle varataan muisti sitä luotaessa, mutta staattisille muuttujille muisti varataan ennen olion luomista.

Staattinen metodi

Vastaavasti kuin staattinen muuttuja, on staattinen metodi luokan "yhteinen", eikä sitä voi kutsua olioinstanssista käsin, vaan sitä kutsutaan luokan nimen avulla.

Esimerkki 1

using System;

public class MunLuokka
{
    public static void TulostaTeksti()
    {
        Console.WriteLine("Heippa staattisesta metodista");
    }
}

public class PaaLuokka
{
    public static void Main()
    {
        MunLuokka.TulostaTeksti();
    }
}

Esimerkki 2

Alla oleva koodi ei sen sijaan käänny...

public class PaaLuokka
{
    public static void Main()
    {
        MunLuokka m = new MunLuokka();
        m.TulostaTeksti();
    }
}

... vaan tuottaa virheen.

error CS0176: Member 'MunLuokka.TulostaTeksti()' cannot be accessed 
with an instance reference; qualify it with a type name instead

Esimerkki 3

Staattinen metodi ei voi muuttaa olioinstanssin jäsenten arvoa. Alla oleva koodi ei käänny.

public class MunLuokka
{
  private int a;

  public static void MuutaArvoa()
  {
    a++;
  }
}

... vaan tuottaa virheen

error CS0120: An object reference is required for the non-static field, 
method, or property 'MunLuokka.a'

Virhe tarkoittaa, että a-muuttujan arvoa ei voi muuttaa ilman olioinstanssia.