CYFROWY BARON • PROGRAMOWANIE • Zobacz wątek - każda klasa w oddzielnym pliku...
Strona 1 z 2

każda klasa w oddzielnym pliku...

Nowy postNapisane: wtorek, 10 października 2017, 00:21
przez Arnold_S
Witam!
Do tej pory pisząc małe klasy na potrzeby zadań domowych z różnych książek, tworzyłem je w jednym pliku *.cpp.
Teraz chciałbym stworzyć kilka własnych klas dziedziczących z abstrakcyjnej klasy i napotkałem kilka niewiadomych.

Na przykład:
Abstrakcyjna klasa "zwierze" składa się z 2 plików: zwierze.h (deklaracje) oraz zwierze.cpp (definicje).
Jeśli jednak wszystkie funkcje będą czysto virtualne (czyli nie muszę umieszczać definicji tych funkcji dla klasy abstrakcyjnej) to po co mi plik: zwierze.cpp?
Czy wystarczy tylko plik z deklaracjami?

Następnie tworzę klasy pochodne, np.: pies, kot, ocelot. Rzecz jasna, że muszę mieć pliki: pies.h, kot.h, ocelot.h (deklaracje) oraz pies.cpp, kot.cpp, ocelot.cpp (definicje). Nie wiem jednak jak jest lepiej?
Czy prywatne składniki (które są wspólne dla wszystkich klas pochodnych) raz jeden umieścić w abstrakcyjnej klasie i nie powtarzać ich deklaracji w klasach pochodnych?
Czy może w klasie abstrakcyjnej nie umieszczać wcale składników, a umieścić je w klasach pochodnych mimo tego, że będą się powtarzały w klasie: pies, kot, ocelot?
Czy umieścić je wszędzie?

I ostatnie pytanie: jeśli includuję jakieś tam biblioteki, np: string czy cctype, to powinienem to zrobić w pliku: *.cpp?

Re: każda klasa w oddzielnym pliku...

Nowy postNapisane: wtorek, 10 października 2017, 10:33
przez polymorphism
to po co mi plik: zwierze.cpp? Czy wystarczy tylko plik z deklaracjami?

Jeśli plik zwierze.cpp jest pusty, to odpowiedź nasuwa się sama. Ten podział na pliki .h i .cpp to nie jest dogmat. W przypadku małych klas czasami nie warto robić oddzielnego pliku źródłowego, wystarczy wszystko zrobić w jednym pliku nagłówkowym.

Czy prywatne składniki (które są wspólne dla wszystkich klas pochodnych) raz jeden umieścić w abstrakcyjnej klasie i nie powtarzać ich deklaracji w klasach pochodnych?

Tu także odpowiedź nasuwa się sama ;) Choć jeśli zależy Ci na "czystej" klasie-interfejsie, wtedy zrób klasę IZwierze, która zawiera tylko deklaracje metoda pure-virtual. Po niej niech dziedziczy np. ZwierzeBase, która zawiera pola i metody wspólne dla wszystkich potomków.

jeśli includuję jakieś tam biblioteki, np: string czy cctype, to powinienem to zrobić w pliku: *.cpp?

Tak by było najlepiej (ale nie zawsze się da).

Re: każda klasa w oddzielnym pliku...

Nowy postNapisane: wtorek, 10 października 2017, 17:44
przez Arnold_S
Czyli rozumiem, że mogę np. zrobić to tak (najprościej jak się da):
Kod: Zaznacz cały
                                                       klasa abstrakcyjna zwierze
                                                       - prywatne składniki klasy (dzięki temu wpiszę je tylko raz)
                                                       - funkcje pure virtual (tylko deklaracje)
                                                       - wszystko umieszczę tylko w pliku zwierze.h
                                                      /                                             \
                                                    /                                                 \
                                                  /                                                     \
                                      klasa kot                                                     klasa ocelot
                             - funkcje wirtualne (def. i dekl.)                              - tak samo jak obok


Dzięki za podpowiedzi! Przyznam szczerze, że mam kilka kont na różnych forach ale TYLKO tutaj dyskusja jest kulturalna, odpowiedzi są rzeczowe i pomocne, bez owijania w bawełnę. Bardzo fajne jest to, że chcecie się dzielić swoją wiedzą. 8-)

Re: każda klasa w oddzielnym pliku...

Nowy postNapisane: wtorek, 10 października 2017, 18:44
przez polymorphism
Czyli rozumiem, że mogę np. zrobić to tak

Tak.

Re: każda klasa w oddzielnym pliku...

Nowy postNapisane: sobota, 28 października 2017, 21:38
przez Arnold_S
...ehhh nadal mam problem z tymi klasami abstrakcyjnymi.
Stworzyłem takie dwie małe klasy na potrzeby przykładu.

Klasa abstrakcyjna "zwierze" w jednym pliku ponieważ zawiera same deklaracje i jedną definicję.
KOD cpp:     UKRYJ  
#ifndef ZWIERZE_H
#define ZWIERZE_H

#include <cstdlib>  //rand();
#include <string>

class zwierze
{
public:
zwierze() {}
virtual ~zwierze() {}

enum rodzaj_kostki {K6, K10, K100};
unsigned short int rzut_kostka(rodzaj_kostki k);

virtual void losuj_uszy(void) = 0;
virtual void losuj_oczy(void) = 0;
virtual void losuj_futro(void) = 0;

virtual UnicodeString wyjmij_string(std::string &) = 0;
};

///////////////////////////////////////////////
unsigned short int zwierze::rzut_kostka(rodzaj_kostki k)
{
        unsigned short int y;
        if(k == K6)
        {       y = 1+(rand()%6);       }
        if(k == K10)
        {       y = 1+(rand()%10);      }
        if(k == K100)
        {       y = 1+(rand()%100);     }
        return y;      
}
#endif


Następnie stworzyłem klasę "kot", która składa się z dwóch plików kot.h i kot.cpp. Jak widać dziedziczy publicznie od abstr. klasy zwierze.
KOD cpp:     UKRYJ  
#ifndef KOT_H
#define KOT_H

#include <string>


class kot : public zwierze
{
private:
std::string     uszy, oczy, futro;
       
public:
kot()
{
        uszy = "nie wylosowano";
        oczy = "nie wylosowano";
        futro = "nie wylosowano";
}
virtual ~kot() {}

enum rodzaj_kostki {K6, K10, K100};
unsigned short int rzut_kostka(rodzaj_kostki k);

void losuj_uszy(void);
void losuj_oczy(void);
void losuj_futro(void);

UnicodeString wyjmij_string(std::string &);
};

#endif


Poniżej zawartość pliku z definicjami:
KOD cpp:     UKRYJ  
#include "kot.h"

void losuj_uszy(void)
{
        unsigned short int x = rzut_kostka(K6);
        if(x <= 3)      { uszy = "długie"; }
        else            { uszy = "krótkie"; }
}

void losuj_oczy(void)
{
        unsigned short int x = rzut_kostka(K10);
        if(x <= 3)                      { oczy = "czerwone"; }
        if(x > 3 && x <= 6)     { oczy = "szare"; }
        if(x > 6)                       { oczy = "niebieskie"; }
}

void losuj_futro(void)
{
        unsigned short int x = rzut_kostka(K100);
        if(x <= 33)                             { futro = "w czarne paski"; }
        if(x > 33 && x <= 66)   { futro = "w plamki"; }
        if(x > 66)                              { futro = "kot bez futra"; }   
}

UnicodeString wyjmij_string(std::string &cos)
{
        UnicodeString wynik = cos.c_str();
        return wynik;
}


W programie normalnie inkluduję te dwie klasy: #include "zwierze.h" i "kot.h". Forma zawiera button: "Losuj" oraz trzy pola Memo, w których pokazują się wylosowane wartości pobrane z wewnątrz klasy kot. Oto zawartość pliku unit1.cpp
KOD cpp:     UKRYJ  
#include <vcl.h>
#pragma hdrstop
#include <iostream>
#include "Unit1.h"
#include "zwierze.h"
#include "kot.h"
#include <ctime>          // dla time();
#include <cstdlib>   // dla srand();

//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
   srand(static_cast<unsigned int>(time(0)));
}
//---------------------------------------------------------------------------

void __fastcall TForm1::Button1Click(TObject *Sender)
{
        kot dachowiec;
        zwierze *wsk = &dachowiec;

        wsk->losuj_uszy();
        wsk->losuj_oczy();
        wsk->losuj_futro();
        Memo1->Lines->Add(wsk->wyjmij_string(dachowiec.uszy));
        Memo2->Lines->Add(wsk->wyjmij_string(dachowiec.oczy));
        Memo3->Lines->Add(wsk->wyjmij_string(dachowiec.futro));
}


Wyskakuje mi komunikat, że E2247 'kot::uszy' is not accessible...wypaliło mi korki. Nie widzę błędu :( Wiem, że to podstawy podstaw ale idzie mi to nad wyraz pod górkę :(
Będę wdzięczny za każdą pomoc.

Re: każda klasa w oddzielnym pliku...

Nowy postNapisane: niedziela, 29 października 2017, 12:13
przez polymorphism
Poniżej zawartość pliku z definicjami:
KOD cpp:     UKRYJ  
void losuj_uszy(void) { ... }

void losuj_oczy(void) { ... }

void losuj_futro(void) { ... }

UnicodeString wyjmij_string(std::string &cos) { ... }

To są definicje funkcji, a nie metod klasy kot. Tak ma być:
KOD cpp:     UKRYJ  
void kot::losuj_uszy(void) { ... }

void kot::losuj_oczy(void) { ... }

void kot::losuj_futro(void) { ... }

UnicodeString kot::wyjmij_string(std::string &cos) { ... }

W wyjmij_string() argument cos powinien być const.



KOD cpp:     UKRYJ  
unsigned short int y;
if(k == K6)
{       y = 1+(rand()%6);       }
if(k == K10)
{       y = 1+(rand()%10);      }
if(k == K100)
{       y = 1+(rand()%100);     }
return y;  

Panie kolego, do takich rzeczy jest switch:
KOD cpp:     UKRYJ  
unsigned short int y;

switch(k)
{
case K6:    y = 1 + (rand() % 6); break;
case K10:   y = 1 + (rand() % 10); break;
case K100:  y = 1 + (rand() % 100); break;
}

return y;      

Prawda, że czytelniej?

Z drugiej strony można przecież to zredukować do:
KOD cpp:     UKRYJ  
enum rodzaj_kostki { K6 = 6, K10 = 10, K100 = 100 };
...

unsigned short int rzut_kostka(rodzaj_kostki k) { return 1 + (rand() % k); }




KOD cpp:     UKRYJ  
class kot : public zwierze
{
    ...      
public:
    ...

    enum rodzaj_kostki {K6, K10, K100};
    unsigned short int rzut_kostka(rodzaj_kostki k);
};

Dlaczego klasa kot definiuje swoje rodzaj_kostki i rzut_kostka()?



KOD cpp:     UKRYJ  
Memo1->Lines->Add(wsk->wyjmij_string(dachowiec.uszy));

Serio? Użycie wyjmij_string() ma być prostsze niż po prostu:
KOD cpp:     UKRYJ  
Memo1->Lines->Add(dachowiec.uszy.c_str());

Re: każda klasa w oddzielnym pliku...

Nowy postNapisane: niedziela, 29 października 2017, 14:58
przez Arnold_S
BARDZO dziękuję za cenne wskazówki!
...switch - no tak w prostocie siła :D
Funkcja "rzut_kostką" jest zdefiniowana raz w abstrakcyjnej klasie "zwierze". Czy to znaczy, że deklaracji (iż będę jej używał) nie muszę ponawiać w każdym nagłówkowym pliku, kolejnych klas potomnych, np: pies.h, opos.h, itd.?

Dlaczego klasa kot definiuje swoje rodzaj_kostki i rzut_kostka()?

Okej, to są publiczne sprawy klasy "zwierze" więc przy publicznym dziedziczeniu, są przekazywane dla potomka :D - dzięki, przeoczyłem to (brak doświadczenia i zez umysłowy).

Serio? Użycie wyjmij_string() ma być prostsze niż po prostu:
KOD cpp:     UKRYJ  
Memo1->Lines->Add(dachowiec.uszy.c_str());

Tworząc tą funkcję miałem bardziej na celu dostanie się do prywatnych składników klasy "kot" i tego samego w innych, np.: pies, opos, a nie prostotę użytkowania.
Zmieniłem kod według Twojego przepisu i nadal otrzymuję ten sam komunikat: [BCC32 Error] Unit1.cpp(32): E2247 'kot::uszy' is not accessible
Czy inkludując pliki nagłówkowe klas: zwierze i kot poprzez: #include "zwierze.h"...itd., to za mało? Muszę te pliki *.h i *.cpp dodać jakoś do projektu?

Mam jeszcze jedną wątpliwość: gdybym na przykład zechciał umieścić w klasie "kot" pryw.składniki: oczy, uszy i futro, w tablicy stringów, np.:
KOD cpp:     UKRYJ  
std:string tablica[3];

Funkcjami losuj_cośtam wypełniałbym pola tej tablicy ale jak miałbym przekazać tą wypełnioną tablicę na zewnątrz klasy, do swojego programu w XE2.
Musiałbym przeładować funkcję "wyjmij_string", tak aby przyjmowała tablicę...to raczej oczywiste, ale co miałaby zwracać abym wygodnie mógł wypełnić pola Memo1,2 i 3?
Myślę, że wewnątrz tej funkcji skopiowałbym zawartość tablicy stringów do lokalnie utworzonej tablicy obiektów UnicodeString i zwrócił wskaźnik do tej tablicy.
KOD cpp:     UKRYJ  
UnicodeString* wyjmij_string(const std::string &tab[], unsigned short int ile_pol)

Czy dobrze myślę?

Re: każda klasa w oddzielnym pliku...

Nowy postNapisane: niedziela, 29 października 2017, 19:52
przez polymorphism
(...) i nadal otrzymuję ten sam komunikat: [BCC32 Error] Unit1.cpp(32): E2247 'kot::uszy' is not accessible

Przeoczyłem to, że uszy są prywatne (zasugerowałem się Twoim kodem).

Czy dobrze myślę?

No a klasyczne gettery nie wystarczą?
KOD cpp:     UKRYJ  
class zwierze
{
public:
        virtual size_t getTabSize() const = 0;
        virtual const std::string* getTab() const = 0;
        ...
};


class kot : public zwierze
{
private:
        std::string     tab[3];
       
public:

        size_t getTabSize() const { return 3; }
        const std::string* getTab() const { return tab; }
        ...
};


std::unique_ptr<zwierze> wsk = std::make_unique<kot>();

for(size_t i = 0; i < wsk->getTabSize(); ++i)
        Memo1->Lines->Add(wsk->getTab()[i].c_str());

Re: każda klasa w oddzielnym pliku...

Nowy postNapisane: niedziela, 29 października 2017, 21:04
przez Arnold_S
/edit
Wydaje mi się i chyba się nie mylę, że funkcja "wyjmij_string" jest takim "getterem".
Mam tylko jedno pytanie. Co może powodować ten błąd E2247. Przecież funkcja "wyjmij_string" powinna mieć dostęp do prywatnych składników swojej klasy...a wygląda na to, że nie ma.

Re: każda klasa w oddzielnym pliku...

Nowy postNapisane: niedziela, 29 października 2017, 21:43
przez polymorphism
Jak podajesz błąd, to podaj pełną treść. Co do pytania: tak, metoda powinna mieć dostęp do prywatnych pól swojej klasy. Bez przykładowego kodu lub wspomnianej pełnej treści błędu trudno coś konkretnego powiedzieć.


PS, gettery to nie są narzędzia, tylko zwykłe metody, które zwracają wartości niepublicznych pól klasy. Hint: getter, setter, accessor, mutator

Re: każda klasa w oddzielnym pliku...

Nowy postNapisane: niedziela, 29 października 2017, 22:12
przez Arnold_S
Klasy "zwierze" i "kot" umieściłem na początku naszej ostatniej dyskusji. Oczywiście klasy poprawiłem tak jak zasugerowałeś.
Poniżej treść przykładowego programu w XE2:
KOD cpp:     UKRYJ  
//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop
#include <iostream>
#include "Unit1.h"
#include "zwierze.h"
#include "kot.h"
#include <ctime>          // dla time();
#include <cstdlib>   // dla srand();

//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
   srand(static_cast<unsigned int>(time(0)));
}
//---------------------------------------------------------------------------

void __fastcall TForm1::Button1Click(TObject *Sender)
{
        kot dachowiec;
        zwierze *wsk = &dachowiec;

        wsk->losuj_uszy();
        wsk->losuj_oczy();
        wsk->losuj_futro();
        Memo1->Lines->Add(dachowiec.wyjmij_string(dachowiec.uszy));
        Memo2->Lines->Add(dachowiec.oczy.c_str());
        Memo3->Lines->Add(dachowiec.futro.c_str());
}
//---------------------------------------------------------------------------


Treść błędu to:
[BCC32 Error] Unit1.cpp(32): E2247 'kot::uszy' is not accessible
Full parser context
Unit1.cpp(25): parsing: void _fastcall TForm1::Button1Click(TObject *)
[BCC32 Error] Unit1.cpp(33): E2247 'kot::oczy' is not accessible
Full parser context
Unit1.cpp(25): parsing: void _fastcall TForm1::Button1Click(TObject *)
[BCC32 Error] Unit1.cpp(34): E2247 'kot::futro' is not accessible
Full parser context
Unit1.cpp(25): parsing: void _fastcall TForm1::Button1Click(TObject *)

Re: każda klasa w oddzielnym pliku...

Nowy postNapisane: niedziela, 29 października 2017, 23:26
przez polymorphism
Ten błąd nie ma nic wspólnego z metodą wyjmij_string(), tylko z TForm1::Button1Click(), w której dobierasz się do prywatnych pół (oczy, uszy i futro) klasy kot.

Re: każda klasa w oddzielnym pliku...

Nowy postNapisane: poniedziałek, 30 października 2017, 00:59
przez Arnold_S
No niby tak...umieściłem to wyjmowanie w funkcji należącej do klasy TForm1 ale przecież staram się ją wyjąc przy pomocy funkcji należącej do klasy "kot". Nie mogę tak zrobić?
Czyli, żebym mógł legalnie operować funkcją "wyjmij_string" muszę zaprzyjaźnić funkcje klasyTForm1 z każdą klasą, np.: kot, pies, opos? Jakoś nie elegancko mi to wygląda...co jeśli będzie 100 buttonów? Czy może klasę abstrakcyjną i jej dziedziców umieścić wewnątrz klasy TForm1 jakby były jej obiektami? To zadziała?

/edit
W pliku zwierze.h i kot.h, w sekcji public, dodałem linijkę: :geek:
KOD cpp:     UKRYJ  
friend class TForm1;

Niestety w trakcie kompilacji pojawiają mi się błędy:
[ILINK32 Error] Error: Unresolved external 'kot::losuj_uszy()' referenced from C:\PROJEKTY\KLASY TEST\WIN32\DEBUG\UNIT1.OBJ
[ILINK32 Error] Error: Unresolved external 'kot::losuj_oczy()' referenced from C:\PROJEKTY\KLASY TEST\WIN32\DEBUG\UNIT1.OBJ
[ILINK32 Error] Error: Unresolved external 'kot::losuj_futro()' referenced from C:\PROJEKTY\KLASY TEST\WIN32\DEBUG\UNIT1.OBJ
[ILINK32 Error] Error: Unresolved external 'kot::wyjmij_string(std::basic_string<char, std::char_traits<char>, std::allocator<char> >&)' referenced from C:\PROJEKTY\KLASY TEST\WIN32\DEBUG\UNIT1.OBJ
[ILINK32 Error] Error: Unable to perform link

Re: każda klasa w oddzielnym pliku...

Nowy postNapisane: poniedziałek, 30 października 2017, 11:56
przez polymorphism
(...) ale przecież staram się ją wyjąc przy pomocy funkcji należącej do klasy "kot".

Mówisz o tym:
KOD cpp:     UKRYJ  
Memo1->Lines->Add(dachowiec.wyjmij_string(dachowiec.uszy));

:?: Przecież tutaj odwołujesz się do prywatnego pola kot::uszy z wnętrza metody, która nie wchodzi w skład klasy kot. To oczywiste, że masz błąd. Pokazałem Ci, jak powinien wyglądać dostęp do prywatnych pól klasy przy użyciu publicznych akcesorów/getterów.

W 99% przypadków nie ma potrzeby zaprzyjaźniania klas. Twój przypadek zdecydowanie nie należy do tego 1%.

Re: każda klasa w oddzielnym pliku...

Nowy postNapisane: poniedziałek, 30 października 2017, 19:47
przez Arnold_S
Jeśli chodzi o std::unique_ptr to chyba nie wypali ponieważ Builder XE2 nie obsługuje nawet C++11 ...a co dopiero C++14.
Poczytam, może na coś wpadnę. Czy jeśli buduję własną klasę pod ten builder, to czy deklaracje funkcji muszę ubierać w konwencje wywołania: __fastcall ?