Tässä oppaassa käsitellään C++ kieltä ja sen olio-ominaisuuksia. Oppaan lähtökohtana, on että lukija on ohjelmoinut C-kielellä.
Aiheeseen liittyviä video-oppaita löytyy sivulta https://www.youtube.com/playlist?list=PLWl0bS7jZq9_6i4B1l4Im6sx9DKwUo7OU
Oppaaseen liittyvien esimerkkien lähdekoodit löytyy sivulta https://github.com/orgs/olio-kurssi/repositories.
Olio-ohjelmoinnin tärkeimmät käsitteet ovat luokka ja olio. Voisit ajatella, että luokka on ohjelmoijan tekemä uusi muuttujatyyppi ja olio muuttuja, jonka tyyppinä on tuo luokka. Voit siis verrata sitä tilanteeseen, jossa määrittelet muuttujan lauseella int age;. Samalla tavalla määrittelet olion lauseella Student myObject;. Sinun on kuitenkin itse luotava tuo luokka nimeltään Student.
Milloin on tarpeen luoda luokka ja olio?
Esimerkiksi teet sovelluksen, jossa käsittelet opiskelijoiden tietoja. Jokaiselle opiskelijalle määritellään nimi, syntymävuosi, osoite, sähköpostiosoite ja puhelinnumero. Yhdelle opiskelijalle sinun on luotava 5 muuttujaa. Jos käsittelet 10 opiskelijan tietoja, sinun on luotava 50 muuttujaa.
Edellistä parempi ratkaisu on luoda tietue, jossa määritellään nuo 5 muuttujaa. Ja sitten luot 10 muuttujaa, joiden tyyppinä on tuo tietue.
Miksi luoda luokka, jos tietuekin toimii?
Luokan sisään voit sijoittaa myös metodeja eli funktioita. Niiden avulla voit esimerkiksi antaa edellä mainutuille 5:lle muuttujalle arvoja hallitummin. Ja voit luoda metodeja jotka tulostavat noiden muuttujien arvoja haluamallasi tavalla.
Kun tehdään graafisia sovelluksia tarvitaan paljon buttoneita, tekstikenttiä, labeleita, alasvetovalikoita jne. Yleensä käytetään jotain valmiita kirjastoja tai frameworkkejä. Niissä on valmiiksi tehtynä esimerkiksi Button luokka, jolla on ominaisuuksia: buttonissa oleva teksti, klikkauksen aiheuttama tapahtuma, buttonin väri, reunojen tyyli jne. Aina kun laitamme omaan sovellukseemme buttonin, luomme Button luokan olion.
Mikä on Qt?
Qt on framework jolla voidaan luoda graafisia sovelluksia. Qt sisältää joukon valmiiksi tehtyjä luokkia, joita voimme hyödyntää. Kun asennat Qt:n voit asentaa myös Qt Creatorin. Qt Creator on graafinen sovellus, jolla voidaan luoda Qt-sovelluksia ja myös C- ja C++-sovelluksia.
Aluksi on syytä tutustua kuinka C++ kielessä tulostetaan konsolin ruudulle ja kuinka sieltä luetaan käyttäjän antamaa dataa. Voit toki käyttää C-kielestä tuttuja funktioita printf ja scanf, mutta niiden sijaan käytetään yleensä olioita: cout ja cin. Molemmat ovat iostream luokan olioita, joten niitä käyttäessäsi sinun tulee lisätä kooditiedostoon alkuun rivi
#include <iostream>
C++ kielessä voidaan käyttää merkkijonoille muuttujatyyppiä string, jota C-kielessä ei ole. C-kielessähän merkkijonoille käytettiin char -taulukkoja. Tuo string, samoin kuin cout ja cin on määritetty nimiavaruudessa eli namespace:ssa nimeltään std.
Voisit tehdä lyhyen ohjelman koodilla
#include <iostream> int main() { std::string name; std::cout<<"Kerro nimesi"<<std::endl; std::cin>>name; std::cout<<"Terve "<<name<<std::endl; return 0; }Koska tuohon std namespaceen joudutaan edellä viittamaan monta kertaa, voisi koodiin lisätä tuon namespacen käyttäen using direktiiviä seuraavasti:
#include <iostream> using namespace std; int main() { string name; cout<<"Kerro nimesi"<<endl; cin>>name; cout<<"Terve "<<name<<endl; return 0; }
Olio-ohjelmoinnin kaksi keskeistä termiä ovat luokka ja olio. Olio-ohjelmoinnissa tieto ja sitä käsittelevä toiminnallisuus kootaan luokkarakenteeksi. Luokista luodaan olioita.
Olio-ohjelmointia tukevia kieliä ovat mm. C++, C#, Java ja Visual Basic. C++ poikkeaa muista edellä mainituista siten, että C++ sovelluksia ajetaan suoraan käyttöjärjestelmän päällä. Muilla edellä mainituilla kielillä tehdyt sovellukset ajetaan virtuaalikoneessa. Edellä mainitun eron vuoksi C++ sovellukset vaativat vähemmän resursseja koneelta ja toimivat näin nopeammin, varsinkin heikkotehoisissa koneissa. Haittapuolena on, että C++ sovelluksen ohjelmoiminen on työläämpää. C++ ohjelmoijan tulee itse huolehtia esimerkiksi muistin varaamisesta ja vapauttamisesta, kun muissa em. kielissä virtuaalikone huolehtii siitä.
Alla olevassa kuvassa kuvataan C++ - ja Java-sovelluksen toimintaa.
Tärkeimpiä olio-ohjelmoinnin periaatteita ovat kapselointi, tiedon kätkentä, periytyminen ja polymorfismi.
Kapselointi tarkoittaa, että olion tiedot ja toiminnot yhdistetään yhdeksi yksiköksi. Olio piilottaa sisäiset tietonsa ja tarjoaa julkisen rajapinnan, jonka avulla ohjelmoija voi käyttää olion toiminnallisuutta ilman, että olion sisäinen rakenne paljastuu. Kapselointi mahdollistaa ohjelmiston rakenteen selkeyttämisen ja tietojen suojaamisen ulkopuolisilta.
Tiedon kätkentä liittyy läheisesti kapselointiin. Tiedon kätkennällä pyritään rajoittamaan pääsyä olion sisäisiin tietoihin, mikä estää ulkopuolisia muuttamasta tai väärinkäyttämästä niitä. Tämä saavutetaan määrittelemällä olion tietojen näkyvyys private
, protected
tai public
-avainsanojen avulla. Tiedon kätkentä auttaa pitämään ohjelmiston vakaana ja virheettömänä.
Periytyminen tarkoittaa, että olio voi periä ominaisuuksia ja toimintoja toiselta oliolta. Periytyminen mahdollistaa yhteisten ominaisuuksien uudelleenkäytön ja laajentamisen, mikä vähentää koodin määrää ja parantaa ohjelmiston ylläpidettävyyttä. Esimerkiksi "Eläin" voi olla perusluokka, josta "Koira" ja "Kissa" voivat periä ominaisuuksia, kuten liikkuminen ja hengittäminen.
Polymorfismi tarkoittaa, että sama operaatio voi toimia eri tavoilla eri olioilla. Se mahdollistaa yhteisen rajapinnan käytön erilaisille olioille, jolloin voimme kutsua esimerkiksi metodin ajaa()
sekä "Auto"- että "Moottoripyörä"-olioille, vaikka niiden toiminta eroaisi toisistaan. Polymorfismi parantaa ohjelman joustavuutta ja laajennettavuutta.
Opiskeltuasi c-kieltä tietue lienee sinulle tuttu käsite. Myös C++ ohjelmassa voit luoda tietueita esimerkiksi seuraavasti:
typedef struct Person_struct{ int age; string name; } person;Nyt sinulla on käytössäsi uusi tietotyyppi ja voit luoda muuttujia, joilla on tietotyyppinä person seuraavasti:
person pe;Ja voit sijoittaa tietuemuuttujille arvoja seuraavasti:
pe.age=23; pe.name="Teppo Testi";Eli person-tietue sisältää muuttujat age ja name. Oheisen esimerkin sovellus, löytyy sivulta https://github.com/olio-kurssi/esim0
Luokka voisi olla samanlainen, mutta usein se sisältää myös metodeja, joiden avulla noita muuttujia käsitellään. Voidaan luoda Person luokka seuraavasti:
class Person{ private: int age; string name; public: int getAge() const { return age; } void setAge(int value){ age=value; } string getName() const { return name; } void setName(string value){ name=value; } };
Luokan sisältämiä muuttujia nimitetään jäsenmuuttujiksi ja luokan sisältämiä funktioita (jotka käsittelevät em. muuttujia) nimitetään jäsenfunktioiksi eli metodeiksi.
public osiossa määritelty getAge-metodi on age muuttujan Getter-metodi. Sen avulla saadaan haettua age muuttujan arvo. Ja setAge on age muuttujan Setter-metodi. Sen avulla muuttujan age arvo voidaan asettaa.
Molemmissa Gettereissä eli getAge ja getName on käytetty const määrettä. Se ei ole pakollinen mutta se aiheuttaa sen, että noiden metodien sisällä ei voida muuttaa muuttujien age ja name arvoja.
Private tyyppisiin muuttujiin ja metodeihin päästään käsiksi vain luokan sisältä. Public tyyppisiin muuttujiin ja metodeihin päästään käsiksi myös luokasta luodun olion kautta. Protected tyyppisiin muuttujiin päästään käsiksi luokan ja perivän luokan sisältä. Usein noudatetaan seuraavia käytäntöjä:
Oheisen esimerkin sovellus, löytyy sivulta https://github.com/olio-kurssi/esim1
Yleensä luokan muuttujien ja metodien määrittelyt tehdään h-tiedostossa. Kun luodaan Person-luokka, niin person.h tiedoston sisältö voisi olla seuraava
#ifndef PERSON_H #define PERSON_H #include <iostream> using namespace std; class Person { public: Person(); int getAge() const; void setAge(int newAge); string getFname() const; void setFname(const string &newFname); private: int age; string fname; }; #endif // PERSON_HEdellä siis private osiossa on määritelty jäsenmuuttujat age ja fname ja public osiossa niiden getterit ja setterit. Ja tuossa h-tiedostossa määritetään metodeista vain niiden palautusarvon tyyppi ja metodin ottamien parametrien tyyppi. Luokan metodien toteutukset kirjoitetaan person.cpp tiedostoon seuraavasti:
#include "person.h" Person::Person() { } int Person::getAge() const { return age; } void Person::setAge(int newAge) { age = newAge; } string Person::getFname() const { return fname; } void Person::setFname(const string &newFname) { fname = newFname; }
this on erityinen osoitin, joka viittaa olioon, jonka jäsenfunktiota parhaillaan suoritetaan.
Käytetään, kun tarvitaan viitettä olion jäseniin, erityisesti silloin, kun jäsenmuuttujien ja parametrien nimet ovat samat.
class MyClass { int age; public: void setValue(int age) { this->age = age; // Erotellaan jäsenmuuttuja ja parametri } };
Olio on luokasta luotu ilmentymä eli instanssi. Voit verrata asiaa siihen, että int on muutujatyyppi ja voit luoda muuttujan lauseella int myVariable. Edellä olevassa esimerkissä on luotu luokka Person ja siitä voidaan luoda olio lauseella Person objectPerson.
Kun luot olioita sinun tulee ymmärtää, että sovelluksellasi on käytössä kahdenlaista muistia
Heap muistia allokoidaan dynaamisesti sovelluksen ajon aikana ja sinun on huolehdittava sen vapauttamisesta (jollet käytä smart pointteria).
Perinteisesti C++ ohjelmoinnissa olioita on voinut luoda kahdella tavalla eli joko
Person objectPerson1;tai näin
//luo osoitin, jonka tyyppinä Person Person *objectPerson2; //varaa osoittimelle muistia new operaattorilla *objectPerson2 = new Person;Edellisen voit korvata myös yhdellä lauseella:
Person *objectPerson2 = new Person;
Ensin mainittu tapa luo ns. automaattisen olion Stack muistiin. Tässä tapauksessa käyttöjärjestelmä huolehtii olion muistinvarauksista ja vapauttamisista, olion luonnin ja tuhoamisen yhteydessä.
Jälkimmäinen tapa luo olion Heap muistiin ja sinun on huolehdittava sen tuhoamisesta, kun et enää tarvitse sitä.
Kun olet luonut olion, pääset sen avulla käsiksi Person luokan metodeihin. Niihin viitataan eri tavalla riippuen siitä teitkö olion Stack vai Heap muistiin. Esimerkiksi setAge metodia voidaan kutsua näin:
objectPerson1.setAge(23);tai
objectPerson2->setAge(23);
Sinun ei itse tarvitse tuhota oliota objectPerson1, mutta sinun on tuhottava olio objectPerson2, kun et sitä enää tarvitse. Se tapahtuu seuraavasti:
delete objectPerson2; objectPerson2 = nullptr;
Käytä automaattista muistialuetta (pino), kun tiedät olion koon etukäteen ja se on suhteellisen pieni. Oliot automaattisella muistialueella tuhoutuvat automaattisesti, kun niiden käyttöalue (kuten funktio) päättyy. Tämä auttaa välttämään muistivuotoja. Automaattinen muistialue on nopeampi käyttää kuin keko, koska sen hallinta on yksinkertaisempaa.
Käytä dynaamista muistialuetta (keko), kun olion koko ei ole tiedossa etukäteen tai se on suuri. Olet vastuussa olion muistialueen vapauttamisesta käytön jälkeen delete-avainsanalla. Oliot dynaamisella muistialueella säilyvät olemassa, kunnes ne tuhotaan manuaalisesti
Moderni C++ mahdollistaa resurssien hallinnan älykkäiden osoittimien avulla, mikä helpottaa muistin vapauttamista ja vähentää mahdollisuutta virheisiin.
C++ sisältää kolmenlaisia smart pontereita:
Smart pointer voidaan luoda koodilla:
#include <memory> unique_ptr<Person> objectPerson = make_unique<Person>();Edellä olio luodaan kekoon, mutta se tuhoutuu automaattisesti.
Seuraava esimerkki havainnollistaa, kuinka shared_ptr:ia voidaan käyttää. Huomaa, että osoittimet ptr1 ja ptr2 osoittavat samaan muistipaikkaan
#include <iostream> #include <memory> using namespace std; class TestClass{ public: TestClass(string value){ fname=value; cout<<"TestClass olio luotiin"<<endl; } ~TestClass(){ cout<<"TestClass olio tuhottiin"<<endl; } string getName(){ return fname; } private: string fname; }; void functionFirst(); void functionSecond(shared_ptr<TestClass> ptr); int main() { functionFirst(); } void functionFirst(){ shared_ptr<TestClass> ptr1=make_shared<TestClass>("Teppo"); cout<<"Funktiossa1 nimi = "<< ptr1->getName()<<endl;; functionSecond(ptr1); } void functionSecond(shared_ptr<TestClass> ptr2){ cout<<"Funktiossa2 nimi = "<< ptr2->getName()<<endl; }Tuloksena on
TestClass olio luotiin Funktiossa1 nimi = Teppo Funktiossa2 nimi = Teppo TestClass olio tuhottiin
Edellä, jo kerrottiin, että luokan muodostin on metodi, jolla on sama nimi kuin itse luokalla. Muodostinta kutsutaan aina kun luokasta luodaan olio. Sillä ei ole koskaan paluuarvoa, eikä paluuarvoksi kirjoiteta edes sanaa void.
Voit lisätä muodostimelle parametrin tai useita parametrejä. Jos edellä muokattaisiin luokan muodostin h tiedostossa muotoon
Person(int value);Ja cpp-tiedostossa muotoon
Person::Person(int value) { age=value; }pitää olioa luotaessa antaa aina myös kokonaisluku eli olion voisi luoda näin:
Person objectPerson(46);TAI
Person *objectPerson=new Person(46);Tällöin 46 sijoitetaan age muutujan arvoksi. Nyt ei enää voi luoda oliota antamatta kokonaislukua, eli seuraavasta seuraisi virheilmoitus
Person *objectPerson=new Person;Jos halutaan, että molemmat toimii, tarvitaan kaksi muodostinta, jolloin h-tiedostoon kirjoitettaisiin
Person(); Person(int value);Huom! Edellä on kyse metodin ylikuormittamisesta, josta kerrotaan myöhemmin.
Luokan tuhoaja on myös saman niminen kuin luokka, mutta sen edessä on merkki ~ eli seuraava olisi h-tiedostossa
~Person();Ja seuraava cpp-tiedostossa
Person::~Person() { cout<<"Person luokan tuhoajaa kutsuttiin\n"; }Jos koodisi olisi seuraava
Person *objectPerson=new Person; delete objectPerson; objectPerson=nullptr;Näkisit tekstin Person luokan tuhoajaa kutsuttiin. Luokan tuhoajaa kutsutaan siis aina, kun olio tuhotaan delete komennolla. Hyvän ohjelmointitavan mukaisesti tuhottuun olioon tulisi asettaa nullptr, kuten edellä.
Sinun ei ole pakko luoda luokalle tuhoajaa, koska kääntäjä luo automaattisesti "näkymättömän tuhoajan". Ohjelmoijan kannattaa luoda tuhoaja vain, jos haluaa lisätä siihen jotain koodia.
Oheiseen esimerkkiin liittyvä sovellus, löytyy sivulta https://github.com/olio-kurssi/esim2
C++ mahdollistaa funktioiden ylikuormittamisen (function overloading) eli sen, että saman nimisiä funktioita on useita, mutta niillä on erilaiset parametrit. Myös palautusarvot voivat olla erilaisia, mutta pelkät erilaiset paluuarvot eivät mahdollista ylikuormitusta, jos parametrit ovat samat. Kääntäjä valitsee funktion kutsussa annettujen argumenttien avulla sopivan funktion.
Esimerkki ylikuormittamisesta
void calcSum(int a, int b){ int sum=a+b; cout<<"Kokonaislukujen summa = "<<sum<<endl; } void calcSum(double a, double b){ double sum=a+b; cout<<"Desimaalilukujen summa = "<<sum<<endl; }
Periytyminen eli inheritance tarkoittaa, että jokin luokka voi periä toisen luokan. Ajatellaan esimerkiksi että rakentaisimme sovellusta, jolla käsitellään jonkin oppilaitoksen dataa. Todetaan, että oppilaitoksessa on opiskelijoita ja opettajia. Molemmilla on monia samalaisia ominaisuuksia, kuten esimerkiksi nimi ja syntymävuosi. Lisäksi opiskelijoilla on ryhmätunnus ja opettajilla on osasto.
Nyt voidaan tehdä niin, että
Perityminen merkitään Student-luokalle näin:
class Student : public Person
Oheiseen esimerkkiin liittyvä sovellus, löytyy sivulta https://github.com/olio-kurssi/esim3. Sovelluksen luokkakaavio näyttää seuraavalta:
Periytymisen yhteydessä on syytä tutustua termiin protected, kun luokka perii toisen luokan se pääsee käsiksi ominaisuuksiin, jotka ovat tyypiltään public tai protected. Seuraavassa taulukossa kuvataan kuinka private, public ja protected rajaavat oikeuksia.
Pääsy | public | protected | private |
---|---|---|---|
Luokan sisällä | kyllä | kyllä | kyllä |
Perivässä luokassa | kyllä | kyllä | ei |
Luokan ulkopuolella | kyllä | ei | ei |
Edellä kuvattiin luokan jäsenten suojauksia. Periytymiselle voidaan myös määrittää suojaus seuraavilla vaihtoehdoilla
Voit lukea lisätietoa periytymisestä sivulta https://www.tutorialspoint.com/cplusplus/cpp_inheritance.htm
Mikäli peritävässä luokassa on muodostin, joka ottaa parametreja, voidaan perivän luokan konstruktorissa kutsua tuota muodostinta.
Esimerkiksi, jos Person luokassa on seuraava muodostin
Person::Person (string na){ name=na; }voisi Student luokan muodostin olla seuraava
Student::Student(string gr, string na ) : Person(na){ groupName=gr; }Edellä name on Person luokan jäsenmuuttuja ja groupName on Student-luokan jäsenmuuttuja.
Jos edellä Person luokalla on oletusmuodostin ja public tyyppinen metodi setName(), voitaisiin Student-luokan muodostin kirjoittaa muotoon:
Student::Student(string gr, string na ) { groupName=gr; this->setName(na); }
Ylikirjoittaminen (overriding) C++-ohjelmoinnissa tarkoittaa, että aliluokka määrittelee uudelleen kantaluokassa perityn virtuaalisen funktion. Kun ylikirjoitettu funktio kutsutaan aliluokan instanssin kautta, suoritetaan aliluokan versio kyseisestä funktiosta. Tämä mahdollistaa polymorfismin, jossa aliluokka voi muokata kantaluokan toiminnallisuutta omien tarpeidensa mukaisesti.
Ylikirjoittaminen edellyttää, että sekä kantaluokan että aliluokan funktiot ovat määritelty samalla nimellä, paluuarvolla ja parametreilla, ja kantaluokan funktion on oltava virtual
-avainsanalla merkitty. Aliluokan funktio voidaan myös merkitä override
-avainsanalla selkeyden vuoksi.
Seuraavassa esimerkissä lisäsin Person luokkaan metodin sayStatus ja ylikirjoitin sen Students ja Teacher luokissa.
Alla rivit h-tiedostoista
person.h virtual void sayStatus(); student.h virtual void sayStatus() override; teacher.h virtual void sayStatus() override;Ja cpp-tiedostossa metodien toteutukset ovat seuraavat
void Person::sayStatus() { cout<<"Person\n "; } void Student::sayStatus() { cout<<"Opiskelija: "; } void Teacher::sayStatus() { cout<<"Opettaja: "; }
Esimerkki löytyy sivulta https://github.com/olio-kurssi/esim4
Luokkien välillä voi olla seuraavanlaisia suhteita
Assosiaatiossa koostavan luokan konstruktorille tai jollekin metodille annetaan parametrina toisen luokan olio esimerkiksi näin:
Engine objectEngine; Car objectCar(objectEngine);Edellä siis luodaan kopio oliosta objectEngine.
Aggregaatiossa koostavan luokan konstruktorille tai jollekin metodille annetaan parametrina referenssi toisen luokan olioon esimerkiksi näin:
Engine objectEngine; Engine &refEngine=objectEngine; Car objectCar(refEngine);
Kompositio eli vahva kooste tarkoittaa tilannetta, jossa koosteluokka sisältää toisen luokan olion/olioita. Esimerkissä 6 on luokka Classroom, joka sisältää kaksi Student-luokan oliota ja yhden Teacher-luokan olion seuraavasti:
class Student { } class Teacher { } Class Classroom{ private: unique_ptr<Student> objStudent1; unique_ptr<Student> objStudent2; unique_ptr<Teacher> objTeacher; }
Sovelluksen luokkadiagrammi pelkistettynä näyttää tältä
Classroom on siis koosteluokka. Tässä esimerkissä Student- ja Teacher-luokan oliot luodaan smart pointtereiden avulla, mutta ne voitaisiin luoda myös "normi" pointtereilla tai pinomuistiin.
Esimerkki löytyy sivulta https://github.com/olio-kurssi/esim6
Kooste ja perintä ovat molemmat oliosuuntautuneita ohjelmistomalleja. Kooste tarkoittaa has a-tyyppistä suhdetta ja perintä is a-tyyppistä suhdetta. Tämän sivun esimerkeistä voidaan sanoa, että
C++-vektori on osa C++ Standard Libraryä ja se on dynaaminen taulukko, joka voi mukautua erilaisiin datamäärien muutoksiin. Se tarjoaa samankaltaisia ominaisuuksia kuin perinteinen taulukko, kuten nopea elementteihin pääsy, mutta toisin kuin perinteiset taulukot, vektorin koko voi muuttua joustavasti.
Jos sovelluksessa tarvitaan monta oliota samasta luokasta, voidaan niistä luoda lista vektorin avulla. Seuraavassa esimerkissä luodaan oliolista, joka sisältää Student-luokan oliota. Listan käytöstä erillisten olioiden sijaan on etuna se, että listaa voidaan käydä läpi toistorakenteella.
#include "student.h" #include <iostream> #include <vector> using namespace std; int main() { vector<Student> studentList; //luodaan Student-luokan oloita Student objectStudent0("Teppo Testi",1999,"TVT23SPL"); Student objectStudent1("Liisa Joki",1998,"TVT23SPL"); Student objectStudent2("Aino Virta",1997,"TVT23SPO"); Student objectStudent3("Matti Virtanen",2001,"TVT23SPO"); Student objectStudent4("Mikko Vilkas",2001,"TVT23SPL"); //lisätään luodut oliot listaan studentList.push_back(objectStudent0); studentList.push_back(objectStudent1); studentList.push_back(objectStudent2); studentList.push_back(objectStudent3); studentList.push_back(objectStudent4); for(int x=0; x<=4; x++){ studentList[x].printStudentData(); } return 0; }
Esimerkissä 7 on käytetty smart_pointteria ja sen main.cpp on seuraava
#include "student.h" #include <iostream> #include <vector> #include <memory> using namespace std; int main() { unique_ptr<vector<Student>> studentList = make_unique<vector<Student>>(); Student objectStudent0("Teppo Testi",1999,"TVT23SPL"); Student objectStudent1("Liisa Joki",1998,"TVT23SPL"); Student objectStudent2("Aino Virta",1997,"TVT23SPO"); Student objectStudent3("Matti Virtanen",2001,"TVT23SPO"); Student objectStudent4("Mikko Vilkas",2001,"TVT23SPL"); studentList->push_back(objectStudent0); studentList->push_back(objectStudent1); studentList->push_back(objectStudent2); studentList->push_back(objectStudent3); studentList->push_back(objectStudent4); // Hae opiskelijalista studentList-osoittimesta vector<Student>& students = *studentList; for(int x=0; x<=4; x++){ // Käytä opiskelijalistan elementtiä `x` ja kutsu printStudentData() -funktiota students[x].printStudentData(); } return 0; }
Esimerkki löytyy sivulta https://github.com/olio-kurssi/esim7
Abstrakti luokka, tarkoittaa luokkaa, josta ei voi luoda oliota, mutta luokkaa voidaan käyttää kantaluokkana muille luokille. Edellisessä esimerkissä voisimme tehdä Person luokasta abstraktin.
Edellisessä esimerkissä voit luoda Person luokasta olion. Luokka saadaan abstraktiksi, jos siihen lisätään yksi pure virtual method. Muutetaan Person luokassa sayStatus() metodin määrittely muotoon
virtual void sayStatus()=0;Funktion toteutus voidaan poistaa person.cpp tiedostosta. Nyt Person on abstrakti luokka ja jos, koetat luoda siitä olion, saat virheilmoituksen.
Esimerkki löytyy sivulta https://github.com/olio-kurssi/esim5
Virtuaalifunktioita sisältävän luokan tuhoajan pitää myös olla virtuaalinen.
Olio-ohjelmoinnissa käsite interface tarkoittaa luokkaa, josta ei tehdä olioita. Siinä määritellään metodeja, muttei niiden toteutuksia. Siis määritetään metodien paluuarvojen tyypit ja parametrien tyypit. Kun jokin luokka perii tuon interface luokan on perivän luokan toteutettava interface luokan metodit, muuten seuraa virheilmoitus.
Javassa ja C#:ssa interface luokka toteutetaan lisäämällä sana interface luokan määrityksen eteen. C++:ssa ei tällaista sanaa ole vaan interface luokka voidaan toteuttaa tekemällä metodeista puhtaita virtuaalimetodeja. Interface luokan nimi alkaa usein kirjaimella I.
Tein esimerkin, jossa on interface luokka nimeltään IPerson ja Student ja Teacher perivät sen, sekä luokan Person. Esimerkki löytyy sivulta https://github.com/olio-kurssi/esim8
Staattinen luokka tarkoittaa luokkaa, jonka metodeita voidaan kutsua luomatta oliota kyseisestä luokasta. C++ kielessä ei voida luoda varsinaista staattista luokkaa, mutta toiminnallisesti käsitettä vastaa luokka, jonka kaikki metodit ovat staattisia.
Esimerkiksi C#:ssa on luokka Math, josta löytyy esimerkiksi funktio sqrt. Ei ole järkevää, että voidaksesi käyttää tuon luokan metodeja sinun tulisi luoda luokasta olio. Voit laskea esimerkiksi luvun 4 neliöjuuren koodilla Math.Sqrt(4).
Tein esimerkin, jossa on luokka nimeltään MyStaticClass ja siellä määritettynä metodi doubleMe. Luokan h-tiedostossa metodi on määritetty näin:
static double doubleMe(double);ja cpp-tiedostossa toteutettu näin:
double MyStaticClass::doubleMe(double value) { return 2*value; }Nyt tuota metodia kutsutaan main.cpp:ssä näin:
myResult=MyStaticClass::doubleMe(myValue);Metodia siis kutsutaan syntaksilla luokan nimi :: metodin nimi
Voit kuitenkin luoda olioita luokasta MyStaticClass. Jos haluat estää olioiden luomisen, voit muokata luokan oletusmuodostimen h-tiedosssa seuraavaan muotoon:
MyStaticClass()=delete;
Esimerkki löytyy sivulta https://github.com/olio-kurssi/esim9
UML eli Unified Modeling Language on standardoitu tapa kuvata ohjelmisto- ja järjestelmäsuunnittelua visuaalisesti. Se tarjoaa erilaisia kaavioita ja työkaluja ohjelmistojen rakenteen, toiminnallisuuden ja käyttötapauksien mallintamiseen. UML auttaa suunnittelijoita kommunikoimaan, ymmärtämään ja dokumentoimaan ohjelmistojen arkkitehtuuria ja toiminnallisuutta.
Luokkakaavio on UML-standardin mukainen mallinnustyyppi, joka kuvaa järjestelmän staattisen rakenteen luokkien ja niiden välisten suhteiden avulla.
Luokka piirretään suorakaiteena, joka on jaettu kolmeen osaan; luokan nimi, luokan tiedot (jäsenmuuttujat) ja luokan toiminnallisuus (jäsenfunktiot).
Diagrammeissa käytetyt suojaustasoa kuvaavat symbolit ovat:
Esimerkiksi jäsenmuuttuja osiossa voisi olla rivi:
-age:int
joka tarkoittaa, että luokka sisältää int tyyppisen muuttujan nimeltään age, jonka suojaustaso on private
Ja jäsenfunktio osiossa voisi olla rivi:
+getInfo(int):float
joka tarkoittaa, että public metodi getInfo ottaa vastaan argumenttina int tyyppisen arvon ja palauttaa float tyyppisen arvon
Kontruktoria ei välttämättä merkitä diagrammiin.
Periytyminen ilmaistaan diagrammeissa nuolella, jossa nuoli osoittaa perittävään luokkaan seuraavasti:
Kooste ilmaistaan diagrammeissa viivalla, jossa vinoneliö osoittaa koostavaan luokkaan seuraavasti:
Huom! Musta vinoneneliö ilmaisee, että kyseessä on vahva kooste.
Oppaaseen liittyvien esimerkkien lähdekoodit löytyy sivulta https://github.com/orgs/olio-kurssi/repositories.
repository | aihe |
---|---|
esim0 | Tietue |
esim1 | Luokka ja olio (pino ja keko) |
esim2 | Muodostin ja tuhoaja |
esim3 | Periytyminen |
esim4 | Virtuaalimetodi |
esim5 | Abstraktiluokka |
esim6 | Koostaminen |
esim7 | Oliotaulukko |
esim8 | Interface luokka |
esim9 | Staattinen luokka |
unit_test | Yksikkätestaus |