2013-06-24 84 views
3

我正在使用一些宏,并观察一些奇怪的行为。宏的奇怪行为C/C++

我已经将PI定义为常量,然后在宏中使用它将度数转换为弧度和弧度度。为弧度工作正常,但弧度度不:当我编译和运行,我得到下面的输出

#include <cmath> 
#include <iostream> 
using namespace std; 

#define PI atan(1) * 4 
#define radians(deg) deg * PI/180 
#define degrees(rad) rad * 180/PI 

int main() 
{ 
    cout << "pi: " << PI << endl; 
    cout << "PI, in degrees: " << degrees(PI) << endl; 
    cout << "45 degrees, in rad: " << radians(45) << endl; 
    cout << "PI * 180/PI: " << (PI * 180/PI) << endl; 
    cout << "3.14159 * 180/3.14159: " << (3.14159 * 180/3.14159) << endl; 
    cout << "PI * 180/3.14159: " << (PI * 180/3.14159) << endl; 
    cout << "3.14159 * 180/PI: " << (3.14159 * 180/PI) << endl; 

    return 0; 

} 

pi: 3.14159 
PI, in degrees: 2880 
45 degrees, in rad: 0.785398 
PI * 180/PI: 2880 
3.14159 * 180/3.14159: 180 
PI * 180/3.14159: 180 
3.14159 * 180/PI: 2880 

好像

piTest.cpp我的常数PI在分子中起作用,但不是分母。我在C中观察到相同的行为。我正在运行gcc版本4.6.3

任何人都可以解释为什么我会得到这种行为吗?

+1

请注意,对于'',您应该有'M_PI'这是一个字面常量。每次使用π时,你的代码实际上会调用'atan',这会使事情减慢很多。 – Potatoswatter

+1

'#define PI 3.14159265359' –

+0

我在这里看不到gcc特有的东西。 – curiousguy

回答

9

宏是(相对简单的)文本替换。

在定义中使用括号(既封闭宏本身和宏参数):

#define PI (atan(1) * 4) 
#define radians(deg) ((deg) * PI/180) 
#define degrees(rad) ((rad) * 180/PI) 
+0

这只是宏不是一个好主意的原因之一。通过'constexpr'关键字,可以替换许多宏,从而允许更安全的类型安全代码。 – Adrian

+0

Arrrrgggg ...当然...谢谢。 – rainbowgoblin

+0

另一种替代方法是用'inline'函数替换宏。这些函数比文本替换提供更多类型的安全性。还要考虑使用** 180.0 **,注意小数点,否则编译器可能会将宏解释为整数除法而不是浮点数。 –

2

您应该对您的宏使用括号来指定优先级。除此之外,我认为在很多情况下,math.h会为你定义PI

2

宏只是文本替换没有上下文方面,所以你风得到的是:

cout << "PI, in degrees: " << atan(1) * 4 * 180/atan(1) * 4 << endl; 

注意明显缺乏第二atan(1) * 4左右括号的,导致其只能通过atan(1)划分,然后乘以4.

相反,使用内联函数和全局:

const double PI = atan(1) * 4; 
double radians(double deg) { return deg * PI/180; } 
double degrees(double rad) { return rad * 180/PI; } 
+0

如果使用C++ 11,请使用'constexpr'以允许以编译时方式使用它。即'constexpr double radians(double deg){return deg * PI/180; }'可以在编译器而不是运行时环境中进行评估。 – Adrian

+0

无意中忽略了'inline'关键字。 –

+0

我没有使用C++ 11。宏比内联函数更快,不是吗?无论如何,我不认为加速会在我的情况下显而易见,只是好奇而已。 – rainbowgoblin

1

还不错的做法:加括号所有参数:

#define radians(deg) ((deg) * PI/180) 

因为你传递作为参数的表达可能包括运营商,也是。

甚至更​​好:使用(inline-)函数而不是宏,以避免副作用的惊喜,当一个参数计算多次喜欢这里:

#define sqr(x) ((x) * (x)) 

您使用内联函数获得唯一的缺点:您可以仅为一种类型定义它们(除非您使用C++模板)

5

首先,cmath定义了M_PI,使用它。

其次,cpp宏做文本替换。这意味着,这样的:

#define PI atan(1) * 4 
a = 1/PI; 

会变成这样:C/C++编译器都有机会看到你的代码

a = 1/atan(1) * 4; 

之前,它将把它等价于:

a = (1/atan(1)) * 4; 

这不是你想要的。

你的定义应该是这样的:

#define PI (atan(1) * 4) 

,一切都应该罚款。

这并不是很奇怪的行为,但是c-preprocessor的记录良好的行为。

您应该搜索网页以寻找宏的其他陷阱。 (提示:参数传递)

+1

'M_PI'是一个扩展,而不是''或'的标准C或C++部分,所以在编写标准代码时不应该依赖它的定义。即使在某些情况下定义它的实现(如请求POSIX支持时)也可能在其他情况下(在严格模式下编译时)忽略它。 –

+0

@EricPostpischil,嗯..总是认为这是标准。但我会继续使用它,懒惰重新发明轮子。 – youdontneedtothankme

+0

我知道M_PI,但我曾经在某处读过,你必须要使用#define _USE_MATH_DEFINES(我想这只是一个Visual C++的东西),而且我担心在那里可能会定义一些可能会弄得乱七八糟的东西与我的代码中的其他东西。由于我只需要pi,我想我只是自己定义它。 – rainbowgoblin