4.3 编程实战
求1到100之间的所有素数
冒泡排序
折半查找
兔子问题
时钟
数据分析
notes
常规法子 | 求 1 到 100 之间的所有素数
#include <stdio.h>
int main() {
int i, j, is_prime;
for (i = 2; i <= 100; i++) {
is_prime = 1; // 假设 i 是素数
for (j = 2; j < i; j++) {
if (i % j == 0) { // 如果 i 能被 j 整除,则 i 不是素数
is_prime = 0;
break;
}
}
if (is_prime) { // 如果 i 是素数,则打印出来
printf("%d ", i);
}
}
printf("\n");
return 0;
}
/* 运行结果:
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
*/
埃氏筛法(Sieve of Eratosthenes) | 4.3.1 视频结尾提到的优化解法 | 求 1 到 100 之间的所有素数
埃氏筛法(Sieve of Eratosthenes)是一种用于求解素数的算法,它的基本思想是:从 2 开始,将每个素数的倍数标记为合数,直到遍历到给定的范围为止。
#include <stdio.h>
#define N 100
int main() {
int i, j, is_prime[N + 1];
// 初始化数组
for (i = 2; i <= N; i++) {
is_prime[i] = 1; // 假设所有数都是素数
}
// 标记合数
for (i = 2; i <= N; i++) {
if (is_prime[i]) { // 如果 i 是素数
for (j = i * 2; j <= N; j += i) {
is_prime[j] = 0; // 标记 i 的倍数为合数
}
}
}
// 输出素数
for (i = 2; i <= N; i++) {
if (is_prime[i]) {
printf("%d ", i);
}
}
printf("\n");
return 0;
}
/* 运行结果:
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
*/
解释:
#define N 100
在上面的示例中,我们首先定义了一个常量 N,表示要求解的范围为从 2 到 N 的所有素数。int is_prime[N + 1];
然后,我们定义了一个名为 is_prime 的 布尔数组,用于记录每个数是否为素数。is_prime[i] = 1;
在初始化数组时,我们将所有的数都假设为素数。is_prime[j] = 0;
接着,我们从 2 开始遍历数组,如果遇到素数,则将它的倍数标记为合数,这里我们可以直接从 i 的倍数开始,因为它们已经被之前的素数标记为合数了。最后,我们输出未被标记为合数的数即为素数。
注意:
- 这种方法虽然效率比较高,但是需要额外的空间来记录每个数是否为素数
- 如果要求解比较大的范围的素数,可能需要使用更高效的算法或者优化算法的实现
算法思想的核心:找出 1-N
中所有非素数,剩下的就都是素数了。
#include <math.h>
#include <stdio.h>
int main() {
const int n = 100;
int isPrim[n] = {0}; // 默认所有数是非素数
int i, j;
for (i = 2; i < sqrt(n); ++i) {
if (isPrim[i] == 0)
for (j = 2 * i; j <= n; j += i)
isPrim[j] = 1; // 将每个素数的倍数标记为合数
}
for (i = 2; i <= n; ++i)
if (isPrim[i] == 0) {
printf("%d ", i);
}
return 0;
}
/* 运行结果:
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
*/
欧拉筛法(Sieve of Euler) | 求 1 到 100 之间的所有素数
欧拉筛法的基本思想是:对于每个合数(即非素数),只会在最小质因子筛选时被筛掉,在后续的枚举过程中不会再次被筛选。因此,在筛质数时,只需要考虑每个数的最小质因子,即第一个能整除它的质数。
欧拉筛法的基本步骤:
- 初始化:先将所有数标记为素数(或者不标记任何数),并且保留每个数的最小质因数,和一个素数列表 primes。
- 筛除合数:从 2 开始逐个枚举每个数,如果这个数是素数,则将其添加到素数列表 primes 中,并标记其倍数为合数,并记录这个合数的最小质因子,如果这个数的最小质因子就是它本身,则跳过。
- 输出结果:最终得到的素数列表即为筛选出的所有质数。
#include <stdio.h>
#include <stdbool.h>
#define MAX_N 100000
int primes[MAX_N]; // 存放素数的数组
bool is_prime[MAX_N]; // 标志数组:判断某个数是否为素数
int num_primes = 0; // 当前素数的个数
// 欧拉筛法
void euler_sieve(int n) {
is_prime[0] = is_prime[1] = false; // 标记 0 和 1 不为素数
for (int i = 2; i <= n; i++) {
if (is_prime[i] == true) {
primes[num_primes++] = i;
}
for (int j = 0; j < num_primes && i * primes[j] <= n; j++) {
is_prime[i * primes[j]] = false;
if (i % primes[j] == 0) break; // 如果 i 除以当前素数得到余数为 0,说明 i 的最小质因子为 primes[j],因此跳出循环
}
}
}
int main() {
int n = 100;
for (int i = 0; i <= n; i++) {
is_prime[i] = true; // 默认所有数一开始都是素数
}
euler_sieve(n);
for (int i = 0; i < num_primes; i++) {
printf("%d ", primes[i]);
}
return 0;
}
冒泡排序
冒泡排序是一种简单、直观的排序算法,其基本思想是 将要排序的元素按照大小关系进行比较并交换,每一次遍历可以找到待排序序列中最大或最小的元素,重复这个过程直到整个序列有序。
冒泡排序的时间复杂度是O(n^2),它虽然简单易懂,但对于大规模数据的排序效率较低,不适合大规模数据的排序。
下面是冒泡排序的伪代码:
- 初始化:设待排序元素的数列为
a[1], a[2], ..., a[N]
。 - 外循环:从第一个元素开始,依次比较相邻两个元素的大小,如果前一个元素比后一个元素大(或小,根据实际需求而定),则交换这两个元素的位置。依次遍历整个数列,最终得到最大(或最小)的元素,放置在数列的末尾(或开头)。
- 内循环:继续从第一个元素开始,重复第2步,但这次不再包括最后一个元素。重复这个过程,直到整个数列有序。
- 输出结果:最终得到的有序数列即为排序结果。
冒泡排序流程示意图:
#include <stdio.h>
int main() {
int a[6] = {16, 8, 4, 32, 5, 9};
int temp;
for (int i = 1; i < 6; ++i)
for (int j = 0; j < 6 - i; j++)
if (a[j] > a[j + 1]) {
temp = a[j];
a[j] = a[j + 1];
a[j + 1] = temp;
}
for (int i = 0; i <= 5; i++)
printf("%d ", a[i]);
return 0;
}
/* 运行结果:
4 5 8 9 16 32
*/
#include <stdio.h>
void bubble_sort(int a[], int n) {
int i, j, temp;
for (i = 0; i < n - 1; i++) { // 外循环
for (j = 0; j < n - 1 - i; j++) { // 内循环
if (a[j] > a[j + 1]) { // 如果前一个值比后一个值大,则交换它们的位置
temp = a[j];
a[j] = a[j + 1];
a[j + 1] = temp;
}
}
}
}
int main() {
int a[5] = {16, 8, 4, 32, 5};
int i;
bubble_sort(a, 5);
for (i = 0; i < 5; i++) {
printf("%d ", a[i]);
}
return 0;
}
/* 运行结果:
4 5 8 16 32
*/
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 个位置
- 以 5 个数
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 次
- 以 5 个数
#include <stdio.h>
void bubble_sort(int a[], int n) {
int i, j, temp;
int flag = 1; // 标志变量 flag,初始值为 true
for (i = 0; i < n - 1 && flag == 1; i++) { // 外循环
flag = 0; // 每次循环开始时,将 flag 设为 false
for (j = 0; j < n - 1 - i; j++) { //内循环
if (a[j] > a[j + 1]) { // 如果前一个值比后一个值大,则交换它们的位置
temp = a[j];
a[j] = a[j + 1];
a[j + 1] = temp;
flag = 1; // 如果进行了交换操作,将 flag 设为 true
}
}
}
}
int main() {
int a[5] = {16, 8, 4, 32, 5};
int i;
bubble_sort(a, 5);
for (i = 0; i < 5; i++) {
printf("%d ", a[i]);
}
return 0;
}
/* 运行结果:
4 5 8 16 32
*/
核心:加标记,判断后续的交换是否有必要进行,以减少交换次数。
如果某一次的内层循环跑完,都没有发生两数交换的情况,那么意味着:
- 该数组已经处于有序状态,此时就没有必要再进行后续的比较了。
flag = 1;
这条语句将不会被执行,flag
的值将保持为0
,外层循环条件不满足,直接结束后续的所有流程。
折半查找(二分查找)
- 折半查找(Binary Search),也被称为 二分查找,是一种常用的查找算法。
- 核心思想:将待查找的元素与已知的中间元素进行比较,从而逐步缩小查找范围,最终找到待查元素所在的位置。
- 前提:待查找的序列必须有序
这里我们假设序列是升序排列的,下面是折半查找具体的实现步骤:
- 初始化:设待查找元素为 x,数组名为 a,开始时,设置左右两个游标 left 和 right,分别指向数组左右两端,即 left = 0,right = n - 1。
- 折半查找:在每一次查找时:
- 计算中间元素的位置 mid
mid = (left + right) / 2
- 将 x 与 a[mid] 进行比较
- 如果 x 等于 a[mid],则找到了要查找的元素
- 如果 x 大于 a[mid],则表明要查找的元素在
[mid + 1, right]
的区间内,更新left = mid + 1
- 如果 x 小于 a[mid],则表明要查找的元素在
[left, mid - 1]
的区间内,更新right = mid - 1
- 计算中间元素的位置 mid
- 结束查找:当 left > right 时,表明数组中不存在 x,查找结束。
#include <stdio.h>
int main() {
int data[10] = {23, 37, 45, 54, 65, 78, 82, 87, 89, 94};
int target = 88;
int len = sizeof(data) / sizeof(data[0]), low = 1, high = len, mid;
mid = (low + high) / 2;
while (low <= high) {
if (data[mid] == target) {
printf("数组中 %d 的下标是 %d\n", target, mid);
return 0;
} else if (data[mid] > target) {
high = mid - 1;
} else {
low = mid + 1;
}
mid = (low + high) / 2;
}
printf("数组中不存在 %d\n", target);
return 0;
}
/* 运行结果:
数组中不存在 88
*/
#include <stdio.h>
int main() {
int data[10] = {23, 37, 45, 54, 65, 78, 82, 87, 89, 94};
int target = 78;
int len = sizeof(data) / sizeof(data[0]), low = 1, high = len, mid;
mid = (low + high) / 2;
while (low <= high) {
if (data[mid] == target) {
printf("数组中 %d 的下标是 %d\n", target, mid);
return 0;
} else if (data[mid] > target) {
high = mid - 1;
} else {
low = mid + 1;
}
mid = (low + high) / 2;
}
printf("数组中不存在 %d\n", target);
return 0;
}
/* 运行结果:
数组中 78 的下标是 5
*/
#include <stdio.h>
int binary_search(int a[], int n, int x) {
int left = 0, right = n - 1; // 定义左右两个游标
while (left <= right) { // 只要游标没有相撞,就不断查找
int mid = (left + right) / 2; // 计算中间位置
if (a[mid] == x) // 找到了
return mid;
else if (a[mid] > x) // 中间值 > 目标元素
right = mid - 1; // 说明目标元素位于 左 侧的区间中 [ left, mid - 1 ]
else // 中间值 <= 目标元素
left = mid + 1; // 说明目标元素位于 右 侧的区间中 [ mid + 1, right ]
}
return -1;
}
int main() {
int a[8] = {1, 3, 4, 6, 8, 9, 11, 13};
int n = sizeof(a) / sizeof(a[0]); // 计算数组长度
int x = 6; // 查找的目标成员
int index = binary_search(a, n, x);
if (index == -1)
printf("数组中不存在%d\n", x);
else
printf("数组中 %d 的下标是 %d\n", x, index);
return 0;
}
/* 运行结果:
数组中 6 的下标是 3
*/
求 Fibonacci 数
问:什么是 Fibonacci 数?
- 第 1 个数为 1
- 第 2 个数为 1
- 头两个数为 1
- 第 3 个数为 2
- 从第三个数开始,每个数等于前两数之和
- 第 4 个数为 3
- 第 5 个数为 5
- 第 6 个数为 8
- 第 7 个数为 13
- 第 8 个数为 21
- 第 9 个数为 34
- ……
#include <stdio.h>
int main() {
const int n = 10;
int f[n], i;
f[1] = 1;
f[2] = 1;
printf("%4d%4d", f[1], f[2]);
for (i = 3; i < n; i++) {
f[i] = f[i - 1] + f[i - 2];
printf("%4d", f[i]);
}
return 0;
}
/* 运行结果:
1 1 2 3 5 8 13 21 34
*/
课件中的这种做法,使用到了数组类型,相当于将每一项的值都给存下来了,随着值的不断增多,数组中装的成员也将越来越多,需要的空间也会越来越大。
#include <stdio.h>
int Fibonacci(int n) {
if (n <= 0)
return -1; // 输入的数据不合法
if (n == 1 || n == 2)
return 1; // 当 n 等于 1 或 2 时,斐波那契数为 1
return Fibonacci(n - 1) + Fibonacci(n - 2); // 定义递归式,并进行递推
}
int main() {
int n = 10;
printf("f(%d) = %d\n", 1, Fibonacci(1));
printf("f(%d) = %d\n", 2, Fibonacci(2));
for (int i = 3; i < n; i++) {
int result = Fibonacci(i);
if (result == -1)
printf("输入数据不合法!");
else
printf("f(%d) = %d\n", i, result);
}
return 0;
}
/* 运行结果:
f(1) = 1
f(2) = 1
f(3) = 2
f(4) = 3
f(5) = 5
f(6) = 8
f(7) = 13
f(8) = 21
f(9) = 34
*/
在以上代码中,我们定义了函数 Fibonacci 进行递推求解斐波那契数列的第 n 项值。
- 当输入的 n 小于或等于 0 时,返回 -1,代表输入数据不合法;当 n 等于 1 或 2 时,返回1;
- 当 n 大于等于 3 时,通过递归计算 f(n-1) 和 f(n-2) 的和,并返回结果;
#include <stdio.h>
int Fibonacci(int n) {
if (n <= 0)
return -1; // 输入的数据不合法
if (n == 1 || n == 2)
return 1; // 当 n 等于 1 或 2 时,斐波那契数为 1
int f1 = 1, f2 = 1, f3 = 0;
for (int i = 3; i <= n; i++) {
f3 = f1 + f2; // 定义递推式
f1 = f2;
f2 = f3; // 进行向后滚动
}
return f3;
}
int main() {
int n = 10;
printf("f(%d) = %d\n", 1, Fibonacci(1));
printf("f(%d) = %d\n", 2, Fibonacci(2));
for (int i = 3; i < n; i++) {
int result = Fibonacci(i);
if (result == -1)
printf("输入数据不合法!");
else
printf("f(%d) = %d\n", i, result);
}
return 0;
}
/* 运行结果:
f(1) = 1
f(2) = 1
f(3) = 2
f(4) = 3
f(5) = 5
f(6) = 8
f(7) = 13
f(8) = 21
f(9) = 34
*/
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
函数的声明
#include <stdio.h>
#include <stdlib.h> // 包含 system 函数的声明
int main()
{
printf("输出一些信息......\n");
// system("CLS"); // 清空控制台屏幕
system("clear");
printf("屏幕清空之后,输出一些其它信息......\n");
return 0;
}
/* 运行结果:
屏幕清空之后,输出一些其它信息...... */
时钟题目描述
需求:定义一个时钟结构体类型,然后编程实现将时钟模拟显示在屏幕上
扩展:获取本机当前时间,修改程序,实现实时显示本机当前时间
休眠
Sleep
和 sleep
是两个不同的函数,用于在不同的操作系统中进行程序的休眠。
Sleep
Sleep
函数是 Windows 操作系统中的一个函数,定义在 Windows API 中- 用于使当前线程进入指定的时间(以 毫秒 为单位)的休眠状态
- 阻塞当前线程 并 限制系统的 CPU 资源使用
- 在使用时需要包含头文件
<windows.h>
sleep
sleep
函数是 Linux 或 Unix 操作系统中的一个函数- 可以将当前线程阻塞指定的 秒数
- 在使用时需要包含头文件
<unistd.h>
注意误差问题
Sleep
函数的精度会受到系统负载的影响,因此在 Windows 操作系统中,使用Sleep
函数进行短时间的休眠时可能会出现误差。sleep
函数则并不受到这种影响。同时,Linux 操作系统也提供了usleep
函数,它的单位是微秒,同样可以用于实现更准确的程序休眠。
#include <stdio.h>
#include <unistd.h>
int main() {
printf("开始休眠 5s\n");
sleep(5); // 休眠 5 秒
printf("休眠结束!\n"); // => 这条语句在 5 秒后才会打印
return 0;
}
/* 运行结果:
开始休眠 5s
休眠结束!
*/
缓冲区
缓冲区(Buffer)是 计算机系统中用于存储数据的一段内存空间,用来 临时存放输入/输出数据的中转站,缓解输入/输出设备和计算机之间速度不匹配的问题,提高数据传输的效率。
在计算机系统中,输入/输出设备的速度通常比内存和处理器的速度慢很多,因此当程序需要进行输入/输出操作时,为了提高效率,会先将数据存储到缓冲区中,然后再由系统将缓冲区中的数据传输到输入/输出设备中。这样,输入/输出设备就不需要等待计算机的处理速度,而是可以按照缓冲区中数据的速度进行数据传输,从而提高了系统的性能。
缓冲区在计算机系统中广泛应用,比如在 👇🏻 下面这些场景中都会使用到缓冲区
- 文件读写
- 网络通信
- 视频音频播放
- ……
缓冲区的大小和使用方式都会对系统的性能和效率产生影响,因此在设计和优化系统时,需要考虑缓冲区的大小和使用方式,以达到最优的效果。
刷新缓冲区,将缓冲区中的数据输出到屏幕上
fflush(stdout)
是 C 语言标准库中的一个函数,它的作用是刷新输出缓冲区,并将缓冲区中的数据立即输出到标准输出设备,比如屏幕、终端等。
在 C 语言程序中,输出到标准输出设备的数据并不是立即显示在屏幕上,而是先存放在输出缓冲区中,当缓冲区满了、程序结束或者遇到 fflush 函数时,才将缓冲区中的数据输出到标准输出设备。如果程序没有输出足够的数据,缓冲区就不会满,输出数据也不会被显示。这时候,使用 fflush(stdout)
函数可以立即刷新输出缓冲区,将缓冲区中的数据输出到屏幕上。
在编写需要实时显示的程序时,比如显示进度条、实时输出时间等,使用 fflush(stdout)
函数可以确保输出数据能够及时显示在屏幕上,而不是等待缓冲区满或者程序结束时才输出。
#include <stdio.h>
#include <unistd.h>
int main() {
int n = 5, i = 0;
printf("开始休眠 5 s\n");
while (++i < n) {
printf("已休眠 %d s\r", i);
fflush(stdout);
sleep(1);
}
printf("已休眠 5 s,休眠结束\n");
}
fflush(stdout);
如果没有这条语句,那么我们要输出的内容,将会堆积在缓冲区中,当循环结束时,才会一并输出。具体效果见下图 👇🏻
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
- ……
#include <stdio.h>
#include <time.h>
int main() {
time_t current_time = time(NULL);
printf("当前时间戳为:%ld\n", current_time);
return 0;
}
/* 运行结果:
当前时间戳为:1679552034
*/
localtime
- 函数声明:
struct tm *localtime(const time_t *timer);
- 参数
timer
是指向以秒为单位的time_t
时间值的 指针
- 返回值
- 一个指向
struct tm
结构体类型的指针 struct tm
结构体类型包含有关时间(年、月、日等)的信息
- 一个指向
localtime
是 C 语言标准库中用于处理时间的函数之一- 作用是将 time_t 类型的时间值转换为当地时间(local time)
struct tm 结构体定义如下:
struct tm {
int tm_sec; /* 秒数(0~59)*/
int tm_min; /* 分钟数(0~59)*/
int tm_hour; /* 小时数(0~23)*/
int tm_mday; /* 日数(1~31)*/
int tm_mon; /* 月数(0~11)*/
int tm_year; /* 年数(自 1900 年起)*/
int tm_wday; /* 一周中的第几天(0~6,0 表示星期天)*/
int tm_yday; /* 一年中的第几天(0~365)*/
int tm_isdst; /* 夏令时标识符(-1 表示无夏令时信息,0 表示无夏令时,正数表示有夏令时)*/
};
localtime
函数的返回值为一个指向 struct tm 结构体的指针,指针指向的结构体包含有关时间的各个部分的信息。
本章要实现的“时钟”demo,需要用到的就是 struct tm 结构体身上的:
- 时
tm_hour
- 分
tm_min
- 秒
tm_sec
#include <stdio.h>
#include <time.h>
int main() {
// 获取当前时间
time_t timestamp = time(NULL);
struct tm* tm_local = localtime(×tamp);
// 输出时分秒
printf("当前时间: %02d:%02d:%02d\n",
tm_local->tm_hour,
tm_local->tm_min,
tm_local->tm_sec);
return 0;
}
/*
当前时间: 06:17:55
*/
补充:有关指针的更多内容,会在第 5 章详细介绍,现在只要知道我们可以通过上述这种方式,拿到当下时间数据即可。
时钟
#include <stdio.h>
#include <windows.h>
struct clock {
int hour;
int minute;
int second;
};
typedef struct clock CLOCK;
int main() {
CLOCK t = {0, 0, 0};
int n = 100, i = 0;
char c;
while (++i < n) {
t.second++;
if (t.second >= 60) {
t.second = 0;
t.minute++;
}
if (t.minute >= 60) {
t.minute = 0;
t.hour++;
}
if (t.hour >= 24)
t.hour = 0;
printf("%2d:%2d:%2d\r", t.hour, t.minute, t.second);
Sleep(1000);
}
}
这段程序丢到 windows 设备上可以正常跑,Linux 上不存在 windows.h
、Sleep
,所以在 Linux 上是没法运行的。
#include <cstdlib>
#include <stdio.h>
#include <unistd.h>
struct clock {
int hour;
int minute;
int second;
};
typedef struct clock CLOCK;
int main() {
CLOCK t = {0, 0, 0};
int n = 100, i = 0;
char c;
while (++i < n) {
t.second++;
if (t.second >= 60) {
t.second = 0;
t.minute++;
}
if (t.minute >= 60) {
t.minute = 0;
t.hour++;
}
if (t.hour >= 24)
t.hour = 0;
printf("%02d:%02d:%02d", t.hour, t.minute, t.second);
system("clear");
// printf("%02d:%02d:%02d\r", t.hour, t.minute, t.second);
fflush(stdout); // 刷新缓冲区
sleep(1);
}
}
/* 运行结果:
00:00:17
*/
实时更新效果:两种写法,效果是非常类似的
- 写法 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 函数刷新输出缓冲区,保证信息能够及时显示
扩展:实时显示系统当前时间
#include <stdio.h>
#include <time.h>
#include <unistd.h>
int main() {
while (1) {
time_t current_time = time(NULL); // 获取当前时间
struct tm* tm = localtime(¤t_time); // 将当前时间转换为本地时间
int hour = tm->tm_hour; // 获取小时数
int minute = tm->tm_min; // 获取分钟数
int second = tm->tm_sec; // 获取秒数
printf("%02d:%02d:%02d\r", hour, minute, second); // 输出时分秒信息
fflush(stdout); // 刷新缓冲区
sleep(1); // 等待1秒
}
return 0;
}
/* 运行结果:
03:02:17
*/
由于 localtime
函数返回的是一个指向 struct tm 结构体类型的指针,因此需要使用 ->
运算符来访问其成员,如 local_time->tm_year
来访问年份信息。
课件源码
#include <stdio.h>
typedef struct student {
char grade[5];
char department[3];
char major[3];
char cclass[3];
char number[4];
} student;
int main() {
char number[14];
student s;
int i;
scanf("%s", number);
for (i = 0; i < 4; ++i)
s.grade[i] = number[i];
s.grade[i] = '\0';
s.department[0] = number[i++];
s.department[1] = number[i++];
s.department[2] = '\0';
s.major[0] = number[i++];
s.major[1] = number[i++];
s.major[2] = '\0';
s.cclass[0] = number[i++];
s.cclass[1] = number[i++];
s.cclass[2] = '\0';
s.number[0] = number[i++];
s.number[1] = number[i++];
s.number[2] = number[i++];
s.number[3] = '\0';
printf("学院:%s;专业:%s;年级:%s;班:%s;学号:%s\n", s.department, s.major,
s.grade, s.cclass, s.number);
}
/* 运行结果:
2015060204030
学院:06;专业:02;年级:2015;班:04;学号:030
*/
使用结构体存储学生信息
#include <stdio.h>
typedef struct student {
int grade;
char department[32];
char major[32];
char cclass[32];
char number[32];
} student;
int main() {
student s = {
.grade = 2022,
.department = "计算机科学与技术",
.major = "软件工程",
.cclass = "1班",
.number = "20220001",
};
printf("年级:%d\n", s.grade);
printf("学院:%s\n", s.department);
printf("专业:%s\n", s.major);
printf("班级:%s\n", s.cclass);
printf("学号:%s\n", s.number);
return 0;
}
/* 运行结果:
年级:2022
学院:计算机科学与技术
专业:软件工程
班级:1班
学号:20220001 */
#include <stdio.h>
typedef struct student {
char nickName[20];
int grade;
char department[32];
char major[32];
char cclass[32];
char number[32];
} student;
int main() {
student s = {0};
printf("请输入学生信息:\n");
printf("昵称:");
scanf("%s", s.nickName);
printf("年级:");
scanf("%d", &s.grade);
printf("学院:");
scanf("%s", s.department);
printf("专业:");
scanf("%s", s.major);
printf("班级:");
scanf("%s", s.cclass);
printf("学号:");
scanf("%s", s.number);
printf("\n学生信息如下:\n");
printf("昵称:%s\n", s.nickName);
printf("年级:%d\n", s.grade);
printf("学院:%s\n", s.department);
printf("专业:%s\n", s.major);
printf("班级:%s\n", s.cclass);
printf("学号:%s\n", s.number);
return 0;
}
/* 运行结果:
请输入学生信息:
昵称:Tdahuyou
年级:2018
学院:信息技术学院
专业:物联网工程
班级:B18-1
学号:1820573
学生信息如下:
昵称:Tdahuyou
年级:2018
学院:信息技术学院
专业:物联网工程
班级:B18-1
学号:1820573
*/
4.4 华为 CloudIDE
删除重复字符
00:01:53 | 输入输出
第四章实验 | 成绩管理系统
notes
练习 1:删除字符串中连续的重复字符
实现删除字符串中连续的重复字符(除字母和数字)。
描述:
输入为字符串,将字符串中连续重复的,不是字母且不是数字的字符删去,然后输出处理后的字符串。
要求:
输入字符串最长 50 个字符,之后截断,只输出处理后的字符串。
算法要点:
- 先用字符数组存储字符串
- 逐个判断字符是否为字母或者数字
- 使用两个下标指针移动赋值操作
| 输入 | 输出 |
| —- | —- |
|
11+++2==13
|11+2=3
| |212(){}abc3212
|212(){}abc3212
| |32---1===!==31
|32-1=!=31
|
#include <ctype.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
// 判断字符是否为字母或数字
bool is_letter_or_digit(char c) { return isalpha(c) || isdigit(c); }
int main() {
char input[51];
// 从输入读取最多 50 个字符的字符串
fgets(input, 51, stdin);
int length = strlen(input);
// 去除换行符,如果存在
if (input[length - 1] == '\n') {
input[length - 1] = '\0';
length--;
}
char result[51]; // 存放删除连续字符后的字符串内容
int result_index = 0;
// 遍历输入字符串
for (int i = 0; i < length; ++i) {
// 如果是字母或数字,直接添加到结果字符串
if (is_letter_or_digit(input[i])) {
result[result_index++] = input[i];
} else {
// 如果不是字母或数字,将字符添加到结果字符串,然后跳过连续的相同字符
result[result_index++] = input[i];
while (i < length - 1 && !is_letter_or_digit(input[i + 1]) &&
input[i] == input[i + 1]) {
i++;
}
}
}
// 添加字符串结束符
result[result_index] = '\0';
// 输出处理后的字符串
printf("%s\n", result);
return 0;
}
/* 运行结果:
11+++2==13
11+2=13
212(){}abc3212
212(){}abc3212
32---1===!==31
32-1=!=31
*/
#include <ctype.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
bool is_letter_or_digit(char c) { return isalpha(c) || isdigit(c); }
int main() {
char input[51];
fgets(input, 51, stdin);
int length = strlen(input);
if (input[length - 1] == '\n') {
input[length - 1] = '\0';
length--;
}
char result[51];
int result_index = 0;
// 简化 for 循环的循环体写法
for (int i = 0; i < length; ++i) {
result[result_index++] = input[i];
if (is_letter_or_digit(input[i])) continue;
while (i < length - 1 && input[i] == input[i + 1]) i++;
}
result[result_index] = '\0';
printf("%s\n", result);
return 0;
}
// 使用到的库 👇🏻
#include <stdio.h> // 标准输入输出库
#include <string.h> // 定义了一个变量类型、一个宏和各种操作字符数组的函数
#include <stdlib.h> // 定义了四个变量类型、一些宏和各种通用工具函数
#include <ctype.h> // 提供了一些函数,可用于测试和映射字符
int main() {
char strs[10000]; // 定义字符数组
scanf("%s", strs);
int i = 0, j = 1;
char res[51];
strncpy(res, strs, 50); // 字符串拷贝函数
int len = strlen(res);
while (j < len) {
if (isdigit(strs[j]) || isalpha(strs[j])) { // 判断是字母或者数字
res[++i] = res[j++]; // 赋值之后下标后移
} else if (res[i] == res[j]) { // 不是字母或者数字且与之前一个字符相同,直接跳过
j++;
} else {
res[++i] = res[j++]; // 不是字母或者数字且与之前一个字符不相同,赋值之后后移
}
};
res[++i] = '\0'; // 添加结束符
printf("%s", res);
return 0;
}
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
int main() {
char strs[10000];
scanf("%s", strs);
int i = 0, j = 1;
char res[51];
strncpy(res, strs, 50);
int len = strlen(res);
while (j < len) {
if (isdigit(strs[j]) || isalpha(strs[j])) res[++i] = res[j++];
else if (res[i] == res[j]) j++;
else res[++i] = res[j++];
};
res[++i] = '\0';
printf("%s", res);
return 0;
}
strncpy(res, strs, 50);
在处理字符串时,直接使用了 strncpy 函数将原始输入字符串截断并复制到 res 数组中。
这样可以避免在循环中处理换行符。
版本 1 中,需要在循环外部对换行符进行处理。
练习 2:学生管理系统
编写学生管理系统,其中学生的信息有姓名(汉语拼音,最多 20 个字符,长度 21 的字符数组),性别(男/女,用 1 表示男,2 表示女,整数)、生日(19850101(年月日),整数)、身高(以 m 为单位,实数),还需要处理 C 语言、微积分两门课的成绩(整数)。
请编写程序实现功能:输入学生的人数和每个学生的信息;输出每门课程的总平均成绩、最高分和最低分,以及获得最高分的学生的信息。需要注意的是某门课程最高分的学生可能不只一人。
算法要点:
- 定义结构体
- 结构体的遍历
输入输出格式要求:身高输出时保留两位小数,请按照例子中进行输出,请勿输出其他字符
测试用例:
输入 | 输出 |
---|---|
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
|
| `2 lisi 1 19920202 1.56 89 88 wangwu 2 19910303 1.6 89 90` | ```markdown
C_average:89
C_max:89
lisi 1 19920202 1.56 89 88
wangwu 2 19910303 1.60 89 90
C_min:89
Calculus_average:89
Calculus_max:90
wangwu 2 19910303 1.60 89 90
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
|
```cpp
#include <stdio.h>
#include <stdbool.h>
// 定义学生结构体
typedef struct {
char name[21]; // 姓名
int gender; // 性别
int birthdate; // 生日
float height; // 身高
int c_score; // C 语言成绩
int calculus_score; // 微积分成绩
} Student;
int main() {
int n;
scanf("%d", &n); // 输入学生人数
Student students[n]; // 定义学生结构体数组
// 输入每个学生的信息
for (int i = 0; i < n; i++) {
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);
}
int c_min = 101, c_max = -1, calculus_min = 101, calculus_max = -1;
float c_sum = 0, calculus_sum = 0;
// 计算每门课程的总成绩、最高分和最低分
for (int i = 0; i < n; i++) {
c_sum += students[i].c_score;
calculus_sum += students[i].calculus_score;
if (students[i].c_score > c_max) c_max = students[i].c_score;
if (students[i].c_score < c_min) c_min = students[i].c_score;
if (students[i].calculus_score > calculus_max) calculus_max = students[i].calculus_score;
if (students[i].calculus_score < calculus_min) calculus_min = students[i].calculus_score;
}
// 输出结果
printf("C_average:%.0f\n", c_sum / n);
printf("C_max:%d\n", c_max);
// 输出获得 C 语言最高分的学生信息
for (int i = 0; i < n; i++) {
if (students[i].c_score == c_max) {
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);
}
}
printf("C_min:%d\n", c_min);
printf("Calculus_average:%.0f\n", calculus_sum / n);
printf("Calculus_max:%d\n", calculus_max);
// 输出获得微积分最高分的学生信息
for (int i = 0; i < n; i++) {
if (students[i].calculus_score == calculus_max) {
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);
}
}
printf("Calculus_min:%d\n", calculus_min);
return 0;
}
一些细微的差异:
printf("C_average:%.0f\n", c_sum / n);
如果是这种写法,那么 (85 + 89 + 89) / 3 打印结果是 88,而测试用例的结果是 87printf("C_average:%d\n", int(c_sum / n));
如果要确保和测试保持统一,这里可以改写为这种写法
3 zhangsan 1 19910101 1.85 85 90 lisi 1 19920202 1.56 89 88 wangwu 2 19910303 1.6 89 90
C_average:88
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
2 lisi 1 99920202 1.56 89 88 wangwu 2 19910303 1.6 89 90
C_average:89
C_max:89
lisi 1 99920202 1.56 89 88
wangwu 2 19910303 1.60 89 90
C_min:89
Calculus_average:89
Calculus_max:90
wangwu 2 19910303 1.60 89 90
Calculus_min:88
1 wangwu 2 19910303 1.6 89 90
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