double summa = Summa(5, 10.5);

Sitten keksimme, että hei, tarvitsemme myös funktion, joka osaa summata kolme lukua, ja haluaisimme kutsua sitä kirjoittamalla

        double summa = Summa(5, 10.5, 30.9);

Kirjoitetaan samanniminen funktio, mutta annetaan sille funktion määrittelyrivillä (esittelyrivillä, otsikkorivillä, eng. header row tai function signature) kolme parametria kahden sijaan. Toteutetaan funktio myös saman tien.

    public static double Summa(double a, double b, double c)
    {
        return a + b + c;
    }

Mutta nyt huomaamme, että meillä on melkein sama koodi näissä kahdessa funktiossa. Muutetaan ensimmäistä funktiota siten, että kutsutaan ensimmäisestä funktiosta (joka osaa vähemmän) toista funktiota (joka osaa enemmän). Annetaan kolmanneksi summattavaksi luvuksi (siis kolmanneksi parametriksi) 0.

    public static double Summa(double a, double b)
    {
        return Summa(a, b, 0);
    }
# funcovverride1

Edelliset koottuna ilman kommentteja. Tulostuksessa on käytetty myöhemmin esiteltävää String Interpolation, jolla muuttujen arvoja on helppo tulostaa tekstin sekaan.

Tehtävä: Lisää koodiin oikeaoppiset kommentit.

public class KuormitusEsimerkki1
{
    public static void Main()
    {
        double summa3 = Summa(5, 10.5, 30.9);
        double summa2 = Summa(5, 10.5);
        System.Console.WriteLine($"summa3 = {summa3}, summa2 = {summa2}");
    }


    public static double Summa(double a, double b, double c)
    {
        return a + b + c;
    }


    public static double Summa(double a, double b)
    {
        return Summa(a, b, 0);
    }
}

 

# funcovverride2

Sama käyttäen C#:in oletusparametreja. Oletusparametrin idea on, että jos kutsussa ei ole riittävästi parametreja, kääntäjä lisää kutsuun automaattisesti vastaavan vakion.

Tehtävä: Lisää koodiin oikeaoppiset kommentit.

public class KuormitusEsimerkki2
{
    public static void Main()
    {
        double summa3 = Summa(5, 10.5, 30.9);
        // kääntäjä tekee seuraavasta kutsun Summa(5, 10.5, 0.0)
        double summa2 = Summa(5, 10.5);
        System.Console.WriteLine($"summa3 = {summa3}, summa2 = {summa2}");
    }


    public static double Summa(double a, double b, double c=0.0)
    {
        return a + b + c;
    }
}

 

Tämän esimerkin avulla näimme yksinkertaisella tavalla sen, mitä kuormittaminen tarkoittaa. Seuraava esimerkki valottaa kuormittamisen hyötyjä paremmin.

6.5.2 Vakiokokoinen lumiukko vs ukon koko parametrina

Voimme luoda vakiokokoisen lumiukon seuraavalla aliohjelmalla.

    /// <summary>
    /// Aliohjelma piirtää vakiokokoisen lumiukon
    /// annettuun paikkaan.
    /// </summary>
    /// <param name="peli">Peli, johon lumiukko tehdään.</param>
    /// <param name="x">Lumiukon alimman pallon x-koordinaatti.</param>
    /// <param name="y">Lumiukon alimman pallon y-koordinaatti.</param>
    public static void PiirraLumiukko(Game peli, double x, double y)
    {
      PhysicsObject alapallo, keskipallo, ylapallo;
      alapallo = new PhysicsObject(2 * 100.0, 2 * 100.0, Shape.Circle);
      alapallo.X = x;
      alapallo.Y = y;
      peli.Add(alapallo);
      
      keskipallo = new PhysicsObject(2 * 50.0, 2 * 50.0, Shape.Circle);
      keskipallo.X = x;
      keskipallo.Y = alapallo.Y + 100 + 50;
      peli.Add(keskipallo);
      
      ylapallo = new PhysicsObject(2 * 30.0, 2 * 30.0, Shape.Circle);
      ylapallo.X = x;
      ylapallo.Y = keskipallo.Y + 50 + 30;
      peli.Add(ylapallo);
}

Voimme kutsua tätä aliohjelmaa Begin:stä vaikkapa seuraavasti.

        PiirraLumiukko(this, 0, Level.Bottom + 200.0);

Mutta entäs jos haluaisimmekin piirtää tämän lisäksi joskus eri kokoisiakin ukkoja? Toisin sanoen voisi olla tarve, että PiirraLumiukko tekisi meille "vakiokokoisen" ukkojen lisäksi myös halutessamme jonkun muun kokoisen ukkelin. Kutsut Begin:ssä voisivat näyttää tältä.

        // Vakiokokoisen ukon kutsuminen (alapallon koko 2 * 100)
        PiirraLumiukko(this, -200, Level.Bottom + 300.0);
        
        // Samannimisen aliohjelman käyttäminen 
        // pienemmän ukon tekemiseen (alapallon koko 2 * 50)
        PiirraLumiukko(this, 0, Level.Bottom + 200.0, 50.0);

Mutta nyt kääntäjä antaa esimerkiksi virheilmoituksen

No overload for method 'PiirraLumiukko' takes 4 arguments.

Joten kirjoitetaan uusi aliohjelma, jonka nimeksi tulee PiirraLumiukko (kyllä, samanniminen), mutta peli-parametrin ja paikan lisäksi parametrina annetaan myös alapallon säde.

    public static void PiirraLumiukko(Game peli, double x, double y, double sade)
    {
     // tähän kirjoitetaan kohta koodia...
    }

Siirretään nyt koodi alkuperäisestä aliohjelmasta tähän uuteen, ja laitetaan pallojen säde riippumaan parametrina annetusta säteestä. Lisäksi laitetaan keski- ja yläpallon paikat riippumaan pallojen koosta! Uusi (neljäparametrinen) aliohjelma näyttäisi nyt seuraavalta.

    public static void PiirraLumiukko(Game peli, double x, double y, double sade)
    {
        PhysicsObject alapallo, keskipallo, ylapallo;
        alapallo = new PhysicsObject(2 * sade, 2 * sade, Shape.Circle);
        alapallo.X = x;
        alapallo.Y = y;
        peli.Add(alapallo);
    
        // keskipallon koko on 0.5 * sade
        keskipallo = new PhysicsObject(2 * 0.5 * sade, 2 * 0.5 * sade, Shape.Circle);
        keskipallo.X = x;
        keskipallo.Y = alapallo.Y + alapallo.Height / 2 + keskipallo.Height / 2;
        peli.Add(keskipallo);
    
        // ylapallon koko on 0.3 * sade
        ylapallo = new PhysicsObject(2 * 0.3 * sade, 2 * 0.3 * sade, Shape.Circle);
        ylapallo.X = x;
        ylapallo.Y = keskipallo.Y + keskipallo.Height / 2 + ylapallo.Height / 2;
        peli.Add(ylapallo);
    }

Nyt voimme kutsua kolmeparametrisesta PiirraLumiukko-aliohjelmasta tuota "versiota", joka osaa tehdä asioita enemmän ilman, että copy-pastetamme koodia.

    public static void PiirraLumiukko(Game peli, double x, double y)
    {
        PiirraLumiukko(peli, x, y, 100);
    }
Ukkelit sulassa sovussa.
Ukkelit sulassa sovussa.
# kuormitettulumiukko

Koko esimerkki kuormiteutusta lumiukosta

using System;
using System.Collections.Generic;
using Jypeli;
using Jypeli.Assets;
using Jypeli.Controls;
using Jypeli.Effects;
using Jypeli.Widgets;

/// @author Antti-Jussi Lakanen
/// @version 30.1.2014
///
/// <summary>
/// Aliohjelmien kuormittaminen
/// </summary>
public class Kuormittaminen : PhysicsGame
{
    /// <summary>
    /// Kutsutaan PiirraLumiukko-aliohjelmaa kahdella eri tavalla.
    /// Ensimmäisessä ei anneta parametrina kokoa, jolloin tulee "vakiokokoinen" lumiukko.
    /// Toisessa tavassa annetaan parametrina haluttu koko.
    /// </summary>
    public override void Begin()
    {
        Level.Background.Color = Color.Black;
        PiirraLumiukko(this, -200, Level.Bottom + 300.0);
        PiirraLumiukko(this, 0, Level.Bottom + 200.0, 50.0);

        PhoneBackButton.Listen(ConfirmExit, "Lopeta peli");
        Keyboard.Listen(Key.Escape, ButtonState.Pressed, ConfirmExit, "Lopeta peli");
    }

    /// <summary>
    /// Aliohjelma piirtää vakiokokoisen lumiukon
    /// annettuun paikkaan.
    /// </summary>
    /// <param name="peli">Peli, johon lumiukko tehdään.</param>
    /// <param name="x">Lumiukon alimman pallon x-koordinaatti.</param>
    /// <param name="y">Lumiukon alimman pallon y-koordinaatti.</param>
    public static void PiirraLumiukko(Game peli, double x, double y)
    {
        PiirraLumiukko(peli, x, y, 100);
    }

    /// <summary>
    /// Aliohjelma piirtää annetun kokoisen lumiukon
    /// annettuun paikkaan
    /// </summary>
    /// <param name="peli">Peli, johon lumiukko tehdään.</param>
    /// <param name="x">Lumiukon alimman pallon x-koordinaatti.</param>
    /// <param name="y">Lumiukon alimman pallon y-koordinaatti.</param>
    /// <param name="sade"></param>
    public static void PiirraLumiukko(Game peli, double x, double y, double sade)
    {
        PhysicsObject alapallo, keskipallo, ylapallo;
        alapallo = new PhysicsObject(2 * sade, 2 * sade, Shape.Circle);
        alapallo.X = x;
        alapallo.Y = y;
        peli.Add(alapallo);

        keskipallo = new PhysicsObject(2 * 0.5 * sade, 2 * 0.5 * sade, Shape.Circle);
        keskipallo.X = x;
        keskipallo.Y = alapallo.Y + alapallo.Height / 2 + keskipallo.Height / 2;
        peli.Add(keskipallo);

        ylapallo = new PhysicsObject(2 * 0.3 * sade, 2 * 0.3 * sade, Shape.Circle);
        ylapallo.X = x;
        ylapallo.Y = keskipallo.Y + keskipallo.Height / 2 + ylapallo.Height / 2;
        peli.Add(ylapallo);
    }
}

 

Onko pelisysteemin sisässä siis vakiokokoisen lumiukon mitat jo valmiina, joihin tuossa "aliohjelma piirtää vakiokokoisen lumiukon mitat annettuun paikkaan"- osuudessa aliohjelmakutsulla viitataan? Sillä vaan mietin kun tässä oppimaani sisäistäessä en saa kunnolla kosketusta siihen että minkä vuoksi parametreiksi riittää vain "peli, x, y, 100"- koordinaatit eikä sen kummempia tietoja..

VL: Siis kommenteissa sanotaan kuten asia on. Ko aliohjelma piirtää aina vakio 100-säteisen pallon alimmaksi palloksi. Siinä alempana on sitten se aliohjelma, jolle annetaan parametrina tuo 100 jotta se tuolla25 rivin kutsulla piirtää nimenomaan 100 (vakio) kokoisen ukon. "Pelisysteemin" sisällä ei ole mitään "ylimääräistä" lumiukkoon liittyvää. Ainostaan tuo PhysicsObject-luomiseen tarvittava koneisto (jolla nyt siis tehdään niitä palloja).

Jaa niin siis aliohjelmalle pitää aina kuitenkin ilmoittaa neljäntenä parametrinä sädemitta joka alimmalla pallolla halutaan olevan, että "vakiokoko"- sanan "kirjoittaminen" ei varsinaisesti auta vielä mitään?

VL: Siinähän on kaksi eri aliohjelmaa. Toinen piirtää aina 100-säteisellä alapalolla ja toinen tarvitsee sen säteen. Ks rivit 25 ja 26. Se että tuo 100-säteisen piirtävä kutsuu sitä toista, niin säästytään ettei tarvitse koko koodia kopioida uudestaan.

Kysymykseni tais olla vahingossa vähän epäselvä, mietintöni koski siis tuota ns alempaa aliohjelmaa ja tarkennusta siihen, eli (peli, x, y, 100)- parametreihin. Ja siis nuo x ja y- parametrit ilmeisesti vain viittaavat noihin ylempänä kahdessa aliohjelmassa oleviin koordinaattiparametreihin. Mietin vielä että jos tuo ylempi aliohjelma piirtää siis 100-säteisen alapallon tuon alemman aliohjelmaparametrin perusteella, niin se että tuohon ylempään toiseen aliohjelmaan kirjoittaa säteen (50.0) jo valmiiksi, ajaa tavallaan yli tuon allaolevan 100-sädekäskyn, eli että ohjelma ei tavallaan kuitenkaan mene sekaisin siitä että tarvitseeko sen piirtää toiseen koordinaattiin 100 vai 50- säteinen pallo alapalloksi? Jos siis haluttaisiin että molempiin eri kohtiin tulevat lumiukot olisivat keskenään samankokoisia niin silloin jätettäisiin kirjoittamatta tuo 50- säde? Hetkinen.. "Se että tuo 100-säteisen piirtävä kutsuu sitä toista".. mitä toista? Ihan siis tuota alinta aliohjelmarimpsua? Eli siis että kun PiirraLumiukko(this, -200, Level.Bottom + 300.0); - niminen ohjelma kutsuu PiirraLumiukko(peli, x, y, 100); -nimistä ohjelmaa, niin se sitä kautta kutsuu siis tuota alinta eli public static void PiirraLumiukko(Game peli, double x, double y, double sade) -nimistä ohjelmaa?

VL: Nyt hieman varoivaisuutta termien kanssa. Ohjelmassa on kolem aliohjelmakutsua (laita HighLight päälle) riveillä: 25, 26 ja 41. Ohjelmassa on kaksi aliohjelmaa (ja Begin metodi) alkaen riveiltä: 39 ja 52. Kun ohjelma lähtee käyntiin, on suoritusjärjetys rivit: 24,25,39,41,52,54-69,42,26,52,54-69,28,29,30. Kannattaa kokeilla tuota debuggereissa. Myäs sanaa "viitata" kannattaa varoa, koska se on varattu viitemuuttujien käyttöön.

Ok, no nyt sain kuitenkin hahmotettua ensalkuun paremmin että mikä ohjelman osa tekee mitäkin (katson kyllä nuo termistötkin paremmin läpi vielä kun tarttis saada ne paremmin haltuun muutenkin, aamuyön virkeyksissä tuli vaan mietittyä että kuinka saa kysymysajatukset selkeimmin ilmi. Ja toisaalta, tällasen "oikean" esimerkin kautta osoitettuna ja havainnollistettuna ne parhaiten myös oppii).

27 Oct 22 (edited 30 Oct 22)

.

# muuttujat

7. Muuttujat

muuttujanTyyppi muuttujanNimi = muuttujanArvo;

muuttujanNimi = muuttujanUusiArvo;

Esim:

int maailmanTarkoitus = -42;

maailmanTarkoitus = -1 * maailmanTarkoitus;

Muuttujat (variable) toimivat ohjelmassa tietovarastoina erilaisille asioille. Muuttuja on kuin pieni laatikko, johon voidaan varastoida asioita, esimerkiksi lukuja, sanoja, tietoa ohjelman käyttäjästä ja paljon muuta. Proseduaalisissa kielissä ilman muuttujia järkevä tiedon käsittely olisi oikeastaan mahdotonta. Funktio-ohjelmoinnissa tosin asiat ovat hieman toisin. Olemme jo ohimennen käyttäneetkin muuttujia, esimerkiksi Lumiukko-esimerkissä teimme PhysicsObject-tyyppisiä muuttujia p1, p2 ja p3. Vastaavasti PiirraLumiukko-aliohjelman parametrit (Game peli, double x, double y) ovat myös muuttujia: Game-tyyppinen oliomuuttuja peli, sekä double-alkeistietotyyppiset muuttujat x ja y.

Termi muuttuja on lainattu ohjelmointiin matematiikasta, mutta niitä ei tule kuitenkaan sekoittaa keskenään - muuttuja matematiikassa ja muuttuja ohjelmoinnissa tarkoittaa hieman eri asioita. Tulet huomaamaan tämän seuraavien kappaleiden aikana.

Muuttuja arvo muuttuu vain sijoituslauseen suoritushetkellä:

    int ika = 21;
    int nyt = 2021;
    int syntymavuosi = nyt - ika;  // arvoksi tulee 2000
    nyt = 2022; // syntymävuosi on edelleen 2000

Muuttujan arvo ei muutu vaikka sen arvon tuottavissa lausekkeissa jokin myöhemmin muuttuisi. Esimerkiksi edellä syntymavuosi on edelleen 2000 vaikka jatkossa tehtäisiin sijoitus.

    nyt = 2022;

Eli lausekkeen arvo lasketaan sillä hetkellä kun sijoitus tehdään.

Muuttujaan sijoitetaan sijoitusoperaattorilla =. Sijoituksessa muuttujan nimi on vasemmalla ja sijoitettava lauseke sijoitusmerkin oikealla puolella. Myös vakioarvo on lauseke.

    muuttujannimi = lauseke;

Muuttujien arvot tallennetaan keskusmuistiin tai rekistereihin, mutta ohjelmointikielissä voimme antaa kullekin muuttujalle nimen (identifier), jotta muuttujan arvon käsittely olisi helpompaa. Muuttujan nimi onkin ohjelmointikielten helpotus, sillä näin ohjelmoijan ei tarvitse tietää tarvitsemansa tiedon keskusmuisti- tai rekisteriosoitetta, vaan riittää muistaa itse nimeämänsä muuttujan nimi. [VES]

Koska kääntäjän pitää osata varata muuttujalle oikean kokoinen muistialue, pitää muuttujalle esitellä myös tyyppi. Muuttujan tyyppiä tarvitaan myös siksi, että tiedetään miten muistipaikkaan tallennettua tietoa pitää käsitellä. Jotta ymmärtäisimme erilaisien tietotyyppien erilaisia tallennustapoja, tutustumme myöhemmin muun muassa binäärilukuihin. Esimerkiksi kahdeksan bitin yhdistelmä, eli tavu 01000001 voidaan tulkita esimerkiksi kirjaimeksi A tai etumerkittömäksi kokonaisluvuksi 65.

Esimerkiksi lauseessa Console.WriteLine(a) ei voitaisi tietää mitä pitää tulostaa, mikäli ei tiedetä muuttujan a tyyppiä. Aivan vastaavasti kuin lauseesta kuusi palaa, ei voida tietää mitä sillä tarkoitetaan jos asiayhteys ei ole selvillä.

7.1 Muuttujan määrittely

Kun matemaatikko sanoo, että "n on yhtä suuri kuin 1", tarkoittaa se, että tuo termi (eli muuttuja) n on jollain käsittämättömällä tavalla sama kuin luku 1. Matematiikassa muuttujia voidaan esitellä tällä tavalla "häthätää".

Ohjelmoijan on kuitenkin tehtävä vastaava asia hieman tarkemmin. C#-kielessä tämä tapahtuisi kirjoittamalla seuraavasti:

# intn
   int n;
   n = 1;

 

Ensimmäinen rivi tarkoittaa väljästi sanottuna, että "lohkaise pieni pala - johon mahtuu int-kokoinen arvo - säilytystilaa tietokoneen muistista, ja käytä siitä jatkossa nimeä n". Toisella rivillä julistetaan, että "talleta arvo 1 muuttujaan, jonka nimi on n, siten korvaten sen, mitä kyseisessä säilytystilassa mahdollisesti jo on".

Merkki = on sijoitusoperaattori ja siitä puhutaan enemmän myöhemmässä luvussa.

Mikä sitten on tuo edellisen esimerkin int?

# muuttujanSijoitus

Tehtävä: Sijoitus

Aukaise Tauno. Sijoita n-muuttujaan arvo 1 vetämällä sen päälle <--1 'laatikko'. Aja ohjelma. Tulostuksen tulisi olla n=1. Kokeile sitten kasvattaa (vedä +1 n:än päälle) ja vähentää (vedä -1) muuttujan arvoa ja seuraa minkälaista ohjelmakoodia Tauno kirjoittaa.

 

C#:ssa jokaisella muuttujalla täytyy olla tietotyyppi (usein myös lyhyesti tyyppi). Tietotyyppi on määriteltävä, jotta ohjelma tietäisi, millaista tietoa muuttujaan tullaan tallentamaan. Toisaalta tietotyyppi on määriteltävä siksi, että ohjelma osaa varata muistista sopivan kokoisen lohkareen muuttujan sisältämää tietoa varten. Esimerkiksi int-tyypin tapauksessa tilantarve olisi 32 bittiä (4 tavua), byte-tyypin tapauksessa 8 bittiä (1 tavu) ja double-tyypin 64 bittiä (8 tavua). Muuttuja määritellään (declare) kirjoittamalla ensiksi tietotyyppi ja sen perään muuttujan nimi. Muuttujan nimet aloitetaan C#:ssa pienellä kirjaimella, jonka jälkeen jokainen uusi sana alkaa aina isolla kirjaimella. Kuten aiemmin mainittiin, tämä nimeämistapa on nimeltään camelCasing.

        muuttujanTietotyyppi muuttujanNimi;

Tuo mainitsemamme int on siis tietotyyppi, ja int-tyyppiseen muuttujaan voi tallentaa kokonaislukuja. Muuttujaan n voimme laittaa lukuja 1, 2, 3, samoin 0, -1, -2, ja niin edelleen, mutta emme lukua 0.1 tai sanaa "Moi". Mutta mitä ikinä laitammekin, niin muuttujassa voi olla vain yksi arvo kerrallaan. Kun muuttujaan sijoitetaan uusi arvo, ei edelliseen arvoon pääse enää mitenkään käsiksi.

Henkilön iän voisimme tallentaa seuraavaan muuttujaan:

        int henkilonIka;

Huomaa, että tässä emme aseta muuttujalle mitään arvoa, vain määrittelemme muuttujan int-tyyppiseksi ja annamme sille nimen.

Samantyyppisiä muuttujia voidaan määritellä kerralla useampia erottamalla muuttujien nimet pilkulla. Tietotyyppiä double käytetään, kun halutaan tallentaa desimaalilukuja.

        double paino, pituus;

Määrittely onnistuu toki myös erikseen (joka on jopa suositeltavampi tapa):

        double paino;
        double pituus;

Miksi tietotyypin määrittely erikseen on jopa suositeltavampi tapa kuin samalla rivillä? Siksikö, että ne ovat paljaalla silmällä helpompi löytää koodista vai jokin muu syy?

22 Sep 16 (edited 22 Sep 16)

VL: kyllä tuossa selkeämmin näkyy mitä muuttujia on esitelty. Ja on helpompi vaihtaa yhden muuttujan typpiä vahingossa vaihtamatta toista. Lisäksi C:ssä

int* a, b; // luo osoittinmuuttujan 
           // ja yhden intin
int *c;    // kun alla oleva taas 
int d;     // selkeämmin näyttää eron
23 Sep 16 (edited 24 Nov 21)

Muuttujaan voi asettaa arvon myös jo määrittelyn yhteydessä. Tällöin puhutaan arvon alustamisesta. Huomattakoon että arvo voi tulla myös lausekkeen tuloksena.

muuttujanTietotyyppi muuttujanNimi = VAKIO;
muuttujanTietotyyppi muuttujanNimi = lausekeJokaTuottaaArvon;
# muuttujanAlustusEsittelyssa
//
        bool onkoKalastaja = true;
        char merkki = 't';
        int kalojenLkm = 0;
        double luku1 = 0, luku2 = 2.0, luku3 = 3+2.4;

 

Miksi tässä ei tarvita $-merkkiä Writelinen sulkujen sisään, kuten edellisen kappaleen esimerkeissä?

VL: Tuo $ tekee merkkijonosta erilaisen muotoilijan, missä muuttujien arvot saa kirjoittaa suoraan lainausmerkkehin. Ks lisää luvusta 12.7.

17 Feb 25 (edited 18 Feb 25)

Muuttujalle sijoitettavan arvon (tai lausekkeen arvon) tulee olla tyypiltään sellainen, että se voidaan sijoittaa muuttujaan. Yksinkertaisiin lainausmerkkeihin kirjoitettu kirjain on arvo, joka voidaan sijoittaa char-tyyppiseen muuttujaan. Esimerkiksi int-tyyppiseen muuttujaan ei voi sijoittaa reaalilukua:

# intiinDouble
//
        int ika = 2.5; // TÄMÄ KOODI EI KÄÄNNY

 

mutta reaalilukuun voi sijoittaa kokonaisluvun

# doubleenInt
//
        double hinta = 20000;

 

Huomattakoon, että reaalilukujen (mm. double) desimaalierottimen ohjelmakoodissa on aina piste (.) eikä tuhaterottimia käytetä.

# mcq1Muuttujat
Mitkä sallittuja?

Mitkä seurvaavista muuttujien määrittelyistä ovat sallittuja:

Miksi muuttujalle ei voi antaa nimeksi numeroa? Esim. tässä tapauksessa tuo int 4;

VL: Mietis mitä tästä seuraisi:

    int 4 = 3;
    Console.WriteLine(4);   
    

Ja jotta tuota olisi helpompi estää, ei muuttujan nimi edes saa alkaa numerolla (vaikka se olisi periaatteessa vielä tehtävissä).

23 Sep 19 (edited 03 Feb 23)

7.2 Alkeistietotyypit

C#:n tietotyypit voidaan jakaa alkeistietotyyppeihin (primitive types, perustyyppi, perustietotyyppi) ja oliotietotyyppeihin (reference types). Oliotietotyyppeihin kuuluu muun muassa käyttämämme PhysicsObject-tyyppi, jota pallot p1 jne. olivat, sekä merkkijonojen tallennukseen tarkoitettu string-olio. Oliotyyppejä käsitellään myöhemmin luvussa 8.

Eri tietotyypit vaativat eri määrän kapasiteettia tietokoneen muistista. Vaikka nykyajan koneissa on paljon muistia, on hyvin tärkeää valita oikean tyyppinen muuttuja kuhunkin tilanteeseen. Suurissa ohjelmissa ongelma korostuu hyvin nopeasti käytettäessä muuttujia, jotka kuluttavat tilanteeseen nähden kohtuuttoman paljon muistikapasiteettia. C#:n alkeistietotyypit on lueteltu alla.

Taulukko 1: C#:n alkeistietotyypit koon mukaan järjestettynä.

Tällä kurssilla tärkeimmät alkeistietotyypit ovat: bool, char, int ja double. Huomaa että vaikka bool on informaatiosisältönä 1 bittiä, vie se muistia kuitenkin yhden tavun, eli 8 bittiä.

Tässä monisteessa suositellaan, että desimaalilukujen talletukseen käytetään aina double-tietotyyppiä (jossain tapauksissa jopa decimal-tyyppiä), vaikka monessa muussa lähteessä float-tietotyyppiä käytetäänkin. Tämä johtuu siitä, että liukuluvut, joina desimaaliluvut tietokoneessa käsitellään, ovat harvoin tarkkoja arvoja tietokoneessa. Itse asiassa ne ovat tarkkoja vain kun ne esittävät jotakin kahden potenssin yhdistelmiä, kuten esimerkiksi 2.0, 7.0, 0.5 tai 0.375.

Useimmiten liukuluvut ovat pelkkiä approksimaatioita oikeasta reaaliluvusta. Esimerkiksi lukua 0.1 ei pystytä tietokoneessa esittämään biteillä tarkasti perustietotyypeillä. Tällöin laskujen määrän kasvaessa lukujen epätarkkuus vain lisääntyy. Tämän takia onkin turvallisempaa käyttää aina double-tietotyyppiä, koska se suuremman bittimääränsä takia pystyy tallettamaan enemmän merkitseviä desimaaleja.

Tietyissä sovelluksissa, joissa mahdollisimman suuri tarkkuus on välttämätön (kuten pankki- tai nanotason fysiikkasovellukset), on suositeltavaa käyttää korkeimpaa mahdollista tarkkuutta tarjoavaa decimal-tyyppiä. Reaalilukujen esityksestä tietokoneessa puhutaan lisää kohdassa 26.6. [VES][KOS]

Seuraavassa esimerkki mitä tapahtuu, kun lasketaan yhteen kaksi liian isoa kokonaislukua tai muuten lisätään muuttujaa liikaa.

# intylivuoto

Tehtävä 7.1

Aja ensin ohjelma muuttamatta. Poista sitten toisesta int-muuttujasta yksi 0. Aja. Mitä tapahtui. Laita takaisin 0. Vaihda tyypit niin, että laskut menevät oikein.

        int luku1 = 1000000000;
        int luku2 = 2000000000;
        int summa = luku1 + luku2;
        byte b = 254;
        b++; b++;
        sbyte sb = 127;
        sb++;

 

Mitä esim. b++ tarkoittaa? Yritin etsiä sitä monisteesta, mutta nämä tässä olevat maininnat b++:sta ovat ainoat.

VL: Kokeileppa etsiä pelkkää ++. Muuttujilla voi olla eri nimiä ja jos jossakin on kerrottu mitä on a++, niin ei erikseen tarvitse sanoa mitä on b++.

13 Sep 16 (edited 17 Sep 23)

Edeltävän taulukon mukaan luku2 pitäisi riittää olla tyyppi int (2,000,000,000 < 2,147,483,647), mutta jos molemmat sekä luku1 että luku2 jättää tyypiksi int, niin double summa = luku1 + luku2 tuottaa virheellisen tuloksen. Sen sijaan int luku1 + double luku2 toimii. Mitä en nyt tajua?? Anteeksi sekava selitys

VL: Kumpikin alkuperäinen int-tyyppinen muuttujat ovat arvoalueessa. Mutta niiden summa ei enää ole. Jos summan tyypin vaihtaa double, niin se ei yksin riitä, koska lasku tehdään "suurimmalla" tyypillä mitä laskussa on, eli int+int on edelleen int. Jos toisen muuttujan tyyppi vaihdetaan double-tyyppiseksi, niin silloin laskut suoritetaan double-tyyppisenä. Sekin auttaisi jos olisi

double summa = 1.0*luku1 + luku2;  // nyt on double + int
10 Sep 23 (edited 10 Sep 23)

7.3 Arvon asettaminen muuttujaan

Muuttujaan asetetaan arvo sijoitusoperaattorilla (assignment operator) =. Lauseita, joilla asetetaan muuttujille arvoja, sanotaan sijoituslauseiksi (assignment statement). On tärkeää huomata, että sijoitus tapahtuu aina oikealta vasemmalle: sijoitettava on yhtäsuuruusmerkin oikealla puolella ja kohde merkin vasemmalla puolella.

# useidenMuuttujienAlustus

Aukaise Tauno. Tee uusi muuttuja, sen nimeksi ika ja arvoksi ikäsi. Tee myös toinen muuttuja, jonka nimeksi tulee opiskeluvuodet ja haluamasi arvo. Katso taunon tekemää koodia. Huomaa kuinka se lisää automaattisesti muuttujan tietotyypin int.

 

Eikö tauno ymmärtä desimaalikukua?

VL: Tauno osaa vain kokonaisluvut.

15 Sep 15 (edited 17 Sep 23)
# arvonasettaminen

Tehtävä 7.2

Esittele muuttujat alla olevia sijoituksia varten niin, että ohjelma kääntyy ja toimii. Esim. int b;




        x = 20.0;
        henkilonIka = 23;
        paino = 80.5;
        pituus = 183.5;
        // 80.5 = paino;  // kokeile, tämä ei toimi!

 

Miksi float ei toimi?

VL: Jos on float-tyyppinen muuttuja vaikka x, niin sille pitäisi sijoittaa float vakio:

x = 20.0f 
15 Sep 15 (edited 17 Sep 23)

Saako tässä asettaa sellaisen formaatin, että desimaalit näkyisivät myös x:lle?
System.Console.WriteLine("x = {0:0.00}", x);

14 Sep 17 (edited 14 Sep 17)

jos paino ja pituus desimaaleisa ei int toimi mutta double toimii, niin miksi tämäohjelma ei ote vastaan doubletietotyyppiä? kai int ja double pitäis pystyä laittamaan samaan koodiin/ohjelmaan niinkun pystyy int ja sbyte teht 7.1.?

VL: tyyppejä ei voi sotkea. Oletko antanut muuttajille tyypit? Jos muuttujan tyyppi on int, ei siihen voi sijoittaa reaalilukua (double). Mutta toisinpäin onnistuu, eli double muuttujalle voi sijoittaa kokonaislukuarvon (koska se on myös reaaliluku samalla).

20 Jan 21 (edited 20 Jan 21)

Huomaa että reaalilukuvakioissa käytetään desimaalipistettä, ei pilkkua.

# arvonasettaminen2

Tehtävä 7.3

Laita muuttujien tyyppi sijoitusriville.

        x = 20.0;
        henkilonIka = 23;
        paino = 80.5;
        pituus = 183.5;
        valovuosiKm = 9460730472580;
        summa = 128;
        merkki = '7';

 

Muuttuja täytyy olla määritelty tietyn tyyppiseksi ennen kuin siihen voi asettaa arvoa. Muuttujaan voi asettaa vain määrittelyssä annetun tietotyypin mukaisia arvoja tai sen kanssa sijoitusyhteensopivia arvoja. Esimerkiksi liukulukutyyppeihin (float ja double) voi sijoittaa myös kokonaislukutyyppisiä arvoja, sillä kokonaisluvut ovat reaalilukujen osajoukko. Alla sijoitamme arvon 4 muuttujaan nimeltä luku2, ja kolmannella rivillä luku2-muuttujan sisältämän arvon (4) muuttujaan, jonka nimi on luku1.

# doubleint
        double luku1;
        int luku2 = 4;
        luku1 = luku2;

 

Toisinpäin tämä ei onnistu: double-tyyppistä arvoa ei voi sijoittaa int-tyyppiseen muuttujaan. Alla oleva koodi ei kääntyisi:

# eikaannyintdouble
// TÄMÄ KOODI EI KÄÄNNY!
        int luku1;
        double luku2 = 4.0;
        luku1 = luku2;

 

Jos edellä oleva sijoitus int <- double halutaan välttämättä tehdä, niin silloin on käytettävä tyypin muunnosta eli typecastia (kokeile edelliseen, vaihda myös 4.0 tilalle 4.8). Tosin tyypinmuunnokseen turvautuminen on aina huono ratkaisu.

       luku1 = (int)luku2;  // pakotetaan luku 2 int-tyyppiseksi. Katkaisu.

Kun decimal-tyyppinen muuttuja alustetaan jollain luvulla, tulee luvun perään (ennen puolipistettä) laittaa m (tai M)-merkki. Samoin float-tyyppisten muuttujien alustuksessa perään laitetaan f (tai F)-merkki ja long-tyyppisten perään L.

# saldojalampotila
        decimal tilinSaldo = 3498.98m;
        float lampotila = -4.8f;

 

Mitä tarkoitetaan alustuksella?

VL: muuttujan voi joko vaan esitellä tai sen voi esitellä ja alustaa sille samalla alkuarvon. Voisi sen sanoa niinkin, että annetaan muuttujalla alkuarvo samalla kun sen tila varataan.

18 Sep 18 (edited 17 Sep 23)

Huomaa, että char-tyyppiseen muuttujaan sijoitetaan arvo laittamalla merkki yksinkertaisten heittomerkkien väliin, esimerkiksi näin.

# ekakirjain
        char ekaKirjain = 'k';

 

Näin sen erottaa myöhemmin käsiteltävästä string-tyyppiseen muuttujaan sijoittamisesta, jossa sijoitettava merkkijono laitetaan (kaksinkertaisten) lainausmerkkien väliin, esimerkiksi seuraavasti.

# nimimuuttuja
        string omaNimi = "Antti-Jussi";

 

Sijoituslause voi sisältää myös monimutkaisiakin lausekkeita, esimerkiksi aritmeettisia operaatioita:

# numeroidenkeskiarvo
        double numeroidenKeskiarvo = (2 + 4 + 1 + 5 + 3 + 2) / 6.0;

 

Sijoituslause voi sisältää myös muuttujia.

# huoneenmitat
        double huoneenPituus = 5.40;
        double huoneenLeveys = huoneenPituus;
        double huoneenAla = huoneenPituus * huoneenLeveys;

 

Mistä C# kaivaa tuohon 5.4 * 5.4 laskutoimitukseen tuon nelosen loppuun? (Ala = 29.16000..004) Johtuuko jotenkin 2:n potensseista? Ja mitenkä tällaisen pystyy väistämään? On tullut itsellä vastaan mm. eräässä taloushallinnon järjestelmässä ja olisi voinut olla pahakin virhe jos olisi mennyt itseltä ohi...

VL: 5.4 ei ole tarkka luku ja kun sillä lähdetään operoimaan tulos ei tarkkene. Vähän sama kuin 0.333333... on 10-järjestelmän laskuissa katkaistava jostakin. Virhe ei johdu kielestä yksin vaan prosessorin tavasta laskea. Tulostuksessa voisi rajata desimaaleja.

12 Sep 22 (edited 12 Sep 22)

Eli sijoitettava voi olla mikä tahansa lauseke, joka tuottaa muuttujalle kelpaavan arvon. Yhdistämällä muuttujia ja operaatoita voi lauseke olla edellisiäkin "monimutkaisempi":

# sijoitusluseke
        double alku = 30;
        double nopeus = 80;
        double matka = alku + (nopeus-10)*5 + System.Math.Sin(0.5);

 

Huomaa edellä, että vaikka paperilla kaavoja kirjoitettaessa ei tarvita kertomerkkiä, niin ohjelmointikielissä käytetään * -merkkiä kertomerkkinä.

C#:ssa täytyy aina asettaa joku arvo muuttujaan ennen sen käyttämistä. Kääntäjä ei käännä koodia, jossa käytetään muuttujaa jolle ei ole asetettu arvoa. Alla oleva ohjelma ei siis kääntyisi.

# tamaeikaanny
// TÄMÄ OHJELMA EI KÄÄNNY!!!!!!!!
public class Esimerkki
{
    public static void Main()
    {
        int ika;
        System.Console.WriteLine(ika);
    }
}

 

Virheilmoitus näyttää tältä:

These are the current permissions for this document; please modify if needed. You can always modify these permissions from the manage page.