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))
基本也就以上这些了,总之就是用来简化代码,提高运行效率,实现一些常规函数无法实现的功能.总的来说,我还是比较喜欢宏的.