写在前面
此系列是本人一个字一个字码出来的,包括示例和实验截图。本人非计算机专业,可能对本教程涉及的事物没有了解的足够深入,如有错误,欢迎批评指正。 如有好的建议,欢迎反馈。码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作。如想转载,请把我的转载信息附在文章后面,并声明我的个人信息和本人博客地址即可,但必须事先通知我。
你如果是从中间插过来看的,请仔细阅读**(一)羽夏看C语言——简述** ,方便学习本教程。
if语句
生活中,经常会有选择或者情况需要自己判断,计算机也是如此。所有的判断语句还是后面将要介绍的循环其实都是由
JCC指令
组成的。我们先给出如下代码示例:
#include <iostream>using namespace std;//如果是C,请自行将头文件包含改为 stdio.h 和 stdlib.h//将 cout 改为 printf_s(可以 printf,但微软编译器编译会报错,自行科普)int main(){int c = 0;int re = 0;cout << "请输入数字:" << endl;cin >> c;if (c==0){re = -c;}else if (c==1){re = c + c;}else if (c==2){re = 4;}else if (c==3){re = c * c;}else if (c==4){re = c + c + 5;}else if (c==5){re = c;}else{re = -1;}system("pause");return 0;}
然后查看一下它的反汇编:
是不是很简单粗暴,每次需要判断是不是,不是再跳转,虽然结构清晰,但生成了大量的汇编代码,影响效率,写起来也挺费劲。
switch语句
switch
语句在多情况判断上是用的最多的,是
if
语句的升级版,绝大多数情况比单纯的
if-else
高效的多,下面我们用代码揭开它神秘的面纱:
#include <iostream>using namespace std;//如果是C,请自行将头文件包含改为 stdio.h 和 stdlib.h//将 cout 改为 printf_s(可以 printf,但微软编译器编译会报错,自行科普)//将 cin 改为 scanf_s(可以 scanf,但微软编译器编译会报错,自行科普)int main(){int c = 0;int re = 0;cout << "请输入数字:" << endl;cin >> c;switch (c){case 0:re = -c;break;case 1:re = c + c;break;case 2:re = 4;break;case 3:re = c * c;break;case 4:re = c + c + 5;break;case 5:re = c;break;default:re = -1;break;}system("pause");return 0;}
然后我们查看它的反汇编:
让我们分析一下比较有意思的反汇编:
mov eax,dword ptr [ebp-0Ch]mov dword ptr [ebp+FFFFFF20h],eaxcmp dword ptr [ebp+FFFFFF20h],5ja 0047255Fmov ecx,dword ptr [ebp+FFFFFF20h]jmp dword ptr [ecx*4+004725C8h]
ebp-0Ch
就是
c
的地址,它先比较这个东西是否
大于5
,如果大于直接到转到
0x0047255F
这个地址,也就是
default
语句,看来编译器还是挺“聪明的”。然而最“聪明”的不在这里,而是
jmp dword ptr [ecx*4+004725C8h]
这句汇编。让我们看看
0x04725C8
这个地址到底存储的是什么东西:
首先打开内存窗口,输入那个地址,然后在内存窗口显示右键选中
四个字节整数
,
没有文本
,
十六进制显示
即可。得到如下图结果:
如果你细心的话,你会发现这里面存储的都是每个
case
的地址,被称为地址表。我只需计算出一次结果,就可以跳转到我需要的位置。
咱们举的例子是情况连续的时候,如果不连续但差距不算太大呢,我们尝试把
case 3
删掉,看看有什么情况出现。
– 反汇编 –
– 地址表 –
可以看出表的成员个数不变,但被删除的
case
的地址处被填充了
default
语句的地址。编译器可以通过某种
推断
来实现地址表的构建提高运行效率,但是如果每个
case
没有任何规律可言的话,那会怎么样呢?
#include <iostream>using namespace std;//如果是C,请自行将头文件包含改为 stdio.h 和 stdlib.h//将 cout 改为 printf_s(可以 printf,但微软编译器编译会报错,自行科普)//将 cin 改为 scanf_s(可以 scanf,但微软编译器编译会报错,自行科普)int main(){int c = 0;int re = 0;cout << "请输入数字:" << endl;cin >> c;switch (c){case 0:re = -c;break;case 15:re = c + c;break;case 200:re = 4;break;case 489:re = c + c + 5;break;case 542:re = c;break;default:re = -1;break;}system("pause");return 0;}
然后看一下反汇编:
哈哈,这回编译器“找不到头脑了”,只能老老实实的用
if-else
的样式生成汇编了。
循环语句
循环语句应该是编程中经常会用到的语句。所有的形式示例如下:
for语句
for (int i = 0; i < 5; i++){//do something}
while语句
int i;do{//do something} while (i<5);
do语句
int i;while (i<5){//do something}
在汇编层面,所有循环到汇编的本质都是一样的,下面我们用代码进行验证:
#include <iostream>//如果是C,请自行将头文件包含改为 stdio.h 和 stdlib.hint main(){int c = 0;//for循环for (int i = 0; i < 5; i++){c++;}//do循环int i = 0;do{c++;i++;} while (i < 5);//while循环i = 0;while (i < 5){c++;i++;}system("pause");return 0;}
然后编译运行,查看它的反汇编,结果如下:
– for循环 –
– do循环 –
– while循环 –
跳转语句
break
、
continue
、
goto
被我统称为跳转语句。
break
和
continue
语句经常在循环语句和
switch
语句出现,经常和
if
配套以判断是否不符合循环条件跳出而使用。翻译到汇编层面,它不过就是一条
jmp
指令,
switch
语句的已经体现了。
goto
语句翻译到汇编也是一条
jmp
指令,但如果处理不善,就会打乱程序执行流程出现不太可预测的结果,不太建议使用。那我们做一个循环语句的,其他自行探索实验,代码如下:
#include <iostream>using namespace std;//如果是C,请自行将头文件包含改为 stdio.h 和 stdlib.h//将 cout 改为 printf_s(可以 printf,但微软编译器编译会报错,自行科普)int main(){for (int i = 0; i < 10; i++){label:if (i==2)continue;if (i == 7)goto label;if (i==8)break;}system("pause");return 0;}
for each语句
经查阅,这个语句仅在微软的编译器里面有。所以本人还是略微做一下实验,来看看
for each
语句到底为我们做了什么东西。在实验之前,需要通过
项目属性页
–
C/C++
–
语言
来关闭
符合模式
,代码如下:
#include <iostream>using namespace std;//如果是C,请自行将头文件包含改为 stdio.h 和 stdlib.h//将 cout 改为 printf_s(可以 printf,但微软编译器编译会报错,自行科普)int main(){int nums[] = { 1,2,3,4,5,6 };int num = 0;for each (int var in nums){num += var;}system("pause");return 0;}
然后看一下反汇编:
一个简简单单的
for each
却为我们生成了好几行代码,剩下的还请自行探索。
下一篇
(五)羽夏看C语言——结构体与类(C++)