Я прохожу ответы теста от своего преподавателя, и вопрос был:
корректная реализация функции как макрос для абсолютного значения:
#define abs(x) ((x)<0 ? (-x) : (x))
#define abs(x) ((x)<0 ? -(x) : (x))
Почему второй корректен по сравнению с первым?
И почему необходимо использовать весь (). Как какой включены правила? Каждой переменной нужно ()? Спасибо.
Существуют различные смежные проблемы, которые решаются с помощью лишних скобок. Я просмотрю их один за другим:
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 добавляется только когда a положительный, а не когда отрицательный. Поэтому нам нужна скобка вокруг всего этого:
#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. Это не должно компилироваться. (Обратите внимание, что это происходит на Си++. Мне пришлось скомпилировать его с gcc вместо g++, чтобы он не скомпилировался с ошибкой "invalid lvalue in assignment")
#define abs(x) ( (x<0) ? -(x) : (x) )
int y = abs((a
Это расширяется до int y = ( ((a, который группирует
<0
с b, а не весь троичник, как предполагалось.
#define abs(x) ( ( (x) < 0) ? -(x) : (x) )
В конце концов, каждый экземпляр x
склонен к некоторой проблеме группировки, для решения которой необходимы скобки.
Общим потоком во всех этих случаях является приоритет оператора : если в вашем вызове abs(...)
поставить оператор, имеющий меньший приоритет, чем что-либо вокруг, где в макросе используется x
, то это приведет к некорректной привязке. Например, abs(a=b)
расширится до a=b<0
, что равнозначно a=(b<0)
... это не то, что имел в виду вызывающий абонент.
abs
Конечно, это неправильный способ реализации abs в любом случае... если вы не хотите использовать встроенные функции (а вы должны, потому что они будут оптимизированы для любого аппаратного обеспечения, на которое вы переносите), то это должен быть встроенный шаблон (при использовании C++) по тем же причинам, которые упоминались, когда Мейерс, Sutter, и другие обсуждают переинсталляцию функций min и max. (В других ответах также упоминалось: что происходит с abs(x++)
?)
С верхушки головы, разумной реализацией может быть:
template<typename T> inline const T abs(T const & x)
{
return ( x<0 ) ? -x : x;
}
Здесь можно не заключать в скобки, так как мы знаем, что x - это единственное значение, а не какое-то произвольное расширение из макроса.
Еще лучше, как отметил Крис Лутц в комментариях ниже, можно использовать специализацию шаблонов для вызова оптимизированных версий (abs, fabs, labs) и получить все преимущества безопасности типов, поддержки неbuiltin-типов и производительности.
#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
Да, каждая переменная должна быть заключена в круглые скобки.
Причина в том, что вы можете передавать в макрос вещи, которые не являются «хорошими», например арифметические выражения или любое выражение, которое не является отдельной переменной. Должно быть легко увидеть, что с abs (1 + 2)
расширенный - (1 + 2)
даст другой результат, чем (- 1 + 2)
. Вот почему - (x)
более правильно.
К сожалению, ни один из макросов не является безопасным, вместо этого вы должны использовать встроенную функцию для подобных вещей. Учтите:
abs (x++); // expands to ((x++) < 0 ? - (x++) : (x++))
Это явно не так с макросом, но он работал бы правильно, если бы вместо него использовалась встроенная функция.
Есть и другие проблемы с использованием макросов вместо функций. См. этот вопрос .
Изменить: учитывая, что вопрос касается C, встроенные функции могут быть доступны только в C99.