Существуют различные связанные проблемы, которые решают дополнительные скобки. Я пройдусь по ним один за другим:
Попробуйте: int y = abs( a ) + 2
Предположим, вы используете:
#define abs(x) (x<0)?-x:x
...
int y = abs( a ) + 2
Это расширяется до int y = (a<0)?-a:a+2
. +2
привязывает только к ложному результату. 2 добавляется только тогда, когда а положительно, а не когда оно отрицательно. Поэтому нам нужно заключить в скобки все это:
#define abs(x) ( (x<0) ? -x : x )
Попробуйте: int y = abs(a+b);
Но тогда у нас может быть int y = abs(a+b)
, который расширяется до int y = ( (a+b<0) ? -a+b : a+b)
. Если a + b отрицательно, то b не отменяется, когда они добавляют для результата. Таким образом, мы должны поставить x
из -x
в скобках.
#define abs(x) ( (x<0) ? -(x) : x )
Попробуйте: int y = abs(a=b);
Это должно быть законно (хотя и плохо), но оно расширяется до int y = ( (a=b<0)?-(a=b):a=b );
, которое пытается присвоить финальный b троичной. Это не должно компилироваться. (Обратите внимание, что это происходит в C ++. Мне пришлось скомпилировать его с помощью gcc вместо g ++, чтобы увидеть, что он не скомпилируется с ошибкой «недопустимое значение lvalue в присваивании».)
#define abs(x) ( (x<0) ? -(x) : (x) )
Попробуйте: int y = abs((a<b)?a:b);
Это расширяется до int y = ( ((a<b)?a:b<0) ? -((a<b)?a:b) : (a<b)?a:b )
, который группирует <0
с b, а не весь троичный, как предполагалось.
#define abs(x) ( ( (x) < 0) ? -(x) : (x) )
В конце концов, каждый экземпляр x
подвержен некоторой групповой проблеме, для решения которой необходимы скобки.
Общая проблема: приоритет оператора
Общим потоком во всех них является приоритет оператора : если в вызове abs(...)
указан оператор с более низким приоритетом, чем что-то, где x
используется в макросе, будет связывать неправильно. Например, abs(a=b)
расширится до a=b<0
, что соответствует a=(b<0)
... это не то, что имел в виду вызывающий.
«Правильный путь» для реализации abs
Конечно, это неправильный способ реализовать abs в любом случае ... если вы не хотите использовать встроенные функции (и вам следует, потому что они будут оптимизированы для любого оборудования, на которое вы портируете), тогда это должен быть встроенным шаблоном (если используется C ++) по тем же причинам, которые были упомянуты, когда Мейерс Саттер и др. обсуждали повторную реализацию функций min и max. (Другие ответы также упоминали это: что происходит с abs(x++)
?)
Вне моей головы, разумная реализация может быть:
template<typename T> inline const T abs(T const & x)
{
return ( x<0 ) ? -x : x;
}
Здесь можно оставить скобки, поскольку мы знаем, что x - это одно значение, а не произвольное расширение из макроса.
Еще лучше, как отметил Крис Латс в комментариях ниже, вы можете использовать специализацию шаблонов для вызова оптимизированных версий (abs, fabs, labs) и получить все преимущества безопасности типов, поддержки не встроенных типов и производительность.
Тестовый код
#if 0
gcc $0 -g -ansi -std=c99 -o exe && ./exe
exit
#endif
#include <stdio.h>
#define abs1(x) (x<0)?-x:x
#define abs2(x) ((x<0)?-x:x)
#define abs3(x) ((x<0)?-(x):x)
#define abs4(x) ((x<0)?-(x):(x))
#define abs5(x) (((x)<0)?-(x):(x))
#define test(x) printf("//%30s=%d\n", #x, x);
#define testt(t,x) printf("//%15s%15s=%d\n", t, #x, x);
int main()
{
test(abs1( 1)+2)
test(abs1(-1)+2)
// abs1( 1)+2=3
// abs1(-1)+2=1
test(abs2( 1+2))
test(abs2(-1-2))
// abs2( 1+2)=3
// abs2(-1-2)=-1
int a,b;
//b = 1; testt("b= 1; ", abs3(a=b))
//b = -1; testt("b=-1; ", abs3(a=b))
// When compiled with -ansi -std=c99 options, this gives the errors:
//./so1a.c: In function 'main':
//./so1a.c:34: error: invalid lvalue in assignment
//./so1a.c:35: error: invalid lvalue in assignment
// Abs of the smaller of a and b. Should be one or two.
a=1; b=2; testt("a=1; b=2; ", abs4((a<b)?a:b))
a=2; b=1; testt("a=2; b=1; ", abs4((a<b)?a:b))
// abs4((a<b)?a:b)=-1
// abs4((a<b)?a:b)=1
test(abs5( 1)+2)
test(abs5(-1)+2)
test(abs5( 1+2))
test(abs5(-1-2))
b = 1; testt("b= 1; ", abs5(a=b))
b = -1; testt("b=-1; ", abs5(a=b))
a=1; b=2; testt("a=1; b=2; ", abs5((a<b)?a:b))
a=2; b=1; testt("a=2; b=1; ", abs5((a<b)?a:b))
}
выход
abs1( 1)+2=3
abs1(-1)+2=1
abs2( 1+2)=3
abs2(-1-2)=-1
a=1; b=2; abs4((a<b)?a:b)=-1
a=2; b=1; abs4((a<b)?a:b)=1
abs5( 1)+2=3
abs5(-1)+2=3
abs5( 1+2)=3
abs5(-1-2)=3
b= 1; abs5(a=b)=1
b=-1; abs5(a=b)=1
a=1; b=2; abs5((a<b)?a:b)=1
a=2; b=1; abs5((a<b)?a:b)=1