Comment fonctionne le range-based for pour les tableaux simples?

87

En C ++ 11, vous pouvez utiliser un basé sur une plage for, qui agit comme le foreachd'autres langages. Cela fonctionne même avec des tableaux C simples:

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

Comment sait-il quand s'arrêter? Cela fonctionne-t-il uniquement avec les tableaux statiques qui ont été déclarés dans la même étendue que celle forutilisée? Comment utiliseriez-vous cela foravec des tableaux dynamiques?

Paul Manta
la source
10
Il n'y a pas de tableaux «dynamiques» en C ou C ++ en soi - il y a des types de tableaux et puis il y a des pointeurs qui peuvent ou non pointer vers un tableau ou un bloc de mémoire alloué dynamiquement qui se comporte principalement comme un tableau. Pour tout tableau de type T [n], sa taille est codée dans le type et est accessible par for. Mais au moment où ce tableau se désintègre en un pointeur, les informations de taille sont perdues.
JohannesD
1
Dans votre exemple, le nombre d'éléments dans numbersest sizeof(numbers)/sizeof(int), par exemple.
JohannesD

Réponses:

57

Cela fonctionne pour toute expression dont le type est un tableau. Par exemple:

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

Pour une explication plus détaillée, si le type de l'expression passée à droite de :est un type tableau, la boucle itère de ptrà ptr + size( ptrpointant vers le premier élément du tableau, sizeétant le nombre d'éléments du tableau).

Cela contraste avec les types définis par l'utilisateur, qui fonctionnent en recherchant beginet en endtant que membres si vous passez un objet de classe ou (s'il n'y a pas de membres appelés de cette façon) des fonctions non membres. Ces fonctions donneront les itérateurs de début et de fin (pointant directement après le dernier élément et le début de la séquence respectivement).

Cette question clarifie pourquoi cette différence existe.

Johannes Schaub - litb
la source
8
Je pense que la question était comment ça marche, pas quand ça marche
sehe
1
@sehe la question contenait plusieurs '?' es. L'un était "Est-ce que ça marche avec ...?". J'ai expliqué comment et quand cela fonctionne.
Johannes Schaub - litb
8
@JohannesSchaub: Je pense que le problème du "comment" ici est de savoir comment obtenir exactement la taille d'un objet d'un type de tableau en premier lieu (à cause de la confusion entre les pointeurs et les tableaux, presque tout le monde ne sait pas que la taille d'un tableau est disponible pour le programmeur.)
JohannesD
Je crois qu'il ne recherche que les non-membres begin`end . It just happens that std :: begin `std::enduse les fonctions membres, et sera utilisé si une meilleure correspondance n'est pas disponible.
Dennis Zickefoose
3
@Dennis no à Madrid, il a été décidé de changer cela et de favoriser les membres débutants et finaux. Ne pas favoriser les membres de début et de fin a provoqué des ambiguïtés difficiles à éviter.
Johannes Schaub - litb
44

Je pense que la partie la plus importante de cette question est de savoir comment C ++ sait quelle est la taille d'un tableau (au moins je voulais le savoir quand j'ai trouvé cette question).

C ++ connaît la taille d'un tableau, car il fait partie de la définition du tableau - c'est le type de la variable. Un compilateur doit connaître le type.

Puisque C ++ 11 std::extentpeut être utilisé pour obtenir la taille d'un tableau:

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

Bien sûr, cela n'a pas beaucoup de sens, car vous devez fournir explicitement la taille dans la première ligne, que vous obtenez ensuite dans la deuxième ligne. Mais vous pouvez également utiliser decltypeet cela devient plus intéressant:

char v[] { 'A', 'B', 'C', 'D' };
int size2{ std::extent< decltype(v) >::value };
std::cout << "Array size: " << size2 << std::endl;
psur
la source
6
C'est en effet ce que je demandais au départ. :)
Paul Manta
19

Selon le dernier projet de travail C ++ (n3376), l'instruction range for est équivalente à ce qui suit:

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

Donc, il sait comment arrêter de la même manière un régulier for boucle utilisant des itérateurs.

Je pense que vous cherchez peut-être quelque chose comme ce qui suit pour fournir un moyen d'utiliser la syntaxe ci-dessus avec des tableaux qui se composent uniquement d'un pointeur et d'une taille (tableaux dynamiques):

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;
};

Ce modèle de classe peut alors être utilisé pour créer une gamme, sur laquelle vous pouvez itérer en utilisant la nouvelle distance pour la syntaxe. J'utilise ceci pour parcourir tous les objets d'animation dans une scène qui est importée à l'aide d'une bibliothèque qui ne renvoie qu'un pointeur vers un tableau et une taille en tant que valeurs séparées.

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

Cette syntaxe est, à mon avis, beaucoup plus claire que ce que vous obtiendriez en utilisant std::for_eachune forboucle simple .

Subvention
la source
3

Il sait quand s'arrêter car il connaît les limites des tableaux statiques.

Je ne sais pas ce que vous entendez par "tableaux dynamiques", dans tous les cas, si ce n'est pas itérer sur des tableaux statiques, de manière informelle, le compilateur recherche les noms beginet enddans la portée de la classe de l'objet sur lequel vous itérez, ou regarde pour begin(range)etend(range) les utiliser recherche et les utilisations en fonction de l' argument comme itérateurs.

Pour plus d'informations, dans la norme C ++ 11 (ou son projet public), "6.5.4 L' forinstruction basée sur la plage ", p. 145

refroidissement
la source
4
Un "tableau dynamique" serait celui créé avec new[]. Dans ce cas, vous n'avez qu'un pointeur sans indication de la taille, il n'y a donc aucun moyen pour le basé forsur la plage de fonctionner avec.
Mike Seymour
Ma réponse inclut un tableau dynamique dont la taille (4) est connue au moment de la compilation, mais je ne sais pas si cette interprétation du "tableau dynamique" est ce que le questionneur voulait.
Johannes Schaub - litb
3

Comment fonctionne le range-based for pour les tableaux simples?

Est-ce que cela se lit comme suit: " Dites-moi ce que fait une distance pour (avec des tableaux)? "

Je répondrai en supposant que - Prenons l'exemple suivant en utilisant des tableaux imbriqués:

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

for (auto &pl : ia)

Version texte:

iaest un tableau de tableaux ("tableau imbriqué"), contenant des [3]tableaux, chacun contenant des [4]valeurs. L'exemple ci-dessus effectue une boucle iapar sa «plage» principale ( [3]), et donc boucle des [3]temps. Chaque boucle produit l' un des iades » [3]valeurs primaires à partir de la première et se terminant par le dernier - un tableau contenant des [4]valeurs.

  • Première boucle: plégale{1,2,3,4} - Un tableau
  • Deuxième boucle: plégale{5,6,7,8} - Un tableau
  • Troisième boucle: plequals {9,10,11,12}- Un tableau

Avant d'expliquer le processus, voici quelques rappels amicaux sur les tableaux:

  • Les tableaux sont interprétés comme des pointeurs vers leur première valeur - L'utilisation d'un tableau sans aucune itération renvoie l'adresse de la première valeur
  • pl doit être une référence car nous ne pouvons pas copier de tableaux
  • Avec les tableaux, lorsque vous ajoutez un nombre à l'objet de tableau lui-même, il avance autant de fois et `` pointe '' vers l'entrée équivalente - Si nest le nombre en question, alors ia[n]est le même que *(ia+n)(Nous déréférencer l'adresse qui est nentrée forward), et ia+nest identique à &ia[n](Nous obtenons l'adresse de cette entrée dans le tableau).

Voici ce qui se passe:

  • Sur chaque boucle, plest défini comme une référence à ia[n], avec négalité du nombre de boucles en cours à partir de 0. Donc, plc'est ia[0]au premier tour, au second c'estia[1] , et ainsi de suite. Il récupère la valeur via une itération.
  • La boucle continue tant que ia+nest inférieur à end(ia).

... Et c'est à peu près tout.

C'est vraiment juste une façon simplifiée d'écrire ceci :

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];

Si votre tableau n'est pas imbriqué, alors ce processus devient un peu plus simple en ce qu'une référence n'est pas nécessaire, car la valeur itérée n'est pas un tableau mais plutôt une valeur `` normale '':

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

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

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

Quelques informations complémentaires

Et si nous ne voulions pas utiliser le automot - clé lors de la créationpl ? À quoi cela ressemblerait-il?

Dans l'exemple suivant, plfait référence à un fichier array of four integers. Sur chaque boucle plest donnée la valeur ia[n]:

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

Et ... c'est comme ça que ça marche, avec des informations supplémentaires pour balayer toute confusion. C'est juste une forboucle «abrégée» qui compte automatiquement pour vous, mais qui manque d'un moyen de récupérer la boucle actuelle sans le faire manuellement.

Super chat
la source
@Andy 9 fois sur 10, le titre correspond à ce qui correspond dans Google / quelles que soient les recherches - Le titre demande comment cela fonctionne-t-il? , pas quand sait-il s'arrêter? . Même ainsi, la question sous-jacente implicite est couverte dans cette réponse dans une certaine mesure, et continue à répondre pour quiconque cherche l' autre réponse. Les questions de syntaxe telles que celles-ci doivent avoir des titres formulés de manière à ce qu'une réponse puisse être écrite en utilisant cela seul, car c'est toute l'information dont le chercheur a besoin pour trouver la question. Vous n'avez certainement pas tort - La question n'est pas intitulée comme elle devrait l'être.
Super Cat
0

Quelques exemples de code pour démontrer la différence entre les tableaux sur Stack et les tableaux sur Heap


/**
 * 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
la source