使用宏灵活的控制代码

来源:互联网 发布:武汉禾田软件 编辑:程序博客网 时间:2024/06/05 10:54

这个系列记录了前一个项目中的一些心得,《接下来一段时间的工作》列了一个目录。不过具体记录时,没有按照目录的顺序。

第一篇记一下关于宏的一些使用。

有一些(特别是纯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_EXPORTSMY_PRJ_API(int) Func();//编译时转为--->   //__declspec(dllexport) int __cdecl Func();


4.统一定义一些Debug时的检查宏

   为了防止出现一些错误导致程序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()

6.压缩代码(生成代码)

有一些公共的操作,几乎在所有的方法中都会被执行,或者几乎在所有的方法被使用之前都需要被执行。那么首先想到的就是将这个操作封装为一个函数,在前面提到的那些位置被调用。这个当然可以,但是函数的代价就是,执行时会伴随着压栈出栈等操作,如果操作数量到达一定的量级,也是会对性能造成影响的。

另一种方法就是把这个公共的操作代码在每一个地方复制粘贴一遍......这个虽然不会存在调用时的性能问题,但是你懂的...

最好的方式就是使用宏函数来实现,由于宏是工作在编译阶段,那么好处就是: 

     一、编译时宏被实际的代码替换了,其实还是相当于公共的代码在每一个需要的地方直接出现了,不存在调用函数时的压栈问题了;

     二、编译成二进制文件后,重复出现又怎么样呢?方正源代码只会该宏定义处有我需要的逻辑,需要对逻辑修改时也只用改那一点。

这里和第4点提到的检查函数是有一些相似之处的。


7.动态生成一些变量等

这点主要用的是#和##两个操作符

#用于在其后的变量两边添加引号,那么就可以使用这个性质来生成字符串

example:

#define GET_STRING( str ) #strchar *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##_##stuNumStudent GET_STUDENT(3,3,25);//--->//Student Grade3_Class3_25;

宏的使用大致就这些,没有涉及到纯C中使用很精细的宏的介绍。

下一篇介绍C++模版的一些高级使用技巧。

原创粉丝点击