Cpp sekalaista
Mureakuha
Tässä luvussa tarkastellaan erilaisia tärkeitä pienempiä asiakokonaisuuksia. Tämä teksti ei ole ollut mikään manuaali, sisältäen kaikkea, pienintä yksityiskohtaa myöten. Mikäli tarvitset lisätietoja, kääntäjäsi referenssimanuaalit lienevät seuraava tiedonetsintäpaikka.
Sisällysluettelo |
Staattiset luokan jäsenet
Luokan jäsenmuuttuja voidaan määritellä staattisesti static avainsanan avulla. (Myös jäsenfunktio voidaan määritellä staattiseksi, mutta tällainen määrittely ei ole lainkaan tavallista, joten asiaa ei käsitellä tässä) Staattisten jäsenmuuttujien avulla voidaan selvittää useita melko hankaliakin tilanteita.
Kun jäsenmuuttuja määritellään staattiseksi, kyseisestä muuttujasta syntyy vain yksi kopio riippumatta siitä kuinka monta oliota lyseiseen luokkaan luodaan. Kaikki luokan oliot jakavat yhteisen staattisen muuttujan.
Staattinen jäsenmuuttuja on olemassa jo ennen kuin yhtään oliota on luotu kyseiseen luokkaan. Saattinen jäsenmuuttuja on itseasiassa globaali muuttuja, jonka voimassaoloalue on rajattu siihen luokkaan, johon se on määritelty. Staattista muuttujaa voidaan jopa käsitellä ilman varsinaista oliota.
Kun staattinen muuttuja esitellään luokassa, kyseessä ei ole määrittely vaan esittely, jolloin varsinainen tilavaraus ja määrittely on tehtävä muualla kuin luokan sisällä. Määrittely on itseasiassa vain uudelleen esittely, jossa kerrotaan myös mihin luokkaan ko. muuttuja kuuluu.
Oletusarvoisesti saattiset jäsenmuuttujat on alustettu 0:ksi. Muuttujalle voidaan toki antaa määrittelyn yhteydessä alkuarvo.
HUOM: Useimmissa C++:n toteutuksissa, jos staattisen jäsenmuuttujan alkuarvo jätetään 0:ksi, määrittelyä ei tarvita lainkaan, Kuitenkin C++ spesifikaation mukaan näin ei tule menetellä. Lienee siis turvallisinta käyttää myös määrittelyosaa, vaikkakaan kääntäjäsi ei sitä vaatisi. (Borlandin C++ 4.x vaatii myös määrittelyn).
Staattisten jäsenmuuttujien tarkoituksena on poistaa tarve varsinaisten globaalien muuttujien käytöltä. Luokat, jotka nojaavat globaaleihin muuttujiin, rikkovat lähes aina kapselointi periaatetta. Kapselointi on OOP:n perusperiaatteita.
#include <iostream> class myClass { static int i; // esittely public: void seti(int n) { i = n; } int geti() { return i; } }; int myClass::i; // määrittely main() { myClass o1, o2; o1.seti(10); cout << "o1.i: " << o1.geti() << '\n'; // näyttää 10:n cout << "o2.i: " << o2.geti() << '\n'; // näyttää saman 10:n return 0; }
Koska staattinen jäsenmuuttuja on olemassa, ennenkuin yhtään oliota on luotu luokkaan, muuttujaan pääsee käsiksi ilman oliota. Seuraavassa edellisen esimerkin muunnos. Huomaa kuinka i on nyt jouduttu esittelemään public-puolella.
#include <iostream> class myClass { public: static int i; // esittely void seti(int n) { i = n; } int geti() { return i; } }; int myClass::i; // määrittely main() { myClass o1, o2; myClass::i = 100; // asetetaan suoraan arvoonsa ilman oliota cout << "o1.i: " << o1.geti() << '\n'; // näyttää 100:n cout << "o2.i: " << o2.geti() << '\n'; // näyttää saman 100:n cout << "suoraan: " << myClass::i << '\n'; // näyttää saman 100:n return 0; }
Staattisen muuttujan eräs tavallinen käyttötapa on käyttää sitä yhteisen resurssin käytön koordinointiin (printteri, levy, verkkopalvelin). Seuraava ohjelma luo output-nimisen luokan, joka ylläpitää yhteistä tulostuspuskuria nimeltään outbuf. Outbuf on staattinen merkkitaulukko. Puskuria käytetään vastaanottamaan putbuf()-jäsenfunktion lähettämää dataa. Funktio lähettää str:n sisältämän datan merkki kerrallaan. Merkit lähetetään, mikäli puskuri on vapaa. Merkkien lähetyksen ajaksi puskuri lukitaan muilta käyttäjiltä.
#include <iostream> #include <string.h> class output { static char outbuf[255]; // yhteinen resurssi static int inuse; // 0: puskuri vapaa, muuten varattu static int oindex; // puskurin indeksi char str[80]; int i; // seuraavan merkin indeksi str:ssä int who; // olion tunniste ( >0 ) public: output( int w, char *s ) { strcpy( str, s ); i = 0; who = w; } void show() { cout << outbuf << '\n'; } int putbuf() { // funktio palauttaa 0:n, jos tulostus onnistui // palauttaa who:n, jos puskuri itsellä käytössä // palauttaa -1, jos puskuri toisella käytössä if (!str[i]) { // tulostus loppu inuse = 0; // puskurin vapautus return 0; // tulostus onnistui } if (!inuse) inuse = who; // otetaan puskuri haltuun if (inuse != who) return -1; // puskuri toisen käytössä if (str[i]) { // vielä tulostettavaa outbuf[ oindex ] = str[ i ]; i++; oindex++; outbuf[ oindex ] = '\0'; return who; } } }; char output::outbuf[255]; int output::inuse = 0; int output::oindex = 0; main() { int status; output o1( 1, "This is" ); output o2( 2, " a test" ); do { status = o1.putbuf(); if (status==0) status = o2.putbuf(); o1.show(); } while ( status ); return 0; }
Taulukkopohjainen I/O
Konsoli- ja tiedosto-I/O:n lisäksi C++ tukee joukkoa toimintoja, jotka käyttävät merkkitaulukkoja syöttö- tai tulostuslaitteina. C++:n taulukkopohjainen I/O vastaa C:n taulukkopohjaista I/O:ta, lähinnä sscanf()- ja sprintf()-funktioita. C++:ssa asia hoidetaan vaan joustavammin ja se käyttökelpoisempaa, koska käyttäjän määrittelemät tyypit ovat integroitavissa taulukkopohjaiseen I/O:hon.
Taulukkopohjainen I/O toimii streamien välityksellä kuten konsoli- ja tiodosto-I/O:kin. Itseasiassa kaikki mikä aiemmin opittiin I/O:sta on edelleen käytettävissä, tarvitaan vain muutama lisäfunktio ja taulukkopohjainen I/O on käytettävissä. Näillä funktioilla stream kytketään muistialueeseen.
Taulukkopohjainen I/O vaatii strstream tiedoston sisällyttämisen ohjelmaan. Tämä tiedosto sisältää seuraavien luokkien määrittelyt: istrstream, ostrstream ja strstream. Nämä luokat luovat taulukkopohjaiset syöttö, tulostus ja syöttö/tulostus virrat. Luokat on johdettu ios-luokasta, niinpä kaikki funktiot ja manipulaattorit, jotka löytyvät istream:sta, ostream:sta ja iostream:sta ovat käytettävissä istrstream:ssa, ostrstream:ssa ja strstream:ssa
Kun käytetään taulukkopohjaista tulostusta, käytetään seuraavaa ostrstream:n konstruktoria:
ostrstream ostr( char *buf, int size, int mode = ios::out );
ostr on virta, joka kytketään puskuriin buf. size kertoo puskurin koon. Tavallisesti mode:sta käytetään oletusarvoa: ios::out, mutta modessa voidaan käyttää mitä tahansa ios:ssa määriteltyä moodilippua.
Kun taulukko on avattu tulostusta varten, taulukkoon lisätään merkkejä, kunnes se on täysi. Taulukkoa ei ylitetä eikä kirjoiteta päälle. Ylitysyrityksestä tulee I/O-virhe. pcount()-jäsenfunktiolla voidaan selvittää puskuriin kirjoitettujen merkkien lukumäärä:
int pcount();
pcount()-funktiota on kutsutta aina streamin kanssa ja se palauttaa taulukkoon kirjoitettujen merkkien lukumäärän sisältäen mahdollisen null-lopetusmerkin.
Kun käytetään taulukkopohjaista syöttöä, käytetään seuraavaa istrstream:n konstruktoria:
istrstream istr( const char *buf );
istr on virta, joka kytketään puskuriin buf. eof() palauttaa Truen, kun taulukon loppu on saavutettu.
Kun käytetään taulukkopohjaista syöttöä ja tulostusta, käytetään seuraavaa strstream:n konstruktoria:
strstream iostr( const char *buf, int size, int mode );
iostr on virta, joka kytketään puskuriin buf syöttöä ja tulostusta varten. Syöttö- tulostusoperaatioita varten mode on ios::in | ios::out. Kaikki aiemmin esitetyt I/O-funktiot mukaanlukien myös binääriset funktiot ja suorasaantifunktiot toimivat myös taulukkopohjaiseen I/O:hon.
Seuraavassa ohjelmassa avataan taulukko tulostusta varten ja tulostetaan siihen.
#include <iostream> #include <strstream> main() { char buf[255]; ostrstream ostr( buf, sizeof( buf ) ); ostr << "Array-based I/O uses streams just like "; ostr << "'normal' I/O\n" << 100; ostr << ' ' << 123.45 << '\n'; // myös manipulaattoreita voidaan käyttää ostr << hex << 100 << " "; // formaattilippuja voidaan käyttää ostr << ostr.setf( ios::scientific) << 123.45 << '\n'; ostr << ends; cout << buf; return 0; }
Seuraavassa esimerkki taulukkopohjaisesta syötteestä. Ohjelma ensin kirjoittaa taulukkoon normaalisti määrittelyn yhteydessä tapahtuvalla alustuksella ja sitten lukee taulukon sisällön ja tulostaa sen konsolille.
#include <iostream> #include <strstream> main() { char buf[] = "Hello 100 123.125 a"; istrstream istr( buf ); char str[80]; int i; float f; char c; istr >> str >> i >> f >> c; cout << str << ' ' << i << ' ' << f; cout << ' ' << c << '\n'; return 0; }
Syötetaulukko käyttäytyy kuten tiedosto, kun se on kytketty streamiin. Esimerkiksi seuraava ohjelma käyttää binääristä I/O:ta ja eof()-funktiota puskurin sisällön lukemiseen.
#include <iostream> #include <strstream> main() { char buf[] = "Hello 100 123.125 a"; istrstream istr( buf ); char c; while (!istr.eof()) { istr.get( c ); cout << c; } return 0; }
Seuraava ohjelma käyttää taulukkoa sekä syötteessä että tulostuksessa.
#include <iostream> #include <strstream> main() { char iobuf[255]; strstream iostr( iobuf, sizeof iobuf, ios::in | ios::out ); iostr << "This is a test\n"; iostr << 100 << hex << 100 << ends; char str[80]; int i; iostr.getline( str, 79 ); iostr >> i; cout << str << ' ' << i << ' '; iostr >> i; cout << i; return 0; }
Linkitystarkentimet ja 'asm'-avainsanan käyttö
C++:ssa on kaksi mekanismia, joiden avulla C++ on helpompi linkittää muihin ohjelmointikieliin. Ensimmäinen on nimeltään linkitystarkennin (linkage specifier), joka kertoo kääntäjälle, että C++ ohjelman yksi tai useampi funktio linkitetään ohjelmaan toisesta ohjelmointikielestä, jonka funktion parametrien välitysmekanismi tai muu sellainen voi poiketa C++:sta. Toinen on asm avainsana, jonka avulla C++ koodiin voidaan sisällyttää assembly käskyjä.
Oletusarvoisesti kaikki funktiot käännetään ja linkitetään C++ funktioina. Kääntäjää voidaan pyytää linkittämään funktio siten, että se on yhteensopiva toiseen kieleen. Kaikki C++ kääntäjät sallivat funktioiden linkityksen joko C++:na tai C:nä. Jotkin kääntäjät sallivat myös kieliä kuten Pascal, Ada tai FORTRAN. Jotta funktio linkitetään toisella kielellä, käytetään linkitystarkentimen yleistä muotoa:
extern "language" function-prototype;
Edellisessä "language" on se kieli, johon linkitys halutaan tehdä. Mikäli useita funktioita linkitetään toiselle kielelle, käytetään seuraavaa muotoa:
extern "language" { function-prototypes }
Kaikkien linkitystarkentimien on oltava globaaleita eli ne eivät voi sijaita funktioiden sisällä.
Tavallisin tilanne linkitystarkentimien käyttöön on silloin, kun ohjelma linkitetään kolmannen osapuolen aliohjelmapakettiin, joka on käännetty jollain toisella kielellä. On myös aivan tavallista, ettet joudu koskaan ohjelmointiurallasi käyttämään linkitystarkentimia.
Vaikka yleensä on mahdollista linkittää assebly kielisiä rutiineita C++ ohjelmaan, on kuitenkin olemassa usein helpompi tapa käyttää assebleria. asm avainsanan avulla voit sisällyttää assembly koodia C++ funktioon. asm avainsanan yleinen muoto on seuraava:
asm("op-code");
jossa op-code on assebly kielinen käsky.
Usein asm-avainsanan käyttö on kääntäjä riippuvaista. Esimerkiksi Borland C++ hyväksyy seuraavat muodot:
asm op-code; asm op-code newline asm { instruction sequence }
Seuraava ohjelma linkittää func()-funktion C:nä ei C++:na:
// osa1.cpp #include <iostream> extern "C" int func( int x ); int func( int x ) { return x/3; }
Nyt edellinen voidaan ottaa mukaan C-kieliseen ohjelmaan, esimerkiksi seuraavaan:
// osa2.c #include <stdio.h> extern int func(); int main(void) { int tulos; tulos = func(9); printf("%d\n", tulos); return 0; }
Seuraavassa linkitetään useita funktioita samalla kertaa C-kielisiksi:
extern "C" { void f1(); int f2( int x ); double f3( double x, int *p ); }
Jos käytät in-line assembleriä, sinun on oltava varma siitä, mitä teet. Rekisterithän voivat olla ajossa olevan ohjelman käytössä. Periaatteellinen esimerkki kuitenkin seuraavassa:
// ÄLÄ KOKEILE, ELLET TIEDÄ MITÄ TAPAHTUU void func() { asm ("mov bp, sp"); asm ("push ax"); asm ("mov c1, 4"); // ... }
Muunnosfunktioiden luonti
Joskus on tarpeen muuttaa tietyn tyyppinen olio toisen tyyppiseksi. Muunnos on mahdollista suorittaa esimerkiksi kuormitetun operaattorifunktion avulla, mutta on olemassa myös toinen usein helpompi tapa, jota kutsutaan muunnosfunktioksi. Muunnosfunktio muuttaa olion arvon yhteensopivaksi toiseen tyyppiin, joka on usein yksi C++:n sisäänrakennetuista tyypeistä. Muunnosfunktio muuttaa automaattisesti olion arvon yhteensopivaksi siihen tyyppiin, mitä vaaditaan siinä yhteydessä, kun oliota käytetään.
Muunnosfunktion yleinen muoto:
operator tyyppi() { return arvo; }
Tässä tyyppi on kohdetyyppi eli tyyppi, jota olion tulisi olla lausekkeessa ja arvo on olion arvo muunnoksen jälkeen. Muunnosfunktiossa ei voi olla parametreja ja muunnos funktio on sen luokan jäsenfunktio, jolle muunnos tehdään.
Kuten esimerkeistä havaitaan, muunnosfunktioiden avulla muunnos tapahtuu siistimmin kuin muilla C++:n tavoilla suorittaa muunnos, sillä oliota voidaan suoraan käyttää lausekkeessa kuin lausekkeessa.
Seuraavassa ohjelmassa coord-luokka sisältää muunnosfunktion integeriin. Muunosfunktio palauttaa itseisarvon pisteen etäisyydestä origosta. Mikä tahansa muukin muunnos on mahdollista, mikäli se sopii sinun sovellukseesi.
#include <iostream> #include <math.h> class coord { int x, y; public: coord( int i, int j ) { x = i; y = j; } operator int() { return sqrt( x*x + y*y ); } }; main() { coord o1( 2, 3 ), o2( 4, 3 ); int i; i = o1; // automaattinen muunnos -> 3 cout << i << '\n'; i = 100 + o2; // automaattinen muunnos -> 5 cout << i << '\n'; return 0; }
Seuraavassa esimerkissä mjono-tyyppinen merkkijono muunnetaan pointteriksi mjonon privaattiin muuttujaan str:
#include <iostream> #include <string.h> class mjono { char str[80]; int len; public: mjono( char *s ) { len = strlen( s ); if (len > (sizeof(str) - 1)) len = sizeof(str) - 1; strncpy( str, s, len); str[len] = '\0'; } operator char *() { return str; } }; main() { mjono ob1( "Tässä on testi\n" ); char *p, s2[ 80 ]; p = ob1; // muunnos cout << p << '\n'; strcpy( s2, ob1 ); // muunnos cout << s2 << '\n'; return 0; }
C:n ja C++:n erot
Kuten tiedät C++ pohjautuu C-kieleen. Tämä tarkoittaa, että yleensä mikä tahansa C-ohjelma on automaattisesti (ei-oliopohjainen) C++ ohjelma. C++:n tuki olio-ohjelmointiin aiheuttaa kuitenkin muutaman pienen eron C:n ja C++:n välille. Jotkut näistä eroista estävät C-ohjelmaa kääntymästä C++ kääntäjällä. Seuraavassa käydään erot läpi kohta kohdalta, vaikkakin ainakin osaa on jo käsitelty aiemminkin.
HUOMAA: Ei-OOP ominaisuudet C:ssä ja C++:ssa eroavat vain hyvin vähän. Useimmiten C-ohjelma on kelvollinen C++ ohjelma sellaisenaan.
- C:ssä Funktion esittely: int f(); ei kerro mitään kyseisen funktion parametreista. Funktiolla voi olla yksi tai useampi parametri tai sillä ei ole parametreja ja mahdolliset parametrit voivat olla mitä tahansa tyyppiä. C++:ssa edellinen esittely tarkoittaa, että funktiolla ei ole parametreja. C++:ssa seuraavat esittelyt tarkoittavat samaa:
int f(); int f(void);
C++:ssa void on vapaaehtoinen ja teknisesti tarpeeton, mutta useat ohjelmoijat käyttävät voidia, jottei kenellekään koodin lukijalle ole epäselvää, onko parametreja vai ei.
- C++:ssa jokaisella funktiolla on oltava prototyyppi. C:ssä prototyyppien käyttö on osittain vapaaehtoista, vaikkakin hyvän ohjelmointitavan mukaisesti myös C:ssä käytetään prototyyppejä.
- Pieni mutta tärkeä ero C:n ja C++:n välillä on se, että C:ssä merkkivakio talletetään automaattisesti integerinä, C++:ssa ei tehdä näin.
- C:ssä voidaan esitellä globaali muuttuja useaan kertaan, vaikkakin tämä on hyvän ohjelmointitavan vastaista. C++:ssa useaan kertaan esittelystä seuraa virhe.
- C:ssä tunniste voi olla korkeintaan 31 merkkiä pitkä, C++:ssa tällaista rajoitusta ei ole. Käytännössä ylipitkät tunnisteet ovat kuitenkin harvinaisia ja hankalia käyttää.
- C:ssä voidaan, vaikkakin näin tehdään harvoin, kutsua main()-funktiota keskeltä ohjelmaa. C++:ssa tämä on kiellettyä.
- C:ssä ei voida ottaa rekisterimuuttujan osoitetta, C++:ssa voidaan. Tästä seuraa kuitenkin siirrettävyys rajoituksia ohjelmallesi, joten tuskin haluat käyttää.
Parametrisoidut tyypit, luokat ja funktiot
Tyyppiparametrisoinnin avulla voidaan määritellä yleisiä luokkia kuten taulukko, lista jne, integer taulukon, double taulukon, char taulukon jne sijasta. Voidaan määritellä yleisiä funktioita kuten max() tai sort() jne. riippumatta tyypeistä. Suurien luokka- ja algoritmikirjastojen tekeminen mahdollistuu tyyppiparametrisoinnin avulla. Parametrisoituja tyyppejä voidaan luoda ns. template mekanismin avulla.
Jos halutaan tehdä esimerkiksi funktio, joka palauttaa suuremman kahdesta luvusta, meidän on tehtävä niin monta erilaista max()-funktiota kuin on erilaisia tyyppejä, joista maksimi halutaan ottaa:
int max( int i, int j ) { return ( (i > j) ? i : j ); } double max( double i, double j ) { return ( (i > j) ? i : j ); } complex max( complex i, complex j ) { return ( (i > j) ? i : j ); } string max( string i, string j ) { return ( (i > j) ? i : j ); } jne.
Olettaen, että kaikille tyypeille on määriteltynä > operaatio.
Ainoa ero funktioissa on argumenttien tyypeissä ja palautustyypeissä:
tyyppi max( tyyppi i, tyyppi j ) { return ( (i > j) ? i : j ); }
Asia voidaan hoitaa template:n avulla:
#include <iostream> template <class Tyyppi> Tyyppi max( Tyyppi i, Tyyppi j ) { return ( (i > j) ? i : j ); } main() { int i=1, j=2; double d=3.0, e=4.0; cout << "max int 1, 2: " << max( i, j ) << '\n'; cout << "max double 3.0, 4.0: " << max( e, d ) << '\n'; return 0; }
Jos halutaan luoda taulukkoluokat IntArray, DoubleArray, jne. Asia hoituu kätevästi template:n avulla. Esimerkiksi seuraavassa ohjelmassa voidaan luoda esimerkiksi integer taulukko, char taulukko, double taulukko jne. Array-nimeä ei nyt enää käytetä yksin (paitsi muutamassa harvassa paikassa määrittelyn sisällä) vaan Array<Tyyppi>, kun kyseessä yleinen tilanne tai Jos kyseessä explisiittisesti esimerkiksi integer-taulukko, käytetään Array<int> jne.
#include <iostream> const int ArraySize = 5; // oletuskoko template <class Tyyppi> class Array { // Tässä ei voi olla: Array<Tyyppi> int size; Tyyppi *ia; public: Array( int sz = ArraySize ); // Voi olla: Array<Tyyppi> Array() { delete ia; } // Voi olla: Array<Tyyppi> Tyyppi &operator[] ( int ); }; template <class Tyyppi> Tyyppi &Array<Tyyppi>::operator[] ( int index) { return ia[index]; } template <class Tyyppi> Array<Tyyppi>::Array( int sz ) // Jälkimmäinen ei voi olla Array<Tyyppi> { size = sz; ia = new Tyyppi [size]; for (int i=0; i<size; i++) ia[ i ] = 0; } main() { int i; Array<int> it( 7 ); // luodaan 7 alkioinen integer taulukko Array<char> ct( 15 ); // luodaan 15 alkioinen char taulukko for ( i = 0; i < 7; i++) it[ i ] = i; for ( i = 0; i < 15; i++) ct[ i ] = i+'a'; cout << "\nit: "; for ( i = 0; i < 7; i++) cout << it[i] << ' '; cout << "\nct: "; for ( i = 0; i < 15; i++) cout << ct[i] << ' '; return 0; }
Kopio lisenssistä (englanniksi) löytyy täältä.
Alkuperäinen (c) Petteri Hämäläinen
