mehrdimensionale Arrays als Parameter
-
Hallo,
Ich habe ein kleines Verständnisproblem zur Übergabe von mehrdimensionalen Arrays als Parameter.#include <iostream> using namespace std; int test(int arr[4][3]){ cout << sizeof(arr) << endl; cout << sizeof(arr[0]) << endl; return 0; } int main(){ int arr[4][3] = {{1,2,3},{4,5,6},{7,8,9},{10,11,12}}; test(arr); return 0; }Ausgabe:
8
12Also, ich übergebe der Funktion <test(int arr[4][3])> ein zweidimensionales array, wobei ich explizit angebe wie viele "zeilen" und "spalten" das array haben soll. Ich weiß, dass eigentlich nur die 2. (und alle weiteren) Dimensionen angegeben werden müssen. Allerdings scheint es so als würde der Compiler die Angabe der ersten Dimension ignorieren, da "sizeof(arr)" 8-byte (also die größe eines zeigers bei 64 bit rechnern) und nicht die größe des arrays liefert. Er verhält sich also genauso, als hätte ich "int arr[][3]" als Parameter angegeben. Für sizeof(arr[0]) wird jedoch wie erwartet die grüße einer gesamten zeile (also 3 * 4 byte) ausgegeben. Hab ich da was falsch verstanden? oder wenn nicht, wieso ignoriert der Compiler die angabe?
lg Lars
-
Von C geerbter Sonderfall. Funktionsparameter können keinen Arraytyp haben; in einer Funktionsdeklaration deklariert die Array-Syntax einen Zeiger. Welche der drei Varianten
void foo(int arr[4][3]); void foo(int arr[][3]); void foo(int (*arr)[3]);du schreibst, ist völlig gleichgültig; arr hat immer den selben Typ.
Allerdings kannst du
int test(int (&arr)[4][3])schreiben, in welchem Fall arr eine Referenz auf ein int[4][3] ist. Damit dürftest du das von dir gewollte Verhalten erreichen, dass die äußere Arraydimension respektiert wird.
-
Wie (fast) immer bei der Verwendung von arrays:
warum nicht std::vector? Damit ist dann auch die Übergabe kein Problem.
-
Wahrscheinlich weil er keinen Bock hat für so etwas einfaches 50 Zeilen Code zu schreiben, Indizes umzurechnen, damit zu leben, dass der Speicher nicht am Stück ist, oder halt externe Libraries einzubinden? Eigentlich ist es doch das Hauptproblem von C++, dass einfache Sachen nicht einfach gehen. Ich habe einen Algorithmus im Kopf und muss ersteine halbe Stunde Vorarbeit leisten um ihn umzusetzen.
Ich würde aber dennoch raten es sauber zu machen, auch wenn es zu verlockend ist da was hinzupfuschen. Bau Dir einen Datentyp, der die Datenstruktur intern auf einen std::vector mappt.
-
Kruzifix schrieb:
Wahrscheinlich weil er keinen Bock hat für so etwas einfaches 50 Zeilen Code zu schreiben[...]
Geht eigentlich ziemlich einfach:
#include <array> #include <iostream> void f(std::array<std::array<int,3>,4> const & arr) { for(auto outer : arr) { for(auto inner : outer) { std::cout << inner << '\n'; } } } int main() { std::array<std::array<int,3>,4> arr {{{1,2,3},{4,5,6},{7,8,9},{10,11,12}}}; f(arr); }
-
Kruzifix schrieb:
Eigentlich ist es doch das Hauptproblem von C++, dass einfache Sachen nicht einfach gehen.
Ein Großteil der Fragen im C-forum drehen sich darum, dass dies eben in C nicht einfach ist. Diese Frage zählt auch, denn sie handelt eben von dem C-Sonderfall.
Indizes umzurechnen
Und wie übergibt man in C allgemeine 2D-Arrays, ohne Indizes umzurechnen? (Wohlgemerkt: In C muss man selber die Rechnung schreiben, in C++ macht das der Wrapper)
dass der Speicher nicht am Stück ist
Da muss man eben die Standardbibliothek kennen. Was uns zur eigentlichen Lösung bringt (@Threadersteller: Dies ist auch interessant für dich):
int test(int arr[4][3])Das was du hier haben möchtest, ist in C++-Sprech:
int test(std::array<std::array<int, 3>, 4> arr)Das ist dann eine echte Kopie (std::array hat eine Kopiersemantik, d.h. es verhält sich wie die meisten Datentypen die du kennst, aber hat eben nicht die Sonderfälle der C-Arrays). Natürlich kann man auch eine Referenz benutzen. Innerhalb der Funktion musst du auch auf nichts mehr achten, alle Größen sind bekannt, Grenzen (auch die äußere!) werden respektiert (nur optional, aber versuch das mal in C), alle Daten liegen am Stück.
Das ist aber so noch nicht sehr schick, da es sehr unflexibel ist und wirklich nur mit
std::array<std::array<int, 3>, 4>funktioniert. Das ist fein, wenn man wirklich immer nur diesen Typ im Programm hat (dann bietet sich ein typedef an
). Aber vielleicht soll die äußere Dimension des Arrays dynamisch sein. In C ist dies ja schon immer so, weil Arrays nur als Pointer übergeben werden. Die naive Lösung wäre daher:int test(std::vector<std::array<int, 3>> arr)Hierfür gilt alles, was auch zum vorherigen Fall gesagt wurde. Jedoch ist dies auch nicht so schön. Warum vector? Ist doch doof, sich so festzulegen. Übergeben wir doch einfach einen Iterator (d.h. ein verallgemeinerter Zeiger, das heißt es können auch tatsächlich Zeiger sein) auf das erste und das letzte Element:
template<typename Iterator> int test(Iterator begin, Iterator end)Nun kann unser äußeres Array sein was es will. Ein std::array, ein std::vector, ein C-Array, ein std::multimap, ein selbstgeschriebener Container. Wir brauchen nur ein iteratorartiges Gebilde auf den Anfang und das Ende, was man bei allen diesen Fällen hat (zumindest wenn der selbstgeschriebene Container was taugt).
Doch hoppla!
Wo ist denn nun das innere Array hin? Das ist nun auch vollständig in der Abstraktion verschwunden. Das heißt, das kann auch alles hin, was man sich vorstellen kann. Lecker
.Das klingt furchtbar kompliziert? Mag beim ersten Mal betrachten so sein, jedoch: Das ist die Standardlösung wie man in C++ alle Arten von Mengen handhabt. Nicht nur mehrdimensionale Felder. Die Standardbibliothek benutzt das (fast) überall, viele externe Bibliotheken machen das so und du solltest es dir auch angewöhnen. Du brachst dann nämlich nur noch den letzten Fall, alles davor kannst du vergessen. Dadurch gewinnst du enorm an Flexibilität und verlierst nichts, weil alles zur Compilezeit aufgelöst wird und dann jeweils optimiert behandelt werden kann. In C ginge diese Flexibilität gar nicht mehr, außer durch Makroorgien oder mit Laufzeitverlust (void*).
Wie nutzt man das? Ein Beispiel:
#include <iterator> // Man könnte den Rückgabetyp auch noch flexibel machen (einfach) // Wenn man wirklich Zeit hat, kann man sogar die Dimension // flexibel machen (dazu braucht man dann aber schon solide // Kenntnisse in Templatemetaprogrammierung) template <typename Iterator> int sum_2D(Iterator begin, Iterator end) { int sum = 0; // Ich benutze mal nicht std::accumulate und auch kein // range-based for, damit du explizit siehst, was passiert: for(; begin != end; ++begin) { for (auto inner_it = std::begin(*begin), inner_end =std::end(*begin); inner_it != inner_end; ++ inner_it) { sum += *inner_it; } } return sum; } // Anwendungsbeispiel: #include <iostream> #include <array> #include <string> #include <vector> using namespace std; int main() { // Das was du wolltest: array<array<int, 3>, 4> arrayarray = {{{{1,2,3}},{{4,5,6}},{{7,8,9}},{{10, 11, 12}}}}; // Die vielen Klammern sind beim GCC nötig, da der hier nicht 100% standardkonform ist. cout << "array<array>-Summe: " << sum_2D(arrayarray.begin(), arrayarray.end()) << '\n'; // Mal was verrückteres: vector<string> vectorstring = {"Hallo", "Welt", "!"}; cout << "vector<string>-Summe: " << sum_2D(vectorstring.begin(), vectorstring.end()) << '\n'; }
-
SeppJ, ich habe mit keinem Wort gesagt, dass es in C einfacher geht. Du triffst Annahmen darüber, was ich gesagt haben könnte, und versuchst es dann zu widerlegen. Ist Dir langweilig? Schau Dir z.B. mal die Möglichkeiten in numpy an. Da weint der C++ Nutzer, wenn er nicht zufällig (das vergleichsweise immer noch viel zu umständliche ublas) zur Verfügung hat.
Davon abgesehen ist die Array-Lösung nicht C++ 98, müsste also nachgebaut werden, wenn man abwärtskompaibel sein. Also quasi immer, wenn man nicht bloß akademische Spielerei betreibt oder ohnehin schon aus anderen Gründen auf bestimmte Compilerversionen fixiert ist. Welche schöne Lösung für dynamische mehrdimensionale Arrays gibt es? De waren hier zwar nicht gefragt, sondern in dem anderen Thread, aber mich würde mal interessieren, was es mittlerweile für diese Standardanwendung so gibt. Mir wäre nichts bekannt, was nicht auf Indexumrechnereien im std::vector hinaus läuft, oder halt nicht Standard ist.
-
Kruzifix schrieb:
Davon abgesehen ist die Array-Lösung nicht C++ 98, müsste also nachgebaut werden, wenn man abwärtskompaibel sein.
Zeig mir einen Compiler im Einsatz, der nicht einmal TR1 hat. TR1 ist bald 10 Jahre alt. Das ist nur wenig jünger als C++!
Welche schöne Lösung für dynamische mehrdimensionale Arrays gibt es? De waren hier zwar nicht gefragt, sondern in dem anderen Thread, aber mich würde mal interessieren, was es mittlerweile für diese Standardanwendung so gibt. Mir wäre nichts bekannt, was nicht auf Indexumrechnereien im std::vector hinaus läuft, oder halt nicht Standard ist.
Natürlich läüft das immer auf Indexrumrechnerei hinaus, denn genau das ist es. Auch in anderen Sprachen. Auch statische mehrdimensionale Arrays sind Indexrumrechnerei, bloß besser versteckt. Meine Lösung am Ende kommt mit allen diesen Fällen klar, braucht dabei nicht einmal zu wissen, was Indizes überhaupt sind.
-
SeppJ schrieb:
Zeig mir einen Compiler im Einsatz, der nicht einmal TR1 hat. TR1 ist bald 10 Jahre alt.
Irrelevant wann ein TR bzw. Standard verabschiedet wurde. Der Zeitpunkt ab dem es jemand implementiert hat zählt. C++ 98 wird IIRC immer noch nicht zu 100% vom msvc und dem gcc implementiert und C++ 11 wird auch noch eine Weile brauchen. Gerade vom msvc geistern in den verschiedensten Entwicklungsbüros noch ganz schön abenteuerliche Versionen umher.
SeppJ schrieb:
Natürlich läüft das immer auf Indexrumrechnerei hinaus, denn genau das ist es. Auch in anderen Sprachen. Auch statische mehrdimensionale Arrays sind Indexrumrechnerei, bloß besser versteckt.
Sag bloß
.SeppJ schrieb:
Meine Lösung am Ende kommt mit allen diesen Fällen klar, braucht dabei nicht einmal zu wissen, was Indizes überhaupt sind.
Du hälst das tatsächlich für einen Vorteil, dass C++ derartige Basis-Datenstrukturen nicht mitbringt, oder?
-
Kruzifix schrieb:
SeppJ schrieb:
Natürlich läüft das immer auf Indexrumrechnerei hinaus, denn genau das ist es. Auch in anderen Sprachen. Auch statische mehrdimensionale Arrays sind Indexrumrechnerei, bloß besser versteckt.
Sag bloß

Was war dann deine Frage? Ob's eine Art std::matrix<3> m(3, 7, 5) gibt? Ne, wäre aber durchaus mal angebracht.
-
Wenn du Python als Beispiel für eine Sprache, die essentielle Dinge von Haus aus mitbringst, verkaufen willst, solltest du noch einmal nachdenken. Numpy ist ja auch ein Zusatzpaket, und Python selbst macht nicht mal bindende Aussagen über die Laufzeitkomplexität seiner Container. Auch findet man keine Baumstrukturen im Basissprachumfang, was ziemlich erbärmlich ist -- Hashtables gehen schlicht nicht für alles. Aber dass es mehrere Wege geben muss, etwas zu tun, will man da ja nicht hören.
Im Übrigen ist es sehr spaßig, sich als Pythonist darüber zu beschweren, ein Feature sei zu neu, es zu benutzen.
</rant>
Jetzt, wo der Djihad durch ist: Dynamische, mehrdimensionale Arrays gehen in C++ seit anno dazumal wie folgt:
std::vector<std::vector<foo> > vec;...das ist natürlich kein Array von Arrays, sondern effektiv ein Zeigerarray, aber etwas anderes sind unsere Dynamiker ja auch nicht gewohnt. Daher dann auch keine Indexumrechnerei mehr.
Wenn es um quadratische/kubische/sonstwie rechtwinklige Arrays geht, gibt es Boost.Multi-Array. Die sind cachefreundlicher, dafür lässt sich nicht jedes Sub-Array einzeln neu dimensionieren.
@cooky: Eine Matrix ist immer zweidimensional. Die Benennung wäre daher nicht so richtig sinnvoll.
-
std::array bzw. tr1::array sind nicht gerade Template-export, was die Compilerunterstützung angeht
.