Suchfunktion in einer Klasse mit mehreren Instanzen



  • @Lumberjack sagte in Suchfunktion in einer Klasse mit mehreren Instanzen:

    Danke euch. Werde mir das mit der Liste und verketteten Listen auch noch anschauen und mich dann melden.

    Wie kommst du jetzt auf (verkettete) Listen? In 99,9% der Fälle willst du keine verkettete Liste benutzen wollen! Konzentriere dich auf die anderen Standard-Container, starte mit std::vector und schau dir dann std::map und std::set an. Hier sind alle Container: https://en.cppreference.com/w/cpp/container - aber wie gesagt, es ist fraglich, ob du list jemals brauchen wirst.



  • Eigene Liste-Implementierung ist zum Üben eigentlich gar nicht mal so verkehrt ... aber eben nur zum Üben.^^

    Du willst quasi eine Deque (Double-ended queue, sprich: "Deck") selber nachbilden: https://www.programiz.com/cpp-programming/deque



  • Hallo @Lumberjack,
    ist ja schon mal ein guter Anfang. Ich würde mich jetzt mal etwas mit dem objektorientierten Paradigma auseinandersetzen. Du hast ja schon mal eine Klasse "Auto". Diese Klasse sollte eigentlich nur Attribute und Methoden zur Verfügung stellen, die ein einzelnes Auto-Objekt betreffen.
    Die Ein- und Ausgabe der einzelnen Autodaten würde ich nicht in die Auto-Klasse packen. Stell Dir vor, Du willst das Programm mal auf etwas anderes als der Konsole laufen lassen (Stichwort 'Wiederverwendbarkeit'). Mach doch für diese Sachen eine oder mehrere Service-Klassen. Diese erfragen z.B. die Daten und übertragen diese in das jeweilige Auto-Objekt.
    Und einen Container (z.B. std::vector) für die Autos kannst Du gut in einer Autoverwalter-Klasse unterbringen, die anderen Instanzen ein oder auch mehrere Autos liefert bzw. diese in den Vector übernimmt.
    Also kurz gefasst, Mut zu noch mehr Klassen, deren Objekte nur das tun, was ihre Aufgabe ist.



  • @DocShoe sagte in Suchfunktion in einer Klasse mit mehreren Instanzen:

    @Lumberjack sagte in Suchfunktion in einer Klasse mit mehreren Instanzen:

    Danke euch. Werde mir das mit der Liste und verketteten Listen auch noch anschauen und mich dann melden.

    std::vector ist alles, was du brauchst.

    Ok. War gerade nebenbei Zufällig bei dem Thema Listen/verkettete Listen. Dann sehe ich mal mit dem vector weiter (Y)



  • @Lumberjack sagte in Suchfunktion in einer Klasse mit mehreren Instanzen:

    @DocShoe sagte in Suchfunktion in einer Klasse mit mehreren Instanzen:

    std::vector ist alles, was du brauchst.

    Ok. War gerade nebenbei Zufällig bei dem Thema Listen/verkettete Listen. Dann sehe ich mal mit dem vector weiter (Y)

    Wenn du die Theorie zum Thema "Algorithmen und Datenstrukturen" lernen willst, ist es nicht verkehrt, sich die Container alle mal anzusehen, zu wissen wie sie funktionieren und was für Vor- und Nachteile sie haben.

    Doch hier ist der Spoiler: Während die Theorie hinter den Datenstrukturen nicht falsch ist, hat @DocShoe dennoch recht. Effekte wie Speicherzugriffszeiten und CPU-Caches dominieren selbst für extrem viele Datensätze (~hunderttausende) die tatsächliche Performance auf realen Computern derart, dass std::vector immer die erste Wahl sein sollte, wenn keine guten Gründe dagegen sprechen.



  • @Lumberjack Also, deiner Beschreibung nach, hätte ich das so umgesetzt (eigene Linked-List und Filterfunktion):

    #include <iostream>
    #include <string>
    #include <stdexcept>
    #include <map>
    using namespace std;
    
    class Element
    {
    private:
        int topspeed;
        int horsepower;
        double kg;
        Element *next = nullptr;
    
    public:
        Element(int kmh, int hp, double kg) : topspeed(kmh), horsepower(hp), kg(kg){};
        // ~Element();
        int getTopspeed();
        int getHorsepower();
        double getKg();
        Element *getNext();
        void setNext(Element *e);
    };
    
    int Element::getTopspeed()
    {
        return topspeed;
    }
    int Element::getHorsepower()
    {
        return horsepower;
    }
    double Element::getKg()
    {
        return kg;
    }
    Element *Element::getNext()
    {
        return next;
    }
    void Element::setNext(Element *e)
    {
        next = e;
        if (e != nullptr)
        {
            next->next = nullptr; // to go sure
        }
    }
    
    class Liste
    {
    private:
        Element *first = nullptr;
    
    public:
        // Liste();
        // ~Liste();
        void addElement(Element *e);
        void printAll();
        void filterPrint(string nameOfAttribute, double min, double max);
    };
    
    void Liste::addElement(Element *e)
    {
        if (e == nullptr)
        {
            throw invalid_argument("nullptr nicht erlaubt hier");
        }
        if (first == nullptr)
        {
            first = e;
            e->setNext(nullptr); // to go sure
            return;
        }
        Element *temp = first;
        while (temp->getNext() != nullptr)
        {
            temp = temp->getNext();
        }
        temp->setNext(e);
    }
    void Liste::printAll()
    {
        Element *temp = first;
        for (size_t i = 1;; i++)
        {
            cout << temp << endl;
            if (temp == nullptr)
            {
                cout << i - 1 << endl
                     << endl;
                break;
            }
            temp = temp->getNext();
        }
    }
    void Liste::filterPrint(string nameOfAttribute, double min, double max)
    {
        map<string, int (Element::*)()> mapi;
        map<string, double (Element::*)()> mapf;
        mapi["topspeed"] = &Element::getTopspeed;
        mapi["horsepower"] = &Element::getHorsepower;
        mapf["kg"] = &Element::getKg;
    
        Element *temp = first;
        for (size_t i = 1;;)
        {
            if (temp == nullptr)
            {
                cout << i - 1 << endl
                     << endl;
                break;
            }
            if ((temp->*mapi[nameOfAttribute])() >= min && (temp->*mapi[nameOfAttribute])() <= max)
            {
                cout << temp << endl;
                i++;
            }
            temp = temp->getNext();
        }
    }
    
    int main(int argc, char const *argv[])
    {
        Element *e1 = new Element(200, 100, 1500);
        Element *e2 = new Element(299, 350, 1900);
        Element *e3 = new Element(325, 500, 1800);
        Element *e4 = new Element(312, 390, 1555);
        Liste *l = new Liste();
        l->printAll();
        l->addElement(e1);
        l->addElement(e2);
        l->addElement(e3);
        l->addElement(e4);
        l->printAll();
        l->filterPrint("topspeed", 250, 313);
        l->filterPrint("horsepower", 401, 999);
        return 0;
    }
    
    

    Interessant ist hier vor allem die Filterfunktion in Zeile 97 bis 121.

    (E: In meine Liste kann man nur hinzufügen, nicht entfernen 🤷♂)

    Und zum Weiterlesen: https://stackoverflow.com/questions/1924844/stdmap-of-member-function-pointers



  • @Lumberjack
    Ist C++20 nicht schön?

    #include <iostream>
    #include <vector>
    #include <algorithm>
    #include <ranges>
    #include <string>
    
    class Car
    {
    private:
        std::string mName;
        unsigned int mTopSpeed;
        unsigned int mHorsepower;
        double mWeight;
    
    public:
        Car() :     // Initialisert die Klasse mit den Standardwerten 
            mName(""),
            mTopSpeed(100),
            mHorsepower(50),
            mWeight(900) {
        }
    
        Car(const std::string& Name, unsigned int TopSpeed, unsigned int Horsepower, double Weight) :
            mName(Name),
            mTopSpeed(TopSpeed),
            mHorsepower(Horsepower),
            mWeight(Weight)
        {}
    
        std::string GetName() const {
            return mName;
        }
    
        unsigned int GetTopSpeed() const {
            return mTopSpeed;
        }
    
        unsigned int GetHorsepower() const {
            return mHorsepower;
        }
    
        double GetWeight() const {
            return mWeight;
        }
    };
    
    int main()
    {
        std::vector<Car> L{
            Car("Audi A3", 200, 100, 1500),
            Car("Daccia", 299, 350, 1900),
            Car("BMW", 325, 500, 1800),
            Car("Opel", 312, 390, 1555),
        };
        double MinWeight = 1525;
        double MaxWeight = 1850;
    
        for (const Car& c : L | std::views::filter([&](const Car& ci) {
            return ci.GetWeight() > MinWeight && ci.GetWeight() < MaxWeight;
            }))
        {
            std::cout << "Car: Name = \"" << c.GetName() <<
                "\"\tTopSpeed = " << c.GetTopSpeed() <<
                "\tHorsepower = " << c.GetHorsepower() <<
                "\tWeight = " << c.GetWeight() << "\n";
        }
            return 0;
    }
    

    Versionshistorie:

    • 1.01: Umstellung der konstanten Attribute auf private, nicht konstante Attribute, da diese besser mit std::vector bzw. std::vector<>::resize() arbeitet.


  • @Quiche-Lorraine
    Nice!

    Edit:
    Aber müssen die Attribute alle const sein? Macht das Handling etwas schwierig, weil man keine Kopien machen kann. Von daher würde ich entweder das const entfernen oder die Attribute privat machen und nur lesende Funktionen ergänzen.



  • Jetzt mal ernsthaft ... Wieso wurde mein Codevorschlag mehrfach(!) negativ bewertet? Sind die Deppen wieder los?

    @Quiche-Lorraine Sieht ganz gut aus.



  • @DocShoe sagte in Suchfunktion in einer Klasse mit mehreren Instanzen:

    Aber müssen die Attribute alle const sein?

    Nö, mit private Variablen wäre ich auch zufrieden.

    Aber die Attribute haben etwas konstantes an sich, da sich diese nicht ändern werden.

    Macht das Handling etwas schwierig, weil man keine Kopien machen kann.

    Diesen Satz verstehe ich nicht da...

    #include <iostream>
    #include <vector>
    #include <algorithm>
    #include <ranges>
    #include <string>
    
    class Car
    {
    public:
        const std::string mName;
        const unsigned int mTopSpeed;
        const unsigned int mHorsepower;
        const double mWeight;
    private:
        double mFuelCapacity = 0;           // 0 = Tank leer, 1 = Tank voll 
    
    public:
        Car(const std::string& Name, unsigned int TopSpeed, unsigned int Horsepower, double Weight) :
            mName(Name),
            mTopSpeed(TopSpeed),
            mHorsepower(Horsepower),
            mWeight(Weight)
        {}
    
        void SetFuel(double Capacity)
        {
            if ((Capacity < 0) && (Capacity > 1))
                throw std::out_of_range("Fuel must be beetween 0 and 1");
            mFuelCapacity = Capacity;
        }
    
        double GetFuel() const
        {
            return mFuelCapacity;
        }
    };
    
    
    int main()  // Testcode
    {
        std::vector<Car> L;       
        Car a1("Opel", 312, 390, 1555);
        //Car a3;     // Fehler, da kein Standardkonstruktor vorhanden ist. Ist ok.
    
        for (int i = 0; i < 100; i++)
        {
            Car a2 = a1;
    
            a2.SetFuel(i / 100.0);
            L.push_back(a2);
        }
        std::for_each(std::begin(L), std::end(L), [](const Car& c) {
            std::cout << "Car: Name = \"" << c.mName <<
                "\"\tTopSpeed = " << c.mTopSpeed <<
                "\tHorsepower = " << c.mHorsepower <<
                "\tWeight = " << c.mWeight <<
                "\tFuel = " << c.GetFuel() << "\n";
            });
        return 0;
    }
    

    Selbst std::vector scheint mit Klassen ohne Standardkonstruktor zurechtzukommen. Warum das so ist, weis ich aber nicht.



  • @Quiche-Lorraine

    Vermutlich, weil std::vector in deinem Fall mit move auskommt und Objekte verschiebt, statt sie zu kopieren.
    L.resize( 105 ) geht aber nicht, da in diesem Fall tatsächlich Car-Objekte default-konstruiert und kopiert werden müssen.



  • Ich würde auch auf const verzichten in dem Fall, und stattdessen (nur) mit Gettern arbeiten.

    Ich hab noch keine Begründung gesehen, was an meinem Code soo falsch sein soll.



  • @Fragender

    An deinem Code ist so ziemlich alles falsch oder schlecht designed, was man so machen kann. Hat Jürgen-Wolf-Qualität. Gibt aber einen Pluspunkt, weil wenigstens nix von Wurstbroterbt. Oder von Supermarkt? Weiß nicht mehr so genau.

    1. Der Name der Klasse Element ist irreführend. Ja, es ist ein Listenelement, aber primär ist es eine Klasse für Fahrzeugdaten.

    2. Warum intrusiv? Ein Auto muss seinen Nachfolger nicht kennen. Mir fällt auch spontan kein Fall ein, wo man eine intrusive Liste bräuchte.

    3. Unklare Besitzverhältnisse der rohen Zeiger. Wer ist für die Freigabe verantwortlich? Und generell ist der Zwang, Elemente auf dem Heap zu erzeugen, unnötig und kann besser gelöst werden. Dein Code löst das gar nicht, weil er den Speicher erst gar nicht mehr freigibt.

    4. Single Responsibility Principle wird verletzt: Deine Liste speichert nicht nur Elemente, sondern filtert und gibt sie auch aus. Gehört da nicht rein und sollte extern gelöst werden. Geht natürlich nicht, weil deine Liste keinen Zugriff auf die einzelnen Elemente zulässt.

    5. Die "interessante" filterPrint-Funktion ist kompletter Humbug. Statt direkt auf Attribute zuzugreifen wird zur Laufzeit ein Attributname vergeben, anhand dessen in zwei std::maps nach entsprechenden Memberfunktionen gesucht wird, die zur Bestimmung des Vergleichswertes zum Filtern benutzt werden. Langsam, fehleranfällig und unflexibel. Man kann nur nach einem Kriterium filtern, und dann muss auch noch ein Bereich festgelegt werden. Wird spätestens dann lustig, wenn man alle Mercedes haben möchte....

    6. Wiederverwendbarkeit: Nada. Dein Code löst genau nur das eine Problem. Wenn man schon einen eigenen hust "Container" baut, dann sollte der wiederverwendbar sein, zumindest, wenn man sich für einen fortgeschrittenen Programmierer hält.

    Eine Idee für eine wiederverwendbare doppelt verkettete Liste könnte so ausehen. Hier nur der Ansatz, die Fleißaufgabe überlasse ich dir. Insbesondere das Kopieren und Aufräumen.

    template<typename T>
    class linked_list
    {
    public:
       using value_type = T;
       using const_reference = T const&;
    
    private:
       struct list_node
       {
          list_node* Pred = nullptr;
          list_node* Succ = nullptr;
          value_type Value;
    
          list_node( const_reference value ) :
             Value( value )
          {
          }
       };
       
       list_node* Head_ = nullptr;
       list_node* Tail_ = nullptr;
    
    public:
       linked_list() = default;
       ~linked_list()
       {
          // to do  
       }
    
       // Kopien/Zuweisungen verbieten
       linked_list( linked_listconst& other ) = delete;
       linked_list& operator=( linked_list const& other ) = delete;
    
       // Move erlauben
       linked_list( linked_list&& other ) :
          Head_( std::move( other.Head_ ),
          Tail_( std::move( other.Tail_ )
       {
       }
    
       linked_list& operator=( linked_list&& other )
       {
          Head_ = std::move( other.Head_ );
          Tail_ = std::move( other.Tail_ );
          return *this;
       }
    
       void append( const_reference value )
       {
          // to do
       }
    };
    

    Da haste noch genug Platz zum Austoben...

    Oder man spart sich den ganzen Quatsch und benutzt std::vector, der, wie @Quiche-Lorraine sehr schön gezeigt hat, die STL Algos unterstützt und man kaum selbst was programmieren muss. Das C++20 Filter-Feature kannte ich noch nicht, aber boost hat sowas auch.



  • @DocShoe sagte in Suchfunktion in einer Klasse mit mehreren Instanzen:

    Vermutlich, weil std::vector in deinem Fall mit move auskommt und Objekte verschiebt, statt sie zu kopieren.
    L.resize( 105 ) geht aber nicht, da in diesem Fall tatsächlich Car-Objekte default-konstruiert und kopiert werden müssen.

    Du hast mich überzeugt.

    So ein nicht funktionierendes resize() ist aus meiner Sicht ein unerwünschter Seiteneffekt. Und eine Definition eines Standardkonstruktors Car ist ja auch problemlos möglich.



  • @Quiche-Lorraine sagte in Suchfunktion in einer Klasse mit mehreren Instanzen:

    @DocShoe sagte in Suchfunktion in einer Klasse mit mehreren Instanzen:

    Vermutlich, weil std::vector in deinem Fall mit move auskommt und Objekte verschiebt, statt sie zu kopieren.
    L.resize( 105 ) geht aber nicht, da in diesem Fall tatsächlich Car-Objekte default-konstruiert und kopiert werden müssen.

    Du hast mich überzeugt.

    So ein nicht funktionierendes resize() ist aus meiner Sicht ein unerwünschter Seiteneffekt. Und eine Definition eines Standardkonstruktors Car ist ja auch problemlos möglich.

    Ist ja nicht so, dass ich in meinem Code sowas nicht auch schon mal probiert hätte 😉 In meinem Fall hatten Objekte eine eindeutige ID, die ich nicht mehr ändern lassen wollte.



  • Zu 6.: Das hat @Quiche-Lorraine doch auch nicht anders gemacht.

    Keiner der Kritikpunkte, bis auf 3., ist gerechtfertigt.

    Das war's dann hier für mich, viel Spaß noch bei dem Unsinn.



  • @Ein-ehemaliger-Benutzer sagte in Suchfunktion in einer Klasse mit mehreren Instanzen:

    Zu 6.: Das hat @Quiche-Lorraine doch auch nicht anders gemacht.

    Keiner der Kritikpunkte, bis auf 3., ist gerechtfertigt.

    Das war's dann hier für mich, viel Spaß noch bei dem Unsinn.

    Allein das war's wert.



  • @Ein-ehemaliger-Benutzer sagte in Suchfunktion in einer Klasse mit mehreren Instanzen:

    Zu 6.: Das hat @Quiche-Lorraine doch auch nicht anders gemacht.

    Keiner der Kritikpunkte, bis auf 3., ist gerechtfertigt.

    Das war's dann hier für mich, viel Spaß noch bei dem Unsinn.

    Ich werde Dich sehr vermissen ...



  • Ich finds ja schade. Hat immer viel Code gezeigt, und auch wenn mal schlechter Code darunter gewesen ist, auch davon kann man lernen, solange andere ihn korrigieren.



  • @zeropage

    Ich finds ja schade. Hat immer viel Code gezeigt, und auch wenn mal schlechter Code darunter gewesen ist, auch davon kann man lernen, solange andere ihn korrigieren.

    Das finde ich bezogen auf Fragender ein wenig widersprüchlich, da Selbstreflektion war noch nie seine Stärke war. Und eine pampige Antwort der Form

    Keiner der Kritikpunkte, bis auf 3., ist gerechtfertigt. Das war's dann hier für mich, viel Spaß noch bei dem Unsinn."

    habe ich ehrlich gesagt schon erwartet.

    Wenn ich meinen Code nicht präsentiert hätte, dann hätte @Lumberjack einen völlig veralteten und fehlerhaften Eindruck von modernem C++ bekommen. Vielleicht mag man von schlechten Code etwas lernen können, dieser ist aber mindestens im gleichen Maße gefährlich, da alte totgesagte Fehler so immer wieder ans Licht kommen und die Anfänger verwirren.


Anmelden zum Antworten