Olio-ohjelmoinnin perusteet luento 3: Muuttujista ja funktioista Sami Jantunen LTY/Tietotekniikan osasto
VAROITUS!!!!!! Tällä luennolla käsitellään perusasioita, joilla ei ole sinänsä paljon tekemistä oliopohjaisuuden kanssa! Esimerkit ovat esitetty ei-oliopohjaisella tavalla! Tarkoitus on vain havainnoillistaa perusasioita Toivottavasti osaat soveltaa näitä jatkossa, kun puhumme lisää luokista!
Sisältö Muuttujat Muuttujien määrittely ja tietotyypit Osoittimet (pointer variables) Viittaukset (reference variables) Funktiot määrittely ja tiedon välitys muuttujien näkyvyysalueet oletusparametrit (default parameters) uudelleenmäärittely (overloading) avoimet funktiot (inline) rekursio
Muuttujista… Tietokoneohjelmat manipuloivat tietoa Meillä on siis tarve tallettaa tietoa Muuttujat: Kuvaavat tiettyä muistin muistipaikkaa Sisältävät tietyn tiedon Kun määrittelet muuttujan: Varaat tilaa muistista Annat muuttujalle nimen Teet tämän kerran kutakin muuttujaa kohden
Kuinka luodaan muuttuja Formaatti: ; Muuttujan nimeksi voit valita melkein mitä vaan Esimerkkejä: byte age; float gpa; String name; char letterGrade; Nyt kun muuttujat on määritelty Ei tarvitse määritellä niitä uudestaan Voit käyttää muuttujan nimeä jatkossa tiedon lukemisessa ja kirjoittamisessa agegpa nameletterGrade
C++ varatut sanat Näitä sanoja et kuitenkaan voi käyttää sellaisenaan muuttujien nimissä: auto, bool, break, case, catch, char, class, const, continue, default, do, double, else, enum, extern, float, for, friend, goto, if, inline, int, long, namespace, new, operator, private, protected, public, register, return, short, signed, sizeof, static, struct, switch, template, this, throw, try, typedef, union, unsigned, void, volatile, while
Sallittuja muuttujien nimiä Ei saa olla varattu sana Ei saa alkaa numerolla Ei saa sisältää symboleja kuten Kuten !, ^, &, /, ), tai välilyönti Poikkeukset: alaviiva _, ja dollarimerkki $ Esimerkkejä: byte $theValue; // legal char test_value;// legal double double;// not legal int rum&coke;// not legal bool true or false; // not legal for two reasons!
Muuttujien alustamisesta Tarkoittaa alku-arvon antamista muuttujalle Arvo annetaan = -operaattoria käyttäen Kopioi oikealla olevan tiedon vasemmalla määriteltyyn muuttujaan. Muuttujan tyyppi ja tiedon formaatti pitää olla yhteensopiva Alustettava tieto voi olla vakio tai toinen muuttuja! Esimerkkejä: int age; age = 15; char letterGrade = ‘B’; char yourGrade = letterGrade;
Tietotyypeistä Tietotyyppi kertoo mikälaista tietoa muuttuja voi pitää sisällään Jotkin tietotyypit ovat “sisäänrakennettuja” ohjelmointikieleen Voimme myös määritellä tietotyyppejä itse (puhutaan tästä lisää myöhemmin) Erilaisia tietotyypin muotoja: Yksinkertaiset tietotyypit Monimutkaiset tietotyypit (Valmistettu yksinkertaisista tietotyypeistä)
Yksinkertaiset tietotyypit Sisäänrakennettuja Numerot: short (melko pienet numerot) int (suuret numerot) long (TOSI suuret numero)
Yksinkertaiset tietotyypit Desimaaliluvut: float (Ei niin tarkka kuin double ) double (Aika tarkka) Tarkkuudella tarkoitetaan kuinka monta lukua tulee desimaalin jälkeen Muita: char (pitää sisällään merkkejä kuten ‘a’, ‘A’, ‘1’, ‘ ‘) bool (pitää sisällään totuusarvon true tai false)
No kuinka iso se on?: sizeof( ) sizeof( ) kertoo kuinka paljon tietotyyppi vie tavuja Esimerkki: int myInt; cout << sizeof (myInt) << endl;
Etumerkki vai ei? C++ -kielessä voi määritellä merkki- ja kokonaislukumuuttujat etumerkittömäksi. Etumerkillisille ja etumerkittömille kokonaisluvuille varataan yhtä paljon muistitilaa Luvut ovat oletusarvoisesti etumerkillisiä Etumerkittömän edut: Etumerkittömät tietotyypit voivat siis sisältää suuremman joukon positiivisia lukuja
Perustietotyypeistä Se kuinka monta tavua kukin tietotyyppi vie tilaa on riippuvainen kääntäjästä ja käyttöjärjestelmästä! Tässä kuitenkin esimerkkejä: TyyppiKoko (Tavua)Arvoalue unsigned int20 …65535 int … unsigned long40 … long … char10 … 255
Miksi koolla on väliä Eri tietotyypit vie eri määrän tavuja muistista Et voi laittaa suuritilaisemman muuttujan sisältöä pienenpään muistitilaan: short s = 5;long l = 5; long l = s;short s = l; long short long short long short
Tyyppimuunnos (Type Casting) Tapahtuu kun pistät jotain enemmän tilaa vievää tietoa pienempään muistipaikkaan Tiedon tarkkuustaso saattaa heikentyä Formaatti: = ( ) ; Esimerkki: long myLong = 17; short myShort = (short)myLong; // We are squeezing myLong into myShort!
short vai long? Jos on pienikin mahdollisuus, että muuttujaan tulee suurempi arvo kun tietotyyppiin mahtuu, niin valitse isompi tietotyyppi.
Etumerkittömien kokonaislukujen ylivuoto Sama ilmiö kun auton matkamittarissa: Kun on maksimi saavutettu, niin aloitetaan uudestaan nollasta Esim: unsigned int myInt = 65535; cout<< myInt << endl; myInt++; cout<< myInt << endl; myInt++; cout<<myInt<<endl; Mitä tapahtuu?? Tuloste:
Etumerkillisten kokonaislukujen ylivuoto Suurinta positiivista arvoa seuraa negatiivisin mahdollinen arvo Esim: int myInt = 32767; cout<< myInt << endl; myInt++; cout<< myInt << endl; myInt++; cout<<myInt<<endl; Mitä tapahtuu?? Tuloste:
unsigned short int luku1; unsigned short int luku2; unsigned short int luku3; Liian työlästä? Altis kirjoitusvirheille Ratkaisuehdotuksia: unsigned short int luku1, luku2, luku3; Peitenimi (Typedef)
Peitenimi (Typedef) #include typedef unsigned short int USHORT; void main() { USHORT Leveys = 5; USHORT Korkeus;...
Yhteenveto Muuttujat ovat nimettyjä soluja tietokoneen muistissa On olemassa erilaisia tietotyyppejä säilyttämään erilaista tietoa Eri tietotyypit vievät eri määrän tilaa Kaikki muuttujat määritellään samanlaisen formaatin mukaisesti
Vakioista Miksi vakioita? -Esimerkki Oletetaan, että koulussa yhdelle luokalle hyväksytään 15 oppilasta Oletetaan myös, että koulun oppilasmäärä saadaan kerrottuna luokkien lukumäärä luokan koon kanssa: int luokkienLukumaara = 20; int oppilasmaara = 15 * luokkienLukumaara; Mikä vikana?
Miksi vakioita? Emme halua kovakoodata arvoja Työlästä muuttaa Emme myöskään halua laittaa pysyviä arvoja muuttujiksi Joku saattaa vahingossa muuttaa niiden arvoja
#define Hieman vanhentunut tapa määritellä vakio Esim: #define oppilaitaLuokassa 15 Ei ota mitään kantaa tietotyyppiin Esikäsittelijä pelkästään korvaa tekstin toisella Kääntäjä siis näkee oppilaitaLuokassa-tekstin sijasta luvun 15
Const Suositeltavampi tapa #define sijasta: const unsigned short int oppilaitaLuokassa = 15; Kääntäjä tietää minkä tyyppinen vakio on. osaa valvoa, että vakiota käytetään oikein Vakiota ei voi muuttaa ohjelman suorituksen aikana. Jos vakio halutaan muuttaa, tulee ohjelma kääntää uudestaan.
Luetellut vakiot Muuttujatyyppi, johon voi sijoittaa vain etukäteen määriteltyjä arvoja. Luetellun vakion määrittely: enum VARI {PUNAINEN, SININEN, VIHREA, VALKOINEN, MUSTA}; Määrittelee sanan VARI Määrittelee vakiot PUNAINEN (arvo 0), SININEN (arvo 1),… Kullekin vakiolle on mahdollista määritellä arvo erikseen: enum VARI {PUNAINEN=100, SININEN, VIHREA=500, VALKOINEN=501, MUSTA=700}; Lause tuottaa vakiot PUNAINEN=100, SININEN=101, VIHREA=500, VALKOINEN=501 ja MUSTA=700 Luetellut vakiot ovat käteviä kun käsitellään rajallista alijoukkoa (värejä, viikonpäiviä, ym…) Ohjelmakoodin luettavuus paranee
Luetellut vakiot käyttöesimerkki #include void main() { enum Paivat {Sunnuntai, Maanantai, Tiistai, Keskiviikko, Torstai, Perjantai, Lauantai }; Paivat vapaaPaiva; cout << “Minkä päivän haluat pitää vapaata (0-6)? “; cin >> vapaaPaiva; if (vapaaPaiva == Sunnuntai || vapaaPaiva == Lauantai) cout << “Viikonloppu on vapaata muutenkin!” << endl; else cout << “OK. pannaan asia muistiin” << endl; }
Lisäys- ja vähennysoperaattorit ++ korottaa muuttujan arvoa yhdelläint counter = 5; counter++;counter = counter +1; += korottaa muuttujan arvoa annetulla määrälläint counter = 5; counter += 3;counter =counter + 3; Sama kuin Huomaa: Myös --, -=, %=, /= versiot
Lisäys- ja vähennysoperaattorit Voidaan toteuttaa joko etuliitteenä (esim. ++counter; ) tai jälkiliitteenä (esim. counter++; ) Lopputulos sama paitsi jos muuttujaa käytetään samassa tilanteessa vielä arvon muutoksen lisäksi johonkin muuhun
Etu- ja jälkiliitelisäys esimerkki … void main() { int minunIka=40; int sinunIka=40; cout << “minun ikä on:” << minunIka++ << endl; cout << “sinun ikä on:” << ++sinunIka << endl; … } Mitä tapahtuu?? Tuloste: minun ikä on:40 Sinun ikä on:41
Osoittimista Oletteko nähneet tällaista? Ohjelmoijan painajainen! Johtuu osoittimen väärinkäytöstä
Tarkastellaan hieman muistia! Muistin osoite sinisellä, Arvot mustalla, Muuttujan nimi punaisella
Määritetään int-muuttuja int myInt; myInt
Mitä tuli tehtyä? Määrittelemällä int- muuttujan varasimme juuri ja juuri tarpeeksi muistitilaa int- luvulle Ei hajuakaan missä päin muistia muuttujamme sijaitsee Tietokone valitsee muistiosoitteen “satunnaisesti” Mikä arvo on varaamassamme muistipaikassa? Voimmeko tulostaa muistipaikan arvon? Tulostaisimme –4717! (roskaa)
Kopioidaan 42 muuttujan osoittamaan muistipaikkaan myInt = 42; myInt
Osoittimet (Pointers) Antaa mahdollisuuden päästä käsiksi muistipaikkaan, missä tieto sijaitsee Osoitin määritellään seuraavanlaisella formaatilla: * ; Example: int *ptr;
Määritetään int osoitin int *ptr; ptr myInt
Mitä nyt tuli tehtyä? Loimme uuden muuttujan joka tulee osoittamaan int tietoon Huomaa, että osoitin ei vielä osoita myInt sisältämään tietoon Mitä jos yritämme tulostaa osoittimen sisällön?
cout << ptr; (ptr:n arvoksi tulostuu 98131) ptr myInt
Ongelma Kuinka voimme saada myInt osoitteen, jotta ptr voi osoittaa sinne? Muista, että voimme vielä käyttää myInt:ä suoraankin int someInt = myInt; Tarvitsisimme todella keinon tallettaa myInt-muuttujan osoite Meidän ei tarvitse tallettaa myInt muuttujan arvoa osoittimeen (vain osoite)
& operaattori & operaattorin avulla päästään käsiksi muuttujan muistiosoitteeseen Mitä seuraavanlainen komentorivi tulostaisi näytölle? cout << &myInt << endl;
Mitä tapahtuisi? cout << &myInt; ptr myInt
Laitetaan osoitin osoittamaan… Nyt pitäisi laittaa “ptr” osoittamaan myInt -muuttujan sisältämään tietoon ptr = &myInt; ptr on osoitin, joten se olettaa että sen arvoksi annetaan osoite & -operaattorilla saamme myInt muuttujan osoitteen ja kopioimme osoitetiedon ptr-osoittimen arvoksi
Ennen ptr myInt
Jälkeen ptr = &myInt; ptr myInt
Mitäs tämä tekisi? ptr = myInt; ptr myInt
Hupsista! ptr = myInt; ptr myInt
Osoittimen sisältö Kuinka osoittimen sisältö saadan luettua? Käytetään *-operaattoria Esimerkki: cout << *ptr << endl; //Tulostaa ptr-osoittimen //osoittaman muistipaikan sisällön!
Seurataan osoitinta ja tulostetaan cout << *ptr << endl; ptr myInt
Toinen esimerkki Muistin osoite sinisellä, Arvot mustalla, Muuttujan nimi punaisella
Määritetään osoitin int *ptr; ptr
Mitä tapahtuu? cout << *ptr << endl; ptr
Hupsista!
Parametrin välittäminen funktioon lähettää kopion alkuperäisestä muuttujan arvosta. Funktio ei siis pysty manipuloimaan pääohjelmassa olevan muuttujan arvoa! void cannotChange (int x) { x = 6; cout << x << endl; } void main ( ) { int myInt = 17; cannotChange (myInt); cout << myInt << endl; } Miksi käyttäisin osoittimia?
void cannotChange (int x) { x = 6; cout << x << endl; } void main ( ) { int myInt = 17; cannotChange (myInt); cout << myInt << endl; } Märitetään myInt myInt
Kutsutaan funktiota myInt void cannotChange (int x) { x = 6; cout << x << endl; } void main ( ) { int myInt = 17; cannotChange (myInt); cout << myInt << endl; }
Kopiointi tapahtuu tässä! myInt x void cannotChange (int x) { x = 6; cout << x << endl; } void main ( ) { int myInt = 17; cannotChange (myInt); cout << myInt << endl; }
Ja muutos kohdistuu vain paikalliseen kopioon myInt x void cannotChange (int x) { x = 6; cout << x << endl; } void main ( ) { int myInt = 17; cannotChange (myInt); cout << myInt << endl; }
Tulostetaan kopion arvo (6) myInt x void cannotChange (int x) { x = 6; cout << x << endl; } void main ( ) { int myInt = 17; cannotChange (myInt); cout << myInt << endl; }
Palataan pääfunktioon (tulostetaan 17) (x on tuhottu, tilalle jäi vain roskaa…) myInt void cannotChange (int x) { x = 6; cout << x << endl; } void main ( ) { int myInt = 17; cannotChange (myInt); cout << myInt << endl; }
void canChange (int* x) { *x = 6; cout << *x << endl; } void main ( ) { int myInt = 17; int* ptr = &myInt; canChange (ptr); cout << myInt << endl; } Ja sitten sama osoittimilla
Luodaan myInt myInt void canChange (int* x) { *x = 6; cout << *x << endl; } void main ( ) { int myInt = 17; int* ptr = &myInt; canChange (ptr); cout << myInt << endl; }
Luodaan ptr osoittamaan myInt:n muistipaikkaan myInt ptr void canChange (int* x) { *x = 6; cout << *x << endl; } void main ( ) { int myInt = 17; int* ptr = &myInt; canChange (ptr); cout << myInt << endl; }
Lähetetään ptr:n kopio funktioon myInt ptr void canChange (int* x) { *x = 6; cout << *x << endl; } void main ( ) { int myInt = 17; int* ptr = &myInt; canChange (ptr); cout << myInt << endl; }
Lähetetään ptr:n kopio funktioon myInt ptr x void canChange (int* x) { *x = 6; cout << *x << endl; } void main ( ) { int myInt = 17; int* ptr = &myInt; canChange (ptr); cout << myInt << endl; }
Muutetaan x:n osoittamaa muistipaikkaa myInt ptr x void canChange (int* x) { *x = 6; cout << *x << endl; } void main ( ) { int myInt = 17; int* ptr = &myInt; canChange (ptr); cout << myInt << endl; }
Muutetaan x:n osoittamaa muistipaikkaa myInt ptr x void canChange (int* x) { *x = 6; cout << *x << endl; } void main ( ) { int myInt = 17; int* ptr = &myInt; canChange (ptr); cout << myInt << endl; }
Tulostetaan x:n osoittaman muistipaikan sisältö (6) myInt ptr x void canChange (int* x) { *x = 6; cout << *x << endl; } void main ( ) { int myInt = 17; int* ptr = &myInt; canChange (ptr); cout << myInt << endl; }
Huomaa pääohjelmassa, että muuttujan arvo on muuttunut (myöskin 6) myInt ptr x void canChange (int* x) { *x = 6; cout << *x << endl; } void main ( ) { int myInt = 17; int* ptr = &myInt; canChange (ptr); cout << myInt << endl; }
Mielenkiintoinen juttu Seuraavat lausekkeet tulostavat saman asian! cout << *ptr << endl; cout << myInt << endl Samoin nämä! cout << ptr << endl; cout << &myInt << endl; MIKSI? void canChange (int* x) { *x = 6; cout << *x << endl; } void main ( ) { int myInt = 17; int* ptr = &myInt; canChange (ptr); cout << myInt << endl; }
Vakio-osoittimet Varattu sana const voidaan kirjoittaa osoitinmuuttujan määrittelyyn ennen tietotyyppiä, tietotyypin jälkeen tai molempiin paikkoihin. Esim: const int * pYksi; int * const pKaksi; const int * const pKolme;
Vakio-osoittimet esimerkit pYksi osoittaa int- tyyppiseen vakioon. Osoittimen osoittamaa tietoa ei voi muuttaa mutta osoittimen arvoa voi muuttaa const int * pYksi; pKaksi on osoitinvakio, joka osoittaa int-tyyppiseen tietoon. Osoittimen osoittamaa int- tietoa voi muuttaa mutta osoittimen arvoa ei voi muuttaa int * const pKaksi; pKolme on osoitinvakio int –tyyppiseen vakioon. Psoittimen osoittamaa tietoa ei voi muuttaa eikä myöskään osoittimen arvoa voi muuttaa const int * const pKolme;
Osoittimet yhteenveto Sinun tulee ymmärtää miten muisti toimii, jotta ymmärtäisit osoittimien toiminnan &-operaattori on osoittimiin liittyvien salaisuuksien äiti Osoittimien sisällön saat * -operaattorilla Voit manipuloida funktioissa pääohjelman muuttujien arvoja välittämällä funktiokutsussa osoittimia.
Viittausmuuttujat (Reference variables) Osoittimia tarvitaan, mutta ne ovat usein pahojen ohjelmointivirheiden lähde. Viittausmuuttujat: osoittaa osoittimen tapaan olemassa olevaan tietoon. on osoittimia turvallisempi. Kääntäjä tekee viittausmuuttujalle enemmän tarkistuksia on osoittimia selkeämpi käyttää Viittausmuuttuja on kuin “peitenimi” tai “synonyymi” olemassa olevalle tiedolle
Viittausmuuttujat Käyttöohje Muodostetaan &-operaattorilla seuraavan formaatin mukaisesti: & = viitattavaTieto; ESIM: int &jokuViittaus = jokuInt; Viittaus on AINA alustettava johonkin tietoon ennen käyttöä! Viittausta ei voi muuttaa viittaamaan toiseen kohteeseen!
Viittausmuuttuja käyttöesimerkki Mitä tapahtuu?? void main () { int intYksi; int &jokuViittaus = intYksi; intYksi = 5; cout << ”intYksi: ” << intYksi << endl; cout << ”jokuViittaus: ” << jokuViittaus << endl; jokuViittaus = 7; cout << ”intYksi: ” << intYksi << endl; cout << ”jokuViittaus: ” << jokuViittaus << endl; } Tulostus: intYksi: 5 jokuViittaus: 5 intYksi: 7 jokuViittaus: 7
Tiedon välitys funktion parametreissä Funktioita koskee kaksi rajoitusta parametrit ovat kopioita alkuperäisistä tiedoista (muistathan osoitin-esimerkin) funktio voi palauttaa vain yhden paluuarvon Rajoitukset voi kiertää joko osoittimella tai viittauksella
Palataan osoitinesimerkin pariin Vaikeaselkoinen (*-operaattorin jatkuva käyttö) void canChange (int* x) { *x = 6; cout << *x << endl; } void main ( ) { int myInt = 17; int* ptr = &myInt; canChange (ptr); cout << myInt << endl; }
Ja sitten sama viittauksia käyttäen void canChange (int &x) { x = 6; cout << x << endl; } void main( ) { int myInt = 17; canChange (myInt); cout << myInt << endl; } Selkeämpää vai mitä????
Useampi paluuarvo funktiolle? Viittaustoteutus int kertoja (int luku, int &nelio, int &kuutio ) { nelio = luku*luku; kuutio = luku*luku*luku; return 1; //onnistui } void main( ) { int luku, nelio, kuutio, tulos; tulos = kertoja(luku, nelio, kuutio); } Miten tämä toteutettaisiin osoittimilla????
Sama osoitintoteutuksena int kertoja (int luku, int *nelio, int *kuutio ) { *nelio = luku*luku; *kuutio = luku*luku*luku; return 1; //onnistui } void main( ) { int luku, nelio, kuutio, tulos; tulos = kertoja(luku, &nelio, &kuutio); }
Parametrien välittäminen kopioina vs. viittauksena -Tehokkuusnäkökulma Parametrien välittämisestä kopioina seuraa taustalla seuraavia tehtäviä: kutsutaan välitettävän tyypin kopiorakentajaa sijoitetaan kopio muistiin kutsutaan välitettävän tyypin purkajaa kutsutaan paluutyypin kopiorakentajaa kutsutaan paluutyypin purkajaa Jos välitettävät parametrit ovat suurikokoisia olioita, kopiointi syö muistia ja suorituskykyä suositaan osoittimia ja viittauksia? (Entäpä turvallisuus ja olion eheys?)
Vakioviittaus Voidaan tehokkaasti välittää funktion parametreina olioita ja olla samalla varma siitä, että oliota ei muuteta
Milloin käytetään viittauksia? Milloin osoittimia? Suosi viittauksia: ulkoasultaan selkeämpiä turvallisempia Viittausta ei voi muuttaa! Käytä osoitinta jos: on tarve osoittaa ensin yhteen tietoon ja sitten toiseen on tarve väliillä osoittaa tietoon, mutta välillä ei
Missä mennään? Muuttujat Muuttujien määrittely ja tietotyypit Osoittimet (pointer variables) Viittaukset (reference variables) Funktiot määrittely ja tiedon välitys muuttujien näkyvyysalueet oletusparametrit (default parameters) uudelleenmäärittely (overloading) avoimet funktiot (inline) rekursio
Termiologiaa Funktio on looginen ryhmä komentoja Uudelleenkäytettävää koodia Kirjoita kerran Kutsu niin monta kertaa kuin haluat Edut Uudelleenkäytettävää Helpottaa abstraktiota Vähentää monimutkaisuutta Vähentää koodin pituutta
Miksi funktioita? (näetkö mitään toistuvaa?) double average; int userNum1, userNum2; cout << “Please enter the 2 numbers” << endl; cin >> userNum1; cin >> userNum2; average = (userNum1 + userNum2) / 2; … // a lot of other code cin >> userNum1; cin >> userNum2; average = (userNum1 + userNum2) / 2; … cin >> userNum1; cin >> userNum2; average = (userNum1 + userNum2) / 2;
Miksi funktioita? (näetkö mitään toistuvaa?) Samaa koodia double average; int userNum1, userNum2; cout << “Please enter the 2 numbers” << endl; cin >> userNum1; cin >> userNum2; average = (userNum1 + userNum2) / 2; … // a lot of other code cin >> userNum1; cin >> userNum2; average = (userNum1 + userNum2) / 2; … cin >> userNum1; cin >> userNum2; average = (userNum1 + userNum2) / 2;
Siirretään toistuva koodi omaksi funktioksi cin >> userNum1; cin >> userNum2; average = (userNum1 + userNum2) / 2; Tee tästä oma funktio double average; int userNum1, userNum2; cout << “Please enter the 2 numbers” << endl; cin >> userNum1; cin >> userNum2; average = (userNum1 + userNum2) / 2; … // a lot of other code cin >> userNum1; cin >> userNum2; average = (userNum1 + userNum2) / 2; … cin >> userNum1; cin >> userNum2; average = (userNum1 + userNum2) / 2;
Ja nimetään sitten funktio cin >> userNum1; cin >> userNum2; average = (userNum1 + userNum2) / 2; double calculateAverage( ) double average; int userNum1, userNum2; cout << “Please enter the 2 numbers” << endl; cin >> userNum1; cin >> userNum2; average = (userNum1 + userNum2) / 2; … // a lot of other code cin >> userNum1; cin >> userNum2; average = (userNum1 + userNum2) / 2; … cin >> userNum1; cin >> userNum2; average = (userNum1 + userNum2) / 2;
Ja kutsutaan funktiota (sen sijaan että duplikoisimme komentoja) double average; int userNum1, userNum2; cout << “Please enter the 2 numbers” << endl; average = calculateAverage(); … // a lot of other code average = calculateAverage(); … average = calculateAverage(); cin >> userNum1; cin >> userNum2; average = (userNum1 + userNum2) / 2; average = calculateAverage();
Mitä taas tuli tehtyä? Kirjoitimme koodin kerran, mutta käytimme sitä useasti Lyhensimme rivien määrää koodissamme Yksinkertaistimme koodia Tämä on proseduraalista abstraktiota (muistathan ensimmäisen luennon?)
Muuttujien näkyvyysalue (Scope) Näkyvyysalue- kuka näkee ja mitä Funktion sisällä määritetyt muuttujat näkyvät vain funktion sisäpuolella! Pitää olla keino lähettää informaatiota funktiolle Pitää olla keino saada tietoa funktiosta Esimerkki: function1 ei näe myInt:iä function1function2 char myChar; int myInt;
Globaalit muuttujat Funktioiden ulkopuolella määritellyt muuttujat (globaalit muuttujat) näkyvät kaikissa funktioissa (myös main:ssa) Paikallinen muuttuja, joka on saman niminen kuin globaali muuttuja ei vaikuta globaalin muuttujan arvoon itse asiassa paikallinen muuttuja “piilottaa” saman nimisen globaalin muuttujan #include //globaaleja muuttujia int x =5, y =7; void main() { cout << “Y on ” << y << endl; omaFunktio(); cout << “Y on ” << y << endl; } void omaFunktio() { y = 10; }
Varoituksen sana globaaleista muuttujista Globaalien muuttujien käyttö on vaarallista! Kukaan ei pidä kirjaa niiden käytöstä Yksi käyttää yhdellä tavalla, toinen toisella altis virheille, mitä on vaikea paikallistaa Globaalien muuttujien käyttöön ei yleensä ole tarvetta Malta vielä hetki. Seuraavilla luennoilla tulee lisää (=oliopohjaisia) ratkaisuja muuttujien näkyvyysongelmaan
Funktion malli unsigned short int LaskeAla (int pituus, int leveys) Paluuarvon tyyppiFunktion nimiParametrien nimet Parametrien tyypit
Paluutyyppi Funktiolla on mahdollisuus palauttaa kutsuvaan koodiin tietoa Jos funktio ei palauta mitään, sen paluutyyppi on void Jos funktio palauttaa tietoa, sen paluutyyppi on samaa tyyppiä kun palautettavan tiedon tyyppi Esimerkkejä paluutyypeistä: int char bool
Tehtävä: Valitse paluutyypit Funktion nimi average getLetterGrade areYouAsleep getGPA printMenu getStudentName Paluutyyppi double or float char boolean double or float void String
Funktion nimestä Samoja sääntöjä kuin muuttujienkin nimeämisessä. Voi olla melkein mitä tahansa paitsi: varattu sana Ei voi alkaa numerolla ei voi sisältää symboloja paitsi _ ja $ Funktion nimi tulisi alkaa pienellä kirjaimella Jos funktion nimessä on useita sanoja aloita ne (ensimmäistä lukuunottamatta) isolla kirjaimella. Esimerkki: thisIsAnExample Tyypillisesti yrityksillä on omat tyylioppaat, joissa on määritelty nimeyskäytännöt tarkemmin!
Esimerkki #include void main ( ) { int num1, num2, num3; double result1, result2; num1 = 5; num2 = 7; num3 = 4; result1 = average (num1, num2); result2 = average (num3, num1); } double average (int x, int y) { return ( (x+y) / 2); } x y // Note: the average function is currently inactive
Esimerkki (määritä muuttujat) num1 num2num3 ? ?? #include void main ( ) { int num1, num2, num3; double result1, result2; num1 = 5; num2 = 7; num3 = 4; result1 = average (num1, num2); result2 = average (num3, num1); } double average (int x, int y) { return ( (x+y) / 2); } x y // Note: the average function is currently inactive
Esimerkki (määritä muuttujat) num1 num2num3 result1result2 ? ?? ?? #include void main ( ) { int num1, num2, num3; double result1, result2; num1 = 5; num2 = 7; num3 = 4; result1 = average (num1, num2); result2 = average (num3, num1); } double average (int x, int y) { return ( (x+y) / 2); } x y // Note: the average function is currently inactive
Esimerkki num1 num2num3 result1result ?? #include void main ( ) { int num1, num2, num3; double result1, result2; num1 = 5; num2 = 7; num3 = 4; result1 = average (num1, num2); result2 = average (num3, num1); } double average (int x, int y) { return ( (x+y) / 2); } x y // Note: the average function is currently inactive
Example (call the function) num1 num2num3 result1result ?? HERÄTYS! #include void main ( ) { int num1, num2, num3; double result1, result2; num1 = 5; num2 = 7; num3 = 4; result1 = average (num1, num2); result2 = average (num3, num1); } double average (int x, int y) { return ( (x+y) / 2); } x y
Example (data passing) num1 num2num3 result1result ?? #include void main ( ) { int num1, num2, num3; double result1, result2; num1 = 5; num2 = 7; num3 = 4; result1 = average (num1, num2); result2 = average (num3, num1); } double average (int x, int y) { return ( (x+y) / 2); } 57 x y
Esimerkki (pääohjelma vaipuu uneen) xy num1 num2num3 result1result ?? 57 // Funktio on nyt aktiivinen #include void main ( ) { int num1, num2, num3; double result1, result2; num1 = 5; num2 = 7; num3 = 4; result1 = average (num1, num2); result2 = average (num3, num1); } double average (int x, int y) { return ( (x+y) / 2); }
Esimerkki (funktio hoitaa hommaansa) xy num1 num2num3 result1result ?? 57 // is 12; 12 / 2 == 6 #include void main ( ) { int num1, num2, num3; double result1, result2; num1 = 5; num2 = 7; num3 = 4; result1 = average (num1, num2); result2 = average (num3, num1); } double average (int x, int y) { return ( (x+y) / 2); }
Esimerkki (Funktio on valmis ja palauttaa tuloksen) xy num1 num2num3 result1result ?? 57 // is 12; 12 / 2 == 6 6 #include void main ( ) { int num1, num2, num3; double result1, result2; num1 = 5; num2 = 7; num3 = 4; result1 = average (num1, num2); result2 = average (num3, num1); } double average (int x, int y) { return ( (x+y) / 2); }
Esimerkki (pääohjelma herää; funktio nukkuu) x y num1 num2num3 result1result ? funktio nukkuu #include void main ( ) { int num1, num2, num3; double result1, result2; num1 = 5; num2 = 7; num3 = 4; result1 = average (num1, num2); result2 = average (num3, num1); } double average (int x, int y) { return ( (x+y) / 2); }
Sääntöjä funktioista Et voi määritellä uutta funktiota toisen funktion sisältä Funktiot ovat yleensä luokan sisällä (tästä puhutaan seuraavalla luennolla) Sijoita funktioiden toteutus kutsuvan koodin yläpuolelle Muussa tapauksessa esittele funktio ennen käyttöä funktion prototyypillä Funktiot eivät näe toistensa muuttujia
Funktion prototyyppi (jos varsinainen toteutus on vasta myöhemmin) #include void printNum (int myNum); void main ( ) { int num1, num2, num3; num1 = 5; num2 = 7; num3 = 4; printNum (num1); } void printNum (int myNum) { // Remember, printNum can’t see num1, num2 or num3! cout << myNum << endl; } Here’s the prototype
Työlään tuntuinen funktiokutsu? -Esimerkki void main() { //Tama ohjelma piirtaa tikkataulun piirraYmpyra(10,150,200); piirraYmpyra(20,150,200); piirraYmpyra(30,150,200);... } void piirraYmpyra(int sade, int x, int y) {...; } Ärsyttääkö mikään???
Oletusparametrien käyttö funktiossa (default parameters) void main() { //Tama ohjelma piirtaa tikkataulun piirraYmpyra(10); piirraYmpyra(20); piirraYmpyra(30);... } void piirraYmpyra(int sade, int x = 150, int y = 200) {...; }
Oletusparametrit sääntöjä Funktion parametreille voi antaa oletusarvot Oletusarvot annetaan funktion toteutuksen esittelyssä. Funktion määrittely on edelleen samanlainen void piirraYmpyra (int sade, int x, int y); void piirraYmpyra(int sade, int x = 150, int y = 200) { Funktiota kutsuttaessa parametrin arvon voi jättää kirjoittamatta jos parametrille on määritelty oletusarvo Funktio käyttää tällöin oletusarvoa Jos parametrillä ei ole oletusarvoa, niin sitä edeltävillekkään parametreille ei voi antaa oletusarvoa määritä pakolliset parametrit ensin ja oletusarvoiset parametrit viimeiseksi
Funktioiden uudelleenmäärittely (overloading) void main() { string omaString; int omaInt; tulosta(omaString); tulosta(omaInt); } void tulosta( string x) { cout << “tulostetaan string: “ << x << endl; } void tulosta( int x) { cout << “tulostetaan int: “ << x << endl; }
Funktioiden uudelleenmäärittely (overloading) Saman nimisestä funktiosta voi tehdä eri versioita Versiot eroavat toisistaan parametrien määrän tai tyyppien suhteen Paluuarvot voivat olla samaa tai eri tyyppiä Jos ainoana erona on paluutyyppi, saadaan käännösvirhe Kääntäjä osaa valita oikean funktion kutsussa käytettyjen parametrien perusteella. Käyttöesimerkki: keskiarvon laskeminen käyttäen erityyppisiä parametreja (sen sijaan että toteuttaisimme IntKeskiarvo(), DoubleKeskiarvo(),…
Avoimet funktiot (Inline function) Funktioon hyppäämiseen ja sieltä palaamiseen kuluu vähän aikaa Jos funktio on hyvin pieni (2-3) riviä funktio ei ole kovin “kustannustehokas” Jos funktion esittelyyn liittää varatun sanan inline, kääntäjä kopioi funktion koko sisällön niihin kohtiin missä funktiota kutsutaan Avoimen funktion kääntöpuoli: koska funktio kopioidaan jokaiseen kutsukohtaan, ohjelmatiedoston koko kasvaa.
Avoin Funktio (inline function) Esimerkki inline int Tuplaa(int) void main() { int luku; cout << “syötä luku: “<< endl; cin >> luku; luku=Tuplaa(luku)...; osta(omaInt); } inline int Tuplaa( int x ) { return 2*x; }
Rekursiiviset Funktiot Funktio voi kutsua itseään (rekursio) Käytetään yleensä tilanteissa, missä funktion saamia tietoja käsitellään samalla tavalla kuin funktion tuottamaa tulostakin. Pidä huolta, että rekursio loppuu jossain pisteessä!
Yhteenveto- Funktiot Funktio on looginen ryhmä komentoja Uudelleenkäytettävää koodia Kirjoita kerran Kutsu niin monta kertaa kuin haluat Edut Uudelleenkäytettävää Helpottaa abstraktiota Vähentää monimutkaisuutta Vähentää koodin pituutta
Yhteenveto- Funktiot Fuktiot noudattavat seuraavanlaista mallia: Funktioille voi määritellä oletusparametereja. Kaikkia parametreja ei näin tarvitse täyttää funktiota kutsuttaessa unsigned short int LaskeAla (int pituus, int leveys) Paluuarvon tyyppiFunktion nimiParametrien nimet Parametrien tyypit
Yhteenveto- Funktiot Saman nimisestä funktiosta voi tehdä eri versioita (overloading) Funktioille voi määritellä oletusparametereja. Kaikkia parametreja ei näin tarvitse täyttää funktiota kutsuttaessa Ohjelmasta saadaan tehokkaampi määrittelemällä lyhyet funktiot ”inline”-funktioiksi Funktio voi kutsua itseään (rekursiivinen funktio)