哔哩大学计算机学院-C语言编程2023-P45练习

https://www.bilibili.com/video/BV1cq4y1U7sg

判断素数

  • 写一个函数可以判断一个数是不是素数。

由上一篇,循环练习-2 中我们可以知道素数的基本判断思路:

在一般领域,对正整数n,如果用2到√n之间的所有整数去除,均无法整除,则n为质数。

则可以根据这一思路以及上一篇的代码写出如何判断素数的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <math.h>

int main()
{
int num = 0;
scanf("%d", &num);
int j = 0; // 用于嵌套循环,存放2到√n之间的所有整数
int flag = 0;
for (j = 2; j <= sqrt(num); j++)
{
if (num % j == 0)
{
flag = 1;
printf("%d不是素数\n", num);
break;
}
}
if (flag == 0)
{
printf("%d是素数\n", num);
}
}

具体思路解释可以查阅上一篇,循环练习-2。

但是这一题目的要求是通过函数调用的方式,写一个函数判断该数字是不是数字,接下来就是想办法怎么将这个代码封装到一个函数里面,首先我们定义一个JudgePrime函数,将上面的判断代码封装进去,因为数num是int型变量,所以我们形参也用int num,采用传值调用就可以解决。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <math.h>

void JudgePrime(int num)
{
int j = 0; // 用于嵌套循环,存放2到√n之间的所有整数
int flag = 0;
for (j = 2; j <= sqrt(num); j++)
{
if (num % j == 0)
{
flag = 1;
printf("%d不是素数\n", num);
break;
}
}
if (flag == 0)
{
printf("%d是素数\n", num);
}
}

int main()
{
int num = 0;
scanf("%d", &num);
JudgePrime(num);
}

只需要写函数名+参数就可以调用函数。

image-20240128101147978

但是上面的代码还是有一点小问题的,看上去主函数只是用短短一行代码调用函数就可以实现功能其实有点弄巧成拙。

因为对于写代码来说,我们要优化代码,使得代码高内聚,低耦合,那么就要函数的功能尽量单一,将打印函数放在函数里面是比较不好的写法,所以我们就可以将上述代码优化如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <math.h>

int JudgePrime(int num)
{
int j = 0; // 用于嵌套循环,存放2到√n之间的所有整数
for (j = 2; j <= sqrt(num); j++)
{
if (num % j == 0)
{
return 1; //如果不是素数返回1
}
}
return 0; //是素数返回0
}

int main()
{
int num = 0;
scanf("%d", &num);
if (JudgePrime(num) == 1)
{
printf("%d不是素数\n", num);
}
else
{
printf("%d是素数\n", num);
}
return 0;
}

结果是一样的。

判断闰年

  • 写一个函数判断一年是不是闰年。

由上一篇,循环练习-2 中我们同样可以知道闰年的基本判定规律:

判定公历闰年应遵循的一般规律为:四年一闰,百年不闰,四百年再闰.

即:

  1. 能不能被4整除,但不能被100整除,即闰年

  2. 能被400整除的也是闰年

根据以上思路结合上一篇的代码,小小修改,很快写出判断闰年的办法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

int main()
{
int year = 0; // 用于存放年份
scanf("%d", &year);
if (year % 4 == 0) // 能被4整除,即余数为0
{
if (year % 100 != 0) // 不能被100整除,即余数不为0
{
printf("%d是闰年\n", year);
}
else if (year % 400 == 0) // 能被400整除的也是闰年
{
printf("%d是闰年\n", year);
}
else
{
printf("%d不是闰年\n", year);
}
}
return 0;
}

接下来同样是将判断闰年的代码封装进函数里面,定义一个JudeLeap函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

int JudgeLeap(int year)
{
if (year % 4 == 0) // 能被4整除,即余数为0
{
if (year % 100 != 0) // 不能被100整除,即余数不为0
{
return 1;
}
else if (year % 400 == 0) // 能被400整除的也是闰年
{
return 1;
}
else
{
return 0;
}
}
}

int main()
{
int year = 0; // 用于存放年份
scanf("%d", &year);
if (JudgeLeap(year) == 1)
{
printf("%d是闰年\n", year);
}
else
{
printf("%d不是闰年\n", year);
}
return 0;
}

还可以再优化一下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

int JudgeLeap(int year)
{
if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) //将判断语句压缩成一行代码
{
return 1;
}
else
{
return 0;
}
}

int main()
{
int year = 0; // 用于存放年份
scanf("%d", &year);
if (JudgeLeap(year) == 1)
{
printf("%d是闰年\n", year);
}
else
{
printf("%d不是闰年\n", year);
}
return 0;
}

甚至还能再优化。。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

int JudgeLeap(int year)
{
return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)); //将判断语句压缩成一行代码
}

int main()
{
int year = 0; // 用于存放年份
scanf("%d", &year);
if (JudgeLeap(year) == 1)
{
printf("%d是闰年\n", year);
}
else
{
printf("%d不是闰年\n", year);
}
return 0;

效果是一样的,因为“||”运算符中有一个值为真,即返回真,否则返回假,即 return 0 。

image-20240128102045023

二分查找

  • 写一个函数,实现一个整形有序数组的二分查找。

循环练习-1 的第三问中“在一个有序数组中査找具体的某个数字n”用到了二分查找的思想。

二分查找也称折半查找(Binary Search),它是一种效率较高的查找方法。但是,折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列。

首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。

以下为适用二分查找的条件:

  1. 必须采用顺序存储结构。

  2. 必须按关键字大小有序排列。

那么编写一个函数,实现一个整形有序的数组的二分查找,写出函数要求:

  • 找到所需要的值,返回值所在数组位置的下标
  • 找不到则返回 -1(因为数组下标从0开始,不会存在负值)

先结合前篇文章写出二分查找的具体思路代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>

int main()
{
int v[10] = { 1,2,3,4,5,6,7,8,9,10 }; // 假设有序数组为1-10
int len = sizeof(v) / sizeof(v[0]); // 数组长度
int n = 0;
scanf("%d", &n);// 输入要查找的数字
int left = 0; // 设置查找的左下标
int right = len; // 设置查找的右下标
int mid = 0;
while (left <= right)
{
mid = (left + right) / 2;
if (v[mid] < n) // 要查找的数字在中间值的右边,则使左下标为中间值+1
{
left = mid + 1;
}
else if (v[mid] > n)
{
right = mid - 1;
}
else
{
printf("要查找的数字下标为:%d\n", mid);
break;
}
}
if (left > right)
{
printf("error");
}
return 0;
}

接下来就是按照上面的思路将代码封装进函数里。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>

int BinarySearch(int n, int v[])
{
int len = sizeof(v) / sizeof(v[0]); // 数组长度
int left = 0; // 设置查找的左下标
int right = len; // 设置查找的右下标
int mid = 0;
while (left <= right)
{
mid = (left + right) / 2;
if (v[mid] < n) // 要查找的数字在中间值的右边,则使左下标为中间值+1
{
left = mid + 1;
}
else if (v[mid] > n)
{
right = mid - 1;
}
else
{
printf("要查找的数字下标为:%d\n", mid);
break;
}
}
if (left > right)
{
printf("error");
}
return 0;
}

int main()
{
int v[10] = { 1,2,3,4,5,6,7,8,9,10 }; // 假设有序数组为1-10
int n = 0;
scanf("%d", &n);// 输入要查找的数字
BinarySearch(n, v);
}

这个代码看似没有错误,那么我们试运行一下,看看结果。

image-20240128110934499

???为什么会返回error呢,通过调试查找一下问题。

image-20240128111601132

通过调试,发现问题出现在数组长度的计算上,那当我们将这条语句放在主函数中,使函数多传递一个参数 len ,发现代码结果正常。

1
2
3
int BinarySearch(int n, int v[], int len)
/*中间省略*/
BinarySearch(n, v, len);

image-20240128112110899

为什么呢?

原因是:数组进行传参,实际传递的不是数组的本身,仅仅只是传过去了数组首元素的地址!

所以实质上,传过去的数组v是一个指针,对于x64的指针大小就是2。

1
int BinarySearch(int n, int* v) //实质上的代码

最后优化一下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>

int BinarySearch(int n, int v[], int len)
{
int left = 0; // 设置查找的左下标
int right = len; // 设置查找的右下标
int mid = 0;
while (left <= right)
{
mid = (left + right) / 2;
if (v[mid] < n) // 要查找的数字在中间值的右边,则使左下标为中间值+1
{
left = mid + 1;
}
else if (v[mid] > n)
{
right = mid - 1;
}
else
{
return mid;
}
}
if (left > right)
{
return -1;
}
}

int main()
{
int v[10] = { 1,2,3,4,5,6,7,8,9,10 };// 假设有序数组为1-10
int len = sizeof(v) / sizeof(v[0]); // 数组长度
int n = 0;
scanf("%d", &n);// 输入要查找的数字
int mid = BinarySearch(n, v, len);
if (mid != -1)
{
printf("数组的下标为:%d\n", mid);
}
else
{
printf("ERROR");
}
return 0;
}

调用次数

  • 写一个函数,每调用一次这个函数,就会将num的值增加1。

传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式。

也就是说想要在函数内部改变外部变量的值,就要将外部变量的地址传进函数内部,直接在函数内部修改外部变量的值,因为有他的地址。

这种传参方式可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操作函数外部的变量。

有了以上概念就很容易写出以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>

void add(int* p)
{
(*p)++;
}

int main()
{
int num = 0;
printf("%d\n", num); //打印0
add(&num);
printf("%d\n", num); //打印1
add(&num);
printf("%d\n", num); //打印2
add(&num);
printf("%d\n", num); //打印3
return 0;
}

image-20240128124418420