以下为个人学习笔记和习题整理
课程:计算机程序设计(C++)- 西安交通大学 @ 中国大学 MOOC
https://www.icourse163.org/course/XJTU-46006
# 课堂笔记
# 程序流程控制
# 结构化程序设计
- 目标
设计出结构清晰、可读性强、易于分工合作编写和调试的程序。 - 步骤
- 自顶向下分析:把复杂问题分解成若干小问题以后再解决
- 模块化设计:将程序划分为若干个模块,每个模块独立存放、完成一个特定功能。
- 结构化编码:使用基本控制结构控制程序的执行流程。
# 模块
模块组成:可以是一条语句、一段程序、一个函数等
基本特征:仅有一个入口和一个出口
模块间关系:相互独立,内聚性很强
graph TD | |
A[C程序] --> B[源程序文件1] | |
A --> C[源程序文件2] | |
A --> D[源程序文件n] | |
C --> E[预处理命令] | |
C --> F[全局变量声明] | |
C --> G[函数1] | |
C --> H[函数n] | |
G --> G1[函数首部] | |
G --> G2[函数体] | |
G2 --> I[局部变量声明] | |
G2 --> J[执行语句] |
# 基本控制结构
graph TB | |
subgraph 循环结构 | |
g --> f | |
f(条件?) --> |成立| g[语句序列] | |
f --> |不成立| n[ ] | |
end | |
subgraph 选择结构 | |
c(条件?) --> |成立| d[语句序列1] | |
c --> |不成立| e[语句序列2] | |
end | |
subgraph 顺序结构 | |
a[语句序列1] --> b[语句序列2] | |
end |
语句序列可以是一条语句或者复合语句
# 顺序结构中使用的语句
- 说明语句
- 赋值语句
- I/O 语句输入输出语句
- 复合语句和空语句
// 复合语句 | |
{ | |
<局部数据说明部分> | |
<执行语句段落> | |
} | |
// 嵌套复合语句 | |
{ | |
<局部数据说明部分1> | |
<执行语句段落1> | |
{ | |
<局部数据说明部分2> | |
<执行语句段落2> | |
} | |
} | |
// 空语句 | |
; |
# 其他控制结构中使用的语句
流程控制语句
- 选择
- 条件分支 if else
- 开关分支 switch case
- 循环
- while
- for
- do while
- 选择
辅助控制语句
- break
- continue
- goto
- return
# 例子:画出流程图
模拟分拣机对 n 个产品的分拣过程,能够根据产品直径对产品分级并送入不同通道。
具体要求如下:
- 直径小于 6cm 为三等,送入通道 3;
- 直径在 6~8cm 为二等,送入通道 2;
- 直径大于 8cm 为一等,送入通道 1。
流程:
- 对于多个产品处理:循环结构
- 对于不同级别的产品处理:选择结构(一等、二等、三等)
- 对于分拣过程的处理:顺序结构(输入产品 ➡️ 测量直径 ➡️ 判断等级 ➡️ 送入通道 ➡️ 输出产品)
# 单路和双路分支
- 单路分支语句
if(a<b) | |
max=b; |
- 双路分支语句
if(a<b) | |
max=b; | |
else | |
max=a; |
表示条件的表达式要能判断真假,如
a>0
,a%2==0
当语句序列仅包含一条语句时,可以省略花括号
关系表达式作为条件
分支嵌套
if(表达式1) 语句1 | |
else if(表达式2) 语句2 | |
else if(表达式3) 语句3 | |
else if(表达式4) 语句4 | |
else 语句5 | |
if(表达式1) { | |
if (表达2) 语句1 | |
} else 语句2 (内嵌if) |
else 总是与它上面最近的 if 配对
如果 if 与 else 的数目不一样,可以加花括弧来确定配对关系
# 多路分支
switch (表达式) | |
{ | |
case 常量表达式1: | |
语句序列1; | |
…… | |
case 常量表达式n: | |
语句序列n; | |
[default: | |
语句序列n+1;] | |
} |
# 例子:成绩转换
编写程序,将一个输入的百分成绩经过运算得到相应的 5 分制成绩。
转换前后的成绩对应关系如下:
90~100 | 5 |
80~89 | 4 |
70~79 | 3 |
60~69 | 2 |
60 以下 | 1 |
#include<iostream> | |
using namespace std; | |
int main() | |
{ | |
int old_grade, new_grade; | |
cin >>old_grade; | |
switch (old_grade/10) | |
{ | |
case 10: | |
case 9: new_grade = 5;break; | |
case 8: new_grade = 4;break; | |
case 7: new_grade = 3;break; | |
case 6: new_grade = 2;break; | |
default: new_grade = 1; | |
} | |
cout <<new_grade; | |
return 0; | |
} |
整型表达式作为运算表达式
switch (old_grade/10)
分支描述
case 10: | |
case 9: new_grade = 5;break; | |
case 8: new_grade = 4;break; | |
case 7: new_grade = 3;break; | |
case 6: new_grade = 2;break; |
- 缺省分支描述
default: new_grade = 1; |
- 中断语句
break
# 已知次数循环
for(表达式1; 表达式2; 表达式3) | |
{ | |
语句序列 | |
} | |
// 表达式 1:< 变量 >=< 初始值表达式 > | |
// 表达式 2:表示循环条件 | |
// 表达式 3:表示增量 |
- 表达式之间的
;
不能省略for(;;)
在语法上是正确的,表示死循环
graph LR | |
A[计算表达式1] --> B{表达式2 ?} | |
B --> |不成立| D[ ] | |
B --> |成立| C[语句序列] --> E[计算表达式3] --> B |
# 例子:计算累加和
编写程序,计算 1+2+3+…+n 和并显示结果。
#include <iostream> | |
using namespace std; | |
int main() | |
{ | |
int sum=0,i,n; | |
cin>>n; | |
for(i=1; i<=n; i++) { | |
sum=sum+i; | |
} | |
cout << sum<<endl; | |
return 0; | |
} |
# 例子:乘法表
编写程序,按正三角形式显示九九乘法表。
格式如下:
1*1=1
1*2=2 2*2=4
1*3=3 2*3=6 3*3=9
……
1*9=9 2*9=18 3*9=27……9*9=81
#include <iostream> | |
using namespace std; | |
int main() | |
{ | |
int i,j; | |
for(i=1; i<=9; i++) // 外层循环 | |
{ | |
for(j=1; j<=i; j++) // 内层循环 | |
{ | |
cout << j<<"*"<<i<<"="<<j*i<<"\t"; // \t 输出按列对齐 | |
} | |
cout<<endl; | |
} | |
return 0; | |
} |
# 已知条件循环
while (表达式) | |
{ | |
<语句序列> | |
} | |
do | |
{ | |
<语句序列> | |
} while (表达式); |
graph LR | |
subgraph do-while | |
c(语句序列) --> d{条件?} --> |成立| c | |
d --> |不成立| e[ ] | |
end | |
subgraph while | |
g --> f | |
f{条件?} --> |成立| g[语句序列] | |
f --> |不成立| n[ ] | |
end |
# 例子:求 e 值
编写程序,使用下列级数近似计算 e 值,直到最后一个通项 < 10-7 为止。
#include <iostream> | |
using namespace std; | |
int main() | |
{ | |
double e = 1.0,u = 1.0; // 基数 e,通项 u | |
int n = 1; | |
while(u >= 1.0E-7) | |
{ | |
u = u/n; // 通项 | |
e = e+u; // 累加和 | |
n = n+1; | |
} | |
cout << "e = " << e << " ( n = " << n << " )" << endl; | |
return 0; | |
} |
#include <iostream> | |
using namespace std; | |
int main() | |
{ | |
double e=1.0,u = 1.0; | |
int n= 1; | |
do | |
{ | |
u = u/n; | |
e = e+u; | |
n = n+1; | |
} while (u>=1.0E-7); | |
cout << "e = " << e << " ( n = " << n << " )" << endl; | |
return 0; | |
} |
# 例子:计算实数 n 次方根
编写程序,能够根据输入的实数 x 和 n,计算 x 的 n 次方根。
具体要求:
- 输入 0 0 时,程序结束
- 当 (x<0 且 n<=0) 或 (x<=0 且 1/n 不为整数) 时,显示 “输入错误” 并允许用户继续输入
- 否则计算并显示 x 的 n 次方根并允许用户继续输入
#include<iostream> | |
#include<cmath> | |
using namespace std; | |
int main() | |
{ | |
double x,n; | |
while (1) | |
{ | |
cin>>x>>n; | |
if(x==0&&n==0) | |
{ | |
cout<<"Program terminated"<<endl; | |
break; // 能够跳出所在位置最近的一层循环 | |
} | |
else | |
if((x<0&&n<=0)||(x<0&&1/n!=int(1/n))) | |
{ | |
cout<<"error reinput"<<endl; | |
continue; // 能够跳过后续语句,开始新一轮的循环 | |
} | |
cout<<x<<"\t"<<n<<"th root"<<pow(x,1.0/n)<<endl; | |
} | |
return 0; | |
} |
# 实例
# 解一元二次方程
输入一元二次方程的 a,b,c 三个系数,解一元二次方程
输出两个根(含复根)。
# 问题分析
Δ>0? Δ<0?
# 算法描述
输入a,b,c;
如果a=0,
如果b=0,
输出“输入的系数不构成方程”;
否则(即b≠0)
计算单根x=-c/b
输出单根x
否则(即a≠0)
计算 delta=b*b-4*a*c
如果 delta>0
delta=sqrt(delta)
输出x1=(-b+delta)/2a和x2=(-b-delta)/2a
否则
delta=sqrt(-delta)
输出f复根:
x1=-b/2a+j*delta/2a;
x2=-b/2a-j*delta/2a(注意j是虚数单位)
结束
# 源程序
#include <iostream> // 包含需要的头文件 | |
#include <cmath> // 求根函数 sqrt 需要的头文件 | |
using namespace std; // 名字空间 | |
int main() // 主函数 | |
{ | |
double a,b,c; // 定义变量保存系数 | |
double delta; // 表示根的判别式 | |
double x,x1,x2; // 表示根 | |
cout<<"请输入一元二次方程的三个系数a,b,c:"; | |
cin>>a>>b>>c; // 输入一元二次方程的系数 | |
if(a==0) { // 二次项系数等于 0 | |
if(b==0) // 一次项系数也等于 0,不是方程 | |
{ | |
cout<<"输入的系数不构成方程"<<endl; | |
} | |
else // 二次项系数等于 0,一次项系数不为 0 | |
{ | |
x=-c/b; // 计算单根 | |
cout<<"实际为一元一次方程,根为"<<x<<endl; | |
} | |
} | |
else // 二次项系数不为 0 | |
{ | |
delta=b*b-4.0*a*c; // 计算判别式的值 | |
if(delta>=0) // 判别式大于等于 0,有实根 | |
{ | |
delta=sqrt(delta); // 判别式开方 | |
x1=(-b+delta)/2.0/a; // 根 1 | |
x2=(-b-delta)/2.0/a; // 根 2 | |
cout<<"方程有实根,它们是:"<<endl; | |
cout<<"x1="<<x1<<", x2="<<x2<<endl; | |
} | |
else // 判别式小于 0,有复根 | |
{ | |
delta=sqrt(-delta); // 判别式变号开方 | |
x1=-b/2.0/a; // 实部 | |
x2=delta/2.0/a; // 虚部 | |
cout<<"方程有复根,它们是:"<<endl; | |
cout<<"x1="<<x1<<"+j"<<x2<<", x2="<<x1<<"-j"<<x2<<endl; | |
// 由于 C++ 中没有复数类型,所以程序中先计算复根的实部和虚部,在输出时构造复数的形式。 | |
} | |
} | |
return 0; | |
} |
# 一个简单的计算器
编程一个简单的计算器功能,实现简单的加、减、乘、除表达式的计算。
设用户输入的表达式具有如下格式:<操作数1> <运算符> <操作数2>
# 问题分析
用户输入表达式后,程序要判断是什么运算,然后再做相应的处理。
该问题可以使用 if,或 if...else 解决。
对于分支较多的情况,C++ 提供 switch 语句。switch 语句也叫开关语句、多分支语句,它计算一个表达式的值,根据结果的不同,执行不同的分支处理语句。
# 算法描述
用num1,num2,op分别表示输入的表达式的两个操作数和一个运算符。
如果op='+',则result=num1+num2,输出result;
如果op='-',则result=num1-num2,输出result;
如果op='*',则result=num1*num2,输出result;
如果op='/',则
如果num2=0,显示“除数为0”
否则,计算result=num1/num2,输出result;
其他,显示“运算符错误”。
# 源程序
#include<iostream> // 包含输入输出头文件 | |
#include<cmath> | |
using namespace std; // 指定名字空间 | |
int main() // 主函数 | |
{ | |
double num1,num2; | |
char op; // 声明字符变量,存放操作符 | |
double result; // 声明变量,存放计算机结果 | |
char caption1[20]="Error,Divided by 0!"; | |
char caption2[20]="Invalid opereator!"; | |
cout<<"Please input the expression:"; | |
cin>>num1>>op>>num2; | |
switch(op) | |
{ | |
case '+': | |
result=num1+num2; | |
cout<<num1<<op<<num2<<"="<<result<<endl; | |
break; // 中断 | |
case '-': | |
result=num1-num2; | |
cout<<num1<<op<<num2<<"="<<result<<endl; | |
break; // 中断 | |
case '*': // 是乘号 | |
result=num1*num2; // 计算积 | |
cout<<num1<<op<<num2<<"="<<result<<endl; | |
break; | |
case '/': // 是除号 | |
if(fabs(num2)<1.0e-8) // 除数为 0 | |
{ | |
cout<<caption1<<endl; | |
} | |
else // 除数不为 0 | |
{ | |
result=num1/num2; // 计算商 | |
cout<<num1<<op<<num2<<"="<<result<<endl; | |
} | |
break; | |
default : // 以上情况都不是 | |
cout<<caption2<<endl; | |
} | |
return 0; | |
} |
switch 的条件是整型表达式;
fabs()
是求绝对值的函数,它包含在 cmath 头文件中;
在每一个 case 处理的最后都有一个 break 语句。
# 寻找自幂数
用户输入位数 n,找出并显示出所有 n 位的自幂数。
数学家发现了很多有趣的数字。
比如,153,一个普通的三位数,然而 13+53+3^3=153, 即它的各位数字的三次方的和等于这个数本身。
更一般地,一个 n 位正整数,哪些数的各位数字的 n 次方的和加起来 还等于这个数呢? 数学家称这样的数为自幂数,也叫自恋数。
n 为 1 时,自幂数称为独身,0,1,2,3,4,5,6,7,8,9 都是自幂数。
n 为 2 时,没有自幂数。
n 为 3 时,自幂数称为水仙花数,153 就是一个水仙花数。
n=4,称为四叶玫瑰数。
n=5,称为五角星数。
n=6,称为六合数。
n=7,称为北斗七星数。
n=8,称为八仙数。
n=9,称为九九重阳数。
# 问题分析
n 位自幂数,各位数字的 n 次方的和加起来还等于这个数。
找出 “各位”
如 153,找个位,可用 153%10=3;
十位,(153/10)%10=5,依次类推。直接求余,就是最低位的数字,除 10,原来的十位就成为新的最低位,重复这一过程,就可以求出各位,直到这个数成为 0。
n 次方的计算:使用数学函数
pow(x,n)
。构造 n 位数。
0 是最小的一位数,10 的 1 次方是最小的两位数,10 的平方是最小的三位数,那么,10 的 n-1 次方就是最小的 n 位数。
# 算法描述
- 输入位数 n。
- 计算 n 位数的起始值和终止值
start=10n-1
end=10n-1
i = start - 如果 i>end,转 9 结束
- m = i, sum = 0
- 如果 m = 0,转 7
- d = m%10,sum=sum+dn,m=m/10,转 5
- 如果 sum=i,显示 i
- i=i+1,转 3
- 结束
# 源程序
#include <iostream> // 包含需要的头文件 | |
#include<cmath> // 数学函数需要的头文件 | |
using namespace std; // 名字空间 | |
int main() { | |
int n; // 表示数的位数 | |
int start,end; // 表示 n 位数的起始值和终止值 | |
int m; // 待分解各位的数,即待判断的数 | |
int digit; // 某个数位的值 | |
int sum; // 各位数的 n 次方的和 | |
int i; // 循环变量,待检验的数 | |
cout<<"求n位自幂数,请输入位数:"; // 提示信息 | |
cin>>n; // 输入位数 | |
while(n>0) // 大于 0 时计算 | |
{ | |
start=pow(10,n-1); //n 位数的起始值 | |
end=pow(10,n)-1; //n 位数的终止值 | |
cout<<n<<"位自幂数:"; // 输出说明信息 | |
for(i=start;i<=end;i++) // 从起始值到终止值逐个检验 | |
{ | |
m=i; // 将 i 赋给 m | |
// 检验过程中 m 的值会改变,而 i 的值不变 | |
sum=0; // 各位数的 n 次方和,检验前赋 0 | |
while(m!=0) //m 开始为待检验的数 | |
{ | |
digit=m%10; // 取最低位数字 | |
sum=sum+pow(digit,n); //n 次方,再求和 | |
m=m/10; // 去掉个位,刚才的十位成为新个位 | |
} | |
// 上面的循环结束时 sum 就是各位数字的 n 次方的和 | |
if(sum==i){ // 逻辑表达式的值为 true 时,表示是自幂数 | |
cout<<i<<" "; // 显示该数 | |
} | |
} | |
cout<<endl; // 换行 | |
cout<<“求n位自幂数,请输入位数:”; | |
cin>>n; // 再输入一个 n 表示位数 | |
} //while 循环 | |
cout<<endl; | |
return 0; | |
} |
# 思路扩展
- 如果一种计算会破坏(或改变)某个变量的值,而这个原始值在后面的计算中还会使用,那就先将其赋值给另一个变量,使用新变量作 “破坏性” 计算,随时可以通过原来的变量获得原始值。
这实际是计算机科学中常用的一种 “冗余” 的思想,要获得某种保障,有意使用更多的时间、空间。
分离各位数字的相反运算是合成一个数,例如有三个变量,a,b,c,分别存放一位整数,比如 1,2,3,如果将它们合成为 a 作百位,b 作十位,c 作各位的三位数呢?
C++ 中,int 型变量能表示的最大正整数为 2147483647,它不过 10 位,那么有 11,12,13 位的自幂数吗?如果有,怎样计算呢?自幂数是有限的吗?如果有,有多少呢?
# 课堂讨论
# 单路和双路分支使用
- 在什么情况下,应该使用分支嵌套?
- 存在两个或者两个以上的逻辑判断
- 逻辑判断是包含关系而不是排列关系。比如 a>0 ,a==0,a<0 就是三个逻辑并列关系,互不补充。而 a>0 和 a>100 是包含关系。当 a>0 的情况实现时,我们再去判断 a>100 的情况。
- 可以理解为主条件里面的附带条件,如果主条件不成立,附带的条件也不会成立。
- 还有什么算法能够实现在三个数中,寻找最大数?
a>=b?(max=a):(max=b); | |
max>=c?(max=max):(max=c); | |
cout<<max<<endl; |
# 多路分支使用
- 在什么情况下,应该使用多路分支语句?
需要多个条件判断时应该使用。最典型的例子就是求 GPA。
此外分段函数也可以使用,比如交电费。0-50 度三毛钱,50-100 的部分六毛钱。100-200 部分九毛钱,超过 200,一块四。这种问题。
- 如果想将百分制成绩划分更多等级,如 60-64 为 2,65-69 为 2.5, 70-74 为 3,75-79 为 3.5 ……,该如何构造 switch 语句中的表达式?
百分制更细划分:除以 5。
#include<iostream> | |
using namespace std; | |
int main() | |
{ | |
int avg; | |
float gpa; | |
cin>>avg; | |
switch(avg/5) | |
{ | |
case 20: | |
case 19:gpa=5;break; | |
case 18:gpa=4.5;break; | |
case 17:gpa=4;break; | |
case 16:gpa=3.5;break; | |
default:gpa=1; | |
} | |
cout<<gpa; | |
return 0; | |
} |
# 已知条件循环使用
- 在求 e 值的程序中,能否将通项 u 的类型由双精度改为整型,初值由 1.0 改为 1?为什么?
不能,如果将初值由 1.0 改为 1,那么其结果会为整数而不会保留小数部分,会损失精度。
- 请总结 break 语句在多路分支和循环语句中的作用。
break 语句通常用在循环语句和开关语句中。
- 当 break 用于开关语句 switch 中时,可使程序跳出 switch 而执行 switch 以后的语句;
- 当 break 语句用于 do-while、for、while 循环语句中时,可使程序终止循环。而执行循环后面的语句,通常 break 语句总是与 if 语句联在一起。即满足条件时便跳 出循环。
- 在什么情况下,可以使用死循环?
当循环条件永远为真时,但使用 break 关键字可以强行跳出循环体。
# 随堂练习
结构化程序由三种基本结构组成,不包括
下列语句中错误的是
与分支语句有关的关键词有 if、else、switch、case 和 break。
若有定义
float w;int a,b;
,则合法的 switch 语句是for 循环语句是先执行循环体内的语句序列,后判断条件表达式。
下列程序段循环了几次
int x=-9;
while(++x){}
++x
到 0 时,即 false,跳出循环。
先 +1,再判断 x 的值,所以 while 的条件是从 x=8 开始的。
D
# 单元测试
假定所有变量均已正确说明,下列程序段运行后,x 的值是 。
a=b=c=0;
x=35;
if (!a) x--;
else if (b);
if (c) x=3;
else x=4;
C++ 语言中 while 循环和 do...while 循环的主要区别是 。
while 后面的 “条件表达式” 一定要用一对 括起来。
执行语句序列:
int x=3;
do
{
x-=2;
cout<<x;
}while(!(--x));
输出结果是 。
B
在 C++ 语言中,所谓选择结构,就是按照 有选择地执行程序中的语句。
在 C++ 语言中,表示一条语句结束的标号是 。
下列程序段的输出是 。
int a=2, b=-1, c=2;
if(a<b)
if(b<c) c=0;
else
c+=1;
cout<<c<<endl;
D,注意 else 对应的不是第一个,而是第二个,不要被缩进误导了
当在程序中执行到 continue 语句时,将结束所在循环语句中循环体的一次执行。
当在程序中执行到 break 语句时,将结束本层循环类语句或 switch 语句的执行。
用 {} 括起来的语句叫复合语句。