4.3 编程实战

求1到100之间的所有素数

4.3.1 求1到100之间的所有素数.mp4 (54.96MB) 访问链接

冒泡排序

4.3.2 冒泡排序.mp4 (172.54MB) 访问链接

折半查找

4.3.3 折半查找.mp4 (149.95MB) 访问链接

兔子问题

4.3.4 兔子问题.mp4 (91.95MB) 访问链接

时钟

4.3.5 时钟.mp4 (160.23MB) 访问链接

数据分析

4.3.6 数据分析.mp4 (63.86MB) 访问链接

notes

常规法子 | 求 1 到 100 之间的所有素数

  1. #include <stdio.h>
  2. int main() {
  3. int i, j, is_prime;
  4. for (i = 2; i <= 100; i++) {
  5. is_prime = 1; // 假设 i 是素数
  6. for (j = 2; j < i; j++) {
  7. if (i % j == 0) { // 如果 i 能被 j 整除,则 i 不是素数
  8. is_prime = 0;
  9. break;
  10. }
  11. }
  12. if (is_prime) { // 如果 i 是素数,则打印出来
  13. printf("%d ", i);
  14. }
  15. }
  16. printf("\n");
  17. return 0;
  18. }
  19. /* 运行结果:
  20. 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
  21. */

埃氏筛法(Sieve of Eratosthenes) | 4.3.1 视频结尾提到的优化解法 | 求 1 到 100 之间的所有素数

埃氏筛法(Sieve of Eratosthenes)是一种用于求解素数的算法,它的基本思想是:从 2 开始,将每个素数的倍数标记为合数,直到遍历到给定的范围为止

  1. #include <stdio.h>
  2. #define N 100
  3. int main() {
  4. int i, j, is_prime[N + 1];
  5. // 初始化数组
  6. for (i = 2; i <= N; i++) {
  7. is_prime[i] = 1; // 假设所有数都是素数
  8. }
  9. // 标记合数
  10. for (i = 2; i <= N; i++) {
  11. if (is_prime[i]) { // 如果 i 是素数
  12. for (j = i * 2; j <= N; j += i) {
  13. is_prime[j] = 0; // 标记 i 的倍数为合数
  14. }
  15. }
  16. }
  17. // 输出素数
  18. for (i = 2; i <= N; i++) {
  19. if (is_prime[i]) {
  20. printf("%d ", i);
  21. }
  22. }
  23. printf("\n");
  24. return 0;
  25. }
  26. /* 运行结果:
  27. 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
  28. */

解释:

  1. #define N 100 在上面的示例中,我们首先定义了一个常量 N,表示要求解的范围为从 2 到 N 的所有素数。
  2. int is_prime[N + 1]; 然后,我们定义了一个名为 is_prime 的 布尔数组,用于记录每个数是否为素数。
  3. is_prime[i] = 1; 在初始化数组时,我们将所有的数都假设为素数。
  4. is_prime[j] = 0; 接着,我们从 2 开始遍历数组,如果遇到素数,则将它的倍数标记为合数,这里我们可以直接从 i 的倍数开始,因为它们已经被之前的素数标记为合数了。最后,我们输出未被标记为合数的数即为素数。

注意:

  • 这种方法虽然效率比较高,但是需要额外的空间来记录每个数是否为素数
  • 如果要求解比较大的范围的素数,可能需要使用更高效的算法或者优化算法的实现

算法思想的核心:找出 1-N 中所有非素数,剩下的就都是素数了。

  1. #include <math.h>
  2. #include <stdio.h>
  3. int main() {
  4. const int n = 100;
  5. int isPrim[n] = {0}; // 默认所有数是非素数
  6. int i, j;
  7. for (i = 2; i < sqrt(n); ++i) {
  8. if (isPrim[i] == 0)
  9. for (j = 2 * i; j <= n; j += i)
  10. isPrim[j] = 1; // 将每个素数的倍数标记为合数
  11. }
  12. for (i = 2; i <= n; ++i)
  13. if (isPrim[i] == 0) {
  14. printf("%d ", i);
  15. }
  16. return 0;
  17. }
  18. /* 运行结果:
  19. 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
  20. */

欧拉筛法(Sieve of Euler) | 求 1 到 100 之间的所有素数

欧拉筛法的基本思想是:对于每个合数(即非素数),只会在最小质因子筛选时被筛掉,在后续的枚举过程中不会再次被筛选。因此,在筛质数时,只需要考虑每个数的最小质因子,即第一个能整除它的质数。

欧拉筛法的基本步骤:

  1. 初始化:先将所有数标记为素数(或者不标记任何数),并且保留每个数的最小质因数,和一个素数列表 primes。
  2. 筛除合数:从 2 开始逐个枚举每个数,如果这个数是素数,则将其添加到素数列表 primes 中,并标记其倍数为合数,并记录这个合数的最小质因子,如果这个数的最小质因子就是它本身,则跳过。
  3. 输出结果:最终得到的素数列表即为筛选出的所有质数。
  1. #include <stdio.h>
  2. #include <stdbool.h>
  3. #define MAX_N 100000
  4. int primes[MAX_N]; // 存放素数的数组
  5. bool is_prime[MAX_N]; // 标志数组:判断某个数是否为素数
  6. int num_primes = 0; // 当前素数的个数
  7. // 欧拉筛法
  8. void euler_sieve(int n) {
  9. is_prime[0] = is_prime[1] = false; // 标记 0 和 1 不为素数
  10. for (int i = 2; i <= n; i++) {
  11. if (is_prime[i] == true) {
  12. primes[num_primes++] = i;
  13. }
  14. for (int j = 0; j < num_primes && i * primes[j] <= n; j++) {
  15. is_prime[i * primes[j]] = false;
  16. if (i % primes[j] == 0) break; // 如果 i 除以当前素数得到余数为 0,说明 i 的最小质因子为 primes[j],因此跳出循环
  17. }
  18. }
  19. }
  20. int main() {
  21. int n = 100;
  22. for (int i = 0; i <= n; i++) {
  23. is_prime[i] = true; // 默认所有数一开始都是素数
  24. }
  25. euler_sieve(n);
  26. for (int i = 0; i < num_primes; i++) {
  27. printf("%d ", primes[i]);
  28. }
  29. return 0;
  30. }

冒泡排序

冒泡排序是一种简单、直观的排序算法,其基本思想是 将要排序的元素按照大小关系进行比较并交换,每一次遍历可以找到待排序序列中最大或最小的元素,重复这个过程直到整个序列有序

冒泡排序的时间复杂度是O(n^2),它虽然简单易懂,但对于大规模数据的排序效率较低,不适合大规模数据的排序。

下面是冒泡排序的伪代码:

  1. 初始化:设待排序元素的数列为 a[1], a[2], ..., a[N]
  2. 外循环:从第一个元素开始,依次比较相邻两个元素的大小,如果前一个元素比后一个元素大(或小,根据实际需求而定),则交换这两个元素的位置。依次遍历整个数列,最终得到最大(或最小)的元素,放置在数列的末尾(或开头)。
  3. 内循环:继续从第一个元素开始,重复第2步,但这次不再包括最后一个元素。重复这个过程,直到整个数列有序。
  4. 输出结果:最终得到的有序数列即为排序结果。

冒泡排序流程示意图:
image.png

  1. #include <stdio.h>
  2. int main() {
  3. int a[6] = {16, 8, 4, 32, 5, 9};
  4. int temp;
  5. for (int i = 1; i < 6; ++i)
  6. for (int j = 0; j < 6 - i; j++)
  7. if (a[j] > a[j + 1]) {
  8. temp = a[j];
  9. a[j] = a[j + 1];
  10. a[j + 1] = temp;
  11. }
  12. for (int i = 0; i <= 5; i++)
  13. printf("%d ", a[i]);
  14. return 0;
  15. }
  16. /* 运行结果:
  17. 4 5 8 9 16 32
  18. */
  1. #include <stdio.h>
  2. void bubble_sort(int a[], int n) {
  3. int i, j, temp;
  4. for (i = 0; i < n - 1; i++) { // 外循环
  5. for (j = 0; j < n - 1 - i; j++) { // 内循环
  6. if (a[j] > a[j + 1]) { // 如果前一个值比后一个值大,则交换它们的位置
  7. temp = a[j];
  8. a[j] = a[j + 1];
  9. a[j + 1] = temp;
  10. }
  11. }
  12. }
  13. }
  14. int main() {
  15. int a[5] = {16, 8, 4, 32, 5};
  16. int i;
  17. bubble_sort(a, 5);
  18. for (i = 0; i < 5; i++) {
  19. printf("%d ", a[i]);
  20. }
  21. return 0;
  22. }
  23. /* 运行结果:
  24. 4 5 8 16 32
  25. */

i < n - 1 外循环

  • 在外循环中,我们依次遍历整个数列,每次找到一个最大的元素,并将其放到数组的后面;
  • 外层循环每跑一趟,确定一个最值,一共要跑 n-1 趟
    • 以 5 个数 {16, 8, 4, 32, 5} 为例,一共需要跑 4 趟,即可确保数组是有序的
    • i == 0 第 1 趟确定第 1 大的值 32,并将其丢到数组的倒数第 1 个位置
    • i == 1 第 2 趟确定第 2 大的值 16,并将其丢到数组的倒数第 2 个位置
    • i == 2 第 3 趟确定第 3 大的值 8,并将其丢到数组的倒数第 3 个位置
    • i == 3 第 4 趟确定第 4 大的值 5,并将其丢到数组的倒数第 4 个位置

j < n - 1 - i 内循环

  • 在内循环中,我们对相邻的元素进行比较,如果前一个元素比后一个元素大,则交换它们的位置,直到找到最大的元素;
  • 内层循环,每跑一趟,比较一对数据,根据此时已经明确的最值数量,决定需要跑多少趟
    • 以 5 个数 {16, 8, 4, 32, 5} 为例
    • i == 0 时,明确的最值数量为 0,此时需要把前面的 5 个数都比较一遍,共比较 4 次
    • i == 1 时,明确的最值数量为 1,此时需要把前面的 4 个数都比较一遍,共比较 3 次
    • i == 2 时,明确的最值数量为 2,此时需要把前面的 3 个数都比较一遍,共比较 2 次
    • i == 3 时,明确的最值数量为 3,此时需要把前面的 2 个数都比较一遍,共比较 1 次
  1. #include <stdio.h>
  2. void bubble_sort(int a[], int n) {
  3. int i, j, temp;
  4. int flag = 1; // 标志变量 flag,初始值为 true
  5. for (i = 0; i < n - 1 && flag == 1; i++) { // 外循环
  6. flag = 0; // 每次循环开始时,将 flag 设为 false
  7. for (j = 0; j < n - 1 - i; j++) { //内循环
  8. if (a[j] > a[j + 1]) { // 如果前一个值比后一个值大,则交换它们的位置
  9. temp = a[j];
  10. a[j] = a[j + 1];
  11. a[j + 1] = temp;
  12. flag = 1; // 如果进行了交换操作,将 flag 设为 true
  13. }
  14. }
  15. }
  16. }
  17. int main() {
  18. int a[5] = {16, 8, 4, 32, 5};
  19. int i;
  20. bubble_sort(a, 5);
  21. for (i = 0; i < 5; i++) {
  22. printf("%d ", a[i]);
  23. }
  24. return 0;
  25. }
  26. /* 运行结果:
  27. 4 5 8 16 32
  28. */

核心:加标记,判断后续的交换是否有必要进行,以减少交换次数。

如果某一次的内层循环跑完,都没有发生两数交换的情况,那么意味着:

  • 该数组已经处于有序状态,此时就没有必要再进行后续的比较了。
  • flag = 1; 这条语句将不会被执行,flag 的值将保持为 0,外层循环条件不满足,直接结束后续的所有流程。

折半查找(二分查找)

  • 折半查找(Binary Search),也被称为 二分查找,是一种常用的查找算法。
  • 核心思想:将待查找的元素与已知的中间元素进行比较,从而逐步缩小查找范围,最终找到待查元素所在的位置
  • 前提:待查找的序列必须有序

这里我们假设序列是升序排列的,下面是折半查找具体的实现步骤:

  1. 初始化:设待查找元素为 x,数组名为 a,开始时,设置左右两个游标 left 和 right,分别指向数组左右两端,即 left = 0,right = n - 1。
  2. 折半查找:在每一次查找时:
    1. 计算中间元素的位置 mid
      1. mid = (left + right) / 2
    2. 将 x 与 a[mid] 进行比较
      1. 如果 x 等于 a[mid],则找到了要查找的元素
      2. 如果 x 大于 a[mid],则表明要查找的元素在 [mid + 1, right] 的区间内,更新 left = mid + 1
      3. 如果 x 小于 a[mid],则表明要查找的元素在 [left, mid - 1] 的区间内,更新 right = mid - 1
  3. 结束查找:当 left > right 时,表明数组中不存在 x,查找结束。
  1. #include <stdio.h>
  2. int main() {
  3. int data[10] = {23, 37, 45, 54, 65, 78, 82, 87, 89, 94};
  4. int target = 88;
  5. int len = sizeof(data) / sizeof(data[0]), low = 1, high = len, mid;
  6. mid = (low + high) / 2;
  7. while (low <= high) {
  8. if (data[mid] == target) {
  9. printf("数组中 %d 的下标是 %d\n", target, mid);
  10. return 0;
  11. } else if (data[mid] > target) {
  12. high = mid - 1;
  13. } else {
  14. low = mid + 1;
  15. }
  16. mid = (low + high) / 2;
  17. }
  18. printf("数组中不存在 %d\n", target);
  19. return 0;
  20. }
  21. /* 运行结果:
  22. 数组中不存在 88
  23. */
  24. #include <stdio.h>
  25. int main() {
  26. int data[10] = {23, 37, 45, 54, 65, 78, 82, 87, 89, 94};
  27. int target = 78;
  28. int len = sizeof(data) / sizeof(data[0]), low = 1, high = len, mid;
  29. mid = (low + high) / 2;
  30. while (low <= high) {
  31. if (data[mid] == target) {
  32. printf("数组中 %d 的下标是 %d\n", target, mid);
  33. return 0;
  34. } else if (data[mid] > target) {
  35. high = mid - 1;
  36. } else {
  37. low = mid + 1;
  38. }
  39. mid = (low + high) / 2;
  40. }
  41. printf("数组中不存在 %d\n", target);
  42. return 0;
  43. }
  44. /* 运行结果:
  45. 数组中 78 的下标是 5
  46. */
  1. #include <stdio.h>
  2. int binary_search(int a[], int n, int x) {
  3. int left = 0, right = n - 1; // 定义左右两个游标
  4. while (left <= right) { // 只要游标没有相撞,就不断查找
  5. int mid = (left + right) / 2; // 计算中间位置
  6. if (a[mid] == x) // 找到了
  7. return mid;
  8. else if (a[mid] > x) // 中间值 > 目标元素
  9. right = mid - 1; // 说明目标元素位于 左 侧的区间中 [ left, mid - 1 ]
  10. else // 中间值 <= 目标元素
  11. left = mid + 1; // 说明目标元素位于 右 侧的区间中 [ mid + 1, right ]
  12. }
  13. return -1;
  14. }
  15. int main() {
  16. int a[8] = {1, 3, 4, 6, 8, 9, 11, 13};
  17. int n = sizeof(a) / sizeof(a[0]); // 计算数组长度
  18. int x = 6; // 查找的目标成员
  19. int index = binary_search(a, n, x);
  20. if (index == -1)
  21. printf("数组中不存在%d\n", x);
  22. else
  23. printf("数组中 %d 的下标是 %d\n", x, index);
  24. return 0;
  25. }
  26. /* 运行结果:
  27. 数组中 6 的下标是 3
  28. */

求 Fibonacci 数

问:什么是 Fibonacci 数?

  • 第 1 个数为 1
  • 第 2 个数为 1
    • 头两个数为 1
  • 第 3 个数为 2
    • 从第三个数开始,每个数等于前两数之和
  • 第 4 个数为 3
  • 第 5 个数为 5
  • 第 6 个数为 8
  • 第 7 个数为 13
  • 第 8 个数为 21
  • 第 9 个数为 34
  • ……
  1. #include <stdio.h>
  2. int main() {
  3. const int n = 10;
  4. int f[n], i;
  5. f[1] = 1;
  6. f[2] = 1;
  7. printf("%4d%4d", f[1], f[2]);
  8. for (i = 3; i < n; i++) {
  9. f[i] = f[i - 1] + f[i - 2];
  10. printf("%4d", f[i]);
  11. }
  12. return 0;
  13. }
  14. /* 运行结果:
  15. 1 1 2 3 5 8 13 21 34
  16. */

课件中的这种做法,使用到了数组类型,相当于将每一项的值都给存下来了,随着值的不断增多,数组中装的成员也将越来越多,需要的空间也会越来越大。

  1. #include <stdio.h>
  2. int Fibonacci(int n) {
  3. if (n <= 0)
  4. return -1; // 输入的数据不合法
  5. if (n == 1 || n == 2)
  6. return 1; // 当 n 等于 1 或 2 时,斐波那契数为 1
  7. return Fibonacci(n - 1) + Fibonacci(n - 2); // 定义递归式,并进行递推
  8. }
  9. int main() {
  10. int n = 10;
  11. printf("f(%d) = %d\n", 1, Fibonacci(1));
  12. printf("f(%d) = %d\n", 2, Fibonacci(2));
  13. for (int i = 3; i < n; i++) {
  14. int result = Fibonacci(i);
  15. if (result == -1)
  16. printf("输入数据不合法!");
  17. else
  18. printf("f(%d) = %d\n", i, result);
  19. }
  20. return 0;
  21. }
  22. /* 运行结果:
  23. f(1) = 1
  24. f(2) = 1
  25. f(3) = 2
  26. f(4) = 3
  27. f(5) = 5
  28. f(6) = 8
  29. f(7) = 13
  30. f(8) = 21
  31. f(9) = 34
  32. */

在以上代码中,我们定义了函数 Fibonacci 进行递推求解斐波那契数列的第 n 项值。

  • 当输入的 n 小于或等于 0 时,返回 -1,代表输入数据不合法;当 n 等于 1 或 2 时,返回1;
  • 当 n 大于等于 3 时,通过递归计算 f(n-1) 和 f(n-2) 的和,并返回结果;
  1. #include <stdio.h>
  2. int Fibonacci(int n) {
  3. if (n <= 0)
  4. return -1; // 输入的数据不合法
  5. if (n == 1 || n == 2)
  6. return 1; // 当 n 等于 1 或 2 时,斐波那契数为 1
  7. int f1 = 1, f2 = 1, f3 = 0;
  8. for (int i = 3; i <= n; i++) {
  9. f3 = f1 + f2; // 定义递推式
  10. f1 = f2;
  11. f2 = f3; // 进行向后滚动
  12. }
  13. return f3;
  14. }
  15. int main() {
  16. int n = 10;
  17. printf("f(%d) = %d\n", 1, Fibonacci(1));
  18. printf("f(%d) = %d\n", 2, Fibonacci(2));
  19. for (int i = 3; i < n; i++) {
  20. int result = Fibonacci(i);
  21. if (result == -1)
  22. printf("输入数据不合法!");
  23. else
  24. printf("f(%d) = %d\n", i, result);
  25. }
  26. return 0;
  27. }
  28. /* 运行结果:
  29. f(1) = 1
  30. f(2) = 1
  31. f(3) = 2
  32. f(4) = 3
  33. f(5) = 5
  34. f(6) = 8
  35. f(7) = 13
  36. f(8) = 21
  37. f(9) = 34
  38. */

f1、f2 这两个变量中,始终存放着当前位的前两位的值。假设当前要计算的是 f(x),那么 f1 就是 f(x - 2)f2 就是 f(x - 1)

清屏

清空屏幕并将光标移动到屏幕左上角

  • system("CLS")
    • 是 C 语言中的一个系统函数调用
    • 常用来清空屏幕从而让控制台窗口中的输出信息更加清晰易读
    • 该函数在 Windows 操作系统中可用
  • system("clear") Linux 或 Mac 系统中应使用 system("clear") 函数调用来实现同样的功能
  • <stdlib.h> 头文件中包含了 C 语言标准库中的 system 函数的声明
  1. #include <stdio.h>
  2. #include <stdlib.h> // 包含 system 函数的声明
  3. int main()
  4. {
  5. printf("输出一些信息......\n");
  6. // system("CLS"); // 清空控制台屏幕
  7. system("clear");
  8. printf("屏幕清空之后,输出一些其它信息......\n");
  9. return 0;
  10. }
  11. /* 运行结果:
  12. 屏幕清空之后,输出一些其它信息...... */

时钟题目描述

需求:定义一个时钟结构体类型,然后编程实现将时钟模拟显示在屏幕上
扩展:获取本机当前时间,修改程序,实现实时显示本机当前时间

休眠

Sleepsleep 是两个不同的函数,用于在不同的操作系统中进行程序的休眠。

  • Sleep
    • Sleep 函数是 Windows 操作系统中的一个函数,定义在 Windows API 中
    • 用于使当前线程进入指定的时间(以 毫秒 为单位)的休眠状态
    • 阻塞当前线程 限制系统的 CPU 资源使用
    • 在使用时需要包含头文件 <windows.h>
  • sleep
    • sleep 函数是 Linux 或 Unix 操作系统中的一个函数
    • 可以将当前线程阻塞指定的 秒数
    • 在使用时需要包含头文件 <unistd.h>

注意误差问题

  • Sleep 函数的精度会受到系统负载的影响,因此在 Windows 操作系统中,使用 Sleep 函数进行短时间的休眠时可能会出现误差。
  • sleep 函数则并不受到这种影响。同时,Linux 操作系统也提供了 usleep 函数,它的单位是微秒,同样可以用于实现更准确的程序休眠。
  1. #include <stdio.h>
  2. #include <unistd.h>
  3. int main() {
  4. printf("开始休眠 5s\n");
  5. sleep(5); // 休眠 5 秒
  6. printf("休眠结束!\n"); // => 这条语句在 5 秒后才会打印
  7. return 0;
  8. }
  9. /* 运行结果:
  10. 开始休眠 5s
  11. 休眠结束!
  12. */

缓冲区

缓冲区(Buffer)是 计算机系统中用于存储数据的一段内存空间,用来 临时存放输入/输出数据的中转站缓解输入/输出设备和计算机之间速度不匹配的问题,提高数据传输的效率

在计算机系统中,输入/输出设备的速度通常比内存和处理器的速度慢很多,因此当程序需要进行输入/输出操作时,为了提高效率,会先将数据存储到缓冲区中,然后再由系统将缓冲区中的数据传输到输入/输出设备中。这样,输入/输出设备就不需要等待计算机的处理速度,而是可以按照缓冲区中数据的速度进行数据传输,从而提高了系统的性能。

缓冲区在计算机系统中广泛应用,比如在 👇🏻 下面这些场景中都会使用到缓冲区

  • 文件读写
  • 网络通信
  • 视频音频播放
  • ……

缓冲区的大小和使用方式都会对系统的性能和效率产生影响,因此在设计和优化系统时,需要考虑缓冲区的大小和使用方式,以达到最优的效果。

刷新缓冲区,将缓冲区中的数据输出到屏幕上

fflush(stdout) 是 C 语言标准库中的一个函数,它的作用是刷新输出缓冲区,并将缓冲区中的数据立即输出到标准输出设备,比如屏幕、终端等。

在 C 语言程序中,输出到标准输出设备的数据并不是立即显示在屏幕上,而是先存放在输出缓冲区中,当缓冲区满了、程序结束或者遇到 fflush 函数时,才将缓冲区中的数据输出到标准输出设备。如果程序没有输出足够的数据,缓冲区就不会满,输出数据也不会被显示。这时候,使用 fflush(stdout) 函数可以立即刷新输出缓冲区,将缓冲区中的数据输出到屏幕上。

在编写需要实时显示的程序时,比如显示进度条、实时输出时间等,使用 fflush(stdout) 函数可以确保输出数据能够及时显示在屏幕上,而不是等待缓冲区满或者程序结束时才输出。

  1. #include <stdio.h>
  2. #include <unistd.h>
  3. int main() {
  4. int n = 5, i = 0;
  5. printf("开始休眠 5 s\n");
  6. while (++i < n) {
  7. printf("已休眠 %d s\r", i);
  8. fflush(stdout);
  9. sleep(1);
  10. }
  11. printf("已休眠 5 s,休眠结束\n");
  12. }

fflush(stdout);
如果没有这条语句,那么我们要输出的内容,将会堆积在缓冲区中,当循环结束时,才会一并输出。具体效果见下图 👇🏻

0.gif
0.gif

time

  • time 函数声明:time_t time(time_t* timer);
  • 参数
    • 如果 timer 不为 NULL,则表示需要 将当前时间的秒数存储到 timer 指向的变量中,同时函数也会返回当前时间的秒数
    • 如果 timer 为 NULL,则表示不需要返回时间值,函数直接返回当前时间的秒数
  • 返回值
    • time 函数返回值为 time_t 类型的数据,即整数型数据,表示当前时间距离格林威治时间 1970 年 1 月 1 日 0 时 0 分 0 秒的时间戳(time stamp)。
    • 例如:函数返回值为 1679376100267 时,表示当前时间为 2023/3/21 13:21:40
    • time_t 是 C 语言标准库中 用于表示时间值的类型
    • 打印 time_t 类型的数据可以使用 %ld 作为格式控制字符
  • time 是 C 语言标准库中的一个函数,它的作用是 获取当前系统时间的秒数
  • 这个时间戳也被称为“UNIX时间戳”

应用场景:

  • 计时器
  • 日志记录
  • 时间戳生成
  • 事件处理
  • ……

获取当前时间的方法不止这一种,还有其他的一些函数和方法,比如:

  • gettimeofday
  • clock
  • localtime
  • ……
  1. #include <stdio.h>
  2. #include <time.h>
  3. int main() {
  4. time_t current_time = time(NULL);
  5. printf("当前时间戳为:%ld\n", current_time);
  6. return 0;
  7. }
  8. /* 运行结果:
  9. 当前时间戳为:1679552034
  10. */

localtime

  • 函数声明:struct tm *localtime(const time_t *timer);
  • 参数
    • timer 是指向以秒为单位的 time_t 时间值的 指针
  • 返回值
    • 一个指向 struct tm 结构体类型的指针
    • struct tm 结构体类型包含有关时间(年、月、日等)的信息
  • localtime 是 C 语言标准库中用于处理时间的函数之一
  • 作用是将 time_t 类型的时间值转换为当地时间(local time)

struct tm 结构体定义如下:

  1. struct tm {
  2. int tm_sec; /* 秒数(0~59)*/
  3. int tm_min; /* 分钟数(0~59)*/
  4. int tm_hour; /* 小时数(0~23)*/
  5. int tm_mday; /* 日数(1~31)*/
  6. int tm_mon; /* 月数(0~11)*/
  7. int tm_year; /* 年数(自 1900 年起)*/
  8. int tm_wday; /* 一周中的第几天(0~6,0 表示星期天)*/
  9. int tm_yday; /* 一年中的第几天(0~365)*/
  10. int tm_isdst; /* 夏令时标识符(-1 表示无夏令时信息,0 表示无夏令时,正数表示有夏令时)*/
  11. };

localtime 函数的返回值为一个指向 struct tm 结构体的指针,指针指向的结构体包含有关时间的各个部分的信息。

本章要实现的“时钟”demo,需要用到的就是 struct tm 结构体身上的:

  • tm_hour
  • tm_min
  • tm_sec
  1. #include <stdio.h>
  2. #include <time.h>
  3. int main() {
  4. // 获取当前时间
  5. time_t timestamp = time(NULL);
  6. struct tm* tm_local = localtime(&timestamp);
  7. // 输出时分秒
  8. printf("当前时间: %02d:%02d:%02d\n",
  9. tm_local->tm_hour,
  10. tm_local->tm_min,
  11. tm_local->tm_sec);
  12. return 0;
  13. }
  14. /*
  15. 当前时间: 06:17:55
  16. */

补充:有关指针的更多内容,会在第 5 章详细介绍,现在只要知道我们可以通过上述这种方式,拿到当下时间数据即可。

时钟

  1. #include <stdio.h>
  2. #include <windows.h>
  3. struct clock {
  4. int hour;
  5. int minute;
  6. int second;
  7. };
  8. typedef struct clock CLOCK;
  9. int main() {
  10. CLOCK t = {0, 0, 0};
  11. int n = 100, i = 0;
  12. char c;
  13. while (++i < n) {
  14. t.second++;
  15. if (t.second >= 60) {
  16. t.second = 0;
  17. t.minute++;
  18. }
  19. if (t.minute >= 60) {
  20. t.minute = 0;
  21. t.hour++;
  22. }
  23. if (t.hour >= 24)
  24. t.hour = 0;
  25. printf("%2d:%2d:%2d\r", t.hour, t.minute, t.second);
  26. Sleep(1000);
  27. }
  28. }

这段程序丢到 windows 设备上可以正常跑,Linux 上不存在 windows.hSleep,所以在 Linux 上是没法运行的。

  1. #include <cstdlib>
  2. #include <stdio.h>
  3. #include <unistd.h>
  4. struct clock {
  5. int hour;
  6. int minute;
  7. int second;
  8. };
  9. typedef struct clock CLOCK;
  10. int main() {
  11. CLOCK t = {0, 0, 0};
  12. int n = 100, i = 0;
  13. char c;
  14. while (++i < n) {
  15. t.second++;
  16. if (t.second >= 60) {
  17. t.second = 0;
  18. t.minute++;
  19. }
  20. if (t.minute >= 60) {
  21. t.minute = 0;
  22. t.hour++;
  23. }
  24. if (t.hour >= 24)
  25. t.hour = 0;
  26. printf("%02d:%02d:%02d", t.hour, t.minute, t.second);
  27. system("clear");
  28. // printf("%02d:%02d:%02d\r", t.hour, t.minute, t.second);
  29. fflush(stdout); // 刷新缓冲区
  30. sleep(1);
  31. }
  32. }
  33. /* 运行结果:
  34. 00:00:17
  35. */

实时更新效果:两种写法,效果是非常类似的

  • 写法 1:\r
    • printf("%02d:%02d:%02d\r", t.hour, t.minute, t.second);
    • 使用 \r 控制字符将光标移动到行首,以便实现实时更新的效果
  • 写法 2:system("clear");
    • printf("%02d:%02d:%02d", t.hour, t.minute, t.second);
    • 每次输出之前先清屏system("clear");,以便实现实时更新的效果

fflush(stdout);

  • 刷新缓冲区
  • 使用 fflush 函数刷新输出缓冲区,保证信息能够及时显示

扩展:实时显示系统当前时间

  1. #include <stdio.h>
  2. #include <time.h>
  3. #include <unistd.h>
  4. int main() {
  5. while (1) {
  6. time_t current_time = time(NULL); // 获取当前时间
  7. struct tm* tm = localtime(&current_time); // 将当前时间转换为本地时间
  8. int hour = tm->tm_hour; // 获取小时数
  9. int minute = tm->tm_min; // 获取分钟数
  10. int second = tm->tm_sec; // 获取秒数
  11. printf("%02d:%02d:%02d\r", hour, minute, second); // 输出时分秒信息
  12. fflush(stdout); // 刷新缓冲区
  13. sleep(1); // 等待1秒
  14. }
  15. return 0;
  16. }
  17. /* 运行结果:
  18. 03:02:17
  19. */

由于 localtime 函数返回的是一个指向 struct tm 结构体类型的指针,因此需要使用 -> 运算符来访问其成员,如 local_time->tm_year 来访问年份信息。

课件源码

  1. #include <stdio.h>
  2. typedef struct student {
  3. char grade[5];
  4. char department[3];
  5. char major[3];
  6. char cclass[3];
  7. char number[4];
  8. } student;
  9. int main() {
  10. char number[14];
  11. student s;
  12. int i;
  13. scanf("%s", number);
  14. for (i = 0; i < 4; ++i)
  15. s.grade[i] = number[i];
  16. s.grade[i] = '\0';
  17. s.department[0] = number[i++];
  18. s.department[1] = number[i++];
  19. s.department[2] = '\0';
  20. s.major[0] = number[i++];
  21. s.major[1] = number[i++];
  22. s.major[2] = '\0';
  23. s.cclass[0] = number[i++];
  24. s.cclass[1] = number[i++];
  25. s.cclass[2] = '\0';
  26. s.number[0] = number[i++];
  27. s.number[1] = number[i++];
  28. s.number[2] = number[i++];
  29. s.number[3] = '\0';
  30. printf("学院:%s;专业:%s;年级:%s;班:%s;学号:%s\n", s.department, s.major,
  31. s.grade, s.cclass, s.number);
  32. }
  33. /* 运行结果:
  34. 2015060204030
  35. 学院:06;专业:02;年级:2015;班:04;学号:030
  36. */

使用结构体存储学生信息

  1. #include <stdio.h>
  2. typedef struct student {
  3. int grade;
  4. char department[32];
  5. char major[32];
  6. char cclass[32];
  7. char number[32];
  8. } student;
  9. int main() {
  10. student s = {
  11. .grade = 2022,
  12. .department = "计算机科学与技术",
  13. .major = "软件工程",
  14. .cclass = "1班",
  15. .number = "20220001",
  16. };
  17. printf("年级:%d\n", s.grade);
  18. printf("学院:%s\n", s.department);
  19. printf("专业:%s\n", s.major);
  20. printf("班级:%s\n", s.cclass);
  21. printf("学号:%s\n", s.number);
  22. return 0;
  23. }
  24. /* 运行结果:
  25. 年级:2022
  26. 学院:计算机科学与技术
  27. 专业:软件工程
  28. 班级:1
  29. 学号:20220001 */
  1. #include <stdio.h>
  2. typedef struct student {
  3. char nickName[20];
  4. int grade;
  5. char department[32];
  6. char major[32];
  7. char cclass[32];
  8. char number[32];
  9. } student;
  10. int main() {
  11. student s = {0};
  12. printf("请输入学生信息:\n");
  13. printf("昵称:");
  14. scanf("%s", s.nickName);
  15. printf("年级:");
  16. scanf("%d", &s.grade);
  17. printf("学院:");
  18. scanf("%s", s.department);
  19. printf("专业:");
  20. scanf("%s", s.major);
  21. printf("班级:");
  22. scanf("%s", s.cclass);
  23. printf("学号:");
  24. scanf("%s", s.number);
  25. printf("\n学生信息如下:\n");
  26. printf("昵称:%s\n", s.nickName);
  27. printf("年级:%d\n", s.grade);
  28. printf("学院:%s\n", s.department);
  29. printf("专业:%s\n", s.major);
  30. printf("班级:%s\n", s.cclass);
  31. printf("学号:%s\n", s.number);
  32. return 0;
  33. }
  34. /* 运行结果:
  35. 请输入学生信息:
  36. 昵称:Tdahuyou
  37. 年级:2018
  38. 学院:信息技术学院
  39. 专业:物联网工程
  40. 班级:B18-1
  41. 学号:1820573
  42. 学生信息如下:
  43. 昵称:Tdahuyou
  44. 年级:2018
  45. 学院:信息技术学院
  46. 专业:物联网工程
  47. 班级:B18-1
  48. 学号:1820573
  49. */

4.4 华为 CloudIDE

删除重复字符

4.4.1 华为CloudIDE-码图37-删除重复字符.mp4 (36.9MB) 访问链接00:00:32 | 题目要求

image.png

00:01:53 | 输入输出

image.png

第四章实验 | 成绩管理系统

4.4.2 成绩管理系统.mp4 (18.68MB) 访问链接

notes

练习 1:删除字符串中连续的重复字符

实现删除字符串中连续的重复字符(除字母和数字)。

描述:
输入为字符串,将字符串中连续重复的,不是字母且不是数字的字符删去,然后输出处理后的字符串。

要求:
输入字符串最长 50 个字符,之后截断,只输出处理后的字符串。

算法要点:

  1. 先用字符数组存储字符串
  2. 逐个判断字符是否为字母或者数字
  3. 使用两个下标指针移动赋值操作 | 输入 | 输出 | | —- | —- | | 11+++2==13 | 11+2=3 | | 212(){}abc3212 | 212(){}abc3212 | | 32---1===!==31 | 32-1=!=31 |
  1. #include <ctype.h>
  2. #include <stdbool.h>
  3. #include <stdio.h>
  4. #include <string.h>
  5. // 判断字符是否为字母或数字
  6. bool is_letter_or_digit(char c) { return isalpha(c) || isdigit(c); }
  7. int main() {
  8. char input[51];
  9. // 从输入读取最多 50 个字符的字符串
  10. fgets(input, 51, stdin);
  11. int length = strlen(input);
  12. // 去除换行符,如果存在
  13. if (input[length - 1] == '\n') {
  14. input[length - 1] = '\0';
  15. length--;
  16. }
  17. char result[51]; // 存放删除连续字符后的字符串内容
  18. int result_index = 0;
  19. // 遍历输入字符串
  20. for (int i = 0; i < length; ++i) {
  21. // 如果是字母或数字,直接添加到结果字符串
  22. if (is_letter_or_digit(input[i])) {
  23. result[result_index++] = input[i];
  24. } else {
  25. // 如果不是字母或数字,将字符添加到结果字符串,然后跳过连续的相同字符
  26. result[result_index++] = input[i];
  27. while (i < length - 1 && !is_letter_or_digit(input[i + 1]) &&
  28. input[i] == input[i + 1]) {
  29. i++;
  30. }
  31. }
  32. }
  33. // 添加字符串结束符
  34. result[result_index] = '\0';
  35. // 输出处理后的字符串
  36. printf("%s\n", result);
  37. return 0;
  38. }
  39. /* 运行结果:
  40. 11+++2==13
  41. 11+2=13
  42. 212(){}abc3212
  43. 212(){}abc3212
  44. 32---1===!==31
  45. 32-1=!=31
  46. */
  1. #include <ctype.h>
  2. #include <stdbool.h>
  3. #include <stdio.h>
  4. #include <string.h>
  5. bool is_letter_or_digit(char c) { return isalpha(c) || isdigit(c); }
  6. int main() {
  7. char input[51];
  8. fgets(input, 51, stdin);
  9. int length = strlen(input);
  10. if (input[length - 1] == '\n') {
  11. input[length - 1] = '\0';
  12. length--;
  13. }
  14. char result[51];
  15. int result_index = 0;
  16. // 简化 for 循环的循环体写法
  17. for (int i = 0; i < length; ++i) {
  18. result[result_index++] = input[i];
  19. if (is_letter_or_digit(input[i])) continue;
  20. while (i < length - 1 && input[i] == input[i + 1]) i++;
  21. }
  22. result[result_index] = '\0';
  23. printf("%s\n", result);
  24. return 0;
  25. }
  1. // 使用到的库 👇🏻
  2. #include <stdio.h> // 标准输入输出库
  3. #include <string.h> // 定义了一个变量类型、一个宏和各种操作字符数组的函数
  4. #include <stdlib.h> // 定义了四个变量类型、一些宏和各种通用工具函数
  5. #include <ctype.h> // 提供了一些函数,可用于测试和映射字符
  6. int main() {
  7. char strs[10000]; // 定义字符数组
  8. scanf("%s", strs);
  9. int i = 0, j = 1;
  10. char res[51];
  11. strncpy(res, strs, 50); // 字符串拷贝函数
  12. int len = strlen(res);
  13. while (j < len) {
  14. if (isdigit(strs[j]) || isalpha(strs[j])) { // 判断是字母或者数字
  15. res[++i] = res[j++]; // 赋值之后下标后移
  16. } else if (res[i] == res[j]) { // 不是字母或者数字且与之前一个字符相同,直接跳过
  17. j++;
  18. } else {
  19. res[++i] = res[j++]; // 不是字母或者数字且与之前一个字符不相同,赋值之后后移
  20. }
  21. };
  22. res[++i] = '\0'; // 添加结束符
  23. printf("%s", res);
  24. return 0;
  25. }
  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <stdlib.h>
  4. #include <ctype.h>
  5. int main() {
  6. char strs[10000];
  7. scanf("%s", strs);
  8. int i = 0, j = 1;
  9. char res[51];
  10. strncpy(res, strs, 50);
  11. int len = strlen(res);
  12. while (j < len) {
  13. if (isdigit(strs[j]) || isalpha(strs[j])) res[++i] = res[j++];
  14. else if (res[i] == res[j]) j++;
  15. else res[++i] = res[j++];
  16. };
  17. res[++i] = '\0';
  18. printf("%s", res);
  19. return 0;
  20. }

strncpy(res, strs, 50);
在处理字符串时,直接使用了 strncpy 函数将原始输入字符串截断并复制到 res 数组中。
这样可以避免在循环中处理换行符。
版本 1 中,需要在循环外部对换行符进行处理。

练习 2:学生管理系统

编写学生管理系统,其中学生的信息有姓名(汉语拼音,最多 20 个字符,长度 21 的字符数组),性别(男/女,用 1 表示男,2 表示女,整数)、生日(19850101(年月日),整数)、身高(以 m 为单位,实数),还需要处理 C 语言、微积分两门课的成绩(整数)。
请编写程序实现功能:输入学生的人数和每个学生的信息;输出每门课程的总平均成绩、最高分和最低分,以及获得最高分的学生的信息。需要注意的是某门课程最高分的学生可能不只一人。

算法要点:

  1. 定义结构体
  2. 结构体的遍历

输入输出格式要求:身高输出时保留两位小数,请按照例子中进行输出,请勿输出其他字符

测试用例:

输入 输出
3 zhangsan 1 19910101 1.85 85 90 lisi 1 19920202 1.56 89 88 wangwu 2 19910303 1.6 89 90 ```markdown

C_average:87 C_max:89 lisi 1 19920202 1.56 89 88 wangwu 2 19910303 1.60 89 90 C_min:85 Calculus_average:89 Calculus_max:90 zhangsan 1 19910101 1.85 85 90 wangwu 2 19910303 1.60 89 90 Calculus_min:88

  1. |
  2. | `2 lisi 1 19920202 1.56 89 88 wangwu 2 19910303 1.6 89 90` | ```markdown
  3. C_average:89
  4. C_max:89
  5. lisi 1 19920202 1.56 89 88
  6. wangwu 2 19910303 1.60 89 90
  7. C_min:89
  8. Calculus_average:89
  9. Calculus_max:90
  10. wangwu 2 19910303 1.60 89 90
  11. Calculus_min:88

| | 1 wangwu 2 19910303 1.6 89 90 | ```markdown C_average:89 C_max:89 wangwu 2 19910303 1.60 89 90 C_min:89 Calculus_average:90 Calculus_max:90 wangwu 2 19910303 1.60 89 90 Calculus_min:90

  1. |
  2. ```cpp
  3. #include <stdio.h>
  4. #include <stdbool.h>
  5. // 定义学生结构体
  6. typedef struct {
  7. char name[21]; // 姓名
  8. int gender; // 性别
  9. int birthdate; // 生日
  10. float height; // 身高
  11. int c_score; // C 语言成绩
  12. int calculus_score; // 微积分成绩
  13. } Student;
  14. int main() {
  15. int n;
  16. scanf("%d", &n); // 输入学生人数
  17. Student students[n]; // 定义学生结构体数组
  18. // 输入每个学生的信息
  19. for (int i = 0; i < n; i++) {
  20. scanf("%s %d %d %f %d %d", students[i].name, &students[i].gender, &students[i].birthdate, &students[i].height, &students[i].c_score, &students[i].calculus_score);
  21. }
  22. int c_min = 101, c_max = -1, calculus_min = 101, calculus_max = -1;
  23. float c_sum = 0, calculus_sum = 0;
  24. // 计算每门课程的总成绩、最高分和最低分
  25. for (int i = 0; i < n; i++) {
  26. c_sum += students[i].c_score;
  27. calculus_sum += students[i].calculus_score;
  28. if (students[i].c_score > c_max) c_max = students[i].c_score;
  29. if (students[i].c_score < c_min) c_min = students[i].c_score;
  30. if (students[i].calculus_score > calculus_max) calculus_max = students[i].calculus_score;
  31. if (students[i].calculus_score < calculus_min) calculus_min = students[i].calculus_score;
  32. }
  33. // 输出结果
  34. printf("C_average:%.0f\n", c_sum / n);
  35. printf("C_max:%d\n", c_max);
  36. // 输出获得 C 语言最高分的学生信息
  37. for (int i = 0; i < n; i++) {
  38. if (students[i].c_score == c_max) {
  39. printf("%s %d %d %.2f %d %d\n", students[i].name, students[i].gender, students[i].birthdate, students[i].height, students[i].c_score, students[i].calculus_score);
  40. }
  41. }
  42. printf("C_min:%d\n", c_min);
  43. printf("Calculus_average:%.0f\n", calculus_sum / n);
  44. printf("Calculus_max:%d\n", calculus_max);
  45. // 输出获得微积分最高分的学生信息
  46. for (int i = 0; i < n; i++) {
  47. if (students[i].calculus_score == calculus_max) {
  48. printf("%s %d %d %.2f %d %d\n", students[i].name, students[i].gender, students[i].birthdate, students[i].height, students[i].c_score, students[i].calculus_score);
  49. }
  50. }
  51. printf("Calculus_min:%d\n", calculus_min);
  52. return 0;
  53. }

一些细微的差异:

  • printf("C_average:%.0f\n", c_sum / n); 如果是这种写法,那么 (85 + 89 + 89) / 3 打印结果是 88,而测试用例的结果是 87
  • printf("C_average:%d\n", int(c_sum / n)); 如果要确保和测试保持统一,这里可以改写为这种写法
  1. 3 zhangsan 1 19910101 1.85 85 90 lisi 1 19920202 1.56 89 88 wangwu 2 19910303 1.6 89 90
  2. C_average:88
  3. C_max:89
  4. lisi 1 19920202 1.56 89 88
  5. wangwu 2 19910303 1.60 89 90
  6. C_min:85
  7. Calculus_average:89
  8. Calculus_max:90
  9. zhangsan 1 19910101 1.85 85 90
  10. wangwu 2 19910303 1.60 89 90
  11. Calculus_min:88
  12. 2 lisi 1 99920202 1.56 89 88 wangwu 2 19910303 1.6 89 90
  13. C_average:89
  14. C_max:89
  15. lisi 1 99920202 1.56 89 88
  16. wangwu 2 19910303 1.60 89 90
  17. C_min:89
  18. Calculus_average:89
  19. Calculus_max:90
  20. wangwu 2 19910303 1.60 89 90
  21. Calculus_min:88
  22. 1 wangwu 2 19910303 1.6 89 90
  23. C_average:89
  24. C_max:89
  25. wangwu 2 19910303 1.60 89 90
  26. C_min:89
  27. Calculus_average:90
  28. Calculus_max:90
  29. wangwu 2 19910303 1.60 89 90
  30. Calculus_min:90