• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

Problem z deklaracją zmiennej w mainie oraz konstruktorami w podklasach.

0 głosów
103 wizyt
pytanie zadane 16 czerwca 2019 w C i C++ przez DamianW Bywalec (2,080 p.)

Witam, w poniższym kodzie chciałbym by każda podklasa posiadała zmienną int dlugosc odziedziczoną po        nadklasie  Porownywalne , która (dlugosc) jest inicjalizowana w konstruktorze nadklasy Porownywalne. Chciałbym również by każda podklasa (kijek,pudełko) posiadały odrębne zmienne (rodzaj_materialu, objetosc) , które są inicjalizowane w konstruktorach podklas. I tu pojawia się problem z moim sposobem wykonania tego zadania. Próbowałem wykorzystać to ,że konstruktor nadklasy jest wywoływany najpierw , a podklasy później.Kompilator wysypuje mi mnóstwo błędów, kilka godzin się męczyłem, ale niestety bezskutecznie :(. Jak zdefiniować same klasy oraz deklarację jakiegokolwiek obiektu w funkcji main ? Z góry dziękuję za pomoc :D.

Notka z kompilatora:

||=== Build: Debug in zadanie_1_polimorfia (compiler: GNU GCC Compiler) ===|

main.cpp||In constructor 'kijek::kijek(std::__cxx11::string)':|
main.cpp|42|error: no matching function for call to 'Porownywalne::Porownywalne()'|
main.cpp|13|note: candidate: Porownywalne::Porownywalne(int)|
|13|note:   candidate expects 1 argument, 0 provided|
\main.cpp|6|note: candidate: Porownywalne::Porownywalne(const Porownywalne&)|
\main.cpp|6|note:   candidate expects 1 argument, 0 provided|
\main.cpp||In constructor 'pudelko::pudelko(int)':|
\main.cpp|52|error: no matching function for call to 'Porownywalne::Porownywalne()'|
\main.cpp|13|note: candidate: Porownywalne::Porownywalne(int)|
\main.cpp|13|note:   candidate expects 1 argument, 0 provided|
\main.cpp|6|note: candidate: Porownywalne::Porownywalne(const Porownywalne&)|

\main.cpp|6|note:   candidate expects 1 argument, 0 provided|
||=== Build failed: 2 error(s), 0 warning(s) (0 minute(s), 0 second(s)) ===|

#include <iostream>

using namespace std;


class Porownywalne
{
private:
    int dlugosc;    /// dla wszystkich obiektow cechą wspolna bedzie jakas tam dlugosc

public:

    Porownywalne (int nowa_dlugosc)
    {
        dlugosc = nowa_dlugosc;
    }

    int porownaj (Porownywalne &inny)
    {
       if ((*this).dlugosc == (&inny)->dlugosc)
        {
            return 0;
        }
        if ((*this).dlugosc <= (&inny)->dlugosc)
        {
            return 1;
        }
        if ((*this).dlugosc >= (&inny)->dlugosc)
        {
            return -1;
        }
    }
friend class kijek;

};

class kijek :public Porownywalne
{
public:
    string rodzaj_materialu;    /// rodzaj materialu jest cecha tylko dla kijka
    kijek(string nowy_material)
    {
        rodzaj_materialu = nowy_material;
    }
};

class pudelko :public Porownywalne
{
public:
    int objetosc;    /// objetosc jest cecha charakterystyczna tylko dla pudelka
    pudelko(int nowa_objetosc)
    {
        objetosc = nowa_objetosc;
    }
};


int main()
{
    /// JAK TO WYWOŁAĆ ?
    return 0;
}

 

2 odpowiedzi

+1 głos
odpowiedź 16 czerwca 2019 przez mokrowski VIP (146,960 p.)
wybrane 17 czerwca 2019 przez DamianW
 
Najlepsza

Na początek literalnie odniosę się do kodu który umieściłeś.

Jeśli masz atrybut klasy typu długość, sam sobie odpowiedz na pytanie czy:

1. Może on mieć wartość ujemną.

2. Czy powinien być atrybutem publicznym w innych klasach. 

Akurat na to ostatnie pytanie już sobie odpowiedziałeś i klasie Porownywalne, masz ten atrybut prywatny. Jeśli tak jest, to skąd złamanie tej konsekwencji dla atrybutów objetosc i rodzaj_materialu? One także powinny być prywatne.

W 90% przypadków gdy masz klasę z 1 argumentem przekazywanym do konstruktora, chcesz by uniknąć konwersji typu:

Porownywalne p = 12;

Jeśli chcesz jej uniknąć (a przyznasz że powyższa linia w kontekście Porownywalne wygląda .. dziwnie), dodajesz do konstruktora explicit. To nie pozwoli na taką konwersję.

Inicjalizowanie atrybutów klasy, staraj się zawsze wykonywać w liście inicjalizacyjnej. Dla tak prostych typów jak int nie będzie widać szczególnej różnicy, aczkolwiek ten kod,

    Porownywalne (int nowa_dlugosc)
    {
        dlugosc = nowa_dlugosc;
    }

.. inicjalizuje atrybut dlugosc, dwa razy. Na początek jest to domyślny konstruktor int, który przypisze wartość 0 (zero) atrybutowi dlugosc, a następnie w ciele konstruktora będziesz miał przypisanie z nowa_dlugosc.

Zgadzam się z kolegą @adrian17 że metoda porownaj(..) wygląda dziwnie. Jeśli jednak jesteś pewien że ma zwracać wynik [-1, 0, 1], to zbędne jest operowanie this przy dostępach do danych bo wystarczy wyłącznie sama nazwa atrybutu oraz odniesienie poprzez inny. Poza tym inny (jako argument funkcji) powinien być przekazany jako stała referencja a sama metoda nie zmieniając stanu obiektu, powinna posiadać atrybut const.

Samo friend jest chyba przez przypadek bo tu jest totalnie zbędne.

Jak docelowo powinna wyglądać ta klasa, IMHO tak:

#include <iostream>
#include <cstddef>

using namespace std;

class Porownywalne
{
private:
    size_t dlugosc;
public:

    explicit Porownywalne (size_t nowa_dlugosc)
      : dlugosc{nowa_dlugosc}
    { }

    int porownaj (const Porownywalne &inny) const
    {
        if(dlugosc == inny.dlugosc) {
            return 0;
        } else if(dlugosc < inny.dlugosc) {
            return 1;
        }
        return -1;
    }
    size_t podaj_dlugosc() const
    {
        return dlugosc;
    }
};

Co do klasy kijek... Jest przecież zrozumiałe że jeśli tworzysz kijek, powinien on mieć długość i rodzaj materiału. Jego długość trafi za pośrednictwem konstruktora rodzica do atrybutu dlugosc a rodzaj materiału do atrybutu rodzaj_materialu. Jak poprzednio, rozsądne jest dodanie dostępu do prywatnego pola rodzaju materiału a dodatkowo przesłanie "ciężkiego" argumentu typu std::string, jako stałej referencji. 

Stąd klasa kilek (dlaczego z małej litery nie wiem, raczej jest konwencja nazywania obiektów z małych liter a klas z dużej), może IMHO wyglądać tak:

class Kijek :public Porownywalne
{
private:
    string rodzaj_materialu;
public:
    Kijek(size_t dlugosc, const string& nowy_material)
      : Porownywalne{dlugosc}, rodzaj_materialu{nowy_material}
    { }
    const string& podaj_rodzaj_materialu() const
    {
        return rodzaj_materialu;
    }
};

Co do pudełka, dziedziczenie z Porownywalne jest dość egzotyczne. Chcesz porównywać pudełka wyłącznie na podstawie długości? W tym kodzie (a odnoszę się do niego literalnie), masz szansę wyłącznie na taką implementację. Mnie osobiście to zastanawia, ale jeśli mam iść tym tropem, to podobnie jak w Kijku. Masz obowiązek podać długość aby inicjować rodzica Porwnywalne.

IMHO może to wyglądać tak:

class Pudelko :public Porownywalne
{
private:
    size_t objetosc;
public:
    Pudelko(size_t dlugosc, size_t nowa_objetosc)
      : Porownywalne{dlugosc}, objetosc{nowa_objetosc}
    { }
    const size_t& podaj_objetosc() const
    {
        return objetosc;
    }
};

Po tych poprawkach, kod da się wywołać np. konstruując kijek tak:

int main()
{
    Kijek kijek{12, "amelinium"};
    std::cout << kijek.podaj_rodzaj_materialu() << '\n';
}

 

komentarz 17 czerwca 2019 przez DamianW Bywalec (2,080 p.)
Dziękuję bardzo za odpowiedź. Chciałbym jeszcze się dowiedzieć w jakich sytuacjach lepsze jest zastosowanie listy inicjalizacyjnej niż konstruktora oraz na odwrót.
1
komentarz 17 czerwca 2019 przez mokrowski VIP (146,960 p.)
Ogólna zasada to "zrób wszystko by inicjalizować w liście inicjalizacyjnej". To nie jest "albo" a "najpierw lista, później (ew.) konstruktor". W przypadkach w których się nie da (np. alokowanie wątków, blokady itp), resztę pracy wykonasz w ciele konstruktora.

Stosowanie listy incjalizacyjnej jako 1 wyboru jest zawsze lepsze. Dzięki niej możesz np. inicjalizować pola stałe (const).

Czasem wystąpi sytuacja gdzie inicjalizowane elementy są od siebie zależne. Wtedy lista inicjalizuje nie w kolejności w jakiej ją zapiszesz, a w kolejności podanych atrybutów w definicji klasy. Wybór jest wtedy determinowany problemem: albo "kruchy kod" (bo ktoś może zmienić kolejność definicji w klasie i lista inicjalizacyjna może zawieść), albo kod bezpieczny. Raczej wybiera się to 2 (bezpieczny). Wtedy inicjalizację przeprowadzisz w konstruktorze (a raczej ją dokończysz).
komentarz 17 czerwca 2019 przez DamianW Bywalec (2,080 p.)
Dziękuję Ci bardzo za udzielenie tych odpowiedzi, były bardzo pomocne. Pozdrawiam :D
+2 głosów
odpowiedź 16 czerwca 2019 przez adrian17 Ekspert (306,660 p.)

no matching function for call to 'Porownywalne::Porownywalne()

Przy inicjalizacji Kijek trzeba wywołać konstruktor klasy bazowej? Który? Domyślnie domyślny bezargumentowy, ale... takiego nie ma. Jest za to:

    Porownywalne (int nowa_dlugosc)

Więc konstruktor klas dziedziczących powinien wyglądać tak:

    Kijek(std::string nowy_material)
        : Porownywalne(123) // jakaś długość, możesz ją przekazać jako argument
    {
        rodzaj_materialu = nowy_material;
    }

A idealnie

    Kijek(std::string nowy_material)
        : Porownywalne(123), // jakaś długość, możesz ją przekazać jako argument
          rodzaj_materialu(nowy_material)
    {}

Boczne pytania:

- po co ten `friend`?

- ten `porownaj` wygląda bardzo dziwnie, czemu nie po prostu to?

if (dlugosc == inny.dlugosc)

(i czemu metoda porownaj zamiast przeładowania operatorów porównania?)

Podobne pytania

0 głosów
2 odpowiedzi 143 wizyt
0 głosów
1 odpowiedź 385 wizyt
0 głosów
1 odpowiedź 89 wizyt
pytanie zadane 30 listopada 2016 w C i C++ przez sh1nen Obywatel (1,590 p.)

86,485 zapytań

135,240 odpowiedzi

300,484 komentarzy

57,232 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto dwie polecane książki warte uwagi. Pełną listę znajdziesz tutaj.

...