这个系列记录了前一个项目中的一些心得,《接下来一段时间的工作》列了一个目录。不过具体记录时,没有按照目录的顺序。
第一篇记一下关于宏的一些使用。
有一些(特别是纯C++)程序猿,认为宏在代码中的作用不大,尤其是使用const XXX代替宏的号召使得很多刚开始学习C或者转为C++的人放弃了使用宏。其实宏有很多用处,尤其是纯C或者嵌入式编程中,比如:
1.求一个结构体中某类型的偏移量、字节数等
2.位运算等
3.求地址、取高低位等
...
不过这些都是处理一些技术细节上的技巧,我想说的是在大型项目中如何使用宏技巧来控制代码,中间有一些方法日常写代码可会用到。
1.错综复杂的头文件包含时,用#ifdef...组合来防止头文件重复包含(略过...)
2.跨平台
不同平台下不同编译器对C/C++的标准执行的不一致,因此如果考虑到后面有跨平台项目的移植问题,那么在项目代码的基础库中最好使用宏将一些基础关键字定义
3.使用宏定义具有本项目特色的变量类型(typedef也可以)
example:
#deine int MyPrjInt
4.dll导出
大型项目多包含n个dll,各种功能块由不同的程序猿分别实现,他们向外输出dll供别人使用,所以就存在dll导出函数和导出类的问题。使用宏可以很方便的控制这些逻辑。
example:
//1.平台管理 #if defined _WINDOWS//windows平台 #define MY_PRJ_DLL_IMPORT __declspec(dllimport)//导入dll关键字 #define MY_PRJ_DLL_EXPORT __declspec(dllexport)//导出dll关键字 #define MY_PRJ_CALL __cdecl//函数调用方式 #define MY_PRJ_CALLBACK __cdecl//回调函数调用方式 #else //...可定义其他平台 #else #endif //2.dll中还是exe中? #ifdef MY_PRJ_EXPORTS #define MY_PRJ_API(Func_Return_Type) MY_PRJ_DLL_EXPORT retype MY_PRJ_CALL #else #define MY_PRJ_API(Func_Return_Type) MY_PRJ_DLL_IMPORT retype MY_PRJ_CALL #endif //3.使用时 #define _WINDOWS //#define MY_PRJ_EXPORTS MY_PRJ_API(int) Func(); //编译时转为---> //__declspec(dllexport) int __cdecl Func();
为了防止出现一些错误导致程序crash,程序猿都会在代码中加入一些用于检查的代码,比如判断指针是否为空等,但是每个程序猿写的检查代码不一样,那么就会出现千奇百怪的检查函数和宏,不方便统一管理,也不方便自动化脚本在crash后去定位和统计问题。因此有必要提供一些统一的检查代码。
example:
//定义检查宏 #define CHECK_FUNC(x) / if ((retCode = (x)) != MY_OK) / { / fprintf(stderr, "My_Func[%d] error./n", retCode); / } //定义函数 RET_CODE My_Func(); //使用检查宏并调用函数 CHECK_FUNC(My_Func());
5.将一组C接口封装为类的成员方法
对于一些C语言类型的dll导出接口,使用起来是很危险的,比如一个功能A,他本身实现时是在dll中使用面向对象的方式,但是向外提供了一组C类型的接口并要求所有使用者通过全局变量同享这个功能。那么对于N个使用者来说,获取这个全局变量并使用功能A就需要协商,90%的情况下可能会使用单件的方式,那么也就需要定义单件类并重新封装这组接口。改代码是可以实现的,但是通过宏还控制,也许更简单。
example:
//原来提供的一组C接口 MY_PRJ_API(int) Func1(); MY_PRJ_API(void) Func2(); MY_PRJ_API(double) Func3(); MY_PRJ_API(bool) Func4(); MY_PRJ_API(char*) Func5(); //定义用于封装的宏 #define BEGIN_SINGLETON(SingletonClassName)\ class SingletonClassName\ {\ public:\ static HINSTANCE m_hInstance; #define END_SINGLETON() }; //使用宏将其变为单件模式接口 BEGIN_SINGLETON(MY_SINGLETON_INTERFACE) (int) Func1(); (void) Func2(); (double) Func3(); (bool) Func4(); (char*) Func5(); END_SINGLETON()
有一些公共的操作,几乎在所有的方法中都会被执行,或者几乎在所有的方法被使用之前都需要被执行。那么首先想到的就是将这个操作封装为一个函数,在前面提到的那些位置被调用。这个当然可以,但是函数的代价就是,执行时会伴随着压栈出栈等操作,如果操作数量到达一定的量级,也是会对性能造成影响的。
另一种方法就是把这个公共的操作代码在每一个地方复制粘贴一遍......这个虽然不会存在调用时的性能问题,但是你懂的...
最好的方式就是使用宏函数来实现,由于宏是工作在编译阶段,那么好处就是:
一、编译时宏被实际的代码替换了,其实还是相当于公共的代码在每一个需要的地方直接出现了,不存在调用函数时的压栈问题了;
二、编译成二进制文件后,重复出现又怎么样呢?方正源代码只会该宏定义处有我需要的逻辑,需要对逻辑修改时也只用改那一点。
这里和第4点提到的检查函数是有一些相似之处的。
7.动态生成一些变量等
这点主要用的是#和##两个操作符
#用于在其后的变量两边添加引号,那么就可以使用这个性质来生成字符串
example:
#define GET_STRING( str ) #str char *p = GET_STRING(123456); //-----> //char *p = "123456";##用于拼接,可以用这个性质来生成变量类型
example:
//用年级、班级_编号这样一个结构来描述一个学生, //当需要生成一个变量来班级,要求使用下面的结构 struct Student { // }; Student Grade3_Class2_43; Student Grade6_Class1_5; Student Grade2_Class5_21; Student Grade4_Class1_17; //使用宏 #define GET_STUDENT(gradeNum,classNum,stuNum) Grade##gradeNum##_Class##_classNum##_##stuNum Student GET_STUDENT(3,3,25); //---> //Student Grade3_Class3_25;
下一篇介绍C++模版的一些高级使用技巧。