Wie funktioniert die bereichsbasierte Funktion für einfache Arrays?

86

In C ++ 11 können Sie eine bereichsbasierte Version verwenden for, die wie die foreachanderer Sprachen fungiert. Es funktioniert sogar mit einfachen C-Arrays:

int numbers[] = { 1, 2, 3, 4, 5 };
for (int& n : numbers) {
    n *= 2;
}

Woher weiß es, wann es aufhören soll? Funktioniert es nur mit statischen Arrays, die im selben Bereich deklariert wurden, in dem sie forverwendet werden? Wie würden Sie dies formit dynamischen Arrays verwenden?

Paul Manta
quelle
10
Es gibt keine "dynamischen" Arrays in C oder C ++ an sich - es gibt Array-Typen und dann gibt es Zeiger, die auf ein Array oder einen dynamisch zugewiesenen Speicherblock verweisen können oder nicht, der sich meistens wie ein Array verhält. Für jedes Array vom Typ T [n] wird seine Größe im Typ codiert und kann von aufgerufen werden for. In dem Moment, in dem das Array in einen Zeiger zerfällt, gehen die Größeninformationen verloren.
JohannesD
1
In Ihrem Beispiel die Anzahl der Elemente in numbersist sizeof(numbers)/sizeof(int), zum Beispiel.
JohannesD

Antworten:

56

Es funktioniert für jeden Ausdruck, dessen Typ ein Array ist. Beispielsweise:

int (*arraypointer)[4] = new int[1][4]{{1, 2, 3, 4}};
for(int &n : *arraypointer)
  n *= 2;
delete [] arraypointer;

Für eine detailliertere Erklärung: Wenn der Typ des rechts übergebenen Ausdrucks :ein Array-Typ ist, iteriert die Schleife von ptrbis ptr + size( ptrzeigt auf das erste Element des Arrays, sizewobei es sich um die Elementanzahl des Arrays handelt).

Dies steht im Gegensatz zu benutzerdefinierten Typen, die nachschlagen beginund endals Mitglieder arbeiten, wenn Sie ein Klassenobjekt übergeben oder (wenn es keine so genannten Mitglieder gibt) Nichtmitgliedsfunktionen. Diese Funktionen ergeben die Start- und Enditeratoren (die direkt nach dem letzten Element bzw. dem Beginn der Sequenz zeigen).

Diese Frage klärt, warum dieser Unterschied besteht.

Johannes Schaub - litb
quelle
8
Ich denke, die Frage war, wie es funktioniert, nicht wann es funktioniert
siehe
1
@sehe die Frage enthielt mehrere '?' es. Einer war "Funktioniert es mit ...?". Ich habe erklärt, wie und wann es funktioniert.
Johannes Schaub - litb
8
@JohannesSchaub: Ich denke, das "Wie" -Problem hier ist, wie genau Sie die Größe eines Objekts eines Array-Typs überhaupt erhalten (aufgrund der Verwirrung zwischen Zeigern und Arrays weiß nicht fast jeder, dass die Größe eines Arrays ist verfügbar für den Programmierer.)
JohannesD
Ich glaube, es sucht nur nach Nicht-Mitgliedern begin`end . It just happens that std :: begin, `std::endbenutze die Mitgliedsfunktionen und werde verwendet, wenn keine bessere Übereinstimmung verfügbar ist.
Dennis Zickefoose
3
@ Tennis nein in Madrid wurde beschlossen, dies zu ändern und Start- und Endmitglieder zu bevorzugen. Nicht-Begünstigung von Anfangs- und Endmitgliedern verursachte Unklarheiten, die schwer zu vermeiden sind.
Johannes Schaub - litb
40

Ich denke, dass der wichtigste Teil dieser Frage ist, wie C ++ weiß, wie groß ein Array ist (zumindest wollte ich es wissen, als ich diese Frage fand).

C ++ kennt die Größe eines Arrays, da es Teil der Array-Definition ist - es ist der Typ der Variablen. Ein Compiler muss den Typ kennen.

Da C ++ 11 std::extentverwendet werden kann, um die Größe eines Arrays zu erhalten:

int size1{ std::extent< char[5] >::value };
std::cout << "Array size: " << size1 << std::endl;

Dies ist natürlich nicht sehr sinnvoll, da Sie die Größe in der ersten Zeile explizit angeben müssen, die Sie dann in der zweiten Zeile erhalten. Sie können aber auch verwenden decltypeund dann wird es interessanter:

char v[] { 'A', 'B', 'C', 'D' };
int size2{ std::extent< decltype(v) >::value };
std::cout << "Array size: " << size2 << std::endl;
psur
quelle
5
Darum habe ich ursprünglich gebeten. :)
Paul Manta
19

Gemäß dem neuesten C ++ - Arbeitsentwurf (n3376) entspricht der Bereich für Anweisungen dem folgenden:

{
    auto && __range = range-init;
    for (auto __begin = begin-expr,
              __end = end-expr;
            __begin != __end;
            ++__begin) {
        for-range-declaration = *__begin;
        statement
    }
}

Es weiß also, wie man eine reguläre forSchleife mit Iteratoren auf die gleiche Weise stoppt .

Ich denke, Sie suchen möglicherweise nach etwas wie dem Folgenden, um die obige Syntax für Arrays zu verwenden, die nur aus einem Zeiger und einer Größe bestehen (dynamische Arrays):

template <typename T>
class Range
{
public:
    Range(T* collection, size_t size) :
        mCollection(collection), mSize(size)
    {
    }

    T* begin() { return &mCollection[0]; }
    T* end () { return &mCollection[mSize]; }

private:
    T* mCollection;
    size_t mSize;
};

Diese Klassenvorlage kann dann verwendet werden, um einen Bereich zu erstellen, über den Sie mithilfe des neuen Bereichs für die Syntax iterieren können . Ich verwende dies, um alle Animationsobjekte in einer Szene zu durchlaufen, die mit einer Bibliothek importiert werden, die nur einen Zeiger auf ein Array und eine Größe als separate Werte zurückgibt.

for ( auto pAnimation : Range<aiAnimation*>(pScene->mAnimations, pScene->mNumAnimations) )
{
    // Do something with each pAnimation instance here
}

Diese Syntax ist meiner Meinung nach viel klarer als das, was Sie verwenden würden, std::for_eachoder eine einfache forSchleife.

Gewähren
quelle
3

Es weiß, wann es aufhören muss, weil es die Grenzen statischer Arrays kennt.

Ich bin mir nicht sicher, was Sie unter "dynamischen Arrays" verstehen. Wenn der Compiler nicht informell über statische Arrays iteriert, sucht er auf jeden Fall nach den Namen beginund endim Bereich der Klasse des Objekts, über das Sie iterieren, oder schaut bereit für begin(range)undend(range) mit argumentabhängiger Suche und verwendet sie als Iteratoren.

Weitere Informationen finden Sie im C ++ 11-Standard (oder im öffentlichen Entwurf davon) unter "6.5.4 Die bereichsbasierte forAnweisung", S. 145

Ausruhen
quelle
4
Ein "dynamisches Array" würde mit erstellt werden new[]. In diesem Fall haben Sie nur einen Zeiger ohne Angabe der Größe, sodass der bereichsbasierte Benutzer nicht fordamit arbeiten kann.
Mike Seymour
Meine Antwort enthält ein dynamisches Array, dessen Größe (4) zur Kompilierungszeit bekannt ist, aber ich weiß nicht, ob diese Interpretation von "dynamisches Array" das ist, was der Fragesteller beabsichtigt hat.
Johannes Schaub - litb
3

Wie funktioniert die bereichsbasierte Funktion für einfache Arrays?

Soll das lauten: " Sag mir, was ein Fernkampf macht (mit Arrays)? "

Ich antworte unter der Annahme, dass - Nehmen Sie das folgende Beispiel mit verschachtelten Arrays:

int ia[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};

for (auto &pl : ia)

Textversion:

iaist ein Array von Arrays ("verschachteltes Array"), die [3]Arrays enthalten , wobei jedes [4]Werte enthält. Das obige Beispiel durchläuft iaseinen primären 'Bereich' ( [3]) und durchläuft daher die [3]Zeiten. Jede Schleife erzeugt eine von ia‚s [3]Primärwerte aus dem ersten und endend mit dem letzten - Ein Array mit [4]Werten.

  • Erste Schleife: plgleich{1,2,3,4} - Ein Array
  • Zweite Schleife: plgleich{5,6,7,8} - Ein Array
  • Dritte Schleife: plgleich {9,10,11,12}- Ein Array

Bevor wir den Prozess erklären, sind hier einige freundliche Erinnerungen an Arrays:

  • Arrays werden als Zeiger auf ihren ersten Wert interpretiert. Wenn Sie ein Array ohne Iteration verwenden, wird die Adresse des ersten Werts zurückgegeben
  • pl muss eine Referenz sein, da wir keine Arrays kopieren können
  • Wenn Sie bei Arrays dem Array-Objekt selbst eine Nummer hinzufügen, wird diese um ein Vielfaches vorwärts weitergeleitet und auf den entsprechenden Eintrag "gezeigt". Wenn nes sich um die betreffende Nummer handelt, ia[n]ist dies dieselbe wie *(ia+n)(Wir dereferenzieren die Adresse, die nEinträge enthält forward) und ia+nist dasselbe wie &ia[n](Wir erhalten die Adresse dieses Eintrags im Array).

Folgendes ist los:

  • Wird in jeder Schleife plals Referenz auf gesetzt ia[n], wobei ndie aktuelle Schleifenzahl ab 0 gleich plist. Ist also ia[0]in der ersten Runde, in der zweiten ist esia[1] und so weiter. Der Wert wird per Iteration abgerufen.
  • Die Schleife dauert so lange an, wie sie ia+nkleiner als ist end(ia).

... und das war's auch schon.

Es ist wirklich nur eine vereinfachte Art, dies zu schreiben :

int ia[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
for (int n = 0; n != 3; ++n)
  auto &pl = ia[n];

Wenn Ihr Array ist nicht verschachtelt, dann wird dieser Prozess etwas einfacher, dass eine Referenz nicht erforderlich, da der iterierten Wert kein Array ist , sondern ein ‚normaler‘ Wert:

 int ib[3] = {1,2,3};

 // short
 for (auto pl : ib)
   cout << pl;

 // long
 for (int n = 0; n != 3; ++n)
   cout << ib[n];

Einige zusätzliche Informationen

Was wäre, wenn wir das autoSchlüsselwort beim Erstellen nicht verwenden wollten?pl ? Wie würde das aussehen?

Im folgenden Beispiel plbezieht sich auf eine array of four integers. In jeder Schleife plwird der Wert angegeben ia[n]:

int ia[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
for (int (&pl)[4] : ia)

Und ... so funktioniert es, mit zusätzlichen Informationen, um Verwirrung zu beseitigen. Es ist nur eine Kurzschleife, fordie automatisch für Sie zählt, aber keine Möglichkeit bietet, die aktuelle Schleife abzurufen, ohne sie manuell auszuführen.

Super Cat
quelle
@Andy 9 von 10 Mal stimmt der Titel mit Google überein / was auch immer sucht - Der Titel fragt, wie diese funktionieren? , nicht wann weiß es wann es aufhören soll? . Trotzdem impliziert die zugrunde liegende Frage ist in dieser Antwort zu einem gewissen Grad bedeckt, und geht weiter zu Antwort für jemand anderes für die Suche andere Antwort. Bei solchen Syntaxfragen sollten Titel formuliert sein, damit eine Antwort allein damit geschrieben werden kann, da dies alle Informationen sind, die der Suchende benötigt, um die Frage zu finden. Sie liegen sicher nicht falsch - Die Frage trägt nicht den Titel, wie sie sein sollte.
Super Cat
0

Einige Beispielcodes, um den Unterschied zwischen Arrays auf Stack und Arrays auf Heap zu demonstrieren


/**
 * Question: Can we use range based for built-in arrays
 * Answer: Maybe
 * 1) Yes, when array is on the Stack
 * 2) No, when array is the Heap
 * 3) Yes, When the array is on the Stack,
 *    but the array elements are on the HEAP
 */
void testStackHeapArrays() {
  int Size = 5;
  Square StackSquares[Size];  // 5 Square's on Stack
  int StackInts[Size];        // 5 int's on Stack
  // auto is Square, passed as constant reference
  for (const auto &Sq : StackSquares)
    cout << "StackSquare has length " << Sq.getLength() << endl;
  // auto is int, passed as constant reference
  // the int values are whatever is in memory!!!
  for (const auto &I : StackInts)
    cout << "StackInts value is " << I << endl;

  // Better version would be: auto HeapSquares = new Square[Size];
  Square *HeapSquares = new Square[Size];   // 5 Square's on Heap
  int *HeapInts = new int[Size];            // 5 int's on Heap

  // does not compile,
  // *HeapSquares is a pointer to the start of a memory location,
  // compiler cannot know how many Square's it has
  // for (auto &Sq : HeapSquares)
  //    cout << "HeapSquare has length " << Sq.getLength() << endl;

  // does not compile, same reason as above
  // for (const auto &I : HeapInts)
  //  cout << "HeapInts value is " << I << endl;

  // Create 3 Square objects on the Heap
  // Create an array of size-3 on the Stack with Square pointers
  // size of array is known to compiler
  Square *HeapSquares2[]{new Square(23), new Square(57), new Square(99)};
  // auto is Square*, passed as constant reference
  for (const auto &Sq : HeapSquares2)
    cout << "HeapSquare2 has length " << Sq->getLength() << endl;

  // Create 3 int objects on the Heap
  // Create an array of size-3 on the Stack with int pointers
  // size of array is known to compiler
  int *HeapInts2[]{new int(23), new int(57), new int(99)};
  // auto is int*, passed as constant reference
  for (const auto &I : HeapInts2)
    cout << "HeapInts2 has value " << *I << endl;

  delete[] HeapSquares;
  delete[] HeapInts;
  for (const auto &Sq : HeapSquares2) delete Sq;
  for (const auto &I : HeapInts2) delete I;
  // cannot delete HeapSquares2 or HeapInts2 since those arrays are on Stack
}
Yip Cubed
quelle