c++巨集定義用法
從上一次寫文章到現在已經快2個月了,為啥沒更新,因為太忙了!
每天都在為了實現一些麻煩的功能而心急火燎,總是沒辦法靜下心來寫文章.所以一直拖延.
對於巨集的一些個人用法其實早就想寫出來了,礙於時間關係一直拖到現在.
巨集在很多人看來就是一個危險的存在.不過在我看來巨集真的是一個不可或缺的好東西.之所以被嗤之以鼻,那是有很多人不會正確使用巨集,導致專案中巨集定義亂飛,各種難以理解的巨集巢狀,巨集展開編譯失敗的複雜提示資訊.
1.標識當前平臺.
因為我的專案需要在windows和linux都要能正常執行,我在windows進行開發,linux上執行.我只是不喜歡在linux上寫程式碼而已,用慣了windows的vs,不太願意再花太多功夫去習慣別的系統.而且是linux這樣的對新手不友好的系統.
先顯式定義三個平臺標識巨集
#define PLATFORM_WINDOWS 0 #define PLATFORM_LINUX 1 #define PLATFORM_ANDROID PLATFORM_LINUX // 正在執行的平臺標識 #ifdef WINDOWS #define RUN_PLATFORM PLATFORM_WINDOWS #endif #ifdef LINUX #define RUN_PLATFORM PLATFORM_LINUX #endif #ifndef RUN_PLATFORM #define RUN_PLATFORM -1 #error "wrong platform!" #endif
因為有些標頭檔案只有windows有,有些標頭檔案只有linux才有,所以就會這樣寫
#if RUN_PLATFORM == PLATFORM_WINDOWS #include#include#include#include#include#include#include#include#endif #if RUN_PLATFORM == PLATFORM_LINUX #include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#endif
2.統一windows和linux的部分操作
因為很多東西在windows和linux上的型別定義是不同的,但是用法大體是一樣的,所以為了方便,就用巨集統一起來.
#if RUN_PLATFORM == PLATFORM_WINDOWS #define MY_THREAD HANDLE #define MY_SOCKET SOCKET #define NULL_THREAD NULL #define THREAD_CALLBACK_DECLEAR(func) static DWORD WINAPI func(LPVOID args) #define THREAD_CALLBACK(class, func) DWORD WINAPI class##::##func(LPVOID args) #define CREATE_THREAD(thread, func, args) thread = CreateThread(NULL, 0, func, args, 0, NULL) #define CLOSE_THREAD(thread) / if (thread != NULL_THREAD) / { / TerminateThread(thread, 0); / CloseHandle(thread); / thread = NULL_THREAD; / } #define CLOSE_SOCKET(socket) closesocket(socket); #define CLASS_NAME(T) string(typeid(T).name()).substr(strlen("class ")) #define SPRINTF(buffer, bufferSize, ...) sprintf_s(buffer, bufferSize, __VA_ARGS__) #elif RUN_PLATFORM == PLATFORM_LINUX #define MY_THREAD pthread_t #define MY_SOCKET unsigned int #define NULL_THREAD 0 #define SOCKADDR_IN sockaddr_in #define THREAD_CALLBACK_DECLEAR(func) static void* func(void* args) #define THREAD_CALLBACK(class, func) void* class##::##func(void* args) #define CREATE_THREAD(thread, func, args) pthread_create(&thread, NULL, func, args) #define CLOSE_THREAD(thread) / if (thread != NULL_THREAD) / { / pthread_cancel(thread); / thread = NULL_THREAD; / } #define CLOSE_SOCKET(socket) close(socket); #define CLASS_NAME(T) removePreNumber(typeid(T).name()) #define SPRINTF(buffer, bufferSize, ...) sprintf(buffer, __VA_ARGS__) #endif
其實也就是執行緒,socket,sprintf的用法統一.
3.一些簡單函式的巨集,用於提高效率
這類巨集其實主要的還是為了效率考慮,一些函式本身就是為了高效執行而存在的,如果再因為呼叫函式而有一些效能上的損失,真是有點太可惜了.
雖然內聯可以達到同樣的效果.但是是否真的內聯了,還得看編譯器實現,並不是定義放到標頭檔案就是內聯.我也沒有去測試到底怎麼樣才能保證真的內聯.實在是沒有多餘的時間花在這上面了.所以為了保證沒有函式呼叫的開銷,用巨集還是最保險的.
// 設定value的指定位置pos的位元組的值為byte,並且不影響其他位元組 #define SET_BYTE(value, b, pos) value = (value & ~(0x000000FF << (8 * pos))) | (b << (8 * pos)) // 獲得value的指定位置pos的位元組的值 #define GET_BYTE(value, pos) (value & (0x000000FF > (8 * pos) #define GET_BIT(value, pos) (((value & (1 > (pos)) & 1) #define SET_BIT(value, pos, bit) value = value & ~(1 << (pos)) | ((bit) = -MathUtility::MIN_DELTA && value <= MathUtility::MIN_DELTA) #define IS_FLOAT_EQUAL(value1, value2) (IS_FLOAT_ZERO(value1 - value2)) #define IS_VECTOR3_EQUAL(vec0, vec1) (IS_FLOAT_ZERO(vec0.x - vec1.x) && IS_FLOAT_ZERO(vec0.y - vec1.y) && IS_FLOAT_ZERO(vec0.z - vec1.z)) #define IS_VECTOR2_EQUAL(vec0, vec1) (IS_FLOAT_ZERO(vec0.x - vec1.x) && IS_FLOAT_ZERO(vec0.y - vec1.y)) #define LENGTH_XY(x, y) sqrt(x * x y * y) #define LENGTH_XYZ(x, y, z) sqrt(x * x y * y z * z) #define LENGTH_2(vec) sqrt(vec.x * vec.x vec.y * vec.y) #define LENGTH_3(vec) sqrt(vec.x * vec.x vec.y * vec.y vec.z * vec.z) #define SQUARED_LENGTH_XY(x, y) (x * x y * y) #define SQUARED_LENGTH_XYZ(x, y, z) (x * x y * y z * z) #define SQUARED_LENGTH_2(vec) (vec.x * vec.x vec.y * vec.y) #define SQUARED_LENGTH_3(vec) (vec.x * vec.x vec.y * vec.y vec.z * vec.z) #define LENGTH_LESS_3(vec, length) (vec.x * vec.x vec.y * vec.y vec.z * vec.z < length * length) #define LENGTH_GREATER_3(vec, length) (vec.x * vec.x vec.y * vec.y vec.z * vec.z > length* length) #define DOT_3(v0, v1) (v0.x * v1.x v0.y * v1.y v0.z * v1.z) #define DOT_2(v0, v1) (v0.x * v1.x v0.y * v1.y) #define CROSS(v0, v1) Vector3(v1.y * v0.z - v0.y * v1.z, v1.x * v0.z - v0.x * v1.z, v1.x * v0.y - v0.x * v1.y) #define CLAMP(value, minValue, maxValue)/ {/ if (value > maxValue){value = maxValue;}/ else if (value < minValue){value = minValue;}/ } #define CLAMP_MIN(value, minValue) if (value < minValue){value = minValue;} #define CLAMP_MAX(value, maxValue) if (value > maxValue){value = maxValue;} #define CLAMP_CYCLE(value, minValue, maxValue, cycle)/ while (value < minValue)/ {/ value = cycle;/ }/ while (value > maxValue)/ {/ value -= cycle;/ } #define CLAMP_ANGLE(angle, minValue, maxValue, pi) CLAMP_CYCLE(angle, minValue, maxValue, pi * 2.0f) #define CLAMP_RADIAN_180(radian) CLAMP_ANGLE(radian, -MathUtility::MATH_PI, MathUtility::MATH_PI, MathUtility::MATH_PI) #define CLAMP_DEGREE_180(degree) CLAMP_ANGLE(degree, -180.0f, 180.0f, 180.0f); #define CLAMP_RADIAN_360(radian) CLAMP_ANGLE(radian, 0.0f, MathUtility::MATH_PI * 2.0f, MathUtility::MATH_PI) #define CLAMP_DEGREE_360(degree) CLAMP_ANGLE(degree, 0.0f, 360.0f, 180.0f) #define TO_DEGREE(radian) (radian * MathUtility::Rad2Deg) #define TO_RADIAN(degree) (degree * MathUtility::Deg2Rad) #define SATURATE(value) CLAMP(value, 0.0f, 1.0f) // 判斷value是否在minRange和maxRange之間,並且minRange和maxRange的順序不固定 #define IS_IN_RANGE(value, minRange, maxRange) (value >= MIN(minRange, maxRange) && value = minRange && value <= maxRange) #define IS_VECTOR2_IN_RANGE(value, minRange, maxRange) (IS_IN_RANGE(value.x, minRange.x, maxRange.x) && IS_IN_RANGE(value.y, minRange.y, maxRange.y)) #define IS_VECTOR2_IN_RANGE_FIXED(value, minRange, maxRange) (IS_IN_RANGE_FIXED(value.x, minRange.x, maxRange.x) && IS_IN_RANGE_FIXED(value.y, minRange.y, maxRange.y)) #define MIN(value0, value1) (value0 < value1 ? value0 : value1) #define MAX(value0, value1) (value0 > value1 ? value0 : value1) #define INVERSE_LERP(a, b, value) ((value - a) / (b - a)) #define LERP_SIMPLE(start, end, t) (start (end - start) * t) #define ABS(value) value = value > 0 ? value : -value; #define SIGN(sign, value)/ if (value > 0) {sign = 1;}/ else if (value < 0) {sign = -1;}/ else {sign = 0;} #define SWAP(value0, value1)/ auto& temp = value0;/ value0 = value1;/ value1 = temp; #define CEIL(value) ((int)(value) >= 0.0f && value > (int)(value)) ? (int)(value) 1 : (int)(value) #define CEIL_2(vec)/ vec.x = (float)(CEIL(vec.x));/ vec.y = (float)(CEIL(vec.y)); #define CEIL_3(vec)/ vec.x = (float)(CEIL(vec.x));/ vec.y = (float)(CEIL(vec.y));/ vec.z = (float)(CEIL(vec.z));
4.一些只能使用巨集來實現的功能
比如獲取當前行號,當然,這是自帶的一個巨集,可以獲取當前行號,型別是整數,只是我這裡使用#將其轉換為了一個字串,因為巨集展開也是有順序的,所以這裡只能定義兩個巨集來將__LINE__轉換為字串.
#define STR(t) #t #define LINE_STR(v) STR(v) #define _FILE_LINE_ "File : " string(__FILE__) ", Line : " LINE_STR(__LINE__)
5.簡單替換
這也許就是巨集最簡單的用法了,只是單純的替換文字.
#define FOR_I(count) for (uint i = 0; i < count; i) #define FOR_J(count) for (uint j = 0; j < count; j) #define FOR_K(count) for (uint k = 0; k < count; k) // 基礎資料型別轉字串 #define INT_TO_STRING(strBuffer, value)/ char strBuffer[16];/ intToString(strBuffer, 16, value); #define FLOAT_TO_STRING(strBuffer, value)/ char strBuffer[16];/ floatToString(strBuffer, 16, value); // 字串拼接,將str0,str1等字串拼接後放入charArray中,會覆蓋charArray中的內容 #define STRCAT2(charArray, length, str0, str1)/ charArray[0] = '/0';/ StringUtility::strcat_s(charArray, length, str0);/ StringUtility::strcat_s(charArray, length, str1); #define STRCAT3(charArray, length, str0, str1, str2)/ charArray[0] = '/0';/ StringUtility::strcat_s(charArray, length, str0);/ StringUtility::strcat_s(charArray, length, str1);/ StringUtility::strcat_s(charArray, length, str2); #define INFO(info) GameLogWrap::logInfo(info, false, true)
比如for迴圈這種東西寫多了也覺得厭煩了,所以就稍微簡化了一些
整數轉字串,避免使用堆記憶體,只使用棧記憶體,安全而且高效.
6.第4條和第5條的結合,目的還是簡化程式碼
第4條中用到了#用於將巨集引數轉換為字串,其他的還有##用於連線文字,__ARGS__用於獲取巨集引數中的可變引數列表
// 用於遍歷stl容器,要在迴圈結束後新增END #define FOREACH(iter, stl)/ auto iter = stl.begin();/ auto iter##End = stl.end();/ FOR(stl, ; iter != iter##End; iter) // 不帶記憶體合法檢測的常規記憶體申請 #define NORMAL_NEW(className, ptr, ...) / NULL; / ptr = new className(__VA_ARGS__); / if(ptr == NULL) / { / ERROR(string("can not alloc memory! ") "className : " STR(className));/ } // 生成靜態字串常量的名字 #define NAME(name) STR_##name // 宣告一個靜態字串常量 #define DECLARE_STRING(name) static const char* NAME(name) // 定義一個靜態字串常量 #define DEFINE_STRING(name) const char* StringDefine::NAME(name) = STR(name) // 建立一個訊息物件 #define PACKET(classType, packet) classType* packet = mNetServer->createPacket(packet, NAME(classType))
基本也就以上這些了,總之就是用來簡化程式碼,提高執行效率,實現一些常規函式無法實現的功能.總的來說,我還是比較喜歡巨集的.