Что является самым элегантным способом петли TWICE в C

Много раз мне нужно делать вещи TWICE в цикле for. Просто я могу настроить цикл for с помощью итератора и пройти его дважды:

for (i = 0; i < 2; i++)
{
 // Do stuff
}

Теперь я заинтересован в том, чтобы делать это как ПРОСТО, как я могу, возможно, без инициализатора или итератора? Существуют ли другие, действительно простые и изящные способы достижения этого?

Ответы

Ответ 1

Как насчет этого?

void DostuffFunction(){}

for (unsigned i = 0; i < 2; ++i, DostuffFunction());

С уважением, Пабло.

Ответ 2

Это элегантно, потому что он выглядит как треугольник; и треугольники элегантны.

i = 0; 
here: dostuff(); 
i++; if ( i == 1 ) goto here;

Ответ 3

Инкапсулируйте его в функцию и вызовите дважды.

void do_stuff() {
  // Do Stuff
}

// .....

do_stuff();
do_stuff();

Примечание:, если вы используете переменные или параметры встроенной функции в логике элементов, вы можете передать их в качестве аргументов извлечен do_stuff.

Ответ 4

Если его только два раза, и вы хотите избежать цикла, просто напишите два слова.

statement1;
statement1;  // (again)

Ответ 5

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

#define TWICE for (int _index = 0; _index < 2; _index++)

Это приведет к тому, что код:

TWICE {
    // Do Stuff
}

// or

TWICE
    func();

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

Ответ 6

К сожалению, это не для C, а только для С++, но делает именно то, что вы хотите:

Просто включите заголовок, и вы можете написать что-то вроде этого:

10 times {
  // Do stuff
}

Я попробую переписать его и для C.

Ответ 7

Итак, через какое-то время, вот такой подход, который позволяет вам написать следующее в чистом 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 итераций (более высокие значения приводят к поведению undefined). Можно изменить в файле заголовка, изменив размер массива _A.
  • Разрешена только определенная глубина вложенности. Текущая реализация поддерживает глубину вложенности 10. Можно настроить путем переопределения макроса _Y.

Объяснение:

Здесь вы можете увидеть полный (= de-obfuscated) исходный код. Скажем, мы хотим разрешить до 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 на части:
    • Мы инициализируем счетчик для текущей глубины вложенности до 0 (выполняется counter[depth] = 0).
    • Шаг итерации - самая сложная часть: нам нужно проверить, достиг ли конца цикла на текущей глубине вложенности. Если это так, мы соответствующим образом обновляем глубину вложенности. Если нет, нам нужно увеличить текущий счетчик глубины вложенности на 1. Переменная lastloop равна 1, если это последняя итерация, иначе 0, и соответственно корректируем текущую глубину вложенности. Основная проблема здесь заключается в том, что мы должны написать это как последовательность выражений, разделенных запятыми, что требует от нас писать все эти условия очень не прямолинейным способом.
    • "Шаг приращения" цикла for состоит только из одного назначения, которое увеличивает соответствующий счетчик (т.е. элемент counter правильной глубины вложенности) и присваивает это значение нашей "счетной переменной" _.

Ответ 8

Что сказал Абеленки.

И если ваш { // Do stuff } является многострочным, сделайте его функцией и дважды вызовите эту функцию.

Ответ 9

Другая попытка:

for(i=2;i--;) /* Do stuff */

Это решение имеет много преимуществ:

  • кратчайшая форма возможно, претензии (13 символов)
  • Тем не менее, читаемый
  • Включает инициализацию
  • Количество повторов ( "2" ) отображается в коде
  • Может использоваться как переключатель (1 или 0) внутри тела, например. для чередования
  • Работает с одной инструкцией, телом команды или вызовом функции
  • Гибкий (не обязательно использовать только для "выполнения дважды" )
  • Соответствует Dijkstra; -)

Из комментария:

for (i=2; i--; "Do stuff");

Ответ 10

Использовать функцию:

func();
func();

Или используйте макрос (не рекомендуется):

#define DO_IT_TWICE(A) A; A

DO_IT_TWICE({ x+=cos(123); func(x); })

Ответ 11

Если ваш компилятор поддерживает это, просто поместите объявление внутри оператора for:

for (unsigned i = 0; i < 2; ++i)
{
 // Do stuff
}

Это так же изящно и эффективно, как может быть. Современные компиляторы могут делать разворот цикла и все такое, доверять им. Если вы им не доверяете, проверьте ассемблер.

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

Ответ 12

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

  i = number_of_reps;
  do
  {
    ... whatever
  } while(--i);

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

  bit rep_flag;

  rep_flag = 0;
  do
  {
    ...
  } while(rep_flag ^= 1); /* Note: if loop runs to completion, leaves rep_flag clear */

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

Ответ 13

Предполагая поддержку С++ 0x lambda:

template <typename T> void twice(T t)
{
    t();
    t();
}

twice([](){ /*insert code here*/ });

Или:

twice([]()
{ 
    /*insert code here*/ 
});

Это не поможет вам, поскольку вы хотели его для C.

Ответ 14

Как Эдсгер У. Дейкстра сам сказал: "два или более, используйте для". Не нужно быть проще.

Ответ 15

Хорошее правило: три или более, сделайте для.

Я думаю, что прочитал, что в Code Complete, но я мог ошибаться. Поэтому в вашем случае вам не нужен цикл for.

Ответ 16

Это минимально возможное без трюков препроцессора/шаблона/дублирования:

for(int i=2; i--; ) /*do stuff*/;

Обратите внимание, что декремент происходит один раз в начале, поэтому он будет зацикливаться ровно дважды с индексами 1 и 0 в соответствии с запросом.

В качестве альтернативы вы можете написать

for(int i=2; i--; /*do stuff*/) ;

Но это чисто отличие вкуса.

Ответ 17

Если то, что вы делаете, несколько сложно обернуть его в функцию и вызвать эту функцию дважды? (Это зависит от того, сколько локальных переменных использует ваш код do stuff).

Вы можете сделать что-то вроде

void do_stuff(int i){
    // do stuff
}

do_stuff(0);
do_stuff(1);

Но это может стать крайне уродливым, если вы работаете над целым рядом локальных переменных.

Ответ 18

jump инструкция довольно медленная, поэтому, если вы пишете строки один за другим, она будет работать быстрее, чем писать цикл. но современные компиляторы очень, очень умны, и оптимизация отличная (если они разрешены, конечно). если вы включили оптимизацию вашего компилятора, вам все равно, вы пишете его - с циклом или нет (:

EDIT: http://en.wikipedia.org/wiki/compiler_optimizations просто взгляните (:

Ответ 19

//dostuff
  stuff;
//dostuff (Attention I am doing the same stuff for the :**2nd** time)
  stuff;

Ответ 20

Во-первых, используйте комментарий

/* Do the following stuff twice */

то,
1) используйте цикл for 2) напишите выражение дважды, или
3) напишите функцию и вызовите функцию дважды
не используйте макросы, как было сказано ранее, макросы являются злыми.

(Мой ответ почти треугольник)

Ответ 21

Что такое элегантность? Как вы его измеряете? Кто-то платит вам за элегантность? Если да, то как они определяют преобразование доллара в элегантность?

Когда я спрашиваю себя: "Как это должно быть написано", я считаю приоритеты человека, который платит мне. Если мне платят, чтобы написать быстрый код, control-c, control-v, done. Если мне платят, чтобы написать код быстро, хорошо.. то же самое. Если мне платят за код, занимающий наименьшее количество места на экране, я сокращаю запас своего работодателя.

Ответ 22

Вблизи вашего примера, элегантный и эффективный:

for (i = 2; i; --i)
{
    /* Do stuff */
}

Вот почему я рекомендую этот подход:

  • Он инициализирует итератор количеством итераций, что делает интуитивный смысл.
  • Он использует декремент над приращением, так что выражение теста цикла сравнивается с нолем ( "i" может быть интерпретировано как "is я true?", которое в C означает "is я non-zero" ), что может оптимизировать работу на определенных архитектурах.
  • Он использует предупликацию в противоположность пост-декременту в выражении подсчета по той же причине (может оптимизироваться лучше).
  • Он использует цикл for вместо do/while или goto или XOR или переключателя или макроса или любого другого подхода, поскольку читаемость и ремонтопригодность более элегантны и важны, чем умные хаки.
  • Это не требует дублирования кода для "Делать материал", чтобы избежать цикла. Дублированный код - это мерзость и кошмар для обслуживания.

Если "Do stuff" является длинным, переместите его в функцию и дайте компилятору разрешить его, если это выгодно. Затем вызовите функцию из цикла for.

Ответ 23

void loopTwice (bool first = true)
{
    // Recursion is your friend
    if (first) {loopTwice(false);}

    // Do Stuff
    ...
}

Я уверен, что там более элегантный способ, но это просто читать и просто писать. Там может быть даже способ устранить параметр bool, но это то, к чему я пришел через 20 секунд.