C++助教问题汇总1
周四实验课遇到的高频编程问题,简单汇总了一下。
如何进行断点调试
程序不动了是迭代次数太多了吗?
遇到这种情况先考虑是不是死循环了。
检查你的循环跳出条件,打断点,监视那些关键变量的变化是否符合预期。
程序出现运行时Bug(和令代码无法通过编译的Bug相区分)时,常用的调试手段有两种。
第一种是 打日志(log) 。
第二种是 断点(breakpoint)调试 。
首先在想要停下的行打上断点,这样调试运行时到此处会自动停下。
编译选项要选Debug,如果处在Release状态下运行,断点调试将被忽略。
点击“本地Windows调试器”开始调试。运行后程序将停在第一个断点处,黄色箭头(红色断点的上面)指示当前程序所执行到的位置。黄色箭头位于第7行,表示前面的都执行过一遍了,而当前所在行(第7行)还未执行。
调试中断时,鼠标放在变量上面能显示它当前值,或者看下方的“局部变量”表也行。因为第7行还未执行,此时变量i
的值为0。
点工具栏上面的“逐语句(F11)”,可以让程序往前执行一行。边上的“逐过程(F10)”其实也行,它们的区别在于遇到对函数的调用时,是跳进去继续追踪,还是等它跳出来再追踪。
连续点击“逐语句(F11)”,令程序连续执行好几步,这个过程中你可以观察变量i的值的变化情况。
逐语句运行时,每走一行,就停下来给你观察现场,并等待你决定接下来做什么。
把断点去掉以后点“继续”,程序就会继续不停地运行,也就是死循环,因为没有输出所以看起来就是黑窗口上只有光标在闪。
不想调试了就点那个方形的“停止调试”,程序就将结束。
inf,NaN是什么意思
inf,NaN是浮点数(float,double)数据类型中非常特殊的两个取值。
inf是无穷大(infinity)的缩写,对一个正数除0.0就会得到inf,对负数除0.0会得到负的inf,或者当试图用double存储一个超过其表示范围的数,比如说1e99999
,也会导致它变成inf)
NaN是非数字(Not A Number)的缩写,对负数开平方,对负数取对数,0.0除以0.0,0.0乘inf, inf除以inf等错误都会得到NaN
当你的浮点运算产生上述不合法运算时,程序不会直接报错,而是使你的运算结果变为这种特殊值。这种特殊值会在运算过程中不断传递,污染后续运算 (NaN加减乘除任何数还是NaN)。
当打印这两种特殊的数值时,控制台(console)会显示inf
,-nan(ind)
,如果你使用的是Visual Studio。或者你使用的是Dev C++等基于g++编译器的IDE,则显示为inf
,nan
。这只是显示方式上的区别!
示例代码如下:
1 |
|
x^y不是x的y次方吗
在C++里不是。
C++ 的^
运算符不是次幂运算符,这是数学符号习惯带来的第一个常见误会,下面的这个示例代码可以最直接地证明这一点。
1 |
|
尽管代码可以通过编译并正常运行,但^
在C++中是“异或运算符”,属于位运算符的一种(详细可参考这篇博客)。C++不存在次幂运算符,不过你可以调用cmath库中的pow()
指数函数来做到这一点。
1 |
|
单独cpp文件不能编译运行吗
为什么Visual Studio打开单独cpp文件不能运行,但交作业只需单独cpp文件?
这个问题解释起来有些复杂……
一言以蔽之,Visual Studio试图让“多文件联合编译”这个问题变得简单,但反而令“单文件编译”变得繁琐。
多文件联合编译是C++的常态
尽管刚开始这门课的时候,大家编写的C++程序只放在一个文件里,但在工程上,一个完整的C++程序项目,代码分散地放在数百个文件里是非常正常的事情。
这是我大三时编译原理实践的放C++代码的目录结构。编译时,需要将所有这些文件里的代码合并到一起编译,才能产生一个程序。
1 |
|
你肯定会想到,为了让编译器知道哪些文件需要参与编译,当然要有个地方存储这样一个索引。对于Visual Studio来说,你所新建的这个“项目”(Project)就主要做了这件事。
正是因为“项目”索引了该工程所涉及的所有代码文件,才使得Visual Studio得以提供跨文件的代码智能提示功能(intellisense),一键的“编译并运行”功能,智能跳转功能以及许许多多便捷功能,使得它荣获“宇宙第一IDE”之美称。
在业界,大部分时候一个单独的cpp文件都是不完整的(你的程序必须包含一个且最多一个main()
才能编译运行),所以Visual Studio被设计成了,在任何时候都需要先有一个项目(其中指明了存在哪些代码文件)才能编译运行。哪怕你的确只有一个cpp文件,你也需要先建立一个项目,再一步步正确地把代码文件添加进去。
对于使用Dev C++的同学,自然不会被这个问题困扰,因为和Visual Studio默认多文件联合编译相反,Dev C++默认进行单文件编译。你打开的是哪个文件,就编译运行的是哪个文件。如果只是经常写一些单文件的C++代码,那么无需每次都繁琐地建立工程文件,无疑是Dev C++的一大优势。
我还是要提醒一句,Visual Studio的编译器(msvc)和Dev C++的编译器(g++)的表现是不完全一样的,尤其是对变量未赋初值的处理方式是不同的。曾经就有同学习惯了g++贴心的变量初值自动置零,而忘记手动赋初值,最后在考场上被msvc狠狠制裁,丢了好几分。
构建系统(build system)不取决于打开了哪些文件
Visual Studio是一款IDE(Integrated Development Environment, 集成开发环境),这意味着它集成了C++的构建系统(也就是你点击“编译”时所运行的系统),能从代码文件生成可执行程序。而构建系统的基础,则在于如何找到生成最终程序所需的各个源代码文件。
构建系统也会随着你添加、删除项目中的代码文件而需要动态地变化。
但是什么叫做“项目中的代码文件”呢,是看它是否出现在了编辑器(也就是屏幕占比最大的中间那块编辑区域)里吗?不是的,对于Visual Studio来说,在“解决方案资源管理器”里看到的树状结构才是它认为的“项目中的代码文件”。
当你点击“编译并运行”时,你在编译的不是面前的这个代码文件,而是这个项目。
你面前的这个代码文件,不一定在当前项目里。同时,这个项目也不一定只包括了你面前的代码文件。
要想知道项目到底包含了哪些文件,一切都以“解决方案资源管理器”里的树状结构为准。
总之,Visual Studio所做的并不是“把打开的代码文件一起联合编译”,而是“按照项目索引的代码文件进行联合编译”。你爱打开几个文件打开几个文件,构建系统都不会变,除非你更改“解决方案资源管理器”里的文件结构。
单独打开cpp文件的迷惑之处
搞清楚上面那点,其实就足够了,下面的有点偏吐槽向。
在Visual Studio的设想下,开发者可以在一个项目内,对每个代码文件进行编辑,修改后编译并运行,一切都是如此美好。
然而有些迷惑人的地方在于,Visual Studio又占据了系统中cpp文件的默认打开方式。当你双击一个cpp文件时,这个cpp文件中的代码会弹出在Visual Studio里。此时你能看到这些代码的语法高亮,能够编辑修改它们,并且这个选项卡看起来和其他的没有什么区别。
按理说,我们只关心自己项目里的代码文件,既然当前打开的这个代码文件不属于我的项目,那按理说应该进入一个“临时浏览/编辑”模式,在显示方式上和项目内的代码文件做明显区分,方便我意识到它不在我项目内才对。
然而Visual Studio并没有这么做!哈哈!全都长得一模一样!
更本质的原因是,Visual Studio作为一款全能型的IDE,既包揽了编辑器的功能,又承接了维护构建系统的功能,但却只有一套IDE的界面。
当开发者打开项目内文件并编辑时,不仅需要语法高亮,修改了代码文件,同时也对构建系统产生影响,此时Visual Studio是作为IDE在工作。
但当开发者编辑项目外文件时,只是需要语法高亮,修改了代码文件,但不对构建系统产生影响,此时Visual Studio仅作为编辑器(editor)工作,这时界面上所有构建系统相关的按钮都变成了干扰视听的混淆项。
我认为规避这一问题的最好方法,就是始终从项目中打开cpp文件,而不通过cpp文件进入Visual Studio,并且将cpp文件的默认打开方式绑定到真正的编辑器上,比如notepad++,VS Code或者Sublime之类。
C++构建系统的原理
当然,我的这些解释有点太过拘泥于IDE的特性,而不是在聊C++本身了。如果你的好奇心依然未被满足,想搞明白关于C++单文件/ 多文件编译的更本质一些的原理,我建议你了解一下g++,也就是从命令行编译运行C++的流程。
Compiling C++ programs with g++
兴许你会开始对makefile和Cmake感兴趣。
C++逗号相关问题
我的评价是水很深。
逗号不是逻辑上的“并且”
把逗号当作逻辑上的“并且”(and)是数学符号习惯带来的第二个常见误会,下面是一个错误的例子:
1 |
|
正确的做法是使用逻辑运算符&&
。
逗号间隔的变量声明初始化是独立赋值
逗号还有一个常见误解是用在变量声明里.
1 |
|
这固然没什么问题,但如果还要赋初值的话。
1 |
|
请记住此时x
和y
并没有变成10,上述代码等价于
1 |
|
逗号运算符通常属于奇技淫巧
还有一种是作为逗号运算符使用,也称“顺序求值运算符”,用于将表达式“串联”起来,返回最后一个表达式的值。
常见于算法竞赛不择手段的压缩行数。
包括但不限于以下用法:
1 |
|
1 |
|
前者是一种畸形的、邪恶的、错误的美学追求,后者有一定的实用性但需要你把握得住。
如果你觉得你把握得住,试试看下面几个例子能不能迷倒你。
1 |
|
1 |
|
感谢阅读!
封面来自电影《飞驰人生》截图。