Цикл for visual studio 2010

Цикл for visual studio 2010 thumbnail

Оператор цикла for. Общая форма. Примеры решения задач

Содержание

  • 1. Назначение оператора цикла for. Общая форма
  • 2. Разновидности цикла for. Примеры
  • 3. Примеры цикла for, в котором количество итераций заведомо известно
  • 4. Примеры цикла for, в котором количество итераций заведомо неизвестно
  • 5. Понятие вложенного цикла for
  • 6. Примеры решения задач, в которых цикл for есть вложенным в другие циклы
  • Связанные темы

Поиск на других ресурсах:

1. Назначение оператора цикла for. Общая форма

Цикл for предназначен для организации циклического процесса. С помощью цикла for можно организовывать циклический процесс любого типа, в котором:

  • количество итераций цикла заведомо известно;
  • количество итераций цикла неизвестно и определяется на основе выполнения некоторого условия.

Общая форма оператора цикла for

for (инициализация; условие; выражение)
{
  // некоторые инструкции, операторы
  // …
}

где

  • инициализация – выражение, инициализирующее переменную-счетчик. Эта переменная определяет количество итераций, которые должны выполниться в цикле;
  • условие – логическое выражение, которое определяет необходимость выполнения следующей итерации цикла. Если условие = true, то выполняется следующая итерация цикла. Если значение условие = false, то происходит прекращение выполнения цикла и переход к следующему оператору, который следует за оператором for;
  • выражение – некоторое выражение, изменяющее значение переменной-счетчика. Необязательно выражение может изменять значение переменной-счетчика. Также переменная-счетчик может изменять свое значение в теле цикла.

Если оператор цикла for применяется для одного оператора, то в общей форме фигурные скобки можно опустить

for (инициализация; условие; выражение)
  оператор;

здесь оператор – один оператор или одна инструкция.

  ⇑

2. Разновидности цикла for. Примеры

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

Пример 1. В примере опущена часть, которая инициализирует переменную-счетчик. Вычисляется сумма

s = 2 + 4 + 8 + … + 100

Фрагмент кода, решающий данную задачу следующий:

// вычислить сумму 2+4+8+…+100
int sum = 0;
int i = 0;

// нет части инициализации переменной-счетчика
for (; i <= 100; i += 2)
  sum += i;
// sum = 2550

Пример 2. В примере опущена часть, которая проверяет условие выполнения следующей итерации. Пример вычисляет сумму элементов массива A.

// вычислить сумму элементов массива A
double[] A = { 2.5, 1.2, 0.8, 3.3, 4.2 };
double sum = 0;
int i;

// нет части, которая проверяет условие выполнения цикла
for (i = 0; ; i++)
{
   if (i == A.Length) break;
  sum += A[i];             
}         
// sum = 12.0

Пример 3. В примере опущена часть выражения, которая изменяет переменную-счетчик. Дано вещественное число a и натуральное число n. Вычислить:

a × (a+1) × … × (a+n-1)

Фрагмент кода, решающий данную задачу

// вычислить произведение a*(a+1)*…*(a+n-1)
int mult;
int i;
int n, a;

n = 5;
a = 4;
mult = 1;

// отсутствует часть прироста переменной-счетчика i
for (i = 0; i < n; )
{
  mult = mult * (a + i);
  i++;
}
// mult = 6720

Пример 4. В примере цикл for не содержит инициализации и условия. Задано натуральное число n. Определить максимальную цифру этого числа.

Решение данной задачи с использованием цикла for (приложение типа Console Application)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication4
{
  class Program
  {
      static void Main(string[] args)
      {
          // определить максимальную цифру числа n
          int n;
          int t, d;
          int max;

           // ввод n
          Console.Write(“n = “);
          n = Convert.ToInt32(Console.ReadLine());

           t = n;
          max = 0; // максимальная цифра

           for (; t > 0; ) // цикл не содержит инициализации и прироста счетчика
          {
               d = t % 10;
               if (max < d) max = d;
                t = t / 10;
          }
          Console.WriteLine(“Max = {0}”, max);
      }
  }
}

Результат выполнения вышеприведенной программы:

n = 283915
Max = 9

Пример 5. В примере цикл for не содержит условия и выражения. Выход из цикла for осуществляется с помощью инструкции break.

Задан массив B чисел типа float. Найти позицию pos первого элемента массива, значение которого лежит в диапазоне от -5 до +5.

Фрагмент кода, решающий данную задачу

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication4
{
class Program
{
static void Main(string[] args)
{
// найти позицию первого вхождения
float[] B = { 12.4f, -11.9f, 7.5f, 2.3f, 1.8f };
int pos;

for (pos = 0; ; ) // цикл for не содержит условия и выражения
{
if (pos == B.Length)
break;
if ((B[pos] >= -5) && (B[pos] <= 5))
break;
pos++;
}

if (pos == B.Length)
Console.WriteLine(“Искомого элемента нет в массиве.”);
else
Console.WriteLine(“pos = {0}”, pos);
}
}
}

Результат выполнения программы

pos = 3

Пример 6. В примере демонстрируется «пустой» цикл for. В общем случае пустой цикл for выглядит следующим образом:

for ( ; ; )
{
// некоторые инструкции
// …
}

  ⇑

3. Примеры цикла for, в котором количество итераций заведомо известно

Пример 1. Найти сумму всех целых чисел от 100 до 200. Фрагмент программы, которая решает данную задачу с использованием цикла for следующий:

// вычислить сумму
int sum;
int i;
sum = 0;
for (i = 100; i <= 200; i++)
sum += i;
// sum = 15150

Пример 2. Дано натуральное число n. Разработать программу, которая находит следующую сумму

Цикл for visual studio 2010

Фрагмент кода, решающий данную задачу

// вычислить сумму
double sum;
int i;
int n;
n = 10;
sum = 0;
for (i = 1; i <= n; i++)
sum = sum + 1.0 / i;
// sum = 2.92896825396825

Пример 3. Рекурентные соотношения. Последовательность чисел a0, a1, a2, … получается по закону:

Дано натуральное число n. Получить a1, a2, …, an.

Текст программы, которая решает данную задачу, следующий:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication4
{
class Program
{
static void Main(string[] args)
{
// получить a1, a2, …, an
double a0, ak;
int k;
int n;

n = 10;
a0 = 1;
k = 0;

Console.WriteLine(“a[{0}] = {1}”, k, a0);
for (k = 1; k <= n; k++)
{
ak = k * a0 + 1.0 / k;
Console.WriteLine(“a[{0}] = {1}”, k, ak);
a0 = ak;
}
}
}
}

В результате выполнения вышеприведенного кода, будет выведен следующий результат

a[0] = 1
a[1] = 2
a[2] = 4.5
a[3] = 13.8333333333333
a[4] = 55.5833333333333
a[5] = 278.116666666667
a[6] = 1668.86666666667
a[7] = 11682.2095238095
a[8] = 93457.8011904762
a[9] = 841120.321825397
a[10] = 8411203.31825397

  ⇑

4. Примеры цикла for, в котором количество итераций заведомо неизвестно

Пример 1. Дано вещественное число a. Найти такое наименьшее n, что

Решение задачи для приложения типа Console Application

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
  class Program
  {
      static void Main(string[] args)
      {
          // цикл for, в котором количество итераций заранее неизвестно
          int n;
          double a;
          double t;
          Console.Write(“a = “);
          a = Convert.ToDouble(Console.ReadLine());

           // цикл вычисления
          for (n = 1, t = 0; t < a; n++)
              t = t + 1.0 / n;
          Console.Write(“n = {0}”, n – 1);
      }
  }
}

Выполнение данной программы даст следующий результат

a = 2
n = 4

Пример 2. Задано число a (1<a≤1.5). Найти такое наименьшее n, что в последовательности чисел

Читайте также:  Два цикла нет месячных

Цикл for visual studio 2010

последнее число есть меньше чем a.

Ниже приведено решение задачи для приложения типа Console Application

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication4
{
class Program
{
static void Main(string[] args)
{
// найти наименьшее n
int n;
double a;
double t;

Console.Write(“a = “);
a = Convert.ToDouble(Console.ReadLine());

// в части инициализации помещаются два выражения
for (n = 2, t = 1 + 1.0 / n; t >= a; n++)
t = 1 + 1.0 / n;

Console.WriteLine(“n = {0}”, n-1);
}
}
}

Как видно из вышеприведенного кода, в цикле for в части инициализации помещаются два выражения, разделенных запятой:

for (n = 2, t = 1 + 1.0 / n; t >= a; n++)
t = 1 + 1.0 / n;

Выполнение программы для некоторого значения a может быть, например, следующим

a = 1.3
n = 4

  ⇑

5. Понятие вложенного цикла for

Цикл for может быть вложенным в любой другой управляющий оператор, которым может быть:

  • оператор условного перехода if;
  • оператор варианта switch;
  • оператор цикла for;
  • оператор цикла while;
  • оператор цикла do…while.

Количество уровней вложения неограничено.

  ⇑

6. Примеры решения задач, в которых цикл for есть вложенным в другие циклы

Пример 1. В примере цикл for вложен в другой цикл for.

Напечатать числа в следующем виде:

1
2 2
3 3 3
4 4 4 4
5 5 5 5 5

Фрагмент кода, который решает данную задачу для приложения типа Console Application:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication4
{
class Program
{
static void Main(string[] args)
{
// в цикле for вложен другой цикл for
for (int i = 1; i <= 5; i++)
{
for (int j = 1; j <= i; j++)
Console.Write(“{0} “, i);
Console.WriteLine();
}
}
}
}

Пример 2. Цикл for вложен в цикл while.

Найти все целые числа из промежутка от 1 до 300, в которых ровно 5 делителей.

Фрагмент кода, который решает данную задачу

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication4
{
class Program
{
static void Main(string[] args)
{
// найти все числа от 1 до 300 в которых ровно 5 делителей
int num;
int i, k;

num = 1;
while (num <= 300)
{
k = 0;
for (i = 1; i <= num; i++) // цикл for вложен в цикл while
if (num % i == 0) k++;
if (k == 5)
Console.WriteLine(“{0}”, num);
num++;
}
}
}
}

Результат выполнения программы

16
81

  ⇑

Связанные темы

  • Оператор условного перехода if. Полная и сокращенная формы. Конструкция if-else-if
  • Оператор выбора switch
  • Оператор цикла while. Примеры

  ⇑

Источник

Циклы

Последнее обновление: 30.10.2015

Еще одним видом управляющих конструкций являются циклы. В VB.NET используется несколько видов циклов.

  • For…Next

  • For Each…Next

  • While

  • Do

Цикл For…Next

В этом цикл выполняется определенное число раз, причем это число задается счетчиком:

For i As Integer = 1 To 9
Console.WriteLine(“Квадрат числа {0} равен {1}”, i, i * i)
Next

Здесь переменная i выполняет роль счетчика. После слова To мы помещаем максимальное значение счетчика. При каждом цикле значение счетчика увеличивается
на единицу. И это значение сравнивается со значением после To. Если эти два значения равны, то цикла прекращает свою работу.

При работе с циклами мы можем увеличивать значение счетчика при каждом проходе не только на единицу, но и вообще на любое число. Для этого
нужно либо использовать ключевое слово Step и после него указать шаг цикла, на который будет увеличиваться значение счетчика, либо можно
увеличивать счетчик непосредственно в цикле:

For i As Integer = 1 To -9 Step -1
For j As Integer = 1 To 9
Console.WriteLine(“Произведение чисел i и j равно {0}”, i * j)
j += 1
Next
Next

Обратите внимание, что в качестве шага в первом цикле выбрано отрицательное значение и значение счетчика с каждым проходом уменьшается на единицу.
Во внутреннем цикле счетчик j при каждом проходе увеличивается на 2, так как он по умолчанию увеличивается на единицу, и еще мы явным образом увеличиваем его в цикле на единицу.
В итоге внутренний цикл отрабатывает пять раз, а внешний девять, то есть фактически получается 45 циклов.

Цикл For Each…Next

Цикл For Each осуществляет перебор элементов в определенной группе, например, в массиве или в коллекции. Предположим у нас есть некоторый массив
типа Integer и нам надо инициализировать этот массив случайными значениями и затем вывести все его элементы на экран:

‘Создаем массив из пяти чисел
Dim nums(4) As Integer
Dim r As New Random()
‘инициализируем массив
For i As Integer = 0 To nums.Length – 1
nums(i) = r.Next(100)
Next
‘Выводим элементы массива
For Each i As Integer In nums
Console.Write(“{0} “, i)
Next

В выражении For Each мы сначала указываем переменную, которая будет принимать значения элементов массива. А после ключевого слова In
указываем группу, в которой надо перебрать все элементы.

Цикл While

В цикл While выполняется до тех пор, пока соблюдается определенное условие, указанное после слова While:

Dim j As Integer = 10
While j > 0
Console.WriteLine(j)
j -= 1
End While

Цикл Do

Цикл Do, также как и цикл While, выполняется, пока соблюдается определенное условие. Однако он имеет разные формы. Так, в следующем примере
сначала проверяется условие, а затем выполняется блок кода, определенный в цикле:

Dim j As Integer = 10
Do While j > 0
Console.WriteLine(j)
j -= 1
Loop

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

Dim j As Integer = 10
Do Until j < 0
Console.WriteLine(j)
j -= 1
Loop

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

Dim j As Integer = -1
Do
Console.WriteLine(j)
j -= 1
Loop Until j < 0
‘либо
Do
Console.WriteLine(j)
j -= 1
Loop While j > 0

Операторы Continue и Exit

Нередко возникает необходимость не дожидаться окончания цикла, а сразу же выйти из цикла, в случае соблюдения определенного условия. Для
этого используют оператор Exit, после которого указывают тип цикла, из которого осуществляется выход, например, Exit Do (Exit While):

Dim r As New Random()
Dim num As Integer = r.Next(100)

For i As Integer = 0 To 100
num -= 1
If num < 50 Then Exit For
Next
Console.WriteLine(num)

Существует и другая задача – осуществить выход не из цикла, а из текущего прохода или итерации и перейти к следующему. Для этого используют
оператор Continue, после которого указывают тип цикла, из которого осуществляется выход, например, Continue While:

Dim r As New Random()
Dim num As Integer = r.Next(100)

For i As Integer = 0 To 10
num -= 7
If num < 50 AndAlso num > 25 Then
Continue For
End If

Console.WriteLine(num)
Next

В данном случае мы в каждом проходе цикла вычитаем из num число 7 и затем смотрим, не принадлежит ли число num интервалу от 25 до 50.
И если принадлежит, переходим к новой итерации цикла, а если нет, то выводим его на экран.

Читайте также:  Цикл книг коридоры событий

Источник

Доброго времени суток, хабражители!
Для желающих начать программировать на языке C# выкладываю четвертую лекцию на тему: «Условия и циклы». Лекция получилась очень большая (на целый час), поэтому, кто готов смотреть так долго я желаю терпения и большого желания не останавливаться на достигнутом.

Ссылки на предыдущие лекции

Лекция 1. Введение
Лекция 2. Hello, World! и знакомство с Visual C# Express 2010
Лекция 3. Переменные и выражения

А сейчас — ответы на предыдущее домашнее задание:
1. Недопустимыми именами для переменных являются:

100metres

(так как имя переменной не может начинаться с цифры) и

csharp.com

(так как имя переменной содержит посторонний символ “.”)
2. Строка “

thisisveryverylongstringindeedisntit

” НЕ является слишком большой, чтобы уместиться в тип string, поскольку память, выделяемая под переменные этого типа динамична и меняется в зависимости от размера.
3. Учитывая приоритет операций выражение:

resultVar += var1 * var2 + var3 % var4 / var5;

будет выполняться со следующей последовательностью:
1) *
2) %
3) /
4) +
5) +=

Надеюсь, что проблем с домашней работой у вас не было, многие мне даже присылали ответы на почту, что очень приятно, ну а мы переходим к самому интересному.
Приятного просмотра!

Новое домашнее задание:

1. При наличии двух целых чисел, хранящихся в переменных var1 и var2, какую булевскую проверку нужно выполнить для выяснения того, является ли то или другое (но не оба вместе) больше 10?
2. Напишите приложение, которое получает два числа от пользователя и отображает их на экране, но отклоняет варианты, когда оба числа больше 10, и предлагает в таком случае ввести два других числа.
3. Что неверно в следующем коде (постарайтесь решить это задание, не используя Visual Studio)?

  1.       int i = 10;
  2.       for (i = 1; i <= 10; i++)
  3.       {
  4.         if ((i % 2) = 0)
  5.           continue;
  6.         Console.WriteLine(i);
  7.       }

* This source code was highlighted with Source Code Highlighter.

Ответы, если хотите, можете также присылать мне на почту или просто делать для себя. Также с радостью приму любые советы по улучшению курса. Для небольшого бонуса прошу читать ниже.

Примеры кода, которые были использованы в лекции.

1. Применение булевских операций

  1.     static void Main(string[] args)
  2.     {
  3.       int myInt;
  4.       Console.WriteLine(“Enter an integer:”); // Введите целое число
  5.       Int32.TryParse(Console.ReadLine(), out myInt);
  6.       Console.WriteLine(“Integer less than 10? {0}”, myInt < 10);
  7.       Console.WriteLine(“Integer between 0 and 5? {0}”, (myInt >= 0) && (myInt <= 5));
  8.       Console.ReadKey();
  9.     }

* This source code was highlighted with Source Code Highlighter.

2. Применение операторов goto, if else, ?: (тернарный оператор)

  1.     static void Main(string[] args)
  2.     {
  3.       string result = String.Empty;
  4.       double var1 = 0, var2 = 0;
  5.  
  6.       begin1:
  7.       Console.WriteLine(“Enter first number:”);
  8.       if (!Double.TryParse(Console.ReadLine(),out var1)) // здесь я упростил код просто поставив “!” перед выражением bool
  9.       {
  10.         Console.WriteLine(“You should enter a double value.”);
  11.         goto begin1;
  12.       }
  13.  
  14.       begin2:
  15.       Console.WriteLine(“Enter second number:”);
  16.       if (!Double.TryParse(Console.ReadLine(), out var2))
  17.       {
  18.         Console.WriteLine(“You should enter a double value.”);
  19.         goto begin2;
  20.       }
  21.  
  22.       if (var1 < var2)
  23.         result = “less than”;
  24.       else
  25.       {
  26.         result = var1 == var2 ? “equal to” : “greater than”;
  27.       }
  28.  
  29.       Console.WriteLine(“The first number is {0} the second number.”, result);
  30.       Console.ReadKey();
  31.     }

* This source code was highlighted with Source Code Highlighter.

3. Применение оператора switch

  1.     static void Main(string[] args)
  2.     {
  3.       const int fail = 10;
  4.       int value = 0;
  5.  
  6.       switch (value)
  7.       {
  8.         case 1:
  9.           Console.WriteLine(“This is one”);
  10.           break;
  11.         case 2:
  12.           Console.WriteLine(“This is two”);
  13.           break;
  14.         case fail:
  15.           Console.WriteLine(“This is fail”);
  16.           break;
  17.         default:
  18.           Console.WriteLine(“This is default”);
  19.           break;
  20.       }
  21.     }

* This source code was highlighted with Source Code Highlighter.

4. Применение циклов

  1.     static void Main(string[] args)
  2.     {
  3.       double balance = 0, interestRate = 0, targetBalance = 0;
  4.       Console.WriteLine(“What is your current balance?”);
  5.       Double.TryParse(Console.ReadLine(), out balance);
  6.       Console.WriteLine(“What is your current interest rate (in %)?”);
  7.       Double.TryParse(Console.ReadLine(), out interestRate);
  8.       interestRate = 1 + interestRate/100.0;
  9.       Console.WriteLine(“What balance would you like to have?”);
  10.       Double.TryParse(Console.ReadLine(), out targetBalance);
  11.  
  12.       int totalYears = 0;
  13.  
  14.       while (balance < targetBalance)
  15.       {
  16.         balance *= interestRate;
  17.         totalYears++;
  18.       }
  19.  
  20.       Console.WriteLine(“In {0} year{1} you will have a balance of {2}.”, totalYears,
  21.         totalYears == 1 ? “” : “s”, balance);
  22.       Console.ReadKey();

* This source code was highlighted with Source Code Highlighter.

Источник

Сегодня необычный для меня формат статьи: я скорее задаю вопрос залу, нежели делюсь готовым рецептом. Впрочем, для инициирования дискуссии рецепт тоже предлагаю. Итак, сегодня мы поговорим о чувстве прекрасного.

Я довольно давно пишу код, и так вышло, что практически всегда на C++. Даже и не могу прикинуть, сколько раз я написал подобную конструкцию:

for (int i=0; i<size; i++) {
[…]
}

Хотя почему не могу, очень даже могу:

find . ( -name *.h -o -name *.cpp ) -exec grep -H “for (” {} ; | wc -l
43641

Наш текущий проект содержит 43 тысячи циклов. Проект пилю не я один, но команда маленькая и проект у меня не первый (и, надеюсь, не последний), так что в качестве грубой оценки пойдёт. А насколько такая запись цикла for хороша? Ведь на самом деле, важно даже не то количество раз, когда я цикл написал, а то количество раз, когда я цикл прочитал (см. отладка и code review). А тут речь очевидно идёт уже о миллионах.

На КПДВ узел под названием «совершенная петля» (perfection loop).

Так каков он, совершенный цикл?

Мы пишем много кода для математического моделирования; код довольно плотный, с огромным количеством целых чисел, которые являются индексами ячеек, функций и прочей лабуды. Чтобы был понятен масштаб проблемы, давайте я просто приведу крохотный кусочек кода из нашего проекта:

for (int iter=0; iter<nb_iter; iter++) { // some iterative computation
for (int c=0; c<mesh.cells.nb(); c++) // loop through all tetrahedra
for (int lv0=0; lv0<4; lv0++) // for every pair of
for (int lv1 = lv0+1; lv1<4; lv1++) // vertices in the tet
for (int d=0; d<3; d++) { // do stuff for each of 3 dimensions
nlRowScaling(weight);
nlBegin(NL_ROW);
nlCoefficient(mesh.cells.vertex(c, lv0)*3 + d, 1);
nlCoefficient(mesh.cells.vertex(c, lv1)*3 + d, -1);
nlEnd(NL_ROW);
}
[…]
}

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

Мы обязаны иметь дело с кучей вложенных циклов; вышеприведённые пять вложенных далеко не предел. Мы уже довольно давно (лет пятнадцать как) пришли к выводу, что стандартный for (int i=0; i<size; i++) — это очень громоздкая конструкция: те самые пять вложенных заголовков for превращаются в совершенно нечитаемую кашу, и даже подсветка синтаксиса не спасает.

Читайте также:  Условия и циклы с таблица

Когда мы читаем стандартный for(;;), мы должны на каждой строчке обратить внимание на три вещи: на инициализацию, на условие выхода и собственно на инкремент. Но ведь это совершеннейший оверкилл для тех случаев, когда нам нужно пройтись от 0 до size-1, а это подавляющее большинство всех циклов. Скажите, как часто вам приходится писать обратный цикл или итерацию с другими границами? Как мне кажется, один раз из десяти — это ещё щедрая оценка.

До появления c++11 мы в итоге пришли к страшной вещи, а именно ввели в самый верхний заголовок вот такой дефайн:

#define FOR(I,UPPERBND) for(int I = 0; I<int(UPPERBND); ++I)

И тогда вышеприведённый кусок кода превращается из тыквы в кабачок:

FOR(iter, nb_iter) {
FOR(c, mesh.cells.nb())
FOR(lv0, 4)
for (int lv1 = lv0+1; lv1<4; lv1++)
FOR(d, 3) {
nlRowScaling(weight);
nlBegin(NL_ROW);
nlCoefficient(mesh.cells.vertex(c, lv0)*3 + d, 1);
nlCoefficient(mesh.cells.vertex(c, lv1)*3 + d, -1);
nlEnd(NL_ROW);
}
[…]
}

Польза такой трансформации в том, что когда я встречаю for (;;), я знаю, что мне нужно насторожиться и внимательно смотреть на все три места (инициализацию, условие, инкремент). В то время как если я вижу FOR(,) то это совершенно стандартный пробег от 0 до n-1 без каких-либо тонкостей. Я совершенно не предлагаю пользоваться вышеприведённым дефайном, но точно знаю, что для нашей команды он сберёг много ресурсов мозга, поскольку мы кода гораздо больше читаем (см. отладка), нежели пишем (как, наверное, и все программисты).

То есть, вопрос, которым я задаюсь, звучит так: “Как выглядит цикл, имеющий минимальную когнитивную нагрузку при чтении кода?

А как дела обстоят у соседей? Вы знаете, местами довольно недурно. Например, в лагере питонистов стандартный цикл выглядит следующим образом:

for i in range(n):
print(i)

Что любопытно, до третьего питона range() создавал в памяти массив индексов, и проходился по нему. И со времён c++11 мы вполне можем делать точно так же!

#include <iostream>
int main() {
int range[] = {0,1,2,3,4,5};
for (int i : range) {
std::cerr << i;
}
}

Разумеется, явно создавать в памяти массив индексов это несерьёзно, и с третьей версии в питоне это тоже поняли. Но и в C++ мы можем сделать не хуже!

Давайте посмотрим на следующую функцию range(int n):

#include <iostream>

constexpr auto range(int n) {
struct iterator {
int i;
void operator++() { ++i; }
bool operator!=(const iterator& rhs) const { return i != rhs.i; }
const int &operator*() const { return i; }
};
struct wrapper {
int n;
auto begin() { return iterator{0}; }
auto end() { return iterator{n}; }
};
return wrapper{n};
}

int main() {
for (int i: range(13)) {
std::cerr << i;
}
return 0;
}

Пожалуйста, не начинайте int vs size_t, разговор не об этом. Если скомпилировать этот код при помощи gcc 10.2 с флагами компиляции -std=c++17 -Wall -Wextra -pedantic -O1, то мы получим следующий ассемблерный код (проверьте тут):

[…]
.L2:
mov esi, ebx
mov edi, OFFSET FLAT:_ZSt4cerr
call std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
add ebx, 1
cmp ebx, 13
jne .L2
[…]

То есть, компилятор начисто убрал все эти обёртки и оставил голый инкремент, ровно как если бы мы написали обычный for (int i=0; i<13; i++).

Лично мне кажется, что for (int i: range(n)) справляется с подчёркиванием обычности цикла чуть хуже, нежели FOR(,), но тоже вполне достойно, и за это не нужно платить дополнительными тактами процессора.

Range for в c++11 нанёс большую пользу. Давайте скажем, что у меня есть массив трёхмерных точек, и мне нужно распечатать икс координаты каждой точки, это можно сделать следующим образом:

#include <vector>
#include <iostream>

struct vec3 { double x,y,z; };

int main() {
std::vector<vec3> points = {{6,5,8},{1,2,3},{7,3,7}};
for (vec3 &p: points) {
std::cerr << p.x;
}
return 0;
}

for (vec3 &p: points) это прекрасная конструкция, никаких костылей, сразу из стандарта языка. Но что если у меня каждая точка из массива имеет цвет, вес или вкус? Это можно представить ещё одним массивом того же размера, что и массив точек. И тогда для доступа к атрибуту мне всё же понадобится индекс, который мы можем сгенерировать, например, вот таким образом:

std::vector<vec3> points = {{6,5,8},{1,2,3},{7,3,7}};
std::vector<double> weights = {4,6,9};
int i = 0;
for (vec3 &p: points) {
std::cerr << p.x << weights[i++];
}

Для этого кода компилятор генерирует следующий ассемблер:

asm

.L2:
movsd xmm0, QWORD PTR [r13+0]
mov edi, OFFSET FLAT:_ZSt4cerr
call std::basic_ostream<char, std::char_traits<char> >& std::basic_ostream<char, std::char_traits<char> >::_M_insert<double>(double)
movsd xmm0, QWORD PTR [rbp+0]
mov rdi, rax
call std::basic_ostream<char, std::char_traits<char> >& std::basic_ostream<char, std::char_traits<char> >::_M_insert<double>(double)
add rbp, 8
add r13, 24
cmp r14, rbp
jne .L2

В принципе, имеет право на жизнь, но гулять так гулять, давайте снимем с программиста заботу о создании параллельного индекса, ровно как сделали в питоне, благо стандарт c++17 имеет structural binding!

Итак, можно сделать следующим образом:

#include <vector>
#include <iostream>
#include “range.h”

struct vec3 {
double x,y,z;
};

int main() {
std::vector<vec3> points = {{6,5,8},{1,2,3},{7,3,7}};
std::vector<double> weights = {4,6,9};

for (auto [i, p]: enumerate(points)) {
std::cerr << p.x << weights[i];
}

return 0;
}

Функция enumerate() определена в следующем заголовочном файле:

range.h

#ifndef __RANGE_H__
#define __RANGE_H__

#include <tuple>
#include <utility>
#include <iterator>

constexpr auto range(int n) {
struct iterator {
int i;
void operator++() { ++i; }
bool operator!=(const iterator& rhs) const { return i != rhs.i; }
const int &operator*() const { return i; }
};
struct wrapper {
int n;
auto begin() { return iterator{0}; }
auto end() { return iterator{n}; }
};
return wrapper{n};
}

template <typename T> constexpr auto enumerate(T && iterable) {
struct iterator {
int i;
typedef decltype(std::begin(std::declval<T>())) iterator_type;
iterator_type iter;
bool operator!=(const iterator& rhs) const { return iter != rhs.iter; }
void operator++() { ++i; ++iter; }
auto operator*() const { return std::tie(i, *iter); }
};
struct wrapper {
T iterable;
auto begin() { return iterator{0, std::begin(iterable)}; }
auto end() { return iterator{0, std::end (iterable)}; }
};
return wrapper{std::forward<T>(iterable)};
}

#endif // __RANGE_H__

При компиляции с флагами -std=c++17 -Wall -Wextra -pedantic -O2 мы получим следующий ассемблерный код (проверьте тут):

ASM

.L14:
movsd xmm0, QWORD PTR [rbx]
mov edi, OFFSET FLAT:_ZSt4cerr
call std::basic_ostream<char, std::char_traits<char> >& std::basic_ostream<char, std::char_traits<char> >::_M_insert<double>(double)
mov rdi, rax
mov rax, QWORD PTR [rsp+32]
movsd xmm0, QWORD PTR [rax+rbp]
call std::basic_ostream<char, std::char_traits<char> >& std::basic_ostream<char, std::char_traits<char> >::_M_insert<double>(double)
add rbx, 24
add rbp, 8
cmp r12, rbx
jne .L14

И снова компилятор начисто убрал обёртку (правда, для этого пришлось поднять уровень оптимизации с -O1 на -O2).
Кстати, в c++20 появился std::ranges, что ещё больше упрощает написание такой функции, но я пока не готов переходить на этот стандарт.

На ваш взгляд, каким должен быть совершенный цикл в 2020м году? Научите меня!

Если вы ещё не задавались этим вопросом, то скопируйте к себе в пет-проект заголовочный файл range.h и попробуйте его поиспользовать хотя бы несколько дней.

Источник