第六章 分支结构
计算机语言和人类语言类似,人类语言是为了解决人与人之间交流的问题,而计算机语言是为了解决程序员与计算机之间交流的问题。程序员编写的程序就是计算机的控制指令,控制计算机的运行。借助于编译工具,可以将各种不同的编程语言的源程序转换为计算机可以执行的机器语言。
计算机程序是计算机的控制指令,控制计算机完成特定的功能。我们编写源程序的过程和建造房屋的过程类似,大致分为五个步骤。
第一步:设计程序的架构,类似于房屋设计。先设计好图纸之后再进行下一步的建造工作。
第二步:准备程序需要的数据,相当于建造房屋的原材料。
第三步:分析程序功能实现的算法,相当于建造房屋的工艺流程。
第四步:编写源程序——开始建造。
第五步:编写完成后调试程序——建造房屋后的清理修补工作。
上述五个步骤中,最重要的就是程序的设计工作。如果设计出了问题,会导致后续工作的失败。
针对功能较为复杂的程序,程序开发有一套标准的流程,我们将上述五个步骤进一步细化:
第一步:分析需求,设计程序结构框架;
第二步:数据定义,定义恰当的数据结构;
第三步:分析算法;
第四步:编写伪代码,即用我们自己的语言来编写程序;
第五步:画流程图,使用Visio、Excel或者其他绘图工具绘制算法流程和逻辑关系图;
第六步:编写源程序,其实就是将我们的伪代码翻译成计算机语言;
第七步:调试程序,修复程序中可能出现的BUG;
第八步:优化代码,尝试更好的设计方案,效率更高的算法,逻辑更为清晰简洁明了。这一步可以使我们学到更多的东西,何乐而不为呢。
在今后的学习中,建议读者严格遵循这样的流程,养成良好的编程习惯,受益终身。
提示
请记住两句话:
程序=数据结构+算法。
代码量的多少是衡量程序员水平最客观的标准。
数据结构是第二步要做的事情,算法是第三步要做的事情。
数据结构就是将数据以什么样的形式存储到计算机内存。数据存储需要考虑以下几个问题:
1.以字节为单位,还是以字为单位,或者是其他大小的内存块为单位存储数据;
2.以连续的形式存储(例如数组),还是不连续的形式存储(例如链表)到计算机的内存;
3.数据是在刚加载程序时就存储到内存(静态分配内存),还是在程序的执行过程中存储到内存(动态分配内存);
4.数据是以数值的方式存储和引用,还是以地址的方式存储和引用,还是两种方式的组合。
5.数据是以什么样的顺序存储,升序还是降序等。
选择合适的数据结构非常重要,关系到程序执行的效率和功能的实现。好的数据结构,可以大大简化程序功能实现的算法。
算法就是程序功能实现的具体流程。计算机程序中的算法和数学中的算法相似,但不完全相同,更多的时候,我们将程序中的算法称为程序执行的流程和先后顺序。
上述提示内容在刚开始学习时不要求理解,随着代码量的增加,会终有所悟。
在程序设计时需要使用三种基本的结构:顺序结构、分支结构和循环结构。不论多么复杂的程序,都是由这三种结构组合而成。接下来,我们分别学习这三种程序设计结构的构建方法。
■顺序结构:按照指令代码排列的先后顺序执行,称之为顺序结构。
■分支结构:指程序根据一定的条件有选择的执行路径。
■循环结构:指在程序中需要反复执行某个功能而设置的一种程序结构,可以看成是一个条件判断语句和一个向回转向语句的组合。
顺序结构相对比较简单,分支结构和循环结构中必定包含顺序结构语句块,本书不再赘述。接下来我们将在本章讲述分支结构的程序设计方法,下一章讲述循环结构的程序设计方法。
本章学习知识概要:
if句
ese语句
switch语句
6.1 if语句
本节必须掌握的知识点:
示例十八
代码分析
汇编解析
示例十九
代码分析
汇编解析
什么是语句?在C语言中,语句大部分是由分号结尾的。
举例
int a = 0;
int b = 1; int c = 2; int d = 3;
语句包括:赋值表达式语句、空语句、复合语句、函数表达式语句、控制语句等。
本章我们将要学习if语句、else if句和switch语句。
6.1.1 示例十八
■if语句表达形式1
程序执行到if语句时,判断表达式的值,如果结果为真(非0),则执行相应的语句。
if(表达式){
statement
}
示例代码十八
●第一步:分析需求,设计程序结构框架。
分析需求:判断输入的整数,是否能被2整除。
设计程序结构框架:分支结构(if语句形式1)if语句。
●第二步:数据定义,定义恰当的数据结构;
int num;//定义一个int类型的整型局部变量。
●第三步:分析算法。
整数num除以2,如果余数为0,则可以被2整除。如果余数不为0,则不能被2整除。
●第四步:编写伪代码,即用我们自己的语言来编写程序。
int main(void) {
定义一个int类型整型变量num;
调用printf函数打印一个提示信息"请输入一个整数:";
调用scanf_s函数接收键盘输入一个整数,并存入变量num;
if (num % 2) 如果条件为真(余数不为0)
调用printf函数输出"您输入的整数不能被2整除!";
system("pause");
return 0;
}
图6-1 示例十八流程图
●第五步:画流程图,使用Visio、Excel或者其他绘图工具绘制算法流程和逻辑关系图; 【注】强烈建议读者在编写工程项目时,一定要写伪代码、画流程图,具体流程图详解见附录D。 本书在后续章节中可能会由于节约篇幅的考虑,省略了部分流程。
●第六步:编写源程序,其实就是将我们的伪代码翻译成计算机语言;
/*
判断输入的整数,是否能被2整除
*/
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int num;
printf("请输入一个整数:");
scanf_s("%d", &num);
if (num % 2)
printf("您输入的整数不能被2整除!\n");//单语句可以省略大括号
system("pause");
return 0;
}
●输出结果:
请输入一个整数:3
您输入的整数不能被2整除!
●第七步:调试程序,修复程序中可能出现的BUG;
参见反汇编代码。
●第八步:优化代码,尝试更好的设计方案,效率更高的算法,逻辑更为清晰简洁明了。
示例十九中我们将改用“if语句表达形式2(if/else结构)”。
6.1.2 代码分析
示例十八由键盘输入一个整数值存入变量num。然后采用num%2取模的算法作为if语句的条件,余数为0,条件为假,不会执行if语句块内的printf语句。如果余数非零,条件为真,执行if语句块内的printf语句。
总结
1.在C语言的语法中规定,大括号内的语句称为语句块,例如函数体、if语句块、while语句块,或者任意大括号内的语句块。
2.如果语句块内的语句为单语句,则可以省略大括号。如果大括号内的语句为复合语句,则不可以省略大括号。
3.C语言语句的缩进关系表示从属关系,例如:
if (num % 2)
printf("您输入的整数不能被2整除!\n");//单语句可以省略大括号
printf语句的缩进表示该语句从属于if语句块。如果没有省略大括号,则不存在任何疑义。
6.1.3 汇编解析
■汇编代码
;C标准库头文件和导入库
include vcIO.inc
.data
num sdword ?
.const
szMsg1 db "请输入一个整数:",0
szMsg2 db "%d",0
szMsg3 db "您输入的整数不能被2整除!",0dh,0ah,0
.code
start:
;输入整数num
invoke printf,offset szMsg1
invoke scanf,offset szMsg2,ADDR num
;
mov eax,num
mov ebx,2
cdq ;被除数扩展到EDX:EAX
idiv ebx
.if edx
invoke printf,offset szMsg3
.endif
;
invoke _getch
ret
end start
●输出结果:
请输入一个整数:3
您输入的整数不能被2整除!
上述汇编代码中,取模运算采用的是有符号数除法指令idiv。因为num为int类型的有符号整数,作为被除数需要使用cdq指令将其扩展为64位EDX:EAX,除数2存入ebx寄存器,然后使用idiv指令除以ebx,商保存在eax寄存器中,余数保存在edx寄存器中。接下来使用高级汇编伪指令.if,edx余数作为其条件表达式,如果edx不为0,则条件为真,执行下面的printf语句。
■反汇编代码
int num;
printf("请输入一个整数:");
002D1952 push offset string "\xc7\xeb\xca\xe4\xc8\xeb\xd2\xbb\xb8\xf6\xd5\xfb\xca\xfd\xa3\xba" (02D7B30h)
002D1957 call _printf (02D104Bh)
002D195C add esp,4
scanf_s("%d", &num);
002D195F lea eax,[num]
002D1962 push eax
002D1963 push offset string "%d" (02D7B44h)
scanf_s("%d", &num);
002D1968 call _scanf_s (02D1154h)
002D196D add esp,8
if (num % 2)
002D1970 mov eax,dword ptr [num]
002D1973 and eax,80000001h
002D1978 jns main+5Fh (02D197Fh) ;符号位为0,最高位为0,即正整数时则跳转
002D197A dec eax ;为负整数时,最高位为1,先减1
002D197B or eax,0FFFFFFFEh ;除最高位和最低位,其余各位置1
002D197E inc eax ;最低位加1,如果最低位为1,则奇数+1为0,否则为偶数
002D197F test eax,eax ;与运算,不保存结果,测试eax值是否为0
002D1981 je main+70h (02D1990h) ;如果eax为0则跳转02D1990h地址处,否则为真
printf("您输入的整数不能被2整除!\n");//单语句可以省略大括号
002D1983 push offset string "\xc4\xfa\xca\xe4\xc8\xeb\xb5\xc4\xd5\xfb\xca\xfd\xb2\xbb\xc4\xdc\xb1\xbb2\xd5\xfb\xb3\xfd!\n" (02D7B48h)
002D1988 call _printf (02D104Bh)
002D198D add esp,4
system("pause");
002D1990 mov esi,esp
以上是C代码的反汇编代码,对比我们自己写的汇编代码,编译器翻译if(num%2)时有显著不同。请读者仔细阅读注释。这段反汇编代码是通过判断num为正整数还是负整数,是奇数还是偶数的方法,最终判断是否可以被2整除。其中使用了两个掩码80000001h和0FFFFFFFEh。如果num的二进制数据位的最后一位为1,则为奇数,最后一位为0,则为偶数。
【注】不同的VS版本的反汇编代码存在差异,但是功能和结果肯定没有问题。
6.1.4 示例十九
■if语句表达形式2
if语句是判断表达式是否成立,当表达式成立时,则做相应的事情;当表达式不成立时,则做另一件事情。语法格式:
if (表达式)
statement1
else
statement2
解析:如果条件成立为真(不为0)时,则执行语句statement1;否则执行语句statsement2。
示例代码十九
●第一步:分析需求,设计程序结构框架。
分析需求:判断输入的整数,是否能被2整除。
设计程序结构框架:分支结构(if语句形式2)if/else语句。
●第二步:数据定义,定义恰当的数据结构;
int num;//定义一个int类型的整型局部变量。
●第三步:分析算法。
整数num除以2,如果余数为0,则可以被2整除。如果余数不为0,则不能被2整除。
●第四步:编写伪代码,即用我们自己的语言来编写程序。
int main(void) {
定义一个int类型整型变量num;
调用printf函数打印一个提示信息"请输入一个整数:";
调用scanf_s函数接收键盘输入一个整数,并存入变量num;
if (num % 2) 如果条件为真(余数不为0)
调用printf函数输出"您输入的整数不能被2整除!";
else
调用printf函数输出"您输入的整数能被2整除!";
system("pause");
return 0;
}
●第五步:画流程图,使用Visio、Excel或者其他绘图工具绘制算法流程和逻辑关系图;
图6-2 示例十九流程图
●第六步:编写源程序,其实就是将我们的伪代码翻译成计算机语言;
/*
判断输入的整数,是否能被2整除
*/
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int num;
printf("请输入一个整数:");
scanf_s("%d", &num);
if (num % 2)
printf("您输入的整数不能被2整除!\n");//单语句可以省略大括号
else
printf("您输入的整数能被2整除!\n");//C语言中缩进表示从属关系
system("pause");
return 0;
}
●输出结果:
测试1
请输入一个整数:3
您输入的整数不能被2整除!
测试2
请输入一个整数:4
您输入的整数能被2整除!
6.1.5 代码分析
上述代码,if语句的条件表达式(num % 2)如果为真,则执行if语句块;如果条件为假,则执行else语句块。同时输出两种情形,不存在任何遗留的情况。代码逻辑更为清晰和完整,也更加人性化一些。
6.1.6 汇编解析
■汇编代码
;C标准库头文件和导入库
include vcIO.inc
.data
num sdword ?
.const
szMsg1 db "请输入一个整数:",0
szMsg2 db "%d",0
szMsg3 db "您输入的整数不能被2整除!",0dh,0ah,0
szMsg4 db "您输入的整数能被2整除!",0dh,0ah,0
.code
start:
;输入整数num
invoke printf,offset szMsg1
invoke scanf,offset szMsg2,ADDR num
;
mov eax,num
mov ebx,2
cdq ;被除数扩展到EDX:EAX
idiv ebx
.if edx
invoke printf,offset szMsg3
.else
invoke printf,offset szMsg4
.endif
;
invoke _getch
ret
end start
●输出结果:
测试1
请输入一个整数:3
您输入的整数不能被2整除!
测试2
请输入一个整数:4
您输入的整数能被2整除!
上述汇编代码使用了高级汇编伪指令.if/.else语句,与C语言if/else语句完全相同。
■反汇编代码
int num;
printf("请输入一个整数:");
00211952 push offset string "\xc7\xeb\xca\xe4\xc8\xeb\xd2\xbb\xb8\xf6\xd5\xfb\xca\xfd\xa3\xba" (0217B30h)
int num;
printf("请输入一个整数:");
00211957 call _printf (021104Bh)
0021195C add esp,4
scanf_s("%d", &num);
0021195F lea eax,[num]
00211962 push eax
00211963 push offset string "%d" (0217B44h)
00211968 call _scanf_s (0211154h)
0021196D add esp,8
if (num % 2)
00211970 mov eax,dword ptr [num]
00211973 and eax,80000001h
00211978 jns main+5Fh (021197Fh)
0021197A dec eax
0021197B or eax,0FFFFFFFEh
0021197E inc eax
0021197F test eax,eax
00211981 je main+72h (0211992h);如果条件为假,即eax=0则跳转0211992h地址处
printf("您输入的整数不能被2整除!\n");//单语句可以省略大括号;if语句块
00211983 push offset string "\xc4\xfa\xca\xe4\xc8\xeb\xb5\xc4\xd5\xfb\xca\xfd\xb2\xbb\xc4\xdc\xb1\xbb2\xd5\xfb\xb3\xfd!\n" (0217B48h)
00211988 call _printf (021104Bh)
0021198D add esp,4
00211990 jmp main+7Fh (021199Fh)
else
printf("您输入的整数能被2整除!\n");//C语言中缩进表示从属关系;else语句块
00211992 push offset string ;"\xc4\xfa\xca\xe4\xc8\xeb\xb5\xc4\xd5\xfb\xca\xfd\xc4\xdc\xb1\xbb2\xd5\xfb\xb3\xfd!\n" (0217B68h)
00211997 call _printf (021104Bh)
0021199C add esp,4
仔细阅读上述反汇编代码的注释:
0021197F test eax,eax
00211981 je main+72h (0211992h);如果条件为假,即eax=0则跳转0211992h地址处
if (num % 2)
如果条件为真,执行if语句块
printf("您输入的整数不能被2整除!\n");//单语句可以省略大括号;if语句块
else
如果条件为假,执行else语句块
printf("您输入的整数能被2整除!\n");//C语言中缩进表示从属关系;else语句块
00211992 push offset
练习
1、输入一个整数,判断是否可以被5整除。
2、输入一个整数,判断是否为奇数。
3、输入学生的成绩,判断是否及格(大于等于60分及格)。
本文摘自编程达人系列教材《汇编的角度——C语言》。