Какой лучший способ сделать обратный цикл в C / C # / C ++? - PullRequest
90 голосов
/ 09 ноября 2008

Мне нужно переместиться назад через массив, поэтому у меня есть такой код:

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

Есть ли лучший способ сделать это?

Обновление: я надеялся, что, возможно, в C # был какой-то встроенный механизм для этого, например:

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

Обновление 2: есть лучшие способы. Руна получает приз с:

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

, который выглядит еще лучше в обычном C (благодаря Twotymz):

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

Ответы [ 15 ]

130 голосов
/ 09 ноября 2008

Хотя, по общему признанию, он немного неясен, я бы сказал, что наиболее типично приятный способ сделать это -

for (int i = myArray.Length; i --> 0; )
{
    //do something
}
110 голосов
/ 09 ноября 2008

В C ++ вы можете выбирать между итерациями с использованием итераторов или индексов. В зависимости от того, есть ли у вас простой массив или std::vector, вы используете разные методы.

Использование std :: vector

Использование итераторов

C ++ позволяет вам сделать это, используя std::reverse_iterator:

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

Использование индексов

Целочисленный тип без знака, возвращаемый std::vector<T>::size, равен , а не всегда std::size_t. Это может быть больше или меньше. Это очень важно для работы цикла.

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

Это работает, так как значения беззнаковых целочисленных типов определяются по модулю их количества битов. Таким образом, если вы устанавливаете -N, вы получите (2 ^ BIT_SIZE) -N

Использование массивов

Использование итераторов

Мы используем std::reverse_iterator для выполнения итерации.

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

Использование индексов

Мы можем безопасно использовать std::size_t здесь, в отличие от выше, поскольку sizeof всегда возвращает std::size_t по определению.

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

Предотвращение ловушек с применением sizeof к указателям

На самом деле вышеприведенный способ определения размера массива - отстой. Если a на самом деле является указателем, а не массивом (что случается довольно часто, и новички могут его перепутать), он молча потерпит неудачу. Лучшим способом является использование следующего, который потерпит неудачу во время компиляции, если дан указатель:

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

Он работает, сначала получая размер переданного массива, а затем объявляя, что возвращает ссылку на массив типа char того же размера. char определено, чтобы иметь sizeof из: 1. Таким образом, возвращаемый массив будет иметь sizeof из: N * 1, что мы и ищем, только с оценкой времени компиляции и нулевыми накладными расходами времени выполнения. *

Вместо того, чтобы делать

(sizeof a / sizeof *a)

Измените свой код так, чтобы он теперь делал

(sizeof array_size(a))
51 голосов
/ 09 ноября 2008

В C # , используя Visual Studio 2005 или более позднюю версию, введите «forr» и нажмите [TAB] [TAB] . Это расширится до цикла for, который проходит назад по коллекции.

Так легко ошибиться (по крайней мере, для меня), что я подумал, что вставить этот фрагмент будет хорошей идеей.

Тем не менее, мне нравится Array.Reverse() / Enumerable.Reverse(), а затем итерация вперед лучше - они более четко обозначают намерение.

32 голосов
/ 03 января 2013

Я бы всегда предпочел бы чистый код вместо типографски приятного 'кода. Таким образом, я бы всегда использовал:

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

Вы можете считать это стандартным способом обратной петли.
Просто мои два цента ...

17 голосов
/ 09 ноября 2008

In C # с использованием Linq :

foreach(var item in myArray.Reverse())
{
    // do something
}
10 голосов
/ 09 ноября 2008

Это определенно лучший способ для любого массива, длина которого является целочисленным типом со знаком. Для массивов, длина которых является целым типом без знака (например, std::vector в C ++), вам необходимо слегка изменить конечное условие:

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

Если вы только что сказали i >= 0, это всегда верно для целого числа без знака, поэтому цикл будет бесконечным циклом.

4 голосов
/ 09 ноября 2008

В C мне нравится это делать:


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

C # пример, добавленный MusiGenesis:

{int i = myArray.Length; while (i-- > 0)
{
    myArray[i] = 42;
}}
4 голосов
/ 09 ноября 2008

выглядит хорошо для меня. Если индексатор не подписан (uint и т. Д.), Возможно, вам придется принять это во внимание. Назовите меня ленивым, но в этом (без знака) случае я мог бы просто использовать счетную переменную:

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

(на самом деле, даже здесь вам нужно быть осторожным с такими случаями, как arr.Length = uint.MaxValue ... возможно!

3 голосов
/ 10 ноября 2008

Лучший способ сделать это в C ++ - это, вероятно, использовать адаптеры итераторов (или, точнее, диапазона), которые будут лениво преобразовывать последовательность при ее прохождении.

В основном,

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

Отображает диапазон «диапазон» (здесь он пуст, но я уверен, что вы можете сами добавлять элементы) в обратном порядке. Конечно, простая итерация диапазона не очень полезна, но передача этого нового диапазона алгоритмам и тому подобному довольно крута.

Этот механизм также можно использовать для более мощных целей:

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

Будет лениво вычислять диапазон «диапазон», где функция «f» применяется ко всем элементам, элементы, для которых «p» не является истинным, удаляются, и, наконец, результирующий диапазон инвертируется.

Синтаксис канала является наиболее читаемым IMO, учитывая его инфикс. Обновление библиотеки Boost.Range, ожидающее рассмотрения, реализует это, но сделать это самостоятельно довольно просто. Еще более здорово с лямбда-DSEL генерировать функцию f и предикат p в строке.

1 голос
/ 25 октября 2016

Я предпочитаю цикл while. Для меня это более понятно, чем уменьшение i в состоянии цикла for

int i = arrayLength;
while(i)
{
    i--;
    //do something with array[i]
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...