1. 宏常量與宏函數
C++中用#define <宏名> <字符串>
命令定義宏,在代碼中將字符串替換宏名出現的位置。定義宏的方式根據是否包含參數可以分爲兩種:
#define <宏名> <字符串>
#define PI 3.1415926
#define <宏名>(<參數列表>) <宏體>
#define A(x) x
2. 使用宏的原因?
在預處理階段的宏替換僅僅是將目標字符串替換宏名,在代碼中對宏的使用必須極其謹慎,否則很容易寫出有問題的程序。定義宏的主要有兩個場景:
- 通過宏定義常量:在常量變更時僅需要修改宏的定義而不需要修改所有使用到常量的位置
- 帶參數的宏可以減少系統調用函數的開銷:對於一些特別簡單的函數而言,函數的調用開銷不可忽視,帶參數的宏在預處理階段就進行了宏展開,提高了程序的運行效率
- 帶參數的宏可以實現模板功能
3. C++是否應該避免使用宏,如何避免使用宏?
C++原則:儘量使用
const
、enum
和inline
替換#define
的使用,防止編譯錯誤不夠明朗,同時加強編譯期間的類型檢查,提高代碼健壯性和可讀性。
3.1 使用const替換#define定義常量
程序編譯分爲預處理、編譯和鏈接三個階段。#define
是不被視爲語言的一部分,在預處理階段就會進行宏展開替換所有的宏,因此進入第二步編譯階段是如果遇到了編譯錯誤,那麼錯誤信息可能會提到3.14
而不是PI
,導致錯誤信息不夠明朗。
// 不推薦
#define PI 3.14
// 推薦
const doule Pi = 3.14;
3.2 使用enum替換#define
我們無法使用#define
創建一個class專屬常量,因爲#define
並不重視作用域。
對於class內定義常量,我們通常使用static
+const
的方式定義:
class Student {
private:
static const int num = 10;
int scores[num];
};
const int Student::num; // static 成員變量,需要進行聲明
如果不想外部獲取到class
專屬常量的內存地址,可以使用enum
的方式定義常量(因爲取一個enum
的地址是不合法的):
class Student {
private:
enum { num = 10 };
int scores[num];
};
3.3 使用inline替換#define
通常使用宏定義函數主要是出於如下考慮:
- 實現模板功能
- 減少函數調用帶來的開銷
另外一個常見的 #define
誤用情況是以它實現宏函數,它不會招致函數調用帶來的開銷,但是用 #define
編寫宏函數容易出錯,如下用宏定義寫的求最大值的函數:
#define MAX(a, b) ( { (a) > (b) ? (a) : (b); } ) // 求最大值
這般長相的宏有着太的缺點,比如在下面調用例子:
int a = 6, b = 5;
int max = MAX(a++, b);
std::cout << max << std::endl;
std::cout << a << std::endl;
輸出結果(以下結果是錯誤的):
7 // 正確的答案是 max 輸出 6
8 // 正確的答案是 a 輸出 7
要解釋出錯的原因很簡單,我們把 MAX
宏做簡單替換:
int max = ( { (a++) > (b) ? (a++) : (b); } ); // a 被累加了2次!
在上述替換後,可以發現 a 被累加了 2 次。我們可以通過改進 MAX
宏,來解決這個問題:
#define MAX(a, b) ({ \
__typeof(a) __a = (a), __b = (b); \
__a > __b ? __a : __b; \
})
簡單說明下,上述的 __typeof
可以根據變量的類型來定義一個相同類型的變量。改進後的 MAX
宏,輸出的是正確的結果,max
輸出 6,a
輸出 7。
雖然改進的後 MAX
宏,解決了問題,但是這種宏的長相就讓人困惑。我們可以用template inline
的方式,寫出短小的函數:
template<typename T>
inline T max(const T& a, const T& b)
{
return a > b? a : b;
}