Suchfunktion in einer Klasse mit mehreren Instanzen



  • Servus zusammen. Ich habe da mal eine Frage. Undzwar ist es möglich, wie in meinem Fall, mit einer Eingabe der Seriennummer eines KFZ´s, die restlichen bekannten Daten abzurufen?
    Man soll einmal die Funktion haben, ein neues KFZ mit Daten anzulegen. Und auch wieder abzurufen. Allerdings würde ich gerne anhand der Eingabe der Seriennummer, die verschiedenen Instanzen durchgehen wollen und die Daten angezeigt bekommen. Ich habe ein wenig probiert, allerdings will das ganze nicht so wirklich. Wie wäre es am sinnvollsten, das Problem zu lösen, dass die Abfrage der Seriennummer, die verschiedenen Instanzen durchsucht? Gerne Vorschläge vor Lösungen.

    #include <iostream>
    #include <string>
    using namespace std;
    
    class Auto
    {
    public:
    	void setInfo();
    	void getInfo();
    	void getSerial();
    
    
    private:
    	bool Richtig = false;
    	int Baujahr = 0;
    	int Serial = 0;
    	string Name;
    	string Modell;
    	string Zusatz;
    };
    
    void Auto::getSerial()
    {
    	int Seriennummer = 0;
    	do
    	{
    		cout << "Geben Sie die Serialnummer des KFZ ein: ";
    		cin >> Seriennummer;
    		
    			if (Seriennummer == Serial)
    			{
    				Richtig = true;
    			}
    			else
    			{
    				cout << "Serialnummer existiert nicht." << endl;
    			}
    			
    	} while (!Richtig);
    	getInfo();
    }
    
    void Auto::setInfo()
    {
    	cout << "PKW Zulassungsdaten: \n";
    	cout << "--------------------\n";
    	cout << "Hersteller: ";
    	cin >> Name;
    	cout << "Modell: ";
    	cin >> Modell;
    	cout << "Zusatz: ";
    	cin >> Zusatz;
    	cout << "Baujahr: ";
    	cin >> Baujahr;
    	cout << "Serialnummer: ";
    	cin >> Serial;
    	cout << endl;
    }
    
    void Auto::getInfo()
    {
    	cout << "\n\nPKW Zulassungsdaten: \n";
    	cout << "--------------------\n";
    	cout << "Hersteller: " << Name << endl;;
    	cout << "Modell: " << Modell << endl;
    	cout << "Zusatz: " << Zusatz << endl;
    	cout << "Baujahr: " << Baujahr << endl;
    }
    
    
    int main() 
    {
    	Auto erstwagen;
    	Auto erstwagen2;
    	Auto erstwagen3;
    
    	erstwagen.setInfo();
    	erstwagen2.setInfo();
    	erstwagen3.setInfo();
    	erstwagen.getSerial();
    	
    }
    
    


  • Die getSerial()-Funktion hat nur Zugriff auf die eigene Instanz und kennt die anderen Instanzen nicht.
    Packe in der main-Funktion die Auto-Objekte in ein std::array oder std::vector und erzeuge dann eine freie Funktion, um ein Auto darin zu suchen (alternativ erstelle eine Klasse für die Auto-Liste - sinnvoll, wenn du noch weitere Funktionen dafür erstellen möchtest).

    PS: Und die Variable Richtig sollte kein Member der Klasse sein, sondern nur eine lokale Variable (es ist ja kein Status eines Autos ;- ).



  • Das Vorgehen ist hier quasi Standard ... Alle Elemente in eine Liste/vector legen und über alle Elemente der Liste iterieren. Oder, für den schnellen Zugriff, eine unordered_map verwenden und über die vorher sinnvoll gewählten Schlüssel/Keys auf die Tupel/Elemente zugreifen. Beides setzt aber voraus, dass auf die Attribute deiner Elemente zugegriffen werden kann, entweder durch Getter-Funktionen oder du deklarierst sie public. Oder über eine Friend-Class: https://www.geeksforgeeks.org/friend-class-function-cpp/ und https://www.geeksforgeeks.org/access-modifiers-in-c/?ref=lbp



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



  • @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.



  • @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.


Anmelden zum Antworten