- 1、写两个函数,分别求两个整数的最大公约数和最小公倍数,用主函数调用这两个函数,并输出结果。两个整数由键盘输入。
- 的根,用 3 个函数分别三种情况下的根并输出结果,从主函数输入 a,b,c 的值。">2、求方程
的根,用 3 个函数分别三种情况下的根并输出结果,从主函数输入 a,b,c 的值。
- 3、写一个判素数的函数,在主函数输入一个整数,输出是否为素数的信息。
- 4、写一个函数,使给定的一个 3×3 的二维整型数组转置,即行列互换。
- 5、写一个函数,使输入的一个字符串按反序存放,在主函数中输入和输出字符串。
- 6、写一个函数,将两个字符串连接。
- include
- define MAXSIZE 100
- 7、写一个函数,将一个字符串中的元音字母复制到另一字符串,然后输出。
- 8、写一个函数,输入一个4位数字,要求输出这4个数字字符,但每两个数字间空一个空格。如输入1990,应输出“1 9 9 0”
- 9、编写一个函数,由实参传来一个字符串,统计此字符串中字母、数字、空格和其他字符的个数,在主函数中输入字符串以及输出上述的结果。
- 10、写一个函数,输入一行字符,将此字符串中最长的单词输出
- 11、写一个函数,用“起泡法”对输入的
10
个字符按由小到大顺序排列。 - ,系数a,b,c,d的值依次为1,2,3,4,由主函数输入。求x在1附近的一个实根。求出根后由主函数输出。">12、用牛顿迭代法求根。方程为
,系数a,b,c,d的值依次为1,2,3,4,由主函数输入。求x在1附近的一个实根。求出根后由主函数输出。
- 13、用递归方法求 n 阶勒让德多项式的值,递归公式为
- 14、输入
10
个学生5
门课的成绩,分别用函数实现下列功能: - 15、写几个函数:
- include
- include
- define N 10 // 10名员工
- 17、用递归法将一个整数n转换成字符串。例如,输入483,应输出字符串“483”。n的位数不确定,可以是任意位数的整数。
- 18、给出年、月、日,计算该日是该年的第几天。
:::tips 习题很多都是重复之前的题目,要用函数实现而已,虽然显得多此一举,题目就是题目,没有凸显函数设计的目的。 :::
1、写两个函数,分别求两个整数的最大公约数和最小公倍数,用主函数调用这两个函数,并输出结果。两个整数由键盘输入。
解题思路:使用辗转相除法求出 gcd
,根据公式 得出
lcm
。答案中无非用全局变量替代函数传参实现,两者都是一致的,减少使用全局变量进行传递是一个很好的习惯。
#include <stdio.h>
int gcd(int a, int b) {
while (b != 0) {
int tmp = a;
a = b;
b = tmp % b;
}
return a;
}
int lcm(int a, int b, int _gcd) {
return a * b / _gcd;
}
int main () {
printf("Please input two number:");
int a, b;
scanf("%d %d", &a, &b);
int _gcd = gcd(a, b);
printf("gcd: %d\n", _gcd);
printf("lcm: %d\n", lcm(a, b, _gcd));
return 0;
}
编译运行:
:::success
b12@PC:~/chapter7$ gcc -Wall ./src/gcdLcm.c -o ./bin/gcdLcm
b12@PC:~/chapter7$ ./bin/gcdLcm
Please input two number:24 16
gcd: 8
lcm: 48
:::
2、求方程
的根,用 3 个函数分别三种情况下的根并输出结果,从主函数输入 a,b,c 的值。
解题思路:关键就是判断 与
0
的关系,然后进行函数调用(参考答案好牵强的函数调用)。需要注意的就是分母一定要用括号括起来 (2 * a)
#include <stdio.h>
#include <math.h>
double x1, x2, p, q, delta;
void gt(double a, double b) {
double disc = sqrt(delta);
x1 = (-b + disc) / (2 * a);
x2 = (-b - disc) / (2 * a);
}
void eq(double a, double b) {
x1 = x2 = -b / (2 * a);
}
void lt(double a, double b) {
p = -b / (2 * a);
q = sqrt(-delta) / (2 * a);
}
int main () {
printf("Please input three numbers:");
double a, b, c;
scanf("%lf %lf %lf", &a, &b, &c);
delta = b * b - 4 * a * c;
if (delta > 0) {
gt(a, b);
printf("x1=%f x2=%f\n", x1, x2);
} else if (delta < 0) {
lt(a, b);
printf("x1=%f+%fi x2=%f-%fi\n", p, q, p, q);
} else {
eq(a, b);
printf("x1=x2=%f\n", x1);
}
return 0;
}
编译运行:
:::success
b12@PC:~/chapter7$ gcc -Wall ./src/sqrt.c -o ./bin/sqrt -lm
b12@PC:~/chapter7$ ./bin/sqrt
Please input three numbers:2 4 1
x1=-0.292893 x2=-1.707107
b12@PC:~/chapter7$ ./bin/sqrt
Please input three numbers:1 2 1
x1=x2=-1.000000
b12@PC:~/chapter7$ ./bin/sqrt
Please input three numbers:2 4 3
x1=-1.000000+0.707107i x2=-1.000000-0.707107i
:::
3、写一个判素数的函数,在主函数输入一个整数,输出是否为素数的信息。
解题思路:判断素数方法如下论证,不再累述为什么答案可以使用 的原因。
课后习题
然后这里再使用我们之前手动实现的 sqrt
函数。
69.x的平方根(简单)
#include <stdio.h>
#include <math.h> // fabs
double mySqrt(int x){
if (x == 0) return 0;
// 利用牛顿迭代法进行
double C = x, X0 = x;
while (1) {
double X1 = 0.5 * (X0 + C / X0);
if (fabs(X0 - X1) < 1e-7) {
break;
}
X0 = X1;
}
return X0;
}
int prime(int a) {
int disc = (int)mySqrt(a);
for (int i = 2; i <= disc; i++) {
if (0 == a % i) {
return 0;
}
}
return a != 1; // 1不是质数
}
int main () {
printf("Please input a number:");
int a;
scanf("%d", &a);
if (prime(a)) {
printf("%d is a prime\n", a);
} else {
printf("%d is not a prime\n", a);
}
return 0;
}
编译运行:
:::success
b12@PC:~/chapter7$ gcc -Wall ./src/prime.c -o ./bin/prime
b12@PC:~/chapter7$ ./bin/prime
Please input a number:1
1 is not a prime
b12@PC:~/chapter7$ ./bin/prime
Please input a number:17
17 is a prime
b12@PC:~/chapter7$ ./bin/prime
Please input a number:25
25 is not a prime
:::
4、写一个函数,使给定的一个 3×3 的二维整型数组转置,即行列互换。
解题思路:题目的转置是由 的方式而不是将矩阵旋转多少度(这种更难实现),即数字按主对角线对换(左下角和右上角对换即可),因此每行交换次数为
,这里减一是主对角线自身不需要对换。
#include <stdio.h>
#define N 3
void convert(int array[][3]) {
for (int row = 0; row < N; row++) {
// 第 row 行需要按对称轴旋转 N-1-row 个数字
for (int col = row + 1; col < N; col++) {
int tmp = array[row][col];
array[row][col] = array[col][row];
array[col][row] = tmp;
}
}
}
int main () {
int matrix[N][N];
printf("Please input %d numbers:", N * N);
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
scanf("%d", &matrix[i][j]);
}
}
printf("Original matrix:\n");
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
printf("%-5d", matrix[i][j]);
}
printf("\n");
}
printf("Convert matrix:\n");
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
printf("%-5d", matrix[i][j]);
}
printf("\n");
}
return 0;
}
编译运行:
:::success
b12@PC:~/chapter7$ gcc -Wall ./src/convert.c -o ./bin/convert
b12@PC:~/chapter7$ ./bin/convert
Please input 9 numbers:1 2 3 4 5
6 7 8 9 (tips:注意这里输入为什么可以换行,原因就是%d)
Original matrix:
1 2 3
4 5 6
7 8 9
Convert matrix:
1 2 3
4 5 6
7 8 9
:::
5、写一个函数,使输入的一个字符串按反序存放,在主函数中输入和输出字符串。
解题思路:字符数组和数组类似,原理一样。
课后习题
额外空间倒置填充和递归实现的方式:
PTA编程—数组
6、写一个函数,将两个字符串连接。
解题思路:本质上让你实现标准库函数: strcat(char *dest, char *src)
,出现在历年考研题目中。
- 实现三个数组,其中第三个长度一定要大与前两者之长。
- IO 两个字符串然后进行连接操作。
:::tips
如果不对数组进行初始化,那么一定要在最后拷贝的时候手动添加一个
'\0'
才可以。 ::: ```cinclude
define MAXSIZE 100
void concatenate(char str1, char str2, char *str3) { int idx = 0; for (int i = 0; str1[i] != ‘\0’; i++) { str3[idx++] = str1[i]; } for (int i = 0; str2[i] != ‘\0’; i++) { str3[idx++] = str2[i]; } str3[idx] = ‘\0’; // 如果数组进行初始化过且有效长度内则不变 }
int main () { char str1[MAXSIZE], str2[MAXSIZE], str3[MAXSIZE]; printf(“Please input str1:”); scanf(“%[^\n]s”, str1); getchar(); // 由于使用非标准的输入法,因此必须添加吸收\n printf(“Please input str2:”); scanf(“%[^\n]s”, str2); getchar(); concatenate(str1, str2, str3); printf(“str1 + str2 = %s\n”, str3); return 0; }
编译运行:
:::success
b12@PC:~/chapter7$ gcc -Wall ./src/strcat.c -o ./bin/strcat<br />b12@PC:~/chapter7$ ./bin/strcat<br />Please input str1:You are<br />Please input str2: the best one!<br />str1 + str2 = You are the best one!
:::
题目中的 `scanf("%s")` 是无法应对含有空格的字符串的。或者使用 `gets(str1);` 虽然编译收到警告,建议使用 `char *fgets(char *buf, int bufsize, FILE *stream);` 函数,然而当输入不足 `n` 字符前就遇到换行,该 `\n` 会被加入到 `buffer` 内;如果输入超过 `n` ,那么只会截取前 `n-1` 个字符。即是说不管你怎么输入,此函数永远预留一个 `'\0'` 给 `buffer` 数组。最大的麻烦就是输入不足 `n` 个任然将 `\n` 加入是无法**忍受**的。<br />例如将上述换成 `fgets(str1, MAXSIZE, stdin);` 进行编译运行,就会有 `\n`
:::success
b12@PC:~/chapter7$ gcc -Wall ./src/strcat.c -o ./bin/strcat<br />b12@PC:~/chapter7$ ./bin/strcat<br />Please input str1:You are<br />Please input str2: newline!<br />str1 + str2 = You are<br /> newline!
b12@PC:~/chapter7$
:::
解决办法就是判断字符串长度,然后在最后位置将 `\n` 删除掉。
```c
#include <stdio.h>
#include <string.h>
#define MAXSIZE 100
void concatenate(char *str1, char *str2, char *str3) {
int idx = 0;
for (int i = 0; str1[i] != '\0'; i++) {
str3[idx++] = str1[i];
}
for (int i = 0; str2[i] != '\0'; i++) {
str3[idx++] = str2[i];
}
str3[idx] = '\0'; // 如果数组进行初始化过且有效长度内则不变
}
int main () {
char str1[MAXSIZE], str2[MAXSIZE], str3[MAXSIZE];
printf("Please input str1:");
fgets(str1, MAXSIZE, stdin);
str1[strlen(str1)-1] = '\0';
printf("Please input str2:");
fgets(str2, MAXSIZE, stdin);
str2[strlen(str2)-1] = '\0';
concatenate(str1, str2, str3);
printf("str1 + str2 = %s\n", str3);
return 0;
}
:::success
b12@PC:~/chapter7$ gcc -Wall ./src/strcat.c -o ./bin/strcat
b12@PC:~/chapter7$ ./bin/strcat
Please input str1:You are
Please input str2: the newline!
str1 + str2 = You are the newline!
:::
7、写一个函数,将一个字符串中的元音字母复制到另一字符串,然后输出。
解题思路:元音字母大小写判断即可,复制字符串后需要留意字符数组中是否进行初始化,没有进行初始化必定要手动在最后加上 '\0'
,否则非法访问。
:::tips
为什么作者这里要使用 gets()
函数而不是 scanf
,任然是输入可能存在空格的字符串。而使用 fgets
在输入不满足 N
字节长之前遇到 \n
不会像 gets()
一样将 '\n'
从缓冲区扔出去,而是直接放入 buffer
内。
:::
#include <stdio.h>
#define N 100
void filterVowels(char dest[], char src[]) {
int idx = 0;
for (int i = 0; '\0' != src[i]; i++) {
if ('a' == src[i] || 'A' == src[i] ||
'e' == src[i] || 'E' == src[i] ||
'i' == src[i] || 'I' == src[i] ||
'o' == src[i] || 'O' == src[i] ||
'u' == src[i] || 'U' == src[i]) {
dest[idx++] = src[i];
}
}
dest[idx] = '\0';
}
int main () {
char str1[N], str2[N];
printf("Please input string:");
fgets(str1, N, stdin); // 注意会将\n写入str1
filterVowels(str2, str1);
printf("The vowel letters are %s\n", str2);
return 0;
}
编译运行:(注意下面有空格的输入字符串)
:::success
b12@PC:~/chapter7$ gcc -Wall ./src/filterVowels.c -o ./bin/filterVowels
b12@PC:~/chapter7$ ./bin/filterVowels
Please input string:abcAe abcAeIj
The vowel letters are aAeaAeI
:::
8、写一个函数,输入一个4位数字,要求输出这4个数字字符,但每两个数字间空一个空格。如输入1990,应输出“1 9 9 0”
解题思路:注意为什么要从最后开始向前进行操作,如果从左向右必定导致后面的数据被覆盖,而数组足够长从后往前一定不会覆盖前者。对于原数组后 个字符(不包含
\0
),全部转换为 x
前缀空格+字符共需要 长度,而第一个字符原地不动。
:::tips
参考答案中先将最后的
\0
移动到对应的位置,然后前面加上了一个空格,很明显是多余的,不满足两数字之间有一个空格。
:::
#include <stdio.h>
#include <string.h>
#define N 100
void spaceJoin(char s[]) {
for (int i = strlen(s); i > 0; i--) {
s[2*i] = s[i];
s[2*i-1] = ' '; // 前置空格
}
}
int main () {
char str[N];
printf("Please input string:");
scanf("%s", str);
spaceJoin(str);
printf("Output %s??\n", str); // 验证后面是否有多余空格
return 0;
}
编译运行:注意看输出后面是有多余空格的
:::success
b12@PC:~/chapter7$ gcc -Wall ./src/spaceJoin.c -o ./bin/spaceJoin
b12@PC:~/chapter7$ ./bin/spaceJoin
Please input string:1990
Output 1 9 9 0 ??
:::
解决办法也很简单,直接将原数组 单独处理即可。
void spaceJoin(char s[]) {
int n = strlen(s);
s[2*n-1] = '\0'; // 末尾的'\0'
for (int i = n - 1; i > 0; i--) { // 从最后一个字符开始
s[2*i] = s[i];
s[2*i-1] = ' '; // 前置空格
}
}
编译运行:注意看输出后面是无多余空格的
:::success
b12@PC:~/chapter7$ gcc -Wall ./src/spaceJoin.c -o ./bin/spaceJoin
b12@PC:~/chapter7$ ./bin/spaceJoin
Please input string:2020
Output 2 0 2 0??
:::
上述实现的条件是原数组足够长,所以可以在原数组上不断进行操作。但是通常实现 'fillchar'.join(str)
函数都是返回新的字符串。简单实现代码如下:(只允许 fillchar
为字符型,而非字符数组)。
#include <stdio.h>
#include <string.h>
#define N 100
void join(char *dest, char *src, char fillchar) {
int n = strlen(src);
dest[0] = src[0]; // 第一字符单独处理,其余' c'格式写入dest
for (int i = 1; i < n; i++) {
dest[2*i] = src[i];
dest[2*i-1] = fillchar;
}
}
int main () {
char str1[N], str2[2 * N]; // 注意是两倍长度
printf("Please input string:");
scanf("%s", str1); // 要求不含空格
getchar(); // 接受多余空格
printf("Please input fillchar:");
char fillchar = getchar();
join(str2, str1, fillchar);
printf("Output %s??\n", str2);
return 0;
}
编译运行:
:::success
b12@PC:~/chapter7$ gcc -Wall ./src/join.c -o ./bin/join
b12@PC:~/chapter7$ ./bin/join
Please input string:2046598790
Please input fillchar:&
Output 2&0&4&6&5&9&8&7&9&0??
:::
9、编写一个函数,由实参传来一个字符串,统计此字符串中字母、数字、空格和其他字符的个数,在主函数中输入字符串以及输出上述的结果。
解题思路:与之前数组的题目类似,出题非常牵强,将 letter
, digit
, space
, others
换成全局变量后就为了使得在函数内部实现变量的改变。参考答案在函数中修改,然后回到 main
函数打印修改的全局变量的值。这样做还不如如下直接实现在该统计函数内部直接打印值就得了,全局变量方便在其他函数内改变,但是非常危险。
PTA函数—指针
10、写一个函数,输入一行字符,将此字符串中最长的单词输出
解题思路:本题关键是如何分隔单词,标准的统计每个单词的长度然后记录最大值和其在数组中的位置,然后输出。(书本中错误统计,如果是字符就该直接 len++
,而不是在非第一次进行相加,虽然结果上是每个单词缺少头部并且其对于非字母单词也不是必须要求大于最长的才变为 len=0
而是只要是非字母单词就重置长度)。
另外一个就是后效性问题,往往因为最后一个单词不是因为空格结尾,所以每次循环结束后还需要再更新一次,否则会把最后的单词给漏掉。而作者就是使用 把最后一起考虑入内。
#include <stdio.h>
#define N 100
int isAlpha(char ch) {
return ('A' <= ch && ch <= 'Z') || ('a' <= ch && ch <= 'z');
}
int longestWord(char str[]) {
// 函数找到最长的单词然后返回其在数组中的起始位置
int isSpace = 1, idx = -1, start = -1, len = 0, maxLen = 0;
for (int i = 0; '\0' != str[i]; i++) {
if (isAlpha(str[i])) { // 如果当前是字母
if (isSpace) { // (1)若是开头
idx = i;
isSpace = 0;
}
len++; // 长度增加
} else {
isSpace = 1; // 遇到非字母
if (len >= maxLen) {
maxLen = len;
start = idx;
}
len = 0; // 清空单词长度
}
}
// 任然需要注意最后一个单词没有遇到空格而结束
if (len >= maxLen) {
maxLen = len;
start = idx;
}
return start;
}
int main() {
char str[N];
printf("Please input a string:");
fgets(str, N, stdin); // 虽然有\n,但是没关系
printf("The longest word is:");
for (int i = longestWord(str); isAlpha(str[i]); i++) {
putchar(str[i]);
}
putchar('\n');
return 0;
}
编译运行:
:::success
b12@PC:~/chapter7$ gcc -Wall ./src/longestWord.c -o ./bin/longestWord
b12@PC:~/chapter7$ ./bin/longestWord
Please input a string:I am a student.
The longest word is:student
b12@PC:~/chapter7$ ./bin/longestWord
Please input a string:You guess what happend
The longest word is:happend
:::
本质上有 strtok
函数可以替代,但是其会破坏字符串并且当两个相邻的分隔符在一起不会产生空串。
#include <stdio.h>
#include <string.h>
#define N 100
char *longestWord(char str[]) {
// 函数找到最长的单词然后返回其在数组中的起始位置
int maxLen = 0;
char *token = strtok(str, " "), *start; // 获取第一个子字符串
while (NULL != token) {
printf("%s\n", token);
if (maxLen <= strlen(token)) {
start = token; // 保留最长字符串起始地址
}
token = strtok(NULL, " "); // 获得后续子字符串
}
return start;
}
int main() {
char str[N];
printf("Please input a string:");
fgets(str, N, stdin); // 虽然有\n,但是没关系
printf("The longest word is: %s\n", longestWord(str));
printf("Original string: %s\n", str);
return 0;
}
编译运行:
:::success
b12@PC:~/chapter7$ gcc -Wall ./src/longestWord.c -o ./bin/longestWord
b12@PC:~/chapter7$ ./bin/longestWord
Please input a string:I am a student.
I
am
a
student.
(这里空格是fgets引起的)
The longest word is: student.
(这里空格是fgets引起的,最长单词因为分隔符是空格导致后续后多余的非字母)
Original string: I
b12@PC:~/chapter7$ ./bin/longestWord
Please input a string:I am a student
I
am
a
student
The longest word is: student
Original string: I
:::
11、写一个函数,用“起泡法”对输入的 10
个字符按由小到大顺序排列。
解题思路:同数字排序一样,因为字符本质也是 ASCII 码比较,因此搬用之前冒泡法即可。
(书本上错误示例:当用户输入超过 10
个字符时,字符数组早就溢出,它居然还用 strlen(str)
判断?这种情况下可以限制输入字符个数或者用 while (getchar())
函数统计先)
#include <stdio.h>
#include <string.h>
#define N 11
void bubbleSort(char str[]) {
int n = strlen(str);
for (int i = 0; i < n - 1; i++) { // 共比较 n-1 趟
for (int j = 0; j < n - i - 1; j++) { // 一趟打 n-i-1 人
if (str[j] > str[j+1]) { // 大的往后冒
char tmp = str[j];
str[j] = str[j+1];
str[j+1] = tmp;
}
}
printf("Round %d:%s\n", i + 1, str);
}
}
int main() {
char str[N];
printf("Please input %d char:", N - 1);
scanf("%10s", str); // 最多接受10个字符,然后外加'\0'
// fgets(str, N, stdin); // 不用的原因:有\n或者没有\n
printf("Original string: %s\n", str);
bubbleSort(str);
printf("Sort: %s\n", str);
return 0;
}
编译运行:(将具体交换细节打印出来)
:::success
b12@PC:~/chapter7$ gcc -Wall ./src/bubbleSort.c -o ./bin/bubbleSort
b12@PC:~/chapter7$ ./bin/bubbleSort
Please input 10 char:9876543210A # 多一位A,不会被读入
Original string: 9876543210
Round 1:8765432109
Round 2:7654321089
Round 3:6543210789
Round 4:5432106789
Round 5:4321056789
Round 6:3210456789
Round 7:2103456789
Round 8:1023456789
Round 9:0123456789
Sort: 0123456789
:::
12、用牛顿迭代法求根。方程为
,系数a,b,c,d的值依次为1,2,3,4,由主函数输入。求x在1附近的一个实根。求出根后由主函数输出。
解题思路:的导数为
,由
得知原函数单调递增,其图像如下所示
因此满足牛顿迭代要求连续的条件,并且从图中看到题目给假设 1
开始迭代是没错的。由牛顿迭代公式 得:
#include <stdio.h>
#include <math.h>
double solve(double a, double b, double c, double d) {
double x1, x0 = 1, f1, f0;
while (1) {
f0 = ((a * x0 + b) * x0 + c) * x0 + 4; // 函数值
f1 = (3 * a * x0 + 2 * b) * x0 + c; // 求导
x1 = x0 - f0 / f1;
if (fabs(x1 - x0) < 1e-3) break;
x0 = x1;
}
return x1;
}
int main() {
printf("Please input a, b, c, d:");
double a, b, c, d;
scanf("%lf %lf %lf %lf", &a, &b, &c, &d);
printf("x = %10.7f\n", solve(a, b, c, d));
return 0;
}
编译运行:
:::success
b12@PC:~/chapter7$ gcc -Wall ./src/solveFormula.c -o ./bin/solveFormula -lm
b12@PC:~/chapter7$ ./bin/solveFormula
Please input a, b, c, d:1 2 3 4
x = -1.6506292
:::
这里求导很机械,因为知道你预先输入什么,如果需要对任何函数进行求导,那么要进行很多判断,一般使用链表实现。
13、用递归方法求 n 阶勒让德多项式的值,递归公式为
解题思路:方程式已经给出,直接条件判断递归调用即可(体现函数入栈出栈)
#include <stdio.h>
double PnX(int n, double x) {
if (0 == n) {
return 1;
} else if (1 == n) {
return x;
}
return ((2 * n - 1) * x - PnX(n - 1, x) - (n - 1) * PnX(n - 2, x)) / n;
}
int main() {
printf("Please input n, x:");
double x;
int n;
scanf("%d %lf", &n, &x);
printf("P%d(%f) = %10.7f\n", n, x, PnX(n, x));
return 0;
}
编译运行:(书本上最后的大于等于 1
错误方程表达式)
:::success
b12@PC:~/chapter7$ gcc -Wall ./src/PnX.c -o ./bin/Pnx
b12@PC:~/chapter7$ ./bin/Pnx
Please input n, x:0 7
P0(7.000000) = 1.0000000
b12@PC:~/chapter7$ ./bin/Pnx
Please input n, x:1 2
P1(2.000000) = 2.0000000
b12@PC:~/chapter7$ ./bin/Pnx
Please input n, x:3 4
P3(4.000000) = 2.8333333
:::
14、输入 10
个学生 5
门课的成绩,分别用函数实现下列功能:
- 计算每个学生的平均分
- 计算每门课的平均分
- 找出所有
50
个分数中最高的分数所对应的学生和课程 - 计算平均分方差:
,其中
为某一学生的平均分。
解题思路:本题没有难度,全是体力活。用数组 double score[N][M]
记录所有人的成绩。
- 每个学生的平均分用数组
double averStu[N]
一个元素表示一个学生的 M 门科目的平均分,如下方图片黄色的列 - 每门课的平均分用数组
double averSub[M]
一个元素表示一个学生的 M 门科目的平均分,如下方图片橙色的行 - 找出所有
50
个分数中最高的分数所对应的学生和课程:关键要记录下所在成绩单的位置,使用全局变量row,col
记录。 - 平均分方差公式已给出,直接计算。
![WKNL$A$FX)QD1`6IS15LUR.png
#include <stdio.h>
#define N 10 // 10名学生,二维数组行
#define M 5 // 5门科目,二维数组列
double score[N][M]; // N行(人)成绩单,一行是该学生所有M门科目成绩
double averStu[N]; // N名学生所有M门科目平均分
double averSub[M]; // M门科目平均分
int row = 0, col = 0; // 最高学生成绩在成绩单中的位置
void studentAverage() {
/* 计算 N 名学生所有 M 门科目平均分:行平均值 */
for (int i = 0; i < N; i++) {
double aver = 0.0;
for (int j = 0; j < M; j++) {
aver += score[i][j];
}
averStu[i] = aver / M;
}
}
void subjectAverage() {
/* 计算 M 门科目平均分:列平均值 */
for (int j = 0; j < M; j++) {
double aver = 0.0;
for (int i = 0; i < N; i++) {
aver += score[i][j];
}
averSub[j] = aver / N;
}
}
void getHigh() {
/* 求出平均分最高的学生并修改全局变量行下标 */
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
if (score[i][j] > score[row][col]) {
row = i;
col = j;
}
}
}
}
double meanVariance() {
double x2Sum = 0.0, xSum = 0.0;
for (int i = 0; i < N; i++) {
x2Sum += averStu[i] * averStu[i];
xSum += averStu[i];
}
return (x2Sum / N - (xSum / N) * (xSum / N));
}
int main() {
// 1.从 stdin 输入数据
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
scanf("%lf", &score[i][j]);
}
}
// 2.计算 N 名学生 M 门科目平均分
studentAverage();
// 3.计算 M 门科目平均分
subjectAverage();
// 4.求出最高成绩(平均分最高)学生
getHigh();
printf("\nNo.\tcourse1\tcourse2\tcourse3\tcourse4\tcourse5\taverage\n");
// 5.输出 10名学生成绩 + 其平均分
for (int i = 0; i < N; i++) {
printf("No %d\t", i + 1);
for (int j = 0; j < M; j++) {
printf("%.2f\t", score[i][j]);
}
printf("%.2f\n", averStu[i]);
}
// 6.输出 M 门科目的平均分
printf("\nSubject average: ");
for (int j = 0; j < M; j++) {
printf("%.2f\t", averSub[j]);
}
// 7.输出最高成绩和数组中的位置
printf("\nHighest: %.2f\tNo. %d course %d\n",
score[row][col], row + 1, col + 1);
printf("avariance: %.2f\n", meanVariance());
return 0;
}
编译运行:(tips:运行过程中创建输入文件 score.txt
,然后使用 Linux 的管道命令,即 scanf
函数从 stdin
获得学成绩,甚至还可以将 stdout
重定向到文件内)
:::success
b12@PC:~/chapter7$ gcc -Wall ./src/scoreSheet.c -o ./bin/scoreSheet
b12@PC:~/chapter7$ cat ./src/score.txt | ./bin/scoreSheet
No. course1 course2 course3 course4 course5 average
No 1 87.00 88.00 92.00 67.00 78.00 82.40
No 2 88.00 86.00 87.00 98.00 90.00 89.80
No 3 76.00 75.00 65.00 65.00 78.00 71.80
No 4 67.00 87.00 60.00 90.00 67.00 74.20
No 5 77.00 78.00 85.00 64.00 56.00 72.00
No 6 76.00 89.00 94.00 65.00 76.00 80.00
No 7 78.00 75.00 64.00 67.00 77.00 72.20
No 8 77.00 76.00 56.00 87.00 85.00 76.20
No 9 84.00 67.00 78.00 76.00 89.00 78.80
No 10 86.00 75.00 64.00 69.00 90.00 76.80
Subject average: 79.60 79.60 74.50 74.80 78.60
Highest: 98.00 No. 2 course 4
avariance: 28.71
:::
15、写几个函数:
- 输入
10
个职工的姓名和职工号 - 按职工号由小到大顺序排序,姓名顺序也随之调整
- 要求输入一个职工号,用折半查找法找出该职工的姓名,从主函数输入要查找的职工号,输出该职工姓名。
解题思路:本例也是体力活,就是敲代码。使用两个数组对员工信息记录(因为数组元素必须是同一类型的,字符数组和员工整型 id 是不能混的。当学到结构体就可以创建一个包含 int id, char *name
的结构体)
- 职工号使用数组
int number[N]
记录,的姓名使用数组char name[N][8]
记录,因此两者对应的关系就是数组下标对应一个员工的信息。 - 按职工号由小到大顺序排序:意味着上面对应的数组
char name[N][8]
也要改变位置。 - 因为通过第
2
步进行排序后可以使用二分查找法进行查找int number[n]
的索引位置idx
,然后打印输出name[idx]
即可。 ```cinclude
include
define N 10 // 10名员工
void sort(int number[], char name[][N]) { / 使用选择排序对 number 内元素交换。同时改变 name 中位置 / char tmpStr[N]; for (int i = 0; i < N - 1; i++) { int idx = i; for (int j = i + 1; j < N; j++) { if (number[idx] > number[j]) { idx = j; // 记录此轮最小的 } } if (idx != i) { // 交换 int tmp = number[idx]; number[idx] = number[i]; number[i] = tmp; strcpy(tmpStr, name[idx]); // 因为二维数组不可以直接交换 strcpy(name[idx], name[i]); strcpy(name[i], tmpStr); } } }
int bisect(int number[], int left, int right, int target) { while (left <= right) { int mid = (right - left) / 2 + left; if (number[mid] < target) { left = mid + 1; } else if (number[mid] == target) { return mid; } else { right = mid - 1; } } return -1; }
int main() { int number[N], x; char name[N][N], choose; // 1.从 stdin 输入数据 for (int i = 0; i < N; i++) { scanf(“%d %9s”, number + i, name[i]); // 最长9字符 } sort(number, name); // 2.打印职工信息 for (int i = 0; i < N; i++) { printf(“%2d\t%s\n”, number[i], name[i]); // 最长9字符 } // 3.交互式查询,注意 %c 清空 while (1) { printf(“Please input number to look for:”); scanf(“%d”, &x); // 注意有\n留在缓冲区 int idx = bisect(number, 0, N - 1, x); if (-1 == idx) { printf(“No found\n”); } else { printf(“No.%d is %s\n”, x, name[idx]); } printf(“continue or not(Y/N):”); getchar(); // 重点!!! choose = getchar(); if (‘N’ == choose || ‘n’ == choose) break; } return 0; }
:::success
b12@PC:~/chapter7$ ./bin/employeeSheet<br />3 Li<br />1 Zhang<br />27 Yang<br />7 Qian<br />8 Sun<br />12 Jiang<br />6 Zhao<br />23 Shen<br />2 Wang<br />26 Han<br /> 1 Zhang<br /> 2 Wang<br /> 3 Li<br /> 6 Zhao<br /> 7 Qian<br /> 8 Sun<br />12 Jiang<br />23 Shen<br />26 Han<br />27 Yang<br />Please input number to look for:3<br />Search 3<br />No.3 is Li<br />continue or not(Y/N):y<br />Please input number to look for:13<br />Search 13<br />No found<br />continue or not(Y/N):n
:::
<a name="JQCQK"></a>
# 16、写一个函数,输入一个十六进制数,输出相应的十进制数。
解题思路:参考答案中分为两步,一是数据输入有效性检测,而是实现 `hex2dec` 函数。
1. IO 有效性检测:共有三个约束条件:
1. 输入结束
1. 合法字符判断: `0-9a-fA-F` ;非法字符,例如 `z`
1. 有效长度为 `1000` ,完全不可能这么长, `int` 早就溢出。最大的也不过是无符号整型 `0xffffffff` 长度也就是 `8` 位。

2. `hex2dec` 函数实现:使用秦久算法 .关键就是判断大小写字母问题(此处可以在上面IO 输入时候进行全部转化为小写)

```c
#include <stdio.h>
#define N 9 // 最大 unsigned int 对应16进制长度
unsigned int hex2dec(char s[]) {
unsigned int res = 0;
for (int i = 0; '\0' != s[i]; i++) {
if ('0' <= s[i] && s[i] <= '9') {
res = res * 16 + s[i] - '0';
} else if ('a' <= s[i] && s[i] <= 'f') {
res = res * 16 + (s[i] - 'a' + 10);
} else {
res = res * 16 + (s[i] - 'A' + 10);
}
}
return res;
}
int main() {
char s[N] = {'\0'};
while (1) {
printf("Please input hex number(>0):");
char ch = getchar(), choice;
int idx = 0, invalid = 0; // idx填充字符数组
while ('\n' != ch && idx < N) {
if (('0' <= ch && ch <= '9') ||
('a' <= ch && ch <= 'f') ||
('A' <= ch && ch <= 'F')) {
s[idx++] = ch;
} else {
invalid = 1; // 标记非法
break;
}
ch = getchar();
}
s[idx] = '\0'; // 由于连续输入,必须截断
if (invalid) { // 这里就不检测长度合法,直接截取
printf("Invalid character %c\n", ch);
} else {
printf("Decimal: %u\n", hex2dec(s));
}
printf("Continue or not(Y/N):");
choice = getchar(); // 注意之前的\n被接受
if ('n' == choice || 'N' == choice) {
break;
}
getchar(); // 吸收上面选择的换行
}
return 0;
}
编译运行:(书本上搞得那么麻烦的判断,真的无语,任何连续输入都有 while(1)
框架,它非要杂糅在一起)
:::success
b12@PC:~/chapter7$ gcc -Wall ./src/hex2dec.c -o ./bin/hex2dec
b12@PC:~/chapter7$ ./bin/hex2dec
Please input hex number(>0):a11
Decimal: 2577
Continue or not(Y/N):y
Please input hex number(>0):10
Decimal: 16
Continue or not(Y/N):y
Please input hex number(>0):f
Decimal: 15
Continue or not(Y/N):n
:::
17、用递归法将一个整数n转换成字符串。例如,输入483,应输出字符串“483”。n的位数不确定,可以是任意位数的整数。
解题思路:递归最重要的是找到递归链+终止点.最难的部分就是终止点. 例如本题:很容易想到被除数为 0
的时候就是结束点,但是事实上是不完全的,当输入数据是 0
,那么我们就漏下这个数字了,也就是它要同 do-while
循环一样,一定要执行一次。而改为 x / 10 == 0
判断即可解决这种问题。
#include <stdio.h>
void int2str(int n) {
if (0 != n / 10) {
int2str(n / 10);
}
putchar(n % 10 + '0'); // 后序遍历
putchar(' '); // 后序遍历
}
int main() {
int num;
printf("Please input a number:");
scanf("%d", &num);
if (num < 0) {
printf("- ");
int2str(-num);
} else {
int2str(num);
}
printf("\n");
return 0;
}
编译运行:
:::success
b12@PC:~/chapter7$ gcc -Wall ./src/int2str.c -o ./bin/int2str
b12@PC:~/chapter7$ ./bin/int2str
Please input a number:123456789
1 2 3 4 5 6 7 8 9
b12@PC:~/chapter7$ ./bin/int2str
Please input a number:-854137
- 8 5 4 1 3 7
b12@PC:~/chapter7$ ./bin/int2str
Please input a number:0
0
:::
18、给出年、月、日,计算该日是该年的第几天。
解题思路:这种题目可以直接使用Linux自带的 date
命令直接得到.
b12@PC:~/chapter7$ date --help
Usage: date [OPTION]... [+FORMAT]
or: date [-u|--utc|--universal] [MMDDhhmm[[CC]YY][.ss]]
Display the current time in the given FORMAT, or set the system date.
Mandatory arguments to long options are mandatory for short options too.
-d, --date=STRING display time described by STRING, not 'now'
--debug annotate the parsed date,
and warn about questionable usage to stderr
FORMAT controls the output. Interpreted sequences are:
%% a literal %
%a locale's abbreviated weekday name (e.g., Sun)
%A locale's full weekday name (e.g., Sunday)
%b locale's abbreviated month name (e.g., Jan)
%B locale's full month name (e.g., January)
%c locale's date and time (e.g., Thu Mar 3 23:05:25 2005)
%C century; like %Y, except omit last two digits (e.g., 20)
%d day of month (e.g., 01)
%D date; same as %m/%d/%y
%e day of month, space padded; same as %_d
%F full date; same as %Y-%m-%d
%g last two digits of year of ISO week number (see %G)
%G year of ISO week number (see %V); normally useful only with %V
%h same as %b
%H hour (00..23)
%I hour (01..12)
%j day of year (001..366)
b12@PC:~/chapter7$ date -d 20201014 +%j
288
但是实现起来也非常简单,从第一个月到第 n-1
月累加再加上第 n
个月第 x
天就是答案,但是需要判断闰年,因为二月多少一天关系到计算结果.
#include <stdio.h>
int sumDay(int year, int month, int day) {
int Month[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
if ((0 == year % 4 && 0 != year % 100) || 0 == year % 400) {
Month[2]++; // 闰年多一天
}
for (int i = 1; i < month; i++) {
day += Month[i];
}
return day;
}
int main() {
int year, month, day;
printf("Please input year, month, day:");
scanf("%d %d %d", &year, &month, &day);
printf("%d/%d/%d is the %d day in this year\n",
year, month, day, sumDay(year, month, day));
return 0;
}
编译运行:
:::success
b12@PC:~/chapter7$ gcc -Wall ./src/sumDay.c -o ./bin/sumDay
b12@PC:~/chapter7$ ./bin/sumDay
Please input year, month, day:2008 8 8
2008/8/8 is the 221 day in this year
b12@PC:~/chapter7$ date -d 20080808 +%j
221
b12@PC:~/chapter7$ ./bin/sumDay
Please input year, month, day:2001 7 9
2001/7/9 is the 190 day in this year
b12@PC:~/chapter7$ date -d 20010709 +%j
190
:::