Цикл for visual studio 2010

Оператор цикла 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. Разработать программу, которая находит следующую сумму
Фрагмент кода, решающий данную задачу
// вычислить сумму
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, что в последовательности чисел
последнее число есть меньше чем 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)?
- int i = 10;
- for (i = 1; i <= 10; i++)
- {
- if ((i % 2) = 0)
- continue;
- Console.WriteLine(i);
- }
* This source code was highlighted with Source Code Highlighter.
Ответы, если хотите, можете также присылать мне на почту или просто делать для себя. Также с радостью приму любые советы по улучшению курса. Для небольшого бонуса прошу читать ниже.
Примеры кода, которые были использованы в лекции.
1. Применение булевских операций
- static void Main(string[] args)
- {
- int myInt;
- Console.WriteLine(“Enter an integer:”); // Введите целое число
- Int32.TryParse(Console.ReadLine(), out myInt);
- Console.WriteLine(“Integer less than 10? {0}”, myInt < 10);
- Console.WriteLine(“Integer between 0 and 5? {0}”, (myInt >= 0) && (myInt <= 5));
- Console.ReadKey();
- }
* This source code was highlighted with Source Code Highlighter.
2. Применение операторов goto, if else, ?: (тернарный оператор)
- static void Main(string[] args)
- {
- string result = String.Empty;
- double var1 = 0, var2 = 0;
- begin1:
- Console.WriteLine(“Enter first number:”);
- if (!Double.TryParse(Console.ReadLine(),out var1)) // здесь я упростил код просто поставив “!” перед выражением bool
- {
- Console.WriteLine(“You should enter a double value.”);
- goto begin1;
- }
- begin2:
- Console.WriteLine(“Enter second number:”);
- if (!Double.TryParse(Console.ReadLine(), out var2))
- {
- Console.WriteLine(“You should enter a double value.”);
- goto begin2;
- }
- if (var1 < var2)
- result = “less than”;
- else
- {
- result = var1 == var2 ? “equal to” : “greater than”;
- }
- Console.WriteLine(“The first number is {0} the second number.”, result);
- Console.ReadKey();
- }
* This source code was highlighted with Source Code Highlighter.
3. Применение оператора switch
- static void Main(string[] args)
- {
- const int fail = 10;
- int value = 0;
- switch (value)
- {
- case 1:
- Console.WriteLine(“This is one”);
- break;
- case 2:
- Console.WriteLine(“This is two”);
- break;
- case fail:
- Console.WriteLine(“This is fail”);
- break;
- default:
- Console.WriteLine(“This is default”);
- break;
- }
- }
* This source code was highlighted with Source Code Highlighter.
4. Применение циклов
- static void Main(string[] args)
- {
- double balance = 0, interestRate = 0, targetBalance = 0;
- Console.WriteLine(“What is your current balance?”);
- Double.TryParse(Console.ReadLine(), out balance);
- Console.WriteLine(“What is your current interest rate (in %)?”);
- Double.TryParse(Console.ReadLine(), out interestRate);
- interestRate = 1 + interestRate/100.0;
- Console.WriteLine(“What balance would you like to have?”);
- Double.TryParse(Console.ReadLine(), out targetBalance);
- int totalYears = 0;
- while (balance < targetBalance)
- {
- balance *= interestRate;
- totalYears++;
- }
- Console.WriteLine(“In {0} year{1} you will have a balance of {2}.”, totalYears,
- totalYears == 1 ? “” : “s”, balance);
- 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
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
#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
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 и попробуйте его поиспользовать хотя бы несколько дней.
Источник