Поиск…


Вступление

Оператор цикла выполняет группу операторов несколько раз, пока не будет выполнено условие. В C ++ существует три типа примитивных циклов: for, while и do ... while.

Синтаксис

  • в то время как (условие) оператор;
  • do statement while ( выражение );
  • for ( for-init-statement ; condition ; expression ) statement ;
  • for ( for-range-declaration : for-range-initializer ) оператор ;
  • перерыв ;
  • Продолжить ;

замечания

algorithm вызова обычно предпочтительнее для ручных петель.

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

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

Диапазон для

C ++ 11

for циклов можно использовать для итерации по элементам диапазона, основанного на итераторе, без использования числового индекса или прямого доступа к итераторам:

vector<float> v = {0.4f, 12.5f, 16.234f};

for(auto val: v)
{
    std::cout << val << " ";
}

std::cout << std::endl;

Это будет выполнять итерацию по каждому элементу из v , причем val получает значение текущего элемента. Следующее утверждение:

for (for-range-declaration : for-range-initializer ) statement

эквивалентно:

{
    auto&& __range = for-range-initializer;
    auto __begin = begin-expr, __end = end-expr;
    for (; __begin != __end; ++__begin) {
        for-range-declaration = *__begin;
        statement
    }
}
C ++ 17
{
    auto&& __range = for-range-initializer;
    auto __begin = begin-expr;
    auto __end = end-expr; // end is allowed to be a different type than begin in C++17
    for (; __begin != __end; ++__begin) {
        for-range-declaration = *__begin;
        statement
    }
}

Это изменение было введено для запланированной поддержки Ranges TS в C ++ 20.

В этом случае наш цикл эквивалентен:

{
    auto&& __range = v;
    auto __begin = v.begin(), __end = v.end();
    for (; __begin != __end; ++__begin) {
        auto val = *__begin;
        std::cout << val << " ";
    }
}

Обратите внимание, что auto val объявляет тип значения, который будет копией значения, хранящегося в диапазоне (мы инициализируем его инициализацию с помощью итератора по мере продвижения). Если значения, хранящиеся в диапазоне, дороги для копирования, вы можете использовать const auto &val . Вы также не обязаны использовать auto ; вы можете использовать соответствующее имя типа, если оно неявно конвертируется из типа значения диапазона.

Если вам нужен доступ к итератору, диапазон на основе не может помочь вам (не без каких-либо усилий, по крайней мере).

Если вы хотите ссылаться на него, вы можете сделать это:

vector<float> v = {0.4f, 12.5f, 16.234f};

for(float &val: v)
{
    std::cout << val << " ";
}

Вы можете итерации по ссылке const если у вас есть контейнер const :

const vector<float> v = {0.4f, 12.5f, 16.234f};

for(const float &val: v)
{
    std::cout << val << " ";
}

Можно использовать пересылку ссылок, когда итератор последовательности возвращает объект-прокси, и вам нужно работать с этим объектом const способом. Примечание: это, скорее всего, путает читателей вашего кода.

vector<bool> v(10);

for(auto&& val: v)
{
    val = true;
}

Типа «Диапазон» предоставляется на основе диапазона for может быть одним из следующих:

  • Языковые массивы:

    float arr[] = {0.4f, 12.5f, 16.234f};
    
    for(auto val: arr)
    {
        std::cout << val << " ";
    }
    

    Обратите внимание, что выделение динамического массива не учитывается:

    float *arr = new float[3]{0.4f, 12.5f, 16.234f};
    
    for(auto val: arr) //Compile error.
    {
        std::cout << val << " ";
    }
    
  • Любой тип, который имеет функции-члены begin() и end() , возвращает итераторы к элементам типа. Стандартные библиотечные контейнеры подходят, но могут использоваться и пользовательские типы:

    struct Rng
    {
        float arr[3];
    
        // pointers are iterators
        const float* begin() const {return &arr[0];}
        const float* end() const   {return &arr[3];}
        float* begin() {return &arr[0];}
        float* end()   {return &arr[3];}
    };
    
    int main()
    {
        Rng rng = {{0.4f, 12.5f, 16.234f}};
    
        for(auto val: rng)
        {
            std::cout << val << " ";
        }
    }
    
  • Любой тип, который имеет функции non-member begin(type) и end(type) которые могут быть найдены через зависимый от аргументов поиск по type . Это полезно для создания типа диапазона без необходимости изменять тип класса:

    namespace Mine
    {
        struct Rng {float arr[3];};
    
        // pointers are iterators
        const float* begin(const Rng &rng) {return &rng.arr[0];}
        const float* end(const Rng &rng) {return &rng.arr[3];}
        float* begin(Rng &rng) {return &rng.arr[0];}
        float* end(Rng &rng) {return &rng.arr[3];}
    }
    
    int main()
    {
        Mine::Rng rng = {{0.4f, 12.5f, 16.234f}};
    
        for(auto val: rng)
        {
            std::cout << val << " ";
        }
    }
    

Для цикла

Цикл for выполняет инструкции в loop body , а condition цикла - true. Прежде, чем initialization statement цикла выполняется ровно один раз. После каждого цикла выполняется исполняющая часть iteration execution .

Цикл for определяется следующим образом:

for (/*initialization statement*/; /*condition*/; /*iteration execution*/)
{
    // body of the loop
}

Объяснение заявлений заполнителя:

  • initialization statement : этот оператор запускается только один раз, в начале цикла for . Вы можете ввести объявление нескольких переменных одного типа, например int i = 0, a = 2, b = 3 . Эти переменные действительны только в области цикла. Переменные, определенные перед циклом с тем же именем, скрываются во время выполнения цикла.
  • condition : Этот оператор оценивается перед выполнением каждого цикла цикла и прерывает цикл, если он оценивает значение false .
  • iteration execution : этот оператор запускается после тела цикла, перед оценкой следующего условия , если цикл for не прерван в теле (по break , goto , return или исключаемому исключению). Вы можете ввести несколько операторов в части iteration execution , например, a++, b+=10, c=b+a .

Грубый эквивалент for цикла, переписан как в while петли:

/*initialization*/
while (/*condition*/)
{
    // body of the loop; using 'continue' will skip to increment part below
    /*iteration execution*/
}

Наиболее распространенным случаем использования цикла for является выполнение операторов определенное количество раз. Например, рассмотрим следующее:

for(int i = 0; i < 10; i++) {
    std::cout << i << std::endl;
}

Правильный цикл также:

for(int a = 0, b = 10, c = 20; (a+b+c < 100); c--, b++, a+=c) {
    std::cout << a << " " << b << " " << c << std::endl; 
}

Пример скрытия объявленных переменных перед циклом:

int i = 99; //i = 99
for(int i = 0; i < 10; i++) { //we declare a new variable i
    //some operations, the value of i ranges from 0 to 9 during loop execution
}
//after the loop is executed, we can access i with value of 99

Но если вы хотите использовать уже объявленную переменную и не скрывать ее, то опустите часть объявления:

int i = 99; //i = 99
for(i = 0; i < 10; i++) { //we are using already declared variable i
    //some operations, the value of i ranges from 0 to 9 during loop execution
}
//after the loop is executed, we can access i with value of 10

Заметки:

  • Операторы инициализации и приращения могут выполнять операции, не связанные с оператором условия, или вообще ничего - если вы этого хотите. Но по соображениям удобочитаемости лучше всего выполнять операции, непосредственно относящиеся к циклу.
  • Переменная, объявленная в инструкции инициализации, видна только внутри области цикла for и освобождается после завершения цикла.
  • Не забывайте, что переменная, которая была объявлена ​​в initialization statement может быть изменена во время цикла, а также переменная, отмеченная в condition .

Пример цикла, который рассчитывается от 0 до 10:

for (int counter = 0; counter <= 10; ++counter)
{
    std::cout << counter << '\n';
}
// counter is not accessible here (had value 11 at the end)

Объяснение фрагментов кода:

  • int counter = 0 инициализирует counter переменных равным 0. (Эта переменная может использоваться только внутри цикла for .)
  • counter <= 10 - это логическое условие, которое проверяет, равен ли counter меньше или равен 10. Если это true , цикл выполняется. Если он false , цикл завершается.
  • ++counter - это операция приращения, которая увеличивает значение counter на 1 перед следующей проверкой состояния.

Если оставить все утверждения пустыми, вы можете создать бесконечный цикл:

// infinite loop
for (;;)
    std::cout << "Never ending!\n";

В while петля эквивалент выше:

// infinite loop
while (true)
    std::cout << "Never ending!\n";

Тем не менее, бесконечный цикл все равно может быть оставлен при использовании операторов break , goto или return или путем исключения исключения.

Следующий общий пример итерации по всем элементам из коллекции STL (например, vector ) без использования заголовка <algorithm> :

std::vector<std::string> names = {"Albert Einstein", "Stephen Hawking", "Michael Ellis"};
for(std::vector<std::string>::iterator it = names.begin(); it != names.end(); ++it) {
    std::cout << *it << std::endl;
}

Пока цикл

В while цикл выполняет операторы , пока данное условие не станет false . Этот оператор управления используется, когда заранее неизвестно, сколько раз должен выполняться блок кода.

Например, чтобы напечатать все числа от 0 до 9, можно использовать следующий код:

int i = 0;
while (i < 10)
{
    std::cout << i << " ";
    ++i; // Increment counter
}
std::cout << std::endl; // End of line; "0 1 2 3 4 5 6 7 8 9" is printed to the console
C ++ 17

Обратите внимание, что поскольку C ++ 17, первые 2 оператора могут быть объединены

while (int i = 0; i < 10)
//... The rest is the same

Чтобы создать бесконечный цикл, можно использовать следующую конструкцию:

while (true)
{
    // Do something forever (however, you can exit the loop by calling 'break'
}

Существует еще один вариант циклов while , а именно do...while construct. Дополнительную информацию см. В примере цикла while-while .

Объявление переменных в условиях

В состоянии циклов for и while также разрешено объявлять объект. Этот объект будет рассматриваться как область действия до конца цикла и будет сохраняться через каждую итерацию цикла:

for (int i = 0; i < 5; ++i) {
    do_something(i);
}
// i is no longer in scope.

for (auto& a : some_container) {
    a.do_something();
}
// a is no longer in scope.

while(std::shared_ptr<Object> p = get_object()) {
   p->do_something();
}
// p is no longer in scope.

Однако не разрешается делать то же самое с do...while loop; вместо этого объявляйте переменную перед циклом и (необязательно) вставляйте как переменную, так и цикл в локальную область, если вы хотите, чтобы переменная выходила из области действия после завершения цикла:

//This doesn't compile
do {
    s = do_something();
} while (short s > 0);

// Good
short s;
do {
    s = do_something();
} while (s > 0);

Это связано с тем, что часть оператора цикла do...while while (тело цикла) оценивается до того, как часть выражения ( while ) достигнута, и, таким образом, любое объявление в выражении не будет видимым во время первой итерации петля.

Цикл Do-while

Цикл Do-то время очень похоже на время цикла, за исключением того, что условие проверяется в конце каждого цикла, а не в начале. Поэтому цикл гарантированно выполняется хотя бы один раз.

Следующий код будет печатать 0 , поскольку условие будет оцениваться как false в конце первой итерации:

int i =0;
do
{
    std::cout << i;
    ++i; // Increment counter
}
while (i < 0);
std::cout << std::endl; // End of line; 0 is printed to the console

Примечание: не забывайте точку с запятой в конце while(condition); , который необходим в конструкции do-while .

В отличие от цикла do- while, следующее не будет печатать ничего, потому что условие принимает значение false в начале первой итерации:

int i =0;
while (i < 0)
{
    std::cout << i;
    ++i; // Increment counter
}    
std::cout << std::endl; // End of line; nothing is printed to the console

Примечание: А в то время как цикл может быть прерван без условия становится ложным, используя break , goto , или return заявление.

int i = 0;
do
{
    std::cout << i;
    ++i; // Increment counter
    if (i > 5) 
    {
        break;
    }
}
while (true);
std::cout << std::endl; // End of line; 0 1 2 3 4 5 is printed to the console

Тривиальный цикл do- while также иногда используется для записи макросов, для которых требуется их собственная область (в этом случае конечная точка с запятой опускается из определения макроса и должна быть предоставлена ​​пользователем):

#define BAD_MACRO(x) f1(x); f2(x); f3(x);

// Only the call to f1 is protected by the condition here
if (cond) BAD_MACRO(var);

#define GOOD_MACRO(x) do { f1(x); f2(x); f3(x); } while(0)

// All calls are protected here
if (cond) GOOD_MACRO(var);

Записи цикла Loop: Break and Continue

Операторы управления циклом используются для изменения потока выполнения из его обычной последовательности. Когда выполнение оставляет область, все автоматические объекты, созданные в этой области, уничтожаются. break и continue - это операторы управления контуром.

Инструкция break прекращает цикл без какого-либо дальнейшего рассмотрения.

for (int i = 0; i < 10; i++)
{
    if (i == 4)
        break; // this will immediately exit our loop
    std::cout << i << '\n';
}

Вышеприведенный код будет распечатан:

1
2
3

Оператор continue не сразу выходит из цикла, а пропускает остальную часть тела цикла и переходит в начало цикла (включая проверку состояния).

for (int i = 0; i < 6; i++)
{
    if (i % 2 == 0) // evaluates to true if i is even
        continue; // this will immediately go back to the start of the loop
    /* the next line will only be reached if the above "continue" statement 
       does not execute  */
    std::cout << i << " is an odd number\n";
}

Вышеприведенный код будет распечатан:

1 is an odd number
3 is an odd number
5 is an odd number

Поскольку такие изменения потока управления иногда трудны для людей, которые легко понимают, break и continue использоваться экономно. Более простую реализацию, как правило, легче читать и понимать. Например, первый цикл for с break выше может быть переписан как:

for (int i = 0; i < 4; i++)
{
    std::cout << i << '\n';
}

Второй пример с continue может быть переписан как:

for (int i = 0; i < 6; i++)
{
    if (i % 2 != 0) {
        std::cout << i << " is an odd number\n";
    }
}

Диапазон - для суб-диапазона

Используя циклы базы значений, вы можете перебрать часть под конкретного контейнера или другого диапазона, создав прокси-объект, который подходит для циклов, основанных на диапазонах.

template<class Iterator, class Sentinel=Iterator>
struct range_t {
  Iterator b;
  Sentinel e;
  Iterator begin() const { return b; }
  Sentinel end() const { return e; }
  bool empty() const { return begin()==end(); }
  range_t without_front( std::size_t count=1 ) const {
    if (std::is_same< std::random_access_iterator_tag, typename std::iterator_traits<Iterator>::iterator_category >{} ) {
      count = (std::min)(std::size_t(std::distance(b,e)), count);
    }
    return {std::next(b, count), e};
  }
  range_t without_back( std::size_t count=1 ) const {
    if (std::is_same< std::random_access_iterator_tag, typename std::iterator_traits<Iterator>::iterator_category >{} ) {
      count = (std::min)(std::size_t(std::distance(b,e)), count);
    }
    return {b, std::prev(e, count)};
  }
};

template<class Iterator, class Sentinel>
range_t<Iterator, Sentinel> range( Iterator b, Sentinal e ) {
  return {b,e};
}
template<class Iterable>
auto range( Iterable& r ) {
  using std::begin; using std::end;
  return range(begin(r),end(r));
}

template<class C>
auto except_first( C& c ) {
  auto r = range(c);
  if (r.empty()) return r;
  return r.without_front();
}

теперь мы можем сделать:

std::vector<int> v = {1,2,3,4};

for (auto i : except_first(v))
  std::cout << i << '\n';

и распечатать

2
3
4

Имейте в виду, что промежуточные объекты, сгенерированные в части for(:range_expression) цикла for будут истекли к моменту начала цикла for .



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow