Polymorphie will nicht wie ich will...



  • Hallo ihr Lieben.
    Ich habe einen QUelltext recht ähnlich zu etwas, was ich +über Polymorphie gelesen habe.
    Ich habe in der Basisklasse "Animal" eine Methode namens "ShoutOut()".
    Die soll ausgeben, welchen Laut ein Tier von sich gibt. Die abgleitete Klasse "Cat" soll "Miau" machen.
    Ich habe die Mehtode in der basisklasse als virtual deklariert.
    Jetzt erwarte ich eigentlich, dass die Ausgabe bei einem Tier namens "Cat" entsprechend "Miau" sein sollte - aber es kommt "Hahaaaa!" (angelehnt an Alf) raus.
    Wo ist mein Denkfehler?

    #include <iostream>
    using namespace std;

    class Animal{
    protected:
    string Name;
    string FavouriteFood;

    public:
    	//Constructor
    	Animal(string _Name, string _FavouriteFood);	//Prototype
    	
    	string get_Name(){return Name;}
    	string get_FavouriteFood(){return FavouriteFood;}
    	
    	virtual void ShoutOut(){cout << "Hahaaaa!" << endl;}
    

    };

    	// Constructor Animal
    	Animal::Animal(string _Name, string _FavouriteFood){
    		Name = _Name;
    		FavouriteFood = _FavouriteFood;
    	}
    

    class Cat : public Animal{

    public:
    	//Constructor
    	Cat(string _Name, string _FavouriteFood) : Animal(_Name, _FavouriteFood) {}
    	
    	void ShoutOut(){
    		cout << "Miau!" << endl;
    	}	
    

    };

    	void gibLaut(Animal* ptr){
    		ptr->ShoutOut();
    	}
    

    int main(){

    Cat Cindy("Cindy", "Fish");
    Animal Alf("Alf", "Cats");
    

    Animal a[] = {Cindy, Alf};

    for(int i=0; i<2; i++)
    {
    gibLaut(&a[i]);
    }

    return 0;
    }



  • @Hawaiihemd sagte in Polymorphie will nicht wie ich will...:

    Jetzt erwarte ich eigentlich, dass die Ausgabe bei einem Tier namens "Cat" entsprechend "Miau" sein sollte - aber es kommt "Hahaaaa!" (angelehnt an Alf) raus.
    Wo ist mein Denkfehler?

    Hier ist dein Denkfehler:

        Cat Cindy("Cindy", "Fish");
        Animal Alf("Alf", "Cats");
        Animal a[] = {Cindy, Alf};
    

    Die Variable a enthält 2 Animals. Arrays enthalten immer nur gleiche Elemente. Ein Array kann also nicht ein Animal und eine Cat speichern. Wenn du einem Objekt einer Basisklasse das einer abgeleiteten Klasse zuweise, dann passiert sogenanntes "Slicing", d.h. es wird nur der Basisklassenanteil kopiert. Du sagst, du möchtest die Katze als Animal behandeln. Das gilt aber nur für Objekte, nicht aber, wenn du Pointer oder Referenzen verwendest.

    Was du hier machen kannst: ein Array kann Pointer enthalten (Pointer sind immer gleich groß, egal worauf sie zeigen).

        Cat Cindy("Cindy", "Fish");
        Animal Alf("Alf", "Cats");
        Animal *a[] = {&Cindy, &Alf};  // <-- hier ein Pointer-Array
    
        for (int i = 0; i < 2; i++) {
            gibLaut(a[i]);  // <-- hier dann kein & mehr, das Array enthält ja pointer
        }
    

    Außerdem würde ich dir raten, std::array oder std::vector zu verwenden statt C-Style-Arrays. Und in der Loop ein range-for.



  • Ja MEGA - und schon klappt. Es!
    Tatsächlich war das mein Denkfehler.
    Vielen Dank Dir!
    So einfach kann es sein 🙂



  • Tja, interessant. Ein zweites Beispiel, bei dem ich wieder einen Denkfehler zu haben scheine.
    Logisch, sonst würde es ja klappen.

    Ich habe jetzt folgenden Code (Fragestellung anbei):

    #include<iostream>
    #include <vector>
     
    using namespace std;
    struct Person;
    void ausgeben(vector<Person*> v)
    {
        for(auto it = v.begin(); it != v.end(); ++it)
        {
           	*it->print_infos(); 
            cout << endl << endl;
        }    
    }
     
     
    class Person{
      protected:
      string name;
      string lastname;
      
      public:
      //Constructors
      Person(string _name, string _lastname){name = _name; lastname = _lastname;}
        // Destructor
        ~Person(){}
        
        // Methods
        string get_name(){return name;}
        string get_lastname(){return lastname;}
        void set_name(string _name){name = _name;}
        void set_lastname(string _lastname){name = _lastname;}
        virtual void print_infos(){
            cout << "Name: " << get_name() << endl;
            cout << "Lastname: " << get_lastname() << endl;
            }
    };
     
    class Mitarbeiter : public Person{
      protected:
      double gehalt;
        public:
        //Constructors
        Mitarbeiter(string _name, string _lastname, double _gehalt):Person(_name, _lastname){
            gehalt = _gehalt;}
        
        double get_gehalt(){return gehalt;}
        
            void print_infos(){
            cout << "Name: " << get_name() << endl;
            cout << "Lastname: " << get_lastname() << endl;
            cout << "Salary: " << get_gehalt() << endl;
            }
    };
     
     
    int main()
    {
        vector<Person*> v{};
        Person p1("Erika", "Mustermann");
        Mitarbeiter m1("Max", "Mueller", 3500.67);
        
        v.push_back(&p1);
        v.push_back(&m1);
        
        ausgeben(v);
        
        
        return 0;
    }
    

    Die einzige Fehlermeldung die kommt, macht mein Compiler in diesem Abschnitt:

    void ausgeben(vector<Person*> v)
    {
        for(auto it = v.begin(); it != v.end(); ++it)
        {
           	*it->print_infos(); 
            cout << endl << endl;
        }    
    }
    

    Er sagt:
    request for member 'print_infos' in '*it.__gnu_cxx::__normal_iterator<Person**, std::vector<Person*> >::operator->()', which is of pointer type 'Person*' (maybe you meant to use '->'?

    Ich benutze doch "->".
    Und so wie ich das verstehe, zeigt der Iterator it zu Beginn auf das erste Element im Vektor. Im Vektor sind Zeiger auf "Person" (bzw. die abgeleitete Version "Mitarbeiter"). Als virtual habe ich die Funktion "print_infos()" deklariert.

    Wo ist da jetzt mein Denkfehler? Ist bestimmt wieder voll easy, aber ich bin raus...



  • @Hawaiihemd
    Formatier bitte deine Beiträge passend. Das Forum verwendet Markdown, wie man damit Text formatiert lässt sich leicht ergoogeln.



  • Schat dir mal https://en.cppreference.com/w/cpp/language/operator_precedence an. Achte auf den Stern und den Pfeil. Und dann überlege dir, ob/wo Klammern helfen könnten.



  • @hustbaer Dankeschön. Sorry, hab ich nicht dran gedacht.
    Mache ich ab sofort immer! Ist editiert!



  • Hawaiihemd proudly presents:
    Die Lösung 🙂

    Danke für Eure Kommentare und Hilfen - einmal mehr :-.)

    #include<iostream>
    #include <vector>
    #include "Person.hpp"
     
    using namespace std;
    
    
    
    void ausgeben(vector<Person*> v)
    {
        for(auto it = v.begin(); it != v.end(); ++it)
        {
           	(*it)->print_infos(); 
            cout << endl << endl;
        }    
    }
    
    class Mitarbeiter : public Person{
      protected:
      double gehalt;
        public:
        //Constructors
        Mitarbeiter(string _name, string _lastname, double _gehalt):Person(_name, _lastname){
            gehalt = _gehalt;}
        
        double get_gehalt(){return gehalt;}
        
            void print_infos(){
            cout << "Name: " << get_name() << endl;
            cout << "Lastname: " << get_lastname() << endl;
            cout << "Salary: " << get_gehalt() << endl;
            }
    };
     
     
    int main()
    {
        vector<Person*> v{};
        Person p1("Erika", "Mustermann");
        Mitarbeiter m1("Max", "Mueller", 3500.67);
        
        v.push_back(&p1);
        v.push_back(&m1);
        
        ausgeben(v);
        
        
        return 0;
    }
    

    Die Klasse "Person" habe ich in die Headerdatei ausgelagert 🙂



  • @Hawaiihemd 2 Anmerkungen.

    1. ausgeben bekommt eine Kopie von einem Vektor. Auch wenn der Vektor nur Pointer enthält, kann das, je nach größe des Vektors teuer sein. Da die Funktion den Vektor nicht verändert, wäre es schöner den als const reference zu übergeben (const vector<Person*>& v).

    2. Wenn man durch einen ganzen Container iterieren möchte, ist es meiner Meinung nach schöner Range based for loops zu verwenden.



  • @Schlangenmensch Vielen Dank auch für diese wertvollen Tipps!


Anmelden zum Antworten