Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ЯП - ПОИТ (Бахтизин) часть 1 редакт.doc
Скачиваний:
1
Добавлен:
01.04.2025
Размер:
1.76 Mб
Скачать

11.3.2. Макросы с параметрами

Формат директивы #define, определяющей макрос с параметрами:

#define идентификатор_макроса(параметры) замещающий_текст

Предупреждение: между идентификатором макроса и открывающейся скобкой не должно быть пробела.

Вызов макроса осуществляется выражением:

идентификатор_макроса(параметры)

Макрос, определяемый директивой препроцессора #define, это символическое имя некоторого набора операторов. Как и в случае символических констант, идентификатор макроса заменяется на замещающий текст до начала компиляции программы. Но сначала в замещающий текст подставляются значения параметров, а затем уже этот расширенный макрос подставляется в текст вместо идентификатора макроса и списка его параметров.

Например, следующий макрос с одним параметром определяет площадь круга, воспринимая передаваемый в него параметр как радиус:

#define CIRC(x) (3.14 * (x) *(x))

Везде в тексте файла, где появится идентификатор CIRC(A), значение параметра A будет использовано для замещения. Например, оператор с макросом в тексте программы

S = CIRC(4);

примет вид:

S = (3.14 * (4) * (4));

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

Если вызов имеет вид

S = CIRC(a + b);

то после расширения макроса тест будет иметь вид:

S = (3.14 * (a + b) * (a + b));

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

Следует обратить внимание на круглые скобки вокруг каждого включения параметра x в тексте рассмотренного макроса и вокруг всего выражения. При вызове типа CIRC(4) они кажутся излишними. Но во втором примере вызова при отсутствии скобок расширение привело бы к оператору:

S = 3.14 * a + b * a + b;

Тогда в соответствии со старшинством операций сначала выполнилось бы умножение 3.14 * a, затем b * a, а затем результаты этих умножений сложились бы друг с другом и с b. Конечно, результат вычислений был бы неверным.

Хороший стиль программирования: При объявлении макроса заключите в скобки параметры в замещающем тексте и сам замещающий текст. Это избавит от возможных неприятностей, связанных с неверной последовательностью вычислений при расширении макроса.

Пример_1: макрос, определяющий площадь эллипса через значения его полуосей может быть определен директивой

#define Ell(x, y) (3.14 * (x) * (y))

Вызов этого макроса может иметь вид:

S = Ell(R1, R2);

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

double circ(double x)

{

return 3.14 * x * x;

}

и вызвать ее оператором:

S = circ(a + b);

Пример_2: следующим образом можно создать элемент связанного списка, используя #define:

#define PTRSTRUCT(name) typedef struct name *name##Ptr

#define STRUCT(name) struct name \

{ \

int value; \

struct name *next; \

}; \

PTRSTRUCT(name)

и работать с ним:

STRUCT(MyStruct);

MyStruct My;

MyStructPtr MyPtr = &My;

My.value = 5;

printf(“\n%d”, My.value);

printf(“\n%d”, (*MyPtr).value);

При этом на экран будет выведено:

5

5

Пример_3: использование макросов вместо стандартных функций:

#define READ(val) scanf("%d", &val)

При использовании в тексте программы READ(y);, макрос расширится до scanf("%d",&y);.

Таким образом, возникает вопрос, что выгоднее использовать: макросы или функции?

Вызов функции сопряжен с дополнительными расходами и увеличивает время выполнения программы (если программа использует функцию, то в выполняемую программу помещается только одна копия функции. Каждый раз, при вызове функции, программа помещает параметры в стек и затем выполняет переход к коду функции. После завершения функции, программа удаляет параметры из стека и переходит обратно к оператору, который следует непосредственно за вызовом функции.). Это соображение работает в пользу использования макросов. С другой стороны, макрос расширяется во всех местах текста, где используется его вызов. Если таких мест в программе много, то это увеличивает размер текста и, соответственно размер выполняемого модуля. Так что функции позволяют сократить объем выполняемого файла, а макросы – увеличить скорость выполнения. Правда, макросы тоже могут быть связаны с дополнительными накладными расходами. В приведенном примере “с вычислением площади круга” значение параметра (a + b) вычисляется дважды, в то время, как в функции это вычисление осуществляется только один раз. Конечно, для таких простых вычислений это не существенно. Но если в качестве параметра передается сложное выражение, обращающееся в свою очередь к каким-нибудь сложным функциям, то эти дополнительные накладные расходы могут стать заметными и увеличить вычисления.

Недостатком макросов является отсутствие встроенного контроля согласования типов фактических и формальных параметров. Отсутствие соответствующих предупреждений компилятора может приводить к ошибкам программы, которые трудно отлавливать. Однако это является иногда и преимуществом. Например, если необходимо создать “универсальную функцию”, подобную шаблонным функциям в C++. Опишем пример работы подобной “универсальной функции”. Создаем макрос – суммирование двух элементов:

#define SUM(x, у) ((х) + (у))

Вызываем его в главной программе, передавая макросу в качестве фактических параметров константы int, float и char типов:

printf("3 + 5 = %d\n", SUM(3, 5));

printf("3.4 + 5.7 = %0.1f\n", SUM(3.4, 5.7));

printf("’a’ + ‘0’ = ‘%c’\n", SUM(‘a’, ‘0’));

После выполнения программы на экране будет отображено следующее:

3 + 5 = 8

3.4 + 5.7 = 9.1

'a'+’0’ = 'q’

Но самый существенный недостаток макросов – возможность появления побочных эффектов, если в качестве аргумента в макрос передается некоторое выражение. Например, если описанный выше макрос CIRC, вычисляющий площадь круга, вызвать следующим образом:

S = CIRC(a++);

предполагая рассчитать площадь и затем операцией постфиксного инкремента увеличить радиус на 1. Макрос будет расширен следующим образом:

S = (3.14 * (a++) * (a++));

Теперь выведем на экран полученную площадь S и переменную a (допустим до этого a == 2):

printf(“\n%f\n%d”, S, a);

получим:

12.56

4

При этом площадь вычислена верно, но постфиксный инкремент вычислялся два раза. В результате значение радиуса a увеличилось не на 1, а на 2.

Если мы попробуем вывести следующим образом (до этого a == 2):

printf(“\n%f\n%d”, CIRC(a++), a);

то получим:

18.84

2

В этом случае один инкремент произошел прямо при подсчёте площади круга.

Если же этот макрос вызвать следующим образом:

S = CIRC(++a);

предполагая увеличить радиус на 1 и вычислить площадь круга с таким увеличенным радиусом. Макрос будет расширен следующим образом:

S = (3.14159 * (++a) * (++a));

Теперь выведем на экран полученную площадь S и переменную a (допустим до этого a == 1):

printf(“\n%f\n%d”, S, a);

получим:

28.56

3

При этом макрос расширился до:

S = (3.14159 * (a + 2) * (a + 2));

Если мы попробуем вывести следующим образом (до этого a == 1):

printf(“\n%f\n%d”, CIRC(a++), a);

то получим:

18.84

1

При этом макрос расширился до:

S = (3.14159 * (a + 1) * (a + 2));

Всех этих побочных эффектов не будет, если вместо макроса использовать описанную выше функцию circ.

При выборе реализации вычислений функцией или макросом надо обеспечивать компромисс между скоростью вычислений и затратами памяти. Для небольших функций, возможно, наилучшим решением является применение встраиваемых функций. Для них проблемы оптимальной реализации решает компилятор.

Хороший стиль программирования: Избегайте применения сложных макросов с параметрами, так как они могут приводить к нежелательным побочным эффектам. Вместо макросов в этих случаях лучше использовать встраиваемые функции inline.

Преимущества макросов с параметрами перед функциями:

1. экономия времени выполнения.

Недостатки:

2. увеличение объема программного кода;

3. неправильная интерпретация изменяющихся параметров;

4. отсутствие контроля согласования типов.