Viitetüüpi muutujad CSharp programmeerimiskeeles

From ICO wiki
Jump to navigationJump to search

Eesmärk

Antud õpiobjekti eesmärgiks on tutvustada viitetüüpi muutujatüüpide olemust ning kasutamisvõimalusi C# programmeerimiskeeles.

  • Väärtus- ja viitetüüpi muutujate erinevus
  • Viitetüüpi muutujate kasutamine C# keeles
  • String, kui viitetüüpi muutujatüüp
  • Pärilus
  • Liidesed
  • Koodinäited
  • Praktilised ülesanded

Väärtus- ja viitetüüpi muutujate erinevus

Kui väärtustüüpi muutujatüübid on eelkõige mõeldud konkreetsete andmete hoidmiseks konkreetse skoobi piires, siis viitetüüpi muutujad sisaldavad ainult viidet andmetele ning konkreetset skoopi ei saa määratleda. .Net raamistikus säilib viitetüüpi muutuja seni, kuni sellele on säilinud vähemalt üks viide.

Samas võib viitetüüpi muutuja viidata ka null väärtusele ehk lihtsustatult: viitetüüpi muutuja võib ka mitte viidata konkreetsele andmehulgale.


Näide:


Kass kiisu; //deklareeritakse uus viitetüüpi muutuja kiisu, mis on Kass tüüpi


kiisu = new Kass(); // kiisu hakkab viitama uuele Kass tüüpi objektile


kiisu = null; // kiisu ei viita enam sellele objektile, kui sellele Kass tüüpi objektile rohkem viiteid ei

//ole, siis selle objekti poole enam pöörduda ei saa

Erinevused praktikas

Suurim erinevus väärtustüüpi ja viitetüüpi muutujatüüpide vahel seisnebki selles, et igale väärtustüüpi muutujale vastab konkreetne koht mälus. Samas viitetüüpi muutujale ei pruugi vastata ühtegi kohta mälus (null viide) ning teisalt võib ühele ja samale kohale mälus viidata mitu erinevat viitetüüpi muutujat.


Näide:


Kass kiisu = new Kass(); // Luuakse Kass tüüpi muutuja kiisu ning luuakse uus Kass tüüpi objekt

Kass k6uts = kiisu; //k6uts ja kiisu viitavad nüüd ühele ja samale objektile

k6uts.MuudaNimi(„Miisu“); // muudetakse kassi nime, läbi viite k6uts, muutub ka kiisu nimi.

Nagu näitest näha, kui kaks viitetüüpi muutujat viitavad ühele ja samale objektile, siis muutes üht muutujat muutub ka teine, sest need viitavad ühele ja samale mäluosale.

Erinevused võrdlemisel

Kui väärtustüüpi muutujate korral võrreldakse kahe muutuja võrdlemisel nende konkreetseid väärtuseid, siis viitetüüpi muutujate korral võrreldakse vaikimisi hoopis viiteid: kui muutujad viitavad ühele ja samale mäluosale, siis loetakse need võrdseks, muul juhul aga mitte.


Näide:


Kass kiisu = new Kass(); // Luuakse Kass tüüpi muutuja kiisu ning luuakse uus Kass tüüpi objekt

kiisu.MuudaNimi(„Jassu“);

Kass k6uts = new Kass(); // Luuakse Kass tüüpi muutuja k6uts ning luuakse uus Kass tüüpi objekt

k6uts.MuudaNimi(„Jassu“); // muudetakse kassi nime, kiisu nimi ei muutu.

If(kiisu == k6uts)

Console.WriteLine(„On võrdsed!“);

Antud hetkel teadet „On võrdsed!“ ei tuleks, sest tegemist on küll Kassidega, millistel on üks nimi, aga samas ei ole need üks ja sama Kass!


Viitetüüpi muutujate kasutamine C# keeles

Viitetüüpi muutuja kasutamiseks tuleb muutuja deklareerida ning seejärel initsialiseerida.


Näide:


Kass kiisu; // deklareeritakse Kass tüüpi muutuja kiisu



Kui muutuja deklareerida, kuid jätta initsialiseerimata, siis on tegemist muutujaga, mis ei viita mitte kuhugi (ehk viide on null). Seega ei ole võimalik ka lugeda ega kirjutada muutuja andmeväljadele, sest neid lihtsalt ei ole veel olemas.

Samuti ei ole võimalik pöörduda selle objekti meetodite poole, põhjus sama: objekti lihtsalt ei eksisteeri, on olemas vaid viide objektile.


Näide:


Kass kiisu; // deklareeritakse Kass tüüpi muutuja kiisu

Kiisu.MuudaNimi(„Miisu“); // tekib viga, sest viite näol on tegemist null viitega



Muutuja initsialiseerimiseks tuleb luua uus vastavat tüüpi andmeobjekt mällu ning luua sellele viide. Seejärel on võimalik pöörduda kõigi selle objekti andmeväljade ja meetodite poole.


Näide:


Kass kiisu; // deklareeritakse Kass tüüpi muutuja kiisu

kiisu = new Kass(); // initsialiseeritakse uus Kass tüüpi andmeobjekt ning muutuja kiisu viitab sellele

Kiisu.MuudaNimi(„Miisu“); // viga ei teki



String, kui viitetüüpi muutujatüüp

String on C# programmeerimiskeeles realiseeritud kui tähemärkide jada (massiiv), millel on palju erisusi võrreldes tavaliste viitetüüpi muutujatega, seepärast on siinkohal vajalik selle muutujatüübi täpsem käsitlemine.

String tüüpi muutuja loomiseks on mitmeid võimalusi, lihtsaim neist on see, kui üks string tüüpi väärtus omistatakse teisele.


Näide:


String tervitus = „Tere“; // siin deklareeriti muutuja tervitus, mis initsialiseeriti teise stringiga „Tere“

string



Siit selgubki, et stringi puhul on realiseeritud omistamine erinevalt tavalistest viitetüüpi muutujatest: kui tavaliselt pannakse viitetüüpi muutujate kasutamisel omistamisel lihtsalt muutuja viitama samale objektile, siis siin on võimalik siiski konkreetne väärtus eraldi konstruktori abil ette anda.

Samas on stringil olemas ka tavapärane konstruktor, mida saab ka kasutada.


Näide:


string tere = new String('a',10); //Siinsel juhul luuakse muutuja tere, mis koosneb 10’st ’a’

//tähemärgist

Stringi meetodid

Et leida string muutujatüübi pikkus, võib kasutada omadust Length.


Näide:


string lause = "Tere";

int lausePikkus = lause.Length; // lausePikkus väärtus on 4



Kontrollimaks, kas string sisaldab mõnd fraasi, saab kasutada stringi meetodit Contains, tulem antakse tõeväärtusena.


Näide:


string lause = "Tere ise ka";

bool kasOn = lause.Contains("ise"); // kasOn sisaldab väärust „true“



Kui on vaja stringist välja lõigata üks osa, kasuta stringi meetodit Substring, esimene argument näitab mitmendast tähest alates ja teine arument näitab mitu tähte


Näide:


string lause = "Tere, Juku!";

string lause2 = lause.Substring(6, 4); //Nüüd on lause2 väärtus “Juku”



Kui soovime teada saada mitmendal kohal asub stringis mõni sümbol, tuleb kasutada stringi IndexOf meetodit.


Näide:


string lause = "Tere, Juku!";

int mitmesKoma = lause.IndexOf(','); // vastus on 4, sest loendama hakatakse 0’st.



Stringi stringi lisamiseks on võimalik kasutada meetodit Insert, esimene argument näitab kuhu lisatakse ja teine näitab mida lisatakse.


Näide:


string lause = "Tere, Juku!";

lause = lause.Insert(6, "kallis ");

int lausePikkus =lause.Length;

lause =lause.Insert( lausePikkus-1, ", tore Sind näha");

// Kokku peaks siis nüüd tulema „Tere, kallis Juku, tore Sind näha!“



Starts.With – kontrollib, kas string algab kindla sümboliga

Remove – võimaldab stringist osa eemaldada

Replace – asendab stringis ühe fraasi teisega

Trim – võimaldab eemaldada kindlad sümbolid

PadRight – lisab sümboleid stringi lõppu, kuni string on kindla pikkusega

Kõik string muutujatüübi meetodid on kirjeldatud: http://msdn.microsoft.com/en-us/library/system.string_methods.aspx


Pärilus

(veebistuudiumi õppematerjalist)

Ajapikku on objektorienteeritud programmeerimiskeelte juurde lisatud mitmesuguseid täiustusi ja võimalusi. Pärilus (inheritance) on mingil moel enamike objektorienteeritud keelte küljes olemas. Nii ka end suhteliselt arenenuks keeleks pidava C# juures.

Objektidega toimetamisel ning pärilusel sealhulgas on vähemalt kaks eesmärki. Püütakse lihtsustada koodi kohandamist. Samuti võimaldab pärilus vältida sama tööd tegeva koodi kopeerimist, lubades samu käsklusi kasutada mitmel pool.

Päriluse abil püütakse programmeerimiskeele objektitüüpide omavahelisi suhteid lähendada inimeste igapäevaselt tajutavale. Üheksakümnendate aastate algul ilmunud objektorienteeritud programmeerimist tutvustavas õpikus oli ilus näide: "sebra on nagu hobune, kellel on triibud". Sarnane tuttava kaudu uue tüübi kokkupanek ongi päriluse põhisisu. Midagi lisatakse, midagi muudetakse, vahel harva ka kaotatakse. Ning märkamatult muutubki hobune sebraks või tühi paneel tekstiväljaks.

Nõnda õnnestub olemasolevate (pool)valmis tükkide abil omale sobiv komplekt kokku panna. Teiselt poolt võidakse objektide päriluspuu ehitada ka oludes, kus kõik programmi osad on enese määrata. Sellisel juhul pole küll eesmärk omale üks ja parim tüüp kokku panna, vaid otsitakse ühisosi, mille puhul on võimalik valminud tüübid gruppidesse jaotada ning nende gruppidega vajadusel koos midagi ette võtta. Tüüpilise näitena pole teatripiletit müües vaja teada inimese sugu ja ametit. Küll aga on balletirolli juures mõlemad andmed tähtsad. Tavaelus tundub loomulikuna, et eriomadusi arvestatakse vaid kohtades, kus need on vajalikud ning muul juhul aetakse vaid üldiste tunnustega läbi. Haruharva tuleb eraldi rõhutada, et "meie teatrisse tohivad vaatajaks tulla inimesed sõltumata usutunnistusest ja nahavärvist". Arvuti juures tuleb aga tasemed selgelt välja tuua. Igal keerukuse astmel tuleb määrata, millised oskused ja omadused sinna juurde kuuluvad ning millise rolli jaoks millisel tasemel oskuste komplekti vaja on. Keerukaks kiskunud seletuste kõrvale selgitused näidete abil.

Päriluseta näide

Alustame inimese klassiga, kus igasugune pärilus puudub. Üks klass oma muutuja, konstruktori ja meetodiga ning testprogrammis saab tema võimalusi katsetada.


Näide

using System;

namespace Parilus1{

class Inimene{

protected int vanus;

public Inimene(int uvanus){

vanus=uvanus;

}

public void YtleVanus(){

Console.WriteLine("Minu vanus on "+vanus+" aastat");

}

}

class InimTest{

public static void Main(string[] arg){

Inimene inim1=new Inimene(13);

inim1.YtleVanus();

}

}

}

/*

C:\Projects\oma\naited>Parilus1

Minu vanus on 13 aastat

*/


Alamklass

Edasi juba juures inimese alamklass (subclass) Modell, kel on olemas kõik inimese omadused (ehk siis selle programmi puhul võime oma vanust öelda), kuid juures käsklus enese esitlemiseks. Et esitlemine koosneb vanuse ja ümbermõõdu teatamisest, on see juba esitlemiskäskluse sisse kirjutatud.

Klass Inimene on Modelli suhtes ülemklassiks (superclass, base class, parent class). C# puhul on igal klassil alati täpselt üks ülemklass. Kui muud pole määratud, siis kasutatakse selleks vaikimisi nimeruumi System klassi Object. Muul juhul aga on ülemklass kirjas klassikirjelduse päriluse osas. Nii tekibki klasside puu, mis algab klassist Object.

Kui klassil konstruktor puudub, siis loob kompilaator vaikimisi tühja konstruktori, millel pole parameetreid ja mis ka midagi ei tee. Inimese puhul siis näiteks

public Inimene(){}

Kui aga vähemalt üks programmeerija loodud konstruktor on olemas, siis seda nähtamatut konstruktorit ei tehta. Päriluse puhul kutsutakse alamklassi eksemplari loomisel alati välja ülemklassi konstruktor. Vaikimisi võtab kompilaator selleks ülemklassi parameetritega konstruktori. Kui see aga puudub või soovitakse käivitada mõnda muud, siis tuleb sobiva konstruktori väljakutse alamklassi juures ära märkida. Siin märgitakse näiteks Modelli loomise juures, et Modelli isendi loomise juures tehakse kõigepealt valmis baasklassi (inimese) isend, kellele siis Modelli enese konstruktoris vajalikud lisandused juurde pannakse. Ülemklassi konstruktori määramine on kohe Modelli konstruktori juures. Pärast koolonit olev base(vanus) ütleb, et kasutatagu inimese puhul seda konstruktorit, kus tuleb täisarvuline vanus kohe ette öelda.

public Modell(int vanus, int uymberm66t):base(vanus){

ymberm66t=uymberm66t;

}

Ehkki praegu tegelikult muud võimalust polnudki, tuleb see ikkagi arvutile ette öelda.


Näide
using System;

namespace Parilus2{

class Inimene{

protected int vanus;

public Inimene(int uvanus){

vanus=uvanus;

}

public void YtleVanus(){

Console.WriteLine("Minu vanus on "+vanus+" aastat");

}

}

class Modell:Inimene {

protected int ymberm66t;

public Modell(int vanus, int uymberm66t):base(vanus){

ymberm66t=uymberm66t;

}

public void Esitle(){

YtleVanus();

Console.WriteLine("Mu ymberm66duks on "+ymberm66t+" sentimeetrit");

}

}

class InimTest{

<nowiki>public static void Main(string[] arg){</nowiki>

Modell m=new Modell(20, 90);

m.Esitle();

}

}

}

/*

C:\Projects\oma\naited>Parilus2

Minu vanus on 20 aastat

Mu ymberm66duks on 90 sentimeetrit

*/



Ülekate

Mõnikord ei piirduta omaduste lisamisega - tahetakse olemasolevaid võimalusi ka muuta. Tavanäiteks on kujundid või graafikakomponendid, mis igaüks ennast vastavalt oma omadustele joonistavad. Aga sarnaselt üle kaetavad võivad olla voogudesse andmete kirjutamise käsklused või andmestruktuuridesse andmeid lisavad või sealt eemaldavad käsklused nii, et neid käsklusi kasutav programm võib lihtsalt kasutatavat tükki usaldada, et tal on vastava nimega käsklus olemas ning ta selle soovitud ajal sooritab.

Siin näites on Inimese alamklassiks tehtud Daam, kel kõik muud kirjeldatud omadused hariliku Inimesega sarnased. Kuid vanuse ütlemine käib mõnevõrra teisiti. Et käske saaks rahus üle katta, selleks on Inimese juurde käsu ehk meetodi YtleVanus ette lisatud sõna virtual, Daami vastava meetodi ette aga override. Selliselt on mõlemat tüüpi objektil vanuse ütlemine selge, need lihtsalt käituvad erinevalt.


Näide

using System;

namespace Parilus3{

class Inimene{

protected int vanus;

public Inimene(int uvanus){

vanus=uvanus;

}

public virtual void YtleVanus(){

Console.WriteLine("Minu vanus on "+vanus+" aastat");

}

}

class Daam:Inimene {

public Daam(int vanus):base(vanus){}

public override void YtleVanus(){

Console.WriteLine("Minu vanus on "+(vanus-5)+" aastat");

}

}

class InimTest{

public static void Main(string[] arg){

Inimene inim1=new Inimene(40);

Daam inim2=new Daam(40);

inim1.YtleVanus();

inim2.YtleVanus();

}

}

}


/*

C:\Projects\oma\naited>Parilus3

Minu vanus on 40 aastat

Minu vanus on 35 aastat

*/



Liidesed

(Veebistuudiumi õppematerjalist)

Nagu eespool kirjeldatud, on C# puhul üks ja kindel objektitüüpide pärinemise puu. Igal klassil on oma ülemklass ning juureks on klass Object nimeruumist System. Samas aga mõnegi tüübi puhul on sellest klassist objekte mugav kasutada tunduvalt rohkemates kohtades, kui otsene päriluspuu ette näeb. Nii nagu Ambla kihelkonna Lehtse valla Läpi küla Troska talu perepoeg Karl oli lisaks nendele ametlikele tiitlitele veel küla sepp, puutöömees ning korralik vanapoiss, nii on hea mõnegi klassi puhul programmi ülesehituse lihtsuse huvides pakkuda sinna rohkem rolle kui otsese päriluse järgi neid kokku tuleks. Näiteks Karlal oli leivateenimise mõttes sepatööst kindlasti rohkem kasu kui teatest, et ta Ambla kirikuraamatus kirjas on. Niisamuti on klassidele võimalik juurde panna liideseid, mis annavad õiguse vastava klassi eksemplare kloonida, järjestada või muid kasulikke omadusi lisada. Liideste arv ühe klassi juures ei ole piiratud. Liidesed mõeldi programmeerimiskeelte juurde välja, kuna selgus, et nõnda kirjeldusi määrates ja kokku leppides õnnestub programmeerimisel vigu vähendada.

Järgnevas näites loodi liides IViisakas. I on liidesel ees sõnast Interface. Liidese juurde käib enamasti sisuline seletus selle kohta, millised seda liidest realiseerivad objektid on. Ning lisaks võivad olla mõned käsklused, millele vastavat liidest realiseerivad objektid on võimelised reageerima. Liidese IViisakas puhul valiti selliseks käskluseks Tervita, mis saab omale tekstilise parameetri. Ehk siis eeldatakse, et iga viisakas tegelane mõistab tervitusele vastata juhul, kui talle öeldakse, kellega on tegemist. Nagu näha - Lapse ja Koera puhul need tervitused on erinevad. Kuid nii nagu liides nõuab - nad on olemas. Sinnamaani suudab kompilaator kontrollida. Ülejäänu on juba programmeerija hoolitseda, et kindlaksmääratud nimega käskluse juurde ka vastavale klassile sobiv sisu saaks.

static void TuleSynnipaevale(IViisakas v){

v.Tervita("vanaema");

}

Loodud meetodi juures on näha, et parameetrina võetakse vastu vaid nende klasside eksemplare, kelle puhul liides IViisakas on realiseeritud. Ning igaühel neist palutakse tervitada vanaema. Kuidas keegi sellega hakkama saab, on juba klassi enese sees hoolitsetud.


Näide

using System;

namespace Liides1{

class Inimene{

protected int vanus;

public Inimene(int uvanus){

vanus=uvanus;

}

public virtual void YtleVanus(){

Console.WriteLine("Minu vanus on "+vanus+" aastat");

}

}

interface IViisakas{

void Tervita(String tuttav);

}

class Laps:Inimene, IViisakas {

public Laps(int vanus):base(vanus){}

public void Tervita(String tuttav){

Console.WriteLine("Tere, "+tuttav);

}

}

class Koer: IViisakas{

public void Tervita(String tuttav){

Console.WriteLine("Auh!");

}

}

class InimTest{

static void TuleSynnipaevale(IViisakas v){

v.Tervita("vanaema");

}

public static void Main(string[] arg){

Laps juku=new Laps(6);

juku.YtleVanus();

Koer muki=new Koer();

TuleSynnipaevale(juku);

TuleSynnipaevale(muki);

}

}

}

/*

C:\Projects\oma\naited>Liides1

Minu vanus on 6 aastat

Tere, vanaema

Auh!

*/



Kui mingil põhjusel jäänuks Lapsele või Koerale käsklus Tervita lisamata, siis annaks kompilaator veateate. Sama tekiks ka juhul, kui käskluse tekstis trükiviga tehtaks. Selline kompilaatoripoolne kontroll aitab vead üles


Koodinäited

Klass Kass

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;


namespace Loeng7

{

class Kass

{

private string nimi;

private string toug;

public enum sugu { isane, emane, lihtsaltKass };

private sugu kassiSugu;


public Kass() : this("Jossu","teadmata",sugu.isane)

{}

public Kass(string nimi)

{

//this.nimi = nimi;

TeeKass(nimi, "teadmata", sugu.isane);

}

public void MuudaNimi(string uusNimi)

{

nimi = uusNimi;

}

public Kass(Kass.sugu sugu)

{

//this.nimi = nimi;

TeeKass("Jossu", "teadmata", sugu);

}

public Kass(string nimi, string Toug)

{

//this.nimi = nimi;

TeeKass(nimi, Toug, sugu.lihtsaltKass);

}

public Kass(string nimi, string toug, Kass.sugu kassisugu)

{

TeeKass(nimi, toug, kassisugu);

}


private void TeeKass(string nimi, string toug, Kass.sugu kassisugu)

{

this.nimi = nimi;

this.toug = toug;

this.kassiSugu = kassisugu;

}


public override string ToString()

{

return "Tere, mina olen kass, nimi on mul: " +

nimi + ", tõug: " +toug +" ja sugu: "+ kassiSugu;

}


~Kass()

{ Console.WriteLine("Tapeti kass nimega: " + this.nimi); }


}

class Program

{

static void Main(string[] args)

{


Kass kiisu = new Kass("Peeter");

Console.WriteLine(kiisu.ToString());

Kass k6uts = new Kass("Jasper","angoora", Kass.sugu.isane);

Console.WriteLine(k6uts.ToString());


string tere = new String('a',10);

Console.WriteLine(tere);


}

}

}