第五章 选择控制结构
分治策略:任务分解细化
程序设计语言:为了让计算机执行由高级语言编写的程序指令,必须把这些指令从高级语言形式转换成计算机能理解的机器语言形式,这种转换是由编译器来完成的
算法:为解决一个具体问题而采取的确定、有限、有序、可执行的操作步骤
数据结构+算法=程序(这个公式仅对面向过程的语言成立)
数据结构是计算机存储、组织数据的方式,指相互之间存在一种或多种特定关系的数据元素的集合
算法是对操作或行为的描述,算法代表着用系统的方法描述解决问题的策略
算法的描述方式:
- 自然语言描述(通俗易懂;冗长,不易直接转化为程序)
- 流程图描述(形象直观、一目了然,易于理解和发现程序中存在的错误;所占篇幅较大)
- NS结构化流程图描述
- 伪码描述
关系运算符
- <
- >
- <=
- >=
- ==
- !=
注意:==是比较,=是赋值,将“==”误写为“=”在C语言语法上是允许的,不会提示错误,但将导致错误的运行结果
条件表达式:用非0值表示“真”,用0值表示“假”
//L5-1(单分支控制)
#include <stdio.h> main() { int a, b, max; printf("Input a, b:"); scanf("%d,%d", &a, &b); if (a > b) max = a; if (a <= b) max = b; printf("max = %d\n", max); }
//运行结果 Input a, b:3,5 max = 5
//L5-2(双分支控制)
#include <stdio.h> main() { int a, b, max; printf("Input a, b:"); scanf("%d,%d", &a, &b); if (a > b) max = a; else max = b; /* 相当于a<=b的情况 */ printf("max = %d\n", max); }
//L5-3(条件运算符)
#include <stdio.h> main() { int a, b, max; printf("Input a, b:"); scanf("%d,%d", &a, &b); max = a > b ? a : b;/* 用条件表达式计算两整数的最大值*/ printf("max = %d\n", max); }
用一对花括号将一组逻辑相关的语句括起来,称为复合语句(类似于Verilog中的begin end)
//L5-4
#include <stdio.h> #include <stdlib.h> #include <math.h> #define EPS 1e-6 main() { float a, b, c, disc, p, q; printf("Please enter the coefficients a,b,c:"); scanf("%f,%f,%f", &a, &b, &c); if (fabs(a) <= EPS) /* a=0时,输出"不是二次方程" */ //浮点数并非真正意义上的实数,只是其在某种范围内的近似,因此也就只能用近似的方法将实数与0进行比较 { printf("It is not a quadratic equation!\n"); exit(0);//exit是C语言提供的标准库函数,作用是终止整个程序的执行,强制返回操作系统 //调用函数exit()需要在程序开头包含头文件<stdlib.h> } disc = b * b - 4 * a * c; /* 计算判别式 */ p = - b / (2 * a); q = sqrt(fabs(disc)) / (2 * a); if (fabs(disc) <= EPS) /* 判别式等于0时,输出两相等实根 */ { printf("x1 = x2 = %.2f\n", p); } else { if (disc > EPS) /* 判别式大于0时,输出两不等实根 */ { printf("x1 = %.2f, x2 = %.2f\n", p+q, p-q); } else /* 判别式小于0时,输出两共轭复根 */ { printf("x1 = %.2f+%.2fi, ", p, q); printf("x2 = %.2f-%.2fi\n", p, q); } } }
用于多路选择的switch语句(类似于Verilog中的case语句)
//L5-5
#include <stdio.h> main() { int data1, data2; char op; printf("Please enter an expression:"); scanf("%d%c%d", &data1, &op, &data2); /* 输入运算表达式 */ switch (op) /* 根据输入的运算符确定执行的运算 */ { case '+': /* 加法运算 */ printf("%d + %d = %d \n", data1, data2, data1 + data2); break;//只有switch语句和break语句配合使用,才能形成真正意义上的多分支 case '-': /* 减法运算 */ printf("%d - %d = %d \n", data1, data2, data1 - data2); break; case '*': /* 乘法运算 */ printf("%d * %d = %d \n", data1, data2, data1 * data2); break; case '/': /* 除法运算 */ if (0 == data2) /* 为避免除0错误,检验除数是否为0 */ printf("Division by zero!\n"); else printf("%d / %d = %d \n", data1, data2, data1 / data2); break; default: /* 处理非法运算符 */ printf("Invalid operator! \n"); } }
每个case后常量的次序发生改变时,不影响程序的运行结果,但不能有重复的case出现
从执行效率角度考虑,一般将发生频率高的情况放在前面
//L5-6
#include <math.h> #include <stdio.h> main() { float data1, data2; char op; printf("Please enter the expression:\n"); scanf("%f %c%f", &data1, &op, &data2); /* %c前有一个空格 */ switch (op) { case '+': printf("%f + %f = %f \n", data1, data2, data1 + data2); break; case '-': printf("%f - %f = %f \n", data1, data2, data1 - data2); break; case '*': case 'x': case 'X': printf("%f * %f = %f \n", data1, data2, data1 * data2); break; case '/': if (fabs(data2) <= 1e-7) /* 实数与0比较 */ printf("Division by zero!\n"); else printf("%f / %f = %f \n", data1, data2, data1 / data2); break; default: printf("Invalid operator! \n"); } }
逻辑运算符
- !:逻辑非
- &&:逻辑与
- ||:逻辑或
运算符&&和||都具有“短路”特性
程序测试:
- 程序测试是确保程序质量的一种有效手段
- 穷尽测试、抽样检查
- 程序测试只能证明程序有错,而不能证明程序无错
- 程序测试的目的是为了尽可能多的发现程序中的错误
测试用例:
- 白盒测试(结构测试):测试程序的内部结构已知,按程序的内部逻辑设计测试用例
- 黑盒测试(功能测试):不考虑程序内部的逻辑结构和处理过程,只检查程序的功能是否符合它的功能说明
- 结合使用:选择有限数量的重要路径进行白盒测试,对重要的功能需求进行黑盒测试
//L5-7
#include <stdio.h> #include <math.h> #define EPS 1e-1 main() { float a, b, c; int flag = 1; printf("Input a,b,c:"); scanf("%f,%f,%f", &a, &b, &c); if (a+b>c && b+c>a && a+c>b) { if (fabs(a-b)<=EPS && fabs(b-c)<=EPS && fabs(c-a)<=EPS) { printf("等边"); /* 等边 */ flag = 0; /* 置标志变量flag为0值 */ } else if (fabs(a-b)<=EPS || fabs(b-c)<=EPS|| fabs(c-a)<=EPS) { printf("等腰"); /* 等腰 */ flag = 0; /* 置标志变量flag为0值 */ } if (fabs(a*a+b*b-c*c)<=EPS || fabs(a*a+c*c-b*b)<=EPS || fabs(c*c+b*b-a*a)<=EPS) { printf("直角"); flag = 0; } if (flag) { printf("一般"); } printf("三角形\n"); } else { printf("不是三角形\n"); } }
//测试结果 Input a,b,c:3,4,5 直角三角形 Input a,b,c:4,4,5 等腰三角形 Input a,b,c:,3,4,6 不是三角形 Input a,b,c:3,4,9 不是三角形 Input a,b,c:10,10,14.14 等腰直角三角形 Input a,b,c:4,4,4 等边三角形
边界测试:
在选用测试用例时,不仅要选用合理的输入数据,还应选用不合理的以及某些特殊的输入数据或者临界的点,对程序进行测试,称为边界测试
//L5-8
#include <stdio.h> main() { int score, mark; printf("Please enter score:"); scanf("%d", &score); if (score<0 || score>100) { mark = -1; } else { mark = score / 10; } switch (mark) { case 10: case 9: printf("%d--A\n", score); break; case 8: printf("%d--B\n", score); break; case 7: printf("%d--C\n", score); break; case 6: printf("%d--D\n", score); break; case 5: case 4: case 3: case 2: case 1: case 0: printf("%d--E\n", score); break; default: printf("Input error!\n"); } }
//5-9
#include <stdio.h> main() { int a, b, max; printf("Input a, b:"); scanf("%d,%d", &a, &b); max = a > b ? a : b; printf("max = %d\n", max); }
//5-10
#include <stdio.h> main() { int a, b, max, ret; printf("Input a, b:"); ret = scanf("%d,%d",&a, &b); /* 记录scanf函数的返回值 */ if (ret != 2) /* 根据scanf函数返回值,判断输入数据个数或者格式是否错误 */ { printf("Input data quantity or format error!\n"); while(getchar() != '\n'); /* 清除输入缓冲区中的错误数据 */ } else /* 此处可以是正确读入数据后应该执行的操作 */ { max = a > b ? a : b; printf("max = %d\n", max); } }
虽然函数scanf()不进行参数类型匹配检查,但是通过检验scanf()的返回值是否为应读入的数据项数,可以判断输入的数据项数和格式(包括输入格式错误、存在非法字符、无数据可读等)是否正确
位运算符:
- 位运算就是对字节或字内的二进制数位进行测试、抽取、设置或移位等操作
- 其操作对象不能是float、double、long double等其他数据类型,只能是char和int类型
- -:按位取反
- <<,>>:左移位、右移位(无论是左移位还是右移位,从一端移走的位不移入另一端,移出的位的信息都丢失了)
- 算术移位
- 逻辑移位
- &:按位与,可用于对字节中的某位清零
- ^:按位异或,可用于对字节中的某位置1
- |:按位或
//L5-11
#include <stdio.h> main() { int x=12, y=8; printf("\n%5d%5d%5d", !x, x||y, x&&y); printf("\n%5u%5d%5d", ~x, x|y, x&y); printf("\n%5d%5d%5d\n", ~x, x|y, x&y); }
//运行结果 0 1 1 4294967283 12 8 -13 12 8
第六章 循环控制结构
循环结构:需要重复执行的操作
被重复执行的语句序列称为循环体
- 计数控制的循环
- 条件控制的循环
- 当型循环结构
- 直到型循环结构
- for
- while
- do-while
while(循环控制表达式) { 语句序列 }
计算循环控制表达式的值,如果循环控制表达式的值为真,执行循环体中的语句,返回;如果循环控制表达式的值为假,退出循环。
do { 语句序列 }while(循环控制表达式);
执行循环体中的语句,计算循环控制表达式的值,如果循环控制表达式的值为真,返回;如果循环控制表达式的值为假,退出循环。
for(初始化表达式;循环控制表达式;增值表达式) { 语句序列 }
初始化表达式的作用是为循环控制变量赋初值,决定了循环的起始条件
如何对循环变量进行增值,决定了循环的执行次数
如果在循环体内再次改变这个变量的值,将改变循环正常的执行次数
//L6-1
#include <stdio.h> main() { int i, n, sum; printf("Input n:"); scanf("%d", &n); sum = 0; /* 累加和变量初始化为0 */ for (i=1; i<=n; i++) { sum = sum + i; /* 做累加运算 */ } printf("sum = %d\n", sum); }
逗号运算符:可把多个表达式连接在一起,作用是实现对各个表达式的顺序求值
空语句(仅由一个分号构成):表示什么也不做,常用于编写延时程序
//L6-2
#include <stdio.h> main() { int i, n; long p = 1; /* 因阶乘值取值范围较大,故p定义为长整型,并赋初值1 */ printf("Please enter n:"); scanf("%d", &n); for (i=1; i<=n; i++) { p = p * i; /* 做累乘运算 */ } printf("%d! = %ld\n", n, p); /* 以长整型格式输出n的阶乘值 */ }
//L6-3
#include <stdio.h> main() { int i, n; long p = 1; printf("Please enter n:"); scanf("%d", &n); for (i=1; i<=n; i++) { p = p * i; printf("%d! = %ld\n", i, p); /* 输出1~n之间的所有数的阶乘值 */ } }
//L6-4(嵌套循环)
#include <stdio.h> main() { int i, j, n; long p, sum = 0; /* 累加求和变量sum初始化为0 */ printf("Input n:"); scanf("%d", &n); for (i=1; i<=n; i++) { p = 1; /* 每次循环之前都要将累乘求积变量p赋值为1 */ for (j=1; j<=i; j++) { p = p * j; /* 累乘求积 */ } sum = sum + p; /* 累加求和 */ } printf("1!+2!+…+%d! = %ld\n", n, sum); }
//运行结果 Input n:4 1!+2!+…+4! = 33
编写累加求和程序的关键在于寻找累加项的构成规律
- 当累加项较为复杂或者前后项之间无关时,需要单独计算每个累加项
- 当累加项的前项和后项之间有关时,可以根据关系通过前项来计算后项
//6-5
#include <stdio.h> main() { int i, j; for (i=0; i<3; i++) /* 控制外层循环执行3次 */ { printf("i=%d: ", i); for (j=0; j<4; j++) /* 控制内层循环执行4次 */ { printf("j=%d ", j); } printf("\n"); } }
//运行结果 i=0: j=0 j=1 j=2 j=3 i=1: j=0 j=1 j=2 j=3 i=2: j=0 j=1 j=2 j=3
//6-6(条件控制的循环)
#include <stdlib.h> #include <stdio.h> main() { int magic; /* 计算机"想"的数 */ int guess; /* 用户猜的数 */ magic = rand(); /* 调用随机函数"想"一个数magic */ printf("Please guess a magic number:"); scanf("%d", &guess); /* 输入用户猜的数guess */ if (guess > magic) /*若guess>magic,则提示"Wrong!Too big"*/ printf("Wrong! Too big!\n"); else if (guess < magic)/*若guess<magic,则提示"Wrong!Too small"*/ printf("Wrong! Too small!\n"); else /* 否则提示"Right!"并打印这个数 */ printf("Right!\n"); }
//6-7
#include <time.h> /* 将函数time()所需要的头文件time.h包含到程序中 */ #include <stdlib.h> #include <stdio.h> main() { int magic, guess, counter = 0; srand(time(NULL)); /* 为函数rand()设置随机数种子 */ magic = rand() % 100 + 1; do{ printf("Please guess a magic number:"); scanf("%d", &guess); counter ++; if (guess > magic) printf("Wrong! Too big!\n"); else if (guess < magic) printf("Wrong! Too small!\n"); else printf("Right!\n"); }while (guess != magic); printf("counter = %d\n", counter); }
//运行结果 Please guess a magic number:50 Wrong! Too small! Please guess a magic number:75 Wrong! Too big! Please guess a magic number:62 Wrong! Too big! Please guess a magic number:56 Wrong! Too big! Please guess a magic number:53 Wrong! Too big! Please guess a magic number:51 Right! counter = 6
//L6-8
#include <time.h> #include <stdlib.h> #include <stdio.h> main() { int magic, guess, counter = 0; srand(time(NULL)); magic = rand() % 100 + 1; do{ printf("Please guess a magic number:"); scanf("%d", &guess); counter ++; if (guess > magic) printf("Wrong! Too big!\n"); else if (guess < magic) printf("Wrong! Too small!\n"); else printf("Right!\n"); }while (guess!=magic && counter<10);/*猜不对且未超过10次时继续猜*/ printf("counter = %d\n", counter); }
//L6-9
#include <time.h> #include <stdlib.h> #include <stdio.h> main() { int magic, guess, counter = 0; int ret; /* 保存函数scanf()的返回值 */ srand(time(NULL)); magic = rand() % 100 + 1; do{ printf("Please guess a magic number:"); ret = scanf("%d", &guess); while (ret != 1) /* 若存在输入错误,则重新输入 */ { while (getchar() != '\n'); /* 清除输入缓冲区中的非法字符 */ printf("Please guess a magic number:"); ret = scanf("%d", &guess); } counter++; if (guess > magic) printf("Wrong!Too big!\n"); else if (guess < magic) printf("Wrong!Too small!\n"); else printf("Right!\n"); } while (guess!=magic && counter<10); /*猜不对且未超过10次时继续猜*/ printf("counter = %d\n", counter); }
//L6-10
#include <time.h> #include <stdlib.h> #include <stdio.h> main() { int magic, guess, counter = 0, ret; char reply; /* 保存用户输入的回答 */ srand(time(NULL)); do{ magic = rand() % 100 + 1; do{ printf("Please guess a magic number:"); ret = scanf("%d", &guess); while (ret != 1) /* 若存在输入错误,则重新输入 */ { while (getchar() != '\n'); /* 清除输入缓冲区中的非法字符 */ printf("Please guess a magic number:"); ret = scanf("%d", &guess); } counter++; if (guess > magic) printf("Wrong!Too big!\n"); else if (guess < magic) printf("Wrong!Too small!\n"); else printf("Right!\n"); } while (guess!=magic && counter<10); /*猜不对且未超10次继续猜*/ printf("counter = %d\n", counter); printf("Do you want to continue(Y/N or y/n)?"); /*提示是否继续*/ scanf(" %c", &reply); /* %c前有一个空格 */ }while (reply=='Y' || reply=='y'); /* 输入Y或y则程序继续 */ }
goto、break、continue和return语句是C语言中用于控制流程转移的跳转语句
goto语句为无条件转向语句,它既可以向下跳转,也可往回跳转
它的作用是在不需要任何条件的情况下直接使程序跳转到该语句标号所标识的语句去执行
其中语句标号代表goto语句转向的目标位置,应使用合法的标识符表示语句标号,其命名规则与变量名相同
break语句用于退出switch结构,也可用于由while、do-while和for构成的循环体中
当执行循环体遇到break语句时,循环将立即终止,从循环语句后的第一条语句开始继续执行
//L6-11
#include <stdio.h> main() { int i, n; for (i=1; i<=5; i++) { printf("Please enter n:"); scanf("%d", &n); if (n < 0) goto END; printf("n = %d\n", n); } END:printf("Program is over!\n"); }
continue语句与break语句都可用于对循环进行内部控制,但二者对流程的控制效果是不同的
当在循环体中遇到continue语句时,程序将跳过continue语句后面尚未执行的语句,开始下一次循环
即只结束本次循环的执行,并不终止整个循环的执行
//L6-12
#include <stdio.h> main() { int i, n; for (i=1; i<=5; i++) { printf("Please enter n:"); scanf("%d", &n); if (n < 0) continue; printf("n = %d\n", n); } printf("Program is over!\n"); }
在嵌套循环的情况下,break语句和continue语句只对包含它们的最内层的循环语句起作用,不能用break语句跳出多重循环。
若要跳出多重循环,break语句只能一层一层地跳出,显然goto语句是跳出多重循环的一条捷径。
以下两种情况使用goto语句可以提高程序的执行效率,使程序结构更清晰
- 快速跳出多重循环
- 跳出共同的出口位置,进行退出前的错误处理工作
//L6-13
#include <stdio.h> main() { int x; int find = 0; /* 置找到标志变量为假 */ for (x=1; !find; x++) /* find为假时继续循环 */ { if (x%5==1 && x%6==5 && x%7==4 && x%11==10) { printf("x = %d\n", x); find = 1; /* 置找到标志变量为真 */ } } }
//运行结果 x = 2111
结构化程序设计:
结构化程序设计是一种进行程序设计的原则和方法,按照这种原则和方法设计的程序,具有结构清晰、容易阅读、容易修改、容易验证等特点
- 程序应该只有一个入口和一个出口
- 不应有不可达语句和死循环
- 尽量避免使用goto语句,因为它破坏了结构化设计风格
- 采用自顶向下、逐步求精的模块化设计方法
常用的程序调试与排错方法:
- 除为了取得堆栈轨迹和一两个变量的值之外,尽量不要使用排错系统,因为人很容易在复杂数据结构和控制流的细节中迷失方向
- 有时以单步运行遍历程序的方式还不如努力思考,并辅之以在关键位置增设打印语句和测试代码,后者的效率更高
排错策略:
- 缩减输入数据,设法找到导致失败的最小输入
- 注释掉一些代码,分而治之
- 增量测试
类型溢出
//L6-14
#include <stdio.h> main() { long i, sum = 0; for (i=1; ;i++) { sum = sum + i*i*i; if (sum >= 1000000) break; } printf("count = %d\n", i); }
//运行结果 count = 45
//L6-15
#include <stdio.h> main() { double term, result = 1; int n; for (n=2; n<=100; n=n+2) { term = (double)(n * n) / ((double)( n - 1) * ( n + 1)); result = result * term; } printf("pi = %f\n", 2 * result); }
//运行结果 pi = 3.126079