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?
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.
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;
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
.
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
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.
pl
est égal à {1,2,3,4}
- Un tableaupl
est égal à {5,6,7,8}
- Un tableaupl
est égal à {9,10,11,12}
- Un tableauAvant d'expliquer le processus, voici quelques rappels amicaux sur les tableaux:
pl
doit être une référence car nous ne pouvons pas copier les tableauxn
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:
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.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.