C-kielestä C++-kieleen

Samankaltaiset tiedostot
Operaattoreiden ylikuormitus. Operaattoreiden kuormitus. Operaattoreiden kuormitus. Operaattoreista. Kuormituksesta

12 Mallit (Templates)

Luokassa määriteltävät jäsenet ovat pääasiassa tietojäseniä tai aliohjelmajäseniä. Luokan määrittelyyn liittyvät varatut sanat:

Osoitin ja viittaus C++:ssa

815338A Ohjelmointikielten periaatteet Harjoitus 3 vastaukset

Loppukurssin järjestelyt C:n edistyneet piirteet

Sisällys. 1. Omat operaatiot. Yleistä operaatioista. Yleistä operaatioista

1. Omat operaatiot 1.1

Loppukurssin järjestelyt

Kääntäjän virheilmoituksia

815338A Ohjelmointikielten periaatteet Harjoitus 5 Vastaukset

C++11 Syntaksi. Jari-Pekka Voutilainen Jari-Pekka Voutilainen: C++11 Syntaksi

Tehtävä 1. TL5302 Olio-ohjelmointi Koe Malliratkaisuja. Tässä sekä a)- että b)-kohdan toimiva ratkaisu:

Ohjelman virheet ja poikkeusten käsittely

Java-kielen perusteet

Tietueet. Tietueiden määrittely

Sisällys. Yleistä attribuuteista. Näkyvyys luokan sisällä. Tiedonkätkentä. Aksessorit. 4.2

15. Ohjelmoinnin tekniikkaa 15.1

Merkkijono määritellään kuten muutkin taulukot, mutta tilaa on varattava yksi ylimääräinen paikka lopetusmerkille:

Virtuaalifunktiot ja polymorfismi

Ohjelmassa muuttujalla on nimi ja arvo. Kääntäjä ja linkkeri varaavat muistilohkon, jonne muuttujan arvo talletetaan.

812347A Olio-ohjelmointi, 2015 syksy 2. vsk. X Poikkeusten käsittelystä

tään painetussa ja käsin kirjoitetussa materiaalissa usein pienillä kreikkalaisilla

13 Operaattoreiden ylimäärittelyjä

815338A Ohjelmointikielten periaatteet Harjoitus 2 vastaukset

ELM GROUP 04. Teemu Laakso Henrik Talarmo

1. Mitä tehdään ensiksi?

C-kielessä taulukko on joukko peräkkäisiä muistipaikkoja, jotka kaikki pystyvät tallettamaan samaa tyyppiä olevaa tietoa.

9. Periytyminen Javassa 9.1

8. Näppäimistöltä lukeminen 8.1

Ohjelmointi 2. Jussi Pohjolainen. TAMK» Tieto- ja viestintäteknologia , Jussi Pohjolainen TAMPEREEN AMMATTIKORKEAKOULU

Apuja ohjelmointiin» Yleisiä virheitä

Olion elinikä. Olion luominen. Olion tuhoutuminen. Olion tuhoutuminen. Kissa rontti = null; rontti = new Kissa();

15. Ohjelmoinnin tekniikkaa 15.1

Metodien tekeminen Javalla

T Olio-ohjelmointi Osa 3: Luokka, muodostin ja hajotin, this-osoitin Jukka Jauhiainen OAMK Tekniikan yksikkö 2010

\+jokin merkki tarkoittaa erikoismerkkiä; \n = uusi rivi.

Metodit. Metodien määrittely. Metodin parametrit ja paluuarvo. Metodien suorittaminen eli kutsuminen. Metodien kuormittaminen

Olio-ohjelmointi Syntaksikokoelma

Tutoriaaliläsnäoloista

Harjoitustyö: virtuaalikone

Sisällys. Yleistä attribuuteista. Näkyvyys luokan sisällä ja ulkopuolelta. Attribuuttien arvojen käsittely aksessoreilla. 4.2

ITKP102 Ohjelmointi 1 (6 op)

Sisällys. 7. Oliot ja viitteet. Olion luominen. Olio Java-kielessä

ITKP102 Ohjelmointi 1 (6 op)

Osa. Listaus 2.1. HELLO.CPP esittelee C++ -ohjelman osat. 14: #include <iostream.h> 15: 16: int main() 17: {

Mallit standardi mallikirjasto parametroitu tyyppi

7. Näytölle tulostaminen 7.1

ITKP102 Ohjelmointi 1 (6 op)

Java-kielen perusteet

Dart. Ryhmä 38. Ville Tahvanainen. Juha Häkli

11. oppitunti III. Viittaukset. Osa. Mikä on viittaus?

C++11 lambdat: [](){} Matti Rintala

TT00AA Ohjelmoinnin jatko (TT10S1ECD)

Lohkot. if (ehto1) { if (ehto2) { lause 1;... lause n; } } else { lause 1;... lause m; } 15.3

Muistin käyttö. Muistin käyttö. Muistin käyttö. Muistin käyttö. Muistin käyttö. Muistin käyttö. Muistin käyttö C-ohjelmassa

Sisältö. 2. Taulukot. Yleistä. Yleistä

Ohjelmointi 1 Taulukot ja merkkijonot

Ohjelmointi 1 / 2009 syksy Tentti / 18.12

C++ rautaisannos. Kolme tapaa sanoa, että tulostukseen käytetään standardikirjaston iostreamosassa määriteltyä, nimiavaruuden std oliota cout:

8. Näppäimistöltä lukeminen 8.1

14. Hyvä ohjelmointitapa 14.1

Sisällys. 15. Lohkot. Lohkot. Lohkot

5. HelloWorld-ohjelma 5.1

Rakenteiset tietotyypit Moniulotteiset taulukot

2. Lisää Java-ohjelmoinnin alkeita. Muuttuja ja viittausmuuttuja (1/4) Muuttuja ja viittausmuuttuja (2/4)

T Olio-ohjelmointi Osa 5: Periytyminen ja polymorfismi Jukka Jauhiainen OAMK Tekniikan yksikkö 2010

Harjoitus Olkoon olemassa luokat Lintu ja Pelikaani seuraavasti:

Ohjelmoinnin perusteet Y Python

Lyhyt kertaus osoittimista

Yleistä. Nyt käsitellään vain taulukko (array), joka on saman tyyppisten muuttujien eli alkioiden (element) kokoelma.

815338A Ohjelmointikielten periaatteet Harjoitus 4 vastaukset

Ohjelmoinnin jatkokurssi, kurssikoe

Harjoitus 5 (viikko 48)

7. Oliot ja viitteet 7.1

Osa III. Olioiden luominen vapaalle muistialueelle

Oliosuunnitteluesimerkki: Yrityksen palkanlaskentajärjestelmä

ITKP102 Ohjelmointi 1 (6 op), arvosteluraportti

Ohjelmointitaito (ict1td002, 12 op) Kevät Java-ohjelmoinnin alkeita. Tietokoneohjelma. Raine Kauppinen

Tässä dokumentissa kuvataan Keimo-projektissa sovellettavia ohjelmointikäytäntöjä. Päivämäärä Projektiryhmä Keimo

Periytyminen. Luokat ja olio-ohjelmointi

19. Olio-ohjelmointia Javalla 19.1

AS C-ohjelmoinnin peruskurssi 2013: C-kieli käytännössä ja erot Pythoniin

Javan perusteita. Janne Käki

Taulukot. Jukka Harju, Jukka Juslin

Sisällys. Metodien kuormittaminen. Luokkametodit ja -attribuutit. Rakentajat. Metodien ja muun luokan sisällön järjestäminen. 6.2

Chapel. TIE Ryhmä 91. Joonas Eloranta Lari Valtonen

Muuttujien roolit Kiintoarvo cin >> r;

Operaattoreiden uudelleenmäärittely

Sisällys. 9. Periytyminen Javassa. Periytymismekanismi Java-kielessä. Periytymismekanismi Java-kielessä

Ohjelmoinnin peruskurssien laaja oppimäärä

Sisällys. 9. Periytyminen Javassa. Periytymismekanismi Java-kielessä. Periytymismekanismi Java-kielessä

Ohjelmoinnin perusteet, syksy 2006

Dynaaminen muisti. Pasi Sarolahti Aalto University School of Electrical Engineering. C-ohjelmointi Kevät 2017.

Moduli 5: Kehittyneitä piirteitä

Ohjelmassa henkilön etunimi ja sukunimi luetaan kahteen muuttujaan seuraavasti:

Osoittimet. Mikä on osoitin?

Luokan operaatiot. Osoittimet ja viittaukset luokan olioihin

Lohkot. if (ehto1) { if (ehto2) { lause 1;... lause n; } } else { lause 1;... lause m; } 16.3

14. oppitunti. Operaattorin ylikuormitus. Osa. Operaattorin ylikuormittaminen

Transkriptio:

C-kielestä C++-kieleen C-kielestä C++-kieleen tottuminen kestää jonkin aikaa itse kultakin, mutta harmaantuneille C- ohjelmoijille prosessi voi olla erityisen hermoja raastava. Koska C-kieli on tehokkaasti C++-kielen osajoukko, kaikki vanhat C-temput toimivat edelleen, mutta ei enää ole sopivaa käyttää monia niistä. Esimerkiksi osoitin osoittimeen näyttää C++-ohjelmoijien mielestä hieman hassulta. Miksi ei ole käytetty viittausta osoittimeen? C on suhteellisen yksinkertainen kieli. Se sisältää itse asiassa vain makroja, osoittimia, struktuureja, taulukoita ja funktioita. Oli ongelma mikä tahansa, niin ratkaisu johtaa aina makroihin, osoittimiin, struktuureihin, taulukoihin ja funktioihin. C++-kielessä asia on toisin. Makrot, osoittimet, rakenteet ja taulukot ovat edelleen tallella, mutta niin ovat myös yksityiset ja suojatut jäsenet, funktioiden kuormittaminen, oletusparametrit, muodostinfunktiot ja tuhoajafunktiot, käyttäjän määrittelemät operaattorit, avoimet funktiot, viittaukset, ystävät, mallit, poikkeukset, nimiavaruudet ja paljon muuta. C++-kielen suunnittelutila on paljon rikkaampi kuin C-kielessä: tarjolla on yksinkertaisesti paljon enemmän harkittavissa olevia vaihtoehtoja. Kun vastaan tulee tällainen määrä vaihtoehtoja, monet C++-ohjelmoijat lyyhistyvät ja pysyttelevät tiukasti kiinni siinä, mihin ovat tottuneet. Tämä ei useimmiten ole mikään suuri synti, mutta eräät C-tottumukset toimivat C++-kielen hengen vastaisesti. Näiden tottumusten vain täytyy poistua. Kohta Kohta 1: Suosi const- ja inline-avainsanoja #defineesikääntäjäkomennon sijasta. Tätä Kohtaa voitaisiin paremmin kutsua otsikolla "suosi kääntäjää esikääntäjän sijasta", koska #define-esikääntäjäkomentoa kohdellaan usein kuin se itse ei olisi ollenkaan osa kieltä. Tämä on yksi ongelmista. Kun kirjoitat jotain tämänkaltaista #define ASPECT_RATIO 1.653 voi olla, että kääntäjät eivät koskaan tule näkemään symbolista nimeä ASPECT_RATIO. Esikääntäjä voi poistaa sen ennen kuin lähdeteksti koskaan saa-

14 Kohta 1 C-kielestä C++-kieleen vuttaa kääntäjän. Tästä on tuloksena se, että ASPECT_RATIO-nimeä ei ehkä koskaan kirjoiteta symboliseen taulukkoon. Tämä voi olla hämmentävää, varsinkin, jos saat käännöksen aikana tämän vakion käyttöä koskevan virheilmoituksen, koska se voi viitata lukuun 1.653, ei ASPECT_RATIO-nimeen. Jos ASPECT_RATIO on määritetty otsikkotiedostossa, jota et kirjoittanut, sinulla ei silloin ole aavistustakaan mistä tuo 1.653 alun perin tuli, ja aikaa kuluu luultavasti hukkaan sen jäljittämisessä. Tämä ongelma voi myös tulla esiin symbolisessa virheenkäsittelijässä, koska ohjelmoitu nimi ei ehkä taaskaan ole symbolisessa taulukossa. Ratkaisu tähän valitettavaan skenaarioon on yksinkertainen ja lakoninen. Sen sijaan, että käyttäisit esikääntäjän makroa, määritä vakio: const double ASPECT_RATIO = 1.653; Tämä näkemys toimii ihanteellisesti. On kuitenkin kaksi mainitsemisen arvoista erikoistapausta. Ensinnäkin asiat voivat mutkistua, kun määritetään vakio-osoittimia. Koska vakioiden määritykset on tavallisesti sijoitettu otsikkotiedostoihin (ne sisältyvät useisiin lähdeteksteihin), on tärkeää, että osoitin esitellään const-tyyppisenä, lisättynä yleensä siihen, mihin osoitin osoittaa. Silloin, kun esimerkiksi määritetään vakio, joka on char*-pohjainen merkkijono otsikkotiedostossa, sinun pitää kirjoittaa constavainsana kahdesti: const char * const authorname = "Scott Meyers"; Jos haluat lisää tietoa const-määreen tarkoituksesta ja käytöstä varsinkin osoittimien yhteydessä, katso Kohta 21. Toiseksi luokkakohtaisten vakioiden määrittäminen on usein asianmukaista, ja tämä ohjaa hieman eri suuntaan. Jos vakion näkyvyysalue halutaan rajoittaa luokkaan, siitä pitää tehdä jäsen, ja jos haluat varmistaa, että vakiosta on ainakin yksi kopio, sinun täytyy tehdä siitä static-tyyppinen jäsen: class GamePlayer { private: static const int NUM_TURNS = 5; int scores[num_turns]; }; // vakion esittely // vakion käyttö On kuitenkin pieni ryppy, ja se on, että se minkä näet alla, on NUM_TURNS-vakion esittely, ei määritys. Luokan staattiset jäsenet täytyy silti määrittää toteutustiedostossa: const int GamePlayer::NUM_TURNS; // pakollinen määritys; // sij. luokan toteut.tied. Sinun ei kannata menettää yöuniasi murehtiessasi tätä yksityiskohtaa. Jos unohdat määrityksen, linkkerisi pitäisi muistuttaa sinua.

C-kielestä C++-kieleen Kohta 1 15 Vanhemmat kääntäjät eivät ehkä hyväksy tätä kielioppia, koska alustavan arvon määritys sen esittelyvaiheessa oli aikoinaan laitonta luokan staattiselle jäsenelle. Luokan sisäinen alustus sallitaan lisäksi vain sisäisille tyypeille (eli int, bool, char jne), ja vain vakioille. Niissä tapauksissa, joissa yllä olevaa kielioppia ei voida käyttää, alustusarvo sijoitetaan määrityksen vaiheessa: class EngineeringConstants { // tämä sijoitetaan luokan private: // otsikkotiedostoon static const double FUDGE_FACTOR; }; // tämä sijoitetaan luokan toteutustiedostoon const double EngineeringConstants::FUDGE_FACTOR = 1.35; Tämä riittää useimmissa tapauksissa. Ainoa poikkeus on silloin, kun tarvitset luokkavakion arvon luokan kääntämisen aikana, kuten silloin, kun esittelet yllä olevan taulukon GamePlayer::scores-funktion (tällöin kääntäjien pitää tietää taulukon koko käännöksen aikana). Tällöin hyväksytty tapa hyvittää kääntäjille, jotka (väärin) kielsivät luokan sisäisen alustavien arvojen määrityksen kokonaisuuteen kuuluville luokkavakioille, on käyttää sitä, mikä lempeästi tunnetaan termillä "the enum hack". Tämä tekniikka hyötyy siitä tosiseikasta, että luetellun tyypin arvoja voidaan käyttää siellä, missä odotetaan int-tyyppisiä arvoja, joten GamePlayer olisi aivan hyvin voitu määrittää tällä tavalla: class GamePlayer { private: enum { NUM_TURNS = 5 }; int scores[num_turns]; }; // "the enum hack" tekee // NUM_TURNS symbolisen nimen // arvolle 5 // toimii Sinun ei pitäisi tarvita käyttää "enum hack" -käsitettä, paitsi jos työskentelet pääasiassa historiallisesti kiinnostavilla kääntäjillä (eli niillä, jotka on kirjoitettu ennen vuotta 1995). Kannattaa silti tietää miltä se näyttää, koska on aika yleistä törmätä siihen lähdetekstissä, joka on päivätty näihin aikaisempiin, yksinkertaisempiin aikoihin. Jos palaamme esikääntäjään, toinen yleinen #define-esikääntäjäkomennon (väärin)käyttö tapahtuu silloin, kun toteutetaan makroja, jotka näyttävät funktioilta, mutta niille ei koidu kustannuksia funktiokutsun kutsumisesta. Kanoninen esimerkki tästä on kahden luvun enimmäisarvojen laskeminen:

16 Kohta 1 C-kielestä C++-kieleen #define max(a,b) ((a) > (b)? (a) : (b)) Tämä esimerkki sisältää niin paljon epäkohtia, että pelkkä ajatteleminen tuottaa tuskaa. Olisi parempi leikkiä moottoritiellä ruuhka-aikana. Aina kun kirjoitat tämänkaltaisen makron, sinun pitää muistaa, että kun kirjoitat makron runkoa, sijoita kaikki argumentit sulkeisiin; muuten voit joutua vaikeuksiin, kun makroa kutsutaan lauseella. Mutta vaikka se menisikin oikein, katso mitä outoja asioita voi tapahtua: int a = 5, b = 0; max(++a, b); // a:n arvoa kasvatetaan // kaksi kertaa max(++a, b+10); // a:n arvoa kasvatetaan // kerran Se, mitä tässä tapahtuu max-funktion sisällä olevalle a-muuttujan arvolle, riippuu siitä, mihin sitä verrataan! Onneksi tämän kaltaista järjettömyyttä ei tarvitse sietää. Voit saada makron kaiken tehokkuuden sekä odotettavan käyttäytymisen ja säännöllisen funktion tyyppiturvallisuuden käyttämällä avoimena määritettyä funktioa (katso Kohta 33): inline int max(int a, int b) { return a > b? a : b; } Yllä oleva ei ole ihan sama kuin edellä oleva makro, koska max-funktion tätä versiota voidaan kutsua vain int-tyypeillä, mutta malli korjaa tuon ongelman aika mukavasti: template<class T> inline const T& max(const T& a, const T& b) { return a > b? a : b; } Tämä malli luo kokonaisen funktioperheen, joista jokainen vastaanottaa kaksi oliota, jotka ovat muunnettavissa samaan tyyppiin ja palauttavat viittauksen (eli vakioversion) suurempaan kahdesta oliosta. Koska et voi tietää, mitä tyyppiä T-luokka tulee olemaan, välität ja palautat tehokkuuden takia viittauksen (katso Kohta 22). Muuten, ennen kuin harkitset mallien kirjoittamista yleisesti ottaen käytännöllisille funktioille, kuten max, tarkista ovatko ne jo standardikirjastossa (katso Kohta 49). Tulet esimerkiksi max-funktion kohdalla iloisesti yllättymään siitä, että voit levätä laakereillasi: max on osa standardia C++-kirjastoa. Kun otetaan huomioon const-avainsanojen ja avointen funktioiden (inlineavainsana) saatavuus, esikääntäjän tarpeellisuus vähenee, mutta sen tarve ei poistu kokonaan. Se päivä on vielä kaukana, jolloin voit hylätä kokonaan #include- ja #ifdef/#ifndef-komennot, ja ne näyttelevät tärkeää roolia jatkossakin ohjatessaan kääntämistä. Esikääntäjä ei tule vielä jäämään eläkkeelle, mutta sinun tulee ehdottomasti antaa sille pitempiä ja säännöllisempiä lomia.

C-kielestä C++-kieleen Kohta 2 17 Kohta 2: Suosi <iostream>-otsikkotiedostoa <stdio.h>otsikkotiedoston sijasta. Kyllä, ne ovat siirrettävissä. Ne ovat myös tehokkaita. Tiedät myös jo, kuinka niitä käytetään. Kyllä kyllä kyllä. Niin palvottuja kuin ne ovatkin, tosiasia on, että scanffunktio, printf-funktio ja muut vastaavat funktiot kaipaavat hieman parannusta. Erityisseikkana mainittakoon, että ne eivät ole tyyppiturvallisia eivätkä laajennettavissa. Koska tyyppiturvallisuus ja laajennettavuus on C++-elämäntavan kulmakiviä, niiden käytöstä voitaisiin aivan hyvin luopua. Sitä paitsi, printf/scanf-perheen funktiot erottavat ne muuttujat, jotka tullaan lukemaan tai kirjoittamaan muotoiluinformaatiosta, joka ohjaa lukemista ja kirjoittamista, aivan kuten FORTRAN-kielessä. On aika jättää haikeat jäähyväiset 1950-luvulle. Ei ole yllätys, että printf/scanf -funktioiden heikkoudet ovat operator>>ja operator<<-funktioiden vahvoja puolia. int i; Rational r; // r on suhdeluku cin >> i >> r; cout << i << r; Kun tämä koodi käännetään, täytyy käyttää operator>>- ja operator<<funktioita, jotka toimivat sen olion kanssa, joka on Rational-tyyppinen. Jos nämä funktiot puuttuvat, seurauksena on virhe. (Int-tyyppisten muuttujien versiot ovat standardi.) Kääntäjät huolehtivat lisäksi myös siitä, mitä versioita operaattoreista käytetään kutsuttaessa eri muuttujia, joten sinun ei tarvitse murehtia, onko ensimmäinen luettava ja kirjoitettava olio int-tyyppinen ja toinen Rational-tyyppinen. Luettavat oliot käydään lisäksi läpi käyttämällä samaa lauseopillista muotoa kuin ne, jotka kirjoitetaan. Sinun ei siis tarvitse muistaa typeriä sääntöjä, kuten silloin, kun käytät scanf-funktioita. Tällöinhän sinun täytyy ottaa huomioon osoite, jos sinulla ei vielä ole osoitinta, mutta jos sinulla jo on osoitin, sinun täytyy varmistaa, että et määritä osoitetta. Anna C++-kääntäjien huolehtia näistä yksityiskohdista. Niillä ei ole parempaa tekemistä ja sinulla on muutakin tekemistä. Huomaa lopuksi, että sisäänrakennetut tyypit, kuten int, luetaan ja kirjoitetaan samalla tavalla kuin käyttäjän määrittelemät tyypit, kuten Rational. Yritäpä tuota käyttämällä scanf-funktiota ja printf-funktiota! Tässä on tapa, jolla voisit kirjoittaa tulostusrutiinin luokalle, joka edustaa suhdelukuja: class Rational { public: Rational(int numerator = 0, int denominator = 1);

18 Kohta 2 C-kielestä C++-kieleen private: int n, d; // osoittaja ja nimittäjä friend ostream& operator<<(ostream& s, const Rational& r); }; ostream& operator<<(ostream& s, const Rational& r) { s << r.n << / << r.d; return s; } Tämä versio Operator<<-funktiosta esittää eräitä hienosyisiä (mutta tärkeitä) kohtia, joita käsitellään muualla tässä kirjassa. Esimerkiksi, operator<<-funktio ei ole jäsenfunktio (Kohdassa 19 selvitetään miksi), ja tulostettava Rational-olio välitetään operator<<-funktiolle mieluummin viittauksena const-tyyppiseen muuttujaan, kuin oliona (katso Kohta 22). Vastaava syöttöfunktio operator>> esiteltäisiin ja toteutettaisiin samalla tavalla. Vaikka olen vastahakoinen myöntämään asian, on joitakin tilanteita, jolloin olisi järkevämpää luottaa kokeiltuun ja hyväksi havaittuun. Ensinnäkin eräät iostream-toimintojen toteutukset ovat vähemmän tehokkaita kuin vastaavat C-kielen virtaustoiminnot, joten on mahdollista (joskin epätodennäköistä), että sinulla on sovellus, johon tämä vaikuttaa ratkaisevasti. Kannattaa kuitenkin muistaa, että tämä ei yleisesti kerro mitään iostream-syöttövirtatoiminnoista, vain tietyissä toteutuksissa. Toiseksi, iostream-kirjasto muutettiin eräillä perustavanlaatuisilla tavoilla silloin, kun se oli matkalla kohti standardisointia (katso Kohta 49). Niinpä sovelluksissa, joiden täytyy maksimaalisesti olla siirrettävissä järjestelmästä toiseen, voidaan huomata, että eri jälleenmyyjät tukevat erilaisia approksimaatioita standardiin. Lopuksi, koska iostream-kirjaston luokilla on muodostinfunktioita ja <stdio.h>-otsikkotiedoston funktioilla ei ole, on harvinaisia tilanteita, jotka liittyvät staattisten olioiden alustusjärjestykseen (katso Kohta 47). Silloin perus-c-kirjasto voi olla paljon käytännöllisempi yksinkertaisesti siksi, että tiedät voivasi kutsua sitä aina ilman rangaistusta. Iostream-syöttövirtakirjaston funktioiden ja luokkien tarjoama tyyppiturvallisuus ja laajennettavuus ovat paljon käytännöllisempiä asioita, kuin voisit alustavasti kuvitella, joten älä heitä niitä menemään siksi, että olet tottunut <stdio.h>-otsikkotiedostoon. Muistot ovat kuitenkin siirtymän jälkeen jäljellä. Kohdan otsikossa ei muuten ole kirjoitusvirhettä: tarkoitan todella <iostream>, en <iostream.h>. Sellaista kuin <iostream.h> ei teknisesti puhuen ole olemassa - standardointikomitea eliminoi sen suosien <iostream>-otsikkoa silloin, kun se lyhensi niiden otsikkojen nimiä, jotka eivät ole standardia C-kieltä. Tähän selitetään syy Kohdassa 49, mutta sinun kannattaa ymmärtää vain se, että jos kääntäjäsi tukee sekä <iostream> että <iostream.h> -otsikkoja (kuten todennäköistä on), otsikot ovat aavistuksen verran erilaisia. Varsinkin, jos kirjoitat komennon #include <iostream>, saat iostream-tyyppisen kirjaston elementit piilotettuna std-nimiavaruuteen (katso Kohta 28), mutta jos kirjoitat komennon

C-kielestä C++-kieleen Kohta 3 19 #include <iostream.h>, saavutat nämä samat elementit globaalissa näkyvyysalueessa. Elementtien saaminen globaalissa näkyvyysalueessa voi johtaa nimeä koskeviin ristiriitoihin, tarkasti ottaen sen kaltaisiin ristiriitoihin, joiden estämiseksi nimiavaruuksien käyttö suunniteltiin. <iostream>-sanassa on sitä paitsi vähemmän kirjoitettavaa kuin <iostream.h>-sanassa. Monille ihmisille se riittää sen suosimiseen. Kohta 3: Suosi new- ja delete-funktioita malloc- ja freefunktioiden sijasta. Ongelma, joka koskee malloc- ja free-funktioita (ja niiden muunnelmia), on yksinkertainen: ne eivät tunne muodostinfunktioita ja tuhoajafunktioita. Tutki kahta seuraavaa tapaa, joilla saat tilaa kymmenestä string-tyyppisestä oliosta koostuvalle taulukolle. Yksi käyttää malloc-funktiota ja muut new-funktioita: string *stringarray1 = static_cast<string*>(malloc(10 * sizeof(string))); string *stringarray2 = new string[10]; stringarray1 osoittaa tässä muistiin, joka riittää kymmenelle string-oliolle, mutta johon ei ole muodostettu yhtään oliota. Taulukon olioiden alustamiseen ei tämän lisäksi ole mitään muuta keinoa, kuin käyttää eräitä melko hämäriä kielellisiä kiemuroita. stringarray1 on toisin sanoen aika hyödytön. Vastakohtana sille, stringarray2 osoittaa taulukkoon, jossa on kymmenen täysin muodostettua string-olioita, joista jokaista voidaan käyttää turvallisesti missä tahansa toiminnossa, joka vastaanottaa string-tyypin. Olettakaamme, että olet joka tapauksessa taianomaisesti onnistunut alustamaan stringarray1-taulukon oliot. Myöhemmin ohjelmassa sinun odotetaan sitten tekevän jotain tämän kaltaista: free(stringarray1); delete [] stringarray2; // katso Kohdasta 5 miksi // [] ovat välttämättömät Free-funktion kutsu vapauttaa muistin, johon stringarray1 osoittaa, mutta muistissa oleviin string-olioihin ei kutsuta tuhoajafunktioita. Jos string-oliot ovat itse varanneet muistia, kuten niillä on taipumus tehdä, kaikki niiden varaama muisti menetetään. Toisaalta, kun delete-operaattoria kutsutaan stringarray2-taulukkoon, tuhoajafunktiota kutsutaan taulukon jokaiselle oliolle ennen kuin mitään muistia vapautetaan. Koska new- ja delete-funktiot vaikuttavat toisiinsa sopivasti muodostinfunktioilla ja tuhoajafunktioilla, ne ovat selvästi suositeltavampi valinta.

20 Kohta 3 C-kielestä C++-kieleen New- ja delete-funktioiden sekoittaminen malloc- ja free-funktioiden kanssa on yleensä huono ajatus. Kun yrität kutsua free-funktiota osoittimeen, jonka vastaanotit new-funktiolla tai kutsut delete-funktiota osoittimeen, jonka vastaanotit malloc-funktiolla, tulokset ovat määrittelemättömät, ja tiedämme kaikki mitä "määrittelemätön" tarkoittaa: se tarkoittaa sitä, että ohjelma toimii kehitysvaiheessa, se toimii testausvaiheessa ja koko homma räjähtää kun olet kasvotusten tärkeimpien asiakkaitesi kanssa. new/delete-funktioiden ja malloc/free-funktioiden yhteensopimattomuus voi johtaa eräisiin aika kiinnostaviin jälkivaikutuksiin. Esimerkiksi strdup-funktio, joka yleensä löytyy <string.h>-otsikosta, vastaanottaa char*-pohjaisen merkkijonon ja palauttaa kopion siitä: char * strdup(const char *ps); // pal. kop. mih. ps os. Sekä C-kieli että C++-kieli käyttävät joskus samaa versiota strdup-funktiosta, joten funktion sisältä varattu muisti on malloc-funktion seurausta. Tuloksena C++ohjelmoijat, jotka tietämättään kutsuvat strdup-funktiota, voivat jättää huomioimatta sen tosiasian, että heidän täytyy käyttää free-funktiota strdup-funktion palauttamassa osoittimessa. Mutta odota! strdup-funktio voidaan joskus päättää kirjoittaa uudelleen C++-kielellä estämällä jälkivaikutukset ja annetaan sitten tämän uudelleen kirjoitetun version kutsua funktion sisältä new-funktiota, valtuuttaen täten kutsujat käyttämään delete-funktiota myöhemmin. Kuten voit kuvitella, tämä voi johtaa eräisiin aika painajaismaisiin siirrettävyysongelmiin kun lähdetekstiä sukkuloidaan edestakaisin niiden paikkojen välillä, jotka käyttävät eri muotoa strdupfunktiosta. C++-ohjelmoijat ovat silti yhtä kiinnostuneita lähdetekstin uudelleenkäyttämisestä kuin C-ohjelmoijat. On olemassa monia C-kirjastoja, jotka perustuvat malloc- ja free-funktioille ja sisältävät paljon lähdetekstiä, joka kannattaa ehdottomasti käyttää uudelleen. Kun hyödyt tällaisesta kirjastosta, on todennäköistä, että huomaat olevasi vastuussa siitä, että sinun täytyy vapauttaa kirjaston malloc-funktion varaama muisti free-funktiolla, ja/tai varata malloc-funktiolla muistia, jonka kirjasto itse vapauttaa free-funktiolla. Hieno homma. Ei ole ollenkaan väärin kutsua C++-ohjelman sisältä malloc- ja free-funktiota, kunhan varmistat, että malloc-funktiolla saadut osoittimet kohtaavat vapahtajansa free-funktiolla ja new-funktiolla saadut osoittimet vapautuvat lopulta delete-funktiolla. Ongelmat alkavat, kun rupeat hutiloimaan ja yrität sekoittaa new-funktiota free-funktion kanssa tai mallocfunktiota delete-funktion kanssa. Kun teet näin, kaivat verta nenästäsi. Kun otetaan huomioon, että malloc ja free ovat tietämättömiä muodostinfunktioista ja tuhoajafunktioista, ja että malloc/free-funktioiden sekoittaminen new/ delete-funktioiden kanssa voi olla yhtä haihtuvaa kuin ylioppilasyhdistyksen sisäänajobileet, on parempi, että pysyttelet aina kun pystyt muita funktioita hylkivällä dieetillä, joka koostuu new- ja delete-funktioista.

C-kielestä C++-kieleen Kohta 4 21 Kohta 4: Suosi C++-tyylisiä kommentteja. C-kielen kommenteissa käytettävä vanha kunnon kielioppi toimii myös C++-kielessä, mutta C++-kielen uudelleenmuodostetulla kommentit-rivin-lopussa-kieliopilla on omat erityiset etunsa. Tutki esimerkiksi tätä tilannetta: if ( a > b ) { // int temp = a; // vaihda a ja b // a = b; // b = temp; } Tässä on lähdetekstilohko, joka on jostain kumman syystä kommentoitu, mutta huumaavana osoituksena ohjelmoinnin insinööritaidosta alkuperäisen lähdetekstin kirjoittanut ohjelmoija sisällytti siihen kommentin, joka osoitti, missä mennään. Silloin, kun C++-kielen kommentointimuotoa käytettiin lohkon kommentointiin, sisennettyä kommenttia ei otettu ollenkaan huomioon, mutta vakaviin ongelmiin olisi varmasti törmätty, jos joku olisi valinnut C-tyylisen kommentoinnin: if ( a > b ) { /* int temp = a; /* vaihda b ja a*/ a=b; b = temp; */ } Huomaa, kuinka sisennetty kommentti sijoittaa huomaamatta ennenaikaisen loppumerkin kommenttiin, jonka tarkoituksena oli kommentoida lähdetekstin lohko. C-tyylisillä kommenteilla on silti oma tehtävänsä. Ne ovat esimerkiksi korvaamattomia sekä C-kielen että C++-kielen kääntäjien muodostamissa otsikkotiedostoissa. Jos kuitenkin käytät C++-tyylisiä kommentteja, sinun kannattaa pysytellä niissä. Kannattaa korostaa, että taantuvat esikääntäjät, jotka oli kirjoitettu vain C-kielelle, eivät tiedä mitä tehdään C++-tyylisillä kommenteilla, joten alla oleva esimerkki ei aina toimi odotetulla tavalla: #define LIGHT_SPEED 3e8 // m/sek (tyhjiössä) Esikääntäjässä, jossa C++ ei ole tuttu, rivin lopussa olevasta kommentista tulee osa makroa! Kuten keskustelimme Kohdassa 1, esikääntäjää ei tietenkään pitäisi ollenkaan käyttää vakioiden määrittämiseen.