课件

3.pdf

思维导图

本章主要讲述程序的三种基本结构:顺序结构、分支结构
搭配一些小游戏、小 demo 来码程序练习

3. 控制语句 - 图1

3.1 引言

  1. 顺序结构
  2. 选择结构
  3. 循环结构

认识“顺序”、“选择”、“循环”分别表示什么含义,本章要介绍的核心 —— 控制语句,主要就是在介绍这 3 中结构该如何使用 C 语言代码来表示。

一劳永逸:上述这些结构,几乎是所有编程语言都具备的,特点几乎完全一致。

3.1.1 引言.mp4 (39.8MB) 00:00:49 | 顺序

image.png
顺序执行:一步一步走

00:01:04 | 选择

image.png

选择执行:根据不同的情况做不同的事儿

00:01:26 | 循环

三天打鱼两天晒网:
image.png
image.png

循环执行:某一件事儿不断地去重复做

00:01:50 | 引出本章的核心

image.png
如何使用 C 语言来表示顺序、选择、循环结构,是本章要介绍的主要内容。

3.2 顺序结构

简述:介绍了顺序结构是什么,以及程序设计语言中的基本语句。

顺序结构

都是一些概念性的玩意儿,有个简单的概念就好,对撸代码没啥实质影响。

3.2.1 顺序结构.mp4 (19.85MB)

notes

程序中的 3 种基本结构

  1. 顺序结构:程序从头到尾按照顺序执行,每条语句都执行一次,不跳过也不重复。
  2. 分支结构:根据条件选择执行某一条或多条语句,通常使用 if-else 语句、switch 语句等。
  3. 循环结构:根据条件重复执行某一条或多条语句,通常使用 while 循环、do-while 循环、for 循环等。

大量的实际问题需要通过各种控制流程来解决,这三种基本结构可以组合起来构成复杂的程序用于解决复杂问题。

补充:

  • 有的书中说,这 3 种基本结构可以解决所有实际问题,也就意味着,所有程序其实都是这 3 种结构组合而成的。
  • 理解这 3 种结构的执行流程并非难事
  • 难点在于利用这 3 种基本结构,编写出满足业务要求的程序,当然,这并非一朝一夕能做到的事儿,后续还有很多知识点待我们挖掘

顺序结构

image.png

程序中的 3 种基本语句

image.png
image.png

  1. 简单语句:执行一条语句的基本单元,它可以是赋值语句、函数调用语句、流程控制语句等。它通常以分号 ; 结尾。
    • 例:z = x + y;c = getchar();
  2. 复合语句:由多个简单语句组成的语句块(简单语句的集合),通常使用花括号 { } 括起来。复合语句中的语句可以是任意类型的语句,包括另一个复合语句。
  3. 空语句:没有实际执行效果的语句,通常用于 占位符调试 目的。空语句只包含一个分号 ;,不会执行任何代码。

3.3 选择结构

选择结构

掌握 if 语句和 switch 语句

通过编写几个简单的 demo 来认识选择结构是什么。

3.3.1 选择结构.mp4 (150.12MB)

notes

if 流程、if-else 流程

image.png

image.png

switch 多路开关语句

image.png

补充:实际开发中 switch 语句出现频率很低,几乎都使用 if-else 来写的。

使用 if-else 结构,输入一个整数,判断这个整数是奇数还是偶数

  1. #include <stdio.h>
  2. int main() {
  3. int num;
  4. printf("请输入一个整数:\n");
  5. scanf("%d", &num);
  6. if (num % 2 == 0) {
  7. printf("%d是偶数。\n", num);
  8. } else {
  9. printf("%d是奇数。\n", num);
  10. }
  11. return 0;
  12. }
  13. /* 运行结果:
  14. 请输入一个整数:
  15. 1
  16. 1是奇数。
  17. 请输入一个整数:
  18. 10
  19. 10是偶数。
  20. */

使用三目运算符,输入一个整数,判断这个整数是奇数还是偶数

  1. #include <stdio.h>
  2. int main() {
  3. int num;
  4. printf("请输入一个整数:\n");
  5. scanf("%d", &num);
  6. num % 2 == 0 ? printf("%d 是偶数。\n", num) : printf("%d 是奇数。\n", num);
  7. return 0;
  8. }
  9. /* 运行结果:
  10. 请输入一个整数:
  11. 1
  12. 1 是奇数。
  13. 请输入一个整数:
  14. 10
  15. 10 是偶数。
  16. */

使用 switch 结构,输入一个月份(数字),打印该月份对应的英文

  1. #include <stdio.h>
  2. int main() {
  3. int month;
  4. printf("请输入月份:");
  5. scanf("%d", &month);
  6. switch (month) {
  7. case 1:
  8. printf("January\n");
  9. break;
  10. case 2:
  11. printf("February\n");
  12. break;
  13. case 3:
  14. printf("March\n");
  15. break;
  16. case 4:
  17. printf("April\n");
  18. break;
  19. case 5:
  20. printf("May\n");
  21. break;
  22. case 6:
  23. printf("June\n");
  24. break;
  25. case 7:
  26. printf("July\n");
  27. break;
  28. case 8:
  29. printf("August\n");
  30. break;
  31. case 9:
  32. printf("September\n");
  33. break;
  34. case 10:
  35. printf("October\n");
  36. break;
  37. case 11:
  38. printf("November\n");
  39. break;
  40. case 12:
  41. printf("December\n");
  42. break;
  43. default:
  44. printf("输入有误,请输入 1-12 之间的整数。\n");
  45. break;
  46. }
  47. return 0;
  48. }
  49. /* 运行结果:
  50. 请输入月份:3
  51. March
  52. */

使用 if-else 结构,输入一个月份(数字),打印该月份对应的英文

  1. #include <stdio.h>
  2. int main() {
  3. int month;
  4. printf("请输入月份:");
  5. scanf("%d", &month);
  6. if (month == 1) {
  7. printf("January\n");
  8. } else if (month == 2) {
  9. printf("February\n");
  10. } else if (month == 3) {
  11. printf("March\n");
  12. } else if (month == 4) {
  13. printf("April\n");
  14. } else if (month == 5) {
  15. printf("May\n");
  16. } else if (month == 6) {
  17. printf("June\n");
  18. } else if (month == 7) {
  19. printf("July\n");
  20. } else if (month == 8) {
  21. printf("August\n");
  22. } else if (month == 9) {
  23. printf("September\n");
  24. } else if (month == 10) {
  25. printf("October\n");
  26. } else if (month == 11) {
  27. printf("November\n");
  28. } else if (month == 12) {
  29. printf("December\n");
  30. } else {
  31. printf("输入有误,请输入 1-12 之间的整数。\n");
  32. }
  33. return 0;
  34. }
  35. /* 运行结果:
  36. 请输入月份:3
  37. March
  38. */

3.4 循环结构

认识 3 种循环结构,重点掌握好 for 循环,相较于 while、do-while 循环,for 循环更加常见

循环结构

通过几个练手的小 demo,认识一下 for、while 循环的执行流程是咋样的。

3.4.1 循环.mp4 (182.8MB)

只要条件满足,那么就不断地做某件事儿,直到条件不满足,才会继续干后续的其它事儿

notes

for 循环

  1. for (表达式 1; 表达式 2; 表达式 3) {
  2. 执行语句;
  3. }
  • 表达式 1:循环变量初始化
  • 表达式 2:循环条件
  • 表达式 3:更新循环变量

3. 控制语句 - 图17

for 循环的执行流程:

  1. 循环变量初始化:在第一次循环开始前,循环变量被赋值为循环初始值。
  2. 循环条件判断:然后判断循环条件是否为真,如果为假,则跳出循环;如果为真,则执行执行语句。
  3. 执行循环体
  4. 更新循环变量:按照循环增量的规则对循环变量进行更新。
  5. 循环条件判断:然后再次判断循环条件是否为真,如果为假,则跳出循环;如果为真,则继续执行执行语句。
  6. 依此类推,直到循环条件为假时跳出循环。

如果按照常规的 for 循环的执行流程来看,上述描述是没问题的。但是需要注意的是,for 循环是非常灵活的,某些情况下我们可能会省略掉一些步骤,从而导致上述的某些步骤在我们看来是没有必要的。比如:

  • 有些步骤可能是在 for 循环外部就已经做了,比如循环变量初始化,此时就没必要在 for 循环中再去做循环变量的初始化了
  • 有些步骤可能是在循环体内部做的,比如循环变量的改变,此时就没必要在 for 循环中再去做循环变量的更新了

while、do-while

  1. while (condition) {
  2. // 循环体
  3. }

其中 condition 是一个表达式

  • 值为 非零 时,循环体会一直执行下去
  • 值为 0 时,循环体停止执行,继续执行循环语句后面的代码
  1. do {
  2. // 循环体
  3. } 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. 首先判断循环条件是否为真。
  2. 如果循环条件为真,则执行循环体中的语句,并回到步骤 1。
  3. 如果循环条件为假,则退出循环。

直到型循环也叫做后测试循环,do-while 循环就是一种直到型循环。它的执行流程如下:

  1. 首先执行循环体中的语句。
  2. 然后判断循环条件是否为真。
  3. 如果循环条件为真,则回到步骤 1。
  4. 如果循环条件为假,则退出循环。

3. 控制语句 - 图18

for 循环的省略形式

  1. #include <stdio.h>
  2. int main() {
  3. int count = 0;
  4. for (;;) {
  5. printf("count = %d\n", count);
  6. count++;
  7. if (count > 10) {
  8. break;
  9. }
  10. }
  11. return 0;
  12. }
  13. /* 运行结果:
  14. count = 0
  15. count = 1
  16. count = 2
  17. count = 3
  18. count = 4
  19. count = 5
  20. count = 6
  21. count = 7
  22. count = 8
  23. count = 9
  24. count = 10
  25. */

上述代码使用了 for 循环的省略形式,即只写了循环体的条件部分,而省略了初始值和增量部分,相当于初始值为 0,增量为 1。循环体中打印出变量 count 的值,并将其自增,当 count 的值大于 10 时,使用 break 语句跳出循环。因此,该程序输出了 0 到 10 的整数值。

  1. for (循环变量的初始值; 循环条件; 循环变量的增量) {
  2. 执行语句;
  3. }

由此 demo 可见,for 循环中的:

  1. 循环变量的初始值
  2. 循环条件
  3. 循环变量的增量

都不是必须的,若我们不需要这些语句,沈略不写即可。但是语法还是要遵循的,语句之间的 ; 分号不能少。

输入整数 n 然后输出 n 的 1-5 次方

  1. #include <stdio.h>
  2. int main() {
  3. int n;
  4. printf("请输入一个整数:");
  5. scanf("%d", &n);
  6. printf("%d的1次方是:%d\n", n, n);
  7. printf("%d的2次方是:%d\n", n, n * n);
  8. printf("%d的3次方是:%d\n", n, n * n * n);
  9. printf("%d的4次方是:%d\n", n, n * n * n * n);
  10. printf("%d的5次方是:%d\n", n, n * n * n * n * n);
  11. return 0;
  12. }
  13. /* 运行结果:
  14. 请输入一个整数:2
  15. 2的1次方是:2
  16. 2的2次方是:4
  17. 2的3次方是:8
  18. 2的4次方是:16
  19. 2的5次方是:32
  20. */

输入整数 n,r 然后输出 n 的 1-r 次方

  1. #include <stdio.h>
  2. int main() {
  3. int n, r;
  4. printf("请输入整数n和r:");
  5. scanf("%d %d", &n, &r);
  6. for (int i = 1; i <= r; i++) {
  7. int result = 1;
  8. for (int j = 1; j <= i; j++) {
  9. result *= n;
  10. }
  11. printf("%d的%d次方是:%d\n", n, i, result);
  12. }
  13. return 0;
  14. }
  15. /* 运行结果:
  16. 请输入整数n和r:3 4
  17. 3的1次方是:3
  18. 3的2次方是:9
  19. 3的3次方是:27
  20. 3的4次方是:81
  21. */

打印九九乘法表

  1. #include <stdio.h>
  2. int main() {
  3. int i, j;
  4. for (i = 1; i <= 9; i++) { // 行
  5. for (j = 1; j <= i; j++) { // 列
  6. printf("%d*%d=%-2d ", j, i, j * i); // 使用 %-2d 实现左对齐
  7. }
  8. printf("\n");
  9. }
  10. return 0;
  11. }
  12. /* 运行结果:
  13. 1*1=1
  14. 1*2=2 2*2=4
  15. 1*3=3 2*3=6 3*3=9
  16. 1*4=4 2*4=8 3*4=12 4*4=16
  17. 1*5=5 2*5=10 3*5=15 4*5=20 5*5=25
  18. 1*6=6 2*6=12 3*6=18 4*6=24 5*6=30 6*6=36
  19. 1*7=7 2*7=14 3*7=21 4*7=28 5*7=35 6*7=42 7*7=49
  20. 1*8=8 2*8=16 3*8=24 4*8=32 5*8=40 6*8=48 7*8=56 8*8=64
  21. 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
  22. */

读入字符并回显,直到读入 * 字符为止的程序段

  1. #include <stdio.h>
  2. int main() {
  3. char c;
  4. while (1) {
  5. scanf("%c", &c);
  6. if (c == '*') {
  7. break;
  8. }
  9. printf("%c", c);
  10. }
  11. return 0;
  12. }
  13. /* 运行结果:
  14. aBCde?f*123123
  15. aBCde?f
  16. */
  1. #include <stdio.h>
  2. int main() {
  3. char ch;
  4. while ((ch = getchar()) != '*') {
  5. putchar(ch);
  6. }
  7. return 0;
  8. }
  9. /* 运行结果:
  10. aBCde?f*123123
  11. aBCde?f
  12. */

3.5 break、continue

break、continue

3.5.1 breakContinue.mp4 (41.62MB)

notes

简述

介绍了 break、continue、goto 语句的作用

break 语句

image.png

break 语句是 C 语言中的一种控制语句。

位置:

  • 循环体中:当执行到 break 语句时,会立即终止当前循环,跳出循环体,继续执行循环后面的语句。
  • switch 语句中:用于结束当前的 case 分支并跳出 switch 语句。

作用:

  • 在满足一定条件时提前结束循环,从而提高程序的执行效率
  • 可以避免程序进入死循环等问题

continue 语句

image.png

continue 是书写在循环语句中的一种流程控制语句。

作用:

  • 当 continue 语句执行时,程序将会跳过循环体中剩余的语句,然后继续下一次循环迭代
  • 在循环中使用 continue 语句来跳过某些特殊情况下的迭代,以避免出现错误或提高程序的效率

goto 语句

image.png

goto 语句是 C 语言中的一种流程控制语句,主要作用是在程序中实现跳转。

在执行过程中,当遇到 goto 语句时,程序会立即跳转到指定的标签处,继续执行该标签后的语句

应用场景:

  • 在出错的情况下,可以使用 goto 语句跳转到异常处理的代码处;
  • 在实现循环、嵌套等复杂的程序流程时,也可以使用 goto 语句;

注意:

  • goto 语句的使用需要谨慎,因为 它容易导致代码的混乱和不易维护
  • 一般情况下,使用结构化编程方式编写程序更容易理解和维护。

补充:据同事说,这玩意儿很少用,学习时只要了解一下它的作用即可,不必找大量 demo 来练习。

break | 打印输入字符,直到输入 s 时截止

  1. #include <stdio.h>
  2. int main() {
  3. char ch;
  4. while (1) {
  5. scanf("%c", &ch);
  6. if (ch == 's') {
  7. break;
  8. } else {
  9. printf("%c", ch);
  10. }
  11. }
  12. return 0;
  13. }
  14. /* 运行结果:
  15. 123sabc
  16. 123
  17. */

break | 打印小于 5 的所有非负整数

  1. #include <stdio.h>
  2. int main() {
  3. int i;
  4. for (i = 0; i < 10; i++) {
  5. if (i == 5) {
  6. break;
  7. }
  8. printf("%d ", i);
  9. }
  10. printf("\n");
  11. return 0;
  12. }
  13. /* 运行结果:
  14. 0 1 2 3 4
  15. */

break | 在九九乘法表中,找第一个乘积为 56 的整数对

  1. #include <stdio.h>
  2. int main() {
  3. int i, j;
  4. for (i = 1; i <= 9; i++) {
  5. for (j = 1; j <= i; j++) {
  6. if (i * j == 56) {
  7. printf("i = %d, j = %d\n", i, j);
  8. break; // 退出内层循环
  9. }
  10. }
  11. if (j <= i) { // 判断内层循环是否已经被退出
  12. break; // 退出外层循环
  13. }
  14. }
  15. return 0;
  16. }
  17. /* 运行结果:
  18. i = 8, j = 7
  19. */

注意:break 只能跳出当前这一层的循环体

break; // 退出内层循环
这个 break 语句位于内层 j 循环中,它只能跳出内层循环而无法跳出外层 i 循环。

break; // 退出外层循环
这个 break 语句位于外层 i 循环中,它能够跳出外层 i 循环。

continue | 循环输出 1 到 10 之间的所有奇数

逆向思维:是偶数就不输出

  1. #include <stdio.h>
  2. int main() {
  3. for (int i = 1; i <= 10; i++) {
  4. if (i % 2 == 0) {
  5. // 跳过所有偶数
  6. continue;
  7. }
  8. printf("%d\n", i);
  9. }
  10. return 0;
  11. }
  12. /* 运行结果:
  13. 1
  14. 3
  15. 5
  16. 7
  17. 9
  18. */

goto | 非负整数数组求和

需求描述:有一个整数数组,但是这个整数数组中可能会存在非负整数,现要求计算这个整数数组中所有元素的和。

  • 如果数组中没有出现负数,那么将所有整数进行求和,并将求和后的结果打印出来;
  • 如果数组中的任何元素为负数,则跳转到 error 标签并输出错误信息;
  1. #include <stdio.h>
  2. int main() {
  3. int arr[5] = {1, 2, 3, -4, 5};
  4. int sum = 0;
  5. int i;
  6. for (i = 0; i < 5; i++) {
  7. if (arr[i] < 0) {
  8. goto error;
  9. }
  10. sum += arr[i];
  11. }
  12. printf("数组元素的和为:%d\n", sum);
  13. return 0;
  14. error:
  15. printf("错误:数组中出现负数。\n");
  16. return 1;
  17. }
  18. /* 运行结果:
  19. 错误:数组中出现负数。
  20. */
  1. #include <stdio.h>
  2. int main() {
  3. int arr[5] = {1, 2, 3, 4, 5};
  4. int sum = 0;
  5. int i;
  6. for (i = 0; i < 5; i++) {
  7. if (arr[i] < 0) {
  8. goto error;
  9. }
  10. sum += arr[i];
  11. }
  12. printf("数组元素的和为:%d\n", sum);
  13. return 0;
  14. error:
  15. printf("错误:数组中出现负数。\n");
  16. return 1;
  17. }
  18. /* 运行结果:
  19. 数组元素的和为:15
  20. */

3.6 编程实战

编程实战1:买苹果

3.6.1 编程实战1:买苹果.mp4 (179.16MB) 00:00:19 | 买苹果 | 题目

image.png

题目描述:
image.png

第 4 点描述不准确,容易产生歧义:

  1. 当天购买的苹果不超过 100 的最大值
  2. 每一天购买的苹果求和,即所有苹果总量不超过 100 的最大值

从视频中提供的源码逻辑来看,应该是指 1 也就是当天购买的苹果总量一旦超过 100,那么就不再继续够买了,开始计算每天花费的平均值

分析:
image.png

00:04:14 | 源码

image.png
image.png

00:17:33 | 作业

image.png

#defined MAX 100 宏定义常量

作业要求:扩展我们的程序,让天数 n 由用户指定。

编程实战2:猜数游戏

3.6.2 编程实战2:猜数游戏.mp4 (153.72MB)

编程实战3:求素数

3.6.3 编程实战3:求素数.mp4 (31.45MB)

notes

简述

独立完成 3 个练习 | 买苹果 | 猜数字 | 求素数

这一部分涉及到一些额外的扩展知识,比如 rand、srand、sqrt 函数的基本基本使用。

引用:

  • 了解题目要求 👉🏻 3.6.1 编程实战1:买苹果
  • 了解题目要求 👉🏻 3.6.2 编程实战2:猜数游戏
  • time 函数使用说明 👉🏻 4.3 编程实战

买苹果

  1. #include <stdio.h>
  2. int main() {
  3. float price = 0.8, avg_price = 0;
  4. int apple_count = 2, day, totalCount = 0;
  5. for (day = 1; apple_count <= 100; day++) {
  6. totalCount += apple_count;
  7. apple_count *= 2;
  8. }
  9. avg_price = (totalCount * price) / (day - 1);
  10. printf("平均每天花费 %.2lf 元\n", avg_price);
  11. return 0;
  12. }
  13. /* 运行结果:
  14. 平均每天花费 16.80 元
  15. */
  1. #include <stdio.h>
  2. int main() {
  3. float price = 0.8, avg_price = 0;
  4. int apple_count = 2, day = 1, totalCount = 0;
  5. while (apple_count <= 100) {
  6. totalCount += apple_count;
  7. printf("第 %d 天,购买了 %d 个苹果,当天花费 %.2f 元,一共购买了 %d "
  8. "个苹果,总花费 %.2f 元\n",
  9. day, apple_count, apple_count * price, totalCount,
  10. totalCount * price);
  11. apple_count *= 2;
  12. day++;
  13. }
  14. avg_price = (totalCount * price) / (day - 1);
  15. printf("平均每天花费 %.2lf 元\n", avg_price);
  16. return 0;
  17. }
  18. /* 运行结果:
  19. 第 1 天,购买了 2 个苹果,当天花费 1.60 元,一共购买了 2 个苹果,总花费 1.60 元
  20. 第 2 天,购买了 4 个苹果,当天花费 3.20 元,一共购买了 6 个苹果,总花费 4.80 元
  21. 第 3 天,购买了 8 个苹果,当天花费 6.40 元,一共购买了 14 个苹果,总花费 11.20 元
  22. 第 4 天,购买了 16 个苹果,当天花费 12.80 元,一共购买了 30 个苹果,总花费 24.00 元
  23. 第 5 天,购买了 32 个苹果,当天花费 25.60 元,一共购买了 62 个苹果,总花费 49.60 元
  24. 第 6 天,购买了 64 个苹果,当天花费 51.20 元,一共购买了 126 个苹果,总花费 100.80 元
  25. 平均每天花费 16.80 元
  26. */

作业:扩展我们的程序,让天数 n 由用户指定。

  1. #include <stdio.h>
  2. int main() {
  3. float price = 0.8, // 苹果的单价
  4. avg_price = 0; // 苹果的平均价格
  5. int apple_count = 2, // 当天购买的苹果的数量
  6. day = 1, // 第几天购买苹果
  7. totalCount = 0, // 购买的苹果的总量
  8. totalDay; // 购买苹果的总天数
  9. // 获取用户输入的购买苹果的总天数
  10. printf("请输入连续购买苹果的天数:");
  11. scanf("%d", &totalDay);
  12. while (day <= totalDay) {
  13. totalCount += apple_count;
  14. printf("第 %d 天,购买了 %d 个苹果,当天花费 %.2f 元,一共购买了 %d "
  15. "个苹果,总花费 %.2f 元\n",
  16. day, apple_count, apple_count * price, totalCount,
  17. totalCount * price);
  18. apple_count *= 2;
  19. day++;
  20. }
  21. avg_price = (totalCount * price) / (day - 1);
  22. printf("平均每天花费 %.2lf 元\n", avg_price);
  23. return 0;
  24. }
  25. /* 运行结果:
  26. 请输入连续购买苹果的天数:6
  27. 第 1 天,购买了 2 个苹果,当天花费 1.60 元,一共购买了 2 个苹果,总花费 1.60 元
  28. 第 2 天,购买了 4 个苹果,当天花费 3.20 元,一共购买了 6 个苹果,总花费 4.80 元
  29. 第 3 天,购买了 8 个苹果,当天花费 6.40 元,一共购买了 14 个苹果,总花费 11.20 元
  30. 第 4 天,购买了 16 个苹果,当天花费 12.80 元,一共购买了 30 个苹果,总花费 24.00 元
  31. 第 5 天,购买了 32 个苹果,当天花费 25.60 元,一共购买了 62 个苹果,总花费 49.60 元
  32. 第 6 天,购买了 64 个苹果,当天花费 51.20 元,一共购买了 126 个苹果,总花费 100.80 元
  33. 平均每天花费 16.80 元
  34. 请输入连续购买苹果的天数:10
  35. 第 1 天,购买了 2 个苹果,当天花费 1.60 元,一共购买了 2 个苹果,总花费 1.60 元
  36. 第 2 天,购买了 4 个苹果,当天花费 3.20 元,一共购买了 6 个苹果,总花费 4.80 元
  37. 第 3 天,购买了 8 个苹果,当天花费 6.40 元,一共购买了 14 个苹果,总花费 11.20 元
  38. 第 4 天,购买了 16 个苹果,当天花费 12.80 元,一共购买了 30 个苹果,总花费 24.00 元
  39. 第 5 天,购买了 32 个苹果,当天花费 25.60 元,一共购买了 62 个苹果,总花费 49.60 元
  40. 第 6 天,购买了 64 个苹果,当天花费 51.20 元,一共购买了 126 个苹果,总花费 100.80 元
  41. 第 7 天,购买了 128 个苹果,当天花费 102.40 元,一共购买了 254 个苹果,总花费 203.20 元
  42. 第 8 天,购买了 256 个苹果,当天花费 204.80 元,一共购买了 510 个苹果,总花费 408.00 元
  43. 第 9 天,购买了 512 个苹果,当天花费 409.60 元,一共购买了 1022 个苹果,总花费 817.60 元
  44. 第 10 天,购买了 1024 个苹果,当天花费 819.20 元,一共购买了 2046 个苹果,总花费 1636.80 元
  45. 平均每天花费 163.68 元
  46. */

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() 函数的返回值作为种子值,可以使每次程序运行时生成的随机数序列不同。
  1. srand(time(NULL)); // 使用当前时间作为种子值
  2. int randomNumber = rand(); // 生成伪随机数
  • srand(time(NULL)) 作用是 初始化随机数生成器的种子,使每次生成的随机数序列不同
  • time(NULL) 函数返回的是一个时间戳,因为当前时间是一直变化的,用它来作为 srand 函数的种子参数是比较合适的
  • 每次调用 rand() 获取随机数之前,调用一下 srand(time(NULL)) 即可确保每次运行程序时都会得到不同的随机数序列,以得到一个“真随机数”
  1. srand((unsigned)time(NULL)); // 使用当前时间作为种子值
  2. 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 的随机数

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <time.h>
  4. int main() {
  5. int n, m, random_number;
  6. printf("请输入两个整数 n 和 m,程序将生成一个 n ~ m 之间的随机数:");
  7. scanf("%d%d", &n, &m);
  8. srand((unsigned)time(NULL)); // 设置随机数种子
  9. random_number = rand() % (m - n + 1) + n; // 生成 n ~ m 之间的随机整数
  10. printf("生成的随机数为:%d\n", random_number);
  11. return 0;
  12. }
  13. // 运行结果:
  14. /*
  15. 请输入两个整数 n 和 m,程序将生成一个 n ~ m 之间的随机数:10 1000
  16. 生成的随机数为:551
  17. */

思考:为什么 rand() % (m - n + 1) 这么写呢?

  • rand() % (m - n + 1) 结果的取值范围 [0 ,m - n]
  • rand() % (m - n) 结果的取值范围 [0 ,m - n - 1]

规律:余数的下限始终是 0,上限始终是 被除数 - 1

补充:

  • 除数:分母
  • 被除数:分子

猜数字

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <time.h>
  4. int main() {
  5. // 生成随机数的种子
  6. srand(time(NULL));
  7. // 随机生成 1~100 的整数
  8. int target = rand() % 100 + 1;
  9. // 初始化猜测次数为 0
  10. int count = 0;
  11. // 最多猜 10 次
  12. while (count < 10) {
  13. printf("请输入你猜测的数字(1~100之间):");
  14. int guess;
  15. scanf("%d", &guess);
  16. count++;
  17. if (guess == target) {
  18. printf("恭喜你,猜对了!\n");
  19. break;
  20. } else if (guess < target) {
  21. printf("你猜的数字偏小。\n");
  22. } else {
  23. printf("你猜的数字偏大。\n");
  24. }
  25. }
  26. // 如果猜测次数已经用完,仍没有猜中,告诉用户正确答案
  27. if (count >= 10) {
  28. printf("很遗憾,你猜错了。正确的数字是:%d\n", target);
  29. }
  30. return 0;
  31. }
  32. /* 运行结果:
  33. 请输入你猜测的数字(1~100之间):30
  34. 你猜的数字偏大。
  35. 请输入你猜测的数字(1~100之间):20
  36. 你猜的数字偏小。
  37. 请输入你猜测的数字(1~100之间):25
  38. 你猜的数字偏大。
  39. 请输入你猜测的数字(1~100之间):23
  40. 你猜的数字偏大。
  41. 请输入你猜测的数字(1~100之间):22
  42. 恭喜你,猜对了!
  43. */

扩展猜数字游戏:

  1. 猜测次数可配置:猜测次数由用户手动输入来指定
  2. 自动更新:当猜测次数超过配置的次数时,程序重新生成一个新的数字
  3. 告知正确数字:如果用户猜测次数用完了,还没猜对数字,那么告知用户系统生成的随机数是什么
  4. 退出游戏:如果用户猜测次数用完了,还没猜对数字,那么可以按下 q 退出游戏
  5. 继续游戏:如果用户猜测次数用完了,还没猜对数字,那么可以按下非 q 继续游戏
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <time.h>
  4. int main() {
  5. int max_guesses; // 最大猜测次数
  6. int guess_count = 0; // 当前猜测次数
  7. int number_to_guess; // 待猜测的数字
  8. int user_guess; // 用户猜测的数字
  9. printf("最多允许猜测的次数:");
  10. scanf("%d", &max_guesses);
  11. srand((unsigned)time(NULL)); // 设置随机数种子
  12. while (1) {
  13. number_to_guess = rand() % 100 + 1; // 生成 1~100 的随机整数
  14. printf("已生成新数字,请猜测!\n");
  15. // 重置猜测次数
  16. guess_count = 0;
  17. while (guess_count < max_guesses) {
  18. printf("第 %d 次猜测: ", guess_count + 1);
  19. scanf("%d", &user_guess);
  20. if (user_guess == number_to_guess) {
  21. printf("恭喜你猜对了!\n");
  22. break; // 猜对了,跳出当前循环
  23. } else if (user_guess > number_to_guess) {
  24. printf("你猜的数字太大了。\n");
  25. } else {
  26. printf("你猜的数字太小了。\n");
  27. }
  28. guess_count++;
  29. }
  30. // 判断猜测次数是否超过最大值
  31. if (guess_count == max_guesses) {
  32. printf("你已超过最大猜测次数。正确答案是 %d。\n", number_to_guess);
  33. }
  34. printf("按 'q' 退出,按任意键重新开始: ");
  35. char c;
  36. scanf(" %c", &c);
  37. if (c == 'q') {
  38. break; // 用户选择退出,跳出整个循环
  39. }
  40. }
  41. return 0;
  42. }
  43. // 运行结果:
  44. /*
  45. 最多允许猜测的次数:5
  46. 已生成新数字,请猜测!
  47. 第 1 次猜测: 10
  48. 你猜的数字太小了。
  49. 第 2 次猜测: 20
  50. 你猜的数字太小了。
  51. 第 3 次猜测: 50
  52. 你猜的数字太大了。
  53. 第 4 次猜测: 30
  54. 你猜的数字太大了。
  55. 第 5 次猜测: 27
  56. 你猜的数字太小了。
  57. 你已超过最大猜测次数。正确答案是 28。
  58. 按 'q' 退出,按任意键重新开始: q
  59. */
  60. /*
  61. 最多允许猜测的次数:5
  62. 已生成新数字,请猜测!
  63. 第 1 次猜测: 50
  64. 你猜的数字太大了。
  65. 第 2 次猜测: 30
  66. 你猜的数字太大了。
  67. 第 3 次猜测: 10
  68. 恭喜你猜对了!
  69. 按 'q' 退出,按任意键重新开始: q
  70. */
  71. /*
  72. 最多允许猜测的次数:5
  73. 已生成新数字,请猜测!
  74. 第 1 次猜测: 5
  75. 你猜的数字太小了。
  76. 第 2 次猜测: 25
  77. 你猜的数字太小了。
  78. 第 3 次猜测: 50
  79. 你猜的数字太小了。
  80. 第 4 次猜测: 75
  81. 你猜的数字太大了。
  82. 第 5 次猜测: 66
  83. 你猜的数字太大了。
  84. 你已超过最大猜测次数。正确答案是 53。
  85. 按 'q' 退出,按任意键重新开始: 1
  86. 已生成新数字,请猜测!
  87. 第 1 次猜测: 70
  88. 恭喜你猜对了!
  89. 按 'q' 退出,按任意键重新开始: 1
  90. 已生成新数字,请猜测!
  91. 第 1 次猜测: 60
  92. 你猜的数字太小了。
  93. 第 2 次猜测: 80
  94. 你猜的数字太大了。
  95. 第 3 次猜测: 70
  96. 你猜的数字太大了。
  97. 第 4 次猜测: 66
  98. 你猜的数字太小了。
  99. 第 5 次猜测: 68
  100. 你猜的数字太小了。
  101. 你已超过最大猜测次数。正确答案是 69。
  102. 按 'q' 退出,按任意键重新开始: q
  103. */

使用 sqrt 函数计算一个实数的平方根

sqrt 函数是 C 标准库中的一个函数,其原型定义在 math.h 头文件中,用于 计算一个非负实数的平方根

  • 函数声明:double sqrt(double x);
  • 函数参数:x 为待计算平方根的数值
  • 返回值:x 的平方根,返回值类型为 double 类型

注意:

  1. 参数 x 必须是非负实数,否则会返回 NaN(不是数字)。
  2. 如果 x 是正无穷,则返回正无穷;如果 x 是负无穷,则返回 NaN。
  3. 对于较大的 x 值,可能存在精度问题,导致返回值不够准确。
  1. #include <math.h>
  2. #include <stdio.h>
  3. int main() {
  4. double num;
  5. printf("请输入一个数字:");
  6. scanf("%lf", &num);
  7. double result = sqrt(num);
  8. printf("该数字的平方根是:%.2lf\n", result);
  9. return 0;
  10. }
  11. /* 运行结果:
  12. 请输入一个数字:4.0
  13. 该数字的平方根是:2.00
  14. */

判素数

素数是只能被 1 和自身 n 整除的正整数。

  • 数字 n 如果从 2 直到 n-1 都不能被整除,则这个数是素数;
  • 反之,则这个是不是素数;

结论:

  1. 除了 2 以外,所有的素数都是奇数。
  2. 在判断一个数 n 是否是素数时,只需要从 2 到 n-1 遍历一次,如果发现一个数可以整除 n,则 n 不是素数。
    1. 优化 | 只需要判断从 2 到 n 的平方根即可,因为如果 n 能被大于平方根的数整除,那么必然也能被小于平方根的数整除。
  1. #include <stdio.h>
  2. int is_prime(int n) {
  3. if (n <= 1) {
  4. return 0;
  5. }
  6. for (int i = 2; i * i <= n; i++) {
  7. if (n % i == 0) {
  8. return 0;
  9. }
  10. }
  11. return 1;
  12. }
  13. int main() {
  14. int n;
  15. printf("请输入一个正整数:");
  16. scanf("%d", &n);
  17. if (is_prime(n)) {
  18. printf("%d 是素数。\n", n);
  19. } else {
  20. printf("%d 不是素数。\n", n);
  21. }
  22. return 0;
  23. }
  24. /* 运行结果:
  25. 请输入一个正整数:3
  26. 3 是素数。
  27. 请输入一个正整数:10
  28. 10 不是素数。
  29. */

i * i <= n; 也可以写成 i <= sqrt(float(n)),效果都是一样的,不过需要注意的是,如果使用 sqrt 函数,需要引入 math.h 头文件 #include <math.h>

  1. #include <math.h>
  2. #include <stdio.h>
  3. int is_prime(int n) {
  4. if (n < 2) {
  5. return 0;
  6. }
  7. int i;
  8. int sqrt_n = sqrt(n);
  9. for (i = 2; i <= sqrt_n; i++) {
  10. if (n % i == 0) {
  11. return 0;
  12. }
  13. }
  14. return 1;
  15. }
  16. int main() {
  17. int n, i;
  18. printf("请输入一个数字n:");
  19. scanf("%d", &n);
  20. printf("1-%d之间的素数有:\n", n);
  21. for (i = 2; i <= n; i++) {
  22. if (is_prime(i)) {
  23. printf("%d ", i);
  24. }
  25. }
  26. printf("\n");
  27. return 0;
  28. }
  29. /* 运行结果:
  30. 请输入一个数字n:100
  31. 1-100之间的素数有:
  32. 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
  33. */

3.7 华为 CloudIDE

求 PI

3.7.1 华为CloudIDE-码图206-求PI.mp4 (94MB) 00:00:51 | 题目编号:206

image.png

第三章实验

3.7.2 华为CloudIDE-码图第三章实验.mp4 (12.05MB) 00:00:24 | 题目编号:268

image.png

notes

简述

直接跳过了……
貌似有俩道编程题,视频可以不看,不过编程题得刷一遍。
目前学不动了,这部分先跳过叭……

练习 1:求 PI

题目描述:下面程序的功能是根据近似公式:π/4=1-1/3+1/5-1/7+1/9……,求 π 值。

输入输出:

  • 输入:一个整数 n(回车),其中 n 代表公式中正向 + 和负向 - 的总数目。
  • 输出:根据公式计算出的 π 值(保留两位小数)。若输入的 n 不合法输出”error”。

思路:

  1. 输入一个整数 n
  2. 判断 n 是否不合法
    1. 若不合法输出 error,并结束
    2. 否则进行第 3 步
  3. 依据题意循环 n 次,第偶数次的符号为 -,第奇数次的符号为 +,分母依此增加 2
  4. 输出结果

测试用例

  1. #include <stdio.h>
  2. int main() {
  3. int n;
  4. scanf("%d", &n);
  5. if (n <= 0) {
  6. printf("error\n");
  7. return 0;
  8. }
  9. double pi = 0;
  10. for (int i = 0; i < n; ++i) {
  11. if (i % 2 == 0) {
  12. pi += 1.0 / (2 * i + 1);
  13. } else {
  14. pi -= 1.0 / (2 * i + 1);
  15. }
  16. }
  17. pi *= 4;
  18. printf("%.2f\n", pi);
  19. return 0;
  20. }
  21. /* 运行结果:
  22. 4
  23. 2.90
  24. 100
  25. 3.13
  26. 1000
  27. 3.14
  28. 0
  29. error
  30. -100
  31. error */
  1. #include <stdio.h>
  2. int main(void) {
  3. int n, j;
  4. float x, y = 0;
  5. scanf("%d", &n); //输入一个n,这里的i就代表题目中所说的n
  6. if (n <= 0) { //判断n输入不合法的情况
  7. printf("error");
  8. } else {
  9. for (j = 1; j < n + 1; j++) //开始循环,从第1项直到第n项
  10. {
  11. x = 2 * j; //可以推出每一项的分母都是增加2
  12. if (j % 2 == 0) //第偶数项 符号为-
  13. {
  14. y -= 1 / (x - 1); //因为x*2是第j个非负偶数,x-1就是第j个非负奇数
  15. } else //第偶数项 符号为+
  16. {
  17. y += 1 / (x - 1);
  18. }
  19. }
  20. printf("%.2f", 4 * y); //因为π/4等于y因此 π=4*y;%.2f中 .2表示保留两位小数
  21. //f表示以浮点数输出
  22. }
  23. }

练习 2: 简单计算器

题目描述:
按照 操作数1 运算符op 操作数2 的格式输入数据进行运算。指定的运算符 op 为 + - * /(加减乘除)然后按照:操作数1 运算符 op 操作数2 = 计算结果 的形式输出,其中操作数和计算结果取 2 位小数,每个数据间隔 1 个空格,最后输出回车。然后输入 y,则继续按照 操作数1 运算符op 操作数2 的格式输入数据,并输出计算结果,否则输入 n,则结束循环。
其他错误输入,输出 error(error 后面有回车)。再根据输入 y 或者 n 决定是否循环计算。y 或者 Y 表示继续循环,非 y 也非 Y 的字符默认表示结束循环

思路:

  1. 输入:操作数1 运算符op 操作数2(实际测试时,中间不要出现多余的空格)
  2. 判断操作符或者操作数是否不合法。若不合法输出 error,跳到第 4 步。否则进行第 3 步;
  3. 输出操作数 运算符 操作数 = 计算结果;
  4. 输入 y 或者 n,入不为 y 或 n,则一直循环第 4 步。若输入 y,回到第 1 步,若输入 n,结束程序。

测试用例

  1. #include <stdio.h>
  2. int main() {
  3. double a, b;
  4. char op;
  5. char s[1000];
  6. do {
  7. scanf("%lf%c%lf", &a, &op, &b);
  8. getchar();
  9. switch (op) {
  10. case '+':
  11. printf("%.2lf%c%.2lf=%.2lf\n", a, op, b, a + b);
  12. break;
  13. case '-':
  14. printf("%.2lf%c%.2lf=%.2lf\n", a, op, b, a - b);
  15. break;
  16. case '*':
  17. if (a == 0 || b == 0) {
  18. printf("%.2lf%c%.2lf=0.00\n", a, op, b);
  19. break;
  20. }
  21. printf("%.2lf%c%.2lf=%.2lf\n", a, op, b, a * b);
  22. break;
  23. case '/':
  24. if (b == 0) {
  25. printf("error\n");
  26. break;
  27. }
  28. printf("%.2lf%c%.2lf=%.2lf\n", a, op, b, a / b);
  29. break;
  30. default:
  31. printf("error\n");
  32. break;
  33. }
  34. scanf("%s", s);
  35. // printf("%c\n",s[0]);
  36. while (s[0] != 'y' && s[0] != 'n') {
  37. printf("error\n");
  38. scanf("%s", s);
  39. }
  40. } while (s[0] == 'y');
  41. return 0;
  42. }

内存循环:while (s[0] != 'y' && s[0] != 'n') { 提示用户输入错误 }
如果输入的内容并非 y、n 那么程序将卡死在这个循环中,不断打印 error,提示用户输入错误。直到用户输入的字符是 y、n 其中一个,才会退出死循环。

外层循环:do { 继续计算 } while(s[0] == 'y')

  • 如果退出死循环时输入的是 y,那么符合外层循环的循环条件,继续计算器的工作
  • 如果退出死循环时输入的是 n,那么不满足外层循环的循环条件,推出外层循环,这相当于结束计算器的工作,最终 main 函数结束,程序执行完毕

3.8 华为云平台使用与不同架构运行时间对比

有一道编程题,题目描述已经记录下来了,现在学不动了,这部分就先跳过了…… 后续补上

3.8.1 华为云平台使用与不同架构运行时间对比.mp4 (93.75MB) 00:00:58 | 计算 e 的 x 次方

image.png