2013-06-24 106 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