Итак, через некоторое время, вот подход, который позволяет вам написать следующее на чистом C:
2 times {
do_something()
}
Пример:
Вы должны будете включить эту маленькую вещь в качестве простого заголовочного файла (я всегда называл файл extension.h
). Тогда вы сможете писать программы в стиле:
#include<stdio.h>
#include"extension.h"
int main(int argc, char** argv){
3 times printf("Hello.\n");
3 times printf("Score: 0 : %d\n", _);
2 times {
printf("Counting: ");
9 times printf("%d ", _);
printf("\n");
}
5 times {
printf("Counting up to %d: ", _);
_ times printf("%d ", _);
printf("\n");
}
return 0;
}
Особенности:
- Простая запись простых петель (в стиле, изображенном выше)
- Счетчик неявно хранится в переменной с именем
_
(простое подчеркивание).
- Разрешено вложение циклов.
Ограничения (и как (частично) их обойти):
- Работает только для определенного числа циклов (что - «конечно» - разумно, так как вы хотели бы использовать такую вещь только для «маленьких» циклов). Текущая реализация поддерживает максимум 18 итераций (более высокие значения приводят к неопределенному поведению). Можно изменить в заголовочном файле, изменив размер массива
_A
.
- Допускается только определенная глубина вложения. Текущая реализация поддерживает глубину вложения 10. Может быть скорректирована путем переопределения макроса
_Y
.
Пояснение:
Здесь вы можете увидеть полный (= де-обфусцированный) исходный код . Допустим, мы хотим разрешить до 18 циклов.
- Получение верхней границы итерации: Основная идея состоит в том, чтобы иметь массив
char
с, которые изначально все установлены в 0 (это массив counterarray
). Если мы позвоним, например, 2 times {do_it;}
, макрос times
должен установить для второго элемента counterarray
значение 1
(т.е. counterarray[2] = 1
). В C можно поменять местами индекс и имя массива в таком присваивании, поэтому мы можем написать 2[counterarray] = 1
для достижения того же самого. Это именно то, что макрос times
делает в качестве первого шага. Затем мы можем позже сканировать массив counterarray
, пока не найдем элемент, который не равен 0, а равен 1. Тогда соответствующий индекс является верхней границей итерации. Хранится в переменной searcher
. Поскольку мы хотим поддерживать вложение, мы должны хранить верхнюю границу для каждой глубины вложения отдельно, это делается с помощью searchermax[depth]=searcher+1
.
- Регулировка текущей глубины вложения: Как уже говорилось, мы хотим поддерживать вложение циклов, поэтому мы должны отслеживать текущую глубину вложения (выполняется в переменной
depth
). Мы увеличиваем его на единицу, если запускаем такой цикл.
- Фактическая переменная счетчика: У нас есть «переменная» с именем
_
, которая неявно получает текущий счетчик. Фактически, мы храним один счетчик для каждой глубины вложения (все хранятся в массиве counter
. Тогда _
- это просто еще один макрос, который извлекает правильный счетчик для текущей глубины вложения из этого массива.
- Фактический цикл for: Мы берем цикл for на части:
- Мы инициализируем счетчик для текущей глубины вложения до 0 (делается с помощью
counter[depth] = 0
).
- Шаг итерации - самая сложная часть: мы должны проверить, достиг ли конец цикла на текущей глубине вложенности. Если это так, мы должны соответствующим образом обновить глубину вложенности. Если нет, мы должны увеличить текущий счетчик глубины вложения на 1. Переменная
lastloop
равна 1, если это последняя итерация, в противном случае - 0, и мы соответствующим образом корректируем текущую глубину вложения. Основная проблема здесь заключается в том, что мы должны записать это как последовательность выражений, разделенных запятыми, что требует от нас написания всех этих условий очень не простым способом.
- «Шаг приращения» цикла for состоит только из одного присваивания, которое увеличивает соответствующий счетчик (то есть элемент
counter
правильной глубины вложения) и присваивает это значение нашей «переменной счетчика» _
.