array übergeben



  • Wutz schrieb:

    http://www.c-plusplus.net/forum/287413

    Danke für den Hinweis. Das Maß war eh voll jetzt. Wurde ja auch schon alles gesagt.



  • Soweit ich das verstanden habe, bist du, blurry333, mittlerweile berühmt dafür, relativ inhaltsleere Posts abzugeben, nicht wahr? 😃 Aber gut, ich will mal nicht so sein ...

    Ein kurzes Beispiel zu eindimensionalen Arrays:

    #include <iostream>
    
    //Beides ist möglich, werden aber immer als Zeiger angesehen:
    int foo(int*p1,int p2[],int plen1,int plen2)
    {
        //Elementweise ausgeben.
        for(int i=0;i<plen1;i++)
            std::cout<<p1[ i]<<" ";
        std::cout<<"\n";
    
        //Und auch hier. Zugriff ist derselbe, für den Compiler handelt es sich bei 
        //p1 und p2 nur um Zeiger, Arrays werden nicht kopiert, höchstens die
        //Zugriffsadresse - wie beim Zeiger.
        for(int i=0;i<plen2;i++)
            std::cout<<p2[ i]<<" ";
        std::cout<<"\n";
    }
    
    int main()
    {
        //Ein Array auf dem Heap ...
        int*a=new int[10];
        for(int i=0;i<10;i++)
            a[ i]=i;
    
        //... und auf dem Stack.
        int b[10]={0,1,2,3,4,5,6,7,8,9};
    
        foo(a,b,10,10);
    
        //Ach ja, Zeiger löschen nicht vergessen.
        delete[] a;
    }
    

    Bei Strings ist das ganze ein bisschen komplizierter, diese gibt es nativ nur mit Arrays von char :

    int main(int ArgC,char**ArgV)
    {
        for(int i=0;i<ArgC;i++)
        {
            //KOPIERT NICHT, sondern setzt den Wert und damit den Zugriff von
            //CurrentString auf das aktuelle Element von ArgV.
            char*CurrentString=ArgV[ i];
    
            //Gibt den String direkt aus
            std::cout<<"Gesamt: "<<CurrentString<<"\n";
    
            //Gibt den String Buchstaben für Buchstaben aus
            std::cout<<"Einzeln: ";
            for(int j=0;CurrentString[j];j++)
                std::cout<<CurrentString[j];
            std::cout<<"\n";
        }
    }
    

    Der Arrayname ist eigentlich nur eine Adresse, die übergeben wird. Wenn du den Adressoperator davorsetzt, wird der Compiler warnen, dass du die Adresse des Arrays explizit übergeben willst, aber es wird funktionieren. Bei Zeigern darfst du das allerdings nicht machen, es sei denn, du arbeitest mit Zeigern auf Zeigern und dereferenzierst diese, denn damit würdest du nicht die Adresse im Zeiger, sondern die Adresse des Zeigers übergeben, und die liegt oft auf dem Stack - es sei denn mal wieder, du arbeitest mit Zeigern auf Zeigern und Zeigerarrays, die auf dem Heap liegen.

    Lange Rede, gar kein Sinn: oben steht, wie's gemacht wird.

    @all: Lasst ihn doch, meine Güte. Er hat Fragen, also stellt er diese. Ist ja nicht so, als ob man euch dazu zwingen würde, zu posten, also tut es auch nicht.



  • 😉 ok danke für Eure Beiträge.



  • so leider noch eine Frage.

    Wenn man an eine funktion ein mehrdimensionales array übergibt muss die größe
    der 2.ten - nten dimension feststehen.

    z.B.

    funktion(arr[][4],int arraysize)
    

    Muss ich jetzt für jedes array einer anderen Größe eine neue Funktion schreiben ?


  • Mod

    blurry333 schrieb:

    Muss ich jetzt für jedes array einer anderen Größe eine neue Funktion schreiben ?

    Wow, eine ausformulierte Frage. 👍 . Da antworte ich sogar mal:

    Nein. Übergib dir die Größen und einen Pointer auf das erste Element und rechne in der Funktion die Indizes selber um (z.B. 2D: Index [x][y] entspricht (Anfang + x * y-Größe + y) . Oder lass mehrdimensionale Arrays gleich bleiben, es gibt in C++ keinen Grund, sich so zu quälen.



  • In C wäre es üblich, die Indizierung zur Laufzeit von Hand zu machen, d.h.

    void foo(int *array, size_t dim_y, size_t dim_x) {
      size_t i, j;
    
      array[i * dim_y + j]; // entspricht array[i][j].
    }
    

    Aber wir sind hier ja zum Glück im C++-Forum, wo man Operatoren überladen und TMP betreiben kann:

    #include <cstddef>
    #include <iostream>
    
    template<typename T>
    class array2d {
    public:
      template<std::size_t M, std::size_t N>
      inline array2d(T (&data)[M][N])
      : data_(static_cast<T*>(static_cast<void*>(data))), dim_x_(N), dim_y_(M) { }
    
      inline T       *operator[](std::size_t y)       { return data_ + dim_x_ * y; }
      inline T const *operator[](std::size_t y) const { return data_ + dim_x_ * y; }
    
      inline std::size_t dim_x() const { return dim_x_; }
      inline std::size_t dim_y() const { return dim_y_; }
    
    private:
      T *data_;
      std::size_t dim_x_;
      std::size_t dim_y_;
    };
    
    void funktion(array2d<int> const &arr) {
      for(std::size_t i = 0; i < arr.dim_y(); ++i) {
        for(std::size_t j = 0; j < arr.dim_x(); ++j) {
          std::cout << arr[i][j] << ' ';
        }
        std::cout << '\n';
      }
    }
    
    int main() {
      int a[][3] = { { 1, 2, 3 },
                     { 4, 5, 6 } };
    
      funktion(a);
    }
    

    Das macht im Prinzip das selbe, nur halt schick verpackt.



  • Memberfunktionen sind implizit inline 🤡



  • seldon schrieb:

    In C wäre es üblich, die Indizierung zur Laufzeit von Hand zu machen, d.h.

    void foo(int *array, size_t dim_y, size_t dim_x) {
      size_t i, j;
    
      array[i * dim_y + j]; // entspricht array[i][j].
    }
    
    // Entspricht undefiniertem Verhalten.
    


  • 314159265358979 schrieb:

    Memberfunktionen sind implizit inline 🤡

    Ihm ging es eh nur darum den Code aufzublaehen und bissi zum zu posen.



  • Ich frage mich ja, ob nicht das hier UB ist:

    static_cast<T*>(static_cast<void*>(data))
    


  • das dim_y in

    array[i * dim_y + j];
    

    muss natürlich dim_x heißen, mein Fehler.

    Was

    static_cast<T*>(static_cast<void*>(data))
    

    angeht, das ist sehr genau definiert. Der Umweg über void* ist aufgrund des strengen Typsystems in C++ notwendig. In der Praxis dürfte

    reinterpret_cast<T*>(data)
    

    wohl das selbe machen, aber die Definition von reinterpret_cast ist deutlich weniger streng.



  • seldon schrieb:

    das dim_y in

    array[i * dim_y + j];
    

    muss natürlich dim_x heißen, mein Fehler.

    Asoooo, dann:

    // seldons Code
    void foo(int *array, size_t dim_y, size_t dim_x) {
      size_t i, j; // enspricht undefiniertem Verhalten.
    
      array[i * dim_x + j]; // entspricht array[i][j].
    }
    


  • Dann füg noch /* i, j initialisieren */ ein. Meine Güte, ich wollte damit ein Prinzip verdeutlichen, nicht einsatzbereiten Code zur Verfügung stellen.



  • ubler schrieb:

    seldon schrieb:

    das dim_y in

    array[i * dim_y + j];
    

    muss natürlich dim_x heißen, mein Fehler.

    Asoooo, dann:

    // seldons Code
    void foo(int *array, size_t dim_y, size_t dim_x) {
      size_t i, j; // enspricht undefiniertem Verhalten.
    
      array[i * dim_x + j]; // entspricht array[i][j].
    }
    

    Bist du so blöd oder stellst du dich nur so?



  • seldon schrieb:

    : data_(static_cast<T*>(static_cast<void*>(data))), dim_x_(N), dim_y_(M) { }
    

    Das geht auch einfacher und auch so, dass T = const Irgendwas unterstützt wird:

    : data_(data[0]), ...
    

    seldon schrieb:

    inline T       *operator[](std::size_t y)       { return data_ + dim_x_ * y; }
      inline T const *operator[](std::size_t y) const { return data_ + dim_x_ * y; }
    

    Die Klasse hat Referenzsemantik. Da ist diese const-Überladung unangebracht. Der Zugriff auf das Array verändert die "Referenz" ja nicht. Das inline ist auch überflüssig. Von daher würde ich schreiben:

    T *operator[](std::size_t y) const { return data_ + dim_x_ * y; }
    

    Den Konstruktor kann man auch noch etwas flexibler machen:

    template<class U, std::size_t M, std::size_t N>
      array2d(U (&data)[M][N], typename boost::enable_if_c<
        boost::is_same<T,U>::value || boost::is_same<T,const U>::value
      >::type* =0)
      : data_(data[0]), dim_x_(N), dim_y_(M) {}
    

    Dann klappt das auch mit dem const richtig:

    void funktion(array2d<[b]const[/b] int> arrayref) {
      for(std::size_t i = 0; i < arrayref.dim_y(); ++i) {
        for(std::size_t j = 0; j < arrayref.dim_x(); ++j) {
          std::cout << arrayref[i][j] << ' ';
        }
        std::cout << '\n';
      }
    }
    

    Jetzt fehlt eigentlich nur noch eine implizite Konvertierung array2d<Dings> nach array2d<const Dings> in der Klasse. Man darf natürlich keine Derived->Base Konvertierung zulassen. Deswegen habe ich oben auch nicht convertible<U*,T*>::value verwendet.

    mein Senf,
    kk



  • Ethon schrieb:

    #include <iostream>
    #include <array>
    
    template<std::size_t Size1, std::size_t Size2>
    void funk(std::array<std::array<int, Size1>, Size2>& p)
    {
      cout<<"Funktioniert";
    }
    
    int main()
    {
      std::array<std::array<int, 4>, 3> arr;
      funk(arr);                                       
    }
    

    Fixed

    Was spricht gegen diese Lösung von mir?
    Reines Interesse, da eure Lösungen immer komplizierter werden.


  • Mod

    Irgendwie gefallen die mir alle nicht, da es Compilezeitlösungen sind. Wenn ich die Ausdehnung schon zur Compilezeit kenne, dann kann ich mir auch eine passende Funktion schreiben.



  • @SeppJ
    Der "array2d" Variante von seldon kann man ja leicht nen nicht-Template Konstruktor verpassen, der die Dimensionen "runtime" entgegennimmt.

    Ich würde das Ding allerdings nicht "array2d" nennen, sondern vielleicht "array_view_2d" oder so. Und der Klasse ein "stride" Member verpassen, so dass man aus einer "array_view_2d" ein "Stück ausschneiden" kann, und wieder eine "array_view_2d" erhält, ohne irgendwas kopieren zu müssen.



  • krümelkacker spricht eine Reihe guter Punkte an. Die implizite Konvertierung array2d<T> -> array2d<T const> ist über einen entsprechenden Konstruktor einfach zu haben:

    array2d(array2d<typename boost::remove_const<T>::type> const &other)
        : data_ (other[0]),
          dim_x_(other.dim_x()),
          dim_y_(other.dim_y()) { }
    

    Unschön ist, dass volatile und const volatile sich dann nicht auf die gleiche Weise entfernen lassen (man würde den gleichen Konstruktor mehrfach definieren, wenn T nicht const volatile ist). Das lässt sich bestimmt auch irgendwie über type traits lösen, aber damit befasse ich mich morgen.


  • Mod

    seldon schrieb:

    Unschön ist, dass volatile und const volatile sich dann nicht auf die gleiche Weise entfernen lassen

    Ich kann keinen Sinn in so einer Konvertierung erkennen.


Anmelden zum Antworten