Kirjautuminen

Haku

Tehtävät

Keskustelu: Ohjelmointikysymykset: C++: Luokkaongelma

Sivun loppuun

KoodiNoppa [11.01.2007 21:49:51]

#

Jälleen ongelmia koodatessa...
Aloin olio-ohjelmoinnin tänään. Tein luokan, jossa on pari arvoa ja funktio. Funktion pitäisi piirää näytölle viiva arvojen avulla. Ohjelma käyttää SDL:ää.

#include <stdlib.h>
#if defined(_MSC_VER)
#include "SDL.h"
#else
#include "SDL/SDL.h"
#endif
#include <math.h>
#include <string.h>

class Line
{
      public:
      int Lx1,Lx2;
      int Ly1,Ly2;
      //for line drawing{
      int leX,leY;
      int ux,uy;
      int xa,ya;
      //}
      int color;
      void Draw();
};


SDL_Surface *screen;


int left,right,up,down,space;

int useless;
int time;

int creAtion=1;



#define PITCH (screen->pitch / 4
void CLS()
{
     int screenie = 0;
     int fillingx = 0;
     int fillingy = 0;
     for (screenie = 0; screenie < 640*480+479; screenie++){
         ((unsigned int*)screen->pixels) [fillingy*640+fillingx] = 0x000000;
         fillingx++;
         if (fillingx>640){fillingy++; fillingx=0;}
     }
}
void Line::Draw()
{
     leX=Lx1-Lx2;
     leY=Ly1-Ly2;
     // x>y
     if (leX>leY && leX>0 && leY>0 )
     {
             ux=Lx1;
             uy=Ly1;
             while (ux!=Lx2)
             {
                   xa=int(ux);
                   ya=int(uy);
                   ((unsigned int*)screen->pixels)[xa+ya*640]=0xffffff;
                   ux+=1;
                   uy+=(leY/leX);
             }
     }
     // y>x
     if (leX<leY && leY>0 && leX>0)
     {
             ux=Lx1;
             uy=Ly1;
             while (uy!=Ly2)
             {
                   xa=int(ux);
                   ya=int(uy);
                   ((unsigned int*)screen->pixels)[xa+ya*640]=0xffffff;
                   ux+=(leX/leY);
                   uy+=1;
             }
     }

     // x>y
     if (leX<0){useless=1; leX=-leX;}
     if (leX>leY && leX>0 && leY>0 && useless==1)
     {
             ux=Lx1;
             uy=Ly1;
             while (ux!=Lx2)
             {
                   xa=int(ux);
                   ya=int(uy);
                   ((unsigned int*)screen->pixels)[xa+ya*640]=0xffffff;
                   ux-=1;
                   uy+=(leY/leX);
             }
     }
     // y>x
     if (leX<leY && leY>0 && leX>0 && useless==1)
     {
             ux=Lx1;
             uy=Ly1;
             while (uy!=Ly2)
             {
                   xa=int(ux);
                   ya=int(uy);
                   ((unsigned int*)screen->pixels)[xa+ya*640]=0xffffff;
                   ux-=(leX/leY);
                   uy+=1;
             }
     }
     if (useless==1){leX=-leX; useless==0;}
     if (leY<0){leY=-leY; useless=1;}
     // x>y
     if (leX>leY && leX>0 && leY>0 && useless==1)
     {
             ux=Lx1;
             uy=Ly1;
             while (ux!=Lx2)
             {
                   xa=int(ux);
                   ya=int(uy);
                   ((unsigned int*)screen->pixels)[xa+ya*640]=0xffffff;
                   ux+=1;
                   uy-=(leY/leX);
             }
     }
     // y>x
     if (leX<leY && leY>0 && leX>0 && useless==1)
     {
             ux=Lx1;
             uy=Ly1;
             while (uy!=Ly2)
             {
                   xa=int(ux);
                   ya=int(uy);
                   ((unsigned int*)screen->pixels)[xa+ya*640]=0xffffff;
                   ux+=(leX/leY);
                   uy-=1;
             }
     }

     if (useless==1){leX=-leX; leY=-leY; useless=0;}
     leX=Lx2-Lx1;
     leY=Ly2-Ly1;
     if (leX<0 && leY<0){
     if (leX<leY)
     {
             ux=Lx1;
             uy=Ly1;
             while (uy>Ly2)
             {
                   xa=int(ux);
                   ya=int(uy);
                   ((unsigned int*)screen->pixels)[xa+ya*640]=0xffffff;
                   ux-=1;
                   uy-=(leY/leX);
             }
     }
     if (leX>leY)
     {
             ux=Lx1;
             uy=Ly1;
             while (ux>Lx2)
             {
                   xa=int(ux);
                   ya=int(uy);
                   ((unsigned int*)screen->pixels)[xa+ya*640]=0xffffff;
                   ux-=(leX/leY);
                   uy-=1;
             }
     }
     }
     leX=Lx2-Lx1;
     leY=Ly2-Ly1;




     //x=y
     if (leX==leY)
     {
             ux=Lx1;
             uy=Ly1;
             while (ux!=Lx2)
             {
                   xa=int(ux);
                   ya=int(uy);
                   ((unsigned int*)screen->pixels)[xa+ya*640]=0xffffff;
                   if (leX>0){
                   ux++;
                   uy++;}
                   if (leX<0){
                   ux--;
                   uy--;}
             }
     }
     //x=0
     if (leX==0)
     {
             ux=Lx1;
             uy=Ly1;
             while (uy<Ly2)
             {
                   xa=int(ux);
                   ya=int(uy);
                   ((unsigned int*)screen->pixels)[xa+ya*640]=0xffffff;
                   if (leY>0){
                   uy++;}
                   if (leY<0){
                   uy--;}
             }
     }
     //y=0
     if (leY==0)
     {
             ux=Lx1;
             uy=Ly1;
             while (ux<Lx2)
             {
                   xa=int(ux);
                   ya=int(uy);
                   ((unsigned int*)screen->pixels)[xa+ya*640]=0xffffff;
                   if (leX>0){
                   ux++;}
                   if (leX<0){
                   uy--;}
             }
     }
}
void render()
{
  // Lock surface if needed
  if (SDL_MUSTLOCK(screen))
    if (SDL_LockSurface(screen) < 0)
      return;

  // Ask SDL for the time in milliseconds
  int tick = SDL_GetTicks();
  CLS();
  Line line1;
  line1.Lx1=100;
  line1.Lx2=300;
  line1.Ly1=100;
  line1.Ly2=250;
  creAtion=0;
  line1.Draw();


  // Unlock if needed
  if (SDL_MUSTLOCK(screen))
    SDL_UnlockSurface(screen);

  // Tell SDL to update the whole screen
  SDL_UpdateRect(screen, 0, 0, 640, 480);
}
// Entry point
//int main
//int main
//int main
//int main
//int main
//int main
//int main
//int main
//int main
//int main
//int main
//int main
//int main
//int main
//int main
//int main
//int main
//int main
//int main
//int main
//int main
//int main
int main(int argc, char *argv[])
{


  // Initialize SDL's subsystems - in this case, only video.
  if ( SDL_Init(SDL_INIT_VIDEO) < 0 )
  {
    fprintf(stderr, "Unable to init SDL: %s\n", SDL_GetError());
    exit(1);
  }

  // Register SDL_Quit to be called at exit; makes sure things are
  // cleaned up when we quit.
  atexit(SDL_Quit);

  // Attempt to create a 640x480 window with 32bit pixels.
  screen = SDL_SetVideoMode(640, 480, 32, SDL_SWSURFACE);

  // If we fail, return error.
  if ( screen == NULL )
  {
    fprintf(stderr, "Unable to set 640x480 video: %s\n", SDL_GetError());
    exit(1);
  }
  // Main loop: loop forever.
  int mouse,mx,my;
  SDL_WarpMouse(320,240);
    SDL_WM_SetCaption("Cave SnowFight",NULL);
  while (1)

  {



    // Render stuff

    // Poll for events, and handle the ones we care about.
    SDL_Event event;
    while (SDL_PollEvent(&event))
    {
          mouse=SDL_GetMouseState(&mx, &my);
      switch (event.type)
      {
      case SDL_KEYDOWN:
        if (event.key.keysym.sym == SDLK_LEFT)
           left = 1;
        if (event.key.keysym.sym == SDLK_RIGHT)
           right = 1;
        if (event.key.keysym.sym == SDLK_UP)
           up = 1;
        if (event.key.keysym.sym == SDLK_DOWN)
           down = 1;
        if (event.key.keysym.sym == SDLK_SPACE)
           space = 1;
        break;
      case SDL_KEYUP:
        // If escape is pressed, return (and thus, quit)
        if (event.key.keysym.sym == SDLK_ESCAPE)
           return 0;
        if (event.key.keysym.sym == SDLK_LEFT)
           left = 0;
        if (event.key.keysym.sym == SDLK_RIGHT)
           right = 0;
        if (event.key.keysym.sym == SDLK_UP)
           up = 0;
        if (event.key.keysym.sym == SDLK_DOWN)
           down = 0;
        if (event.key.keysym.sym == SDLK_SPACE)
           space = 0;
        break;

      case SDL_QUIT:
        return(0);
      }
    }


    SDL_ShowCursor(false);

    render();



  }
  return 0;
}

Siinä olis tarkistettavaa jos jaksaa... Luulen että virhe on aika tökerö ja helposti korjattavissa. Olen hakenut render()-funktion netistä ja tehnyt muutamankin kerran tiedoston päälle uutta ohjelmaa, joten turhaa tavaraa voi olla aika paljon.

ezuli [11.01.2007 22:16:00]

#

Vain vasen sulkumerkki?

#define PITCH (screen->pitch / 4

Huomenna lisää, mutta minkälainen vika on? Ei käänny vai ei toimi?

A-P [12.01.2007 09:48:06]

#

Huom! C++ ei ole vahvimpia puoliani.

KoodiNoppa kirjoitti:

class Line
{
      public:
      int Lx1,Lx2;
      int Ly1,Ly2;

      int leX,leY;
      int ux,uy;
      int xa,ya;

      int color;
      void Draw();
};

Sinänsä teknisesti oikein, mutta rikotaan olio-ohjelmoinnin periaatteita. Nykyisellään se on kuin C++ laajennettu rakenne (struct), jossa voi olla muuttujien lisäksi funktioita. Luokan kaikki attribuutit ja metodit saavat sellaisen "näkyvyysalueen" kuin tarvitsevat. Tasoja on yleensä kolme yksityinen (private), suojattu (protected) ja julkinen (public). Luokan attribuuteilla tarkoitetaan yleisiä muuttujia ja metodeilla luokan jäsenfunktioita. Jos attribuutteihin on päästävä käsiksi, niin ohjelmoidaan niitä varten luku- ja kirjoitusmetodit esim. getX- ja setX-metodi.
Luokassa tyypillisesti on myös muodostin.

Korjaus olisi seuraavanlainen

class Line
{
      // Attribuutit näkyvät vain tälle luokalle.
private:
      int Lx1,Lx2;
      int Ly1,Ly2;

      int leX,leY;
      int ux,uy;
      int xa,ya;

      int color;

      // Metodit, jotka näkyvät luokan ulkopuolelle.
public:
      // Muodostin
      Line(int x1, int y1, int x2, int y2);
      // Tuhoaja
      ~Line();

      void Draw();
};

Muodostimen ja tuhoajan koodit. Muodostin luo olion ja tuhoaja vapauttaa olion käytössä olleen muistin.

Line::Line(int x1, int y1, int x2, int y2) {
  Lx1=x1;
  Lx2=x2;
  Ly1=y1;
  Ly2=y2;
}

Line::~Line() {
  // Luokka ei käytä dynaamisesti varattua muistia, joka pitää muistaa
  // vapauttaa.
}

Lopuksi korjattu luokan käyttöä.

Line line1=new Line(100, 100, 300, 250);
creAtion=0;
line1->Draw(); // Huom! Piste-erottimen paikalla nuoli (->)
delete line1; // Vapautetaan se.

KoodiNoppa [12.01.2007 15:37:23]

#

Line line1=new Line(100,100,300,250);

tuottaa virheilmoituksen:
conversion from 'Line*' to non-scalar type 'Line' requested

line1->Draw();

base operand of '->' has non-pointer type 'Line'

delete line1;

type 'class Line' argument given to 'delete', expected pointer

ezuli:
sulkuhan olisi hyvä laittaa paikalleen mutta ilmeisesti toimii ilman :)
Ohjelma kääntyy hyvin, mutta ei tee mitään.

A-P [12.01.2007 15:47:40]

#

Hups, jäi virhe. Epähuomiossa jätin muuttamatta kohdan Line line1 muotoon Line *line1 eli line1 on osoitin luotavaan olioon. Alla sama koodina.

Line *line1=new Line(100, 100, 300, 250); // korjattu lisäämällä osoitinmerkki
creAtion=0;
line1->Draw(); // Huom! Piste-erottimen paikalla nuoli (->)
delete line1; // Vapautetaan se.

Megant [12.01.2007 15:48:57]

#

KoodiNoppa kirjoitti:

Line line1=new Line(100,100,300,250);

tuottaa virheilmoituksen:
conversion from 'Line*' to non-scalar type 'Line' requested

New palauttaakin osoittimen olioon, joten muuta määrittelysi tällaiseksi:

Line * line1 = new Line(100, 100, 300, 250);

EDIT: Oho, hidas

Pekka Karjalainen [12.01.2007 16:12:41]

#

Toinen tapa on taas sanoa näin:

Line line1 = Line (100, 100, 300, 250);

Silloin line1-muuttujan arvoihin taas viitataan .-operaattorilla. Se olisi siis

line1.draw()

tässä esimerkissä. Näillä tavoilla on hyvin merkittävä ero, jota en osaa lyhyesti selittää. Jotenkin vaan tuntuu, että aloittelevalle C++-ohjelmoijalle ei kannata opettaa vain new & delete -tapaa.

KoodiNoppa, osaatko arvata, mitä seuraava ohjelma tulostaa ja missä järjestyksessä? Siinä on tarkoituksella eräs virhe (puuttuva delete), joka ei estä ohjelman kääntämistä ja suoritusta.

#include <iostream>

using namespace std;

class Olio {
  public:
    int value;
    Olio (int n) { cout << "Luotiin olio arvolla " << n << endl;
                   value = n; }
    ~Olio () { cout << "Tuhottiin olio, jolla oli arvo " <<
                        value << endl; }
    void metodi (void) { cout << "Metodia kutsuttiin" << endl; }
};

void funktio (void) {
  Olio olio1 (3);
  Olio* olio2ptr = new Olio (5);
  cout << "Kiva funktio sulla. Et kai haluaisi, että sille tapahtuisi"
          " jotain ikävää???" << endl;
  olio1 . metodi();
  olio2ptr -> metodi();
  // oops, delete unohtui mafioson takia. pahuksen mafioso
}

int main() {
  funktio();
}

// loppu

Mistä olioiden käyttöä C++:ssa oikein kannattaisi ruveta opettelemaan? En osaa oikeasti sanoa, ja toivon, etten vain sotke asioita tällä jutullani.

KoodiNoppa [12.01.2007 18:36:54]

#

Kaikki:
nyt on

Line line1=Line(100,100,300,250);
line1.Draw();
delete line1

käytössä. delete line1; valittaa samaa kuin ennenkin.

Osoittimen ja newin kanssa tulee (paikkaa ei ilmoiteta)
[linker error] undefined reference to 'operator new(unsigned int]' sekä
[linker error] undefined reference to 'operator delete[void*]'

Huoh, olen niin kyllästynyt linker erroreihin... :(

Kopeekka:
Luotiin olio arvolla 3
Luotiin olio arvolla 5
Metodia kutsuttiin
Metodia kutsuttiin
Näinkö?

Pekka Karjalainen [12.01.2007 20:58:15]

#

Delete tulee silloin, kun käytät osoitinta. Siitä kertoo se *. En ollut tarpeeksi selvä omassa lisäyksessäni. Anteeksi.

Vaihtoehdot ovat siis nämä:

A-P:n esityksen mukaan

Line *line1=new Line(100, 100, 300, 250);
line1->Draw(); // Huom! (->)
delete line1; // Vapautetaan se.

Tai minun tapani:

Line line1=Line(100,100,300,250);
line1.Draw(); // Huom! (.)
// ei mitään deleteä missään

Jos jompikumpi noista nyt antamistani koodinpätkistä ei vieläkään toimi, niin kysy lisää. En kerennyt kokeilla koko ohjelmaa.

Lyhyesti ohjelmastani. C++:ssa on tärkeää hallita muistia tarkasti, ja vapauttaa varattu muisti. Kun jokin olio luodaan, se tarvitsee muistia ja ehkä muita resursseja (tiedosto tms.), ja ne varataan oliota luodessa, eli muodostimessa. Kun olio on palvellut täysin, se poistetaan ja silloin kutsutaan tuhoajaa. Se on tuo ~-merkillä alkava metodi class-määrittelyn sisällä.

Ohjelmani näyttää perustavan (virheen oikeastaan), miten voi jäädä koko olio tuhoamatta. Tuhoajan sisällä on tulostustoiminto, jotta sen suoritusajan voi nähdä. Huomaat, jos kokeilet ohjelmaa, että vain toinen tuhoaja suoritetaan. Jossain vaiheessa C++-harrastus sinun pitää ymmärtää miksi, mutta ehkä nyt riittää, että muistat tämän säännön: jos on new, pitää olla vastaava delete. Ohjelmani on virheellinen, koska jätän sen deleten pois.

Kunhan alat käyttää STL:n tietorakenteita, matkaan tulee vielä toinen mutka. Silloin delete-operaation suorittaminen voi ollakin tietorakenteen vastuulla.

Joo, tämä on sekavaa, kun näin kerron. Anteeksi. Suosittelen, että keskityt saaman tuon ohjelmasi toimimaan. Käytä vain jompaa kumpaa tapaa, joka neuvottiin. Jos ei vieläkään tosiaan toimi, niin kysele vaan kärsivällisesti lisää. Palaa vaikeisiin asioihin myöhemmin ja selvitä aikanaan, mitä niksejä näiden olioiden luomisten ja tuhoamisten kanssa oikein on. Se tieto on kaikissa hyvissä oppaissa ja kirjoissa, eikä minun auta tähän lyhennellä. (Sotken vain lisää.)

C++ ei pyytele anteeksi vaikeuttaan. Se on vaikea vekotin. Ei kuitenkaan mahdoton.

KoodiNoppa [12.01.2007 21:05:26]

#

Poistin deleten. Nyt tulee seuraava virheilmoitus:
[linker error] undefined reference to '__gxx_personality_sj0'

Onko linker erroreihin mitään yleispätevää korjausta?

Megant [12.01.2007 21:44:18]

#

KoodiNoppa kirjoitti:

Poistin deleten. Nyt tulee seuraava virheilmoitus:
[linker error] undefined reference to '__gxx_personality_sj0'

Onko linker erroreihin mitään yleispätevää korjausta?

Ekana tulisi mieleen, että yrität kääntää C++-koodia C-kääntäjällä.

Ja eihän siihen mitään varsinaista yleispätevää korjausta ole, riippuu siitä mikä on väärin.

KoodiNoppa [12.01.2007 22:38:45]

#

Kyl se c++:lla on... Alkaa vaan ärsyttämään nuo linker errorit, niitä kun tulee nykyään aina kun yritän jotain hiemankin liian monimutkaista D:<

Vaikka ei tämäkään mitenkään monimutkainen juttu ole, 90% Ohjelmointiputkan väestä varmaankin osaisi tehdä sen etu- ja takaperin ja viidellä kielellä :P

koo [12.01.2007 23:13:26]

#

Megant kirjoitti:

Koodinoppa kirjoitti:

Onko linker erroreihin mitään yleispätevää korjausta?

Ekana tulisi mieleen, että yrität kääntää C++-koodia C-kääntäjällä.

Tai siis että yrität linkata C++-koodia linkkerin C-asetuksilla.

Koodinoppa kirjoitti:

Kyl se c++:lla on...

Minulla ei ole Dev-C++:aa. Voisiko joku Dev-C++:n tuntija kertoa rautalanka- tai -kankimallin avulla, miten varmistetaan, että projekti on todellakin C++- eikä C-projekti. Tuon saa varmaan muutettua käsinkin jostakin Dev-C++:n luomasta projektin asetustiedostosta.

Taitaa nimittäin olla niin, että linkkauskomentona on gcc eikä g++.

Pekka Karjalainen [13.01.2007 11:40:51]

#

Guuglasin tuota virheilmoitusta ja tällaista löytyi:

http://www.cygwin.com/ml/cygwin/2002-02/msg01182.html

http://brewforums.qualcomm.com/showthread.php?t­=6738

Osumia oli lisääkin. Parasta varmaan olisi, että kuvaisit tarkasti käyttämäsi ohjelmat ja mitä asetuksia käytät. Joku, joka käyttää samoja ohjelmia voi neuvoa, mitä pitää korjata.

Voit myös kokeilla komentoriviltä suoraan c++-komennolla. Minulla on yhdessä Windows-asennuksessa MinGW näin:

C:\MinGW\bin>dir c*
[...]

 Kansio C:\MinGW\bin

21.09.2004  11:13            90 624 c++.exe
05.09.2004  02:44           428 032 c++filt.exe
21.09.2004  11:14            89 600 cpp.exe
               3 tiedosto(a)        608 256 tavua
               0 kansio(ta)  12 734 726 144 tavua vapaana

Tarkista, onko sinulla tuo c++.exe omalla koneellasi. Sitten laita sen hakemisto PATH-muuttujaan, tai aja ohjelma suoraan koko polun kanssa. Jälkimmäinen tapa olisi

c:\mingw\bin\c++.exe <ohjelmasi> -o <exen nimi>

missä tietenkin vaihdat ohjelmasi lähdekooditiedoston nimen tuohon ensimmäiselle paikalle ja toiselle paikalle puolestaan haluamasi exen (suoritettava tiedosto). Sen voi jättää pois, jolloin se muistaakseni antaa A.EXE:n automaattisesti sieltä.

Jos tämä ei toimi heti, unohda koko juttu. Parempi saada se oikea kehitysympäristö toimimaan niin kuin on tarkoitus.

KoodiNoppa [13.01.2007 13:13:19]

#

Hmm...Ainakaan includet eivät taida olla syynä, tällä hetkellä on vain sdl.h ja sdl/sdl.h jonkinlaisen if-loopin sisällä. Käytän seuraavaa:
w2k
Dev-c++
SDL
projektin asetuksissa kaikki c++-compilerin säädöt pois
compilerin asektuksissa c++-kääntäjänä g++
SDL-linkkeri tietenkin
Mitään asetuksia ei vaihdettu
lähdetiedosto .cpp ja mielestäni dev-cpp kääntää sen oletuksena c++:na

Kysy jos muuta tarvitaan vielä.

CyberianRat [13.01.2007 16:10:35]

#

Heippa

Kerroit että olet juuri aloittanut C++-ohjelmoinnin, onnea matkaan! Tässä jotain mitä tulee pikaisella silmäyksellä mieleen:

#include <math.h>
#include <string.h>
//c++-ohjelmoijana haluat käyttää mieluummin näitä:
#include <cmath>
#include <string>

class Line
{
public:
  // Nämä saatat haluta yksityisiksi riippuen
  // vähän luokan luonteesta
  int Lx1,Lx2;
  int Ly1,Ly2;
  int color;

  // Tällaisia et halua välttämättä luokkiin ollenkaan
  // vaan käytät paikallisia muuttujia
    int leX,leY;
    int ux,uy;
    int xa,ya;

  void Draw();
  // Haluat sen konstruktorin:
  Line (int x1, int x2, int y1, int y2) : Lx1(x1),Lx2(x2),Ly1(y1),Ly2(y2)
  { }
};

if (leX>leY && leX>0 && leY>0 )
{
  ux=Lx1;
  uy=Ly1;
  while (ux!=Lx2)
  {
    xa=int(ux);
    ya=int(uy);
    ((unsigned int*)screen->pixels)[xa+ya*640]=0xffffff;
    ux+=1;
    uy+=(leY/leX); // Huomaa, että koska leX > leY, (leY / leX) == 0
                  // (kokonaislukujen jakolaskun ominaisuus). Ehdotan että
                 // haet googlella Bresenham-nimistä viivapiirtoalgoritmia.
  }
}

KoodiNoppa [13.01.2007 23:52:57]

#

Hmm...c++ on ollut käytössä ~4 kuukautta...Nyt vasta aloitin olioiden opiskelun. Sen lisäksi kyseinen funktio ON bresenham (ehkä hieman omaan tyyliin parin kymmenen ylimääräisen muuttujan kanssa :D). Kun tota koodia uudestaan katoin, niin ehkä pitäis muuttaa pari int:iä doubleiksi. Niin ja math ja string eivät ole enää käytössä (no, sitä ei kukaan täällä olisi voinut tietää...), mutta voivat tulla vielä takaisinkin, joten kiitos!

...mutta linker error ei ole vieläkään poistunut. Pastetan tämänhetkisen koodin tähän jos joku siten löytäisi virheen. Ylimääräisiä muuttujia en ole vielä jaksanut poistaa.
(Mod. edit. Ai, onko siinä muka jotain merkittävää eroa edelliseen?)

koo [14.01.2007 15:43:41]

#

Tässä taitaa nyt olla puurot ja vellit vähän sekaisin, otetaan hitaasti ja rautalangan kanssa:

Hommahan menee siis niin, että ne lähdetiedoston #includet ovat käännösaikaisia asioita. Ohjelmaa käännettäessä kääntäjä lukee #includen kohtaan tiedoston, joka yleensä sisältää funktioiden, tyyppien, vakioiden yms. esittelyitä. Nämä ovat tarpeen, jotta kääntäjä osaa tehdä tarkistuksia ja käyttää sellaisiakin asioita, joita juuri tässä lähdetiedostossa ei ole. C++-kääntäjän ei pitäisi sallia minkään sellaisten funktioiden, tyyppien, vakioiden yms., joita ei ole sille esitelty ennen käyttökohtaa, mutta erilaisista historiallisista yhteensopivuussyista kääntäjä voi joskus poiketa tästä säännöstä.

Kun kääntäjä on kääntänyt lähdetiedostot (jotka siis voivat #includoida esittelytiedostoja) objektitiedostoiksi, objektitiedostot linkataan ajettavaksi ohjelmaksi (tai vaihtoehtoisesti ne voidaan niputtaa erilaisiksi kirjastoiksi, mutta se on toinen juttu). Tämän homman hoitaa linkkeri. Jotta C++-koodista syntyy ajettava ohjelma, se täytyy mukaan täytyy saada myös ne jutut, jotka on esitelty esittelytiedostoissa ja joihin lähdetiedostoissa suorasti tai epäsuorasti viitataan. Yleensä kääntäjän mukana tuleekin standardi-C++-esittelytiedostot sekä C++-kirjastopaketti. Linkkauskomennossa luetellaan omista lähdetiedostoista käännöksessä syntyneet objektitiedostot sekä sellaiset kirjastot, joita linkkeri ei automaattisesti ota mukaan ajettavaa ohjelmaa rakentaessaan.

Jos käyttää esimerkiksi Dev-C++-kehitysympäristöä, nämä ohjelman buildaamisen kaksi vaihetta eivät näy niin selvästi. Työkalu määrittelee asiat projektin puitteissa ja hoitelee tarvittavan kasailuautomatiikan itsestään. Paitsi silloin kun homma tökkää.

Linkkerin virheilmoitus "undefined reference" tarkoittaa, että kun linkkeri kasaa ajettavaa ohjelmaa, objektitiedostoissa viitataan johonkin sellaiseen asiaan, jota linkkeri ei löydä listallaan olevista objektitiedostoista, kirjastoista eikä edes automaattisesti viittaamistaan vakiokirjastoista. Joskus tämä johtuu siitä, että aiemmassa käännösvaiheessa joku #include puuttui ja kääntäjä teki oletuksen esimerkiksi jonkun funktion tyypistä ja että linkkauksessa tuota väärin oletettua funktiota ei sitten löydykään. Tämä vaihtoehto tuntuu kuitenkin koko ajan epätodennäköisemmältä.

Todennäköinen syy on se, että Dev-C++:n projektityyppi on C eikä C++. Silloin linkkaukseen ei tule automaattisesti C++-kirjastoja, joten aina jotain C++:n juttua - stringiä, ciniä, couttia tms. - käyttäessä linkkeri urputtaa undefined referenceistä. Toinen vaihtoehto on, että projektin määrityksessä on muutettu linkkauskomentoa käsin niin, että siinä ennestään olleet tarpeelliset kirjastot ovat jääneet pois. Kolmas vaihtoehto on, että Dev-C++-asennus on rikki, tarpeelliset kirjastot ovat jotenkin hävöksissä.

Tämä homma ei luultavasti selviä heittämällä sivustolle kerrallaan puolitusinaa sivua siivoamatonta lähdekoodia ja kyselemällä, että "tää ei toimi; kertokaa, missä vika; ainiin, tästä tulee vielä linker error". Jos jokin ei toimi, pitää kasata pienin mahdollinen pätkä, jossa virheen saa toistumaan, ja kertoa, millainen virheilmoitus, ympäristö ja muut vaikuttavat seikat on kyseessä. Jollakin voi toki olla intoa, mutta itse en juurikaan viitsi edes katsoa "tässä on jokin vika" juttua, jossa on joko alle kolme tai yli kaksikymmentä riviä koodia. Jos kysyy miten sattuu, vastauksetkin - jos niitä tulee - on mitä sattuu.

Koitappas luoda ihka uusi C++-projekti. Luo sinne tällainen main.cpp-tiedosto.

#include <string>
#include <iostream>

int main()
{
    std::string s = "Heippa!\n";
    std::cout << s;
}

Jos tuo "ei toimi", kerro millä tavalla se ei toimi. Jos "toimii", katso mitkä asetukset tässä nyt sitten olivatkin eri lailla verrattuna noihin toimimattomiin projekteihin.

Ehkä sen jälkeen voidaan palata pohtimaan bresenhameja.

Tulipas käsittämättömän pitkä sepustus, vaikkakin nopeasti. On tämä kuitenkin kysymyksiä lyhyempi, mutta lupaan lopettaa tällaiset hommat.

Blaze [14.01.2007 19:06:29]

#

koo kirjoitti:

lupaan lopettaa tällaiset hommat.

Sikäli kun taso pysyy samana, oot tervetullu kirjottamaan vastaavia romaaneja vaikka joka päivä :)

KoodiNoppa [14.01.2007 20:34:16]

#

Hmm... Kyllä se toimii... Sain oliotkin toimimaan, mutta yksi ongelma on vielä: miten voin tehdä ison kasan olioita antamatta jokaiselle manuaalisesti eri nimeä?

Megant [14.01.2007 20:39:23]

#

Ihan tavallisesti taulukoilla.

class Olento {
      public:
         int ika;
         std::string nimi;
};

Olento oliot[50]; // 50 oliota

Olento * olennot = new Olento[50]; // sama dynaamisesti, pitää muistaa vapauttaa!

Metabolix [14.01.2007 20:44:55]

#

Olento * olennot = new Olento[50];
delete [] olennot; // Laitetaan se vapautus vielä.

KoodiNoppa [14.01.2007 21:02:42]

#

Olin (vihdoinkin) googlettanut ja löytyi juuri tuo, mutta kiitos kuitenkin :D
Jos joku vielä tietää keinon, jossa voi asettaa aina uusia olentoja ilman että tarvitsee taulukon kokoa asettaa taivaisiin - vähän niin kuin stringiin ei tarvitse merkkien määrää - niin sellainen kelpaisi. Olen kyllä aika varma ettei sellaista ole : (

koo [14.01.2007 21:22:17]

#

Käytä C++:n kokoelmaluokkia, joista string on itse asiassa yksi. Taulukkomuotoista dynaamista varausta (p = new jotain[monta]) kannattaa käyttää vain hyvin rajatuissa tapauksissa.

std::vector<Olento> olennot;
// tai std::deque<Olento> olennot;
// tai std::list<Olento> olennot;

for (int i = 0; i < montako; ++i) {
  Olento uusi;
  // ...
  olennot.push_back(uusi);
}

Muuten, sait sitten sen linkkauksen toimimaan? Nyt kun tässä on pitemmän aikaa asiaa jännätty, niin voisitko kertoa, että mikä siellä lopulta mätti.

KoodiNoppa [14.01.2007 22:51:47]

#

Hmm...En tiedä vieläkään. Ja ongelmia lisää.
Tein toisen ohjelman aivan alusta. Nyt se on lähtöpisteessä, eli kääntyy muttei tee mitään. Toinen ohjelma taas valittaa linker erroria, vaikka kaikkien asetusten pitäisi olla samanlaiset. Olen "eristänyt" pätkän, joka tuottaa virheen: render()-funktio + kutsu.

Tein uuden kokeilun ohjelman päälle, jolla olin onnistuneesti testannut Bresenhamin algoritmia. Kaikki toimi, sain luokan määriteltyä ja kaikki funktiot, kunnes loin olion, ja linker error palasi. En enää yhtään tiedä mitä tehdä : (
-mikään EI ole jäänyt includesta. includet ovat identtiset kummassakin tapauksessa.
-kumpikin ON c++-projekti. Löysin uuden valikon, jossa oli valinta "compile as c++ file" ja rastitin sen. Mikään ei muuttunut.
-Dev-cpp:n asennus ON kunnossa. Muutenhan kumpikaan ei toimisi...vai?

Hmm...saattaa tietenkin olla, että olen tehnyt jonkin virheen, olen kuitenkin vielä aika aloittelija :P
Hmm...olisiko mahdollista että kummankin ohjelman "oireet" johtuisivat samasta syystä, mutta ilmenisivät eri tavalla?

koo [14.01.2007 23:59:24]

#

Mutta tuo Heippa-ohjelma siis kääntyy ja toimii?

Haluat tehdä ohjelman SDL-kirjastoa käyttäen? Ota ensimmäisenä tuo toimiva Heippa-ohjelma ja lisää ohjelman projektiin SDL:n kirjastot. Koodia et muuta vielä tippaakaan. Kääntyykö ja toimiiko?

Lisää lähdetiedostoon SDL-rajapinnan vaatimat #includet, mutta älä muuta koodia vielä muuten. Kääntyykö ja toimiiko?

Lisää lähdekoodiin SDL-rajapinnan alustus- ja loppusiivous-funktiokutsut, mutta älä lisää muuta toiminnallisuutta. Kääntyykö ja toimiiko?

Lisää omia juttujasi lähdekoodiin vähän kerrallaan (toteuta esimerkiksi funktiot ensin tyhjinä) ja katso, kääntyykö ja toimiiko.

Idea siis on, että lähdet liikkeelle minimaalisesta toimivasta pätkästä ja muutat/lisäät yhden jutun kerrallaan, kunnes jokin lakkaa toimimasta.

Kerro sitten muillekin, mitä löysit. Joku Dev-C++:n käyttäjä voi tietää ratkaisun tarkempaan ongelmaan tai ainakin haluta tietää ratkaisusta.

Pekka Karjalainen [15.01.2007 13:23:23]

#

Haluaisin lisätä koon viestiin yhden asian. Hänen ehdottamansa tapa on toimiva, mutta ilman sopivan vektorikoon ilmoittamista etukäteen se aiheuttaa huomattavasti ylimääräistä olioiden kopioimista. Jos käyttää vektoria, johon laittaa olioita, tulee kutsua sopivassa välissä reserve( n )-metodia, joka varaa vektoriin tilaa n:lle alkiolle.

Jos näin ei tee, aina kun vektorin taakse lisää uuden olion, vektorin kokoa kasvatetaan ja kaikki (!) vanhat oliot pitää kopioida uudelleen. Jos vektori on iso, tämä on aivan käsittämätön määrä turhaa työtä.

Lisäksi on hyvä mainita, että monesti vektoriin tai muuhun rakenteeseen ei laiteta itse olioita, vaan osoitin, viittaus tai muu kahva, joka on niin pieni, että sitä on joskus varaa kopioidakin. Silloin tässä esitetty tilanne ei ole mikään suuri ongelma.

Olioiden kopioimista voi seurata, jos tekee itse oman kopiorakentajan olioluokkaansa ja loggaa sen jokaisen kutsun. Hyvässä kehitysympäristössä tämän voisi varmasti tehdä muutenkin (sotkematta oikeaa koodia tällaisella). Tämä esimerkki kuitenkin toimii missä vain ja tulostaa kaiken tiedon coutin kautta. (Niin, jos minulla on vielä C++-standardi jotenkin hallussa...)

Kopiorakentaja on sellainen rakentaja, joka ottaa parametriksi viittauksen samanlaiseen olioon. Alla se on Olio( const Olio& vanha ). Sen tehtävä on tehdä uusi olio, joka on kopio jo olemassa olevasta oliosta.

#include <iostream>
#include <vector>

const int KOKO = 5; // vektorin suurin koko

using namespace std;

class Olio {
  int tag;
public:
  Olio( int n ) { tag = n;}
  Olio( const Olio& vanha ) : tag (vanha.tag) {
    cout << "kopioitiin olio: " << vanha.tag << endl;
  }
  ~Olio() { cout << "tuhottiin olio: " << tag << endl;}
};

int main () {
  vector< Olio > oliot1;
  vector< Olio > oliot2;
  oliot2.reserve( KOKO ); // varataan usealle tilaa

  for( int i = 0; i != KOKO; ++i ) {
    cout << endl << "silmukka alkaa i:n arvolla: " << i << endl;
    Olio uusi = Olio( i );
    cout << "oliot1-vektoriin lisäys" << endl;
    oliot1.push_back( uusi );
    cout << "oliot2-vektoriin lisäys" << endl;
    oliot2.push_back( uusi );
    cout << "silmukka loppuu" << endl;
  }
  cout << "main loppuu" << endl;
}

(Yritän vaihtaa sulutustekniikkaa C++-ohjelmiini. Saas nähä miten käy.)

Tulostuksen osa näyttää seuraavalta kun i = 4 silmukassa.

silmukka alkaa i:n arvolla: 4
oliot1-vektoriin lisäys
kopioitiin olio: 0
kopioitiin olio: 1
kopioitiin olio: 2
kopioitiin olio: 3
kopioitiin olio: 4
tuhottiin olio: 0
tuhottiin olio: 1
tuhottiin olio: 2
tuhottiin olio: 3
oliot2-vektoriin lisäys
kopioitiin olio: 4
silmukka loppuu
tuhottiin olio: 4

Tuo kohta, missä tuhotaan useita olioita on merkki siitä, että vektorin pidentäminen push_back()-kutusn jälkeen on aiheuttanut turhaa työtä. Ohjelma tekee kopion kaikista vanhoista olioista uuteen vektoriin ja hävittää sitten vanhat. Huomaa, että oliot2-vektoria käsitellessä kopiodaan ainoastaan uusin olio. Se johtuu juuri tuosta reserve()-kutsusta, joka varaa aluksi jo tarpeeksi tilaa vektoriin. Käsittääkseni reserveä voi kutsua aina uudestaan tarpeen mukaan (pitäisi oikeastaan tarkistaa Stroustrupin kirjasta tämä).

Yksi hyvä tapa käyttää reserveä näin on aina tuplata vektorin koko, kun se tulee täyteen. Silloin joka hetkellä vektorissa on korkeintaan puolet turhaa tilaa ja yleensä jonkin verran vähemmän. Jos aineiston koon arvioiminen on todella vaikeaa, silloin on yleensä lista vektoria parempi tietorakenne. Se on joskus hitaampi kuin vector ja sen vaatimat linkit (toteutuksen sisällä) vievät hieman ylimääräistä tilaa, mutta korvauksena se on paljon joustavampi.

Yritin tehdä tästäkin lyhyen viestin, vaan se oli vaikeaa... Toivottavasti jollekin oli iloa.

koo [15.01.2007 14:01:16]

#

Juttu on kyllä niin, että ellei tosiaan tiedä, paljonko std:vector:iin meinaa olioita laittaa, reserve:ä on aika turha ruveta käyttämään. Vector kyllä kasvaa tarvittaessa automaattisesti ja tekeepä sen vielä jokseenkin samalla tavalla - ellei sitten optimaalisemmin - kuin mitä Kopeekka manuaalisella reserveämisellä ehdottaa.

Kyllä std::list:lläkin on omat hyvät puolensa - etenkin jos tarvitsee listaa - mutta itse kyllä pidän std::deque:ta usein peruskäytössä sopivana. Ellei kokoelman "välistä" poisteta alkioita, dequessa kopiodaan tavaroita ihan yhtä vähän kuin listassakin. Silloin kun tarvitaan yhteensopivuutta perinteisen C-taulukon kanssa, std::vector on sopiva valinta.

Mitä tulee olioiden kopioimiseen, huoli on erinomaisen usein aivan turha. Kannattaa ainakin vähän mitata, että onko tuolla kopioinnilla minkäänlaista merkitystä suorituskyvyn kannalta. Mitä olen kavereiden kanssa C++-koodijuttuja seuraillut, koodarin osaamistaso näyttää olevan käänteisesti verrannollinen koodarin kirjoittelemien paljaiden new-kutsujen määrään. Kyllä new:täkin käytetään, mutta se tapahtuu aina jonkin sortin omistajaluokan (esmes auto_ptr) alaisuudessa. Jos oma olio on TODELLA SUURI, tehdään sitten se kahvaluokka ja laitetaan niitä noihin C++:n standardi-containereihin, mutta osoittimia ei oikeastaan koskaan.

Mitä tulee alkioiden kopiointiin kokoelmaluokkien sisällä, std::string on surkeimmasta päästä, mutta siihenkin on syynsä.

Ottaen huomioon, mitä alussa ja matkalla on kysytty, sanoisin että käytä sinä KoodiNoppa std::vector:ia vaihtuvakokoisiin "taulukoihin" ihan huoletta niin kuin käytät std::string:iä merkkitiedolle. Mistään reserve-puheista ei kannata piitata pätkääkään, ellei heti alussa tiedä, montako alkiota meinaa "taulukkoonsa" laittaa.

KoodiNoppa [15.01.2007 15:45:41]

#

Tökkäsi tuohon rajapinnan luontiin. Ja itseasiassa kaikkiin SDL-käskyihin. Linkkeri ja #includet ovat, mutta ei toimi. Miksi se ei aikaisemmin ole valittanut?

EDIT: kuinka vaikea OpenGL on aloittelijalle? :P

firebug [15.01.2007 16:14:35]

#

KoodiNoppa kirjoitti:

Tökkäsi tuohon rajapinnan luontiin. Ja itseasiassa kaikkiin SDL-käskyihin. Linkkeri ja #includet ovat, mutta ei toimi. Miksi se ei aikaisemmin ole valittanut?

EDIT: kuinka vaikea OpenGL on aloittelijalle? :P

Pitkä aika on siitä kun viimeksi olen kirjoitellut mutta laitappa virheviestit näkyviin niin korjataan ongelma! Nykyisen ohjelmakoodisi voisit pastettaa vaikka osoitteeseen http://pastebin.com/ .

Ja tuosta OpenGL:stä: Ei se ole vaikeaa, mikäli ymmärtää jotain matikasta ja ohjelmoinnista. Riippuu ihan siitä mitä sillä haluaa tehdä: pyörivä kuutio on tottakai helpompi kuin täysiverinen 3D-räiskintä.

Edit: Suosittelisin, että pysyt SDL:n parissa jonkin aikaa ja teet sillä esimerkiksi yksinkertaisen pelin/pelejä. Näin opit pelikoodaukseen liittyvää perusmatikkaa ja saat kokemusta ohjelmoinnista. Kun tunnet osaavasi SDL:n tarpeeksi hyvin, voit aloitella kasaamaan OpenGL-sovelluksia (joihin SDL on muuten myös loistava apu).

Pekka Karjalainen [15.01.2007 17:19:33]

#

Olin näköjään väärässä vektorin kasvatuksen suhteen. Se osaa itse kasvattaa itseään järkevällä inkrementtisarjalla. Se on muuten minun käyttämässäni toteutuksessani 1,2,4,8,16,32,64 jne. tyhjästä alkavalle vector-luokalle.

Aina ei muista näitä asioita.

KoodiNoppa [15.01.2007 18:35:07]

#

Hmmm...pastebin näyttää toimivan hitaasti, yritän myöhemmin uudelleen. Virheilmoitukset ovat samanlaisia kuin en olisi includoinut SDL:ää, esim. SDL_Surface *screen; tuottaa virheilmoituksen expected constructor, destructor or type conversion before ;, pätkästä löytyvä SDL_INIT_VIDEO tuottaa virheilmoituksen 'SDL_INIT_VIDEO' undeclared (first use of this function).

Sen verran voin koodista kertoa, että siinä on nyt tuo if-lauseen sisällä oleva SDL-includointi, SDL-linkkeri, SDL_Surfacen alustus ja ensimmäisestäkin koodista löytyvä int main():in sisällä oleva SDL-koodisotku (siis ei tapahtumia, vaan surfacen säätö).

Matikka on (luulisin) jo tarpeeksi hallinnassa yksinkertaisille 3d-peleille. Osaan pienentää kaukana olevat kohteet (kokeilin QBasicilla) ja luulen osaavani kääntää kameraa...tavallaan.

tesmu [24.01.2007 16:22:36]

#

Tuli muuten mieleen että oletko muistanut linkittää tarvittavat tiedostot laittamalla linker parametreihin esin -lSDL jne jne? Pelkkä includetus ei riitä...


Sivun alkuun

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta