下面的代码为什么在VC2010下面编译不过去?
#include <iostream.h>
int main()
{
cout<<"Hello World."<<endl;
return 0;
}
错误信息:fatal error C1083: 无法打开包括文件:“iostream.h”: No such file ordirectory
造成这个错误的原因在于历史原因,在过去C++98标准尚未订立的时候,C++的标准输入输出流确实是定义在这个文件里面的,这是C风格的定义方法,随着C++98标准的确定,iostream.h已经被取消,至少在VC2010下面是这样的,取而代之的是我们要用<iostream>头文件来代替,你甚至可以认为<iostream>是这样定义的:
namespacestd
{
#include "iostream.h"
}
因此我们可以简单的修改我们的Hello World。
#include<iostream>
using namespace std;
int main()
{
cout<<"Hello World."<<endl;
return 0;
}
iostream.h是属于C++的头文件,而非C的,因此标准订立的时候被改成了<iostream>。而C的头文件stdio.h等依然可以继续使用,这是为了兼容C代码。但是它们依然有对应的C++版本,如<cstdio><cstdlib>等。记住,在VC2010上面采用C++风格的头文件而不是C风格的头文件,除非你是在用C。
warning C4996
这是一个警告,请看下面的代码:
#include <iostream>
using namespace std;
int main()
{
char sz[128] = {0};
strcpy( sz, "Hello World!" );
cout<< sz << endl;
return 0;
}
上面的strcpy会产生这个警告:
warning C4996: 'strcpy': This function or variablemay be unsafe. Consider using strcpy_s instead. To disable deprecation, use_CRT_SECURE_NO_WARNINGS. See online help for details.
这是因为VC从2005版本开始,微软引入了一系列的安全加强的函数来增强CRT(C运行时),这里对应的是strcpy_s。_s意为safe的意思,同样的道理,strcat也是同样。因此要解决这个问题,我们可以用strcpy_s来替换strcpy,但是注意strcpy_s并非所有编译器都提供,因此如果要跨编译器,请采用错误信息中所提示的方式,定义_CRT_SECURE_NO_WARNINGS宏来掩耳盗铃吧。另外注意并非所有的加强函数都是在屁股后面加_s,比如stricmp这个字符串比较函数的增强版名字是_stricmp。下面,用strcpy_s来更改程序:
int main()
{
char sz[128] = {0};
strcpy_s( sz, "Hello World!" );
cout<< sz << endl;
char* pSz2 = new char[128];
strcpy_s( pSz2, 128, "hello");
cout<< pSz2 << endl;
delete pSz2;
return 0;
}
注意,strcpy_s有两个版本,一个可以帮助我们自动推断缓冲区的大小,而另外一个不能帮助我们推断,因此在编译器不能推断缓冲区大小的时候,我们需要自己指定缓冲区的大小,如上面的程序所演示的那样,关于增强版的函数请参考我写的《深入学习C++ String2.1版》。
TCHAR、wchar_t、char
请大家看下面这个程序:
#include <iostream>
#include <Windows.h>
#include <tchar.h>
using namespace std;
int main()
{
MessageBox( NULL, "你好HelloWorld!", "Information", 0 );
return 0;
}
貌似没什么问题吧?错了,如果你是按照我教你的方法创建的控制台空工程的话,那么会有编译错误:
error C2664: “MessageBoxW”: 不能将参数 2 从“const char [17]”转换为“LPCWSTR”
这个问题太普遍了,几乎所有的初学者都会遇到而且感到难以应付,因为按照提示使用(LPCWSTR)强制转型貌似并不能帮助我们解决问题,而且这个程序在VC6下面应该是没有任何问题的,那问题出现在哪里呢?问题在这里,请右键单击解决方案浏览器下面的项目,属性,
问题的根本就是字符集问题,在VC6中,我们默认使用的是多字节字符集,而现在我们默认需要的是UNICODE字符集,简单的,我们把这个字符集改成多字节字符集这个问题就解决了:
再试试应该就可以了吧?但是我并不推荐大家这么做,因为让自己的程序适应各种字符集是我们写代码的人义不容辞的义务。
我们把程序改成下面这样:
#include <iostream>
#include <Windows.h>
#include <tchar.h>
using namespace std;
int main()
{
MessageBox( NULL, TEXT("你好HelloWorld!"), TEXT("Information"), 0 );
MessageBox( NULL, _T("你好HelloWorld!"), _T("Information"), 0 );
return 0;
}
用两个宏TEXT或者_T都可以解决这个问题,它们两个并没有太大区别,也许区别在于前者是通过windows.h头文件引入的,而_T是通过tchar.h引入的,我推荐大家使用_T和tchar.h,因为tchar.h还帮助我们引入了其它一些很有用的宏,比如_tcscpy_s,这个宏在使用UNICODE字符集的时候被替换成wcscpy_s,在使用多字节字符集的使用被替换成strcpy_s。关于这部分的内容,请大家不要错过《Windows核心编程》的第二章(第四版或第五版都可以),以及《深入学习C++ String2.1版》。它们都有提到。
有人听说_T可以把多字节字符串转换成UNICODE,因此他写了如下的代码:
const char* pStr = "haha哈哈";
MessageBox( NULL, _T(pStr), _T("Information"), 0 );
当然,除非你运气好的抓狂,否则你是编译不过去的,为什么呢?我们现在应该知道对于"Hello"这样的字符串,VC2010会默认的将它视为const char*,即多字节字符串,而L"Hello"前面有个L前缀的被视为UNICODE字符串,这和C#是有区别的,因为C#的字符串总是被视为UNICODE,C++/CLI下面编译器也会帮助我们做到这件事情,所以它们不需要L(C++/CLI兼容L这种写法)。
让我们看看_T的定义吧:
#definewxCONCAT_HELPER(text, line) text ## line
/* could already be defined by tchar.h(it's quasi standard) */
#ifndef _T
#if !wxUSE_UNICODE
#define _T(x) x
#else /* Unicode */
/* use wxCONCAT_HELPER so that xcould be expanded if it's a macro */
#define _T(x) wxCONCAT_HELPER(L, x)
#endif /* ASCII/Unicode */
#endif /* !defined(_T) */
_T在UNICODE下面最终会被替换成L ## x。 ##是一个编译预处理指令,意味着让L和x贴在一起,比如L ## "Hello"最终就是L"Hello",因此它可以把"Hello"转换成UNICODE字符串。那为什么上面的程序不行呢?让我们看看_T("pStr")会被替换成什么:L ## pStr ->LpStr,哦,LpStr是一个新的标识符,如果你没有定义过它,你当然不能通过编译啦。
因此我们可以了解到_T这样的宏只能处理直接的常量字符串,不能处理其它的情况。而我们上面演示的那种情况需要我们动态的去转换编码,Windows有API可以帮助我们做到,C库也有函数可以帮助我们。恰好我曾经写过这样的代码,欢迎大家参考:ASCII/UNICODE/UTF8字符串互相转换的C++代码
对于_T宏,再说一点东西,或许你会感到奇怪为什么_T不直接定义成#define _T(x) L ##x,而要绕个圈子去调用wxCONCAT_HELPER呢?这实际上涉及到宏展开顺序和截断的问题。在这里,我们需要说一个宏参数的概念,这很函数的参数是类似的,这里_T(x)的x就是宏参数,好,记住下面一句话:
如果你定义的宏中使用了#或者是##的话,宏参数将不会被展开,也就是说_T(x)如果直接定义成L##x那么在下面这种情况就会出错( PS: #是给参数加引号的意思):
_T(__FUNCTION__),__FUNCTION__是一个预定义的宏,它代表了当前函数的名字,这个展开会是什么呢?L__FUNCTION__。为什么间接调用wxCONCAT_HELPER就能得到正确的结果呢?因为当我们调用wxCONCAT_HELPER的时候,__FUNCTION__已经被_T展开成了函数名。
说多了说多了,如果你觉得复杂可以暂时跳过这些东西,我只是顺便说说。
重定义的编译错误和链接错误
让我们在项目里面再添加一个Test.h头文件,方法是右击解决方案中的项目,添加,新建项,C++头文件,名称输入test.h。然后我们在test.h中输入:
/*#pragma once*/
void print()
{
}
回到main.cpp中:
#include <iostream>
using namespace std;
#include "Test.h"
#include "Test.h"
int main()
{
return 0;
}
编译一下我们会得到重定义的编译错误:
error C2084: 函数“void print(void)”已有主体
或许你会说,你引用(#include)了两次,我没你那么傻,我只引用一次不就好了么?是的。你聪明,但是是小聪明哈,因为你不能保证每个人都不去引用它。
这个问题演示的是#pragma once的用处,让我们解开它的注释。编译成功!#pragma once的作用就在于防止头文件被多次引用。你或许见过
#ifndef __TEST_H__
#define__TEST_H__
代码
#endif
这样的代码,它们的作用是一样的,如果你跟我一样懒,那么就用#pragma once,如果你打算去没有这个指令的编译器上编译代码,那么还是用后面一种方式吧。
现在让我们来见识一个对初学者稍微复杂一点的链接错误,用创建main.cpp的方法再添加一个test.h头文件,输入#include "Test.h"即可。
让我们再编译一次。
1>test.obj : error LNK2005: "void__cdecl print(void)" (?print@@YAXXZ) 已经在 Main.obj 中定义
1>e:\documents\visual studio 2010\Projects\HelloWorld\Debug\HelloWorld.exe :fatal error LNK1169: 找到一个或多个多重定义的符号
如果说编译错误好找的话,链接错误对于初学者来说就有点麻烦了,聪明的初学者会去Google、百度寻找答案,笨的初学者就会找所谓的高手、前辈问,而这些高手Or前辈未必有心情为你解释。要解决这个错误有无数种方法。
1.内联,把print声明为内联函数。
inline void print()
{
}
这个方法的好处是简单,坏处是局限性太强,意味着你总是需要公开print的实现,因为内联函数必须在编译时就知道实现才行。
2.static,把print声明为static函数:
static void print()。
这便告诉编译器,哥是唯一的,而且哥只能被本编译单元的代码调用,这和extern是对应的。简单来说,想要哥帮你做事,请先include哥声明的头文件,也就是#include "test.h"。
3..h头文件中只放声明,实现放到.cpp中去。
现在test.h中只有void print();,而实现在test.cpp中:
#include "Test.h"
voidprint()
{
int a = 1;
cout<< a++ << endl;
}
这个时候有意思的是我们在main.cpp无需包含test.h头文件也可以引用print函数,因为print并非static的函数:
void print();
int main()
{
print();
print();
return 0;
}
但是声明一下是必须的。
由于百度空间的帖子的篇幅是有限制的,因此今天只好就说这么几点了。新的内容请大家等候下一章。
合理组织项目、使用外部工具让工作更...
这一章跟大家分享一些与c++项目管理、VAX、SVN、VS快捷键等方面的东西。
有效的在项目中组织C++文件,分配各种文件的目录对以后的维护会有好处的,至少不会出现不知道什么东西在什么地方,特别是大的项目,这里用TextSearcher来做例子。
使用SVN来管理项目会让我们的工作更轻松,工作也会更简单容易。
掌握常用的快捷键和常用的VS功能让我们的工作更有效。
合理的组织文件体系
首先说在IDE中为我们的文件分类组织,如下图所示:
我把不同功能的代码和文件放在不同的Filter下面,如何添加这样的Filter呢?
这样就可以添加筛选器了,默认情况下VS为我们创建三个筛选器:头文件、源文件和资源文件,实际上我们可以再增加很多。这样区分开的好处就是各个功能的代码被分开了,在文件很多的情况下不会造成混乱。如TextSearcher,它的搜索算法、软件控制逻辑、自定义控件、GUI模块、线程化操作都是分开的,这样我可以很容易找到我想找的文件,而且还可以检视自己的模块划分是否合理等等。
接下来推荐大家在项目资源浏览器中为不同的文件划分目录。
比如上图将头文件和源文件、资源图标文件、本地化文件和配置文件分开组织,这样也是为了防止混乱。值得注意的是当我们把文件用文件分开的时候,需要在项目属性设置里面包含我们的子目录,否则我们无法在源文件中直接用#include指令包含我们的头文件。如下图所示这样的情况,如果不添加,无法找到头文件。
如下图所示,找到项目属性中,C++,常规中把我们的子目录作为附加路径添加到“附加包含目录”中。
使用SVN或其它源代码管理工具管理我们的项目
如果你打算写一个比较大一点的项目,我推荐你使用源代码管理工具来管理你的C++项目,你可以选择SVN,也可以选择其它的,我推荐SVN,因为简单容易上手。
当你在做一个很复杂的东西的时候,花了两三天的时间去做修改,不过后来发现这个修改并不合适,想还原到三天以前,如果你没有用源代码管理工具管理自己的项目也没有自己手动的备份,那恭喜你,你得开始人肉还原了,这是多么悲剧的一件事情啊,然而如果你使用了SVN管理的话,只需要在三天前开始修改前的最后一次稳定版本Commit一次,三天之后如果要还原,只需要使用工具Revert就好了,而且不但可以回到三天前的版本,你甚至可以回到以前每一次Commit的版本,(⊙o⊙ )哇,这是多么好的工具啊!
要使用SVN,首先需要SVN客户端,SVN服务器是可选的。我推荐大家使用TortoiseSVN这个SVN客户端,因为它简单易用、免费,支持Windows32Bit、64Bit,你可以去他们的官方网站下载,地址点我。有了这个工具当然还不够,为了让我们的SVN跟VS2010结合的更紧密,我们需要再下载一个SVN For VS的插件,我强烈推荐你使用它,因为它也非常简单易用,下载地址点我。最新版是支持VS2010的。当你装了这个插件的时候,如果你的项目是在SVN的管理之下,那么你的项目看起来会一些不同:
看到文件左边的勾了吗?灰色的勾表示正常,而橙色的勾表示已经更改了,而PendingChanges则告诉我们哪些文件时新加的,哪些文件被改过了,如果要还原的话,只需要选择该文件,右键点击,Revert就好了:
如果你已经安装好了AnkhSVN插件但是又看不到的话,那么请检查下系统选项卡里面是否选择了它作为默认的源代码管理工具:
回过头去继续说SVN,当我们安装好TortoiseSVN之后(安装后可能需要重启),我们在资源管理器中点击右键的时候就能看到它的菜单了:
虽然TortoiseSVN有中文语言包,但是我推荐大家使用英文的,就像古诗一定要用中文来表达一样,没有比英语单词表达SVN项目管理更恰当的词了。
SVN CheckOut可以让你获取其它地方SVN服务器上面的某个项目的源码,当然,前提是你要有权限才行,现在让我们试试。随便找个盘符如F盘,点右键,选择 SVN CheckOut,然后Urlof Repository中输入svn://www.svnhost.cn/TextSearcher这个地址,其它的不变,点击OK。如图所示:
如果不出意外你能看到:
如果你看到这个画面说明你已经CheckOut成功了,那么恭喜你,你已经取到了TextSearcher的源码,当然要说一点的是,这个源码可能你取到之后编译不了,因为还有另外一些依赖项如dbsoft、boost、wxWidgets并不在这个SVN上面。
这样大家就可以去一些开源的网站上面Check你想要的东西了,哈哈。推荐大家去这个网站找自己感兴趣的代码CheckOut:http://sourceforge.net/
现在我们说如何管理自己的项目,大家可以像我一样去一些提供免费SVN服务器服务的网站上面注册一个账号建立项目就好了,比如说我使用的这个www.svnhost.cn,这个毕竟是国内的,另外上面的sourceforge.net也可以。当你注册建立项目成功之后就可以通过菜单Import把需要导入的东西导入到SVN服务器了:
导入界面的地址栏输入我们的SVN服务器地址即可,类似于:svn://www.svnhost.cn/TextSearcher。导出的时候记得输入日志。除了可以导入SVN服务器之外,我们还可以使用文件协议在自己的电脑上管理我们的代码,如果你不需要在多台电脑上面共同维护这个项目的话。
让我们随便找个地方新建一个文件夹,如D:\TestSVNServer这个文件夹,然后对着这个新建的文件夹点右键,选择SVN>Create repository here,OK,你的本地SVN服务器已经建好了。如果创建成功,你会发现这个文件夹里面多了好多东西:
没关系,这是SVN服务必须要的一些东西,现在让我们使用文件协议导入我们的项目到该SVN服务器中,找到我们要导入的项目,跟导入网络上的SVN服务器一样,右击项目文件夹,SVN>Import。在Import界面中这样填:
不出意外你会导入成功,那么现在我们需要去其它地方重新获取这些东西了,换个地方,比如F盘根目录,选择SVN CheckOut,然后地址输入刚才导入的地址:
注意如上图这样是不行的,因为我们没办法在F盘根目录下面创建一个D:\TestSVNServer文件夹,把D:\这个SVN帮我们自己填充的路径删掉然后点确定。
现在我们已经CheckOut了刚才导入的项目了,Planet文件夹已经带上了一个绿色的勾。点击这个Planet目录,选择SVN>ShowLog,你会看到:
在这个界面我们还可以检查代码的改变都是什么,这里由于是新的项目,所以没有更改,我们可以找到TextSearcher的更改来比较它们的改动都是什么:
Show Changes:
可能你弹出的界面跟我的不一样,因为我的比较工具是外部工具而不是默认自带的,因为默认自带的有问题,在合并的时候总是出错(公司项目)。所以我对默认的没有好感。这个软件的名称是:Araxis.Merge.Professional.2010,你可以去搜索来下载,这里有一份可以试试,不行就自己去搜索吧。
装好这个软件之后需要去SVN里面设置一下,SVN》Setting,设置如下:
这样就可以使用外部工具了。
下面再说明一下如何提交代码(Commit).
我们可以通过右击被SVN管理的项目,选择SVNCommit输入相关信息之后就可以提交了,通常提交都需要你提供用户名和密码验证等等,特别是网络上的SVN服务器,本地SVN服务器可以不管。
注意,SVN不会把你新添加的文件默认选中,它只会默认选中已经在SVN中添加了的文件。所以当新加了文件的时候注意勾上,防止漏传,这个在现在的SVN管理中漏上传时非常普遍且经常发生的事情。那么现在用VS的SVN插件也可以上传啊,对于添加到VS2010的IDE中的文件我推荐大家使用这种方式,因为这样可以有效的防止漏上传新加的代码,注意,有的二进制文件如资源文件MP3、PNG或者其他资源打包文件等可能不会添加到IDE中,因此这时候一定要注意不要漏传,去资源管理器中右击项目,SVN》ADD,将新加的有用的文件添加进去,一些临时文件和垃圾文件不要添加。
VS的SVN插件要提交更简单,在Pending Changes中选择要上传的,点击右键,Commit即可:
如果你的这个选项卡没有调出来可以通过视图》PendingChanges把它调出来。
如果项目中的其它人更新了代码,版本增加,那么请用Update获取最新的代码。
对于SVN,有很多丰富的内容,不是这里短短的时间可以说完的,建议大家去申请一个SVN服务器自己多使用一下,多摆弄下就熟悉了,能够熟练掌握SVN这样的源代码管理工具在找工作的时候是一个加分的不错的砝码。
使用Visual AssistX让我们的工作更有效
Visual AssistX(简称VAX)是一款优秀的VS辅助插件,最新版支持VS2010。如果你有钱而且生活宽裕,建议你购买正版,你可以去他们的官方网站购买;如果你和我一样是个不折不扣的穷鬼,那么你可以点这里去下载破解版,当然请大家在心里感谢VAX的工程师们吧。
通常我们安装好VAX之后它会默认的帮我们开启它的大部分功能,而且这通常都是足够的,使用VAX的View选项卡可以轻松的在大型项目中找到我们想要的文件,如下图所示:
在符号表中还能轻松的找到我们的类在什么地方:
自动着色、自动添加括号,自动换行等等功能可以给我们莫大的帮助。
通过编辑Snippet可以让我们轻松添加Doxygen风格的注释帮助:
通过VAX的OutLine选项卡让我们阅读代码变得轻松:
没有这两个选项卡可以通过VAX的菜单,Tools把它们调出来。更多功能请自己去体会。
让我们熟悉VS2010的快捷键和其它一些有用的功能
VS2010是一个强大的工具,下面介绍一些常用的功能和快捷键。
1. 搜索所有文件
大部分朋友都知道用Ctrl+F来搜索和替换文件,但是并非每个朋友都知道Ctrl+Shift+F可以搜索所有文件:
我们还可以通过点击工具栏上面的那个按钮来弹出这个界面,这个界面可以搜索整个解决方案(一个解决方案可以有多个项目)、整个项目、打开的文件、单个文件和选定内容,还支持普通搜索、正则表达式和通配符搜索,如上图所示,我们搜索所有的boost。查找结果1会显示出项目文件中所有出现boost的地方,不区分大小写,不完整匹配:
双击就可以定位到行。
2.格式化代码先按Ctrl+K 接着按Ctrl+F
通常我们都应该保持良好的编码风格,除非你打算参加“国际混乱程序大赛”。这个功能实际上在菜单》编辑》高级中就有,至少大部分人都不喜欢去挖掘这些功能。通常我们都是要格式化当前文件的所有代码,所以这个快捷键通常和Ctrl+A组合(全选)使用。先全选,然后再全部格式化。
3. Ctrl + Tab 在前一个文件和后一个文件之间切换
首先我们打开多个文件,然后要切来切去用哪个,就用这个Ctrl + Tab:
4. VAX快捷键Alt+G
这个快捷键超有用,当你把光标定位到头文件,Alt+G会打开那个头文件,当你把光标定位到类、变量、函数等的时候,Alt+G都会跳到它的定义或者实现。它等于装了VAX之后右上角的那个GO!按钮。为什么要用这个,VS不是也有智能感知和识别吗?当你用VS多了的时候你就知道VS的智能感知是多么弱智的了。
5.编译生成调试相关
F5调试Ctrl+F5直接运行 F7生成解决方案即生成所有项目 Ctrl+Alt+F7强行重新生成所有项目
F9 当前光标处设置断点F10逐语句不进入子过程 F11逐语句并进入子过程
6. VAX快捷键Shift+Alt+O打开文件
这个和上面说的VAXView中搜索并打开文件的功能差不多,这个会更友好一些。
7. 其它
编辑器相关的 Ctrl+S保存 Ctrl + Z 撤销Ctrl+Y重做 ...
这一章的内容到此结束了,希望对大家有帮助,另外说一下,以上内容不仅适用于VC2010,包括VC2005、VC2008都是适用的。下一章再见。
系列六:VC2010常见调试技术
犹豫了好久,最终还是决定开始这一章,因为我不清楚到底有没有必要写这样的一章,是应该在这里说明一些简单的调试方法,还是干脆直接让大家去看《Visual C++ 2005入门经典》的第10章,因为那里已经说出了我们几乎所有的常见调试方法。
另外一点就是这一章也许会是《Visual C++ 2010入门教程》系列的最后一章了,因为在入门的这方面,我已经找不到值得和大家分享的经验了,算是黔驴技穷了吧。回头看看这个系列,最初的目的就是为了解决一些初学者常见的问题,教会初学者如何使用VS2010这个工具,因为我也经历过那些阶段,我希望我能帮助那些“曾经的我”少走弯路。
过去,我们讨论了一些诸如C++和VC有什么区别、怎么用C++做项目这样的问题,介绍了SVN的使用,常见功能的快捷方式,VC配置等等。接下来就是最后的一些与调试相关的东西与大家分享。另外强烈推荐对基本VC调试技术不熟悉的朋友去看看《Visual C++ 2005入门经典》的第十章。
断点
没有比断点更常用的了,通过点击代码左边边栏或者移动光标到指定行按F9等都可以添加断点。值得注意的是并非每一行都可以添加断点,这个就留给大家去实践中体会吧。
通过菜单》调试》窗口》断点或者直接按Alt+F9可以调出断点选项卡,通过选项卡我们可以设置条件断点、数据断点等。
单步和监控
调试中除了F5之外,另外更常用的估计是F10、F11了,前者是一次一个语句的执行,或者可以看出一行;而后者如果出现能进入的子过程,那么就会进入子过程。这个请大家找个程序,至少要有函数调用的,当断点触发的时候,请自行体验一下F10和F11的效果你就明白了。说白了,实践才是最好的老师,我负责告诉你有这么个东西。
两个选项卡,局部变量和自动变量,它们都负责显示一些当前断住状态下的变量的值,注意,这些只有在程序中断的时候才有意义。自动变量选项卡并非指auto变量,而是指VS帮我们猜想我们可能感兴趣的一些变量的值,或者函数返回值,而局部变量基本上就是本过程的一些变量的值了。
注意,这些选项卡不仅仅可以用于查看,甚至可以用于你临时修改它们的值,方法就是双击值就可以了,如下图所示:
这时候你可以把它临时改成false都行哈。
大家看到监视1了吗?这个选项卡是留给用户的,如果前面的变量太多你不想用滚动条滚来滚去的看就可以在这里输入要监视的变量了:
这里我检查了当前语言字符串的设置。注意并非什么变量都可以检视,必须是调试器可以为我们推断出来的才行,即调试器知道它的地址是什么。如上图所示有一个特殊的用法$err,hr这个是VS特别的,它的意义相当于让调试器帮你获取GetLastError的值,这在Windows编程的时候非常有用。
对于一些指针类型的变量我们还可以在监视里面对它做强制转型,比如你的函数传递一个void* p进来,但是你知道这次你传递的是一个Data结构体的指针,而调试器是无法知道这个p指向的是Data,所以你可以在监视中输入(Data*)p。这样调试器会自动帮我们把他当做Data结构体的指针来识别。
调用堆栈
调试过程中调用堆栈实在是太重要了,因为它指出了你的程序是正在处于什么状态,是谁调用了谁:
如果你没有这个选项卡可以通过Alt+7或者调试》窗口》调用堆栈把它调出来。
运行时也可以获取调用堆栈的,这个需要Windows API的帮助,这个请看我写的这个。
日志
日志有很多种,你可以写一个专门的日志系统来处理日常的日志工作,但是这里我只说把信息数出到VC的输出窗口,像这样:
使用WindowsAPI OutputDebugString来实现,当然你也可以对他做一些封装,在程序中在重要的代码部分记录下日志,这对调试很有帮助,你这样会一眼知道哪里出了问题,甚至你可以把调用对战嵌入到这个包装中去:
通过模块选项卡发现外部模块错误
假设我们依赖于一个外部库Test.dll,这个DLL在系统目录下面有一个,而在Path路径下面还有一个,而他们的版本不同,甚至只是名字相同而内容完全不同。或者其他一系列的类似的问题,都可以通过模块选项卡来察觉,另外这个模块选项卡还告诉了我们我们依赖了那些外部DLL,这在发布的时候很有用,使得我们可以漏掉需要的DLL。
通过暂停按钮发觉死锁和死循环
当我们的程序失去响应的时候我们不妨尝试点击调试窗口上面的暂停按钮:
如果中断(暂停)成功那么我们会看到死锁或者死循环的调用堆栈了。
断言(assert)
assert大家应该很熟悉了吧,这是最直接提供错误信息的方法了。特别的,当我们在调试的时候,调试器会帮助我们定位到断言触发的地方。
暂时就想到这么多,如果您还有其它好Case,一定不要忘了要同大家分享。
转眼间毕业一年了,感触良多。每当我情绪低落的时候,我就看苏珊大妈的视频,因为苏珊大妈是我的偶像,因为她告诉我有梦想就一定要坚持,当你具备了成功的基础的时候,梦想就会慢慢的实现。