web-dev-qa-db-fra.com

Quelle est la meilleure façon de faire une boucle arrière en C / C # / C ++?

J'ai besoin de revenir en arrière dans un tableau, j'ai donc un code comme celui-ci:

for (int i = myArray.Length - 1; i >= 0; i--)
{
    // Do something
    myArray[i] = 42;
}

Existe-t-il une meilleure façon de le faire?

Mise à jour: J'espérais que C # avait peut-être un mécanisme intégré pour cela, comme:

foreachbackwards (int i in myArray)
{
    // so easy
}

Mise à jour 2: Voici sont meilleurs moyens. Rune prend le prix avec:

for (int i = myArray.Length; i-- > 0; )
{    
    //do something
}
//or
for (int i = myArray.Length; i --> 0; )
{
    // do something
}

qui est encore plus beau en C normal (grâce à Twotymz):

for (int i = lengthOfArray; i--; )
{    
    //do something
}
91
MusiGenesis

Certes, un peu obscur, je dirais que la manière la plus agréable de le faire est de

for (int i = myArray.Length; i --> 0; )
{
    //do something
}
132
Rune

En C++, vous avez le choix entre itérer à l'aide d'itérateurs ou d'index. Selon que vous avez un tableau simple ou un std::vector, tu utilises différentes techniques.

Utiliser std :: vector

Utiliser les itérateurs

C++ vous permet de faire cela en utilisant std::reverse_iterator:

for(std::vector<T>::reverse_iterator it = v.rbegin(); it != v.rend(); ++it) {
    /* std::cout << *it; ... */
}

Utilisation d'indices

Le type intégral non signé retourné par std::vector<T>::size est pas toujours std::size_t. Cela peut être plus ou moins. Ceci est crucial pour que la boucle fonctionne.

for(std::vector<int>::size_type i = someVector.size() - 1; 
    i != (std::vector<int>::size_type) -1; i--) {
    /* std::cout << someVector[i]; ... */
}

Cela fonctionne, car les valeurs des types intégraux non signés sont définies au moyen de modulo leur nombre de bits. Ainsi, si vous définissez -N, vous vous retrouvez à (2 ^ BIT_SIZE) -N

Utiliser des tableaux

Utiliser les itérateurs

Nous utilisons std::reverse_iterator faire l'itération.

for(std::reverse_iterator<element_type*> it(a + sizeof a / sizeof *a), itb(a); 
    it != itb; 
    ++it) {
    /* std::cout << *it; .... */
}

Utilisation d'indices

Nous pouvons utiliser en toute sécurité std::size_t ici, contrairement à ce qui précède, puisque sizeof renvoie toujours std::size_t par définition.

for(std::size_t i = (sizeof a / sizeof *a) - 1; i != (std::size_t) -1; i--) {
   /* std::cout << a[i]; ... */
}

Eviter les pièges avec sizeof appliqué aux pointeurs

En fait, la méthode ci-dessus pour déterminer la taille d'un tableau est nulle. Si a est en fait un pointeur au lieu d'un tableau (ce qui arrive assez souvent et que les débutants le brouilleront), il échouera silencieusement. Un meilleur moyen consiste à utiliser ce qui suit, ce qui échouera au moment de la compilation si un pointeur lui est attribué:

template<typename T, std::size_t N> char (& array_size(T(&)[N]) )[N];

Cela fonctionne en obtenant d'abord la taille du tableau passé, puis en déclarant de renvoyer une référence à un tableau de type char de la même taille. char est défini pour avoir sizeof sur: 1. Le tableau retourné aura donc un sizeof sur: N * 1, ce que nous recherchons, avec seulement compiler évaluation du temps et zéro temps d’exécution.

Au lieu de faire

(sizeof a / sizeof *a)

Changez votre code pour qu'il le fasse maintenant

(sizeof array_size(a))
111

En C #, utilisation de Visual Studio 2005 ou version ultérieure, tapez 'forr' et appuyez sur [TAB] [TAB]. Cela se développera en une boucle for qui recule dans une collection.

Il est si facile de se tromper (du moins pour moi), que je pensais que l'insertion de cet extrait serait une bonne idée.

Cela dit, j'aime bien Array.Reverse()/Enumerable.Reverse() et puis itérer , puis mieux - ils indiquent plus clairement l'intention.

52
Jay Bazuzi

Je voudrais toujours préférer le code clair contre le code ' plaisant typographiquement '. Ainsi, j'utiliserais toujours:

for (int i = myArray.Length - 1; i >= 0; i--)  
{  
    // Do something ...  
}    

Vous pouvez le considérer comme le moyen standard de boucler en arrière.
Juste mes deux cents...

36
Jack Griffin

Dans C # en utilisant Linq:

foreach(var item in myArray.Reverse())
{
    // do something
}
17
Keltex

C'est certainement le meilleur moyen pour tout tableau dont la longueur est un type intégral signé. Pour les tableaux dont les longueurs sont de type entier non signé (par exemple, un std::vector en C++), vous devez alors modifier légèrement la condition de fin:

for(size_t i = myArray.size() - 1; i != (size_t)-1; i--)
    // blah

Si vous venez de dire i >= 0, ceci est toujours vrai pour un entier non signé, la boucle sera donc une boucle infinie.

10
Adam Rosenfield

Cela me semble correct. Si l'indexeur n'était pas signé (uint, etc.), vous devrez peut-être en tenir compte. Appelez-moi paresseux, mais dans ce cas (non signé), je pourrais simplement utiliser une variable de compteur:

uint pos = arr.Length;
for(uint i = 0; i < arr.Length ; i++)
{
    arr[--pos] = 42;
}

(En fait, même ici, vous devez faire attention aux cas comme arr.Length = uint.MaxValue ... peut-être un! = quelque part ... bien sûr, c'est un cas très improbable!)

4
Marc Gravell

En C j'aime faire ça:


int i = myArray.Length;
while (i--) {
  myArray[i] = 42;
}

Exemple C # ajouté par MusiGenesis:

{int i = myArray.Length; while (i-- > 0)
{
    myArray[i] = 42;
}}
4
Twotymz

La meilleure façon de faire cela en C++ est probablement d'utiliser des adaptateurs d'itérateur (ou mieux, de plage), qui transformeront paresseux la séquence au fur et à mesure de son défilement.

Fondamentalement,

vector<value_type> range;
foreach(value_type v, range | reversed)
    cout << v;

Affiche la plage "plage" (ici, c'est vide, mais je suis assez sûr que vous pouvez ajouter des éléments vous-même) dans l'ordre inverse. Bien sûr, il n’est pas utile d’itérer simplement la plage, mais il est plutôt cool de transmettre cette nouvelle plage à des algorithmes et à d’autres choses.

Ce mécanisme peut également être utilisé pour des utilisations beaucoup plus puissantes:

range | transformed(f) | filtered(p) | reversed

Calculera paresseusement la plage "plage", où la fonction "f" est appliquée à tous les éléments, les éléments pour lesquels "p" n'est pas vrai sont supprimés et la plage résultante est finalement inversée.

La syntaxe des tuyaux est l’OMI la plus lisible, étant donné qu’elle est infixée. La mise à jour en attente de la bibliothèque Boost.Range l'implémente, mais il est assez simple de le faire vous-même. C'est encore plus cool avec un DSEL lambda de générer la fonction f et le prédicat p en ligne.

3
loufoque

Je préfère une boucle while. C'est plus clair pour moi que de décrémenter i dans la condition d'une boucle for

int i = arrayLength;
while(i)
{
    i--;
    //do something with array[i]
}
1
Petko Petkov
// this is how I always do it
for (i = n; --i >= 0;){
   ...
}
1
Mike Dunlavey

J'utiliserais le code dans la question initiale, mais si vous vouliez vraiment utiliser foreach et avoir un index entier en C #:

foreach (int i in Enumerable.Range(0, myArray.Length).Reverse())
{
    myArray[i] = 42; 
}
0
xyz

Je ne suis pas sûr de comprendre pourquoi l’une ou l’autre des solutions de rechange est meilleure, si la bonté inclut la clarté ou la maintenabilité.

0
dkretz