Elikkäs tässäkun tuppaa tulemaan ongelmia tämän olio-ohjelmoinnnin hanskaamisessa niin ajattelin sitten kysellä täältä kun tulee jotain astetta haastavampaa eteen.
Nyt näin alkuun pari kysymystä:
Henkilo &Henkilo::operator=(const Henkilo &p_henkilo) { // <- Virhe osoittaa tähän if (this == &p_henkilo) return (*this); nimi = p_henkilo.nimi; hetu = p_henkilo.hetu; osoite = p_henkilo.osoite; postinumero = p_henkilo.postinumero; asuinkaupunki = p_henkilo.asuinkaupunki; ika = p_henkilo.ika; syntymaaika = p_henkilo.syntymaaika; return (*this); ... definition of implicitly-declared `Henkilo& Henkilo::operator=(const Henkilo&)' }
Elikkäs mistä tuollainen virhe mahdollisesti johtuu.
Ja seuraava helpompi kysymys on se että kuinka teen koosteluokan viitauksen avulla?
Kokeilin luoda tälläistä omaan ajoneuvo luokkaan mutta sain vain virheitä aikaan joten pientä opastusta.
Elikkäs viittauksen avulla tämä toinen olio pitää alustaa tällä tavoin muodostimessa:
Ajoneuvo::Ajoneuvo() : rekkari(*(new Merkkijono)) : nimi(*(new Merkkijono))
Samalla siis hajoittimessa pitää tuhota nämä:
if (&rekkari) delete rekkari; if (&nimi) delete nimi;
Tämä ei tietenkään toiminut suoraan. Lähinnä tuo alustus kun Hietasen kirjasta en äkkiseltään löytänyt miten noita luokkia alustetaan useampi kuin yksi tuossa muodostimessa.
Mutta tässä tämän hetken ongelmat. Lisää tulee kun etenen :)
Ovatko nuo rekkari
ja nimi
tuossa osoittimia, vai normaaleja muuttujia? Ja mitä tyyppiä?
rekkari(*(new Merkkijono))
Tuon kohdan mukaan voisi luulla että ne ovat normaaleja muuttujia. Joka tapauksessa tuossa luodaan uusi Merkkijono-olio, mutta ei vapauteta sitä koskaan, mikä ei varmaankaan vastaa tarkoitusta.
if (&rekkari) delete rekkari; if (&nimi) delete nimi;
Tuosta taas olisi loogista päätellä, että ne olisivat osoittimia, koska niitä yritetään vapauttaa, mutta se taas ei täsmää esimmäisen kohdan kanssa.
Henkilo
-ongelma johtuu luultavasti siitä, että luokkamäärittelyssä ei ole esitelty tuota sijoitusoperaattoria. Koska toteutat sijoitusoperaattorin, tarvii todennäköisesti tehdä myös kopioiva konstruktori ja destruktori.
class Henkilo { public: // ... Henkilo(const Henkilo &p_henkilo); Henkilo &Henkilo::operator=(const Henkilo &p_henkilo); ~Henkilo(); // ... };
Tuosta viitesalustuksesta voi oikeastaan vain sanoa, että niin ei oikeasti tehdä. Käytä ihan tavallisia muuttujia. Jos viittauksia meinaa käyttää osoittimien tapaan, miksei sitten käyttäisi ihan vaan osoittimia?
Tuo alustussyntaksi menee näin:
Ajoneuvo::Ajoneuvo() : rekkari(*(new Merkkijono)) , nimi(*(new Merkkijono)) { }
mutta jankkaan vielä, että tuossa tehdään jotain aika älytöntä.
Henkilo &operator=(const Henkilo &p_henkilo);
piti sanomani tuossa yhdellä rivillä. :-)
Ja nyt samalla kun tuli katsottua tarkemmin, mitä tuolla Henkilo
-luokan sijoitusoperaattorissa oikein tehdään, niin eihän tuo luokka edes tarvitsisi erikseen kirjoitettua sijoitusoperaattoria, kopiointikonstruktoria eikä destruktoria. Kääntäjän generoimat oletusjutut kelpaisivat ihan hyvin.
Joo, kiitän vastauksista ja kokeilen niitä kunhan saan taas koodaus vaihteen silmään. Juuri töistä tulleena ei ole paljoa inspiraatiota :)
Tuosta viittauksesta että se ei vielä tuossa kohdassa ole oleellinen / tarpeellinen. Tuossa voi käyttää "suoraa määrittelyä" mutta myöhemmissä harkoissa tarvitsen tuota koosteolion muodostusta niin ajattelin ennakkoon kokeilla tuotakin.
Eikö tuon pitäsi luoda rekkari ja nimi oliot ajoneuvo olion sisään ja vapauttaa ne sitten ajoneuvo olion hajoittimessa? Tästä sitten olisi mahdollista myös irrottaa nuo luokat ja vaikka liittää ne jonnekkin muualle jos tarve vaatii?
Tässä tapauksessa tarpeentonta mutta kokeilinpahan vain.
Zmyrgel kirjoitti:
Eikö tuon pitäsi luoda rekkari ja nimi oliot ajoneuvo olion sisään ja vapauttaa ne sitten ajoneuvo olion hajoittimessa?
Kyllä, mutta siihen ei käytetä viitteitä vaan pointtereita. Ja jos arvoja ollaan antamassa ulospäin, ne voi välittää ihan arvoina tai const-viitteinä, ei pointtereina. C++:ssa on monta mahdollista tapaa tehdä asioita, mutta tämä kannattaa tehdä näin.
(*(new Jokin))
on ehkä järjettömin asia, jonka olen vähään aikaan nähnyt. Kannattaa hieman tutustua osoittimien ja new-operaattorin perusteisiin ja miettiä sitten, mitä tulikaan tehtyä. Ilman *-merkkiä tuo olisi jokseenkin järkevä ajatus.
if (&A) delete A;
on toinen järjetön lause. &A on aina tosi, koska se palauttaa A:n sijainnin muistissa, ja kyllähän A muistista löytyy. Ilman &-merkkiä tuostakin voisi jotain järkevää tulla.
Tuo esimerkki on tehty Hietasen C++ ja olio-ohjelmointi -oppaan mukaan. Nopeasti kattelin että se olisi noin mahdollista suorittaa, ja sitten tietysti myös pointtereilla. Pitää perehtyä vielä tarkemmin asiaan kun on aikaa.
(*(new Olio))
kyllä periaatteessa "toimii", mutta muistin vapauttamatta jättäminen on aika paha ohjelmointivirhe. if(&A)
on myös C++ syntaksin mukainen merkintä, mutta, kuten Metabolix sanoi, siinä ei ole mitään järkeä; "&A != NULL
" on aina tosi lause.
Sanotaan nyt vielä, että "B = (*(new Olio));
" on käytännössä sama kuin tämä:
Olio * A = new Olio; // Luodaan osoittimeen A uusi Olio Olio B = *A; // Luodaan Olio B ja laitetaan sen arvoksi sama kuin A:n osoittaman Olion.
Ei, kun tässä oli puhe viitteistä, joten tuo tyhmä temppu
Ajoneuvo::Ajoneuvo() : rekkari(*(new Merkkijono)) , nimi(*(new Merkkijono)) {}
vastaa osapuilleen koodia
Merkkijono *tmp = new Merkkijono; Merkkijono &rekkari = *tmp; // huom. arvoa ei kopioida tmp = new Merkkijono; // ihan uusi arvo jossain toisaalla Merkkijono &nimi = *tmp; // eikä kopioida nytkään
Destruktoripuolella on syntaksivirhe ja turha if
(deletoitava osoite saa olla ihan hyvin null, delete
hoitaa tarkastuksen ihan itsekin), joten koodin pitäisi olla
delete &nimi; delete &rekkari;
En ole Hietasen kirjaa lukenut enkä tajua, mitä siellä oikein näitten viitteiden kanssa meinataan. Alunperin viitteet keksittiin C++:aan, jotta operaattoreiden overloadaus saatiin pelaamaan. Noissa esimerkeissä on tyhmää se, että ne sotii järkevää C++-käytäntöä vastaan. C++ ei takaa viitteen olevan jotain muuta kuin null. Kielen puolesta voi siis olla
Olio &a = //... jotakin, vaikka *(Olio *)0; if (&a == 0) // tämä voi olla true
Järkevän käytännön mukaan pitää kuitenkin ohjelmoida ja voida olettaa, että viite ei koskaan ole null. Jos tuossa yllä siis tuo if
on true
, joku älypää on tössinyt ja siltä pitäisi tervata pe..., krhm, yksi paikka.
Jos siis haluaa pelata dynaamisen muistin kanssa, pitää käyttää pointtereita ja nuo pätkät pitää koodata näin (auto_ptr
siksi, että joskus voi tulla exceptioneita):
class Ajoneuvo { Merkkijono *rekkari; Merkkijono *nimi; // ... }; Ajoneuvo::Ajoneuvo() { auto_ptr<Merkkijono> r(new Merkkijono); auto_ptr<Merkkijono> n(new Merkkijono); rekkari = r.release(); nimi = n.release(); } Ajoneuvo::~Ajoneuvo() { delete nimi; delete rekkari; }
Jos taas tekee homman kaikin puolin järkevästi, niin käyttää tavallisia jäsenmuuttujia, niin eipä tarvi sitten pahemmin konstruktoreita ja destruktoreita edes kirjoitella:
class Ajoneuvo { Merkkijono rekkari; Merkkijono nimi; // ... };
Näin on näreet. Tulihan taas pitkä sepustus, mutta korpee kun Lopullinen Totuus ei meinaa muuten tulla selväksi. :-)
Hyvä on sitten. Käytetään noita osoittimia.
Elikkäs onko tämä seuraava pätkä oikein?
class Vuokraus { private: PVM *apaiva; PVM *lpaiva; Henkilo *tilaaja; Ajoneuvo *vuokrattu; ... Vuokraus::Vuokraus() { apaiva = new PVM; lpaiva = new PVM; tilaaja = new Henkilo; vuokrattu = new Ajoneuvo; } Vuokraus::~Vuokraus() { delete apaiva; delete lpaiva; delete tilaaja; delete vuokrattu; }
Mutta sitten ihan uuteen ongelmaan. Minulla on Lasku -niminen luokka jossa on noilla pointtereilla muutama muuttuja määritelty. Nyt ohjelmaa kääntäessä se valittaa että tuosta Lasku luokasta liitetyssä tiedostossa virhe ja ohjelma viittaa PVM luokassa Lataa ja Tallenna metodien esittelyyn missä ei ole mitään virhettä ollut aikaisemmin. Virheet sinälläänkin ovan mielenkiintoisia sillä ne valittavat että Talleta ja Lataa on määritetty palauttamaan void arvo ja "Lataa(fstream &)" rivillä tulisi olla ";" ennen ensimmäistä sulkua.
Jos tästä joku saa selvää niin arvostaisin.
Oli muuten siinä henkilo luokassa se että en tosissaan ollut muistanut esitellä sitä metodia mutta nyt kun sen esittelin niin kääntäjä valitti että se olisi esitetty liian usein koodissa.
EDIT:
In file included from Lasku.hpp:4,from Lasku.cpp PVM.hpp variable or field `Talleta' declared void PVM.hpp expected `;' before '(' token PVM.hpp variable or field `Lataa' declared void PVM.hpp expected `;' before '(' token Makefile.win [Build Error] [Lasku.o] Error 1
Ja PVM luokassa nuo rivit ovat:
void Talleta(fstream &) const; void Lataa(fstream &);
Olethan muistanut laittaa määrittelyihin sen voidin eteen? Eli pelkkä "Lataa(fstream&);" ei riitä, vaan siinä pitää olla edessä palautusarvon tyyppi tai void. Ja mielellään suoraan kopioituna ne virheet, kun niistä usein tulee paremmin rutiinilla vastaus kuin käännöksistä...
Ja sitten tuon yllä olevan lisäksi tuo henkilo luokan virhe:
multiple definition of `Henkilo::operator=(Henkilo const&)' ... // Ja itse luokka #ifndef HENKILO_HPP #define HENKILO_HPP #include "Merkkijono.hpp" #include "PVM.hpp" using namespace std; class Henkilo { private: Merkkijono *nimi; Merkkijono *hetu; Merkkijono *asema; Merkkijono *osoite; int postinumero; Merkkijono *asuinkaupunki; PVM ika; PVM syntymaaika; public: Henkilo(); Henkilo(const Henkilo &kopio); Henkilo &operator=(const Henkilo &p_henkilo); ~Henkilo(); void Kysy(); void AsetaNimi(const Merkkijono &p_nimi); void AnnaNimi(const Merkkijono &p_nimi); void Nayta(); void Lataa(fstream &); void Talleta(fstream &) const; }; #endif
Zmyrgel, ehdottaisin kuitenkin vielä, että käyttäisit luokissa tavallisia jäsenmuuttujia osoitinten (ja etenkin viitteiden) sijasta. Vaikuttaisi siltä, että et ole tekemässä mitään, mikä erityisesti vaatisi osoitinten käyttöä ja osoittimet tuntuvat vain aiheuttavan enemmän ongelmia kuin mitä ne ratkaisevat.
Tuo class Vuokraus
-pätkä on periaatteessa oikein, mutta siinä on tosi iso mutta: exception safety elikkä poikkeusturvallisuus. Mitä tapahtuukaan, jos luokan konstruktorissa vaikkapa new Ajoneuvo;
epäonnistuu? Sehän paiskoo exceptionin, tietty, mutta sielläpä ei näytä olevan ketään siivoamassa pois aiemmin luotuja otuksia. Jos käyttää ihan tavallisia jäsenmuuttujia, koko juttua ei tarvi edes murehtia. Rautalangasta: ei tarvisi kirjoittaa kopioivaa konstruktoria, ei sijoitusoperaattoria eikä destruktoria.
Noita muita virheitä on pikkasen hankala arvailla, kun näkösällä on vain joko kääntäjän virheilmoitukset ilman todellista koodia tai koodi ilman todellista virheilmoitusta. Olisiko niin, että kääntäjä pohjimmiltaan valittaa, ettei std::fstream
ole esitelty? Siis jostakin puuttuu rivi #include <fstream>
tai vähintäänkin #include <iosfwd>
?
Multiple definition
-virhe tarkoittanee yksinkertaisesti, että tuo juttu on todellakin kirjoitettu kahteen kertaan jossakin.
Voisi muuten olla ihan hyvä ajatus määritellä nuo lataus- ja talletusfunktiot näin: void Lataa(std::istream &)
ja void Talleta(std::ostream &) const
.
koo kirjoitti:
Zmyrgel, ehdottaisin kuitenkin vielä, että käyttäisit luokissa tavallisia jäsenmuuttujia osoitinten (ja etenkin viitteiden) sijasta. Vaikuttaisi siltä, että et ole tekemässä mitään, mikä erityisesti vaatisi osoitinten käyttöä ja osoittimet tuntuvat vain aiheuttavan enemmän ongelmia kuin mitä ne ratkaisevat.
Kouluharkka ja tehtäväanto antaa pieniä määreitä kuinka suoritetaan. Menee hankalamman kautta niin oppiipa ainakin :)
lainaus:
Tuo
class Vuokraus
-pätkä on periaatteessa oikein, mutta siinä on tosi iso mutta: exception safety elikkä poikkeusturvallisuus. Mitä tapahtuukaan, jos luokan konstruktorissa vaikkapanew Ajoneuvo;
epäonnistuu? Sehän paiskoo exceptionin, tietty, mutta sielläpä ei näytä olevan ketään siivoamassa pois aiemmin luotuja otuksia. Jos käyttää ihan tavallisia jäsenmuuttujia, koko juttua ei tarvi edes murehtia. Rautalangasta: ei tarvisi kirjoittaa kopioivaa konstruktoria, ei sijoitusoperaattoria eikä destruktoria.
Ööö, kävisikö siihen sitten tuo aiemmin esitelty Autoptr? Pitää vissii lukassa tarkemmin sitä käsittelevä kappale opuksesta.
lainaus:
Noita muita virheitä on pikkasen hankala arvailla, kun näkösällä on vain joko kääntäjän virheilmoitukset ilman todellista koodia tai koodi ilman todellista virheilmoitusta. Olisiko niin, että kääntäjä pohjimmiltaan valittaa, ettei
std::fstream
ole esitelty? Siis jostakin puuttuu rivi#include <fstream>
tai vähintäänkin#include <iosfwd>
?
Multiple definition
-virhe tarkoittanee yksinkertaisesti, että tuo juttu on todellakin kirjoitettu kahteen kertaan jossakin.Voisi muuten olla ihan hyvä ajatus määritellä nuo lataus- ja talletusfunktiot näin:
void Lataa(std::istream &)
javoid Talleta(std::ostream &) const
.
Joo, nuo muut ratkesi kun pikkasen väsäsin tuota koodia eteenpäin, en edes tiedä mikä siinä loppujen lopuksi oli mutta nyt koodi kääntyy kunnolla. Pitää jatkaa rakentelua ja hieman optimoida tuota koodia. Testiohjelma kaatuu ekaan syötteeseen mutta se ei tullut yllätyksenä, sen näköinen syötteen käsittely jos olisi toiminut niin olisin yllättynyt. Mutta kiitokset taas opastuksesta. Postaan lisää ongelmakohtia kun niitä ilmestyy.
Zmyrgel kirjoitti:
Kouluharkka ja tehtäväanto antaa pieniä määreitä kuinka suoritetaan. Menee hankalamman kautta niin oppiipa ainakin :)
Ei se, että tekee kaiken mahdollisimman hankalasti opeta hyvää ohjelmointia. Kuten koo sanoi, pointtereiden käyttö aiheuttaa tässä tapauksessa vain ylimääräisiä ongelmia, ja kopiokonstruktoria sekä sijoitusoperaattoria ei tarvitse tehdä, koska kääntäjä tekee täsmälleen samanlaiset automaattisesti.
Vertaa kahta seuraavaa koodinpätkää, jotka tekevät saman asian.
int *a, *b, *c; a = new int; b = new int; c = new int; if(a==NULL || b==NULL || c==NULL) { cerr << "Virhe.\n"; return 1; } cin.operator>>(*a); cin.operator>>(*b); *c = *a + *b; cout.operator<<(*c); delete a; delete b; delete c;
ja
int a, b; cin >> a >> b; cout << a+b;
Kumpi on havainnollisempi ja kumpi alttiimpi virheille?
os kirjoitti:
Ei se, että tekee kaiken mahdollisimman hankalasti opeta hyvää ohjelmointia. Kuten koo sanoi, pointtereiden käyttö aiheuttaa tässä tapauksessa vain ylimääräisiä ongelmia, ja kopiokonstruktoria sekä sijoitusoperaattoria ei tarvitse tehdä, koska kääntäjä tekee täsmälleen samanlaiset automaattisesti.
No, kuten yllä jossain vaiheessa huomautin niin tarvitsen ko. tietoa myöhemmin rakennellessa tätä ohjelmaa. Ajattelin lähinnä että yrittää sisäistää tuon toiminnan itse tekemällä ja sitten kun osaa sen niin tekee sitten vaikka tuon yksinkertaisemmin mallin mukaan.
lainaus:
Kumpi on havainnollisempi ja kumpi alttiimpi virheille?
Ja kummasta on mahdollista oppia esim. pointtereiden käyttöä? Huono esimerkki mutta kuitenkin. Asiayhteys pitää ottaa huomioon. Mutta väittely sikseen, on uusien ongelmien aika.
Elikkäs seuraavissa seteissä olisi ongelmia:
//fstream tdsto("tiedot.dat", ios::binary | ios::in); //std::ostream save("file.dat", ios::binary | ios::in); //std::istream load("file.dat", ios::binary | ios::out);
fstream toimii jos käytän std nimiavaruutta mutten saanut muuten sitä toimimaan. Kokeilin siinä std:: etuliitettä mutta ei. En kyllä usko että se kuuluu edes siihen nimiavaruuteen. Yritän saada koodin toimimaan ilman nimiavaruuksien käyttöä kun niin aina joka puolella kehotetaan tekemään. Eli mikä se yllä olevalle setille sitten olisi? Pelkkä std::fstream ei toimi. Mitenkä, onko nuo sen alla olevat rivit oikein?
Ja sitten poikkeuksista. Luokkien määrittelyyn on lisätty "class Dynerr{};" jota sitten pääohjelmasta kutsun jos ohjelmassa tulee ongelmia dynaamisen muistinvarauksen kanssa . Ongelmana, kuinka saan nuo kutsut toimimaan kerralla:
//catch(Firma::Dynerr; Henkilo::Dynerr; Lasku::Dynerr; Merkkijono::Dynerr; Varasto::Dynerr; Vuokraus::Dynerr) //{ // std::cerr << "Dynaamisen tietojäsenen alustus epäonnistui.\n"; //}
Ylläoleva ei tietenkään toimi tuollein joten kysynkin oikeaa syntaksi tuolle. Lähinnä ettei tarvitsisi laittaa jokaista erikseen. Tietääkseni noista voisi tehdä oman luokan mutta olkoon nyt toistaiseksi vain pääohjelmassa.
Ja vielä noista dynaamisista taulukoista. Eli taas syntaksia kaivataan.
//Määritelty: Laskurivi *rivit[30]; //Alustettu rivit[30] = new Laskurivi; //Poistettu if (rivit) delete [] rivit; rivit = NULL; //MIKSI EI TOIMI?
Elikkäs ongelma on tuossa poistossa johon ohjelma antaa seuraavaa:
incompatible types in assignment of `int' to `Laskurivi*[30]'
try { // kooodia } catch(X::Dynerr) { }
Ja mieleen taas juolahti että tuohon poikkeustilanteeseen pitää varmaan vielä katsella muistinhallinnan kannalta jotain settiä ettei jää mitään jämiä muistiin mutta se taitaa hoitua ihan manuaalisesti delete komennon avulla. Lähinnä tuossa yllä ongelmana on se että en saa noita yhdelle riville laitettua. Luulisin että siihenkin on keino mutta ei tule itselle mieleen mikä.
Ja noista pointtereiden käytöstä että kyllä ne tuossa henkilo-luokassa ovat turhaan käytössä mutta ohjelmassa on tällä hetkellä sellaiset 10 luokkaa niin osassa niistä muissa on aika pakollistakin käyttää noita pointtereita niin ajattelin ympätä ne tuohon niin tulee kysyttyä samassa pakettissa noita asioita.
Pitää vielä kokeilla kun pääsen taas koodin äären että vaikuttaako tuo rivit = NULL kohta tuohon kun siinä ei oteta tuota taulukkoa huomioon.
Tämä juttu alkaa vähän levitä käsiin. Olisikohan syytä aloittaa uusi aihe tai vaikka kolme? :-)
Onko koodissa varmasti #include <fstream>
? Kyllä std::fstream
silloin toimii, ellet sitten käytä jotakin ei-standardinmukaista kääntäjää viime vuosituhannelta.
Juttu on kyllä niin, että asiat nimenomaan kannattaa tehdä nimiavaruusmääritysten kanssa, ei ilman niitä.
std::istream
ja std::ostream
toimivat, kunhan jossakin on vastaavasti #include <istream>
ja #include <ostream>
. Joidenkin juttujen esittelyihin riittää kyllä myös #include <iosfwd>
, mutta varsinaiseen käyttöön ei.
std::istream
ja std::ostream
ovat streamien kantaluokkia, ei itsenäisiä otuksia. Funktiot kannattaa tehdä niin, että ne ottavat parametrinaan viitteet näihin kantaluokkiin, niin silloin funktioiden kutsuja saa päättää, luetaanko ja kirjoitetaanko lopulta tiedostoja, stringejä, näyttöä+näppistä jne., eli kutsuja antaa parametrina johdettua luokkaa olevan otuksen.
Exceptioneitten nappaaminen yhdellä catch
illä ei onnistu tuumimallasi tavalla. Jos nuo ovat eri exceptioneita, niillä pitää olla eri catch
it. Yksi vaihtoehto on johtaa nuo exceptionit samasta kantaluokasta ja tehdä catch
vain kantaluokalle tai sitten käyttää vain yhtä ja samaa exceptionia kaikissa tapauksissa.
Exceptioneitten kanssa pitää kyllä miettiä, mitä virhetilanteissa voi lopulta tehdä. Kannattaako rakennella kovin huikeita kehitelmiä, jos fakta on lopulta "muisti lopahti"? Ja tätä varten new
paiskaa std::bad_alloc
in, siihen ei omia juttuja paljon edes tarvi.
Sellainen exception-vinkki on sitten vielä, että exception pitää paiskata arvona ja siepata viitteenä.
try { // ... throw MyException(); } catch (MyException &e) { // ... }
Exceptioneiden yhteydessä kaikki tavalliset muuttujat ja luokat siivotaan automaattisesti. Yksi näihin pointtereihin liittyvä ongelma onkin, että dynaamisesti varatut jutut pitää siivota enemmän tai vähemmän manuaalisesti. Siksi kannattaa käyttää std::auto_ptr
ia.
Tuo taulukkojuttu menee niin, että luot 30-alkioisen (ei siis mitenkään dynaamisen) taulukon, jonka alkiot ovat osoittimia laskuriveihin. Sitten luot dynaamisesti yhden laskurivin ja yrität sijoittaa sen osoitteen taulukon loppuun niin, että se menee taulukosta yhdellä yli (taulukon viimeinen kelvollinen indeksi on 29). Lopuksi tarkistat, että taulukon osoite ei ole null, mikä on aina tosi, ja yrität tuhota taulukkoa jota ei edes ole varattu dynaamisesti (taulukon osoittamia laskurivejä ei yritetäkään tuhota). Viimeiseksi yrität asettaa taulukon osoitteeksi nullin, mikä ei voi onnistua, koska taulukko on taulukko, ei osoitinmuuttuja. Tässä siis lyhykäisesti se, että miksi ei toimi.
Miten tuo sitten pitäisi tehdä? Oikea vastaus: std::deque
. Mutta arvaan, että tässä nyt pitää kuitenkin voimistella noiden osoittimien kanssa, joten
int const lkm = 30; Laskurivi *rivit[lkm]; // alustus for (int i = 0; i < lkm; ++i) { try { rivit[i] = new Laskurivi; } catch (...) { for (int j = i-1; j >= i; --j) delete rivit[j]; throw; } } // poisto for (int i = lkm-1; i >= 0; --i) delete rivit[i];
lainaus:
fstream toimii jos käytän std nimiavaruutta mutten saanut muuten sitä toimimaan. Kokeilin siinä std:: etuliitettä mutta ei. En kyllä usko että se kuuluu edes siihen nimiavaruuteen
Olisiko niin, että olet sisällyttänyt vanhan otsikkotiedoston:
#include <fstream.h>
Jos näin on niin muuta se uudempaan ja suositeltavampaan muotoon:
#include <fstream>
Nuo aikaisemmin käytetyt .h-otsikkotiedostot eivät tietääkseni nimenomaan määrittele niitä asioita std-nimiavaruuteen, kuten uudemmissa versioissa tapahtuu.
lainaus:
Ja vielä noista dynaamisista taulukoista. Eli taas syntaksia kaivataan.
//Määritellään laskurivi-osoitin Laskurivi* rivit; //Alustetaan 30-paikkainen taulukko, ja laitetaan rivin osoittamaan siihen rivit = new Laskurivi[30]; //Poistetaan if( rivit ) { delete [] rivit; rivit = NULL; } // tätäkö meinasit? alkuperäisessä jälkimmäinen lause suoritettin joka tapauksessa
Muoks. höö, myöhässä... :( Muutenkin tuossa ylemmässä selitetty paremmin.
Noista tiedostoista vielä:
Zmyrgel kirjoitti:
std::ostream save("file.dat", ios::binary | ios::in); std::istream load("file.dat", ios::binary | ios::out);
koon mainitsemat istream
ista ja ostream
ista periytetyt tiedostoluokat ovat ifstream
ja ofstream
.
Kannattaa perehtyä näihin luokkahierarkioihin vaikkapa osoiteessa http://www.cplusplus.com/ref/iostream/
Toimiva koodi on siis:
std::ofstream save("file.dat", ios::binary | ios::out); std::ifstream load("file.dat", ios::binary | ios::in);
Ja mitä pointtereihin ja oppimiseen tulee, tarkoitin vain, että pointtereiden käyttöä kannattaa opetella sellaisissa yhteyksissä, joissa niitä oikeasti tarvitaan, kuten esimerkiksi linkitettyjen listojen ja dynaamisesti varattujen taulukoiden kanssa tai harjoitella pointterikikkailua ihan eriskseen (helpottaa huomattavasti olio-ohjelmoinnin opettelua).
Aihe on jo aika vanha, joten et voi enää vastata siihen.