Всем привет.
После того как yeputons разочаровал меня сказав, что эту задачу нельзя решить адекватным способом, и дал ссылку на полезные данные по макросам, я решил, что этим всем нужно поделиться со всеми.
Значит приступим.
Для тех, кто не в курсе, макрос — это фрагмент кода, которому дается имя. Когда это имя используется — оно заменяется на содержание макроса. Есть два типа макросов: обычные и функциональные(на английском они называются object-like и function-like, но я бы назвал их именно так).
Обычный макрос — это простой идентификатор, который будет заменен фрагментом кода. В основном используется как константа, или чтобы дать имя списку чисел и/или константным значениям других типов.
Пример 1.#define MAXN 1000
Теперь всегда когда мы будем писать MAXN
будет использоваться число 1000
.
Пример 2.#define nums 1, 2, 3
Теперь вместо слова nums
будет подставляться 1, 2, 3
Пример 3.#define mc my_class_with_very_very_long_name
Теперь вместо написания иногда очень долгих имен типов мы просто напишем mc
.
Функциональный макрос — который будет использоваться как функция. Имеет очень много возможностей.
Пример 4.#define abs(x) ((x)>=0?(x):-(x))
Теперь мы можем использовать функцию abs(x), при чем x
может быть любого типа для которого будет работать сравнение x>=0
.
Пример 5.#define max(a,b) ((a)>(b)?(a):(b))
Такой макрос можно спокойно (ну почти спокойно... См. Предупреждение 3.) использовать для нахождения максимума, и как и в прошлом примере для корректности работы необходимо, чтобы выражение (a)>(b)
имело смысл.
Предостережение 1.
Все переменные имеет смысл, а иногда даже нужно, брать во скобки, чтобы избежать проблем со старшинством операций. Например, если в качестве аргумента пойдет выражение с битовыми операциями.
Макросы могут вызывать друг друга, и в отличии от функций макрос может вызывать макрос, который описан позже него. Но с рекурсией макросы не дружат — ни с прямой, ни с непрямой.
<a name="stringification>Перевод имени переменной в строку (англ. stringification. Кто-то может перевести это слово?). Можно получить имя переменной как строку.
Пример 6.#define id(x) #x
Тут я немного о ней рассуждал. Единственное скажу — поистине магическая штука...
Конкатенация строки к имени переменной. Сразу перейдем к примеру.
Пример 7.#define get(name) (get_##name())
Очень полезно в том же самом ООП, когда методов геттеров много. Хотя по сути жизнь и без этого прекрасна.
Очень много стандартных макросов в С++. Думаю очень много внимания этому уделять не стоит. Кто хочет — почитайте тут.
Переопределение и удаление макросов. Переопределение производится новым вызовом директивы #define name
, где name
имя уже используемого макроса. Макрос можно переопределить в обычный или функциональный независимо от того, каким он был до этого. Удаление макроса используется при помощи директивы #undef name
, где name
имя макроса.
Пример 8.#define func 2
— обычный макрос, который возвращает 2#undef func
— теперь func
это обычное имя переменной#define func 5
— снова обычный макрос#define func(x) ((x)+5)
— переопределение в функциональный
Макрос может быть переопределен даже во время его использования.
Пример 9.
#define f(a,b) ((a)*(b))
...
f(2,
#undef f
#define f 3
f)
Такой код спокойно возвратит 6. Компилятор при этом будет молчать.
Макросы с переменным числом аргументов. Принцип работы примерно такой же, как и в функциях и/или шаблонах с переменным числом аргументов. Примеры тут. От себя, к сожалению, ничего не добавлю.
Предостережение 2.
Не желательно передавать аргументами макроса функции. Лучше передать ее результат. Ибо если та переменная в макросе используется больше раза — то функция вызовется очень много раз.
Пример 10.
Вспомним макрос max
и попробуем запустить такой код max(x, func(y))
. Этот код после окончательной замены будет выглядеть вот так: ((x)>(func(y))?(x):(func(y))
. Как видим func(y)
вызывается два раза. В худшем случае это может существенно повлиять на время работы.
Перевод строки в макросе. Если нужно записать макрос в нескольких строках можно использовать перевод строки \
. Думаю к этому можно обойтись и без примера.
Борьба с нежелательной точкой с запятой.
Пример 11.
Допустим у нас был такой код:
#define cnt(x, y)\
{\
y = 0;\
while (x > 0) {\
++y;\
x /= 10;\
}
Этот код узнает сколько цифр в числе x
и записывает результат в y
(кстати в этом примере скобки не нужны, так как отправь мы в этот макрос не переменную, код не будет компилироваться). Тогда, если вставить его перед else
, компилятор заругается на синтаксическую ошибку. Эта проблема решается вот так:
#define cnt(x, y)\
do {\
y = 0;\
while (x > 0) {\
++y;\
x /= 10;\
} while (0)
И теперь после него можно всегда ставить точку с запятой.
И еще несколько примеров.
Пример 12.#define forn(i,a,b) for(int i = (a); i < (b); ++i)
Самый частый макрос, для упрощения написания циклов.
Пример 13.#define sum(a,b) ((a)+(b))
Бесполезный, но прикольный макрос, для нахождения оператора +
двух переменных. Сумма для чисел, конкатенация для строк и т.д.
Надеюсь эта запись будет полезна для вас.
UPD. Предупреждение 3.
Спасибо hellman_ за пример с инкрементом.
Даже скобки не всегда могут от всего спасти. Поэтому можно либо следить за всеми переменными, или более реальный вариант — использовать такой или похожий макрос:#define max(a,b) ((___x = (a)) > (___y = (b)) ? x : y)
Но тогда переменные ___x
и ___y
придется описать сразу. И тогда вылезают проблемы, ибо ___x
и ___y
у нас статистического типа и т.д. Поэтому полноценного варианта с помощью макроса я не знаю. Но в большинстве случаев первый работает очень хорошо. Могу посоветовать просто не кидать туда никакие выражения.
про variadic макросы я лишь добавлю этот блог: дебаг-функция всегда полезна, а ты наверняка бросишься ее писать)
и еще полезно посмотреть что делает препроцессор, удобно ловить ошибки в макросах:
g++ -std=c++11 -E main.cpp
(для быстроты и "чистоты" лучше убирать все include перед запуском)С макросами нельзя быть спокойным, например
max(i++, j)
может один или два раза инкрементировать i. У этого же свойства есть проблема неоптимальности:max(heavy_func(), 0)
может вызватьheavy_func()
два раза. Такие штуки желательно писать на шаблонах, хотя и с ними есть проблемы — иногда приходится в ручную приводить тип (типаmax(1, (int)1.0)
, не помню нужно ли с int и long long).Зато на макросах можно делать удобные дебажные штуки, типа:
Вывод (210 это номер строки):
(Код можно глянуть в моих сабмитах, я его у кого-то утащил и переделал под себя, по моему из статьи про трюки C++).
Ещё, чтобы избавиться из-за проблемы перевычисления границы в цикле (типа простой
forn(i,0,strlen(s))
будет каждый раз вычислять strlen(s)), можно сохранять граничное значение в дополительной переменной:Тут VAR(x) делает переменную типа
__tmpvar__x_123
, чтобы не было коллизий.На счетmax(i++,j)
— все переменные в скобки взяты не зря.На счет
max(heavy_func(x), 0)
— в конце есть предупреждение.А на счет последнего — реально полезные макросы.
Ничем тебе скобки не помогут от того, что i++ развернется два раза
upd: нашел предупреждение про вызов 2 раза, но, кажется, его логичнее подвинуть к тому месту, где описана max()
Да, сорри, не так понял.
Это ужасно!
Единственное, имеющее смысл из всего перечисленного — использование "#" для вывода имени переменной.
Все остальное можно написать на порядок надежнее с использование шаблонов, функций, типизированных констант и лямбд: в этом случае хотя бы предупреждение компилятора при несовпадении типов будет, может и спасти от чего-нибудь.
Абсолютно непонятно, почему в примере 11 не написать бы соответствующую функцию?
И непонятно, почему бы не использовать для вычислении abs, min и max вот это:
В комментариях верно заметили про подводные камни с многократным вычислением значения у
#define abs(x) ((x)>=0?(x):-(x))
Мое личное мнение, что так писать нельзя, а за злоупотребление следует отрывать руки.
Этой записью я не хотел призвать использовать макросы всегда и везде в кодинге. Я хотел просто показать, какие функциональные бывают макросы и т.д., хотя да, согласен, чаще всего использвать их не стоит.
Если вы не сторонник частого их использования, то попробуйте написать статью о том, как реализовать все написанное без макросов.
На мой взгляд, это будет гораздо более полезная и "окультуривающая" статья. :)
Даже многими любимый FORN можно реализовать на шаблоне и лямбде, а код, его использующий, будет не сильно больше.
Не согласен. В лямбде придется явно захватывать все внешние переменные, которые будут использоваться в цикле.
P.S. Я ни в коей мере не являюсь сторонником использования макроса FORN, даже наоборот.
а как же "[&]" ?
Не могу понять, как можно FORN реализовать без макроса?
Как-то так.
Было:
Стало:
Можно пойти дальше и, например, параметризовать тип счетчика, добавить параметр "от какого значения начинать" и др.
Согласись, менее удобно использовать, чем макрос?
(Хотя я не сторонник макросов тоже, да)
Лично для меня разница в "[&](int i)" и ")" незначительна по сравнению с тем, какие проблемы можно огрести, если неудачно заиспользовать макрос.
Функциональности намного меньше чем у макроса. Например, до меня до сих пор не дошло как сделать вложеные циклы.В одной лямбде использовать параметр
i
, в другой —j
.Пойду-ка я разбираться с лямбдами...
А вот и нет: будет ошибка компиляции :)
Сходу даже и не знаю, как малой правкой заставить компилироваться.
надо сказать у меня и ваш пример не компилится. А если убрать & в определении ForN (или дописать const) — тогда компилится и мой и ваш
PS: это, конечно, запрещает модифицировать переданный callable объект, но, надеюсь, никто не собирался так делать
Да, виноват: нужно было писать "const Func&".
С такой правкой оба варианта работают.
MSVS сильно расслабляет в этом плане: позволяет временную переменную передавать в неконстантную ссылку.
Я собирался. Ну то есть, хотелось бы таки найти полное соответсвие макросу.
Слабо себе представляю эквивалентый макрос, который вы имеете ввиду.
Мы походу друг друга не поняли. Я имею ввиду написать полноценную функцию для цикла, которая будет работать в любом случае: и для вложеных циклов и т.д.
Ну я сходу не вижу вещей. которые не работают с шаболном, но работают с макросом
Ну я имею ввиду изменение callable объекта, которое работать не хочет. Или я снова что-то не так понял?
А что вы понимаете под этой фразой (в контексте решения с макросом)?
Ну насколько я понял там переменная i будет записана с const. Тогда в случаях когда ее нужно изменить мы ничего сделать не сможем. Но иногда очень даже нужно перепрыгнуть через одну итерацию, без всяких извращений.
P.S. в таком случае эта функция будет больше похожа на pascal'евский for
скорее всего, если написать
[](int& i){}
то будет работать(Хотя я плохо представляю случай, когда это нужно, если у нас все равно делается ++i)
А вот с breakом сложнее — пока не знаю, как реализовать удобнее, чем "i = МНОГО; return"
И во вложеных циклах тоже?
А почему бы нет.
Можно сделать так, чтобы "return true" из "Func" было равносильно continue, а "return false" из "Func" — break-у.
Я если нужно сделать return из функции в которой мы сейчас находимся? В main'е вроде понятно — exit, и нет проблем, а вот если где-то в функции — то уже как-то проблемы вылезают...
Да, этот пункт я не учел. Шаблон в данном случае бессилен.
Вот в этом случае макрос forn и пригодится, но по сути, сам я такие макросы не использую, и призывать кого-то их использовать не буду.
Да, правда. Хотя код начинает уже тогда прилично разрастаться и я бы скорее перешел на мое решение:)
В таком подходе есть один большой недостаток: невозможность использования операторов break и continue.
Макрос FORN используется в олимпиадном программировании для ускорения набора кода. Никаких дополнительных проблем, которых не было бы в обычной конструкции цикла
for (int i = 0; i < n; i++)
при этом не возникает.Ваша же шаблонная конструкция в написании ничем не проще стандартного цикла и в то же время, если не учитывать оптимизатор, будет работать значительно дольше.
continue = return
А вот return и break уже не ясно, как реализовывать.
для этой цели в большинстве редакторов есть сниппеты. Быстрый набор, небольшой шаблон, легко читаемый (особенно другими) код, возможность что-нибудь изменить прямо на месте.
Можно посмотреть у меня в коде почти по любой задаче. Получается гораздо длиннее, но написал 1 раз и забил
В коде вызывается примерно так:
Ну это уже больше на Python-like циклы смахивает.
Ну ими и вдохновлено, конечно.
Главное, что я вижу в макросе — написание переменной цикла 1 раз и уменьшение кол-ва пунктуации (так что не напишешь +i вместо ++i) он дает