命令行编译C/C++程序

最后更新于:2022-04-01 23:01:43

# VS构建工具介绍 我们都知道C/C++源代码要生成可执行的.exe程序,需要经过编译、链接的过程。你在VS工具中只需要选择菜单Build或按一下F5可以编译、链接、运行了,其实IDE帮我隐藏了好多的具体细节。 我先假设VS2010安装在以下目录中: > C:\Program Files (x86)\Microsoft Visual Studio 10.0 打开安装目录下的*VSDIR*\VC\bin可以看到一系列的可执行程序.exe和批处理文件,这些就是VS2010构建、编译、链接时要用到的工具。看一下几个主要的工具:  cl.exe:编译程序  link.exe:链接程序  lib.exe:加载lib库的程序  nmake.exe:用makefile进行构建、编译的工具 # 命令行编译程序 要在命令行(而不是VS)中编译程序,先要配制环境变量。网上有些教程说执行*VSDIR*\VC\bin\vcvars32.bat的批处理就可以了,但我执行这个批处理时会报错: > Setting environment for using Microsoft Visual Studio 2010 x86 tools. 这又是另外一个问题,我就不管了,直接手动配制环境变量把:  右键我的计算机->属性->高级系统设置->高级->环境变量,配制的环境变量(建议配制在用户的环境变量中)的值如下:  **VS2010_DIR:**  C:\Program Files (x86)\Microsoft Visual Studio 10.0  **WIN_SDK:**  C:\Program Files (x86)\Microsoft SDKs  **path:**  C:\Users\Administrator.dnx\bin;%VS2010_DIR%\VC\bin;%VS2010_DIR%\Common7\IDE  **include:**  %VS2010_DIR%\VC\include;%WIN_SDK%Windows\v7.0A\Include;  **lib:**  %VS2010_DIR%\VC\lib;%WIN_SDK%\Windows\v7.0A\Lib; ## 测试 D:\CppWorkspace\CommandTest\HelloWorld.cpp: ~~~ #include #include int main() { std::cout << "This is a native C++ program." << std::endl; printf("printf: Hello World"); return 0; } ~~~ 编译结果:  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03d759c9.jpg)  命令行中编译C/C++程序 HelloWorld.obj就是编译出的二进制文件,HelloWorld.exe就是链接成的可执行文件。 * * * ## 说明 在以上的编译过程中我们只用了cl的编译命令就帮我们最终的可执行文件HelloWorld.exe,这是因为cl.exe程序在编译时自己会去调用link.exe、lib.exe等程序。 可通过”cl -help “查看常用的编译选项 | 选项 | 作用 | | --- | --- | | /O1 | 创建小代码 | | /O2 | 创建快速代码 | | /Oa | 假设没有别名 | | /Ob | 控制内联展开 | | /Od | 禁用优化 | | /Og | 使用全局优化 | | /Oi | 生成内部函数 | 更详细的中文介绍也可参考这篇博文:  [http://www.lellansin.com/%E5%BE%AE%E8%BD%AF-cl-exe-%E7%BC%96%E8%AF%91%E5%99%A8.html](http://www.lellansin.com/%E5%BE%AE%E8%BD%AF-cl-exe-%E7%BC%96%E8%AF%91%E5%99%A8.html)
';

Property Manager的配制

最后更新于:2022-04-01 23:01:40

# 从问题入手 ## 抛出问题 在我的电脑上原本安装了VS2010专业版,现在由于项目的需要又安装了VS2015,但原来的VS2010用不了,在VS2010下编译程序时报如下错误: > 1>TRACKER : error TRK0002: Failed to execute command: “”C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin\amd64_x86\CL.exe” @C:\Users\Administrator\AppData\Local\Temp\tmp6095048feb5e4db6845129a7e84fde38.rsp”. 操作标识符不正确。 ## 解决方案 从这个错误提示中我们可以发现,用VS2010编译时用的是VS2015的编译器(Visual Studio 14.0),而且是64位的,说明是用的编译器不匹配,那如何设置回VS2010的编译器呢?  通过Google找到了如下答案:  菜单View->Property Manager->Debug|Win32->Microsoft.Cpp.Win32.user,双击它打开设置页,查看VC++ Directories->Executable Directories值,我们会发现果然设置成了VS2015的编译器,将它改了,设置成VS2010安装目录下的VC\bin目录,如我的是:C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\bin。  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03cedeb1.jpg)  Property Manager 再次编译程序,会发现这个错误没有了,完美搞定! 但Property Manager这个设置以前还真没怎么用过,它有什么功能呢?它与右键工程->Properties的属性页又有什么区别呢?于是对它进行了深一步的探索! # 深入研究 ## 概念性了解 首先,明白两个概念:Project Property和Property Sheet。  Project Property:又称项目属性,是你当前项目的属性配制,保存在你工程的配制文件中,*ProjectName*.vcxproj中。 Property Sheet:又称属性表,可用于多个工程的属性配制,可以自己创建添加属性配制,也可以使用系统默认的属性表,保存在.props为拓展名的文件中。而属性表(Property Sheet)的添加和管理就是在Property Manager中进行设置的。 ## Property Manager的使用 1. 打开Property Manager窗口。菜单View->Property Manager或View->Other Windows->Property Manager。可看到如下界面:  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03d0ea34.jpg)  Property Manager 2. 属性的设置。双击属性表名称(如Microsoft.Cpp.Win32.user)就可以对它进行设置了。你会发现它的配制项与右键工程->Properties打开的项目属性是一样的。Microsoft.Cpp.Win32.user是当前系统用户默认的属性表,保存在C:\Users\Administrator\AppData\Local\Microsoft\MSBuild\v4.0\Microsoft.Cpp.Win32.user.props中,使用这个用户名登录操作系统,用VS创建的所用项目都会继承这个属性表的配制属性。 3. 设置VC++ Directories。在这个配制项中,我们可以设置VC++编译、链接需要的各种工具和资源的路径。  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03d1f7ac.jpg) VC++ Directories Executable Directories:可执行程序(如cl.exe、link.exe、lib.exe等)的路径,一般会包含VC\bin的路径;  Include Directories:要包含的头文件(如CRT的头文件)的路径,一般会包含VC\include的路径;  Reference Directories:引用的库的路径,如MFC的库(VC\atlmfc\lib);  Libbrary Directories:要包含的lib库的路径,一般会包含VC\lib的路径;  Source Directories:源代码的路径,一般会包含VC\crt\src的路径;  Exclude Directories:不被包含文件的路径。 4. 自定义宏。在配制工程属性时你是否经常会看到一些宏,如(SolutionDir)、(Configuration)、$(OutDir)等,但你却不知道如何更改它,这些都是MSBuild为你预定义的一些宏。其实你也可以定义一些自己的宏,甚至可以重写原有的宏。方法如下:  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03d3774f.jpg) User Macros 这时你在配制自己的工程属性(如Additional Include Directories)时就能看到自己定义的宏了:  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03d49218.jpg) Use User Macros 5. 添加或导入Property Sheet。这个太简单了,直接看图:  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03d63dde.jpg)  Import and Export Property sheet  这样你可以在创建同一类型的项目时都(导入进来)使用自己定义的属性表,就可以多个项目使用相同的设置了,减少设置的负担。 ## 属性的继承关系 项目的属性是分层的。 每一层会继承前一层的值,但是继承的值可以通过设置属性来显式地重写。 下面是基本的继承关系(继承树):  1\. 来自 MSBuild CPP工具集的默认设置(..\Program Files\MSBuild\Microsoft.Cpp\v4.0\Microsoft.Cpp.Default.props,它将被 *ProjectName*.vcxproj文件导入)。  2\. Property sheets(属性表),也就是Property Manager中设置的属性表。  3\. 工程文件*ProjectName*.vcxproj .(可以重写默认值和property sheet 中的设置)。  4\. 每一项的元数据。 参考文章:[https://msdn.microsoft.com/en-us/library/669zx6zc.aspx](https://msdn.microsoft.com/en-us/library/669zx6zc.aspx)
';

incremental linking(增量链接)的作用

最后更新于:2022-04-01 23:01:38

今天编译一个C++程序时,报了一个奇怪的错误(之前是好好的): 1>LINK : fatal error LNK1123: failure during conversion to COFF: file invalid or corrupt Google上搜了一下解决方案: 把Project Properties -> Configuration Properties  -> Linker (General) -> Enable Incremental Linking中的Yes (/INCREMENTAL)改成No (/INCREMENTAL:NO) 果然管用! 但又有个新的疑问出现了:Incremental Linking是什么?它有什么作用? 大概总结一下: Incremental Linking翻译成中文就是“增量链接”,是一个链接的参数选项,作用就是为了提高链接速度的。什么意思呢?不选用增量链接时,每次修改或新增代码后进行链接时会重新洗牌,把原来的.exe删了,重新链接成一个新的.exe,这样对于大型项目来说链接会比较慢。而选用增量链接时,在对代码做小的改动时会把新成的函数或数据穿插到已有的.exe中,而不重新生成.exe,只有做了大量修改时才可能会重新编排,这样就可以提高链接的速度。 一般VS的默认设置会把Debug版的Incremental Linking设置成Yes (/INCREMENTAL),而把Release版的设置成No (/INCREMENTAL:NO)。 关于Incremental Linking更详细的介绍和分析请参考另一位博主的原文: [http://www.cnblogs.com/Dahaka/archive/2011/08/01/2124256.html](http://www.cnblogs.com/Dahaka/archive/2011/08/01/2124256.html) 为方便阅读,直接将这文章拷贝了一份: 好的,文接上回,本文我就来讲讲微软link.exe连接器的Incremental Liking这个特性。当然这个其实不是微软linker独有的特性,很多链接器都有这个特性,这个特性实际上是为了提高链接速度的。   想象一下这个场景,我写了两个函数foo()和bar(),其中foo()在0x400100处而bar()紧接着保存在0x400200处。现在我将foo()改写了一下,添加了一些perfect的功能,然后编译了新的代码。不过现在的麻烦是foo()不可避免的变大了,他现在需要200h字节来保存了。那么链接器该怎么办?   一般的思考是——重新洗牌,将现有的编译好的exe删除了,然后重新布局所有的函数,也即是说bar()函数向后挪动0x100h字节的位置,给foo()腾出空间来。然后之后所有的函数都需要重新定位……对于大型软件来说这个处理时间开销是痛苦的,但作为程序员我们却不能避免需要不断的调试改代码,不断地重复这个耗时的工作。   不过我们现在并不需要给客户最终的发行代码,我们只是想要尽快地将程序的bug改掉然后去休息而已!于是,Incremental Linking出现了!它的原理如下: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03ccdb33.gif)   现在连接器不会将所有函数紧挨着放在一块儿了,他们会在函数之间加上padding,这个时候函数要想添几句指令就有余地了。只要我们的改动不大,没有超过padding的范围连接器就不需要重新洗牌,这大大提高了链接的速度。   先别高兴,加入我们的改动很大,以至于超过padding能够搞定的范围怎么办?如上图,我们还会在整个section末尾设置一个较大的padding(当然具体在哪里要看实现,比如我这图是从GCC那里搞得,说的就是ld.exe的行为方式),这时候就可以将这个函数搬到这里来了。但有个毁灭性的问题——所有调用我这个函数的函数都必须重定位他们的call指令啊!   为了解决这个问题,我们引入了一个ILT表(Incremental Linking Table),这个表是放在.text区域中的(我在IDA中观察得知)。它的原理是什么呢?我们来看: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-10-30_5632e1b8d3b57.gif) ~~~ ;之前我们都是直接调用函数 call foo ;现在我们来点小把戏 call foo_stub foo_stub: jmp foo ~~~ ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-10-30_5632e1b8d3b57.gif)   我们现在不直接调用函数,而是call到一个包含jmp指令的地方,然后由这个指令将我们的程度带往foo()函数的实现去。现在如果我们将foo()的实现改动过大后,linker直接将foo()移动了,然后只需要修改这个jmp指令就行了。可以看到,这种实现方式开销是O(1)。然后当很多个函数都用这种方式时,就形成了一个有jmp指令构成的表——这就是ILT表啦。   有兴趣的童鞋可以做下实验,在VS2010编译一次代码,然后用IDA或者W32Dasm之类的软件可以看到两个函数之间间隔了不少距离,而这些间隔就是我们所谓padding。padding被填充以0xCCh的数据。熟悉win32汇编的朋友这时候该笑而不语了,是的,这个值就是指令INT 3。在WIndows下,执行这个指令会引发一个异常,然后程序会被终止或是回到调试器去,这当然是出于安全性考虑的。这之后如果你在前一个函数加几句话,编译后可以看到两个函数位置不变,但函数间的padding变小了。 **和.textbss的关系**   嘛,之前有篇我讨论了PE常见的section,里面提到了这个节,下面我就详细介绍一下它的作用。   首先Incremental Linking作用不仅仅是在于减少我们重新连接程序所需要的时间,他还是我们调试时能够动态改动代码的前提。不知你还记得不,在那个炎热的夏天,你正汗流浃背地在没有空调的部屋里调试C代码(咳,说远了……)你直接修改了代码,然后VS直接在调试的时候将你的改动反映到程序里去了。这就是VS在Debug模式时动态编译代码的功能。   实际上这个功能是基于Incremental Linking机制的,而且是使用的Incremental Linking的第二套方案——直接找个大的地儿把修改的函数挪过去。   但是和.textbss有啥关系?   首先我们看到,.textbss有关键字bss,这就说明实际上这个节没有占据实际的硬盘空间。然后text关键字告诉我们这里段是包含代码的,另外用工具查知这个段有可执行属性更是印证了这个观点。没有代码,那要这个节有啥用呢?   你想到了么?是的,在VS动态编译的时候,他直接将被修改的函数放到了.textbss节里,然后修改了对应的ILT表项,是他指向这个位置。   说是简单,但实际上这个过程还个细节需要注意——你把我的函数挪地儿了,要是我正在执行这个函数怎么办?实际上,在改了ILT之后立刻会做的,就是检查当前程序所有线程的TIB,如果他们的EIP指向老的函数(它们正在执行老版本函数),我们就修改EIP使其指向新版本函数的对应位置。当然,这实际上暗示了,这个工作非要在调试程序的帮助下不可了。注意动态编译的功能只在Debug版本程序下有效,Release版本是不行的,因为Release版本默认禁用Incremental Linking。 **下面是小亡的实验时间,以验证我的观点:**   我就用手头上的程序来测试。有函数CheckValidPE(),它的RVA是0x12490h(你可理解为内存地址)。我的程序地址空间部分如下:  Section        RVA      Size  .textbss      1000h     10000h  .text        11000h     7000h   可以看到这两个函数实际上是位于.text节内的。我在CheckValidPE()上下个断,可以看到: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-10-30_5632e1b8d3b57.gif) ~~~ bool PEAnalyser::checkValidPE() { 00D92490 push ebp 00D92491 mov ebp,esp 00D92493 sub esp,0FCh 00D92499 push ebx 00D9249A push esi 00D9249B push edi 00D9249C push ecx ...... ~~~ ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-10-30_5632e1b8d3b57.gif)   从这里我发觉了VS貌似从来都是随机装载PE映像的ImageBase位置的,搞得我之前一次实验满心欢喜用常用的0x400000h为基址换算了所有的RVA~~T_T。。。   我之前说了CheckValidPE()的RVA是0x12490h,这里的绝对地址是0x00D92490h,我们用0x00D92490h-0x12490h = 0xD80000h,得到两个的差值。哈哈,看来这次的映像基址被射到了0xD80000h。   我回到VS源代码视图上,对代码稍作粉饰,嗯,然后它发生了!!! ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-10-30_5632e1b8d3b57.gif) ~~~ bool PEAnalyser::checkValidPE() { 00D81000 push ebp 00D81001 mov ebp,esp 00D81003 sub esp,0FCh 00D81009 push ebx 00D8100A push esi 00D8100B push edi 00D8100C push ecx 00D8100D lea edi,[ebp-0FCh] 00D81013 mov ecx,3Fh 00D81018 mov eax,0CCCCCCCCh 00D8101D rep stos dword ptr es:[edi] ..... ~~~ ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-10-30_5632e1b8d3b57.gif)   注意看函数变到这个地址来了!!而且VS的调试程序指针也确实说明EIP被更改了。   我们来算一下,看看这个地址在哪里?用这里新的函数起始地址减去我们之前计算的的基址得到RVA = 0xD81000h - 0xD80000h = 0x1000h   回去查一下我的内存地址空间,RVA为0x1000h正是位于.textbss节的起始位置。看来我的猜测是正确的。   小结一下,关于Incremental Linking,由于他的机制所致,势必带来程序体积的臃肿以及执行的低效,但是由于我们只是在Debug程序是使用,所以问题不大。另外VS默认是在Debug是开启Incremental Linking而Release模式关闭这个特性的。这说明在Release时,我们不能够动态的改动代码了。   另外注意Incremental Linking是和**/LTCG** 选项不兼容的,你不能同时开启Incremental Linking和Link Time Code Generation,从这个角度讲,使用Incremental Linking进一步会造成程序执行效率下降。所以,我们应该在发布程序时,注意避免带上这个特性。
';

结局汇总

最后更新于:2022-04-01 23:01:36

# 感谢 这一系列文章陆陆续续写了一个月,也差不多可以告一个段落了。感谢读者们一直以来对我关注和支持!现将这一系列文章在这做一个汇总,以方便大家查阅。若是初学者,建议按顺序阅读。 # 系列文章目录 [带你玩转Visual Studio——开篇介绍](http://blog.csdn.net/luoweifu/article/details/48664887) [带你玩转Visual Studio——带你新建一个工程](http://blog.csdn.net/luoweifu/article/details/48692267) [带你玩转Visual Studio——带你了解VC++各种类型的工程](http://blog.csdn.net/luoweifu/article/details/48816605) [带你玩转Visual Studio——带你高效开发](http://blog.csdn.net/luoweifu/article/details/48852119) [带你玩转Visual Studio——带你高效管理代码](http://blog.csdn.net/luoweifu/article/details/48866717) [带你玩转Visual Studio——带你发布自己的工程库](http://blog.csdn.net/luoweifu/article/details/48895765) [带你玩转Visual Studio——带你管理多种释出版本](http://blog.csdn.net/luoweifu/article/details/48912241) [带你玩转Visual Studio——带你多工程开发](http://blog.csdn.net/luoweifu/article/details/48915347) [带你玩转Visual Studio——带你理解微软的预编译头技术](http://blog.csdn.net/luoweifu/article/details/49010627) [带你玩转Visual Studio——带你跳出坑爹的Runtime Library坑](http://blog.csdn.net/luoweifu/article/details/49055933) [带你玩转Visual Studio——带你理解多字节编码与Unicode码](http://blog.csdn.net/luoweifu/article/details/49382969) # 后续 虽然说告一个段落,但其实还有很多的内容没有写,C++是一个非常精深的语言,VS也是一个非常强大而又庞大的工具。以上写的这些是最为常用的功能,也是我在开发中经常用的部分。 在以后的工作过程中,随着经验的积累和知识的丰富,若对VC++或VS有新的更深入的理解,将会以续集的方式加在本系列的后面,如“__cdecl、__fastcall、__stdcall三种调用约定”一直想写,但自己觉得现在对它的理解还不够深入,等我对它有更深入和系统的理解时再以更优质的文章呈现给大家。欢迎大家继续关注这个专栏和这系列文章! # 续集加载中…… [带你玩转Visual Studio——incremental linking(增量链接)的作用](http://blog.csdn.net/luoweifu/article/details/49826393) [带你玩转Visual Studio——Property Manager的配制](http://blog.csdn.net/luoweifu/article/details/49836809) [带你玩转Visual Studio——命令行编译C/C++程序](http://blog.csdn.net/luoweifu/article/details/49847749) [带你玩转Visual Studio——VS2010断点进不去解决方法](http://blog.csdn.net/luoweifu/article/details/42922067)
';

字符集和字符编码(Charset & Encoding)

最后更新于:2022-04-01 23:01:33

原文:[字符集和字符编码(Charset & Encoding)](http://www.cnblogs.com/skynet/archive/2011/05/03/2035105.html) 原作者:[吴秦(Tyler)](http://www.cnblogs.com/skynet/) *——每个软件开发人员应该无条件掌握的知识!* *——Unicode伟大的创想!* 相信大家一定碰到过,打开某个网页,却显示一堆像乱码,如"бЇЯАзЪСЯ"、"�????????"?还记得HTTP中的Accept-Charset、Accept-Encoding、Accept-Language、Content-Encoding、Content-Language等消息头字段?这些就是接下来我们要探讨的。 目录: * [1.基础知识](http://www.cnblogs.com/skynet/archive/2011/05/03/2035105.html#_1.基础知识) * [2.常用字符集和字符编码](http://www.cnblogs.com/skynet/archive/2011/05/03/2035105.html#_2.常用字符集和字符编码) * [2.1\. ASCII字符集&编码](http://www.cnblogs.com/skynet/archive/2011/05/03/2035105.html#_2.1._ASCII字符集&ASCII编码) * [2.2\. GBXXXX字符集&编码](http://www.cnblogs.com/skynet/archive/2011/05/03/2035105.html#_2.2._GBXXXX字符集&编码) * [2.3\. BIG5字符集&编码](http://www.cnblogs.com/skynet/archive/2011/05/03/2035105.html#_2.3._BIG5字符集&编码) * [3.伟大的创想Unicode](http://www.cnblogs.com/skynet/archive/2011/05/03/2035105.html#_3.伟大的创想Unicode) * [3.1.UCS & UNICODE](http://www.cnblogs.com/skynet/archive/2011/05/03/2035105.html#_3.1.UCS_&_UNICODE) * [3.2.UTF-32](http://www.cnblogs.com/skynet/archive/2011/05/03/2035105.html#_3.2.UTF-32) * [3.3.UTF-16](http://www.cnblogs.com/skynet/archive/2011/05/03/2035105.html#_3.3.UTF-16) * [3.4.UTF-8](http://www.cnblogs.com/skynet/archive/2011/05/03/2035105.html#_3.4.UTF-8) * [4.Accept-Charset/Accept-Encoding/Accept-Language/Content-Type/Content-Encoding/Content-Language](http://www.cnblogs.com/skynet/archive/2011/05/03/2035105.html#_4.Accept-Charset/Accept-Encoding/Ac) * [参考文献&进一步阅读](http://www.cnblogs.com/skynet/archive/2011/05/03/2035105.html#_参考文献&进一步阅读) # 1.基础知识 计算机中储存的信息都是用二进制数表示的;而我们在屏幕上看到的英文、汉字等字符是二进制数转换之后的结果。通俗的说,按照何种规则将字符存储在计算机中,如'a'用什么表示,称为"编码";反之,将存储在计算机中的二进制数解析显示出来,称为"解码",如同密码学中的加密和解密。在解码过程中,如果使用了错误的解码规则,则导致'a'解析成'b'或者乱码。 **字符集(Charset)**:是一个系统支持的所有抽象字符的集合。字符是各种文字和符号的总称,包括各国家文字、标点符号、图形符号、数字等。 **字符编码(Character Encoding)**:是一套法则,使用该法则能够对自然语言的字符的一个集合(如字母表或音节表),与其他东西的一个集合(如号码或电脉冲)进行配对。即在符号集合与数字系统之间建立对应关系,它是信息处理的一项基本技术。通常人们用符号集合(一般情况下就是文字)来表达信息。而以计算机为基础的信息处理系统则是利用元件(硬件)不同状态的组合来存储和处理信息的。元件不同状态的组合能代表数字系统的数字,因此字符编码就是将符号转换为计算机可以接受的数字系统的数,称为数字代码。 # 2.常用字符集和字符编码 常见字符集名称:ASCII字符集、GB2312字符集、BIG5字符集、GB18030字符集、Unicode字符集等。计算机要准确的处理各种字符集文字,需要进行字符编码,以便计算机能够识别和存储各种文字。 ## 2.1\. ASCII字符集&编码 **ASCII**(**A**merican **S**tandard **C**ode for **I**nformation **I**nterchange,**美国信息交换标准代码**)是基于[拉丁字母](http://zh.wikipedia.org/wiki/%E6%8B%89%E4%B8%81%E5%AD%97%E6%AF%8D)的一套[电脑](http://zh.wikipedia.org/wiki/%E7%94%B5%E8%84%91 "电脑")[编码](http://zh.wikipedia.org/wiki/%E7%BC%96%E7%A0%81)系统。它主要用于显示[现代英语](http://zh.wikipedia.org/wiki/%E7%8F%BE%E4%BB%A3%E8%8B%B1%E8%AA%9E "现代英语"),而其扩展版本[](http://zh.wikipedia.org/wiki/EASCII)EASCII则可以勉强显示其他[西欧](http://zh.wikipedia.org/wiki/%E8%A5%BF%E6%AC%A7)[语言](http://zh.wikipedia.org/wiki/%E8%AF%AD%E8%A8%80)。它是现今最通用的单[字节](http://zh.wikipedia.org/wiki/%E5%AD%97%E8%8A%82)编码系统(但是有被[](http://zh.wikipedia.org/wiki/Unicode)Unicode追上的迹象),并等同于国际标准[](http://zh.wikipedia.org/wiki/ISO/IEC_646)**ISO/IEC 646**。 **ASCII字符集**:主要包括控制字符(回车键、退格、换行键等);可显示字符(英文大小写字符、阿拉伯数字和西文符号)。 **ASCII编码**:将ASCII字符集转换为计算机可以接受的数字系统的数的规则。使用7位(bits)表示一个字符,共128字符;但是7位编码的字符集只能支持128个字符,为了表示更多的[欧洲](http://baike.baidu.com/view/3622.htm)常用字符对ASCII进行了扩展,ASCII扩展字符集使用8位(bits)表示一个字符,共256字符。ASCII字符集映射到数字编码规则如下图所示: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03c460ec.png) 图1 ASCII编码表 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03c81840.png) 图2 扩展ASCII编码表 ASCII的最大缺点是只能显示26个基本拉丁字母、阿拉伯数字和英式标点符号,因此只能用于显示现代美国英语(而且在处理英语当中的外来词如naïve、café、élite等等时,所有重音符号都不得不去掉,即使这样做会违反拼写规则)。而EASCII虽然解决了部份西欧语言的显示问题,但对更多其他语言依然无能为力。因此现在的苹果电脑已经抛弃ASCII而转用[Unicode](http://zh.wikipedia.org/wiki/Unicode)。 ## 2.2\. GBXXXX字符集&编码 计算机发明之后的很长一段时间,只用应用于美国及西方一些发达国家,ASCII能够很好满足用户的需求。但是当天朝也有了计算机之后,为了显示中文,必须设计一套编码规则用于将汉字转换为计算机可以接受的数字系统的数。 天朝专家把那些127号之后的奇异符号们(即EASCII)取消掉,规定:一个小于127的字符的意义与原来相同,但两个大于127的字符连在一起时,就表示一个汉字,前面的一个字节(他称之为高字节)从0xA1用到 0xF7,后面一个字节(低字节)从0xA1到0xFE,这样我们就可以组合出大约7000多个简体汉字了。在这些编码里,还把数学符号、罗马希腊的 字母、日文的假名们都编进去了,连在ASCII里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的"全角"字符,而原来在127号以下的那些就叫"半角"字符了。 上述编码规则就是**GB2312**。**GB2312**或**GB2312-80**是[中国国家标准](http://zh.wikipedia.org/wiki/%E4%B8%AD%E5%8D%8E%E4%BA%BA%E6%B0%91%E5%85%B1%E5%92%8C%E5%9B%BD%E5%9B%BD%E5%AE%B6%E6%A0%87%E5%87%86 "中华人民共和国国家标准")[简体中文](http://zh.wikipedia.org/wiki/%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87)[字符集](http://zh.wikipedia.org/wiki/%E5%AD%97%E7%AC%A6%E9%9B%86 "字符集"),全称《**信息交换用汉字编码字符集·基本集**》,又称[**GB0**](http://zh.wikipedia.org/wiki/%E5%9B%BD%E5%AE%B6%E6%A0%87%E5%87%86%E4%BB%A3%E7%A0%81 "国家标准代码"),由[中国国家标准总局](http://zh.wikipedia.org/w/index.php?title=%E4%B8%AD%E5%9B%BD%E5%9B%BD%E5%AE%B6%E6%A0%87%E5%87%86%E6%80%BB%E5%B1%80&action=edit&redlink=1 "中国国家标准总局")发布,[1981年](http://zh.wikipedia.org/wiki/1981%E5%B9%B4)[5月1日](http://zh.wikipedia.org/wiki/5%E6%9C%881%E6%97%A5)实施。GB2312编码通行于中国大陆;[新加坡](http://zh.wikipedia.org/wiki/%E6%96%B0%E5%8A%A0%E5%9D%A1)等地也采用此编码。中国大陆几乎所有的中文系统和国际化的软件都支持GB2312。GB2312的出现,基本满足了汉字的计算机处理需要,它所收录的汉字已经覆盖中国大陆99.75%的使用频率。对于[人名](http://zh.wikipedia.org/wiki/%E4%BA%BA%E5%90%8D)、[古汉语](http://zh.wikipedia.org/wiki/%E5%8F%A4%E6%B1%89%E8%AF%AD "古汉语")等方面出现的[罕用字](http://zh.wikipedia.org/wiki/%E7%BD%95%E7%94%A8%E5%AD%97),GB2312不能处理,这导致了后来[GBK](http://zh.wikipedia.org/wiki/GBK)及[GB 18030](http://zh.wikipedia.org/wiki/GB_18030)汉字字符集的出现。下图是GB2312编码的开始部分(由于其非常庞大,只列举开始部分,具体可查看[GB2312简体中文编码表](http://www.knowsky.com/resource/gb2312tbl.htm)): ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03ca41d7.png) 图3 GB2312编码表的开始部分 由于[GB 2312-80](http://zh.wikipedia.org/wiki/GB_2312 "GB 2312")只收录6763个汉字,有不少汉字,如部分在GB 2312-80推出以后才简化的汉字(如"啰"),部分人名用字(如中国前总理[朱镕基](http://zh.wikipedia.org/wiki/%E6%9C%B1%E9%95%95%E5%9F%BA)的"镕"字),台湾及香港使用的[繁体字](http://zh.wikipedia.org/wiki/%E7%B9%81%E4%BD%93%E4%B8%AD%E6%96%87 "繁体中文"),[日语](http://zh.wikipedia.org/wiki/%E6%97%A5%E8%AF%AD)及[朝鲜语](http://zh.wikipedia.org/wiki/%E9%9F%93%E8%AA%9E "韩语")汉字等,并未有收录在内。于是厂商微软利用GB 2312-80未使用的编码空间,收录GB 13000.1-93全部字符制定了GBK编码。根据[微软](http://zh.wikipedia.org/wiki/%E5%BE%AE%E8%BD%AF)资料,GBK是对GB2312-80的扩展,也就是[*CP936*](http://zh.wikipedia.org/w/index.php?title=CP936&action=edit&redlink=1 "CP936")*字码表 (Code Page 936)*的扩展(之前CP936和GB 2312-80一模一样),最早实现于[Windows 95](http://zh.wikipedia.org/wiki/Windows_95)简体中文版。虽然GBK收录[GB 13000.1-93](http://zh.wikipedia.org/wiki/GB_13000 "GB 13000")的全部字符,但编码方式并不相同。GBK自身并非国家标准,只是曾由[国家技术监督局](http://zh.wikipedia.org/wiki/%E5%9B%BD%E5%AE%B6%E6%8A%80%E6%9C%AF%E7%9B%91%E7%9D%A3%E5%B1%80 "国家技术监督局")标准化司、[电子工业部](http://zh.wikipedia.org/wiki/%E7%94%B5%E5%AD%90%E5%B7%A5%E4%B8%9A%E9%83%A8 "电子工业部")科技与质量监督司公布为"技术规范指导性文件"。原始GB13000一直未被业界采用,后续国家标准[GB18030](http://zh.wikipedia.org/wiki/GB18030 "GB18030")技术上兼容GBK而非GB13000。 **GB 18030**,全称:[国家标准](http://zh.wikipedia.org/wiki/%E4%B8%AD%E5%8D%8E%E4%BA%BA%E6%B0%91%E5%85%B1%E5%92%8C%E5%9B%BD%E5%9B%BD%E5%AE%B6%E6%A0%87%E5%87%86 "中华人民共和国国家标准")GB 18030-2005《信息技术 中文编码字符集》,是[中华人民共和国](http://zh.wikipedia.org/wiki/%E4%B8%AD%E5%8D%8E%E4%BA%BA%E6%B0%91%E5%85%B1%E5%92%8C%E5%9B%BD)现时最新的内码字集,是GB 18030-2000《信息技术 信息交换用汉字编码字符集 基本集的扩充》的修订版。与[GB 2312-1980](http://zh.wikipedia.org/wiki/GB_2312 "GB 2312")完全兼容,与[GBK](http://zh.wikipedia.org/wiki/GBK)基本兼容,支持[GB 13000](http://zh.wikipedia.org/wiki/GB_13000 "GB 13000")及[Unicode](http://zh.wikipedia.org/wiki/Unicode)的全部统一汉字,共收录汉字70244个。GB 18030主要有以下特点: * 与[UTF-8](http://zh.wikipedia.org/wiki/UTF-8)相同,采用多[字节](http://zh.wikipedia.org/wiki/%E5%AD%97%E8%8A%82)编码,每个字可以由1个、2个或4个字节组成。 * 编码空间庞大,最多可定义161万个字符。 * 支持中国国内[少数民族](http://zh.wikipedia.org/wiki/%E5%B0%91%E6%95%B0%E6%B0%91%E6%97%8F)的文字,不需要动用造字区。 * 汉字收录范围包含繁体汉字以及日韩汉字 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03cb4c5d.jpg) 图4 GB18030编码总体结构 本规格的初版使中华人民共和国信息产业部电子工业标准化研究所起草,由国家质量技术监督局于[2000年](http://zh.wikipedia.org/wiki/2000%E5%B9%B4)[3月17日](http://zh.wikipedia.org/wiki/3%E6%9C%8817%E6%97%A5)发布。现行版本为国家质量监督检验总局和中国国家标准化管理委员会于[2005年](http://zh.wikipedia.org/wiki/2005%E5%B9%B4)[11月8日](http://zh.wikipedia.org/wiki/11%E6%9C%888%E6%97%A5)发布,[2006年](http://zh.wikipedia.org/wiki/2006%E5%B9%B4)[5月1日](http://zh.wikipedia.org/wiki/5%E6%9C%881%E6%97%A5)实施。此规格为在中国境内所有软件产品支持的强制规格。 ## 2.3\. BIG5字符集&编码 **Big5**,又称为**大五码**或**五大码**,是使用[繁体中文](http://zh.wikipedia.org/wiki/%E7%B9%81%E4%BD%93%E4%B8%AD%E6%96%87)(正体中文)社区中最常用的电脑[汉字](http://zh.wikipedia.org/wiki/%E6%B1%89%E5%AD%97 "汉字")[字符集](http://zh.wikipedia.org/wiki/%E5%AD%97%E7%AC%A6%E9%9B%86 "字符集")标准,共收录13,060个汉字。中文码分为[内码](http://zh.wikipedia.org/wiki/%E5%85%A7%E7%A2%BC)及[交换码](http://zh.wikipedia.org/wiki/%E4%BA%A4%E6%8F%9B%E7%A2%BC)两类,Big5属中文内码,知名的中文交换码有[CCCII](http://zh.wikipedia.org/wiki/CCCII "CCCII")、[CNS11643](http://zh.wikipedia.org/wiki/CNS11643 "CNS11643")。Big5虽普及于[台湾](http://zh.wikipedia.org/wiki/%E5%8F%B0%E7%81%A3)、[香港](http://zh.wikipedia.org/wiki/%E4%B8%AD%E5%9C%8B%E9%A6%99%E6%B8%AF "中国香港")与[澳门](http://zh.wikipedia.org/wiki/%E4%B8%AD%E5%9C%8B%E6%BE%B3%E9%96%80 "中国澳门")等繁体中文通行区,但长期以来并非当地的国家标准,而只是[**业界标准**](http://zh.wikipedia.org/wiki/De_facto "De facto")。[倚天中文系统](http://zh.wikipedia.org/wiki/%E5%80%9A%E5%A4%A9%E4%B8%AD%E6%96%87%E7%B3%BB%E7%B5%B1)、[Windows](http://zh.wikipedia.org/wiki/Windows "Windows")等主要系统的字符集都是以Big5为基准,但厂商又各自增加不同的造字与造字区,派生成多种不同版本。[2003年](http://zh.wikipedia.org/wiki/2003%E5%B9%B4),Big5被收录到CNS11643中文标准交换码的附录当中,取得了较正式的地位。这个最新版本被称为Big5-2003。 Big5码是一套[双字节字符集](http://zh.wikipedia.org/wiki/%E5%8F%8C%E5%AD%97%E8%8A%82%E5%AD%97%E7%AC%A6%E9%9B%86 "双字节字符集"),使用了双八码存储方法,以两个字节来安放一个字。第一个字节称为"高位字节",第二个字节称为"低位字节"。"高位字节"使用了0x81-0xFE,"低位字节"使用了0x40-0x7E,及0xA1-0xFE。在Big5的分区中: | **0x8140-0xA0FE**|**保留给用户自定义字符([造字](http://zh.wikipedia.org/wiki/%E9%80%A0%E5%AD%97)区)**| |--|--| | **0xA140-0xA3BF**|[**标点符号**](http://zh.wikipedia.org/wiki/%E6%A8%99%E9%BB%9E%E7%AC%A6%E8%99%9F"标点符号")**、[](http://zh.wikipedia.org/wiki/%E5%B8%8C%E8%85%8A%E5%AD%97%E6%AF%8D "希腊字母")希腊字母及特殊符号,包括在0xA259-0xA261,安放了九个[](http://zh.wikipedia.org/wiki/%E8%A8%88%E9%87%8F%E7%94%A8%E6%BC%A2%E5%AD%97)计量用汉字:兙兛兞兝兡兣嗧瓩糎。**| | **0xA3C0-0xA3FE**| **保留。此区没有开放作造字区用。**| | **0xA440-0xC67E**|[**常用汉字**](http://zh.wikipedia.org/wiki/%E5%B8%B8%E7%94%A8%E6%BC%A2%E5%AD%97)**,先按[](http://zh.wikipedia.org/wiki/%E7%AD%86%E5%8A%83 "笔划")笔划再按[](http://zh.wikipedia.org/wiki/%E5%BA%B7%E7%86%99%E5%AD%97%E5%85%B8 "康熙字典")部首排序。**| | **0xC6A1-0xC8FE**| **保留给用户自定义字符(造字区)**| | **0xC940-0xF9D5**|[**次常用汉字**](http://zh.wikipedia.org/wiki/%E6%AC%A1%E5%B8%B8%E7%94%A8%E5%AD%97 "次常用字")**,亦是先按笔划再按部首排序。**| | **0xF9D6-0xFEFE**| **保留给用户自定义字符(造字区)**| Unicode字符集&UTF编码 # 3.伟大的创想Unicode *——不得不单独说Unicode* 像天朝一样,当计算机传到世界各个国家时,为了适合当地语言和字符,设计和实现类似GB232/GBK/GB18030/BIG5的编码方案。这样各搞一套,在本地使用没有问题,一旦出现在网络中,由于不兼容,互相访问就出现了乱码现象。 为了解决这个问题,一个伟大的创想产生了——Unicode。Unicode编码系统为表达任意语言的任意字符而设计。它使用**4字节**的数字来表达每个字母、符号,或者表意文字(ideograph)。每个数字代表唯一的至少在某种语言中使用的符号。(并不是所有的数字都用上了,但是总数已经超过了65535,所以2个字节的数字是不够用的。)被几种语言共用的字符通常使用相同的数字来编码,除非存在一个在理的语源学(etymological)理由使之不这样做。不考虑这种情况的话,每个字符对应一个数字,每个数字对应一个字符。即不存在二义性。不再需要记录"模式"了。U+0041总是代表'A',即使这种语言没有'A'这个字符。 在[计算机科学](http://zh.wikipedia.org/wiki/%E9%9B%BB%E8%85%A6%E7%A7%91%E5%AD%B8 "计算机科学")领域中,**Unicode**(**统一码**、**万国码**、**单一码**、**标准万国码**)是业界的一种标准,它可以使电脑得以体现世界上数十种文字的系统。Unicode 是基于[通用字符集](http://zh.wikipedia.org/wiki/%E9%80%9A%E7%94%A8%E5%AD%97%E7%AC%A6%E9%9B%86 "通用字符集")(Universal Character Set)的标准来发展而来,并且同时也以书本的形式[[1]](http://zh.wikipedia.org/wiki/Unicode)对外发表。Unicode 还不断在扩增, 每个新版本插入更多新的字符。直至目前为止的第六版,Unicode 就已经包含了超过十万个[字符](http://zh.wikipedia.org/wiki/%E5%AD%97%E7%AC%A6)(在[2005年](http://zh.wikipedia.org/wiki/2005%E5%B9%B4),Unicode 的第十万个字符被采纳且认可成为标准之一)、一组可用以作为视觉参考的代码图表、一套编码方法与一组标准[字符编码](http://zh.wikipedia.org/wiki/%E5%AD%97%E7%AC%A6%E7%BC%96%E7%A0%81 "字符编码")、一套包含了上标字、下标字等字符特性的枚举等。Unicode 组织(The Unicode Consortium)是由一个非营利性的机构所运作,并主导 Unicode 的后续发展,其目标在于:将既有的字符编码方案以Unicode 编码方案来加以取代,特别是既有的方案在多语言环境下,皆仅有有限的空间以及不兼容的问题。 (**可以这样理解:Unicode是字符集,UTF-32/ UTF-16/ UTF-8是三种字符编码方案。**) ## 3.1.UCS & UNICODE **通用字符集**(Universal Character Set,**UCS**)是由[ISO](http://zh.wikipedia.org/wiki/%E5%9C%8B%E9%9A%9B%E6%A8%99%E6%BA%96%E5%8C%96%E7%B5%84%E7%B9%94 "国际标准化组织")制定的**ISO 10646**(或称**ISO/IEC 10646**)标准所定义的标准字符集。历史上存在两个独立的尝试创立单一字符集的组织,即国际标准化组织(ISO)和多语言软件制造商组成的[统一码联盟](http://zh.wikipedia.org/wiki/%E7%B5%B1%E4%B8%80%E7%A2%BC%E8%81%AF%E7%9B%9F)。前者开发的 ISO/IEC 10646 项目,后者开发的[统一码](http://zh.wikipedia.org/wiki/%E7%B5%B1%E4%B8%80%E7%A2%BC "统一码")项目。因此最初制定了不同的标准。 [1991年](http://zh.wikipedia.org/wiki/1991%E5%B9%B4)前后,两个项目的参与者都认识到,世界不需要两个不兼容的字符集。于是,它们开始合并双方的工作成果,并为创立一个单一编码表而协同工作。从Unicode 2.0开始,Unicode采用了与ISO 10646-1相同的字库和字码;ISO也承诺,ISO 10646将不会替超出U+10FFFF的UCS-4编码赋值,以使得两者保持一致。两个项目仍都存在,并独立地公布各自的标准。但统一码联盟和ISO/IEC JTC1/SC2都同意保持两者标准的码表兼容,并紧密地共同调整任何未来的扩展。在发布的时候,Unicode一般都会采用有关字码最常见的字型,但ISO 10646一般都尽可能采用[Century字型](http://zh.wikipedia.org/w/index.php?title=Century%E5%AD%97%E5%9E%8B&action=edit&redlink=1 "Century字型")。 ## 3.2.UTF-32 上述使用4字节的数字来表达每个字母、符号,或者表意文字(ideograph),每个数字代表唯一的至少在某种语言中使用的符号的编码方案,称为UTF-32。UTF-32又称**UCS-4**是一种将[Unicode](http://zh.wikipedia.org/wiki/Unicode)字符编码的协定,对每个字符都使用4字节。就空间而言,是非常没有效率的。 这种方法有其优点,最重要的一点就是可以在常数时间内定位字符串里的第N个字符,因为第N个字符从第4×Nth个字节开始。虽然每一个码位使用固定长定的字节看似方便,它并不如其它Unicode编码使用得广泛。 ## 3.3.UTF-16 尽管有Unicode字符非常多,但是实际上大多数人不会用到超过前65535个以外的字符。因此,就有了另外一种Unicode编码方式,叫做UTF-16(因为16位 = 2字节)。UTF-16将0–65535范围内的字符编码成2个字节,如果真的需要表达那些很少使用的"星芒层(astral plane)"内超过这65535范围的Unicode字符,则需要使用一些诡异的技巧来实现。UTF-16编码最明显的优点是它在空间效率上比UTF-32高两倍,因为每个字符只需要2个字节来存储(除去65535范围以外的),而不是UTF-32中的4个字节。并且,如果我们假设某个字符串不包含任何星芒层中的字符,那么我们依然可以在常数时间内找到其中的第N个字符,直到它不成立为止这总是一个不错的推断。其编码方法是: * 如果字符编码U小于0x10000,也就是十进制的0到65535之内,则直接使用两字节表示; * 如果字符编码U大于0x10000,由于UNICODE编码范围最大为0x10FFFF,从0x10000到0x10FFFF之间 共有0xFFFFF个编码,也就是需要20个bit就可以标示这些编码。用U'表示从0-0xFFFFF之间的值,将其前 10 bit作为高位和16 bit的数值0xD800进行 逻辑or 操作,将后10 bit作为低位和0xDC00做 逻辑or 操作,这样组成的 4个byte就构成了U的编码。 对于UTF-32和UTF-16编码方式还有一些其他不明显的缺点。不同的计算机系统会以不同的顺序保存字节。这意味着字符U+4E2D在UTF-16编码方式下可能被保存为4E 2D或者2D 4E,这取决于该系统使用的是大尾端(big-endian)还是小尾端(little-endian)。(对于UTF-32编码方式,则有更多种可能的字节排列。)只要文档没有离开你的计算机,它还是安全的——同一台电脑上的不同程序使用相同的字节顺序(byte order)。但是当我们需要在跨系统之间传输这个文档的时候,也许在万维网中,我们就需要一种方法来指示当前我们的字节是怎样存储的。不然的话,接收文档的计算机就无法知道这两个字节4E 2D表达的到底是U+4E2D还是U+2D4E。 为了解决这个问题,多字节的Unicode编码方式定义了一个"字节顺序标记(Byte Order Mark)",它是一个特殊的非打印字符,你可以把它包含在文档的开头来指示你所使用的字节顺序。对于UTF-16,字节顺序标记是U+FEFF。如果收到一个以字节FF FE开头的UTF-16编码的文档,你就能确定它的字节顺序是单向的(one way)的了;如果它以FE FF开头,则可以确定字节顺序反向了。 ## 3.4.UTF-8 **UTF-8**(8-bit Unicode Transformation Format)是一种针对[Unicode](http://zh.wikipedia.org/wiki/Unicode)的可变长度[字符编码](http://zh.wikipedia.org/wiki/%E5%AD%97%E7%AC%A6%E7%BC%96%E7%A0%81 "字符编码")([定长码](http://zh.wikipedia.org/w/index.php?title=%E5%AE%9A%E9%95%BF%E7%A0%81&action=edit&redlink=1 "定长码")),也是一种[前缀码](http://zh.wikipedia.org/w/index.php?title=%E5%89%8D%E7%BC%80%E7%A0%81&action=edit&redlink=1 "前缀码")。它可以用来表示Unicode标准中的任何字符,且其编码中的第一个[字节](http://zh.wikipedia.org/wiki/%E5%AD%97%E8%8A%82 "字节")仍与[ASCII](http://zh.wikipedia.org/wiki/ASCII)兼容,这使得原来处理ASCII字符的[软件](http://zh.wikipedia.org/wiki/%E8%BB%9F%E9%AB%94 "软件")无须或只须做少部份修改,即可继续使用。因此,它逐渐成为[电子邮件](http://zh.wikipedia.org/wiki/%E9%9B%BB%E5%AD%90%E9%83%B5%E4%BB%B6 "电子邮件")、[网页](http://zh.wikipedia.org/wiki/%E7%B6%B2%E9%A0%81)及其他[存储](http://zh.wikipedia.org/wiki/%E5%84%B2%E5%AD%98%E8%A3%9D%E7%BD%AE "存储设备")或传送文字的应用中,优先采用的编码。[互联网工程工作小组](http://zh.wikipedia.org/wiki/%E7%B6%B2%E9%9A%9B%E7%B6%B2%E8%B7%AF%E5%B7%A5%E7%A8%8B%E5%B7%A5%E4%BD%9C%E5%B0%8F%E7%B5%84 "互联网工程工作小组")(IETF)要求所有[互联网](http://zh.wikipedia.org/wiki/%E7%B6%B2%E9%9A%9B%E7%B6%B2%E8%B7%AF "互联网")[协议](http://zh.wikipedia.org/wiki/%E7%BD%91%E7%BB%9C%E5%8D%8F%E8%AE%AE "网络协议")都必须支持UTF-8编码。 UTF-8使用一至四个[字节](http://zh.wikipedia.org/wiki/%E5%AD%97%E8%8A%82 "字节")为每个字符编码: 1. 128个US-ASCII字符只需一个字节编码(Unicode范围由U+0000至U+007F)。 2. 带有[附加符号](http://zh.wikipedia.org/wiki/%E9%99%84%E5%8A%A0%E7%AC%A6%E5%8F%B7)的[拉丁文](http://zh.wikipedia.org/wiki/%E6%8B%89%E4%B8%81%E6%96%87 "拉丁文")、[希腊文](http://zh.wikipedia.org/wiki/%E5%B8%8C%E8%87%98%E6%96%87 "希腊文")、[西里尔字母](http://zh.wikipedia.org/wiki/%E8%A5%BF%E9%87%8C%E7%88%BE%E5%AD%97%E6%AF%8D "西里尔字母")、[亚美尼亚语](http://zh.wikipedia.org/wiki/%E4%BA%9E%E7%BE%8E%E5%B0%BC%E4%BA%9E%E8%AA%9E "亚美尼亚语")、[希伯来文](http://zh.wikipedia.org/wiki/%E5%B8%8C%E4%BC%AF%E4%BE%86%E6%96%87 "希伯来文")、[阿拉伯文](http://zh.wikipedia.org/wiki/%E9%98%BF%E6%8B%89%E4%BC%AF%E6%96%87 "阿拉伯文")、[叙利亚文](http://zh.wikipedia.org/wiki/%E5%8F%99%E5%88%A9%E4%BA%9A%E6%96%87 "叙利亚文")及[它拿字母](http://zh.wikipedia.org/wiki/%E5%AE%83%E6%8B%BF%E5%AD%97%E6%AF%8D)则需要二个字节编码(Unicode范围由U+0080至U+07FF)。 3. 其他[基本多文种平面](http://zh.wikipedia.org/wiki/%E5%9F%BA%E6%9C%AC%E5%A4%9A%E6%96%87%E7%A8%AE%E5%B9%B3%E9%9D%A2)(BMP)中的字符(这包含了大部分常用字)使用三个字节编码。 4. 其他极少使用的Unicode[辅助平面](http://zh.wikipedia.org/wiki/%E8%BC%94%E5%8A%A9%E5%B9%B3%E9%9D%A2 "辅助平面")的字符使用四字节编码。 在处理经常会用到的ASCII字符方面非常有效。在处理扩展的拉丁字符集方面也不比UTF-16差。对于中文字符来说,比UTF-32要好。同时,由位操作的天性使然,使用UTF-8不再存在字节顺序的问题了,一份以utf-8编码的文档在不同的计算机之间是一样的比特流(在这一条上你得相信我,因为我不打算给你展示它的数学原理)。 总体来说,在Unicode字符串中不可能由码点数量决定显示它所需要的长度,或者显示字符串之后在文本缓冲区中光标应该放置的位置;组合字符、变宽字体、不可打印字符和从右至左的文字都是其归因。所以尽管在UTF-8字符串中字符数量与码点数量的关系比UTF-32更为复杂,在实际中很少会遇到有不同的情形。 **优点** * UTF-8是ASCII的一个[超集](http://zh.wikipedia.org/wiki/%E8%B6%85%E9%9B%86 "超集")。因为一个纯ASCII字符串也是一个合法的UTF-8字符串,所以现存的ASCII文本不需要转换。为传统的扩展ASCII字符集设计的软件通常可以不经修改或很少修改就能与UTF-8一起使用。 * 使用标准的面向字节的排序例程对UTF-8排序将产生与基于Unicode代码点排序相同的结果。(尽管这只有有限的有用性,因为在任何特定语言或文化下都不太可能有仍可接受的文字排列顺序。) * UTF-8和UTF-16都是[可扩展标记语言](http://zh.wikipedia.org/wiki/%E5%8F%AF%E6%89%A9%E5%B1%95%E6%A0%87%E8%AE%B0%E8%AF%AD%E8%A8%80 "可扩展标记语言")文档的标准编码。所有其它编码都必须通过显式或文本声明来指定。 * 任何[面向字节](http://zh.wikipedia.org/w/index.php?title=%E9%9D%A2%E5%90%91%E5%AD%97%E8%8A%82&action=edit&redlink=1 "面向字节")的[字符串搜索算法](http://zh.wikipedia.org/wiki/%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%90%9C%E7%B4%A2%E7%AE%97%E6%B3%95)都可以用于UTF-8的数据(只要输入仅由完整的UTF-8字符组成)。但是,对于包含字符记数的正则表达式或其它结构必须小心。 * UTF-8字符串可以由一个简单的算法可靠地识别出来。就是,一个字符串在任何其它编码中表现为合法的UTF-8的可能性很低,并随字符串长度增长而减小。举例说,字符值C0,C1,F5至FF从来没有出现。为了更好的可靠性,可以使用正则表达式来统计非法过长和替代值(可以查看[W3 FAQ: Multilingual Forms](http://www.w3.org/International/questions/qa-forms-utf-8)上的验证UTF-8字符串的正则表达式)。 **缺点** 因为每个字符使用不同数量的字节编码,所以寻找串中第N个字符是一个O(N)复杂度的操作 — 即,串越长,则需要更多的时间来定位特定的字符。同时,还需要位变换来把字符编码成字节,把字节解码成字符。 # 4.Accept-Charset/Accept-Encoding/Accept-Language/Content-Type/Content-Encoding/Content-Language 在HTTP中,与字符集和字符编码相关的消息头是Accept-Charset/Content-Type,另外主要区分Accept-Charset/Accept-Encoding/Accept-Language/Content-Type/Content-Encoding/Content-Language: Accept-Charset:浏览器申明自己接收的字符集,这就是本文前面介绍的各种字符集和字符编码,如gb2312,utf-8(通常我们说Charset包括了相应的字符编码方案); Accept-Encoding:浏览器申明自己接收的编码方法,通常指定压缩方法,是否支持压缩,支持什么压缩方法(gzip,deflate),(注意:这不是只字符编码); Accept-Language:浏览器申明自己接收的语言。语言跟字符集的区别:中文是语言,中文有多种字符集,比如big5,gb2312,gbk等等; Content-Type:WEB服务器告诉浏览器自己响应的对象的类型和字符集。例如:Content-Type: text/html; charset='gb2312' Content-Encoding:WEB服务器表明自己使用了什么压缩方法(gzip,deflate)压缩响应中的对象。例如:Content-Encoding:gzip Content-Language:WEB服务器告诉浏览器自己响应的对象的语言。 # 参考文献&进一步阅读 1. 百度百科. 字符集. [http://baike.baidu.com/view/51987.htm](http://baike.baidu.com/view/51987.htm), 2010-12-28 2. 维基百科. 字符编码. [http://zh.wikipedia.org/wiki/%E5%AD%97%E7%AC%A6%E7%BC%96%E7%A0%81](http://zh.wikipedia.org/wiki/%E5%AD%97%E7%AC%A6%E7%BC%96%E7%A0%81), 2011-1-5 3. 维基百科. ASCII. [http://zh.wikipedia.org/wiki/ASCII](http://zh.wikipedia.org/wiki/ASCII), 2011-4-5 4. 维基百科. GB2312. [http://zh.wikipedia.org/wiki/GB_2312](http://zh.wikipedia.org/wiki/GB_2312), 2011-3-17 5. 维基百科. GB18030. [http://zh.wikipedia.org/wiki/GB_18030](http://zh.wikipedia.org/wiki/GB_18030), 2010-3-10 6. 维基百科. GBK. [http://zh.wikipedia.org/wiki/GBK](http://zh.wikipedia.org/wiki/GBK), 2011-3-7 7. 维基百科. Unicode. [http://zh.wikipedia.org/wiki/Unicode](http://zh.wikipedia.org/wiki/Unicode), 2011-4-30 8. Laruence. 字符编码详解(基础). [http://www.laruence.com/2009/08/22/1059.html](http://www.laruence.com/2009/08/22/1059.html), 2009-8-22 9. Jan Hunt. Character Sets and Encoding for Web Designers - UCS/UNICODE.[http://www.uninetnews.com/other_standards/charset.php](http://www.uninetnews.com/other_standards/charset.php)
';

带你理解多字节编码与Unicode码

最后更新于:2022-04-01 23:01:31

上一篇文章[带你玩转Visual Studio——带你跳出坑爹的Runtime Library坑](http://blog.csdn.net/luoweifu/article/details/49055933#comments)帮我们理解了Windows中的各种类型C/C++运行时库及它的来龙去脉,这是C++开发中特别容易误入歧途的一个地方,我们对它进行了总结和归纳。本篇文章我们将继续讲解C++开发中容易混淆的另一个概念——多字节字符集与Unicode字符集。 # 多字节字符与宽字节字符 ## char与wchar_t 我们知道C++基本数据类型中表示字符的有两种:char、wchar_t。  char叫**多字节字符**,一个char占一个字节,之所以叫多字节字符是因为它表示一个**字**时可能是一个字节也可能是多个字节。一个英文字符(如’s’)用一个char(一个字节)表示,一个中文汉字(如’中’)用3个char(三个字节)表示,看下面的例子。 ~~~ void TestChar() { char ch1 = 's'; // 正确 cout << "ch1:" << ch1 << endl; char ch2 = '中'; // 错误,一个char不能完整存放一个汉字信息 cout << "ch2:" << ch2 << endl; char str[4] = "中"; //前三个字节存放汉字'中',最后一个字节存放字符串结束符\0 cout << "str:" << str << endl; //char str2[2] = "国"; // 错误:'str2' : array bounds overflow //cout << str2 << endl; } ~~~ 结点如下: > ch1:s  > ch2:  > str:中 wchar_t被称为**宽字符**,一个wchar_t占2个字节。之所以叫宽字符是因为所有的字都要用两个字节(即一个wchar_t)来表示,不管是英文还是中文。看下面的例子: ~~~ void TestWchar_t() { wcout.imbue(locale("chs")); // 将wcout的本地化语言设置为中文 wchar_t wch1 = L's'; // 正确 wcout << "wch1:" << wch1 << endl; wchar_t wch2 = L'中'; // 正确,一个汉字用一个wchar_t表示 wcout << "wch2:" << wch2 << endl; wchar_t wstr[2] = L"中"; // 前两个字节(前一个wchar_t)存放汉字'中',最后两个字节(后一个wchar_t)存放字符串结束符\0 wcout << "wstr:" << wstr << endl; wchar_t wstr2[3] = L"中国"; wcout << "wstr2:" << wstr2 << endl; } ~~~ 结果如下: > ch1:s  > ch2:中  > str:中  > str2:中国 **说明:**  1\. 用常量字符给wchar_t变量赋值时,前面要加L。如: wchar_t wch2 = L’中’;  2\. 用常量字符串给wchar_t数组赋值时,前面要加L。如: wchar_t wstr2[3] = L”中国”;  3\. 如果不加L,对于英文可以正常,但对于非英文(如中文)会出错。 ## string与wstring 字符数组可以表示一个字符串,但它是一个定长的字符串,我们在使用之前必须知道这个数组的长度。为方便字符串的操作,STL为我们定义好了字符串的类string和wstring。大家对string肯定不陌生,但wstring可能就用的少了。 string是普通的多字节版本,是基于char的,对char数组进行的一种封装。 wstring是Unicode版本,是基于wchar_t的,对wchar_t数组进行的一种封装。 ### string 与 wstring的相关转换: 以下的两个方法是跨平台的,可在Windows下使用,也可在Linux下使用。 ~~~ #include #include #include // wstring => string std::string WString2String(const std::wstring& ws) { std::string strLocale = setlocale(LC_ALL, ""); const wchar_t* wchSrc = ws.c_str(); size_t nDestSize = wcstombs(NULL, wchSrc, 0) + 1; char *chDest = new char[nDestSize]; memset(chDest,0,nDestSize); wcstombs(chDest,wchSrc,nDestSize); std::string strResult = chDest; delete []chDest; setlocale(LC_ALL, strLocale.c_str()); return strResult; } // string => wstring std::wstring String2WString(const std::string& s) { std::string strLocale = setlocale(LC_ALL, ""); const char* chSrc = s.c_str(); size_t nDestSize = mbstowcs(NULL, chSrc, 0) + 1; wchar_t* wchDest = new wchar_t[nDestSize]; wmemset(wchDest, 0, nDestSize); mbstowcs(wchDest,chSrc,nDestSize); std::wstring wstrResult = wchDest; delete []wchDest; setlocale(LC_ALL, strLocale.c_str()); return wstrResult; } ~~~ # 字符集(Charcater Set)与字符编码(Encoding) **字符集(Charcater Set或Charset):**是一个系统支持的所有抽象字符的集合,也就是一系列字符的集合。字符是各种文字和符号的总称,包括各国家文字、标点符号、图形符号、数字等。常见的字符集有:ASCII字符集、GB2312字符集(主要用于处理中文汉字)、GBK字符集(主要用于处理中文汉字)、Unicode字符集等。 **字符编码(Character Encoding):**是一套法则,使用该法则能够对自然语言的字符的一个字符集(如字母表或音节表),与计算机能识别的二进制数字进行配对。即它能在符号集合与数字系统之间建立对应关系,是信息处理的一项基本技术。通常人们用符号集合(一般情况下就是文字)来表达信息,而计算机的信息处理系统则是以二进制的数字来存储和处理信息的。字符编码就是将符号转换为计算机能识别的二进制编码。 一般一个字符集等同于一个编码方式,ANSI体系(ANSI是一种字符代码,为使计算机支持更多语言,通常使用 0x80~0xFF 范围的 2 个字节来表示 1 个字符)的字符集如ASCII、ISO 8859-1、GB2312、GBK等等都是如此。一般我们说一种编码都是针对某一特定的字符集。  一个字符集上也可以有多种编码方式,例如UCS字符集(也是Unicode使用的字符集)上有UTF-8、UTF-16、UTF-32等编码方式。 从计算机字符编码的发展历史角度来看,大概经历了三个阶段:  第一个阶段:ASCII字符集和ASCII编码。  计算机刚开始只支持英语(即拉丁字符),其它语言不能够在计算机上存储和显示。ASCII用一个字节(Byte)的7位(bit)表示一个字符,第一位置0。后来为了表示更多的欧洲常用字符又对ASCII进行了扩展,又有了EASCII,EASCII用8位表示一个字符,使它能多表示128个字符,支持了部分西欧字符。 第二个阶段:ANSI编码(本地化)  为使计算机支持更多语言,通常使用 0x80~0xFF 范围的 2 个字节来表示 1 个字符。比如:汉字 ‘中’ 在中文操作系统中,使用 [0xD6,0xD0] 这两个字节存储。  不同的国家和地区制定了不同的标准,由此产生了 GB2312, BIG5, JIS 等各自的编码标准。这些使用 2 个字节来代表一个字符的各种汉字延伸编码方式,称为 ANSI 编码。在简体中文系统下,ANSI 编码代表 GB2312 编码,在日文操作系统下,ANSI 编码代表 JIS 编码。  不同 ANSI 编码之间互不兼容,当信息在国际间交流时,无法将属于两种语言的文字,存储在同一段 ANSI 编码的文本中。 第三个阶段:UNICODE(国际化)  为了使国际间信息交流更加方便,国际组织制定了 UNICODE 字符集,为各种语言中的每一个字符设定了统一并且唯一的数字编号,以满足跨语言、跨平台进行文本转换、处理的要求。UNICODE 常见的有三种编码方式:UTF-8(1个字节表示)、UTF-16((2个字节表示))、UTF-32(4个字节表示)。 我们可以用一个树状图来表示由ASCII发展而来的各个字符集和编码的分支:  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03be61c3.jpg)  图 1: 各种类型的编译 如果要更详细地了解字符集和字符编码请参考:  [字符集和字符编码(Charset & Encoding)](http://blog.csdn.net/luoweifu/article/details/49385121) # 工程里多字节与宽字符的配制 右键你的工程名->Properties,设置如下:  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03c07b6b.jpg)  图 2: Character Set 1. 当设置为Use Unicode Character Set时,会有预编译宏:_UNICODE、UNICODE  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03c19657.jpg) 图 3: Unicode 2. 当设置为Use Multi-Byte Character Set时,会有预编译宏:_MBCS  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03c2dc57.jpg) 图 4: Multi-Byte ## Unicode Character Set与Multi-Byte Character Set有什么区别呢? Unicode Character Set和Multi-Byte Character Set这两个设置有什么区别呢?我们来看一个例子:  有一个程序需要用MessageBox弹出提示框: ~~~ #include "windows.h" void TestMessageBox() { ::MessageBox(NULL, "这是一个测试程序!", "Title", MB_OK); } ~~~ 上面这个Demo非常简单不用多说了吧!我们将Character Set设置为Multi-Byte Character Set时,可以正常编译和运行。但当我们设置为Unicode Character Set,则会有以下编译错误: > error C2664: ‘MessageBoxW’ : cannot convert parameter 2 from ‘const char [18]’ to ‘LPCWSTR’ 这是因为MessageBox有两个版本,一个MessageBoxW针对Unicode版的,一个是MessageBoxA针对Multi-Byte的,它们通过不同宏进行隔开,预设不同的宏会使用不同的版本。我们使用了Use Unicode Character Set就预设了_UNICODE、UNICODE宏,所以编译时就会使用MessageBoxW,这时我们传入多字节常量字符串肯定会有问题,而应该传入宽符的字符串,即将”Title”改为L”Title”就可以了,”这是一个测试程序!”也一样。 ~~~ WINUSERAPI int WINAPI MessageBoxA( __in_opt HWND hWnd, __in_opt LPCSTR lpText, __in_opt LPCSTR lpCaption, __in UINT uType); WINUSERAPI int WINAPI MessageBoxW( __in_opt HWND hWnd, __in_opt LPCWSTR lpText, __in_opt LPCWSTR lpCaption, __in UINT uType); #ifdef UNICODE #define MessageBox MessageBoxW #else #define MessageBox MessageBoxA #endif // !UNICODE ~~~ 上面的Multi-Byte Character Set一般是指ANSI(多字节)字符集,关于ANSI请参考第二小节[字符集(Charcater Set)与字符编码(Encoding)](http://blog.csdn.net/luoweifu/article/details/49382969#t4)。而Unicode Character Set就是Unicode字符集,一般是指UTF-16编码的Unicode。也就是说每个字符编码为两个字节,两个字节可以表示65535个字符,65535个字符可以表示世界上大部分的语言。 一般推荐使用Unicode的方式,因为它可以适应各个国家语言,在进行软件国际时将会非常便得。除非在对存储要求非常高的时候,或要兼容C的代码时,我们才会使用多字节的方式 。 # 理解_T()、_Text()宏即L”“ [上一小节](http://blog.csdn.net/luoweifu/article/details/49382969#t6)对MessageBox的调用中除了使用L”Title”外,还可以使用_T(“Title”)和_TEXT(“Title”)。而且你会发现在MFC和Win32程序中会更多地使用_T和_TEXT,那_T、_TEXT和L之间有什么区别呢? 通过第一小节[多字节字符与宽字节字符](http://blog.csdn.net/luoweifu/article/details/49382969#t0)我们知道表示多字节字符(char)串常量时用一般的双引号括起来就可以了,如”String test”;而表示宽字节字符(wchar_t)串常量时要在引号前加L,如L”String test”。 查看tchar.h头文件的定义我们知道_T和_TEXT的功能是一样的,是一个预定义的宏。 ~~~ #define _T(x) __T(x) #define _TEXT(x) __T(x) ~~~ 我们再看看__T(x)的定义,发现它有两个: ~~~ #ifdef _UNICODE // ... 省略其它代码 #define __T(x) L ## x // ... 省略其它代码 #else /* ndef _UNICODE */ // ... 省略其它代码 #define __T(x) x // ... 省略其它代码 #endif /* _UNICODE */ ~~~ 这下明白了吗?当我们的工程的Character Set设置为Use Unicode Character Set时_T和_TEXT就会在常量字符串前面加L,否则(即Use Multi-Byte Character Set时)就会以一般的字符串处理。  # Dword、LPSTR、LPWSTR、LPCSTR、LPCWSTR、LPTSTR、LPCTSTR VC++中还有一些常用的宏你也许会范糊涂,如Dword、LPSTR、LPWSTR、LPCSTR、LPCWSTR、LPTSTR、LPCTSTR。这里我们统一总结一下: **常见的宏:** | 类型 | MBCS | UNICODE | | --- | --- | --- | | WCHAR | wchar_t | wchar_t | | LPSTR | char* | char* | | LPCSTR | const char* | const char* | | LPWSTR | wchar_t* | wchar_t* | | LPCWSTR | const wchar_t* | const wchar_t* | | TCHAR | char | wchar_t | | LPTSTR | TCHAR*(或char*) | TCHAR* (或wchar_t*) | | LPCTSTR | const TCHAR* | const TCHAR* | * * * **相互转换方法:**  LPWSTR->LPTSTR: W2T();  LPTSTR->LPWSTR: T2W();  LPCWSTR->LPCSTR: W2CT();  LPCSTR->LPCWSTR: T2CW(); ANSI->UNICODE: A2W();  UNICODE->ANSI: W2A(); * * * **字符串函数:**  还有一些字符串的操作函数,它们也有一 一对应关系: | MBCS | UNICODE | | --- | --- | | strlen(); | wcslen(); | | strcpy(); | wcscpy(); | | strcmp(); | wcscmp(); | | strcat(); | wcscat(); | | strchr(); | wcschr(); | | … | … | 通过这些函数和宏的命名你也许就发现了一些霍规律,一般带有前缀w(或后缀W)的都是用于宽字符的,而不带前缀w(或带有后缀A)的一般是用于多字节字符的。 # 理解CString产生的原因与工作的机理 CString:动态的TCHAR数组,是对TCHAR数组的一种封闭。它是一个完全独立的类,封装了“+”等操作符和字符串操作方法,换句话说就是CString是对TCHAR操作的方法的集合。它的作用是方便WIN32程序和MFC程序进行字符串的处理和类型的转换。 关于CString更详细的用法请参考:  [CString与string、char*的区别和转换](http://blog.csdn.net/luoweifu/article/details/20232379)  [CString的常见用法](http://www.cnblogs.com/Caiqinghua/archive/2009/02/16/1391190.html) * * * * * * 参考文章:  [字符集和字符编码(Charset & Encoding)](http://www.cnblogs.com/skynet/archive/2011/05/03/2035105.html)  [字符,字节和编码](http://www.regexlab.com/zh/encoding.htm)  [《windows核心编程系列》二谈谈ANSI和Unicode字符集](http://blog.csdn.net/ithzhang/article/details/7916732)  [Dword、LPSTR、LPWSTR、LPCSTR、LPCWSTR、LPTSTR、LPCTSTR](http://www.cnblogs.com/goed/archive/2011/11/11/2245702.html) 上一篇回顾:  [带你玩转Visual Studio——带你跳出坑爹的Runtime Library坑](http://blog.csdn.net/luoweifu/article/details/49055933#comments) 下一篇要讲述的内容:  带你玩转Visual Studio——带你解读__cdecl、__fastcall、__stdcall三种调用约定
';

带你跳出坑爹的Runtime Library坑

最后更新于:2022-04-01 23:01:29

上一篇文章[带你玩转Visual Studio——带你理解微软的预编译头技术](http://blog.csdn.net/luoweifu/article/details/49010627)我们了解了微软的预编译头技术,预编译的方式让我们的工程编译的更加快速;本篇文章将继续介绍微软的另一项技术,也就是运行时库Runtime Library。 * * * 在Windows下进行C++的开发,不可避免的要与Windows的底层库进行交互,然而VS下的一项设置MT、MTd、MD和MDd却经常让人搞迷糊,相信不少人都被他坑过,特别是你工程使用了很多第三库的时候,及容易出现各种链接问题。看一下下面这个错误提示:  LIBCMT.lib(_file.obj) : error LNK2005: ___initstdio already defined in libc.lib(_file.obj)  LIBCMT.lib(_file.obj) : error LNK2005: ___endstdio already defined in libc.lib(_file.obj) 有多少人被这玩意坑过,被坑过的请举脚!哈哈…… 既然这里这么容易出问题,我们就有必要对其进行深入的了解,**知其然且知其所以然才能万事无惧!** # 1\. 什么是Runtime Library? Runtime Library就是运行时库,也简称CRT(C Run Time Library)。是程序在运行时所需要的库文件,通常运行时库是以Lib或Dll形式提供的。 Windows下**C Runtime Library**是微软对C标准库函数的实现,这样每个程序可以直接使用C标准库的函数;后来出现了C++,于是又在C Runtime Library基础上开发了**C++ Runtime Library**,实现了对C++标准库的支持。因此现在Windows下的C/C++运行时库既包含子C标准库,也包含了C++标准库。如果你安装了VS2010,在安装目录下的VC\crt\src下(如我的目录是C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\crt\src)有运行时库(CRT)的源代码,这里既有C的文件(如output.c、stdio.h等),也有C++的文件(如iostream、string)。 在C Runtime Library出现之前,许多程序都使用C编写,而这些程序都要使用标准的C库,按照以前的方式每一个程序最终都要拷贝一份标准库的实现到程序中,这样同一时刻内存中可能有许多份标准库的代码(一个程序一份),所以微软出于效率的考虑把标准C库做为动态链接来实现,这样多个程序使用C标准库时内存中就只有一份拷贝了。 确切地说运行时库指的就是对这些底层的基础功能实现的动态库(Dll),运行时库和普通的Dll一样,只有程序用到了它才会被加载,没有程序使用的时候不会驻留内存的。话虽如此,但有多少系统的东西说不定也是用C写的,这些东西的存在就使C运行时库存在于内存中了,所以运行时库几乎总是需要的。虽然说运行时库应该是动态库,但习惯上我们把与动态运行时库相同代码编译出来的静态库也称为运行时库,因此VC++下的运行时库有ML、MLd、MT、MTd、MD、MD六种(这个后面会讲)。 ## 1.1 运行时库的主要作用 1. 提供C标准库(如memcpy、printf、malloc等)、C++标准库(STL)的支持。 2. 应用程序添加启动函数,启动函数的主要功能为将要进行的程序初始化,对全局变量进行赋初值,加载用户程序的入口函数。 不采用宽字符集的控制台程序的入口点为mainCRTStartup(void)。下面我们以该函数为例来分析运行时库究竟为我们添加了怎样的入口程序。这个函数在crt0.c中被定义,下列的代码经过了笔者的整理和简化: ~~~ void mainCRTStartup(void) {  int mainret;  /*获得WIN32完整的版本信息*/  _osver = GetVersion();  _winminor = (_osver >> 8) & 0x00FF ;  _winmajor = _osver & 0x00FF ;  _winver = (_winmajor << 8) + _winminor;  _osver = (_osver >> 16) & 0x00FFFF ;  _ioinit(); /* initialize lowio */  /* 获得命令行信息 */  _acmdln = (char *) GetCommandLineA();  /* 获得环境信息 */  _aenvptr = (char *) __crtGetEnvironmentStringsA();  _setargv(); /* 设置命令行参数 */  _setenvp(); /* 设置环境参数 */  _cinit(); /* C数据初始化:全局变量初始化,就在这里!*/  __initenv = _environ;  mainret = main( __argc, __argv, _environ ); /*调用main函数*/  exit( mainret ); } ~~~ 从以上代码可知,运行库在调用用户程序的main或WinMain函数之前,进行了一些初始化工作。初始化完成后,接着才调用了我们编写的main或WinMain函数。只有这样,我们的C语言运行时库和应用程序才能正常地工作起来。 除了crt0.c外,C运行时库中还包含wcrt0.c、 wincrt0.c、wwincrt0.c三个文件用来提供初始化函数。wcrt0.c是crt0.c的宽字符集版,wincrt0.c中包含 windows应用程序的入口函数,而wwincrt0.c则是wincrt0.c的宽字符集版。 # 2\. MT、MTd、MD、MDd、(ML、MLd 已废弃)的区别与原理 我们可以在Properties->Configuration Properties->C/C++->Code Generation->Runtime Library中设置采用的运行时库的类型。  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03baca12.jpg)  Runtime Library 在[带你玩转Visual Studio——带你发布自己的工程库](http://blog.csdn.net/luoweifu/article/details/48895765)一文中已经详细讲解了静态库(Lib)与动态库(Dll)的区别。我们知道编译出的静态库只有一个ProjectName.lib文件,而编译出的动态库有两个文件:ProjectName.lib+ProjectName.dll,一个是导入库,一个动态库。 **VC++中有六种Runtime Library的类型:** | 类型 | 简称 | 含义 | 对应的库名称 | 备注 | | --- | --- | --- | --- | --- | | Single-Threaded | /ML | Release版的单线程静态库 | libc.lib | VS2003以后被废弃 | | Single-Threaded Debug | /MLd | Debug版的单线程静态库 | libcd.lib | VS2003以后被废弃 | | Multi-threaded | /MT | Release版的多线程静态库 | libcmt.lib | | | Multi-threaded Debug | /MTd | Debug版的多线程静态库 | libcmtd.lib | | | Multi-threaded DLL | /MD | Release版的多线程动态库 | msvcrt.lib+msvcrtxx.dll | | | Multi-threaded DLL Debug | MDd | Debug版的多线程动态库 | msvcrtd.lib+msvcrtxxd.dll | | 你可以在VS的安装目录下找到这些库文件,如我的VS2010安装在C:\Program Files (x86)\Microsoft Visual Studio 10.0,则可以在C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\lib\和C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\lib\amd64\中分别找到对应32位和64位的libcmt.lib、libcmtd.lib、msvcrt.lib、msvcrtd.lib库。libc.lib和libcd.lib由于在VS2005就已经废弃了,所以在这肯定找不到。 通过上面的表格你会发现,多线程的动态运行时库是|msvcrt.lib+msvcrtxx.dll,之所以是msvcrtxx.dll是因为每一个版本的VS使用的库名称还不一样。而且还不止包含一个库,除了主要的MSVCRT库外,还有MSVCPRT、MSVCIRT库。它们之间的对应关系如下: | 导入库 | MSVCRT.LIB | MSVCRTD.LIB | MSVCPRT.LIB | MSVCPRTD.LIB | MSVCIRT.LIB | MSVCIRTD.LIB | | --- | --- | --- | --- | --- | --- | --- | | Visual C++ 5.0 | MSVCRT.DLL | MSVCRTD.DLL | MSVCP5.DLL | MSVCP5D.DLL | MSVCIRT.DLL | MSVCIRTD.DLL | | Visual C++ 6.0 | MSVCRT.DLL | MSVCRTD.DLL | MSVCP6.DLL | MSVCP6D.DLL | MSVCIRT.DLL | MSVCIRTD.DLL | | Visual C++ .NET 2002 | MSVCR70.DLL | MSVCR70D.DLL | MSVCP70.DLL | MSVCP70D.DLL | | | | Visual C++ .NET 2003 | MSVCR71.DLL | MSVCR71D.DLL | MSVCP71.DLL | MSVCP71D.DLL | | | | Visual C++ 2005 | MSVCR80.DLL | MSVCR80D.DLL | MSVCP80.DLL | MSVCP80D.DLL | | | | Visual C++ 2008 | MSVCR90.DLL | MSVCR90.DLL | MSVCP90.DLL | MSVCP90D.DLL | | | | Visual C++ 2010 | MSVCR100.DLL | MSVCR100D.DLL | MSVCP100.DLL | MSVCP100D.DLL | | | 参考阅读:[https://support.microsoft.com/en-us/kb/154753](https://support.microsoft.com/en-us/kb/154753) 在你的VS安装目录下(如C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\redist\x64\Microsoft.VC100.CRT),及系统目录C:\Windows\System32、C:\Windows\SysWOW64下都能找到对应的.dll库。 很多的软件在发布自己的产品时也都会带上这些DLL(防止用户的操作系统没有安装VS,或在系统目录下找不到对应的DLL),如我电脑上的百度影音安装目录下就有MSVCR71.DLL(C:\Program Files (x86)\baidu\BaiduPlayer\4.1.2.286\MSVCR71.DLL),WPS的安装目录下有msvcr100.dll(C:\Program Files (x86)\WPS Office\9.1.0.5132\wtoolex\msvcr100.dll)和msvcp100.dll(C:\Program Files (x86)\WPS Office\9.1.0.5132\wtoolex\msvcp100.dll) (1). 静态链接的单线程库  静态链接的单线程库只能用于单线程的应用程序, C 运行时库的目标代码最终被编译在应用程序的二进制文件中。通过 /ML 编译选项可以设置 Visual C++ 使用静态链接的单线  程库。  (2). 静态链接的多线程库  静态链接的多线程库的目标代码也最终被编译在应用程序的二进制文件中,但是它可以在多线程程序中使用。通过 /MT 编译选项可以设置 Visual C++ 使用静态链接的多线程库。  该选项生成的可执行文件运行时不需要运行时库dll的参加,会获得轻微的性能提升,但最终生成的二进制代码因链入庞大的运行时库实现而变得非常臃肿。当某项目以静态链接库的形式嵌入到多个项目,则可能造成运行时库的内存管理有多份,最终将导致致命的“Invalid Address specified to RtlValidateHeap”问题。  (3). 动态链接的运行时库  动态链接的运行时库将所有的 C 库函数保存在一个单独的动态链接库 MSVCRTxx.DLL 中, MSVCRTxx.DLL 处理了多线程问题。使用 /MD 编译选项可以设置 Visual C++ 使用动态。  链接时将按照传统VC链接dll的方式将运行时库MSVCRxx.DLL的导入库MSVCRT.lib链接,在运行时要求安装了相应版本的VC运行时库可再发行组件包(当然把这些运行时库dll放在应用程序目录下也是可以的)。 因/MD和/MDd方式不会将运行时库链接到可执行文件内部,可有效减少可执行文件尺寸。当多项目以MD方式运作时,其内部会采用同一个堆,内存管理将被简化,跨模块内存管理问题也能得到缓解。 /MDd 、 /MLd 或 /MTd 选项使用 Debug runtime library( 调试版本的运行时刻函数库 ) ,与 /MD 、 /ML 或 /MT 分别对应。 Debug 版本的 Runtime Library 包含了调试信息,并采用了一些保护机制以帮助发现错误,加强了对错误的检测,因此在运行性能方面比不上 Release 版本。 **结论:/MD和/MDd将是潮流所趋,/ML和/MLd方式请及时放弃,/MT和/MTd在非必要时最好也不要采用了。** # 3\. 如何避免这种错误 3.1./MD和/MDd将是潮流所趋,/ML和/MLd方式请及时放弃,/MT和/MTd在非必要时最好也不要采用了。尽量使用/MD、/MDd这种方式,除非有某些特殊的需要,如希望编译出来的.exe可执行文件不需要依赖运行时库的.dll;  2.在多工程开发时,所有的工程使用同一种运行时库。如Utils的Solution下有两个Project:Utils和UsingUtils,UsingUtils工程要使用Utils工程编译出来的库。如果Utils使用了/MDd的方式,UsingUtils也要使用/MDd的方式,否则会报链接错误。  如果Utils使用MTd的方式,而UsingUtils使用/MDd的方式,则会出现重定义的错误,如: ~~~ 1>LIBCMTD.lib(setlocal.obj) : error LNK2005: __configthreadlocale already defined in MSVCRTD.lib(MSVCR100D.dll) 1>LIBCMTD.lib(dbgheap.obj) : error LNK2005: __free_dbg already defined in MSVCRTD.lib(MSVCR100D.dll) 1>LIBCMTD.lib(dbgheap.obj) : error LNK2005: __CrtSetCheckCount already defined in MSVCRTD.lib(MSVCR100D.dll) ~~~ 这是因为Utils使用MTd的方式,包含了libcmtd.lib库;而UsingUtils使用/MDd的方式,要包含msvcrtd.lib+msvcrtxxd.dll。libcmtd.lib和msvcrtd.lib是用相同代码编译的,一个是静态库,一个动态库的导入库,同时包含libcmtd.lib和msvcrtd.lib肯定就对相同的函数进行了重复的定义。 1. 以Release方式进行编译时使用Release的库,使用Debug的方式编译时使用Debug的库。如编译Release版本的UsingUtils时,要使用Release方式编译出来的Utils库,编译Debug版本的UsingUtils时,要使用Debug方式编译出来的库。  # 4\. 历史发展的角度讲解运行时库 ## 4.1 从操作系统的角度出发 我们知道操作系统一般是用C写的(因为那时还没有C++,C已经算是最高级的计算机语言了),不管是Linux/Unix还是Windows底层都是大量的C代码。在开发操作系统及相应的应用程序时,很多的程序都会用到相同基础功能的函数库。为了方便开发就把经常用到的底层的基础函数封闭成库(不然你每写一个程序都要把这基础功能实现的源代码拷贝一份到自己的工程,或自己再实现一次),于是就有了**C运行时库(C Runtime Library)**,也就是静态库libc.lib(Release版)、libcd.lib(Debug版)。 因为早期的操作系统和程序都相应简单,用户的需求也不高,那时的操作系统还没有多任务、多线程的概念。所以libc.lib、libcd.lib当然只能支持单线程的程序,而无法支持多线程的程序,因此这个运行时库叫Single-Threaded(/ML)的方式。 后来,随着计算机的普及和发展,计算机要完成的任务越来越多,人们对时间和性能的要求也越来越高,为满足这些需求,操作系统就有了多任务的概念,也有了多线程的技术。而之前的运行时库libc.lib、libcd.lib只能用于单线程,已经无法满足很多程序的需要,于是多线程的运行时库也就应运而生,这就是libcmt.lib、libcmtd.lib,也就是/MT、/MTd的方式。 /MT、/MTd解决了多线程的问题,但随着程序的越来越复杂,一个程序可能会用到多个其他程序的库,多个程序可能会用到相同的库,在内存中会保存多份的相同的静态库。假设A程序使用了C.lib,B程序也使用了C.lib,A、B程序同时运行时,在内存中就会同时存在两份C.lib。  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03bbe90b.jpg)  静态库在内存中的呈现方式 为了解决这个问题,就产生了动态库的技术。于是就有了动态的运行时库Multi-threaded DLL(/MD)、Multi-threaded DLL Debug(/MDd)。多个程序使用同一个动态库,在内存中只会有一份,效果图如下  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03bcf373.jpg)  动态库在内存中的呈现方式 ## 4.2 从语言的角度 我们都知道,先有了C语言,后来才在C的基础上发展出了C++语言,而C++又对C兼容,相当是C的一个超集。  一开始的运行时库,只是C的运行时库(C Runtime Library),包含了对C标准函数的实现。后来随着C++的产生,又把C++程序运行时要用的底层基础库加了进去,就有了C/C++的运行时库(C/C++ Runtime Library),这时的运行时库既包含了标准C函数的实现,也包含了C++ STL的实现。 上一篇回顾:  [带你玩转Visual Studio——带你理解微软的预编译头技术](http://blog.csdn.net/luoweifu/article/details/49010627) 下一篇要讲述的内容:  带你玩转Visual Studio——带你理解多字节编码与Unicode码
';

带你理解微软的预编译头技术

最后更新于:2022-04-01 23:01:26

通过上一篇文章[带你玩转Visual Studio——带你多工程开发](http://blog.csdn.net/luoweifu/article/details/48915347)的讲解,我们能够在一个Solution中创建多个Project,统一管理一个项目的多个Project。本篇文章我们将继续讲解微软的预编译头技术。 # 不陌生的stdafx.h 还记得[带你玩转Visual Studio——带你新建一个工程](http://blog.csdn.net/luoweifu/article/details/48692267)一文中的图2(为方便阅读,已拷贝到下图 1)吗?我们默认勾选了Precompiled header复选框,创建的工程中就自动添加了stdafx.h和stdafx.cpp,**stdafx.h就是预编译头文件**,勾选Precompiled header就表明采用了微软的预编译头技术。  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd0375d08b.jpg)  图 1:Application Settings ## 打开或关闭预编译方式 右键工程名->Properties->Configuration Properties->C/C++->Precompiled Header,Precompiled Header下拉列表框选择Use(/Yu)表示**打开**(使用)预编译头的方式,选择Not Using Precompiled Headers表示**关闭**预编译头的编译方式。  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03b7f1fa.jpg)  图 2:预编译头的设置 参数说明:  Precompiled Header:是否采用预编译头的方式;  Precompiled Header File:预编译头文件的名称,VS中文件名的大小写不敏感,但最好保持大小写相同。  Precompiled Header Output File:生成的.pch文件我名称,关于.pch文件将再在下面**预编译原理**小节中讲述。 如果是要使用预编译头文件的方式,还需要设置stdafx.cpp文件的属性。右键stdafx.cpp->Properties->Configuration Properties->C/C++->Precompiled Header,Precompiled Header下拉列表框中选择Create(Yc).  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03b974b9.jpg)  图 3:设置.cpp的属性 ## 注意事项 **每一个源文件的第一行代码必须包含预编译头文件。**如果你的工程选用了预编译头文件的方式,每一个.cpp文件的第一行代码必须包含预编译头文件(#include “stdafx.h”),否则会编译出错。 # 预编译技术的内存原理 在Windows程序开发时,经常要在各个文件中包含windows.h、afx.h等标准头文件,而这些文件非常的大,在编译时就非常的慢,非常耗时。为解决这个问题,已是就有了预编译头文件的技术。 所谓头文件预编译技术,就是把一个工程(Project)中常用的一些头文件(如标准头文件Windows.h、Afxwin.h等,也可以是自己定义的头文件)包含在stdafx.h中,并对stdafx.h预先编译(在所有的.cpp文件编译之前进行编译),得到编译结果.pch文件(默认名称为ProjectName.pch),后期该工程在编译其它.cpp文件时不再编译stdafx.h中的内容(即使include了它),仅仅使用预编译的结果。  其中stdafx.h叫做**预编译头文件**,stdafx名称的英文全称为:Standard Application Framework Extensions,当然你也可以自己定义预编译头文件的名称,手动重命名stdafx.h,同时将上面图2和图3中对应的名称也得改过来。ProjectName.pch叫做**预编译头**。 采用预编译头技术后,可以加快编译速度,节省编译时间。因为只需要预先编译一次就可以在所有的.cpp编译时使用,不用再次编译。这样带来的一个问题就是**每一个.cpp文件的开头都要包含预编译头文件#include “stdafx.h”。**因为预编译头技术是假定预编译头中的内容会在所有.cpp文件中使用,在编译你的 .cpp 的时候,就会将预编译头中已经编译完的部分加载到内存中。 使用预编译头文件需要注意的几个要点:  1\. 你编写的任何.cpp文件都必须首先包含stdafx.h。  2\. 如果你有工程文件里的大多数.cpp文件需要的.h文件,顺便将它们加在stdafx.h(后部)上,然后预编译stdafx.cpp。  3\. 由于.pch文件具有大量的符号信息,它是你的工程文件里最大的文件。 # 如何在非MFC工程中使用MFC库 这部分内容之前写过,就不再赘述,直接给出链接:  [非MFC工程中使用MFC库](http://blog.csdn.net/luoweifu/article/details/41527069) 上一篇回顾:  [带你玩转Visual Studio——带你多工程开发](http://blog.csdn.net/luoweifu/article/details/48915347) 下一篇要讲述的内容:  带你玩转Visual Studio——带你跳出Runtime Library大坑
';

带你多工程开发

最后更新于:2022-04-01 23:01:24

上一篇文章[带你玩转Visual Studio——带你管理多种释出版本](http://blog.csdn.net/luoweifu/article/details/48912241)让我们了解了Debug与Release的区别,并学会也如果管理 多个不同释出版本的配制项。在一个大型的项目中,往往不止一个工程,而会有多个工程相互关联,每个工程也都会有自己的释出版本,这就是这篇文章将要讲述的内容。 # 一个Solution多个Project ## 多个工程简介 在[带你玩转Visual Studio——带你新建一个工程](http://blog.csdn.net/luoweifu/article/details/48692267)一文中提到一个Solution(解决方案)可以有多个Project(工程),那什么时候需要有多工程呢? 多工程又有什么好处呢? **应用场景:**当一个项目由多个不同的组件(模块)构成时,为每一个组件创建一个工程,所有的组件工程在同一个解决方案下。  **优点:**结构清晰,可进行分模块开发,对复杂程序进行解耦。 ## 创建一个多工程项目 我们还是以Utils这个工程为例,在[带你发布自己的工程库](http://blog.csdn.net/luoweifu/article/details/48895765)一文及上一篇[带你管理多种释出版本](http://blog.csdn.net/luoweifu/article/details/48912241)中已经创建了一个Utils工程,并为它编译出了多个版本的库,但我们使用这个库的时候却是在另一个Solution下进行的,其实我们完全可以而且也应该把使用Utils库的工程与Utils工程放在一个解决方案下。 1. **新建Project添加到已有Solution中**,File->New->Project… 打开新建工程对话框新建一个UsingUtils工程,注意在Solution这一栏中我们选择Add to solution。  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03a9442f.jpg)  添加一个工程 2. **添加引用关联**,这时我们要使用这个Utils编译出来的库,配制也要简单一点了。右键UsingUtils工程->Properties->Common Properties->Framework and references,添加引用(依赖)的工程Utils。设置引用关联后,如果Utils工程发生改动或更新,在编译UsingUtils工程时就会重新编译Utils工程。  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03aaa3ed.jpg) 添加引用关联 3. **设置头文件的路径**。  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03ac221a.jpg) 设置头文件的路径 4. **设置启动工程**,在有多个工程的Solution中要设置启动工程(也就是要第一个开始执行的工程),不然你按F5运行时不知道从哪个工程开始执行。选择UsingUtils工程名右键鼠标->Set as Startup Project。然后就可以执行或调试UsingUtils工程了。 # 编译结果和目录管理 在多个组件同时开发时,把相关的Project放在同一个Solution下会方便很多。但你有没有发现一个新的问题,如果一个Solution有很多的Project,每一个Project目录下都会有一个编译结果的目录,如下图这样你昏不昏?  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03ad5ffc.jpg)  编译结果目录 那如何管理这些目录,使这些目录看起来不这么混乱呢?其实我们是可以设置这些目录的输出路径的,可以把它们放在一起管理。我们可以将输出目录设计成这样: * Utils  * Utils * UsingUtils * Output  * Win32  * Debug  * Bin * Lib * Temp * Release  * Bin * Lib * Temp * Linux  * Debug  * Bin * Lib * Temp * Release  * Bin * Lib * Temp 这样看起来是不是结构清晰多了!Output为输出目录,Win32为Windows X32下编译的结构,Linux为Linux平台下的编译结果(这个涉及到跨平台开发,暂时不谈),Win32下再有Debug和Release等多个不同的释出版本,Bin下放置编译出的最终结果(如.exe等),Lib下放置编译出的所有.lib文件,Temp放置编译过程的临时文件(如.obj等)。 我们还是以Utils为例进行说明。Utils Solution下有两个Project:Utils(编译出Utils工具库)和UsingUtils(使用Utils的库),仅以释出Debug_Static进行说明,其它的释出方式与此类似。 1. **所有Project使用同一组配制项。**  什么意思呢?我们在[带你玩转Visual Studio——带你管理多种释出版本](http://blog.csdn.net/luoweifu/article/details/48912241)一文说到Debug和Release就是一组配制项,其实整个Solution有一个配制项,每一个Project也有自己的配制项。  整个Solution的配制项也就是下图工具栏中你能看到的这些配制项:  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03ae63ea.jpg)  Solution的配制项 而每一个Project的配制荐是你右键工程名->Properties能看到的配制项:  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03b04bc7.jpg) Project的配制项 一般一个Solution下的所有的Project最好使用同组配制项,这样不容易混乱。 2. **给UsingUtils添加Debug_Static配制项。**我们设置Utils的属性时已经配制了Debug_Static的配制项,并设置了Solution的Debug_Static配制项,再给UsingUtils添加Debug_Static的配制项。  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03b15284.jpg) 添加配制项 标“4”的Create new solution configurations表示为整个Solution也添加(Debug_Static)配制项,这个复选框得取消勾选,因为设置Utils时已经为Solution默认添加了Debug_Static配制项,不然会添加不上。 3. **设置Utils的输出路径**,右键Utils工程->Properties进行如下配制。  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03b2a0d3.jpg) 设置输出路径 4. **拷贝导出库.lib**,我们可以将Utils编译出的静态库拷贝Utils.lib到Lib目录下,这样我们就可以直接把这个文件提供到调用方使用。  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03b3d2fe.jpg) Build Events中可以设置编译前、链接前、编译后要进行的处理事件。这里我们目地是编译后将编译出的Utils.lib拷贝到Lib文件夹下,所以我们在Post-Build Event输入以下命令 ~~~ :如果Lib目录不存在,侧创建这个目录 if not exist $(SolutionDir)Output\Win32\$(Configuration)\Lib md $(SolutionDir)Output\Win32\$(Configuration)\Lib :将(ProjectName).lib文件拷贝到Lib目录下 copy /y $(SolutionDir)Output\Win32\$(Configuration)\Bin\$(ProjectName).lib $(SolutionDir)Output\Win32\$(Configuration)\Lib\ ~~~ 1. **设置UsingUtils的输出路径**,与Utils类似如下:  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03b5304d.jpg) 设置UsingUtils的输出路径 2. **设置完成**,Ok,编译一下再来看看输出结果目录,是不是清晰多了!  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03b692a2.jpg)  新的输出结果目录 上一篇回顾:  [带你玩转Visual Studio——带你管理多种释出版本](http://blog.csdn.net/luoweifu/article/details/48912241) 下一篇要讲述的内容:  带你玩转Visual Studio——带你理解微软的预编译头技术
';

带你管理多种释出版本

最后更新于:2022-04-01 23:01:22

上一篇文章[带你玩转Visual Studio——带你发布自己的工程库](http://blog.csdn.net/luoweifu/article/details/48895765)让我们了解基本的静态库和动态库,并将自己的工程编译成库,也能在自己的工程中引入第三方库。正如上文提到的,我们在实际开发中可能会将工程释出(也叫发布)多个不同类型的版本,如即要有静态库也要有动态库,即有Debug也有Release。如何管理这些不同的释出版本就是本文要讲的内容。 * * * # 为什么要有多种释出版本 我们每打开一个工程你都会在工具栏中看到有Debug和Release两个编译方式的选择。它们有什么区别,又为什么要有Debug和Release呢?  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd039d9a61.jpg)  图 1:Debug与Release ## Debug Debug 通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序。 ## Release Release 称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以使编译出的可执行文件或库的性能最高,以便用户很好地使用。 ## Debug与Release的区别 Debug和Release本质上就是两组不同的编译选择配制,各自的默认设置一般不用更改。它们最大的区别在于Release进行了优化,去掉了调试信息,因此Release常用无法进行正确的调试;而Debug没有进行优化,包含了调试信息,以方便程序员进行调试。我们可以通过比较它们的配制发现它们之间的区别: 1. 代码编译的优化  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd039ebc29.jpg) Debug  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03a084ee.jpg) Release 2. 使用的运行时库  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03a1c091.jpg) Debug  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03a2e360.jpg) Release Debug使用的运行时库一般后面会加d,而Release的不加,关于运行时库的问题后面还会有专门章节的进行讲解。 3. 链接的优化  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03a43266.jpg) Debug  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03a54bed.jpg) Release # 释出方式的配制和管理 既然Debug或Release就一组配制,那能不能息增加一组配制呢?当然是可以的。还记得上一文[带你发布自己的工程库](http://blog.csdn.net/luoweifu/article/details/48895765)中的例子Utils吗?既要编译静态库又要编译动态库,我们之前都是在Debug下配制的,但编译静态库与动态库切换时每次都要更改配制非常麻烦,其实我们只要为静态库与动态库添加两组两组机制,每次编译时切换一下编译项就可以了。 1. 点击上面图1下拉列表框的Configuration Manager…或右键工程->属性->右上角的Configuration Manager…。打开Configuration Manager对话框。 2. 在Active solution configuration下拉列表中点击New新建的一组配制。  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03a6616c.jpg)  Configuration Manager 3. Copy settings from里有几个选择,Empty表示不拷贝任何配制,添加一组默认设置的配制项;Debug表示会拷贝已有的Debug这组配制中的所有设置,添加完后你再基于这个配制进行修改。一般我们会从已有的Debug或Release中拷贝然后再进行相应的修改,因为Debug或Release中有很多已经设置好的配制,这样不容易出错,而且Debug或Release也是默认的标准配制。  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03a796e4.jpg)  New Solution Configuration 4. 选择我们新添加的配制项Debug_static,右键工程->Properties进行相应的设置,这个在前面一文中已经讲过了,不再赘述。 5. 同样的方式,我们可以增加Debug_dynamic、Release_Static、Release_Dynamic等配制项。  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03a867a5.jpg)  新增多个配制项 * * * * * * 上一篇回顾:  [带你玩转Visual Studio——带你发布自己的工程库](http://blog.csdn.net/luoweifu/article/details/48895765) 下一篇要讲述的内容:  带你玩转Visual Studio——带你多工程开发
';

带你发布自己的工程库

最后更新于:2022-04-01 23:01:20

上一篇文章[带你玩转Visual Studio——带你高效管理代码](http://blog.csdn.net/luoweifu/article/details/48866717)通过对VisualSVN优秀插件的讲解,让我们掌握了在集成开发环境VS中快捷高效地管理代码的技能。然而我们开发的程序并不总是直接地生成可执行的软件,我们可能只是开发某个大型系统的一个组件,也可能是开发某个软件的内核SDK提供给上层的应用程序调用,在开发的过程中我们也可能会用到第三方的开源库。那如果将自己的程序编译成程序库给调用方用呢?又如何在自己的程序中引用第三方库呢?这将是这篇文章要讲的内容——发布自己的工程库。 # 什么是程序库? 库是写好的现有的,成熟的,可以复用的代码。现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常。比如你经常使用的STL(Standard Template Library)也是库,有了STL你才能方便地使用std::string、std::cout这些类。 本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存,被别的程序调用执行。C++的库有两种:静态库和动态库。将一个程序编译成可执行文件一般经过 预编译–>编译–>链接 这几个过程,而静态库与动态库的区别主要体现在链接这个过程。 ## 静态库: 在链接阶段,会将编译的目标文件.obj 与引用到的库.lib 一起链接打包到可执行文件exe(也称为目标代码)中,程序运行时将不再需要该静态库。 因此最终链接成的可执行文件(.exe)**体积较大**。在Windows中一般以.lib为后缀名,在Linux中一般以.a为后缀名。 ## 动态库: 在链接阶段,动态库.dll并没有真正被连接到目标代码中,只是将这个动态库的声明链接到目标代码中(这样程序运行时才知道怎样使用这个动态库),动态库.dll依然是独立存在的,只有在程序运行是才会将.dll载入到内存中被程序调用。因此程序运行时必须要有这个动态库且放在正确的路径中。 因此最终链接成的可执行文件(.exe)**体积较小**。在Windows中一般以.dll为后缀名,在Linux中一般以.so为后缀名。 ## 静态库与动态库的区别: | 特点 | 静态库 | 动态库 | | --- | --- | --- | | 对函数库的链接时机 | 在编译的链接阶段完成的 | 推迟到程序运行的时期 | | 运行过程与库的关系 | 程序在运行时与静态库再无瓜葛 | 程序在运行时与动态库库需要一直存在且路径正确 | | 是否链接到可执行文件 | 静态库被链接合成一个可执行文件 | 动态库不会被链接到可执行文件中 | | 目标文件大小 | 体积较大 | 体积较小 | | 内存占用度 | 占用内存。如果多个程序使用了同一个静态库,每一个程序者会包含这个静态库 | 节约内存。如果多个程序使用了同一个动态库,可以实现进程之间的资源共享(因此动态库也称为共享库) | | 程序移植 | 移植方便 | 移植不太方便,需要所有动态库的头文件 | | 程序升级 | 程序升级麻烦,需要下载整个程序进行升级 | 程序升级更简单,只需要升级某个DLL或某个程序,下载一个升级包即可 | | 编译出的结果文件 | ProjectName.lib | ProjectName.lib+ProjectName.dll, 这里的ProjectName.lib与静态库的.lib文件不同,这只是一个导入库,只包含了地址符号表等,以便调用方的程序能找到对应的函数,真正的库文件是ProjectName.dll | * * * * * * # 编译自己的工程库 假设我们有这样一个工程,这个工程的作用就是提供一些常用的工具类和方法,然后我们要将这个工程编译成库提供给别人使用。 ## 编译静态库 假设我们已经建好工程并写好了相应的代码:  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03903d49.jpg)  工程目录 ***Utils.h:*** ~~~ //=============================================================== //Summary: // Utils 类, 工具类 //FileName: // Utils.h //Remarks: // ... //Date: // 2015/10/4 //Author: // Administrator(luoweifu@126.com) //=============================================================== #ifndef __UTILS_H__ #define __UTILS_H__ #include #include //#include class Utils { public: Utils(void); ~Utils(void); public: //--------------------------------------------------------------- //function: // WString2String wstring 到 string 的转换 //Access: // public //Parameter: // [in] const std::wstring & ws - wstring字符串 //Returns: // std::string - string字符串 //Remarks: // 些方法跨平台,可移植版本 //author: luoweifu //--------------------------------------------------------------- static std::string WString2String(const std::wstring& ws); //--------------------------------------------------------------- //function: // String2WString string 到 wstring 的转换 //Access: // public //Parameter: // [in] const std::string & s - string 字符串 //Returns: // std::wstring - wstring字符串 //Remarks: // 些方法跨平台,可移植版本 //author: luoweifu //--------------------------------------------------------------- static std::wstring String2WString(const std::string& s); }; //--------------------------------------------------------------- //function: // ConvertToString 将int转换成string //Parameter: // [in] int val - 要转换的变量 //Returns: // std::string - 转换后的字符串 //Remarks: // ... //author: luoweifu //--------------------------------------------------------------- std::string ConvertToString(int val); #endif //__UTILS_H__ ~~~ 上述声明的实现参考后面的附录[Utils.cpp](http://blog.csdn.net/luoweifu/article/details/48895765#t11)。 这里的注释是通过VAssistX生成的,关于VAssistX的用法可参考前面写的一篇文章[带你玩转Visual Studio——带你高效开发](http://blog.csdn.net/luoweifu/article/details/48852119)。 要编译成静态库,我们可以这样设置我们的工程:  右键工程->Properties  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03912474.jpg)  编译成静态库 然后右键Build就可以了,你可以在解决方案下的Debug(实际的情况中一般要编译成Release版本,设置的方法一样,这里的内容后一章中再讲)目录下就能看到Utils.lib,这就是编译出的库。要将这个库给别人使用,只要提供这个Utils.lib和这个工程的头文件就可以。将Utils.h拷贝到D:\ReleaseLibs\StaticLib\Includes,将Utils.lib拷贝到D:\ReleaseLibs\StaticLib\Libs,把D:\ReleaseLibs\StaticLib这个文件提供出去就可以了。静态库的使用请看后一小节[使用静态库](http://blog.csdn.net/luoweifu/article/details/48895765#t8) ## 编译动态库 与静态库相比,编译动态库要麻烦一些,一般要在导出函数的声明处加上_declspec(dllexport)关键字前缀。  1. ****Utils.h***的声明如下 ~~~ //=============================================================== //Summary: // Utils 类, 工具类 //FileName: // Utils.h //Remarks: // ... //Date: // 2015/10/4 //Author: // Administrator(luoweifu@126.com) //=============================================================== #ifndef __UTILS_H__ #define __UTILS_H__ #include #include //#include //=============================================================== //=============================================================== class Utils { public: Utils(void); ~Utils(void); public: //--------------------------------------------------------------- //function: // Max 获得两个数中的最大值 //Access: // public //Parameter: // [in] int nValue1 - 第一个数 // [in] int nValue2 - 每二个数 //Returns: // int - 最大值 //Remarks: // ... //author: luoweifu //--------------------------------------------------------------- static int Max(int nValue1, int nValue2); //--------------------------------------------------------------- //function: // Min 获得两个数中的最小值 //Access: // public //Parameter: // [in] int nValue1 - 第一个值 // [in] int nValue2 - 第二个值 //Returns: // int - 最小值 //Remarks: // ... //author: luoweifu //--------------------------------------------------------------- static int Min(int nValue1, int nValue2); //--------------------------------------------------------------- //function: // Range 将一值限定在一个范围内 //Access: // public //Parameter: // [in] int nMin - 最小值 // [in] int nMax - 最大值 //Returns: // int - 返回在限制在该范围内的一个值 //Remarks: // ... //author: luoweifu //--------------------------------------------------------------- static int Range(int nMin, int nMax, int nValue); }; //--------------------------------------------------------------- //function: // ConvertToInt 将一个常量字符串转换成int类型数据 //Access: // public //Parameter: // [in] const char * pStr - 常量字符串 //Returns: // int - 转换成的int值 //Remarks: // ... //author: luoweifu //--------------------------------------------------------------- int ConvertToInt(const char* pStr); #endif //__UTILS_H__ ~~~ 1. 要编译成动态库,我们可以这样设置我们的工程:  右键工程->Properties  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd039234fb.jpg) 设置编译的目标类型 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd0393abd8.jpg) 设置预编译宏 然后右键Build就可以了,你可以在解决方案下的Debug(实际的情况中一般要编译成Release版本,设置的方法一样,这里的内容后一章中再讲)目录下就能看到Utils.dll和Utils.lib,这就是编译出的库。要将这个库给别人使用,只要提供这个Utils.dll、Utils.lib和这个工程的头文件就可以。将Utils.h拷贝到D:\ReleaseLibs\DynamicLib\Includes,将Utils.dll和Utils.lib拷贝到D:\ReleaseLibs\DynamicLib\Libs,把D:\ReleaseLibs\DynamicLib这个文件提供出去就可以了。静态库的使用请看后一小节[使用动态库](http://blog.csdn.net/luoweifu/article/details/48895765#t9) 也许你要问为什么编译出的静态库是Utils.lib,编译出的动态库也有Utils.lib,这两个.lib文件是一样的吗?  你比较一下两个.lib文件的大小就会发现相差很大(静态库的lib有235KB,动态库的lib只有2.7KB),所以肯定不是一样的啦!**动态库对应的lib文件叫“导入库”,导入库只包含了地址符号表等,确保调用方的程序能找到对应函数的一些基本地址信息,而实际的执行代码位于DLL文件中。**静态库的lib文件本身就包含了实际执行代码、符号表等。 # 使用导入(第三方)库 在实际的开发中经常要用第三方提供的库,如开源库,或大型系统中合作方提供的组件。如果使用呢?我们就以上面自己制作的库为例进行讲解。假设我们有一个工程TestProject要使用上面自己制作的Utils库。 ## 使用静态库 1. 右键工程->Properties,进行如下的设置。  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd0394f898.jpg) 设置头文件所在的路径 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd0396198a.jpg) 设置lib库所在的路径 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd039759ef.jpg) 设置要导入哪个lib库 2. 测试代码如下: ~~~ #include #include #include "Utils.h" int _tmain(int argc, _TCHAR* argv[]) { int nMax = Utils::Max(25, 37); std::cout << nMax << std::endl; int nMin = Utils::Min(10, 44); std::cout << nMin << std::endl; int nValue = Utils::Range(0, 100, 115); std::cout << nValue << std::endl; char* pStr = "1234"; int nValue2 = ConvertToInt(pStr); std::cout << nValue2 << std::endl; return 0; } ~~~ ## 使用动态库 1. 右键TestProject工程->Properties,进行如下的设置。  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd0398ec56.jpg) 设置头文件所在的路径  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd039a32ce.jpg) 设置lib库所在的路径  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd039c3b41.jpg) 设置要导入哪个导入库 2. 将Utils.dll放入与TestProject的输出文件TestProject.exe相同的路径下。这个很最重,不然会编译成功会是执行失败,因为找不到对应的.dll文件。 3. 测试代码与静态库的一样。 * * * * * * # 附录 ## Utils.cpp ~~~ #include "Utils.h" Utils::Utils(void) { } Utils::~Utils(void) { } int Utils::Max( int nValue1, int nValue2 ) { return nValue1 > nValue2 ? nValue1 : nValue2; } int Utils::Min( int nValue1, int nValue2 ) { return nValue1 < nValue2 ? nValue1 : nValue2; } int Utils::Range( int nMin, int nMax, int nValue ) { if (nMax < nMin) { int temp = nMin; nMin = nMax; nMax = temp; } if (nValue < nMin) { return nMin; } else if (nValue > nMax) { return nMax; } else { return nValue; } } int ConvertToInt( const char* pStr ) { int val; std::strstream ss; ss << pStr; ss >> val; return val; } ~~~ 参考文章:[C++静态库与动态库](http://www.cnblogs.com/skynet/p/3372855.html)
';

带你高效管理代码

最后更新于:2022-04-01 23:01:17

上一篇文章[带你玩转Visual Studio——带你高效开发](http://blog.csdn.net/luoweifu/article/details/48852119)通过对VAssistX优秀插件的讲解,让我们掌握了快速开发C++代码的技能。然而大部分的程序都不是一个人的开发的,是由一个团队的多个人一起开发的,大型的系统还可能由多个不同的团队分包进行开发。多人进行协作开发时,代码的管理就显得及为重要,需要借助代码管理工具的辅助,这种工具又称为**版本控制系统**。 目前主流的版本控制系统有:  **CVS:**是一个用于代码版本控制的自由软件,它是一个比较早出现的工具,由于它有很多自身的缺陷,现在几乎被SVN所取代了。  **SVN:**SVN是Subversion的简称,它是集中式的版本控制系统。SVN继承了CVS的基本思想,包含了CVS的几乎所有功能。你可以认为SVN是CVS的升级版(但实际上它们完全是两个软件)。  **GIT:**GIT是分布式的版本控制系统。相信玩过开源代码的都知道[github](https://github.com/)吧,它就是一个基于git的代码托管平台。  关于SVN与GIT的区别,可参考:[GIT和SVN之间的五个基本区别](http://www.vaikan.com/5-fundamental-differences-between-git-svn/) **本文假设您已经对SVN有基本的了解和掌握,并用TortoiseSVN客户端进行过代码的迁入、选出、更新等功能进行团队的协作开发。如果没有这方面的知识,建议先查阅其它的相关资料,以对SVN有大致的了解后再来阅读本文。** # VisualSVN的简单介绍与下载安装 ## 简介 VS2010有一个自带的版本管理工具,也就是菜单中的Team(团队协作),但用过的人应该都知道非常难用,与SVN和GIT相比已经被甩出几条街了。  所以很多的公司或团队会选择SVN来进行代码的管理,安装一个SVN的客户端进行的迁入与迁出。但可能很少人知道VS上还有一个插件可以帮助我们更好地使用SVN,它就是**VisualSVN**。 VisualSVN是一个用在Visual Studio上的插件,SVN的客户端TortoiseSVN(SVN有一个服务器Server用于集中管理资源库,有一个客户端Client用于团队的每一个成员进行访问资源库、提交代码和迁出代码等。)是一个版本控制工具。VisualSVN是基于TortoiseSVN的,也就是说VisualSVN要调用TortoiseSVN软件的功能,所以要使用VisualSVN,必须同时安装VisualSVN和TortoiseSVN。 ## 下载 VisualSVN的官方下载地址:  [https://www.visualsvn.com/visualsvn/download/](https://www.visualsvn.com/visualsvn/download/)  SVN的官方下载地址:  [http://tortoisesvn.net/downloads.html](http://tortoisesvn.net/downloads.html)  **注意:**SVN的版本要和VisualSVN的版本对应,也就是要下载给定的VisualSVN版本建议的SVN的版本。如VisualSVN5.1.2可用的SVN版本是1.8.x、1.9.x,建议的SVN版本是 TortoiseSVN 1.9.2. ## 安装 安装这个很简单,一般先安装TortoiseSVN再安装VisualSVN。选择默认的设置就可以,这个不多说了。 # VisualSVN的常用功能介绍 ## 向服务器上传一个工程 关于SVN环境的搭建是另外一个主题,你可以自己去查找相关的资料,这里只讲VS相关的东西。您也可以参考这篇文章:[Windows下的SVN环境搭建详解](http://www.cnblogs.com/oyjt/p/3295801.html),个人觉得写的还可以。要是在公司里一般会有已经搭建好的SVN环境,你只需要一个代码存放的地址。 现在假设你已经搭建好了SVN的环境,有一个SVN的工作地址:svn://localhost/CppRepos(这是我本SVN机服务器的库地址,远程服务器的地址也是一样的),有一个SVN的用户名(luoweifu)和密码(svn_luoweifu)。现在要把本机D:\CppWorkspace\DesignPattern下的DesignPattern工程上传到服务器,以后在VS2010下开发这个工程并在VS2010提交和更新代码。基本的操作流程如下: 1. 用VS2010打开DesignPattern工程。安装完VisualSVN插件后,VS上会多一个菜单项VisualSVN。 2. 选择菜单VisualSVN\Add Solution to Subversion… 3. 选择要添加工程的本地路径,一般会自动给你设置好,如果路径是正确的就直接一步。  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03847fcf.jpg)  图 1:Add Solution to Subversion 1 4. 这里我们选择Existing Repository,因为我们已经有一个SVN的地址,如果服务器没有创建对应的库可选择New Repository创建一个新的库。  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03856ea8.jpg)  图 2:Add Solution to Subversion 2 5. 这里填入你的SVN的地址:svn://localhost/CppRepos  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03864948.jpg)  图 3:Add Solution to Subversion 3 6. 点击Import就可以了,这时你的VS会有一个Pending Changes的视图,如果没有可通过View\Other Windows\Pending Changes菜单调出来。这个视图将显示所有你要添加到服务器的文件。  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03872a2d.jpg)  图 4:Add Solution to Subversion 4 7. Import后只是将本地的文件与服务器上的地址做了映射,并没有真正将文件上传到服务器。你需要将所有文件提交(Commit),才是真正上传到服务器。选中Pending Changes中的所有文件并右键Commit。  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03881e67.jpg)  图 5:Add Solution to Subversion 5 8. 对话框的底部会显示你要提交的所有文件,你可以勾选要提交或不提交哪些文件。写上你提交的原因或备注(这个很重要,一定要有这样的习惯,以便后期查看历史记录时一看就能知道你做了什么),然后点击Ok就可以了。  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03896b6f.jpg)  图 6:Add Solution to Subversion 6 9. 第一次提交会要求输入用户名和密码,输入你的SVN用户名和密码就可以了。  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd038ab043.jpg)  图 7:Add Solution to Subversion 7 ## VisualSVN的主要功能 VisualSVN有TortoiseSVN的几乎所有功能,它的作用就是将TortoiseSVN集成到VS中,这样代码的编码、代码的管理就可以都在一个IDE中进行,而不用每次提交或更新代码都要切换到TortoiseSVN去做。这跟Java的MyEclipse、Intellij IDEA的SVN插件是类似的。 添加VisualSVN插件后,VisualSVN菜单会有TortoiseSVN的几乎所有功能。如将Solution添加到服务器,从服务器获得Solution,更新代码、提交代码,打分支、合并分支等,如下图。这些功能只要你对SVN有大致的了解,模索模索就会用了,不再过多的介绍。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd038c00ed.jpg)  图 8:VisualSVN的整体视图 Pending Chages视图将显示所有新添加、删除或被改动的文件。  Solution Explorer视图对不同状态的文件也会用不同颜色的圆点标注:绿色表示未做更改的文件,黄色表示已做改动或新添加的文件。 ## C++工程上传服务器要忽视的文件 在[带你玩转Visual Studio——带你新建一个工程](http://blog.csdn.net/luoweifu/article/details/48692267#t6)一文中讲到了C++工程中的各种类型文件,然后我们在代码的版本控制时有很多文件和目录并不需要上传到服务器。因为服务器中只需要存一些有用的数据和文件,一些无用的辅助性文件(如pch文件,Debug目录等)只会给服务器和管理带来负担。这里将列出需要上传和不需要上传的文件类型。 ### 需要上传的文件类型: > h: 头文件  > cpp: 源文件  > txt: 说明文件,如readme  > rc: 资源文件  > rc2: 资源文件  > ico: 图标,如logo等  > sln: 解决方案工程文件  > vcxproj: 工程文件  > filters: 文件过虑器 ### 不要上传的文件类型 > Debug、Release等编译结构目录  > ipch目录  > aps: last resource editor state  > exe: build result  > idb: build state  > ipch: build helper  > lastbuildstate: build helper  > lib: build result. Can be 3rd party  > log: build log  > manifest: build helper. Can be written yourself.  > obj: build helper  > pch: build helper  > pdb: build result  > res: build helper  > sdf: intellisense dbase  > suo: solution user options  > tlog: build log  > user: debug settings. Do preserve if just one dev or custom debug settings 如果用TortoiseSVN进行管理,需要手动添加ignore属性将不需要上传的文件忽略掉。在你工程目录里,右键->TortoiseSVN->Properties->New->Other,弹出的对话框中Property name中选择svn:ignore,Property value中填入要忽略的内容(这里可以使用能配符),如下图。  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd038e08cd.jpg)  图 9:添加ingore属性 用VisualSVN进行上传Solution时,默认就帮你设置好了,将不需要的文件给屏蔽了。这也是VisualSVN的一大好处。 * * * * * * 上一篇回顾:  [带你玩转Visual Studio——带你高效开发](http://blog.csdn.net/luoweifu/article/details/48852119) 下一篇要讲述的内容:  带你玩转Visual Studio——带你发布自己的工程库
';

带你高效开发

最后更新于:2022-04-01 23:01:15

上一篇文章[带你玩转Visual Studio——带你了解VC++各种类型的工程](http://blog.csdn.net/luoweifu/article/details/48816605#t29)一文中讲了各种类型VC++工程的主要功能和用途。现在将带你一起快速开发c++程序。 有过Java开发经验的人都知道Java的常用开发工具(如Eclipse、Intellij IDEA等)都有非常强大的关键字高亮、智能提示、快速追踪等的功能。那也许你就要问了:进行C++开发的Visual Studio是否也有这个功能呢?首先要说明的是Visual Studio本身并没有这么强大的功能(至少VS2010及之前版本是这样的,有一些简单的代码跟踪能力但是很难用),但它有一个非常强大和好用的插件有这些功能,它就量VassistX,也称为VC助手,它就是本文要讲的内容。 # VassistX的简单介绍与下载安装 ## 简单介绍 VassistX的全称是Visual Assist X,是whole tomato开发的一个非常好用的插件,可用于VC6.0及Visual Studio的各个版本(包括VS.NET2002、VS.NET2003、VS2005、VS2008、VS2010、VS2012、VS2013及VS2015)。 VassistX有以下主要功能:  **智能提示:**关键字的提示,已有类名、方法名的提示,类成员(成员数据和成员函数)的提示;  **代码高亮:**关键字、类名、方法名等用不同的颜色进行高亮,方便代码的阅读;  **快速追踪:**声明与实现的快速跳转,.cpp与.h文件的快速切换;  **高效查找:**文件名的查找,标识符的查找,标识符补引用的查找;  **代码重构:**重命名标识符(变量名、方法名、类名等,这个非常有用),自动添加成员变量的Set/Get方法,自动添加函数的注释等。 ## 下载与安装 ### 下载: 官方下载地址:[http://www.wholetomato.com/downloads/default.asp](http://www.wholetomato.com/downloads/default.asp)  你也可以到中文的一些网站去下载,网上一搜一大片。 ### 安装: 安装非常简单,你只需要点击VA_X_Setupxxxx.exe进行默认安装即可,这没什么要说的。 ### 注册码 还是那句话:自己想办法(以后都不再进行说明),如果有钱还是支持正版,毕竟作为程序员的我们应该知道写一个好的程序不容易。 # VassistX的使用指南 ## 1.通过“Tip of the day”快速学习。 在你安装VassistX后,启动VS时,会有一个帮助提示(如下图),这是你快速学习VassistX的最好方式,还有小动画提示,一看就懂。如果你看不懂英文,看小动画也能明白怎么回事。  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd037bbe4b.jpg)  图 1:tip of the day 如果你不希望每次启动VS时都弹出这个对话框,只需把左下角的”Show tips at startup”的勾选去掉就可以。如果需要再次弹出对话,在菜单中选择VassitX\Visual Assist X Option…–>Starup,把Show tip of the day勾选上即可。 ## 2.主要窗口和工具 插件装上之后你应该就能感觉比以前好多了,比如代码高亮啊,智能提示啊,应该立马就能体现出来。但这还是要简单介绍一下。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd037d1656.jpg)  图 2:窗口视图 插件成功安装之后,菜单栏会多一个VassistX的菜单,这里包含了VassistX的所有功能和相关的设置,设置方面后面还会进一步讲。  左侧会有两个VA Outline和VA View视图(如果没有,可能菜单VassistX\Tools\VA Outline调出来)。VA Outline这个视图很有用,也是我用的最多的一个视图,它能清晰地呈现一个类中的各个成员。  工具栏也会多一个VassistX工具组。这个我用的最多的要数最右边的.h与.cpp的切换按钮。 ## 3常用的快捷键 这里只列出一些最常用的快捷键,一般记住这些也就够用了。 | 快捷键 | 说明 | 备注 | | :-- | :-- | :-- | | Alt+G | 快速跳转,如由声明跳转到实现,由实现跳转到声明。 | 光标要在标识符处 | | Alt+O | .h与.cpp文件的快速切换 | | | Alt+Shift+F | 查找标识符所有被引用的位置 | 光标要在标识符处 | | Alt+Shift+O | 查找整个Solution下的某个文件 | O指open,打开指定的文件 | | Alt+Shift+S | 查找标识符 | S指Symbol | | Alt+Shift+R | 重命名标识符 | 光标要在标识符处,R指ReName | | Ctrl+Shift+V | 选择剪切板的内容进行粘贴 | | ## 更改快捷键 菜单Tools\Option…,然后在打开的对话框中选择Keyboard,Show commands containning中输入VAssistX就能看到VAssistX的各种功能,你可以设置某一指定功能的快捷键。如你输入VAssistX.refactorRename,就能看到重命名的默认快捷键是Shift+Alt+R,你在Press shortcut keys中按下快捷键更改它。  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd037f3d66.jpg)  图 3:设置快捷键 # VassistX的高效开发的属性配制 ## 选项设置 VassistX有些默认设置并不太友好,我们可以自己重新设置它。在菜单中选择VassitX\Visual Assist X Option…打开设置对话框。以下是我个人的偏好设置,仅作参考(只说明一些关键设置的含义)。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd0381089f.jpg)  图 4:设置1 1.拼写错误检测(这个不要选,不然老会有红色的波浪线提示);  2.类型错误检测(这个选上,可以测试你的变量类型是否正确); ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03821340.jpg)  图 5:设置2 1.高亮引用的标识符(设置颜色,一个当前词的颜色,一个是非当前词的颜色);  2.自动高亮当前光标所在处标识符  3.高亮查找引用的结果;  4.高亮重命名标识符时关联的变量; ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd0383681d.jpg)  图 6:设置3 1.(黑色)高亮匹配的括号;  2.(红色)高亮不匹配的括号;  3.高亮当前光标所在的行(可选择高亮前景或高亮背景);  4.纵向指示器的分隔线,一般用于控制一行代码的字符数;  5.高亮查找结果。 ## 注释配制与自动添加代码 注释也是程序的一个重要组成部分之一,好的注释是提高代码的可读性和程序的可维护性的一个关键因素,而要写大量的注释是一件非常繁琐的事情,给程序员增加了不少负担,而VAsssistX就可能帮我们减轻这种负担。将光标移到函数名处,右键鼠标选择Refactor\Document Method可自动帮我们添加函数的注释。 我们可以自己配制自定义的注释格式。选择菜单:VAssistX\Insert VA Snippet…\Edit VA Snippet…进行设置。给出我的相关设置,仅作参考: **Title:** File header detailed  **Shortcut:** fh  **Description:** 对整个文件进行注释 ~~~ //=============================================================== //Summary: // $FILE_BASE$ 类, $end$ //FileName: // $FILE_BASE$.$FILE_EXT$ //Remarks: // ... //Date: // $YEAR$/$MONTH$/$DAY$ //Author: // %USERNAME%(luoweifu@126.com) //=============================================================== ~~~ **说明:**两个$符号之间的内容都是VAssistX中定义的宏,参见文后的附录:[Visual Assist X Snippets 宏](http://blog.csdn.net/luoweifu/article/details/48852119#t15) **Title:** Refactor Document Method  **Shortcut:**  **Description:** 对函数进行注释 ~~~ //--------------------------------------------------------------- //function: // $SymbolName$ $end$ //Access: // $SymbolVirtual$ $SymbolPrivileges$ $SymbolStatic$ //Parameter: // [in] $MethodArg$ - //Returns: // $SymbolType$ - //Remarks: // ... //author: %USERNAME%[luoweifu] //--------------------------------------------------------------- ~~~ **Title:** #ifndef … #endif  **Shortcut:** #ifn  **Description:** 防止头文件重复包含 ~~~ #ifndef __$FILE_BASE_UPPER$_H__ #define __$FILE_BASE_UPPER$_H__ $end$ #endif //__$FILE_BASE_UPPER$_H__ ~~~ **Title:** Refactor Encapsulate Field  **Shortcut:**  **Description:** 添加类中成员变量的Get/Set方法。 ~~~ $end$$SymbolType$ Get$GeneratedPropertyName$() const { return $SymbolName$; } void Set$GeneratedPropertyName$($SymbolType$ val) { $SymbolName$ = val; } ~~~ **Title:**  **Shortcut:** //-  **Description:** 用于某一个重要修改的注释,并记录修改时间。 ~~~ // $end$ [$YEAR$/$MONTH$/$DAY$ %USERNAME%] ~~~ **Title:**  **Shortcut:** ///  **Description:** 分隔线,用于重要的代码块的分隔。 ~~~ //=============================================================== $end$ ~~~ # 附录 ## Visual Assist X Snippets 宏 | . | Reserved String | Meaning | 样例或说明 | | --- | --- | --- | --- | | Date | DATE | Year/month/day formatted as %04d/%02d/%02d | 2015/10/02 | | | DATELOCALE | Current date in locale format | 本地化格式境 | | | DAY | Day of month formatted as %d | 2 | | | DAY02 | Day of month formatted as %02d | 02 | | | DAYNAME | Day abbreviation in locale format | //Fri | | | DAYLONGNAME | Full name of day in locale format | //Friday | | | MONTH | Month formatted as %d | 10 | | | MONTH02 | Month formatted as %02d | 10 | | | MONTHNAME | Month abbreviation in locale format | Oct | | | MONTHLONGNAME | Full name of month in locale format | October | | | YEAR | Year formatted as %d | 2015 | | | YEAR02 | Year formatted as %02d | 15 | | Time | HOUR | Hour formatted as %d | 16 | | | HOUR02 | Hour formatted as %02d | 16 | | | MINUTE | Minute formatted as %02d | 29 | | | SECOND | Second formatted as %02d | 30 | | File | FILE | Full filename with path* | D:\CppWorkspace\DesignPattern\DesignPattern\Water.h | | | FILEUPPER | Full filename with path in uppercase* | D:\CPPWORKSPACE\DESIGNPATTERN\DESIGNPATTERN\WATER.H | | | FILEBASE | Filename without path or extension* | Water | | | FILEBASEUPPER | Filename without path or extension in upper case* | WATER | | | FILEEXT | Filename extension* | h | | | FILEEXTUPPER | Filename extension in upper case* | H | | | FILEPATH | Path of file* | D:\CppWorkspace\DesignPattern\DesignPattern | | | FILEPATHUPPER | Path of file in upper case* | D:\CPPWORKSPACE\DESIGNPATTERN\DESIGNPATTERN | | General | clipboard | Current clipboard | 当前剪贴板内容 | | | end | Position of caret after expansion | 输入完成光标位置 | | | selected | Current selection** | 选定内容 | | | $$ | Literal ‘$’ character | $转义 | | Symbol Context | MethodName | Name of containing method | | | | MethodArgs | Method parameters | 函数参数列表 | | | ClassName | Name of containing class | 类名称 | | | BaseClassName | Name of base class of containing class | 基类名称 | | | NamespaceName | Fully qualified namespace name | 命名空间 | | GUID | GUIDDEFINITION | Generated GUID formatted for use in a definition | GUID定义,“,”分割 | | | GUIDSTRING | Generated GUID formatted for use in a string | GUID字符串,“-”分割 | | | GUIDSTRUCT | Generated GUID formatted for use in a struct | GUID结构 | | | GUIDSYMBOL | Generated GUID formatted with underscores | GUID符号,”_”分割 | | | GUIDSTRINGUPPER | Uppercase version of GUIDSTRING | 略 | | | GUIDSTRUCTUPPER | Uppercase version of GUIDSTRUCT | 略 | | | GUIDSYMBOLUPPER | Uppercase version of GUIDSYMBOL | 略 | | | The following reserved strings are available only in refactoring snippets | | | | Refactor | GeneratedPropertyName | Property name generated during Encapsulate Field | 与类属性同名的访问方法,首字符大写(类似Java中的Set/Get方法) | | | generatedPropertyName | Same as GeneratedPropertyNamebut with lower-case first letter | 与类属性同名的访问方法,首字符小写 | | | MethodArg | One parameter of the method and its type | 不可用 | | | MethodArgName | One parameter of the method | 方法参数 | | | MethodArgType | Type of one parameter of the method | 函数体 | | | MethodBody | Body of implementation | 函数体 | | | MethodQualifier | Optional qualifiers of method | 尚不清楚 | | | ParameterList | Parameters separated by commas | 参数列表 | | | SymbolContext | Context and name of method | 函数全名(含类信息等) | | | SymbolName | Name of method | 函数名称 | | | SymbolPrivileges | Access of method | 访问控制 | | | SymbolStatic | Keyword static or blank | static | | | SymbolType | Return type of method | 函数返回类型 | | | SymbolVirtual | Keyword virtual or blank | virtual | 上一篇回顾:  [带你玩转Visual Studio——带你了解VC++各种类型的工程](http://blog.csdn.net/luoweifu/article/details/48816605) 下一篇要讲述的内容:  带你玩转Visual Studio——带你高效管理代码
';

带你了解VC++各种类型的工程

最后更新于:2022-04-01 23:01:13

上一篇文章[带你玩转Visual Studio——带你新建一个工程](http://blog.csdn.net/luoweifu/article/details/48692267)一文中提到新建一个工程时会有很多的工程类型(图1),现在将简单介绍各种类型工程的含义和主要用途。由于这里包含的工程类型太多,有很多本人也没有接触过,有些可能理解的不太对的地方还请谅解。 首先说明一下,如果你的初学者或者C++开发的时间不长,本篇文章的很多内容理解不了是正常的(我当初也不理解)。你可先有大概的概念,等工作(或有实际的项目开发经验)一两年后再回来看看也许就明白了。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd0373cdf8.jpg)  图 1:New Project # 理解几个概念 在开讲之前先大概理解几个概念,这是理解后面各种工程含义的基础。 ## COM **COM(Component Object Model)组件对象模型**是microsoft制定的一个组件软件标准,跟unix上的CORBA一样。凡是遵循COM标准开发出来的组件称为COM组件。目地是实现二进制方式的软件重用 。在windows平台上,COM的实现形式有DLL(进程内组件)和EXE(进程外组件)2种。 ## OLE **OLE(Object Linking and Embedding)对象连接与嵌入**是微软的复合文档技术,可方便实现应用程序之间的通信。在后来的OLE2中才导入了 COM,提供了对COM的支持,利用这种技术可开发可重复使用的软件组件COM。OLE是软件比较早提出的一种技术。 ## ATL **ATL(Active Template Library)活动模板库**是一套C++模板库,常用于开发COM程序和ActiveX程序。要理解ATL技术可从以下两方面理解:  1.ATL可以说是把COM封装了一下,象MFC一样做成一个库,并有一个向导,使COM应用开发尽可能地自动化、可视化,这就决定了ATL只面向COM开发提供支持。  2.ATL因其采用了特定的基本实现技术,摆脱了大量冗余代码,使用ATL开发出来的COM应用的代码简练高效。  在ATL产生以前,开发COM组件的方法主要有两种:一是使用COM SDK(COM软件开发包)直接开发COM组件,另一种方式是通过MFC提供的COM支持来实现。而现在 ATL已经成为Microsoft支持COM应用开发的主要开发工具。 ## MFC **MFC(Microsoft Foundation Classes)微软基础类**是微软提供的一个用于Windows程序开发的基础类库。MFC以C++类的形式封装了Windows的API,并且包含一个应用程序框架,以减少应用程序开发人员的工作量。其中包含的类包含大量Windows句柄封装类和很多Windows的内建控件和组件的封装类。 ## ActiveX ActiveX是微软提出的一组使用COM技术使得软件组件在网络环境中进行交互的技术集,它与具体的编程语言无关。作为针对Internet应用开发的技术,ActiveX被广泛应用于WEB服务器以及客户端的各个方面。同时,ActiveX技术也被用于方便地创建普通的桌面应用程序,此外ActiveX一般具有界面。 ActiveX既包含服务器端技术,也包含客户端技术。其主要内容是:  1\. ActiveX控制(ActiveX Control);用于向WEB页面、Microsoft Word等支持ActiveX的容器(Container)中插入COM对象。  2\. ActiveX文档(ActiveX Document);用于在WEB Browser或者其它支持ActiveX的容器中浏览复合文档(非HTML文档),例如Microsoft Word文档,Microsoft Excel文档或者用户自定义的文档等。  3\. ActiveX脚本描述(ActiveX Scripting);用于从客户端或者服务器端操纵ActiveX控制和Java程序,传递数据,协调它们之间的操作。  4\. ActiveX服务器框架(ActiveX Server Framework);提供了一系列针对WEB服务器应用程序设计各个方面的函数及其封装类,诸如服务器过滤器、HTML数据流控制等。  5\. 在Internet Explorer中内置Java虚拟机(Java Virtual Machine),从而使Java Applet能够在Internet Explorer上运行,并可以与ActiveX控制通过脚本描述语言进行通信。 ## OLE、ActiveX、COM之间的区别 从时间的角度讲,OLE是最早出现的,然后是COM和ActiveX;从体系结构角度讲,OLE和ActiveX是建立在 COM之上的,所以COM是基础;单从名称角度讲,OLE、ActiveX是两个商标名称,而COM则是一个纯技术名词,这也是大家更多的听说ActiveX和OLE的原因。既然OLE是最早出现的,那么就从OLE说起,自从Windows操作系统流行以来,“剪贴板”(Clipboard)首先解决了不同程序间的通信问题(由剪贴板作为数据交换中心,进行、粘贴的操作),但是剪贴板传递的都是“死”数据,应用程序开发者得自行编写、解析数据格式的代码,于是动态数据交换(Dynamic Data Exchange,DDE)的通信协定应运而生,它可以让应用程序之间自动获取彼此的最新数据,但是,解决彼此之间的“数据格式”转换仍然是程序员沉重的负担。对象的链接与嵌入(Object Linking and Embedded,OLE)的诞生把原来应用程序的数据交换提高到“对象交换”,这样程序间不但获得数据也同样获得彼此的应用程序对象,并且可以直接使用彼此的数据内容,其实OLE是Microsoft的复合文档技术,它的最初版本只是瞄准复合文档,但在后续版本OLE2中,导入了COM。由此可见,COM是应OLE的需求而诞生的,所以虽然COM是OLE的基础,但OLE的产生却在COM之前。 COM的基本出发点是,让某个软件通过一个通用的机构为另一个软件提供服务。COM是应OLE 的需求而诞生,但它的第一个使用者却是OLE2,所以COM与复合文档间并没有多大的关系,实际上,后来COM就作为与复合文档完全无关的技术,开始被广泛应用。这样一来, Microsoft就开始“染指”通用平台技术。但是COM并不是产品,它需要一个商标名称。而那时Microsoft的市场专家们已经选用了OLE作为商标名称,所以使用COM技术的都开始贴上了 OLE的标签,虽然这些技术中的绝大多数与复合文档没有关系。  ====本段内容转载自《[OLE、ActiveX、COM、ATL联系与区别](http://www.jeepshoe.org/804052511.htm)》==== # 各种工程结构 ## ATL ### ATL Project 创建一个基于ATL的工程,用ATL的方式进行COM组件的开发,ATL提供了大量可重用的模板。ATL可用于COM组件的开发,也可用于ActiveX的开发。 ## CLR ### Class Library ### CLR Console Application ### CLR Empty Project ### Windows Forms Application ### Windows Forms Control Library CLR(Common Language Runtime)是公共语言运行库,和Java虚拟机一样也是一个运行时环境。CLR的核心功能包括:内存管理、程序集加载、安全性、异常处理和线程同步,可由面向CLR的所有语言使用。并保证应用和底层操作系统之间必要的分离。CLR/C++是托管的C++程序,数据和代码是由CLR管理的,调用方不用管内存的分配和释放,CLR好像常用于.net。 这一块我还真不了解,就不说了,怕误人子弟!!! ## General ### Empty Project 就是创建一个空的工程,不给你添加任何.cpp或.h文件,不进行任何特殊的设置。 ### Custom Wizard 就是用户自定义向导,什么意思呢?比如你每次建一个新的工程时都期望这个工程中有main.cpp、projectDescription.txt这两个文件,并且main.cpp中有一个默认的main函数。那么你可以建一个Custom Wizard工程,并配制好main.cpp、projectDescription.txt文件及所在目录结构;然后你每次创建一个新的工程时选择都基于这个已有的Custom Wizard工程,新建的工程就有自动添加main.cpp、projectDescription.txt文件了。**说白了Custom Wizard就是一个模型,定义工程的默认文件和默认的配制。** ### Makefile Project makefile就是对.cpp和.h等文件的组织、构建、编译规则。这个在跨平台开发中会用到,如你开发的程序既要在Windows下编译也要在Linux、Mac下编译,一般就会使用makefile的编译规则。说明:Windows下有一个微软自己的NMake构建器,因为在VS下makefile文件中的内容要符合NMake的规则才能够编译成功。 ## MFC ### MFC ActiveX Control 就是以支持MFC的方式创建ActiveX程序,可快速地开发带有界面的ActiveX程序。  拓展阅读《[使用VS2010创建MFC ActiveX工程项目](http://blog.csdn.net/longhuahaha/article/details/8556964)》 ### MFC Application 这个就是你平常开发MFC程序时会用到的工程, MFC(Microsoft Foundation Classes)是微软提供的一个用于Windows程序开发的基础类库,也是快速开发Windows上的桌面程序一般会选择的方式。 ### MFC DLL 它也是创建一个MFC的程序,与MFC Application的不同之处是:MFC Application工程生成的是一个.exe的可执行文件,而MFC DLL工程生成的是一个.dll的动态库文件。 ## Test ### Test 顾名思义,这就是一个测试工程,可用来进行单元测试、顺序测试、压力测试等。 ## Win32 ### Win32 Console Application 这个就是你最熟悉的控制台应用程序了,编译成功,运行时会出现一个黑色的命令行窗口。上篇文章[带你玩转Visual Studio——带你新建一个工程](http://blog.csdn.net/luoweifu/article/details/48692267)的样例也就是创建的这个类型的工程。 ### Win32 Project MFC其实是对Windows API进行的一种封闭,使其具有面向对象的特性。而这个Win32 Project工程就是以直接调用Windows API的方式,使用Windows SDK开发带有窗口界面的程序。 # 说明 其实,像一般的开发常用的工程类型也就几种:  **Win32 Console Application:** 控制台应用程序,也是常见的那个黑色命令窗口。  **Win32 Project:** 直接用Windows API进行Windows桌面应用程序的开发。  **MFC Application:** MFC工程,用微软提供的类库进行界面程序的快速开发。 上一篇回顾:  [带你玩转Visual Studio——带你新建一个工程](http://blog.csdn.net/luoweifu/article/details/48692267) 下一篇要讲述的内容:  带你玩转Visual Studio——带你高效开发
';

带你新建一个工程

最后更新于:2022-04-01 23:01:11

接着上一篇文章[带你玩转Visual Studio——开篇介绍](http://blog.csdn.net/luoweifu/article/details/48664887)继续讲这个主题,现在我们从创建一个新的工程开始。 # 一步一步创建项目 1. 依次选择菜单:File\New\Project,打开New Project对话框。 2. .选择项目类型:Win32 Console Application;.填写工程名(Project Name):TestProject(可以给它取一个任意你想要的名字);.选择该工程存放的路径:D:\CppWorkspace。  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd0373cdf8.jpg)  图 1:New Project 你可能会想问:列表里这么多工程类型,分别代表什么含义啊? 这个问题后面再讨论,你先记住这张图。 1. 点击Ok,然后点击Next,会出现这一步  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd0375d08b.jpg)  图 2:Application Settings 2. 我们就采用默认的设置,点击Finish,工程创建完成。  (这是工程相关的设置,同样先不用管它,先记住这张图,后面再讨论)。 * * * # 工程结构与目录结构 ## 工程结构 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03770c57.jpg)  图 3:工程结构 创建完成之后,我们会在左侧看到这样一个工程结构。分别说明如下:  **External Dependencies:**  工程的外部依赖文件,这个目录是VS自动生成的,你最好别动它。 **Header Files:**  头文件,也就是.h(.hpp)后缀的头文件。 **Source Files:**  源文件,也就.cpp(或.c,针对C语言)后缀的文件。 **Header Files:**  资源文件,如果你创建的是MFC的工程,*.rc文件就会在这里。 ### 工程结构的作用: 1. 方便管理,将不同类型或不同用途的文件配制在不同的结构下,方便文件的浏览、查找和代码的管理。 2. 工程结构的目录与真实文件的目录不一定相同。 3. 你可以根据文件的不同用途或不同含义,对工程结构重新配置。  比如我觉得Source Files和Header Files这名太长,你可以选中它按F2,分别把它改重命名成Source和Header。关于工程结构的自定义配置,后续章节的文章还会再提到。 ## 目录结构 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03784400.jpg)  图 4:目录结构 正如上面提到的“工程结构的目录与真实文件的目录不一定相同”,我们在文件管理器中打开TestProject所在的目录,可以看到 *.h 和 *.cpp文件都在同一个目录下,在我的目录里也就是位于D:\CppWorkspace\TestProject\TestProject。 * * * # 写一个HelloWorld程序 所有计算机语言的学习都从HelloWorld开始,Ok,那我们也从HelloWorld开始写一个程序吧!在TestProject.cpp文件中写一个HelloWorld程序如下: ~~~ #include "stdafx.h" #include using namespace std; int _tmain(int argc, _TCHAR* argv[]) { cout << "Hello World" << endl; return 0; } ~~~ 编译:  右键工程名TestProject选择Build,或者点击菜单Build\Build Project。 运行:  内容太简单,直接运行吧,按Ctrl+F5或点击菜单Debug\Start Without Debugging。  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd037a1081.jpg)  图 5:程序运行结果 * * * # 工程目录下各文件的含义 一般大部分的文章可能介绍到上面就算结束了,但我这还没有。创建工程产生的各个文件都你知道是什么用的吗? 如果你是一个初学者,你可能会不知道(老手请跳过本文)。Ok,我就带你逐一了解吧。 ## 解决方案与工程 在这之前先了解一个概念:解决方案与工程。  **解决方案(Solution):**一个大型项目的整体的工作环境;  **工程 (Project):**一个解决方案下的一个子工程; 在VS中,**一个Solution可以有一个或多个Project**。在我们创建一个工程时,如果没有指定Solution,VS会帮我们创建一个与工程名相同的Solution,这时一个Solution里只有一个Project。所有在我们的TestProject的文件目录结构中TestProject文件夹下还有一个TestProject文件夹(如图4),第一个就是整个Solution的目录,第二个才是Project的目录。 ## 解决方案相关的文件: **TestProject.sln:**  整个解决方案(Solution)的配制文件,组织多个工程和相关的元素到一个解决方案中。用鼠标双击它就能用VS打开整个工程项目。 **TestProject.sdf:**  浏览相关的数据库文件,它支持浏览和导航的特性。如跳转到方法、变量的声明,查找所有对象的所有被引用的地方,类视图等等。 **TestProject.suo:**  (solution user opertion) 解决方案用户选项,记录所有将与解决方案建立关联的选项, 以便在每次打开时,它都包含您所做的自定义设置. **TestProject.opensdf:**  打开解决方案(Solution)时的临时文件,这个文件只有你的解决方案在VS打开的状态才会有,工程一关闭文件就被删除了。 ## 工程相关的文件 **TestProject.vcxproj:**  记录工程(Project)相关的属性配制。 **TestProject.vcxproj.filters:**  文件过虑器,上图3“工程结构”中各个文件的组织和编排都是定义在这个文件中的。如果由于某种特殊的原因(如系统或VS突然崩溃)导致你打开工程时文件的组织结构是乱的,100%就是这个文件的原因。 **TestProject.vcxproj.user:**  用户相关的一些配制。 上面这些文件中有几个比较重要的一定不能删的文件是:  TestProject.sln、TestProject.vcxproj、TestProject.vcxproj.filters 不要问我是怎样知道这些文件的作用的,请看官方文档:  VS2010定义:[https://msdn.microsoft.com/en-us/library/3awe4781.aspx](https://msdn.microsoft.com/en-us/library/3awe4781.aspx).  VS2015定义:[https://msdn.microsoft.com/en-us/library/vstudio/hx0cxhaw(v=vs.110).aspx](https://msdn.microsoft.com/en-us/library/vstudio/hx0cxhaw%28v=vs.110%29.aspx) 当然有一些说明是需要经过项目的实践才能理解的。 * * * * * * 上一篇回顾:  [带你玩转Visual Studio——开篇介绍](http://blog.csdn.net/luoweifu/article/details/48664887) 下一篇要讲述的内容:  **带你玩转Visual Studio——带你了解VC++各种类型的工程**
';

开篇介绍

最后更新于:2022-04-01 23:01:08

# 开篇之前,先唠叨几句 本人从事C++开发工作一年半,总想就C++开发方面写点东西。写什么呢?想了一下还是写点跟开发密切相关的吧,要说跟开发最密切相关的那莫过于就是开发工具了,也就是常常说的集成开发环境(IDE)。 这一年多从事的是C++的跨平台开发,在Linux和Windows下都开发过,但主要还是在Windows下进行开发(一般会在Windows下开发,多个平台下编译),因为Windows下有强大好用的开发工具,那就Visual Studio。 # 讲述的范围 ## 本系列博文讲述的内容为: 1. Windows下Visual Studio在C++开发方面的功能和使用方法; 2. 用Visual Studio进行C++的高效开发; 3. C++编译、链接的一些方法,以及部分跟编译相关的Window技术。 ## 本系列博文相关用例的特定环境: 语言:C++  版本:VS2010  系统:win7 x64 ## 说明: 本系列博文虽然是基于VS2010进行说明的,但Visual Studio的各个版本在使用的方法、讲述的原理上应该是类似的,你可以将其中大部分的方法和原理拓展到Visual Studio的各个版本。 # VS2010的下载、安装 ## VS2010的下载: 用度娘搜一下“VS2010 下载”你就可以看到非常多的下载地址,下一个可用的就行。  在这里我要介绍一下官方的下载地址:[http://msdn.itellyou.cn/](http://msdn.itellyou.cn/),选择“开发工具”\“Visual Studio 2010”,这里提供了各种版本:中文的、英文的,专业版、旗舰版等,此外还提供了基于VS2010的各种工具和插件。选择一个适合你自己的版本下载就好。(我用的英文的专业版Professional)  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd036daba3.jpg)  [msdn.itellyou.cn(MSDN,我告诉你)](http://msdn.itellyou.cn/)是微软官方的一个产品发布平台,只要你能想到的几乎所有的微软的产品在这里都能找到,比如各个版本的Windows操作系统,Office软件套件,各个开发工具等、技术文档等。 ## VS2010的安装: 这个非常简单,跟着向导一步一步来,基本不会有错。  1\. 将的下载的安装包解压,点击setup.exe进行安装;  2\. 点击“Install Microsoft Visual Studio 2010”进行安装;  3\. 到协议条款时选择同意接受(“I have read and accept the license terms.”),然后下一步;  4\. 到这一步时,可以选择完整安装或自定义安装(一般选择完整安装,特别是你对VS还不太熟悉的时候),可以设置你要的安装的路径。  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd037040be.jpg) 5\. 等待安装,时间会比较长,耐心等待。  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03727409.jpg) 6\. 安装完毕后,关闭退出安装程序即可。 ## VS2010的激活: 这个自己想办法(有钱的话也可以自己买一个),说太多又要被微软举报了(这个在CSDN也不是什么稀奇的事,我就有过先列了)。 # 文后说明 写博客四年以来,这是头一篇“踩”的人数大于“顶”的人数,评论的人很多,但正面的很少。在这对各位的问题统一回答一下。 **Q:这么烂的文章还放首页?**  A:我也是看了你的评论后才知道上了首页,感谢您的提醒! **Q:虎头蛇尾,通篇下来其实什么也没说。**  A:首先必须要说明的是这是一个系列文章的第一篇。如果你把它当作单篇的文章来看,确实什么也没说;如果你把它当作一个系列的文章来看,这只是开头的第一篇,希望有好戏在后头。  VS是一个大型的企业级的开发工具,我尽量做到这里讲的内容也可以直接应用于企业级的开发。如果所讲的内容一两句话就能说明白,那也就没有必要写这些文章了,四年写博客的经验告诉我“短文多篇”的易读性要好于几万字的“通篇大论”,万望大家理解! **Q:VS2015都出来了,怎么还写VS2010的?**  A:只是因为我个人对VS2010比较熟悉,要写肯定就写自己最懂的啊!不然就更容易误人子弟了……如果原理你都知道了,再使用一个类似的新的工具,相信也很快就能上手! **Q:为什么更新这么慢?**  A:预计两天写一篇的,但计划总赶不上变化。因为前两天搬家就耽搁了几天。不过我将尽量在接下来的两周的内写完整个系列,国庆不回家,趁着这几天多写几篇,感谢您的关注! **Q:后续文章将会讲什么内容?**  A:本来想随着系列博文的逐步发布您们自然就会知道,但又担心我所写的内容并非你期待的内容而浪费您的时间。想想还是先剧透一下,以便于您对后面要写的内容有个大致的了解: * * * 1.VS2010的下载、安装与简单介绍  1.1本系列博文的开篇语,VS2010应用的范围、使用的语言  1.2VS2010的下载、安装与简单介绍 [2.带你新建一个工程](http://blog.csdn.net/luoweifu/article/details/%28http://blog.csdn.net/luoweifu/article/details/48692267%29)  2.1跟着向导逐步介绍,注意各个细节。  2.2工程结构与目录结构  2.3工程目录下各文件的含义 [_2.带你了解VC++各种类型的工程](http://blog.csdn.net/luoweifu/article/details/48816605)  _2.1 3.带你高效开发  3.1VassistX的简单介绍与下载安装  3.2VassistX的使用指南(主要的窗口和工具介绍)  3.3VassistX的高效开发的属性配制(注释、高亮等) 4.带你高效代码管理  4.1VisualSVN的简单介绍与下载安装  4.2VisualSVN的常用功能介绍 5.带你发布自己的工程库  5.1库的概念(动态库与静态库的简单介绍)  5.2编译自己的工程库  5.3使用导入(第三方)库 6.带你管理多种释出版本  6.1为什么要有多种释出版本(Debug、Release等)  6.2翻出方式的配制和管理 7.带你多工程开发  7.1一个Solution下可以有多个Project。  7.1.1多工程有什么作用?  7.1.2什么时候需要有多工程?  7.2编译结果和目录管理  (将各个工程的编译结果放在相同的output目录下)  7.3多工程的自动关联  (关联后编译某一个工程时会自动编译其它工程) 8.带你跳出坑爹的RuntimeLibrary坑  8.1什么是RuntimeLibrary  8.2MT、MTd、MD、MDd、(ML、MLd已废弃)的区别与原理  8.3如何避免这种错误 9.带你理解微软的预编译头技术  9.1不陌生的stdafx.h  (主要功能与使用时的注意事项,如每一个cpp都要包含stdafx.h)  9.2对编译技术的内存原理  9.3如何在非MFC工程中使用MFC库 10.带你理解多字节编码与Unicode码  10.1多字节字符与宽字节字符  10.2工程里多字节与宽字符的配制  10.3理解_T()、_Text()宏  10.4理解CString产生的原因与工作的机理 * * * # 文章汇总 感谢大家的观注,本系列已基本完成,附上文章汇总链接:  [带你玩转Visual Studio——结局汇总  ](http://blog.csdn.net/luoweifu/article/details/49499019) ——2015年10月30日 * * * **如果这些内容是您想要了解或学习的,那欢迎您继续关注本系列的后续博文!我将尽量在接下来的两周的内写完整个系列。如果这些内容并非您需要的东西,那您也不必在这浪费过多的时间了,大胆跳过吧!^.^**
';

VS2010断点进不去解决方法

最后更新于:2022-04-01 23:01:06

按F9断点变灰,Debug调试时进不去断点,提示: The breakpoint will not currently be hit. The source code is different from original version. 大概的意思是说当前的代码与执行文件对应版本的代码不同,这就需要重新编译代码。 解决方案: 1.rebuild工程。 2.在工程的代码文件(.cpp或.h)适当的位置,敲几个回车或按几个空格,然后rebuild。 3.重启VS2010,把OUT目录的东西全删了,然后rebuild。 4.断点处右键点击,选择“Location…”  把“Allow the source code to be different from the original version”复选框勾上。
';

非MFC工程中使用MFC库

最后更新于:2022-04-01 23:01:04

# 需求说明 C++工程的类型有很多,从VS(或VC)可以看到常见的有:Win32 Console Application、MFC Application、Win32 Project等。在创建MFC工程时,通过IDE的向导会自动帮我们创建相应的类文件和包含必需的头文件,但有时候我们需要在非MFC工程中包含MFC的库。至于为什么会有这个需要,为何不在一开始就创建MFC工程呢?可能有两种原因:1.在MFC工程会产生很多向导生成的代码以及资源文件,如基于单文档的工程会有View,Doc等类,很多时候我们并不需要这些东西,只需要一个空工程就可以了。2.使用第三方框架创建的工程,我们很难更改它的工程属性(如用Firebreath开发浏览器插件,通过脚本文件firebreath会自动帮我们生成VS下的工程)。   # 常见问题 在非MFC工程中使用MFC的库就需要包含相应的头文件,经常会遇到下面这个问题: 1. fatal error C1189: #error :  Building MFC application with /MD[d] (CRT dll version) requires MFC shared dll version. Please #define _AFXDLL or do not use /MD[d] 2. fatal error C1189: #error :  WINDOWS.H already included.  MFC apps must not #include    # 问题分析 对于第1个问题,很简单: 选中工程名右键属性(Project),在Properties\Configuration Properties\General\Use of MFC中选择Use MFC in a Shared DLL  出现上面第2个问题主要是因为包含头文件的顺序不对。为什么包含WINDOWS.H的时候会有顺序要求,网上有一段传播的非常广泛解释:  如果在MFC工程中#include   ,那么会有以下编译错误(因为afxwin.h文件中包含了afx.h,afx.h文件中包含了afxver_.h,afxver_.h中包含了afxv_w32.h,而afxv_w32.h中包含了windows.h,请看以下分析):   compile   error:      c:\program   files\microsoft   visual   studio\vc98\mfc\include\afxv_w32.h(14)   :      fatal   error   C1189:   #error   :     WINDOWS.H   already   included.     MFC   apps   must   not   #include               如果编译器在编译afxv_w32.h文件之前编译了windows.h文件,编译器会报上面的错误,因为在afxv_w32.h文件中有下面的一句预编译报警:      #ifdef   _WINDOWS_      #error   WINDOWS.H   already   included.     MFC   apps   must   not   #include         #endif           问题在于为什么afxv_w32.h中要有这么一句预编译处理。看了afxv_w32.h和windows.h文件就有点明白了。      在afxv_w32.h中有下面的预编译语句:      ...   ...      #undef   NOLOGERROR      #undef   NOPROFILER      #undef   NOMEMMGR      #undef   NOLFILEIO      #undef   NOOPENFILE      #undef   NORESOURCE      #undef   NOATOM      ...   ...      在afxv_w32.h中还有一句:      #include   "windows.h"           而在windows.h文件中有下面的预编译语句:      ...   ...      #define   NOATOM      #define   NOGDI      #define   NOGDICAPMASKS      #define   NOMETAFILE      #define   NOMINMAX      #define   NOMSG      #define   NOOPENFILE      ...   ...           注意到在windows.h的开头有防止windows.h被重复编译的预编译开关:      #ifndef   _WINDOWS_      #define   _WINDOWS_           这样问题就明白了,虽然我不知道微软为什么要这么做,但是我知道如果在afxv_w32.h没有那句预编译报警,那么如果在编译afxv_w32.h之前      编译了windows.h,那么在windows.h中#define的NOATOM等宏就会被#undef掉,可能会导致相应的错误发生。           猜想原因可能如上所述,我的解决方法是,将包含有#include   “windows.h"的头文件放在所有包含的头文件的最后面,这样使得对afxv_w32文件      的编译处理发生在先,这样,由于在afxv_w32.h中已经包含了windows.h,那么宏_WINDOWS_将被定义,后继的#include   "windows.h"语句将形同虚设,      上面的编译报警也不会发生了。我觉得这种处理要比将所有的#include   "windows.h”语句删掉要好一点。           一句话,编译器必须在编译windows.h之前编译afxv_w32.h,因为我不是十分清除什么时候afxv_w32.h会被编译,所以我将可能包含有#include   "windows.h"的头文件放在其他头文件之后#include。   # 参考解决方法 解决这个问题的总体思路是:把#include 的包含语句把到最前面。 sunshine1314 的博文《[非](http://blog.csdn.net/sunshine1314/article/details/459809)[MFC工程使用MFC](http://blog.csdn.net/sunshine1314/article/details/459809)[库时的问题及解决办法](http://blog.csdn.net/sunshine1314/article/details/459809)》给出了一序列的解决方案,大家可能参考一下,也许能解决你们的问题。但我当时通过这一系列方法还是没能解决我的问题。   # 我的解决方案 我的问题是:用Firebreath开发浏览器插件,通过fbgen.py和prep2010.cmd脚本帮我们生成了基于VS2010的工程(这个工程中没有stdaf.h),我们要在这个工程中获得MFC中的HDC以及使用MessageBox,于是就碰到了上面提到的问题。 解决方案: 手动添加stdafx.h和stdafx.cpp文件使用预编译机制,在stdafx.h的最前面包含。于是问题就变成了stdafx.h的原理和手动添加stdafx.h文件及相应配置。下面我们以Win32 Console Application工程的TextProject为例,演示一下这过程。 1.在VS2010中创建Win32 Console Application工程的TextProject,创建向导会自动生成的stdafx.h和stdafx.cpp,省去了手动添加的过程。如果你的工程没有这两个文件可以手动创建。 2.stdafx.h和stdafx.cpp这两个文件已创建并添加到工程,下面讲讲相关的配制。 2.1选中工程名,右键属性(Properties),在Precompiled Header/Precompiled Header中选择Use(/Yu),Precompiled Header File中填stdafx.h。设置工程编译时使用预编译头文件stdafx.h(在VS中文件名的大小写不敏感,即StdAfx.h和stdafx.h是等价的)。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd03694889.jpg)    2.2选中stdafx.cpp文件,右键属性(Properties),在Precompiled Header/Precompiled Header中选择Create(/Yc), Precompiled Header File中填stdafx.h。这样设置的作用是:每次编译stdafx.cpp文件时创建.pch文件(扩展名pch表示预编译头文件 )。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dd036b5383.jpg)    3.在stdafx.h的开发包含文件: #include  4.这时在我们的main函数中写入下面这句话,就可以弹出一个消息框: AfxMessageBox(L"非MFC工程使用MFC库", MB_OK, 0 ); # Stdafx.h的原理 关于stdafx.h的原理请看下一篇文章《[预编译头文件](http://blog.csdn.net/luoweifu/article/details/41527289)[(stdafx.h)](http://blog.csdn.net/luoweifu/article/details/41527289)[的原理](http://blog.csdn.net/luoweifu/article/details/41527289)》。
';

CString与string、char*的区别和转换

最后更新于:2022-04-01 23:01:02

我们在C++的开发中经常会碰到string、char*以及CString,这三种都表示字符串类型,有很多相似又不同的地方,常常让人混淆。下面详细介绍这三者的区别、联系和转换: ## 各自的区别 ### char*: char*是一个指向字符的指针,是一个内置类型。可以指向一个字符,也可以表示字符数组的首地址(首字符的地址)。我们更多的时候是用的它的第二的功能,来表示一个字符串,功能与字符串数组char ch[n]一样,表示字符串时,最后有一个 '\0'结束符作为字符串的结束标志。 【例1】 ~~~ #include using namespace std; void testCharArray() { char ch1[12] = "Hello Wrold"; //这里只能ch1[12],ch1[11]编译不通过,提示array bounds overflow char *pch1 , *pch2 = "string"; char *pch3, *pch4; pch3 = &ch1[2]; //ch1[2]的地址赋给pch3 char ch = 'c'; pch4 = &ch; pch1= ch1; cout << ch1 << endl; //输出ch1[0]到\0之前的所有字符 cout << pch1 << endl; //输出ch1[0]到\0之前的所有字符 cout << pch2 << endl; //输出ch1[0]到\0之前的所有字符 cout << pch3 << endl; //输出ch1[2]到\0之前的所有字符 cout << *pch3 << endl; //解引用pch3输出pch3指向的字符 cout << *pch4 << endl; //解引用pch4输出pch4指向的字符 } ~~~ **结果为:** Hello Wrold Hello Wrold string llo Wrold l C ### string: string是C++标准库(STL)中的类型,它是定义的一个类,定义在头文件中。里面包含了对字符串的各种常用操作,它较char*的优势是内容可以动态拓展,以及对字符串操作的方便快捷,用+号进行字符串的连接是最常用的操作。 【例2】 ~~~ #include void testString() { string s1 = "this"; string s2 = string(" is"); string s3, s4; s3 = string(" a").append("string."); s4 = s1 + s2 + s3; cout << s1 << endl; cout << s2 << endl; cout << s3 << endl; cout << s4 << endl; cout << s4.size() << endl; s4.insert(s4.end()-7, 1, ' '); cout << s4 << endl; } ~~~ **结果为:** this  is  astring. this is astring. 16 this is a string. ### CString CString常用于MFC编程中,是属于MFC的类,如从对话框中利用GetWindowText得到的字符串就是CString类型,CString定义在头文件中。CString(typedef CStringT> CString)为Visual C++中最常用的字符串类,继承自CSimpleStringT类,主要应用在MFC和ATL编程中,所以使用CString时要包含afx.h文件#include 。 【例3】 ~~~ #include //因为CString不是标准C++库定义的类型,没有对<<运算符进行重载, //所以不能通过cout<property中,C/C++里面Preprocessor(预编译),加入_AFXDLL这个宏,OK搞定!! **方法2**:对着你的项目点击右键,依次选择:属性、配置属性、常规,然后右边有个“项目默认值”,下面有个MFC的使用,选择“在共享 DLL 中使用 MFC”,就OK了~~~ 参考文章:[http://blog.csdn.net/ahjxly/article/details/8465209](http://blog.csdn.net/ahjxly/article/details/8465209)                          [http://blog.csdn.net/zhoxier/article/details/7929920](http://blog.csdn.net/zhoxier/article/details/7929920)   讲明白了char*、string及CString的关系,可能有人对、、这几个头文件又糊涂了,由于篇幅的原因,这部分的内容将在下一节进行说明,欢迎阅读:《[ 与、的区别](http://blog.csdn.net/luoweifu/article/details/20242307)》   ## 相互的转换 既然这三种类型都可用于表示字符串,但又是不同的类型,那他们如何转换呢?可用的方法参见如下: ### char*与string的转换 【例4】 ~~~ void pCharToString() { //from char* to string char * ch = "hello world"; string s1 = ch; //直接初始化或赋值 string s2(ch), s3; s3 = string(ch); cout << s1 << endl; cout << s2 << endl; cout << s3 << endl; //from string to char* string str = string("string is commonly used."); /************************************************************************* 其实没有很大的必要将string转换成char*,因为string可以直接当成字符数组来使用, 即通过下标来访问字符元素,如str[1]表示第1个字符't' **************************************************************************/ const char *ch1 = str.c_str(); cout << ch1 << endl; } ~~~ **结果为:** hello world hello world hello world string is commonly used. ### char*与CString 【例5】 ~~~ void pCharToCString() { //from char* to CString char *ch = "char pointer."; CString cStr1 = ch; CString cStr2 = CString(ch); printCString(cStr1); printCString(cStr2); //from CString to char* CString cstr = "CString"; char* chs=cstr.getbuffer(0);//此方法在VS2010下编译不通过,原因见【例6】 cout << chs << endl; } ~~~ **结果为:** char pointer. char pointer. CString ## string与CString 【例6】 **结果为:** string1 to CString string2 to CString string3 to CString CString to string3 CString to string4 c_str()和data()区别是:前者返回带'/0'的字符串,后者则返回不带'/0'的字符串. 在VS2010环境下,cstr2.Format("%s", s2.c_str());cstr2.Format("%s", s3.data());及str=cstr3.GetBuffer(0);str = LPCSTR(cstr4); 可能会编不过,会报类似error C2664: 'void ATL::CStringT::Format(const wchar_t *,...)' : cannot convert parameter 1 from 'const char [3]' to 'const wchar_t *'的错误。这是因为你的工程的字符集不是多字节字符集,将你的工程属性设置为多字节字符集即可,方法是:右键点击你的工程,选择Properties\Configurations Properties\General,在右侧的Project Defaults下的Character Set选择Use Multi-Byte Character Set。 ## 总结 从灵活度来说,string最灵活易用,其次是CString,char*的拓展性和灵活性比较差。 一般来说在基于标准库开发时用string,在在MFC和ATL编程时用CString。 CString、string之间的转换还有其它的一些方向,但基本上都是通过char*作为桥梁,因为char*即可以方便地转换成string,也可以方便地转换成CString。 更多CString的用法也可参考以下链接,他们写的更详细,我就不再重复了。 http://www.cnblogs.com/Caiqinghua/archive/2009/02/16/1391190.html http://blog.csdn.net/lewutian/article/details/6787024 欢迎加入"C/C++梦之队" 学习群:226157456
';

前言

最后更新于:2022-04-01 23:00:59

> 原文出处:[编程语言专栏文章](http://blog.csdn.net/column/details/visualstudio.html) > 作者:[罗伟富](http://blog.csdn.net/luoweifu) **本系列文章经作者授权在看云整理发布,未经作者允许,请勿转载!** #带你玩转Visual Studio > 本系列是从事C++开发工作一年半以来对VS用法的经验和总结,主要内容有:1、Windows下Visual Studio在C++开发方面的功能和使用方法;2、用Visual Studio进行C++的高效开发;3、C++编译、链接的一些方法,以及部分跟编译相关的Window技术。
';