2.4 监视/快速监视
当程序处于调试状态时,可以使用快速监视和监视窗口命令,执行变量值查看和修改等操作。
2.4.1 快速监视
快速监视(Quick Watch)每次只能查看或者修改一个变量的值,而且必须关闭“快速监视”窗口之后才能进行调试,因为它是一个模态的窗口。打开“快速监视”窗口的操作也比较简单:在调试状态并且程序处于暂停的情况下,将鼠标指针放到需要快速监视的变量上,然后单击鼠标右键,就会弹出“快速监视”的选择菜单,如图2-13所示。
图2-13 “快速监视”的选择菜单
然后用鼠标左键单击选择“快速监视”(或者按Shift+F9组合键),就会弹出“快速监视”窗口,同时会把该变量当前值的信息显示出来,如图2-14所示。无论该变量是一个简单变量、一个结构体或者一个类,相应的信息都会显示出来,而且非常清晰。
图2-14 通过“快速监视”窗口查看变量
同时“快速监视”可以修改变量的值,也可以修改结构体对象的某个字段或者类对象的某些成员变量的值等。比如我们要修改图2-13中的bus变量下bus_seat_number字段的值,只需要用鼠标左键双击它进入修改状态,即可进行修改。我们可以按回车键来保存对这个字段的修改,也可以通过将鼠标指针移动到其他字段来保存修改。完成修改后,“快速监视”窗口对变量的值的显示会有一个变化,提示该变量已经被修改,在变量对应的行(即第一行)会用不同的颜色来显示,如图2-15所示。
图2-15 通过“快速监视”窗口修改变量的值
2.4.2 监视窗口
监视(Watch)窗口是“快速监视”窗口的升级版本,不存在“快速监视”窗口的局限性,在调试状态下,可以通过“调试”菜单的“窗口”命令来打开“监视”窗口。“监视”窗口可以同时显示或者修改多个变量,而且可以同时打开多个“监视”窗口。如果在调试过程中想要监视的变量非常多,则“监视”窗口会非常有帮助。比如在同时调试多个程序的情况下,可以针对每一个程序使用一个“监视”窗口,从而避免一个“监视”窗口中的变量太多而引起混淆。
可以通过拖动的方式将希望监视的变量添加到“监视”窗口中,也可以在变量上单击鼠标右键,然后选择“添加监视”命令添加要监视的项。
还可以在“监视”窗口中直接输入要监视的变量名。如果该变量名在当前调试的上下文中有效,那么该变量就会成功被监视;如果该变量名不存在或者找不到,那么就会提示错误。“监视”窗口如图2-16所示。
图2-16 “监视”窗口
图2-16中有两个“监视”窗口:“监视1”和“监视2”。其中“监视1”窗口中共监视了4个变量:bus_no、bus、test_val以及number。test_val是一个无效的变量,所以它的值显示为“未定义标识符”。而number是一个在该调试上下文中无效的变量,即number不是全局变量,也不是在该函数内可以访问的变量,所以它显示为被禁用的状态(number其实是另外一个函数中的一个临时变量,是在调试那个函数的时候被添加到“监视1”窗口中的)。
“监视”窗口和“快速监视”窗口的功能相同,可以修改变量的值,该功能非常有用。有时在调试过程中,程序执行到断点处暂停下来,我们没有得到期望的变量值,于是无法执行某些特殊的语句,这时就可以去修改该变量的值。比如图2-16中变量bus的ordered字段,当前的值是true,因为它是一个布尔型的值,后面执行的逻辑会因为这个值而有所不同。如果想查看ordered的值为false的执行情况,那么可以直接将其修改为false或者0,程序继续执行就会得到期望的结果。
如果变量是简单数据类型,比如整型、布尔型等,修改起来是比较方便的,可以直接修改。字符数组修改起来则稍微麻烦一些,需要定位到具体的某个位置,修改对应的某个字符,而不能像整型、布尔型等变量那样直接修改整个变量。比如我们想修改图2-16中的bus_no变量,由于bus_no是一个字符串类型,当前值为“京A78169”,如果想将其修改成“京A78179”,就需要定位到bus_no[6],然后将其修改为“7”,如图2-17所示。如果希望修改为中文字符,就需要知道中文字符对应的ASCII编码(一个中文字符对应两个ASCII字符)。
图2-17 修改字符串变量
对于标准库的字符串(比如std::string类型),仍然可以通过这种方式进行修改,这是VC 2019的增强功能。早期的VC版本不能修改标准库的std::string类型变量,甚至连监视和查看字符串内容都不容易实现。
即使一个变量被声明为const,仍然可以在“监视”窗口中进行修改。还可以在“监视”窗口中计算表达式的值,使用四则运算来进行一些简单的计算;也可以使用C/C++的一些关键字,比如查看某个数据类型的大小、计算系统结构体的大小等。
如果变量是一个指针(比如字符串指针),那么修改起来不是特别直观。例如,将字符串变量const char* test_str = "this is a test string"添加到“监视”窗口中,可以发现“监视”窗口中只显示了第一个字符,其余的字符并没有显示出来。如果想修改字符串中的某个字符,就只能将指针变量对应位置的值添加到“监视”窗口中。比如要把“a”修改成“b”,就需要将test_str[8]添加到“监视”窗口中,然后进行修改,如图2-18所示。
图2-18 修改字符串指针的值
2.4.3 表达式支持
处于调试状态时,可以在“监视”窗口或者“快速监视”窗口中输入表达式,并且可以计算表达式的值。
1.内嵌函数支持
在使用表达式时,可以使用调试器支持的一些函数(也称为内嵌函数),这些函数的名称和功能与Windows API或者C/C++运行时库中的函数名称和功能都是相同的,分别为字符串长度函数、字符串比较函数、字符串查找函数、Win32 API等,如表2-3所示。
表2-3 VC调试器内嵌函数列表
下面通过几个简单的例子来了解如何在“监视”窗口中调用这些内嵌函数,如图2-19所示。
图2-19 “监视”窗口调用调试器内嵌函数
在图2-19中,“监视”窗口中调用了字符串相关的strlen、wcslen、strcmp、strchr和GetLastError,都成功地返回了结果。
2.不支持的表达式
虽然可以在调试时使用一些表达式和一些调试器内嵌函数,但是其中有些表达式是不受支持的,这些表达式不能在监视窗口中使用。
● 构造函数、析构函数、类型转换:比如下面这些构造函数的方式和类型转换都是不受支持的表达式。
CMyClass myclass (CMyClass)test
● 预处理器宏:预处理器宏也是不受支持的,比如定义了一个宏“#define MAX_VALUE 100”,那么在“监视”窗口中就不能使用MAX_VALUE。
● 不能使用using namespace声明:如果要访问一个类型名称或当前命名空间之外的变量,就必须使用完全限定的名称。