Урок 12.2. Опредлеление макрофункций
12.2. Опредлеление макрофункций
В программировании принято называть идентификатор, следующий после #define, макроопределением. В случае если этот идентификатор встречается в программе, он называется макровызовом, а замена препроцессором макроопределения на строку замещения называется макрорасширением.
Если непосредственно за идентификатором следует открывающая скобка, то препроцессор считает, что в программе определено макроопределение с аргументами (его также называют макрофункцией). Общая форма записи директивы #define:
#define <идент.1>(<идент.2>,...,<идент.N>) <строка подстановки> |
В этом случае <идентификатор2>,...,<идентификаторN> называются формальными параметрами макроопределения. Использование макроопределений очень просто и очень похоже на вызов функции. Чтобы использовать макроопределение, нужно указать его имя (<идентификатор1>), а затем в скобках – фактические параметры. Встретив где-либо в программе имя макроопределения с последующим списком фактических параметров, заключенным в скобки (макровызов), препроцессор заменяет его на <строку подстановки>. При этом все вхождения формальных параметров в <строку подстановки> заменяются соответствующими фактическими параметрами, исключая те, которые в <строке подстановки> заключены в кавычки. После этого, как и в случае макроопределения без параметров, строка вновь просматривается, и если в ней есть другие макроопределения, они также соответствующим образом заменяются (расширяются). И в этом случае, естественно, строки, заключенные в двойные кавычки, считаются "защищенными". Пример:
#define VOL(a,b,c) a*b*c #define PRNT(x) printf("x равен %d \n",x) void main() { int x, y, z, v; x = 3; y = 17; z = 15; v = VOL(x,y,z); PRNT(v); } |
После завершения работы препроцессора эта программа будет иметь вид:
void main(){ int x, y, z, v; x = 3; y = 17; z = 15; v = x * y * z; printf("x равен %d \n", v); } |
При макрорасширении строки PRNT(v) формальный параметр заменился на фактический только во втором случае, так как в первом случае он "защищен" кавычками.
Обратите внимание на строку v=VOL(x,y,z);. После расширения она "превратилась" в строку v=x*y*z;. Следовательно, как мы уже отмечали, препроцессор не выполняет вычислений, а только замещает строку. Таким образом, если бы мы написали v=VOL(x+3,y,z+12);, мы бы получили v=x+3*y*z+12;. Поскольку приоритет операции умножения выше, чем операции сложения, сначала будет выполнено умножение, а потом сложение. Но очевидно, что макрофункция VOL написана для перемножения своих аргументов. Для того чтобы избежать подобных ситуаций, следует каждый формальный параметр макроопределения в строке подстановки заключать в скобки:
#define VOL(a,b,c) (a)*(b)*(c) |
Тогда если мы запишем v = VOL(x+3, y, z+12);, то после работы препроцессора получим v = (x+3)*(y)*(z+12); и при работе программы вычисления будут выполняться правильно. Кроме того, рекомендуется всю строку подстановки заключать в скобки во избежание ошибок, подобных следующей: h = 15/VOL(x+3, y, z+12); . После макрорасширения эта строка преобразуется в h = 15/(x+3)*(y)*(z+12);. Так как вычисления выполняются слева направо, то сначала выполнится деление, а затем частное будет умножено на y и на (z+12), хотя ясно, что сначала имелось в виду выполнить умножение, а затем деление. Заключив строку подстановки в скобки, мы сможем избежать и этой ошибки:
#define VOL(a,b,c) ((a)*(b)*(c))
В этом случае h = 15/VOL(x+3,y,z+12); после расширения будет иметь вид h = 15/((x+3)*(y)*(z+12)); и вычисления будут производиться правильно.
На этом примере видно очень важное отличие макроопределений от функций:
при вызове функций передается значение аргумента в функцию при выполнении программы, а макровызов передает строку аргументов при компиляции программы |
(поэтому программист, используя макроопределения, не должен заботиться о совпадении типов формальных и фактических параметров. Ведь передается не значение, а строка.) Символ #, помещенный перед формальным параметром макроопределения в строке подстановки, указывает на необходимость преобразования его в строку. При макрорасширении #<формальный параметр> заменяется на "фактический параметр".
Если в приведенном выше примере программы вместо
#define PRNT(x) printf("x равен %d \n", x) //написать #define PRNT(x) printf(#x"равен %d \n", x), |
то встретив в программе PRNT(v), препроцессор заменит его на printf("v""равен %d\n", v).
Довольно часто перед программистами встает вопрос: что использовать – макроопределение или функцию? Здесь можно привести следующие соображения:
использование макроопределений увеличивает объем программы и чревато побочными эффектами; использование функций увеличивает время выполнения программы (бывает, что весьма существенно) и, кроме того, при использовании функций нужно заботиться о совпадении типов формальных и фактических параметров, чего не нужно делать при использовании макроопределений. |
Оставить комментарий