Klassen, Pointer und Funktionen



  • Hallo, danke euch beiden.
    Na, das war ja ein schoen dummer Fehler.
    Habe jetzt also die Zeile durch

    this->projection = direction;
        this->projection.Normalize();
    

    ersetzt und jetzt meckert zumindest der Compiler nicht mehr 🙂

    Ich haette da aber noch eine Frage. Es ist naemlich zB auch gegeben:

    static void Cross3(Vec3f &c, const Vec3f &v1, const Vec3f &v2) {
        float x = v1.data[1]*v2.data[2] - v1.data[2]*v2.data[1];
        float y = v1.data[2]*v2.data[0] - v1.data[0]*v2.data[2];
        float z = v1.data[0]*v2.data[1] - v1.data[1]*v2.data[0];
        c.data[0] = x; c.data[1] = y; c.data[2] = z; }
    

    In der Definition der Klasse Vec3f.

    Warum steht da das "&" bei den Parametern?
    Bedeutet das einfach, dass man der Funktion aus irgendeinem Grund Adressen uebergeben muss?
    Wenn dann aber in der Funktion selbst v1 statt &v1 verwendet wird, was bedeutet das dann? Kapiert der Compiler dann selbst, dass mit v1 das Objekt gemeint ist, dass die Adresse &v1 beeinhaltet?
    Mache ich Sinn?



  • ?!? schrieb:

    Ich würde versuchen andere Parameternamen und außerdem eine Initialisierungsliste zu verwenden.

    Ob man andere Namen verwendet, oder nicht ist ziemlich egal (Ich bevorzuge sogar die Variante von ihm, mit gleichen Namen der Übergabeparameter). Wo du definitiv recht hast, ist mit der Initialisierungsliste.

    Nur zur Information. Du kannst auch in der Initialisierungsliste gleiche Bezeichner verwenden, da das linke Argument sich immer auf die Membervariable und das rechte auf den Scope (hier die Übergabeparameter) bezieht:

    Kamera::Kamera(Vec3f center, Vec3f projection)
    :   center(center),
        projection(projection)
    {
        projection.Normalize();
    }
    

    ?!? schrieb:

    Das läuft allerdings auf's gleiche hinaus.

    Nein, tut es nicht. Die Initialisierungsliste steuert den Konstruktoraufruf, die Zuweisung im Konstruktorrumpf ist und bleibt eine nachträgliche Zuweisung (mit vorherigen Aufruf des Standardkonstruktors).

    Aber nun zum eigentlichen Posting zurück:

    l'abra d'or schrieb:

    An sich dachte ich mir das ist ja easy,peasy das geht einfach so:

    Kamera::Kamera(Vec3f center, Vec3f projection){
        this->center = center;  (1)
        this->projection = projection.Normalize();  (2)
    }
    

    Sodele.
    (1) Scheint auch zu funktionieren. Wenn ich das richtig sehe, ist this ja ein Pointer auf mein derzeitiges Kamera Objekt. Mit dem "->" dereferenziere ich auf den member "center" und KOPIERE das Objekt center dahin.
    (2) Klappt nicht und ich raff nicht warum. Kann mir da bitte jemand helfen?

    Zu 1:
    a) this ist der Zeiger auf das Aktuelle Objekt der Klasse. Das mit der Dereferenzierung stimmt auch.
    b) Der Ausdruck Kopieren ist hier verkehrt. Du führst eine Zuweisung durch, da center an der Stelle bereits existiert. Über die Initialisierungsliste würdest du tatsächlich Kopieren, nicht Zuweisen (Zuweisungsoperator vs. Kopierkonstruktor).

    Zu 2:
    Dies ist sogar mehr als logisch. Normalize hat den Rückgabewert void. Und du versuchst ein void dem Objekt projection zuzuweisen.

    Hier gibt es nur zwei Wege:
    a) Du rufst Normalize nach der Zuweisung auf.
    b) Du sorgst dafür das Normalize ein Objekt oder eine Referenz zurückgibt.

    Möglich wäre z.B. das folgende:

    // Header
    class Vec3f
    {
        // ...
        Vec3f & Normalize();
    };
    
    // Sorce
    Vec3f & Vec3f::Normalize()
    {
        // ...
        return *this; // Gibt sich selbst zurück...
    }
    


  • HugoW schrieb:

    Vec3f(const Vec3f &V) {
        data[0] = V.data[0];
        data[1] = V.data[1];
        data[2] = V.data[2]; }
    

    data ist wohl ein float Array, was? Du brauchst diesen Konstruktor nicht selbst schreiben, weil der Kopierkonstruktor, der vom Compiler automatisch generiert werden kann, hier genau das richtige tut (elementweises Kopieren).

    HugoW schrieb:

    Kamera::Kamera(Vec3f center, Vec3f projection){
        this->center = center;  (1)
        this->projection = projection.Normalize();  (2)
    }
    
    Kamera::Kamera(Vec3f center, Vec3f projection)
    : center(center), projection(projection)
    {
        this->center = center;  (1)
        this->projection.Normalize();
    }
    

    wobei ich normalize als freie Funktion geschrieben hätte:

    inline Vec3f normalize(Vec3f v)
    {
      v *= 1.f / v.Length();
      return v;
    }
    

    Das gilt auch für andere Funktionen, wie zB cross3 oder length .

    Bzgl & als Teil einer Parameter/Variablen-Deklaration guck mal in Dein C++ Buch im Kapitel über Referenzen nach.



  • Zu deiner ersten Frage:
    Normalize() verändert das Objekt selbst und gibt nichts zurück. Daher funktioniert deine letzte Methode und nicht das was du anfangs geschrieben hast.

    Das & in der Parameterliste steht für Referenzen, was der Funktion erlaubt diese permanent zu verändern (so sie nicht const sind) und was verhindert das die Objekte kopiert werden müssen (unnötiger Overhead).



  • krümelkacker schrieb:

    Bzgl & als Teil einer Parameter/Variablen-Deklaration guck mal in Dein C++ Buch im Kapitel über Referenzen nach.

    Je nachdem, wo das & steht, kann es etwas anderes bedeuten:

    In Ausdrücken:
    - Adressoperator (unärer Präfix-Operator)
    - Bit-Und (binär, infix)

    Als Teil eines Deklarators:
    - Referenz (verhält sich wie ein unärer Präfix-Operator)



  • HugoW schrieb:

    static void Cross3(Vec3f &c, const Vec3f &v1, const Vec3f &v2);
      //...
    

    Warum steht da das "&" bei den Parametern?
    Bedeutet das einfach, dass man der Funktion aus irgendeinem Grund Adressen uebergeben muss?

    Nein. Es handelt sich hier um Referenzen, nicht um Zeiger.

    Schau dir mal folgendes Programm an:

    #include <iostream>
    
    void foo1(int a)
    {
        a = 2;
    }
    
    void foo2(int & a)
    {
        a = 3;
    }
    
    int main()
    {
        int i = 1;
    
        std::cout << i << std::endl;
        foo1(i);
        std::cout << i << std::endl;
        foo2(i);
        std::cout << i << std::endl;
    }
    

    Die Ausgabe ist:

    1
    1
    3
    

    Im ersten Fall ("void foo1(int a)") wird die Übergabe als Kopie gemacht. Sprich innerhalb von der Funktion arbeitet man auf einer Kopie, nicht dem Original.

    Im zweiten Fall ("void foo2(int & a)") übergibt man eine Referenz, einen sogenannten Aliasnamen. Im wesentlichen ist eine Referenz gleichbedeutend mit dem Originalwert. Eine Änderung an der Referenz wirkt sich direkt auf den Ursprungswert aus.

    Mit Zeigern hast du aber nicht ganz unrecht. Referenzen können (müssen aber nicht!) intern als Zeiger umgesetzt sein. Im Gegensatz zu Zeigern kann man aber eine Referenz nicht nachträglich "umbiegen", NULL ist unzulässig und zu guter Letzt bieten Referenzen eine Syntax wie du sie von "normalen" Objekten gewohnt bist.

    Und hierzu noch etwas... Der folgende Konstruktor ist aus Performancesicht tendenziell problematisch:

    Kamera::Kamera(Vec3f center, Vec3f projection)
    

    Du übergibst hier center und projection als Kopie. Bei Objekten ist es in der Regel besser statt einer Kopie eine konstante Referenz zu übergeben:

    Kamera::Kamera(Vec3f const & center, Vec3f const & projection)
    // oder (gleichbedeutend)
    Kamera::Kamera(const Vec3f & center, const Vec3f & projection)
    

    Hier wird keine Kopie angelegt, aber gleichzeitig garantiert das der Wert des Objektes nicht geändert werden kann.



  • krümelkacker schrieb:

    Kamera::Kamera(Vec3f center, Vec3f projection)
    : center(center), projection(projection)
    {
        this->center = center;  (1)
        this->projection.Normalize();
    }
    

    Upps, da habe ich die erste this-> Zeile übersehen. Die muss natürlich weg.



  • Hui hui,
    jetzt habe ich ja soviele Antworten bekommen, dass ich ganz durcheinander komme.
    Ich versuchs jetzt mal zu ordnen:

    @allgemein:
    Die vorgegebenen Funktionen cross und Normalize will ich nicht ändern.

    @asc:

    - Initalisierungsliste:
    Aha, das ist ja interessant. Habe ich vorher noch nie gesehen, werds dann wohl mal anwenden, wenns in der Regel vorzuziehen ist.
    -

    b) Der Ausdruck Kopieren ist hier verkehrt. Du führst eine Zuweisung durch, da center an der Stelle bereits existiert. Über die Initialisierungsliste würdest du tatsächlich Kopieren, nicht Zuweisen (Zuweisungsoperator vs. Kopierkonstruktor).

    Hmmm....ich dachte bei einer Zuweisung wird der "Inhalt" des Objekts rechts des "=" in den Speicherplatz des Objekts links kopiert? Nein? Wie darf ich mir eine Zuweisung dann speichertechnisch vorstellen?

    @ zu

    static void Cross3(Vec3f &c, const Vec3f &v1, const Vec3f &v2);
    

    Okay, man übergibt also eine Referenz, wenn ich das richtig verstanden habe, ist das jedoch eine Adresse. (Steht hier doch auch so, oder: http://www.cplusplus.com/doc/tutorial/pointers/

    The address that locates a variable within memory is what we call a reference to that variable

    )

    Ich habe den Unterschied zwischen Kopie und direktem ändern, dank ascs Beispiel verstanden, finde es aber irgendwie immernoch unlogisch. Wenn &foo eine Adresse ist, ist es komisch, dass man damit wie mit einem normalen Objekt arbeiten kann.
    Wie übergebe ich einer Funktion denn dann richtige Adressen? 😮



  • HugoW schrieb:

    Ich habe den Unterschied zwischen Kopie und direktem ändern, dank ascs Beispiel verstanden, finde es aber irgendwie immernoch unlogisch. Wenn &foo eine Adresse ist, ist es komisch, dass man damit wie mit einem normalen Objekt arbeiten kann.
    Wie übergebe ich einer Funktion denn dann richtige Adressen? 😮

    Indem du einen Zeiger empfängst.

    int foo (int& f ){} // 1
    int foo (int* pf ){} // 2
    
    int main ()
    {
      int n;
      foo ( n ); // ruft 1 auf
      foo ( &n ); // ruft 2 auf
    }
    

    Hier darfst du den & Operator nicht verwechseln, welcher (hier) 2 Bedeutungen hat. Wenn er bei einem Typen steht, dann macht er einen Referenztypen draus. Wenn er bei einer Variable steht, dann gibt dir der Operator die Adresse zum Objekt zurück. Ein Zeiger erwartet eine Adresse, also musst du den &-Operator benutzen, um an die Adresse zu kommen.
    Die Referenz wird mit dem normalen Objekt initialisiert und erwartet somit keinen Zeiger des Objektes.



  • HugoW schrieb:

    -

    b) Der Ausdruck Kopieren ist hier verkehrt. Du führst eine Zuweisung durch, da center an der Stelle bereits existiert. Über die Initialisierungsliste würdest du tatsächlich Kopieren, nicht Zuweisen (Zuweisungsoperator vs. Kopierkonstruktor).

    Hmmm....ich dachte bei einer Zuweisung wird der "Inhalt" des Objekts rechts des "=" in den Speicherplatz des Objekts links kopiert? Nein? Wie darf ich mir eine Zuweisung dann speichertechnisch vorstellen?

    Das problem ist einfach das es zwei Formen gibt (Kopie und Zuweisung) die man sprachlich irgendwie auseinander halten sollte. Eine Kopie wird durch Aufruf des Kopierkonstruktors erledigt, man erstellt dabei ein neues Objekt mit als Kopie des ersten. Bei einer Zuweisung existiert das Zielobjekt bereits, und jeder Wert wird einzeln zugewiesen - Du kannst es zwar kopieren nennen, aber dann kommt man hier ein wenig in Namenskonflikt.

    Und bestimmte Sachen kann man nicht zuweisen, sondern nur Kopieren (Referenzen, Konstanten...).

    HugoW schrieb:

    @ zu

    static void Cross3(Vec3f &c, const Vec3f &v1, const Vec3f &v2);
    

    Okay, man übergibt also eine Referenz, wenn ich das richtig verstanden habe, ist das jedoch eine Adresse.

    Vergiß hier einfach mal Zeiger. Referenzen mögen intern als Zeiger abgebildet werden (können unter Umständen aber vom Compiler gänzlich wegoptimiert werden), sind aber von der Behandlung etwas anderes. Unter C gab es nur die Zeiger, und dort hat sich Referenz in dem Zusammenhang im Sprachgebrauch etabliert. In C++ gibt es im wesentlichen drei Übergabeformen:

    int foo1(int a);   // [1] Als Kopie (C, C++)
    int foo2(int & a); // [2] Als Referenz (C++)
    int foo3(int * a); // [3] Als Zeiger (C, C++)
    

    1. Hier wird eine Kopie angelegt und man arbeitet innerhalb der Funktion mit der Kopie.
    1.1 Diese Übergabeform ist für integrale Datentypen (int, float...) die Übliche.

    2. Übergabe als Referenz. Wie sie intern abgebildet wird (ob nun wegoptimiert oder als Zeiger) vergiss hier einfach mal. Eine Referenz lässt sich wie die Originalvariable verwenden, sie IST (aus Programmierersicht) die Originalvariable.
    2.1 Diese Übergabeform sollte man verwenden, wenn man den Originalwert manipulieren will.
    2.2 Eine konstante Referenz sollte man der Übergabe als Kopie vorziehen, wenn man Objekte von Klassen/Strukturen übergeben will.
    2.3 Referenzen können keine temporären Variablen zugeordnet werden, Konstanten Referenzen schon (siehe Beispiel unten)

    3. Übergabe als Zeiger. Ein Zeiger ist eine Adresse im Speicher und man muss ihn dereferenzieren um an das eigentliche Objekt zu gelangen. Zudem kann ein Zeiger die Adresse NULL (0) annehmen, um anzudeuten das kein Objekt übergeben wurde. Dies muss man auch bei der Programmierung berücksichtigen.
    3.1 Zeiger würde ich immer dann wählen, wenn ein Objekt nur optional ist.

    Ein großes Problem das du hast, ist die Syntax. Bei einer Referenzübergabe gibst du niemals eine Adresse an, sondern rufst sie wie bei einer "normalen" Funktion auf. Nur Zeiger benötigen Adressen, und leider wird hier (dank C-Herkunft das & Symbol als Adressoperator verwendet - Dies hat aber nichts mit einer Referenz zu tun):

    int foo1(int a);   // [1] Als Kopie (C, C++)
    int foo2(int & a); // [2] Als Referenz (C++)
    int foo3(int * a); // [3] Als Zeiger (C, C++)
    
    int main()
    {
        int zahl = 0;
        foo1(zahl);         // \ Hier unterscheiden sich vom Aufruf weder die
        foo2(zahl);         // / Übergabe als Kopie, noch als Referenz.
        foo1(3);            // Die Übergabe eines temporären Wertes geht aber
        // Fehler: foo2(3)     nicht mit (nicht-konstanten) Referenzen
    
        // Zeiger sind was ganz anderes...
        foo3(&zahl);        // Hier dient "&" als Adressoperator
    }
    

Anmelden zum Antworten