课件
思维导图
本章主要讲述程序的三种基本结构:顺序结构、分支结构
搭配一些小游戏、小 demo 来码程序练习
3.1 引言
- 顺序结构
- 选择结构
- 循环结构
认识“顺序”、“选择”、“循环”分别表示什么含义,本章要介绍的核心 —— 控制语句,主要就是在介绍这 3 中结构该如何使用 C 语言代码来表示。
一劳永逸:上述这些结构,几乎是所有编程语言都具备的,特点几乎完全一致。
顺序执行:一步一步走
00:01:04 | 选择
选择执行:根据不同的情况做不同的事儿
00:01:26 | 循环
三天打鱼两天晒网:
循环执行:某一件事儿不断地去重复做
00:01:50 | 引出本章的核心
如何使用 C 语言来表示顺序、选择、循环结构,是本章要介绍的主要内容。
3.2 顺序结构
简述:介绍了顺序结构是什么,以及程序设计语言中的基本语句。
顺序结构
都是一些概念性的玩意儿,有个简单的概念就好,对撸代码没啥实质影响。
notes
程序中的 3 种基本结构
- 顺序结构:程序从头到尾按照顺序执行,每条语句都执行一次,不跳过也不重复。
- 分支结构:根据条件选择执行某一条或多条语句,通常使用 if-else 语句、switch 语句等。
- 循环结构:根据条件重复执行某一条或多条语句,通常使用 while 循环、do-while 循环、for 循环等。
大量的实际问题需要通过各种控制流程来解决,这三种基本结构可以组合起来构成复杂的程序用于解决复杂问题。
补充:
- 有的书中说,这 3 种基本结构可以解决所有实际问题,也就意味着,所有程序其实都是这 3 种结构组合而成的。
- 理解这 3 种结构的执行流程并非难事
- 难点在于利用这 3 种基本结构,编写出满足业务要求的程序,当然,这并非一朝一夕能做到的事儿,后续还有很多知识点待我们挖掘
顺序结构
程序中的 3 种基本语句
- 简单语句:执行一条语句的基本单元,它可以是赋值语句、函数调用语句、流程控制语句等。它通常以分号
;
结尾。- 例:
z = x + y;
、c = getchar();
- 例:
- 复合语句:由多个简单语句组成的语句块(简单语句的集合),通常使用花括号
{ }
括起来。复合语句中的语句可以是任意类型的语句,包括另一个复合语句。 - 空语句:没有实际执行效果的语句,通常用于 占位符 或 调试 目的。空语句只包含一个分号
;
,不会执行任何代码。
3.3 选择结构
选择结构
掌握 if 语句和 switch 语句
通过编写几个简单的 demo 来认识选择结构是什么。
notes
if 流程、if-else 流程
switch 多路开关语句
补充:实际开发中 switch 语句出现频率很低,几乎都使用 if-else 来写的。
使用 if-else 结构,输入一个整数,判断这个整数是奇数还是偶数
#include <stdio.h>
int main() {
int num;
printf("请输入一个整数:\n");
scanf("%d", &num);
if (num % 2 == 0) {
printf("%d是偶数。\n", num);
} else {
printf("%d是奇数。\n", num);
}
return 0;
}
/* 运行结果:
请输入一个整数:
1
1是奇数。
请输入一个整数:
10
10是偶数。
*/
使用三目运算符,输入一个整数,判断这个整数是奇数还是偶数
#include <stdio.h>
int main() {
int num;
printf("请输入一个整数:\n");
scanf("%d", &num);
num % 2 == 0 ? printf("%d 是偶数。\n", num) : printf("%d 是奇数。\n", num);
return 0;
}
/* 运行结果:
请输入一个整数:
1
1 是奇数。
请输入一个整数:
10
10 是偶数。
*/
使用 switch 结构,输入一个月份(数字),打印该月份对应的英文
#include <stdio.h>
int main() {
int month;
printf("请输入月份:");
scanf("%d", &month);
switch (month) {
case 1:
printf("January\n");
break;
case 2:
printf("February\n");
break;
case 3:
printf("March\n");
break;
case 4:
printf("April\n");
break;
case 5:
printf("May\n");
break;
case 6:
printf("June\n");
break;
case 7:
printf("July\n");
break;
case 8:
printf("August\n");
break;
case 9:
printf("September\n");
break;
case 10:
printf("October\n");
break;
case 11:
printf("November\n");
break;
case 12:
printf("December\n");
break;
default:
printf("输入有误,请输入 1-12 之间的整数。\n");
break;
}
return 0;
}
/* 运行结果:
请输入月份:3
March
*/
使用 if-else 结构,输入一个月份(数字),打印该月份对应的英文
#include <stdio.h>
int main() {
int month;
printf("请输入月份:");
scanf("%d", &month);
if (month == 1) {
printf("January\n");
} else if (month == 2) {
printf("February\n");
} else if (month == 3) {
printf("March\n");
} else if (month == 4) {
printf("April\n");
} else if (month == 5) {
printf("May\n");
} else if (month == 6) {
printf("June\n");
} else if (month == 7) {
printf("July\n");
} else if (month == 8) {
printf("August\n");
} else if (month == 9) {
printf("September\n");
} else if (month == 10) {
printf("October\n");
} else if (month == 11) {
printf("November\n");
} else if (month == 12) {
printf("December\n");
} else {
printf("输入有误,请输入 1-12 之间的整数。\n");
}
return 0;
}
/* 运行结果:
请输入月份:3
March
*/
3.4 循环结构
认识 3 种循环结构,重点掌握好 for 循环,相较于 while、do-while 循环,for 循环更加常见
循环结构
通过几个练手的小 demo,认识一下 for、while 循环的执行流程是咋样的。
只要条件满足,那么就不断地做某件事儿,直到条件不满足,才会继续干后续的其它事儿。
notes
for 循环
for (表达式 1; 表达式 2; 表达式 3) {
执行语句;
}
- 表达式 1:循环变量初始化
- 表达式 2:循环条件
- 表达式 3:更新循环变量
for 循环的执行流程:
- 循环变量初始化:在第一次循环开始前,循环变量被赋值为循环初始值。
- 循环条件判断:然后判断循环条件是否为真,如果为假,则跳出循环;如果为真,则执行执行语句。
- 执行循环体
- 更新循环变量:按照循环增量的规则对循环变量进行更新。
- 循环条件判断:然后再次判断循环条件是否为真,如果为假,则跳出循环;如果为真,则继续执行执行语句。
- 依此类推,直到循环条件为假时跳出循环。
如果按照常规的 for 循环的执行流程来看,上述描述是没问题的。但是需要注意的是,for 循环是非常灵活的,某些情况下我们可能会省略掉一些步骤,从而导致上述的某些步骤在我们看来是没有必要的。比如:
- 有些步骤可能是在 for 循环外部就已经做了,比如循环变量初始化,此时就没必要在 for 循环中再去做循环变量的初始化了
- 有些步骤可能是在循环体内部做的,比如循环变量的改变,此时就没必要在 for 循环中再去做循环变量的更新了
while、do-while
while (condition) {
// 循环体
}
其中 condition
是一个表达式
- 值为 非零 时,循环体会一直执行下去
- 值为 0 时,循环体停止执行,继续执行循环语句后面的代码
do {
// 循环体
} while (condition);
和 while 循环语句类似,其中 condition
是一个表达式
- 值为 非零 时,循环体会一直执行下去
- 值为 0 时,循环体停止执行,继续执行循环语句后面的代码
注意:
- do-while 循环属于“当型循环”、while 和 for 属于“直到型循环”
- do-while 循环语句先执行一次循环体,然后再检查 condition 的值,即使 condition 的值一开始就是 0,循环体也会被执行一次
- 在 while 和 do-while 循环中,循环体内部需要改变循环条件的值,否则循环会一直进行下去,导致死循环
从使用场景的角度来对比 for 循环和 while 循环
for
循环适合用于 在已知循环次数 的情况下进行循环控制- 在 for 循环中,循环计数器的作用更加明确,循环控制更加紧凑,因此在循环次数已知的情况下,for 循环更加常用。
while
循环适合用于在 不确定循环次数 的情况下进行循环控制- 在需要对循环计数器进行复杂的操作,或者需要在循环体内部根据特定的条件来终止循环的情况下,while 循环可能更为适合。
- 所有能用 while 循环做的事儿,使用 for 循环都能做;
- 所有能用 for 循环做的事儿,使用 while 循环都能做;
- 就 UP 实际工作经验来看,同事们更多倾向于使用 for 循环,while 循环出现频率相对较少;
- 并不是说 for 循环没法用于循环次数未知的循环,只不过“业内潜规则”这么规定罢了,随着遵循这套规则的人越来越多,自然而然大伙就都默认这么干了。
两种循环类型:当型循环、直到型循环
当型循环 和 直到型循环 是两种常见的循环结构。
当型循环也叫做 条件循环,while
循环和 for
循环都是当型循环,它的执行流程如下:
- 首先判断循环条件是否为真。
- 如果循环条件为真,则执行循环体中的语句,并回到步骤 1。
- 如果循环条件为假,则退出循环。
直到型循环也叫做后测试循环,do-while
循环就是一种直到型循环。它的执行流程如下:
- 首先执行循环体中的语句。
- 然后判断循环条件是否为真。
- 如果循环条件为真,则回到步骤 1。
- 如果循环条件为假,则退出循环。
for 循环的省略形式
#include <stdio.h>
int main() {
int count = 0;
for (;;) {
printf("count = %d\n", count);
count++;
if (count > 10) {
break;
}
}
return 0;
}
/* 运行结果:
count = 0
count = 1
count = 2
count = 3
count = 4
count = 5
count = 6
count = 7
count = 8
count = 9
count = 10
*/
上述代码使用了 for 循环的省略形式,即只写了循环体的条件部分,而省略了初始值和增量部分,相当于初始值为 0,增量为 1。循环体中打印出变量 count 的值,并将其自增,当 count 的值大于 10 时,使用 break 语句跳出循环。因此,该程序输出了 0 到 10 的整数值。
for (循环变量的初始值; 循环条件; 循环变量的增量) {
执行语句;
}
由此 demo 可见,for 循环中的:
循环变量的初始值
循环条件
循环变量的增量
都不是必须的,若我们不需要这些语句,沈略不写即可。但是语法还是要遵循的,语句之间的 ;
分号不能少。
输入整数 n 然后输出 n 的 1-5 次方
#include <stdio.h>
int main() {
int n;
printf("请输入一个整数:");
scanf("%d", &n);
printf("%d的1次方是:%d\n", n, n);
printf("%d的2次方是:%d\n", n, n * n);
printf("%d的3次方是:%d\n", n, n * n * n);
printf("%d的4次方是:%d\n", n, n * n * n * n);
printf("%d的5次方是:%d\n", n, n * n * n * n * n);
return 0;
}
/* 运行结果:
请输入一个整数:2
2的1次方是:2
2的2次方是:4
2的3次方是:8
2的4次方是:16
2的5次方是:32
*/
输入整数 n,r 然后输出 n 的 1-r 次方
#include <stdio.h>
int main() {
int n, r;
printf("请输入整数n和r:");
scanf("%d %d", &n, &r);
for (int i = 1; i <= r; i++) {
int result = 1;
for (int j = 1; j <= i; j++) {
result *= n;
}
printf("%d的%d次方是:%d\n", n, i, result);
}
return 0;
}
/* 运行结果:
请输入整数n和r:3 4
3的1次方是:3
3的2次方是:9
3的3次方是:27
3的4次方是:81
*/
打印九九乘法表
#include <stdio.h>
int main() {
int i, j;
for (i = 1; i <= 9; i++) { // 行
for (j = 1; j <= i; j++) { // 列
printf("%d*%d=%-2d ", j, i, j * i); // 使用 %-2d 实现左对齐
}
printf("\n");
}
return 0;
}
/* 运行结果:
1*1=1
1*2=2 2*2=4
1*3=3 2*3=6 3*3=9
1*4=4 2*4=8 3*4=12 4*4=16
1*5=5 2*5=10 3*5=15 4*5=20 5*5=25
1*6=6 2*6=12 3*6=18 4*6=24 5*6=30 6*6=36
1*7=7 2*7=14 3*7=21 4*7=28 5*7=35 6*7=42 7*7=49
1*8=8 2*8=16 3*8=24 4*8=32 5*8=40 6*8=48 7*8=56 8*8=64
1*9=9 2*9=18 3*9=27 4*9=36 5*9=45 6*9=54 7*9=63 8*9=72 9*9=81
*/
读入字符并回显,直到读入 *
字符为止的程序段
#include <stdio.h>
int main() {
char c;
while (1) {
scanf("%c", &c);
if (c == '*') {
break;
}
printf("%c", c);
}
return 0;
}
/* 运行结果:
aBCde?f*123123
aBCde?f
*/
#include <stdio.h>
int main() {
char ch;
while ((ch = getchar()) != '*') {
putchar(ch);
}
return 0;
}
/* 运行结果:
aBCde?f*123123
aBCde?f
*/
3.5 break、continue
break、continue
notes
简述
介绍了 break、continue、goto 语句的作用
break 语句
break 语句是 C 语言中的一种控制语句。
位置:
- 循环体中:当执行到 break 语句时,会立即终止当前循环,跳出循环体,继续执行循环后面的语句。
- switch 语句中:用于结束当前的 case 分支并跳出 switch 语句。
作用:
- 在满足一定条件时提前结束循环,从而提高程序的执行效率
- 可以避免程序进入死循环等问题
continue 语句
continue 是书写在循环语句中的一种流程控制语句。
作用:
- 当 continue 语句执行时,程序将会跳过循环体中剩余的语句,然后继续下一次循环迭代
- 在循环中使用 continue 语句来跳过某些特殊情况下的迭代,以避免出现错误或提高程序的效率
goto 语句
goto 语句是 C 语言中的一种流程控制语句,主要作用是在程序中实现跳转。
在执行过程中,当遇到 goto 语句时,程序会立即跳转到指定的标签处,继续执行该标签后的语句。
应用场景:
- 在出错的情况下,可以使用 goto 语句跳转到异常处理的代码处;
- 在实现循环、嵌套等复杂的程序流程时,也可以使用 goto 语句;
注意:
- goto 语句的使用需要谨慎,因为 它容易导致代码的混乱和不易维护。
- 一般情况下,使用结构化编程方式编写程序更容易理解和维护。
补充:据同事说,这玩意儿很少用,学习时只要了解一下它的作用即可,不必找大量 demo 来练习。
break | 打印输入字符,直到输入 s
时截止
#include <stdio.h>
int main() {
char ch;
while (1) {
scanf("%c", &ch);
if (ch == 's') {
break;
} else {
printf("%c", ch);
}
}
return 0;
}
/* 运行结果:
123sabc
123
*/
break | 打印小于 5 的所有非负整数
#include <stdio.h>
int main() {
int i;
for (i = 0; i < 10; i++) {
if (i == 5) {
break;
}
printf("%d ", i);
}
printf("\n");
return 0;
}
/* 运行结果:
0 1 2 3 4
*/
break | 在九九乘法表中,找第一个乘积为 56 的整数对
#include <stdio.h>
int main() {
int i, j;
for (i = 1; i <= 9; i++) {
for (j = 1; j <= i; j++) {
if (i * j == 56) {
printf("i = %d, j = %d\n", i, j);
break; // 退出内层循环
}
}
if (j <= i) { // 判断内层循环是否已经被退出
break; // 退出外层循环
}
}
return 0;
}
/* 运行结果:
i = 8, j = 7
*/
注意:break 只能跳出当前这一层的循环体
break; // 退出内层循环
这个 break 语句位于内层 j 循环中,它只能跳出内层循环而无法跳出外层 i 循环。
break; // 退出外层循环
这个 break 语句位于外层 i 循环中,它能够跳出外层 i 循环。
continue | 循环输出 1 到 10 之间的所有奇数
逆向思维:是偶数就不输出
#include <stdio.h>
int main() {
for (int i = 1; i <= 10; i++) {
if (i % 2 == 0) {
// 跳过所有偶数
continue;
}
printf("%d\n", i);
}
return 0;
}
/* 运行结果:
1
3
5
7
9
*/
goto | 非负整数数组求和
需求描述:有一个整数数组,但是这个整数数组中可能会存在非负整数,现要求计算这个整数数组中所有元素的和。
- 如果数组中没有出现负数,那么将所有整数进行求和,并将求和后的结果打印出来;
- 如果数组中的任何元素为负数,则跳转到 error 标签并输出错误信息;
#include <stdio.h>
int main() {
int arr[5] = {1, 2, 3, -4, 5};
int sum = 0;
int i;
for (i = 0; i < 5; i++) {
if (arr[i] < 0) {
goto error;
}
sum += arr[i];
}
printf("数组元素的和为:%d\n", sum);
return 0;
error:
printf("错误:数组中出现负数。\n");
return 1;
}
/* 运行结果:
错误:数组中出现负数。
*/
#include <stdio.h>
int main() {
int arr[5] = {1, 2, 3, 4, 5};
int sum = 0;
int i;
for (i = 0; i < 5; i++) {
if (arr[i] < 0) {
goto error;
}
sum += arr[i];
}
printf("数组元素的和为:%d\n", sum);
return 0;
error:
printf("错误:数组中出现负数。\n");
return 1;
}
/* 运行结果:
数组元素的和为:15
*/
3.6 编程实战
编程实战1:买苹果
题目描述:
第 4 点描述不准确,容易产生歧义:
- 当天购买的苹果不超过 100 的最大值
- 每一天购买的苹果求和,即所有苹果总量不超过 100 的最大值
从视频中提供的源码逻辑来看,应该是指 1 也就是当天购买的苹果总量一旦超过 100,那么就不再继续够买了,开始计算每天花费的平均值
分析:
00:04:14 | 源码
00:17:33 | 作业
#defined MAX 100
宏定义常量
作业要求:扩展我们的程序,让天数 n 由用户指定。
编程实战2:猜数游戏
编程实战3:求素数
notes
简述
独立完成 3 个练习 | 买苹果 | 猜数字 | 求素数
这一部分涉及到一些额外的扩展知识,比如 rand、srand、sqrt 函数的基本基本使用。
引用:
- 了解题目要求 👉🏻 3.6.1 编程实战1:买苹果
- 了解题目要求 👉🏻 3.6.2 编程实战2:猜数游戏
- time 函数使用说明 👉🏻 4.3 编程实战
买苹果
#include <stdio.h>
int main() {
float price = 0.8, avg_price = 0;
int apple_count = 2, day, totalCount = 0;
for (day = 1; apple_count <= 100; day++) {
totalCount += apple_count;
apple_count *= 2;
}
avg_price = (totalCount * price) / (day - 1);
printf("平均每天花费 %.2lf 元\n", avg_price);
return 0;
}
/* 运行结果:
平均每天花费 16.80 元
*/
#include <stdio.h>
int main() {
float price = 0.8, avg_price = 0;
int apple_count = 2, day = 1, totalCount = 0;
while (apple_count <= 100) {
totalCount += apple_count;
printf("第 %d 天,购买了 %d 个苹果,当天花费 %.2f 元,一共购买了 %d "
"个苹果,总花费 %.2f 元\n",
day, apple_count, apple_count * price, totalCount,
totalCount * price);
apple_count *= 2;
day++;
}
avg_price = (totalCount * price) / (day - 1);
printf("平均每天花费 %.2lf 元\n", avg_price);
return 0;
}
/* 运行结果:
第 1 天,购买了 2 个苹果,当天花费 1.60 元,一共购买了 2 个苹果,总花费 1.60 元
第 2 天,购买了 4 个苹果,当天花费 3.20 元,一共购买了 6 个苹果,总花费 4.80 元
第 3 天,购买了 8 个苹果,当天花费 6.40 元,一共购买了 14 个苹果,总花费 11.20 元
第 4 天,购买了 16 个苹果,当天花费 12.80 元,一共购买了 30 个苹果,总花费 24.00 元
第 5 天,购买了 32 个苹果,当天花费 25.60 元,一共购买了 62 个苹果,总花费 49.60 元
第 6 天,购买了 64 个苹果,当天花费 51.20 元,一共购买了 126 个苹果,总花费 100.80 元
平均每天花费 16.80 元
*/
作业:扩展我们的程序,让天数 n 由用户指定。
#include <stdio.h>
int main() {
float price = 0.8, // 苹果的单价
avg_price = 0; // 苹果的平均价格
int apple_count = 2, // 当天购买的苹果的数量
day = 1, // 第几天购买苹果
totalCount = 0, // 购买的苹果的总量
totalDay; // 购买苹果的总天数
// 获取用户输入的购买苹果的总天数
printf("请输入连续购买苹果的天数:");
scanf("%d", &totalDay);
while (day <= totalDay) {
totalCount += apple_count;
printf("第 %d 天,购买了 %d 个苹果,当天花费 %.2f 元,一共购买了 %d "
"个苹果,总花费 %.2f 元\n",
day, apple_count, apple_count * price, totalCount,
totalCount * price);
apple_count *= 2;
day++;
}
avg_price = (totalCount * price) / (day - 1);
printf("平均每天花费 %.2lf 元\n", avg_price);
return 0;
}
/* 运行结果:
请输入连续购买苹果的天数:6
第 1 天,购买了 2 个苹果,当天花费 1.60 元,一共购买了 2 个苹果,总花费 1.60 元
第 2 天,购买了 4 个苹果,当天花费 3.20 元,一共购买了 6 个苹果,总花费 4.80 元
第 3 天,购买了 8 个苹果,当天花费 6.40 元,一共购买了 14 个苹果,总花费 11.20 元
第 4 天,购买了 16 个苹果,当天花费 12.80 元,一共购买了 30 个苹果,总花费 24.00 元
第 5 天,购买了 32 个苹果,当天花费 25.60 元,一共购买了 62 个苹果,总花费 49.60 元
第 6 天,购买了 64 个苹果,当天花费 51.20 元,一共购买了 126 个苹果,总花费 100.80 元
平均每天花费 16.80 元
请输入连续购买苹果的天数:10
第 1 天,购买了 2 个苹果,当天花费 1.60 元,一共购买了 2 个苹果,总花费 1.60 元
第 2 天,购买了 4 个苹果,当天花费 3.20 元,一共购买了 6 个苹果,总花费 4.80 元
第 3 天,购买了 8 个苹果,当天花费 6.40 元,一共购买了 14 个苹果,总花费 11.20 元
第 4 天,购买了 16 个苹果,当天花费 12.80 元,一共购买了 30 个苹果,总花费 24.00 元
第 5 天,购买了 32 个苹果,当天花费 25.60 元,一共购买了 62 个苹果,总花费 49.60 元
第 6 天,购买了 64 个苹果,当天花费 51.20 元,一共购买了 126 个苹果,总花费 100.80 元
第 7 天,购买了 128 个苹果,当天花费 102.40 元,一共购买了 254 个苹果,总花费 203.20 元
第 8 天,购买了 256 个苹果,当天花费 204.80 元,一共购买了 510 个苹果,总花费 408.00 元
第 9 天,购买了 512 个苹果,当天花费 409.60 元,一共购买了 1022 个苹果,总花费 817.60 元
第 10 天,购买了 1024 个苹果,当天花费 819.20 元,一共购买了 2046 个苹果,总花费 1636.80 元
平均每天花费 163.68 元
*/
rand、srand
- rand 函数是 C 语言标准库中的一个函数
- 它的原型定义在 stdlib.h 头文件中
- 函数原型:
int rand(void);
- 作用:rand 用于生成伪随机数
- 返回值:rand() 函数返回一个随机的整数值,该值在
[0, RAND_MAX]
之间,其中 RAND_MAX 是一个常量,表示 rand() 函数返回的最大值,一般为 32767。 - rand 函数需要调用 srand 函数来设置随机数的种子,否则每次程序运行时都会得到相同的随机数序列
- 伪随机数:计算机程序生成的看似随机的数,其实是基于确定的 算法 和 种子值 生成的,而非真正的随机数
- rand 函数返回结果的生成过程实际上是基于一定的算法,该算法利用前一次生成的随机数作为下一次生成的种子,再经过一系列的计算得到新的随机数。
- 由于这个算法是确定性的,也就是说 如果知道了前一次生成的随机数和算法,那么就可以准确地预测下一次生成的随机数。
- rand() 函数生成的数字并不是真正的随机数,而是“伪随机数”。
- 虽然伪随机数不能完全避免重复和预测,但由于其生成过程复杂,通常能够满足大多数实际应用的需要。
- 为了尽可能减少重复和预测,生成伪随机数的算法通常使用当前时间、计数器、内存地址等多种随机因素作为种子值,并 不断更新种子值来增加随机性。
- 伪随机数的值是由随机数生成器的 算法 和 初始种子值 共同决定的。
- 随机数生成器的算法是确定的
- 只要初始种子值一样,生成的随机数序列也是一样的
- srand 函数可以接受一个整数参数,用于设置随机数的种子
- 生成真随机数:
- 为了生成更为随机的伪随机数,需要不断改变种子值。
- 在实际应用中,常用的方法是使用当前时间作为初始种子值,这样每次运行程序生成的随机数序列都不同。
- 修改初始种子值:
- 在使用 rand() 函数前调用 srand() 函数可以修改初始种子值。
- srand() 函数需要传入一个 整数值,通常使用 time() 函数的返回值作为种子值,可以使每次程序运行时生成的随机数序列不同。
srand(time(NULL)); // 使用当前时间作为种子值
int randomNumber = rand(); // 生成伪随机数
srand(time(NULL))
作用是 初始化随机数生成器的种子,使每次生成的随机数序列不同time(NULL)
函数返回的是一个时间戳,因为当前时间是一直变化的,用它来作为 srand 函数的种子参数是比较合适的- 每次调用 rand() 获取随机数之前,调用一下
srand(time(NULL))
即可确保每次运行程序时都会得到不同的随机数序列,以得到一个“真随机数”
srand((unsigned)time(NULL)); // 使用当前时间作为种子值
int randomNumber = rand(); // 生成伪随机数
srand((unsigned)time(NULL));
- time(NULL) 函数返回的是 time_t 类型的值,而不是 unsigned 类型。
(unsigned)time(NULL)
这种写法相当于对 time 函数的返回值的类型做了一个强制类型转换,将其强制转换为 unsigned 类型。srand((unsigned)time(NULL));
这么做事为了 将 time_t 类型的时间值转换成一个适合作为随机数种子的无符号整数。
Q:类型 time_t 和类型 unsigned 之间的差异
time_t 类型和 unsigned 类型是两种不同的数据类型,虽然它们 都可以表示非负整数,但它们的 语义和用法不同。
time_t
类型是一个表示时间的整数类型,通常被用来表示从某个特定时间点起到现在所经过的秒数,例如time(NULL)
返回的就是当前时间距离 Unix 时间戳起点(1970 年 1 月 1 日 0 时 0 分 0 秒)的秒数。unsigned
类型则是一个不带符号的整数类型,可以表示从 0 开始的任意非负整数。它通常被用于计算机的位运算和计算机内存地址等场景中。
生成一个 n ~ m 的随机数
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main() {
int n, m, random_number;
printf("请输入两个整数 n 和 m,程序将生成一个 n ~ m 之间的随机数:");
scanf("%d%d", &n, &m);
srand((unsigned)time(NULL)); // 设置随机数种子
random_number = rand() % (m - n + 1) + n; // 生成 n ~ m 之间的随机整数
printf("生成的随机数为:%d\n", random_number);
return 0;
}
// 运行结果:
/*
请输入两个整数 n 和 m,程序将生成一个 n ~ m 之间的随机数:10 1000
生成的随机数为:551
*/
思考:为什么 rand() % (m - n + 1)
这么写呢?
rand() % (m - n + 1)
结果的取值范围[0 ,m - n]
rand() % (m - n)
结果的取值范围[0 ,m - n - 1]
规律:余数的下限始终是 0,上限始终是 被除数 - 1
补充:
- 除数:分母
- 被除数:分子
猜数字
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main() {
// 生成随机数的种子
srand(time(NULL));
// 随机生成 1~100 的整数
int target = rand() % 100 + 1;
// 初始化猜测次数为 0
int count = 0;
// 最多猜 10 次
while (count < 10) {
printf("请输入你猜测的数字(1~100之间):");
int guess;
scanf("%d", &guess);
count++;
if (guess == target) {
printf("恭喜你,猜对了!\n");
break;
} else if (guess < target) {
printf("你猜的数字偏小。\n");
} else {
printf("你猜的数字偏大。\n");
}
}
// 如果猜测次数已经用完,仍没有猜中,告诉用户正确答案
if (count >= 10) {
printf("很遗憾,你猜错了。正确的数字是:%d\n", target);
}
return 0;
}
/* 运行结果:
请输入你猜测的数字(1~100之间):30
你猜的数字偏大。
请输入你猜测的数字(1~100之间):20
你猜的数字偏小。
请输入你猜测的数字(1~100之间):25
你猜的数字偏大。
请输入你猜测的数字(1~100之间):23
你猜的数字偏大。
请输入你猜测的数字(1~100之间):22
恭喜你,猜对了!
*/
扩展猜数字游戏:
- 猜测次数可配置:猜测次数由用户手动输入来指定
- 自动更新:当猜测次数超过配置的次数时,程序重新生成一个新的数字
- 告知正确数字:如果用户猜测次数用完了,还没猜对数字,那么告知用户系统生成的随机数是什么
- 退出游戏:如果用户猜测次数用完了,还没猜对数字,那么可以按下
q
退出游戏 - 继续游戏:如果用户猜测次数用完了,还没猜对数字,那么可以按下非
q
继续游戏
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main() {
int max_guesses; // 最大猜测次数
int guess_count = 0; // 当前猜测次数
int number_to_guess; // 待猜测的数字
int user_guess; // 用户猜测的数字
printf("最多允许猜测的次数:");
scanf("%d", &max_guesses);
srand((unsigned)time(NULL)); // 设置随机数种子
while (1) {
number_to_guess = rand() % 100 + 1; // 生成 1~100 的随机整数
printf("已生成新数字,请猜测!\n");
// 重置猜测次数
guess_count = 0;
while (guess_count < max_guesses) {
printf("第 %d 次猜测: ", guess_count + 1);
scanf("%d", &user_guess);
if (user_guess == number_to_guess) {
printf("恭喜你猜对了!\n");
break; // 猜对了,跳出当前循环
} else if (user_guess > number_to_guess) {
printf("你猜的数字太大了。\n");
} else {
printf("你猜的数字太小了。\n");
}
guess_count++;
}
// 判断猜测次数是否超过最大值
if (guess_count == max_guesses) {
printf("你已超过最大猜测次数。正确答案是 %d。\n", number_to_guess);
}
printf("按 'q' 退出,按任意键重新开始: ");
char c;
scanf(" %c", &c);
if (c == 'q') {
break; // 用户选择退出,跳出整个循环
}
}
return 0;
}
// 运行结果:
/*
最多允许猜测的次数:5
已生成新数字,请猜测!
第 1 次猜测: 10
你猜的数字太小了。
第 2 次猜测: 20
你猜的数字太小了。
第 3 次猜测: 50
你猜的数字太大了。
第 4 次猜测: 30
你猜的数字太大了。
第 5 次猜测: 27
你猜的数字太小了。
你已超过最大猜测次数。正确答案是 28。
按 'q' 退出,按任意键重新开始: q
*/
/*
最多允许猜测的次数:5
已生成新数字,请猜测!
第 1 次猜测: 50
你猜的数字太大了。
第 2 次猜测: 30
你猜的数字太大了。
第 3 次猜测: 10
恭喜你猜对了!
按 'q' 退出,按任意键重新开始: q
*/
/*
最多允许猜测的次数:5
已生成新数字,请猜测!
第 1 次猜测: 5
你猜的数字太小了。
第 2 次猜测: 25
你猜的数字太小了。
第 3 次猜测: 50
你猜的数字太小了。
第 4 次猜测: 75
你猜的数字太大了。
第 5 次猜测: 66
你猜的数字太大了。
你已超过最大猜测次数。正确答案是 53。
按 'q' 退出,按任意键重新开始: 1
已生成新数字,请猜测!
第 1 次猜测: 70
恭喜你猜对了!
按 'q' 退出,按任意键重新开始: 1
已生成新数字,请猜测!
第 1 次猜测: 60
你猜的数字太小了。
第 2 次猜测: 80
你猜的数字太大了。
第 3 次猜测: 70
你猜的数字太大了。
第 4 次猜测: 66
你猜的数字太小了。
第 5 次猜测: 68
你猜的数字太小了。
你已超过最大猜测次数。正确答案是 69。
按 'q' 退出,按任意键重新开始: q
*/
使用 sqrt 函数计算一个实数的平方根
sqrt 函数是 C 标准库中的一个函数,其原型定义在 math.h 头文件中,用于 计算一个非负实数的平方根。
- 函数声明:
double sqrt(double x);
- 函数参数:
x
为待计算平方根的数值 - 返回值:
x
的平方根,返回值类型为 double 类型
注意:
- 参数 x 必须是非负实数,否则会返回 NaN(不是数字)。
- 如果 x 是正无穷,则返回正无穷;如果 x 是负无穷,则返回 NaN。
- 对于较大的 x 值,可能存在精度问题,导致返回值不够准确。
#include <math.h>
#include <stdio.h>
int main() {
double num;
printf("请输入一个数字:");
scanf("%lf", &num);
double result = sqrt(num);
printf("该数字的平方根是:%.2lf\n", result);
return 0;
}
/* 运行结果:
请输入一个数字:4.0
该数字的平方根是:2.00
*/
判素数
素数是只能被 1 和自身 n 整除的正整数。
- 数字 n 如果从 2 直到 n-1 都不能被整除,则这个数是素数;
- 反之,则这个是不是素数;
结论:
- 除了 2 以外,所有的素数都是奇数。
- 在判断一个数 n 是否是素数时,只需要从 2 到 n-1 遍历一次,如果发现一个数可以整除 n,则 n 不是素数。
- 优化 | 只需要判断从 2 到 n 的平方根即可,因为如果 n 能被大于平方根的数整除,那么必然也能被小于平方根的数整除。
#include <stdio.h>
int is_prime(int n) {
if (n <= 1) {
return 0;
}
for (int i = 2; i * i <= n; i++) {
if (n % i == 0) {
return 0;
}
}
return 1;
}
int main() {
int n;
printf("请输入一个正整数:");
scanf("%d", &n);
if (is_prime(n)) {
printf("%d 是素数。\n", n);
} else {
printf("%d 不是素数。\n", n);
}
return 0;
}
/* 运行结果:
请输入一个正整数:3
3 是素数。
请输入一个正整数:10
10 不是素数。
*/
i * i <= n;
也可以写成 i <= sqrt(float(n))
,效果都是一样的,不过需要注意的是,如果使用 sqrt 函数,需要引入 math.h
头文件 #include <math.h>
#include <math.h>
#include <stdio.h>
int is_prime(int n) {
if (n < 2) {
return 0;
}
int i;
int sqrt_n = sqrt(n);
for (i = 2; i <= sqrt_n; i++) {
if (n % i == 0) {
return 0;
}
}
return 1;
}
int main() {
int n, i;
printf("请输入一个数字n:");
scanf("%d", &n);
printf("1-%d之间的素数有:\n", n);
for (i = 2; i <= n; i++) {
if (is_prime(i)) {
printf("%d ", i);
}
}
printf("\n");
return 0;
}
/* 运行结果:
请输入一个数字n:100
1-100之间的素数有:
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97
*/
3.7 华为 CloudIDE
求 PI
第三章实验
notes
简述
直接跳过了……
貌似有俩道编程题,视频可以不看,不过编程题得刷一遍。
目前学不动了,这部分先跳过叭……
练习 1:求 PI
题目描述:下面程序的功能是根据近似公式:π/4=1-1/3+1/5-1/7+1/9……
,求 π 值。
输入输出:
- 输入:一个整数 n(回车),其中 n 代表公式中正向
+
和负向-
的总数目。 - 输出:根据公式计算出的 π 值(保留两位小数)。若输入的 n 不合法输出”error”。
思路:
- 输入一个整数 n
- 判断 n 是否不合法
- 若不合法输出 error,并结束
- 否则进行第 3 步
- 依据题意循环 n 次,第偶数次的符号为 -,第奇数次的符号为 +,分母依此增加 2
- 输出结果
#include <stdio.h>
int main() {
int n;
scanf("%d", &n);
if (n <= 0) {
printf("error\n");
return 0;
}
double pi = 0;
for (int i = 0; i < n; ++i) {
if (i % 2 == 0) {
pi += 1.0 / (2 * i + 1);
} else {
pi -= 1.0 / (2 * i + 1);
}
}
pi *= 4;
printf("%.2f\n", pi);
return 0;
}
/* 运行结果:
4
2.90
100
3.13
1000
3.14
0
error
-100
error */
#include <stdio.h>
int main(void) {
int n, j;
float x, y = 0;
scanf("%d", &n); //输入一个n,这里的i就代表题目中所说的n
if (n <= 0) { //判断n输入不合法的情况
printf("error");
} else {
for (j = 1; j < n + 1; j++) //开始循环,从第1项直到第n项
{
x = 2 * j; //可以推出每一项的分母都是增加2
if (j % 2 == 0) //第偶数项 符号为-
{
y -= 1 / (x - 1); //因为x*2是第j个非负偶数,x-1就是第j个非负奇数
} else //第偶数项 符号为+
{
y += 1 / (x - 1);
}
}
printf("%.2f", 4 * y); //因为π/4等于y因此 π=4*y;%.2f中 .2表示保留两位小数
//f表示以浮点数输出
}
}
练习 2: 简单计算器
题目描述:
按照 操作数1 运算符op 操作数2
的格式输入数据进行运算。指定的运算符 op 为 +
-
*
/
(加减乘除)然后按照:操作数1 运算符 op 操作数2 = 计算结果
的形式输出,其中操作数和计算结果取 2 位小数,每个数据间隔 1 个空格,最后输出回车。然后输入 y,则继续按照 操作数1 运算符op 操作数2
的格式输入数据,并输出计算结果,否则输入 n,则结束循环。
其他错误输入,输出 error(error 后面有回车)。再根据输入 y 或者 n 决定是否循环计算。y 或者 Y 表示继续循环,非 y 也非 Y 的字符默认表示结束循环
思路:
- 输入:
操作数1 运算符op 操作数2
(实际测试时,中间不要出现多余的空格) - 判断操作符或者操作数是否不合法。若不合法输出 error,跳到第 4 步。否则进行第 3 步;
- 输出操作数 运算符 操作数 = 计算结果;
- 输入 y 或者 n,入不为 y 或 n,则一直循环第 4 步。若输入 y,回到第 1 步,若输入 n,结束程序。
#include <stdio.h>
int main() {
double a, b;
char op;
char s[1000];
do {
scanf("%lf%c%lf", &a, &op, &b);
getchar();
switch (op) {
case '+':
printf("%.2lf%c%.2lf=%.2lf\n", a, op, b, a + b);
break;
case '-':
printf("%.2lf%c%.2lf=%.2lf\n", a, op, b, a - b);
break;
case '*':
if (a == 0 || b == 0) {
printf("%.2lf%c%.2lf=0.00\n", a, op, b);
break;
}
printf("%.2lf%c%.2lf=%.2lf\n", a, op, b, a * b);
break;
case '/':
if (b == 0) {
printf("error\n");
break;
}
printf("%.2lf%c%.2lf=%.2lf\n", a, op, b, a / b);
break;
default:
printf("error\n");
break;
}
scanf("%s", s);
// printf("%c\n",s[0]);
while (s[0] != 'y' && s[0] != 'n') {
printf("error\n");
scanf("%s", s);
}
} while (s[0] == 'y');
return 0;
}
内存循环:while (s[0] != 'y' && s[0] != 'n') { 提示用户输入错误 }
如果输入的内容并非 y、n 那么程序将卡死在这个循环中,不断打印 error,提示用户输入错误。直到用户输入的字符是 y、n 其中一个,才会退出死循环。
外层循环:do { 继续计算 } while(s[0] == 'y')
- 如果退出死循环时输入的是 y,那么符合外层循环的循环条件,继续计算器的工作
- 如果退出死循环时输入的是 n,那么不满足外层循环的循环条件,推出外层循环,这相当于结束计算器的工作,最终 main 函数结束,程序执行完毕
3.8 华为云平台使用与不同架构运行时间对比
有一道编程题,题目描述已经记录下来了,现在学不动了,这部分就先跳过了…… 后续补上