CONDITIONS and BRANCHES in C++(if statements)
C++ 条件和分支
if 语句
条件语句,if语句或者分支结构到底意味着什么?当代码需要去评估某个值来决定我们下一步执行的操作。当我们写if语句的时候,实际上有两件事发生,当我们运行代码时,就会有对实际条件情况的比较。该代码有实际条件的评估,然后又依赖于该评估的分支,所以换句话说,如果我们的条件评估为真,我们需要跳转到源代码的某个部分,如果评估为假,我们需要跳转到源代码的另一部分。当然我说的是源代码,但在一个正在运行的程序中,它实际上意味着机器指令,所以换句话说,我们跳转到我们机器代码或者CPU指令的分支中,或者跳转到我们的另一部分CPU指令中。
现在当我们启动一个引用程序时,整个应用程序及其模块都被加载到内存,所以基本上构成我们程序的指令都是存储在内存中。当我们遇到分支的条件时,我们告诉计算机跳转到这部分内存然后开始执行我们的指令,由于所有在内存和分支中跳跃,这实际上比我所说的要复杂一些,这里有很多值得探索的东西。我们比较条件,然后跳转到内存中的其他部分,然后执行指令。实际上意味着if语句和分支确实会带来一些开销。如果你尝试编写非常快的代码,你可能会决定不使用if语句。
实际上代码优化代码避免分支,避免比较,因为这样会让程序变慢。这个系列的最后我们会开一些将分支结构去除的优化教程。无论如何不要让这太复杂,记住一个条件在我们的if语句的条件下,如果为真,那么我们想要跳转或执行一组代码,这就是基础的全部内容。
Equality Operator
int x = 5;
bool comparisonResult = x == 5;
==被称为相等运算符,它基本上是检查x是否等于5,等于为true,不等于为false。这个运算符是这个含义的原因是它在C的标准库中已经被加重载过了。某人写了一个函数,可以接纳两个整数,然后从内存中调出整数,进行比较确保是否相等。
实际上在大多数原始数据结构中,当你检查两个整数是否相等时,其实时把他们的四个字节给抓取出来,然后逐字节逐位地进行比较,在内存中对应位的二进制数都得相同,才能使这个整数相等
if(comparisonResult)
{
//主体语句
Log("Hello World!");
}
运行代码,控制台输出Hello World!
如果我们想输入了解背后的运行原理,我们可以查看汇编代码。
Disassembly View
调试代码的时候右击查看反汇编
int main()
{
00007FF7A5A165A0 push rbp
00007FF7A5A165A2 push rdi
00007FF7A5A165A3 sub rsp,128h
00007FF7A5A165AA lea rbp,[rsp+20h]
00007FF7A5A165AF lea rcx,[__08FD88C6_main@cpp (07FF7A5A230E6h)]
00007FF7A5A165B6 call __CheckForDebuggerJustMyCode (07FF7A5A113F2h)
int x = 6;
00007FF7A5A165BB mov dword ptr [x],6 //将6加载到寄存器中
bool comparisonResult = x == 5;
00007FF7A5A165C2 cmp dword ptr [x],5 //将5加载到同一个寄存器中
00007FF7A5A165C6 jne main+34h (07FF7A5A165D4h) //jne(jump not equal)
//不等于会跳转 07FF7A5A165D4h 这个内存(实际在下面能看到这个内存)
00007FF7A5A165C8 mov dword ptr [rbp+0F4h],1 //这行代表真。
00007FF7A5A165D2 jmp main+3Eh (07FF7A5A165DEh)
00007FF7A5A165D4 mov dword ptr [rbp+0F4h],0 //跳转到这一行
//将值0移动到这个寄存器中(事实这个结果就是false,0代表false)
00007FF7A5A165DE movzx eax,byte ptr [rbp+0F4h]
00007FF7A5A165E5 mov byte ptr [comparisonResult],al
if (comparisonResult)
00007FF7A5A165E8 movzx eax,byte ptr [comparisonResult] //把变量加载到寄存器中
00007FF7A5A165EC test eax,eax //查看我们的eax寄存器是否为真
00007FF7A5A165EE je main+5Ch (07FF7A5A165FCh) //跳转到相等的地方
{
Log("Hello World!");
00007FF7A5A165F0 lea rcx,[string "Hello World!" (07FF7A5A1AC18h)]
00007FF7A5A165F7 call Log (07FF7A5A11352h)
}
std::cin.get();
00007FF7A5A165FC mov rcx,qword ptr [__imp_std::cin (07FF7A5A21190h)]
00007FF7A5A16603 call qword ptr [__imp_std::basic_istream<char,std::char_traits<char> >::get (07FF7A5A21150h)]
}
下面的汇编代码对应上面的语句。对照汇编代码,我可以实际上发生了什么。
if 语句
如果我们创建一个布尔值实际上会占用一个字节的内存,我们没有必要确保那位设置为1,所以就像真的一样,如果有任何东西,不是0,那么就为真。如果我们只处理一位,他们它可能会是真的,当然我们只有两个可能的值0和1。如果它是0,那它会是假的。
上述是Debug模式下,编译器没有优化,x=6和5不一定要到运行的时候再比较,我们可以在编译的时候就可以进行比较。这种称为常数折叠的技术,所以它把这些转到一个常变量中,因为这些常量都会在编译时被知晓。如果比较结果,它实际上和这个if语句全部放在一起。既然if语句里面不会执行,那么就不需要编译了。当然这些都是在优化模式下进行,如果你想知道原理还是要在debug模式下。
布尔值实际上只是一个整数它时0表示false而不是其他任何东西,例如1表示true,所以if语句实际上在做什么是检查这个数字是不是0,如果是0那么它就不会执行那个if语句,但如果它是0以外的任何东西,比如1它会跳到if语句里面。
if(x==5)
Log("Hello World!");
也可以直接把x=5直接写到f语句里面 。还可以将if语句的大括号取消,这样写。如果将Log函数和if语句写在一行,那么调试的时候会有影响。
if(x)
Log("Hello World!");
我们也可以这样写if(x),if仅仅判断值是不是0。
const char* ptr="Hello";
if(ptr)
Log("Hello World");
这样对检查指针也很常见,如果我们想检查指针是否为null,null当然是0。我们基本上可以像这样将指针放到if语句条件中,看看它是否是null。if判断的是ptr是否是有效值。这样只能在C++中,但是不能再java或者C#中
else if 语句
const char* ptr = nullptr;
if(ptr)
Log("Hello World!");
else if (ptr == "Hello")
Log("ptr is Hello")
其实是
if(ptr)
Log("Hello World!");
else
{
if(ptr == "Hello")
Log("ptr is Hello")
}
上面的语句可以拆分成下面的形式,else if 并不是什么关键字组合,它其实就是else{ if() }的简写形式。C++中没有else if这样的关键字,就仅仅是else 和if的组合
编程有两个真实的部分,一种是数学编程,一种是逻辑编程,只是我喜欢将编程分为这两个部分。当然也有些编程你只要进行数学运算即可。大多数快速代码实际上是通过像数学运算一样编写的,前面提到的逻辑编程就比较少,他们完全是和逻辑相关的。