web-dev-qa-db-fra.com

Comment fonctionne la plage basée sur pour les tableaux simples?

Dans C++ 11, vous pouvez utiliser un for basé sur une plage, qui agit comme le foreach des autres langages. Il 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 des tableaux statiques qui ont été déclarés dans la même portée que for est utilisée? Comment utiliseriez-vous ce for avec des tableaux dynamiques?

74
Paul Manta

Il 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 est passé à droite de : est un type de tableau, puis la boucle itère de ptr à ptr + size (ptr pointant 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 begin et end comme membres si vous passez un objet classe ou (s'il n'y a pas de membres appelés de cette façon) des fonctions non membres . Ces fonctions produiront 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.

50

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.

Depuis C++ 11 std::extent peut ê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 decltype et 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;
32
psur

Selon le dernier projet de travail C++ (n3376), l'instruction à distance est équivalente à la suivante:

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

Il sait donc comment arrêter de la même manière qu'une boucle for régulière 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 ensuite être utilisé pour créer une plage, sur laquelle vous pouvez itérer en utilisant la nouvelle syntaxe ranged for. 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 distinctes.

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_each ou une boucle simple for.

16
Grant

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", en tout cas, si ce n'est pas en itérant sur des tableaux statiques, informellement, le compilateur recherche les noms begin et end dans la portée de la classe de l'objet sur lequel vous effectuez une itération, ou recherche begin(range) et end(range) à l'aide de la recherche dépendante de l'argument et les utilise comme itérateurs.

Pour plus d'informations, dans la norme C++ 11 (ou une version publique de celle-ci), "6.5.4 L'instruction for basée sur la plage", p. 145

3
chill

Comment fonctionne la plage basée sur pour les tableaux simples?

Est-ce à lire comme suit: " Dites-moi ce que fait un à distance (avec des tableaux)? "

Je répondrai en supposant que - Prenez 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:

ia est un tableau de tableaux ("tableau imbriqué"), contenant [3] tableaux, chacun contenant [4] valeurs. L'exemple ci-dessus parcourt ia par sa "plage" principale ([3]), et boucle donc [3] fois. Chaque boucle produit l'une des valeurs primaires de ia[3] à partir de la première et se terminant par la dernière - Un tableau contenant [4] valeurs.

  • Première boucle: pl est égal à {1,2,3,4} - Un tableau
  • Deuxième boucle: pl est égal à {5,6,7,8} - Un tableau
  • Troisième boucle: pl est égal à {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 les tableaux
  • Avec les tableaux, lorsque vous ajoutez un nombre à l'objet tableau lui-même, il avance à plusieurs reprises et "pointe" vers l'entrée équivalente - Si n est le nombre en question, alors ia[n] est identique à *(ia+n) (nous déréférençons l'adresse qui est n entrées vers l'avant), et ia+n est identique à &ia[n] (nous obtenons le l'adresse de cette entrée dans le tableau).

Voici ce qui se passe:

  • Sur chaque boucle, pl est défini comme référence à ia[n], avec n égal le nombre de boucles en cours commençant à 0. Ainsi, pl est ia[0] au premier tour, au second c'est ia[1], et ainsi de suite. Il récupère la valeur via l'itération.
  • La boucle continue tant que ia+n est inférieur à end(ia).

... Et c'est tout.

C'est vraiment juste un manière 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 sens qu'une référence 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 supplémentaires

Et si nous ne voulions pas utiliser le mot clé auto lors de la création de pl? À quoi cela ressemblerait-il?

Dans l'exemple suivant, pl fait référence à un array of four integers. Sur chaque boucle, pl reçoit 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 ... Voilà comment cela fonctionne, avec des informations supplémentaires pour dissiper toute confusion. C'est juste une boucle "abrégée" for qui compte automatiquement pour vous, mais il manque un moyen de récupérer la boucle actuelle sans le faire manuellement.

2
Super Cat