刚接触到mfc编程的人往往会被mfc target=_blank

上传人:kms****20 文档编号:40639818 上传时间:2018-05-26 格式:DOC 页数:11 大小:35.50KB
返回 下载 相关 举报
刚接触到mfc编程的人往往会被mfc target=_blank_第1页
第1页 / 共11页
刚接触到mfc编程的人往往会被mfc target=_blank_第2页
第2页 / 共11页
刚接触到mfc编程的人往往会被mfc target=_blank_第3页
第3页 / 共11页
刚接触到mfc编程的人往往会被mfc target=_blank_第4页
第4页 / 共11页
刚接触到mfc编程的人往往会被mfc target=_blank_第5页
第5页 / 共11页
点击查看更多>>
资源描述

《刚接触到mfc编程的人往往会被mfc target=_blank》由会员分享,可在线阅读,更多相关《刚接触到mfc编程的人往往会被mfc target=_blank(11页珍藏版)》请在金锄头文库上搜索。

1、刚接触到刚接触到 MFCMFC 编程的人往往会被编程的人往往会被 MFCMFC target=_blanktarget=_blank刚接触到 MFC 编程的人往往会被 MFC 向导生成的各种宏定义和预处理指令所吓倒,但是预处理和宏定义又是 C 语言的一个强大工具。使用它们可以进行简单的源代码控制,版本控制,预警或者完成一些特殊的功能。一个经典的例子使用预处理与宏定义最经典的例子莫过于加在一个头文件中以避免头文件被两次编译。试想这种的情况,有一个文件 headerfile.h它被包含在 headerfile1.h 中,同时在 headerfile2.h 中也被包含了,现在有一个 CPP 文件,i

2、mplement.cpp 包含了headerfile1.h 和 headerfile2.h:#include “headerfile1.h“#include “headerfile2.h“假设 headerfile.h 中定义了一个全局变量 iglobal 。int iglobal; 在编译的时候编译器两次编译 headerfile,也就会发现iglobal 被定义了两次,这时就会发生变量重定义的编译错误。传统的解决办法是使用#ifdef 以及#endif 来避免头文件的重复编译,在上面的例子中,只需要加上这么几行:#ifndef smartnose_2002_6_21_headerfile_

3、h#define smartnose_2002_6_21_headerfile_hint iglobal;#endif仔细的考虑上面的宏定义,会发现当编译器编译过一次headerfile.h 以后,smartnose_2002_6_21_headerfile_h 这个宏就被定义了,以后对 headerfile.h 的编译都会跳过 int iglobal 这一行。当然 smartnose_2002_6_21_headerfile_h 这个宏是可以任意定义的,但是这个宏本身不能和其它文件中定义的宏重复,所以 MFC 在自动生成的文件中总是使用一个随机产生的长度非常长的宏,但我觉得这没有必要,我建议

4、在这个宏中加入一些有意义的信息,比方作者,文件名,文件创建时间等等,因为我们有时候会忘记在注释中加入这些信息。在 VC.Net 中我们不会再看见这些宏定义了,因为在这里会普遍使用一个预处理指令:#pragma once只要在头文件的最开始加入这条指令就能够保证头文件被编译一次,这条指令实际上在 VC6 中就已经有了,但是考虑到兼容性并没有太多的使用它。源代码版本控制当我们为许多平台开发多个版本的时候预编译指令和宏定义也能够帮我们的忙。假设我们现在为 WINDOWS 和 LINUX 开发了一套软件,由于这两种系统的不同,我们不得不在程序控制源代码的版本。比方内存的分配,我们可以在 LINUX 上

5、使用标准 C 的 malloc 函数,但是我们希望在 WINDOWS 上使用 HeapAlloc API。下面的代码演示了这种情况:main().#ifdef _WINDOWS_PLATFORMHeapAlloc(5);#elsemalloc(5);#endif.当我们在 WINDOWS 平台上编译此程序的时候,只需要定义_WINDOWS_PLATFORM 这个宏,那么 HeapAlloc 这条语句就能够起作用了。这样就能够让我们在同一个文件中为不同的平台实现不同版本的代码,同时保持程序的良好结构。在许多情况下,我们还可以为一个方法使用不同的算法,然后用宏定义来针对不同的情况选择其中的一个进行

6、编译。这在 MFC 应用程序中是使用得最多的。最明显的就是文件中经常存在的#ifdef _DEBUG.some code.#endif这样的代码,这些代码在应用程序的调试版(DEBUG)中会发挥其作用。#Pragma 指令在所有的预处理指令中,#Pragma 指令可能是最复杂的了,它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作。其格式一般为#Pragma Para其中 Para 为参数,下面来看一些常用的参数。message 参数。 Message 参数是我最喜欢的一个参数,它能够在编译信息输出窗口中输出相应的信息,这对于源代码信息的控制是非常重要的。其使用方法为:#Pragma

7、 message(“消息文本“)当编译器遇到这条指令时就在编译输出窗口中将消息文本打印出来。当我们在程序中定义了许多宏来控制源代码版本的时候,我们自己有可能都会忘记有没有正确的设置这些宏,此时我们可以用这条指令在编译的时候就进行检查。假设我们希望判断自己有没有在源代码的什么地方定义了_X86 这个宏可以用下面的方法#ifdef _X86#Pragma message(“_X86 macro activated!“)#endif当我们定义了_X86 这个宏以后,应用程序在编译时就会在编译输出窗口里显示“_X86 macro activated!“。我们就不会因为不记得自己定义的一些特定的宏而抓耳

8、挠腮了。另一个使用得比较多的 pragma 参数是 code_seg。格式如:#pragma code_seg( “section-name“,“section-class“ )它能够设置程序中函数代码存放的代码段,当我们开发驱动程序的时候就会使用到它。最后一个比较常用的就是上面所说的#pragma once 指令了。VC 预定义的宏在 VC 中有一类宏并不是由用户用#define 语句定义的,而是编译器本身就能够识别它们。这些宏的作用也是相当大的。让我们来看第一个,也是 MFC 中使用得最频繁的一个:_FILE_ 。当编译器遇到这个宏时就把它展开成当前被编译文件的文件名。好了,我们马上就可以

9、想到可以用它来做什么,当应用程序发生错误时,我们可以报告这个错误发生的程序代码在哪个文件里,比方在文件 test.cpp 中有这样的代码:trychar * p=new(char10);catch(CException *e )TRACE(“ there is an error in file: %sn“,_FILE_);在程序运行的时候,如果内存分配出现了错误,那么在调试窗口中会出现 there is an error in file: test.cpp 这句话,当然,我们还可以把这个错误信息显示在别的地方。如果我们还能够记录错误发生在哪一行就好了,幸运的是,与_FILE_宏定义一样,还有一

10、个宏记录了当前代码所在的行数,这个宏是_LINE_。使用上面的两个宏,我们可以写出一个类似于 VC提供的 ASSERT 语句。下面是方法#define MyAssert(x) if(!(x) MessageBox(_FILE_,_LINE_,NULL,MB_OK);我们在应用程序中可以象使用 ASSERT 语句一样使用它,在错误发生时,它会弹出一个对话框,其标题和内容告诉了我们错误发生的文件和代码行号,方便我们的调试,这对于不能使用 ASSERT 语句的项目来说是非常有用的。除了这两个宏以外,还有记录编译时间的_TIME_,记录日期的_DATE_,以及记录文件修改时间的_TIMESTAMP_宏

11、。使用这些预定义的宏,我们几乎可以生成和 VC 能够生成的一样完整的源代码信息报表。结论翻开 MFC 和 Linux 的源代码,宏定义几乎占据了半边天,消息映射,队列操作,平台移植,版本管理,甚至内核模块的拆卸安装都用宏定义完成。毫不夸张的说,有些文件甚至就只能看见宏定义。所以学习宏定义,熟练的使用宏定义对于学习 C 语言乃至 VC 都是非常关键的。在上一篇文章中,我演示了几个常用的宏定义和预处理指令,但可以说这些都是相当常规的技巧。下面要介绍的宏定义与预处理指令的用法也是 ATL,MFC 以及 LINUX 中使用得比较多的非常重要的技巧。# 连接符与# 符# 连接符号由两个井号组成,其功能是

12、在带参数的宏定义中将两个子串(token)联接起来,从而形成一个新的子串。但它不可以是第一个或者最后一个子串。所谓的子串(token)就是指编译器能够识别的最小语法单元。具体的定义在编译原理里有详尽的解释,但不知道也无所谓。同时值得注意的是#符是把传递过来的参数当成字符串进行替代。下面来看看它们是怎样工作的。这是 MSDN 上的一个例子。假设程序中已经定义了这样一个带参数的宏:#define paster( n ) printf( “token“ #n “ = %d“, token#n )同时又定义了一个整形变量:int token9 = 9;现在在主程序中以下面的方式调用这个宏:paster

13、( 9 );那么在编译时,上面的这句话被扩展为:printf( “token“ “9“ “ = %d“, token9 );注意到在这个例子中,paster(9);中的这个“9“被原封不动的当成了一个字符串,与“token“连接在了一起,从而成为了 token9。而#n 也被“9“所替代。可想而知,上面程序运行的结果就是在屏幕上打印出 token9=9在 ATL 的编程中,我们查看它的源代码就会经常看见这样的一段:#define IMPLEMENTS_INTERFACE(Itf) _wait_event(wq,condition); while(0)这是一个奇怪的循环,它根本就只会运行一次,为

14、什么不去掉外面的 do.while 结构呢?我曾一度在心里把它叫做“怪圈“。原来这也是非常巧妙的技巧。在工程中可能经常会引起麻烦,而上面的定义能够保证这些麻烦不会出现。下面是解释:假设有这样一个宏定义#define macro(condition) if(condition) dosomething();现在在程序中这样使用这个宏:if(temp)macro(i);elsedoanotherthing();一切看起来很正常,但是仔细想想。这个宏会展开成:if(temp)if(condition) dosomething();elsedoanotherthing();这时的 else 不是与第一

15、个 if 语句匹配,而是错误的与第二个if 语句进行了匹配,编译通过了,但是运行的结果一定是错误的。为了避免这个错误,我们使用 do.while(0) 把它包裹起来,成为一个独立的语法单元,从而不会与上下文发生混淆。同时因为绝大多数的编译器都能够识别 do.while(0)这种无用的循环并进行优化,所以使用这种方法也不会导致程序的性能降低。几个小小的警告正如微软声称的一样,宏定义与预编译器指令是强大的,但是它又使得程序难以调试。所以在定义宏的时候不要节省你的字符串,一定要力争完整的描述这个宏的功能。同时在定义宏的时候如有必要(比方使用了 if 语句)就要使用 do.while(0)将它封闭起来。在宏定义的时候一定要注意各个宏之间的相互依赖关系,尽量避免这种依赖关系的存在。下面就有这样一个例子。设有一个静态数组组成的整型队列,在定义中使用了这样的方法: int array=5, 6, 7, 8;我们还需要在程序中遍历这个数组。通常的做法是使用一个宏定义#define ELE_NUM 4.for(int I=0;IELE_NUM;I+)coutarrayI;由于某种偶然的原因,我们删除了定义中的一个元素,使它变成:array=5,6,7而却忘了修改 ELE_NUM 的值。那么在上面的代码中马上就会发生访问异常,程序崩溃。然后是彻夜不眠的调试,最后发现问

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 生活休闲 > 科普知识

电脑版 |金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号