Отправляет email-рассылки с помощью сервиса Sendsay

C и C++ для начинающих

  Все выпуски  

C и C++ для начинающих Выпуск 16. Макроопределения (часть 2).


Служба Рассылок Subscribe.Ru
Архив рассылки Подписаться Статистика рассылки Форум

C и C++ для начинающих. Выпуск 15. Макроопределения - часть 2.

Доброго времени суток! Сегодня речь пойдет о макрофункциях. Тем, кто только подписался на рассылку и хочет получить архив, отсылаю к ссылке вверху.

Вы уже познакомились с макроопределениями и умеете их использовать. Правда, я забыл рассказать вам, что макрозамены не производятся в строковых константах:
 #include <stdio.h>
 #define str строка

 int main (void)
 {
  printf ("Это моя str.\n");  // Печатается "Это моя str."
  return 0;
 }

 #include <stdio.h>
 #define str "строка"

 int main (void)
 {
  printf ("Это моя %s.\n", str);  // Печатается "Это моя строка."
  return 0;
 }
Как видите, макрозамена будет произведена только во втором случае.

А теперь главная идея сегодняшнего выпуска: у макроопределения могут быть параметры. Макроопределение с таким названием называется макрофункцией или просто макросом (в дальнейшем я буду придерживаться этого термина). Как оформляется макрос? Так же, как и макроопределение, но только вслед за именем макроса перечисляются его параметры через запятую:
 #include <stdio.h>
 #define sqr(x) x*x

 int main (void)
 {
  printf ("%d * %d = %d\n", 5, 5, sqr(5)); // Печатается "Это моя строка."
  return 0;
 }
В таком случае будет произведена такая же макрозамена, но вместо параметров макроса будет подставлено их значение. То есть заместо 6-й строки будет подставлено printf ("%d * %d = %d\n", 5, 5, 5*5);. А теперь четыре правила работы с макросами:
  1. В определении макроса не должно быть разделителей (пробелов и символов табуляции. Препроцессор считает за определение первое слово после #define. Таким образом, при определенном #define sqr (x) x*x выражение sqr(5) преобразуется в (x) x*x(5) (в скобках - сама подстановка), что, разумеется, приведет к ошибке.
  2. Имена всех параметров в макроопределении необходимо заключать в скобки. В противном случае при определенном #define sqr(x) x*x выражение sqr(5+2) заменится на 5+2*5+2, что равно 17, а никак не 49.
  3. Весь текст макроса также рекомендуется заключать в скобки. Иначе при определенном #define sqr(x) (x)*(x) выражение 100/sqr(2) заменится на 100/2*2, что равно 100, а не 25, как ожидалось.
  4. Не используйте как параметр макроса операторы, изменяющие переменную (наиболее частый пример - ++ и --). В таком случае переменная увеличится (уменьшится, произойдет вызов функции или еще что-нибудь, что вы написали) столько раз, сколько эта переменная была использована в макросе. Так, при том же самом sqr(x) и определенном int i = 5; выражение sqr(i++) преобразуется в (i++)*(i++), в результате чего значение такой конструкции будет равно 5*6 = 30, а переменная i примет значение 7.
Итак, правильное определение - #define sqr(x) ((x)*(x)) .

Мощь макроса заключается в том, что в качестве его параметров может выступать что угодно: значения, переменные, имена типов, операторы. Например, мы можем объявить такой макрос:
 #include <stdlib.h>
 #define CreateArray(t,n) ((t*) malloc (n * sizeof (t))

 int main (void)
 {
  float *a;
  a = CreateArray (float, 100);
  free (p);
  return 0;
 }
Макрос CreateArray создает в памяти массив из n элементов типа t. В данном случае, как вы видите, в качестве параметров передается имя типа.

Еще несколько слов о операторах препроцессора. Как вы помните, любая директива препроцессора начинается с символа #. Этот символ в теле макроса имеет еще одно значение: это строка, содержащая переданный параметр. Поясню на примере:
 #include <stdio.h>
 #define str(x) (#x)

 int main (void)
 {
  printf ("%s", str(x != 2));  // Обратите внимание: x не определен!
  return 0;
 }
Данная программа напечатает на экране строку "x != 2". Обратите внимание: нас нисколько не интересует сам x, поскольку он используется только как часть строки.

Другми полезным оператором является оператор склейки лексем ##. Этот оператор соединяет несколько лексем в одну. Например, при определенном #define f(x,y) (x##y) выражение f(a,b) будет заменено на ab. Где это может быть использовано? Например, здесь (фрагмент исходника):
 char
  szField [64],
  szAction [64],
  szId [64],
  szVote [64],
  szRemark [4096],
  szName [64],
  szDepartment [64],
  szSignature [64],
  *pDest;

 // ...
 // вырезано за ненадобностью
 // ...

 #define SET_DECODE_DEST(x) if (!strcmp (szField, #x)) pDest = sz##x;

  SET_DECODE_DEST (Action);
  SET_DECODE_DEST (Id);
  SET_DECODE_DEST (Vote);
  SET_DECODE_DEST (Remark);
  SET_DECODE_DEST (Name);
  SET_DECODE_DEST (Department);
  SET_DECODE_DEST (Signature);

 #undef SET_DECODE_DEST
В данном случае я заменяю проверку множества условий вида:
 if (!strcmp (szField, "Action"))
  pDest = szAction;
Когда таких проверок становится слишком много, мне кажется, что лучше их писать так - проще потом переделывать, если придется.

Язык С не позволяет использовать переменное число параметров в макросах. Однако это ограничение можно легко обойти двумя путями:
 // Первый путь
 #define array(t,n,v,i) t v[n] = {i}
 #define _ ,
 array (int, 10, v, 1 _ 2 _ 3 _ 4 _ 5 _ 6 _ 7 _ 8 _ 9 _ 10);

 // Второй путь
 #define array(t,n,v,i) t v[n] = {/i/}
 array (int, 10, v, *"*/ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 /*"*);
Первый путь основан на том, что на самом деле в макрос передается 4 параметра, а после этого последний разбивается при помощи переопределенного подчеркивания. Второй основан на том, что после выполнения подстановки выражение заменится на следущее:
 int v[10] = /*"*/ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 /*"*/;
(Обратите внимание на отсутствие пробелов в выражении /i/ в определении макроса и выражениях *"*/ и /*"* в его использовании. Теперь (жирным выделена собственно выражение, переданное как параметр) кавычки оказались взятыми в комментарии, и открылись запятые, до этого объединенные строковой константой. Думаю, все оценили :-) Обе идеи не мои, а были взяты мной с RSDN, за что им огромное спасибо. Но в общем оба способа не являются гарантированно работоспособными. Поведение компилятора в этом случае сильно зависит от него самого. Оба способа проверены на Microsoft Visual C++ 6.0.

Напоследок маленькое лирическое отступление. Идеолог C++ Б. Страуструп категорически рекомендовал не использовать макросы в программах: "Пpактически каждый макpос свидетельствyет о недостатке в языке пpогpаммиpования, пpогpамме или пpогpаммисте..." Я в общем с ним не согласен. Использовать макросы в программах нужно, но делать это надо осторожно. Протестируйте макрос на реальных параметрах, а уже потом оформляйте его в #define. И помните: ошибки в макросах - одни из самых труднонаходимых в программе!

За сим на сегодня все. Домашнее задание:

16.1. Напишите макросы для вычисления модуля (абсолютной величины) числа и его знака.
16.2. Напишите макрос для объявления символьного массива и инициализации его его собственным именем.

До встречи!

Ведущий рассылки,
av

http://subscribe.ru/
E-mail: ask@subscribe.ru
Отписаться
Убрать рекламу
Рейтингуется SpyLog

В избранное