通过上次课题的讲述,相信大家一定对什么是数据及数据的定义和使用方法有一定的了解了,在看本次课题之前希望大家能趁热打铁,再到网上下本C语言的教程,最好是买本数来巩固下变量相关的知识。
大家明白了怎么定义变量,怎么给变量赋值了,自然的就应该进一步了解一下一些详细的代码怎么编写了。
今天我们的任务比较简单,就讲一下如何编写代码及实现代码的流程控制。应该说这个是非常简单的东西了,本来我没打算讲它,可是本着一步一个脚印的原则,我还是简要的说一下。在本次课题之后,我会紧跟着出一个函数的专题,来作为代码篇的完善,至此大家应该能独立的写自己的程序了。
本次课题知识点不是很多(也不少,但是都很简单。),主要是在于多写,多练,知道自己能把一些现实的问题转换成代码来解决问题。
不多废话,先说下本次课题要写的主要内容:
- 程序的运算和逻辑判断
- 三种程序流程控制结构详解。
- 养成良好的代码编写风格。
- 结束语
下面开始进入正题。
一、 程序的运算和逻辑判断。
说计算是电脑最基本的功能,相必没有人会反对的。做程序,自然最基础的就是这些运算了。
我知道,看这个破烂文章的朋友对数据应该都是不感冒的。不过大家放心,这里牵扯的运算都很简单,就是小学的加、减、成、除、取余数,是、非、与或和位移,没别的了。先让我们来了解下算术运算。
1. 算术运算。
加减乘除相比大家都应该很了解的。这里我不想费太多的篇幅讲述这些大家都明白的知识,我把要说的知识列出来,大家自己了解就好。
用于算术运算的运算符有:
我想不用我说,加、减、成、除这些运算符大家都接触过的,关于“%”运算符,其实就是取余数;用这些运算符可以将一些数字,变量等连接起来,进行运算,这样的式子叫做表达式。例如:
int i =5;
i +7; // 这里就是一个表达式。
“=”这个运算符其实不属于算术运算,它也并非是我们数学课上讲的“等于”,而是一个“赋值”运算符,它用来将一个常量(数字、字母等)赋值给一个变量的赋值运算符,由于我们进行算术运算以后,通常都会将运算结果保存到一个变量中,所以,我把这个赋值运算符归类到这里。而我们日常的“等于”运算符是:“= =”,它属于逻辑运算符,我们将在下一节中讲到它。
关于“++ 、 — —”这两个运算符,上图中已经说明了,它是对变量的对自己的自加或者自减运算,等同于:变量 = 变量+1; 或者 变量 = 变量 — 1;
现在让我们来举几个例子,来说明下这些算术运算符的用法。
void main()
{
int x = 1;
int y = 2;
int z = 20;
x ++; // 等同于x = x + 1;
y --; // 等同于y = y - 1;
z /= x; // 等同于z = z / 1;
printf("%d, %d, %d\n", x, y, z); // 打印 x,y,z 的结果。
z %= x; // 计算得出 z / x 以后的余数
x = x+y;
z -= x;
printf("%d, %d, %d\n", x, y, z); // 打印 x,y,z 的结果。
}
请先不要看下面的截图,先看下这个程序,分析一下,这两次的输出结果会是多少,然后对比下图:
看下就算的是不是正确,如果正确了,那恭喜你,基础的算术运算就算通过了,接下来,我们需要调试这段代码,来熟悉一些相关的汇编命令,具体操作如下图所示:
在代码上右击鼠标,选择如下命令:
我们来详细分析一下我们现在看到的代码:
1: #include "stdio.h"
2:
3: void main()
4: {
00401010 push ebp
00401011 mov ebp,esp ;// 将现有的堆栈给了EBP寄存器。
00401013 sub esp,4Ch ;// 分配x4C大小的堆栈空间用来运算。
00401016 push ebx
00401017 push esi
00401018 push edi ;// 保存寄存器环境。
00401019 lea edi,[ebp-4Ch] ;// 这里用ebp寄存器减去一个偏移来定位我们定义的变量,
0040101C mov ecx,13h ;// 这里-4Ch是用来定位到堆栈头,把堆栈内容改成int3中断以免内存泄露。
00401021 mov eax,0CCCCCCCCh ;// 以上操作是保存堆栈环境,分配堆栈空间。
00401026 rep stos dword ptr [edi] ;// 在下一次课题讲述函数时,我们会讲到,这里大家可以略过。
5: int x = 1;
00401028 mov dword ptr [ebp-4],1;// [ebp-4]是我们的变量x,dword ptr是用来修饰这个变量是DWORD类型(也就是整型)。
6: int y = 2;
0040102F mov dword ptr [ebp-8],2;// MOV就是汇编指令,相当于我们C语言中"=" 赋值运算符,它的具体用法自己百度。
7: int z = 20;
00401036 mov dword ptr [ebp-0Ch],14h
8:
9: x ++; // 等同于x = x + 1;
0040103D mov eax,dword ptr [ebp-4]
00401040 add eax,1; // add指令就是我们C语言的"+"运算符,还有一个INC指令相当于我们的"++"运算符
00401043 mov dword ptr [ebp-4],eax
10: y --; // 等同于y = y - 1;
00401046 mov ecx,dword ptr [ebp-8]
00401049 sub ecx,1
0040104C mov dword ptr [ebp-8],ecx
11:
12: z /= x; // 等同于z = z / 1;
0040104F mov eax,dword ptr [ebp-0Ch]
00401052 cdq
00401053 idiv eax,dword ptr [ebp-4]
00401056 mov dword ptr [ebp-0Ch],eax
13: printf("%d, %d, %d\n", x, y, z);
00401059 mov edx,dword ptr [ebp-0Ch]
0040105C push edx
0040105D mov eax,dword ptr [ebp-8]
00401060 push eax
00401061 mov ecx,dword ptr [ebp-4]
00401064 push ecx
00401065 push offset string "%d, %d, %d\n" (0042001c) // 传递参数,具体规则将在下次课题“函数”中讲述。
0040106A call printf (004010f0) ; // 调用printf函数打印结果
0040106F add esp,10h ; // 这里是C条用的对战平衡方式。(具体将在下次课题“函数”中讲述)
14:
15: z %= x;
00401072 mov eax,dword ptr [ebp-0Ch]
00401075 cdq
00401076 idiv eax,dword ptr [ebp-4]
00401079 mov dword ptr [ebp-0Ch],edx
16:
17: x = x+y;
0040107C mov edx,dword ptr [ebp-4]
0040107F add edx,dword ptr [ebp-8]
00401082 mov dword ptr [ebp-4],edx
18: z -= x;
00401085 mov eax,dword ptr [ebp-0Ch]
00401088 sub eax,dword ptr [ebp-4]
0040108B mov dword ptr [ebp-0Ch],eax
19: printf("%d, %d, %d\n", x, y, z);
0040108E mov ecx,dword ptr [ebp-0Ch]
00401091 push ecx
00401092 mov edx,dword ptr [ebp-8]
00401095 push edx
00401096 mov eax,dword ptr [ebp-4]
00401099 push eax
0040109A push offset string "%d, %d, %d\n" (0042001c)
0040109F call printf (004010f0)
004010A4 add esp,10h
20: }
004010A7 pop edi ; // 恢复寄存器环境
004010A8 pop esi
004010A9 pop ebx
004010AA add esp,4Ch ; // 平衡堆栈
004010AD cmp ebp,esp
004010AF call __chkesp (00401170) ; // DEBUG 模式程序专用的堆栈检查函数。
004010B4 mov esp,ebp
004010B6 pop ebp
004010B7 ret
相信你根据上述代码中的提示,应该能将这个汇编代码看的差不多,当然,看不明白也没有关系,我们需要掌握的汇编指令及其用法很少,就下面几个:
mov/lea: 赋值/取地址。
add: 加法指令。
sub: 减法指令。
div/idiv: 除法指令。
mul/imul: 乘法指令。
这些汇编指令的具体用法大家自己百度或者参考相关资料,这里不做详细说明, 下面开始我们的逻辑、关系运算。
2. 逻辑、关系运算。
提起什么逻辑运算,或者什么关系运算,看名字貌似很复杂的。不过这里可能让大家放心的是,这些运算我们日常生活中经常用到,无非就是 真的,假的,是,不是,并且,或者之类的操作。
用于逻辑运算的运算符有如下几个:
运算符
|
含义
|
&&
|
与(并且)
|
||
|
或(或者)
|
!
|
非(不是)
|
用于关系运算的运算符有如下几个:
这些运算无非就是为了判断一个表达式成立不成立,在C语言中,只要表达式的值不为零并且符合关系运算符的要求,那这个表达式就成立的,就可以用上述的两种运算符进行比较运算,一般情况下,这些运算符会配合下节我们要讲述的流程控制语句来使用,所以这里我就不给出具体用法的例子了,有情趣的朋友,可以继续看下面章节中的例子
到现在我想主要的运算我都讲完了,虽然不是很详细,但是我想,只要大家坚持努力,多多百度,这些知识一定会掌握好的。
小学的时候,我们学过四则运算,在运算的时候,遵循先乘除,后加减,有括弧的先算括弧里面的,这个规则在这里一样使用,只不过在编程环境中,运算符很多,所以需要有个更为详细的运算符优先级表。这里我把它贴出来,但是还希望大家能够尽量的使用括弧来让人看的容易,以免出错,具体优先级表转载如下:
更详细的用法可以参考如下链接:http://www.xxlinux.com/linux/article/development/soft/20060909/4128.html
二、 三种程序流程控制结构详解。
有了上述的运算,我想大家都可以写出一些很简单的代码了,但是我们在写代码的时候,肯定会遇到类似这样的问题:
Ø 有时候,我们写的代码必须要在达到某种条件之后才可以执行,否则不让运行。
Ø 有时候,我们写的代码很庞大,很罗嗦,因为它有几种可能需要我们来写出几个程序。
Ø 有时候,我们写的代码需要一直重复执行直到某种条件不成立了才不执行。
只要你遇到过上述的问题,那我们这节课的内容就正是你所需要的。不多废话,进入正题:
1. 顺序结构
到现在为止,我们写的所有的代码都是顺序结构的,所谓顺序结构,就是代码从第一条指令开始执行,直到执行完最后一条。中间不会落下任何一条指令。
想必大家现在应该能理解什么是顺序结构了,所以我不想再这里浪费太多的篇幅,直接进入下一小节。
2. 分支结构
所谓分支结构,就是代码在达到某种条件的时候,执行某些指令,否则就执行别的指令。
分支结构是改变代码执行顺序最简单的方式,所以大家一定可以很容易的掌握它的,下面让我们一个一个的来看。
a) if … else结构
这个结构算是编程中最基础的结构了,它有三种格式,我在这里列出来,大家可以根据实际情况选择使用哪个:
第一种格式:
if (条件表达式)
{
//条件成立时执行这里的语句
…
}
第二种格式:
if (条件表达式)
{
//条件成立时执行这里的语句
…
}
else
{
//条件不成立时执行这里的语句
…
}
第三种格式:
if (条件表达式1)
{
//条件表达式1成立时执行这里的语句
…
}
else if (条件表达式2) // 这里的else if可以有无限多个(如果有很多个时可以参考使用switch语句)。
{
//条件表达式2成立时执行这里的语句
…
}
else
{
//条件表达式都不成立时执行这里的语句
…
}
为了更好的说明这个语句的用法,我举个例子:
int MaxNum(int num001, int num002, int num003)
{
if (num001 >= num002)
{
if (num001 >= num003)
{
return num001; // 将 num001 作为函数的结果返回出来。
}
else
{
return num003;
}
}
else
{
if (num002 >= num003)
{
return num002;
}
else
{
return num003;
}
}
}
说明:上述代码中的功能是从提供的三个数:num001、num002、num003中选出最大的数来。
至于与这些if有关的汇编指令就是跳转,像sub,cmp,test,之类的比较指令来影响相应的标志寄存器还有JE,JNE,JB,JNB之类的跳转指令来跳转到指定的代码中执行,大家可以像我们分析算术运算的方式一样,去调试它,去分析它,去掌握这些比较和跳转指令的用法。
这里我就省下篇幅,继续我们的switch结构。
b) switch … 结构
上小节中讲述的if语句,是用于少数分支时的处理语句,它写起来方便,代码简洁明了,但是如果一些表达式的结构可能有5种甚至更多种结果,需要我们分别作出不同的处理时,最好的选择就是用wsitch语句。
先说明一下switch结构的语法格式:
switch(表达式结果或者存放结果的变量)
{
case 结果1:
// 当switch后的括弧中的值是结果时,就执行这里的语句
...;
break; // break是用来跳出分支结构的关键字,如果这里没有它,只要结果是结果1,那从结果1开始向下的所有指令都会被执行(包括结果2,结果3……)。
case 结果2:
// 当switch后的括弧中的值是结果时,就执行这里的语句
...;
break; // 如果这里没有这个关键字时,只要上面的结果是结果2,那从结果2开始向下的所有指令都会被执行(包括结果3,结果4……)。
case 结果3:
// 当switch后的括弧中的值是结果时,就执行这里的语句
...;
break; // 同上
case 结果N:
// 当switch后的括弧中的值是结果N时,就执行这里的语句
...;
break;
default:
// 当switch后的括弧中的值不是上面列出的任何一个值时,就执行这里的语句
...;
}
这里写的可能有点模糊,我给出一个代码片段,我说明一下switch语句的用法:
PGAME_CHAR_INFO pGCI = GetCharInfoPoint();
switch (pGCI->dwZhiYe)
{
case 1:
lstrcpyW(szTemp, L"灵剑\0");
break;
case 2:
lstrcpyW(szTemp, L"日羽\0");
break;
case 3:
lstrcpyW(szTemp, L"枪侠\0");
break;
case 4:
lstrcpyW(szTemp, L"萨满\0");
break;
case 5:
lstrcpyW(szTemp, L"法皇\0");
break;
case 6:
lstrcpyW(szTemp, L"药王\0");
break;
default:
lstrcpyW(szTemp, L"未知\0");
break;
}
其实,这个switch的汇编形式跟if结构很像,唯一的区别就是每个分支后面都会有一个break跳转(JMP)指令,大家可以自己试着去调试这段代码,分析一下尽量掌握这些代码的汇编形式。
3. 循环结构
所谓循环结构,就是一直重复执行某段指定的语句,知道条件不满足了为止。
a) for循环结构
好,按照我们的习惯,我先写出这个语句的基本语法结构:
for (初始值; 满足条件; 增量)
{
要循环的语句;
}
例如下面的代码:
#include "stdio.h"
void main()
{
int i = 10;
// 循环打印输出0到之间的所有自然数。
for (int x = 0; x < i; x++)
{
printf("%d\n", x);
}
}
打印结果如下:
通过这个结果和上面的语法结构,我们可以猜测出for语句的执行流程如下图所示:
左图中描述了for语句的执行流程,先从上面的语句中执行到for关键字,然后开始初始化操作,在判断一下循环的条件是否满足:
如果条件满足,则继续按照绿色的箭头执行,开始执行要循环的语句,执行完以后,会来到增量(或叫 步长)这个语句对计数器(记录循环次数的变量)进行增加,然后在去判断是否满足循环条件,如果满足继续执行要循环的语句,如此循环。
直到条件不满足了,按照紫色箭头所示,跳出循环继续执行后面的语句。
现在让我们来调试跟踪一下上面的那段程序, 确切的体会下电脑是如何执行循环语句的:
1: #include "stdio.h"
2:
3: void main()
4: {
00401010 push ebp
00401011 mov ebp,esp
00401013 sub esp,48h
00401016 push ebx
00401017 push esi
00401018 push edi
00401019 lea edi,[ebp-48h]
0040101C mov ecx,12h
00401021 mov eax,0CCCCCCCCh
00401026 rep stos dword ptr [edi]
5: int i = 10;
00401028 mov dword ptr [ebp-4],0Ah ; // 初始化变量i
6:
7: // 循环打印输出0到之间的所有自然数。
8: for (int x = 0; x < i; x++)
0040102F mov dword ptr [ebp-8],0 ; // 初始化x变量
00401036 jmp main+31h (00401041) ; // 强制跳到1041 这个位置
00401038 mov eax,dword ptr [ebp-8]
0040103B add eax,1 ; // 自变量加一,继续判断比较
0040103E mov dword ptr [ebp-8],eax
00401041 mov ecx,dword ptr [ebp-8] ; // 开始比较i和x两个变量
00401044 cmp ecx,dword ptr [ebp-4]
00401047 jge main+4Ch (0040105c); // 如果ebp-8得值(也就是x的值)大于等于i的值则跳出循环
9: {
10: printf("%d\n", x);
00401049 mov edx,dword ptr [ebp-8]
0040104C push edx
0040104D push offset string "%d\n" (0042001c)
00401052 call printf (00401090) ; // 打印变量
00401057 add esp,8
11: }
0040105A jmp main+28h (00401038) ; // 调回去,累加器加一,然后继续循环
12: }
0040105C pop edi ; // 退出循环,恢复上下文环境。
0040105D pop esi
0040105E pop ebx
0040105F add esp,48h ; // 平衡堆栈。
00401062 cmp ebp,esp
00401064 call __chkesp (00401110)
00401069 mov esp,ebp
0040106B pop ebp
0040106C ret
好,到这里相信大家对for循环已经明白了,其实那个语法结构中的什么初始化,什么增量,无所谓的,只要明白了它的执行顺序,然后写上相应的代码就可以了,接下来就是多多使用它,熟练它就好。
b) while循环结构
与for循环相比,while有很多的优点,当然它也简单的一塌糊涂。我直截了当的说下这个while语句的两种写法和它们之间的区别:
按照 for 语句中图的分析方法,可以看出while的两种写法都比较容易,它们的区别也很明显,一个先判断下是否应该循环,另一个就不管三七二十一,先执行一遍循环体再说……
这个while语句比较容易,而且与for可以兼容,所以这里我就不再浪费篇幅去分析它了。大家如果有想去可以自己分析一下,看下它们的汇编模样。
到这里,我们这一小节的所有知识点都已经说完了,希望大家能灵活运用它们,根据实际情况,它们之间是可以嵌套的,比如if语句中使用for语句,for语句中使用一个switch语句,等等。
三、 养成良好的代码编写风格。
引用一下高质量C++/C编程指南:http://man.chinaunix.net/develop/c&c++/c/c.htm(只看能看懂的部分)
四、 结束语
通过本次专题的学习,我相信大家可以写一个很基础的代码了,再配合我们下节课要讲述的内容,我们的基础部分就要结束了。
多写程序,多看人家的程序,多作一些C语言的作业,灵活一下。