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