文章目录
写在前面
题目:中南大学计算机学院2023秋季C语言期中考考题(共六道,本文分享前三道的思路)
旨在分享自己的思考过程和一点经验,并给出一种输出正确的答案供大家讨论批判,有任何批评、问题可以直接在评论区留言或者直接私信告诉我,大家共同进步。
文字主要面向新手
A:个税计算——阅读理解与数据类型转换
原题
小南毕业工作了,他想知道自己每年的扣税情况。他查到的综合所得的个人所得税税率表如下:
级数 | 全年应纳税所得额 | 税率(%) | 速算扣除数 |
1 | 不超过36000元的部分 | 3 | 0 |
2 | 超过36000-144000元的部分 | 10 | 2520 |
3 | 超过144000-300000元的部分 | 20 | 16920 |
4 | 超过300000-420000元的部分 | 25 | 31920 |
5 | 超过420000-660000元的部分 | 30 | 52920 |
6 | 超过660000-960000元的部分 | 35 | 85920 |
7 | 超过960000元的部分 | 45 | 181920 |
你能根据小南的收入算出他每年所要缴纳的税款吗?
输入
多个样例。每个样例输入一个整数m(104≤m≤107)代表小南的年终收入,为100的整数倍。
输出
每个样例输出一个整数,表示小南缴纳的个税。每个样例输出结果占一行。
样例输入
20000
36000
400000
660000
样例输出
600
1080
68080
145080
题目分析
这题有同学拿到手,没听过速算扣除数,不知怎么算。有同学知道了怎么算,提交只有80%,我们来看看。
题目理解
其实速算扣除数不论有没听过,怎么看都是要减去这个数的,怎么减?乘以税率之前还是之后?还有超过“的部分”,是不是要单独每部分算?其实这些问题全可以靠样例来解决——当题目关于计算方法、计算公式等没有用详尽的文字表述时,不需要太纠结,从样例输入输出可以判断出计算方法。
比如这题,大可以猜一猜怎么算,第一组第二组明显是20000×0.03,36000×0.03,没有问题。第三组400000,先别想太多,400000×0.25是100000,而结果是68080,也该看出来了,是100000减去对应的速算扣除数。
现在明白了,就是简单的乘以税率减去扣除数,带入第四组按一下计算器,没有问题。
代码实现与问题解决
大致涉及到这些知识:
- 区间数if语句判断
- 精度问题和解决
我们一个个看
-
if语句判断区间
这时候有一种思想,按顺序判断,承上启下结构。什么意思呢?先判断是否大于A,else if是否大于B,此时由于else if只会在前一个if语句为假的时候判断,所以当判断这个“是否大于B”,就隐含了“不是大于A”的前提,也就是大于B,并且小于等于A。简单的思想,自己写过一次就能知道了。
好处就是不用再写 大于B&&小于等于A了,结构也清晰。
具体代码就是长这样:if(n > 960000){ t = n * 0.45 - 181920; }else if(n > 660000){ t = n * 0.35 - 85920; }else if(n > 420000){ t = n * 0.30 - 52920; }else if(n > 300000){ t = n * 0.25 - 31920; }else if(n > 144000){ t = n * 0.20 - 16920; }else if(n > 36000){ t = n * 0.10 - 2520; }else{ t = n * 0.03; }
-
精度问题和解决
有同学看到百分比,下意识用了小数处理,这就要注意精度问题了。例如这样写,就会遇到错误80%,有的数算出差1:
#include <stdio.h>
int main(){
int n;
while(scanf("%d", &n) != EOF){
int t;
if(n > 960000){
t = n * 0.45 - 181920;
}else if(n > 660000){
t = n * 0.35 - 85920;
}else if(n > 420000){
t = n * 0.30 - 52920;
}else if(n > 300000){
t = n * 0.25 - 31920;
}else if(n > 144000){
t = n * 0.20 - 16920;
}else if(n > 36000){
t = n * 0.10 - 2520;
}else{
t = n * 0.03;
}
printf("%d\n", t);
}
return 0;
}
这是因为在整数、小数之间转换的时候,会丢失精度,此时可以把t声明成float,输出使用%.0f既可。
当然,脑子转的快的同学看到题目中的“100的整数倍”,下意识的除以100,这是最好的,也就是在积算的时候先除以100,此时得到的一定是精确的整数,再乘以百分比,这样就不会损失精度了。
我的代码
#include <stdio.h>
int main(){
int n;
while(scanf("%d", &n) != EOF){
float t;
if(n > 960000){
t = n * 0.45 - 181920;
}else if(n > 660000){
t = n * 0.35 - 85920;
}else if(n > 420000){
t = n * 0.30 - 52920;
}else if(n > 300000){
t = n * 0.25 - 31920;
}else if(n > 144000){
t = n * 0.20 - 16920;
}else if(n > 36000){
t = n * 0.10 - 2520;
}else{
t = n * 0.03;
}
printf("%.0f\n", t);
}
return 0;
}
或者:
#include <stdio.h>
int main(){
int n;
while(scanf("%d", &n) != EOF){
int t;
if(n > 960000){
t = n / 100 * 45 - 181920;
}else if(n > 660000){
t = n / 100 * 35 - 85920;
}else if(n > 420000){
t = n / 100 * 30 - 52920;
}else if(n > 300000){
t = n / 100 * 25 - 31920;
}else if(n > 144000){
t = n / 100 * 20 - 16920;
}else if(n > 36000){
t = n / 100 * 10 - 2520;
}else{
t = n / 100 * 3;
}
printf("%d\n", t);
}
return 0;
}
B:时制转换——问题是一点点解决的
原题
12小时制和24小时制都是一种记录时间的方式,两者的区别在于:
(1)12小时制是把一天二十四小时分为上午和下午两个十二小时的时间段,上午从午夜12:00到上午11:59,后缀标记am或者AM,下午从正午12:00到晚上11:59,后缀标记pm或者PM。
(2)24小时制是把一天二十四个小时从午夜开始用00:00~23:59表示。
(3)使用12小时制显示时间时中午12点显示为12:00 pm或者12:00 PM,而将午夜显示为12:00 am或者12:00 AM。
小南的任务:将12小时制格式显示的时间转换为24小时制格式显示。
输入
多个样例。每个样例输入1行形式为hh:mmcd的数据,其中整数hh(00≤hh≤12)代表小时,整数mm(00≤mm≤59)代表分钟,cd是字母组合“am、AM、pm、PM”中的一个,分别表示上午(am或AM)和下午(pm或者PM)。
输出
每个样例输出新的24小时制格式的时间,形式为hh:mm。每个样例输出结果占一行。
样例输入
12:00am
12:59PM
11:09pm
03:02AM
04:18PM
样例输出
00:00
12:59
23:09
03:02
16:18
题目分析
拿到这个题目,大概脑子里会有两个问题:怎么读入数据?怎么转换?我们一个个看。
我们先读入数据:整数+冒号+整数+一个字符+一个字符。怎么读?%d:%d%c%c
这样就行了!C语言先识别一个整数,遇到非数字后停止(这里是遇到了“:”,它发现这个不是数字,又把它放回去了),然后找一个冒号,再找一个整数,同理遇到p、P、a、A后停止。最后读两个字符。
这样分别存到四个变量后,就可以开始核心部分了。
怎么转换?转换就是把不同的东西变成相同的,什么不同?12小时和24小时最大的不同就在下午,也就是中午12点过后(pm),下午4点就是16点,下午9点就是21点——明显是+12。
这下明白了:只要pm(或者PM),把小时数加12就行了。
于是乎写出代码:
#include <stdio.h>
int main(){
char a, b;
int h, m;
while(scanf("%d:%d%c%c", &h, &m, &a, &b) != EOF){
if(a == 'p' || a == 'P'){
h += 12;
}
printf("%02d:%02d\n", h, m);
}
return 0;
}
但是看看样例就知道出了问题:
- 12:00am应当是00:00,怎么办?好说,如果小时数是12,而且是am,那就变成0——除此之外,am都和24小时的小时数是一样的,比如早上3点就是3:00,这是一致的,除了12:00am。
- 12:00pm应当是12:00,怎么办?好说,当是pm,小时数不是12的时候,再加12就行了。
我们说问题是一点点解决的,虽然这个问题脑子转得快的同学第一次看题就能看出来,但是如果一次想不到也没有关系,通过一步步修改、调试来完善代码,最后也能达到正确答案,这正是遇到复杂问题的解决方法。当然在不断的训练中也要有意识的想一想:漏了什么?想全了吗?有没有别的情况?尽量在测试之前自己找出一些问题,或者尽量提前预料到问题,提前规避。比如这一题,在题目中强调了12点的问题,是应当加以留意的。
我的代码
#include <stdio.h>
int main(){
char a, b; // 存储pm、am
int h, m; // 存储小时、分钟(分钟是没有用的)
while(scanf("%d:%d%c%c", &h, &m, &a, &b) != EOF){ // 读入数据
if((a == 'a' || a == 'A') && h == 12){ // 如果是am,并且小时数是12的话,小时数转成0
h = 0;
}
if((a == 'p' || a == 'P') && h != 12){ // 如果是pm,而且小时数不是12的话,小时数加12
h += 12;
}
printf("%02d:%02d\n", h, m); // 输出,%02d是用0来补位到两位
}
return 0;
}
C:统计进位——人教版小学二年级数学上册第二单元《进位加》的C语言实现
原题
老师发现很多学生在进行加法的时候,发现“进位”特别容易出错。于是交给小南一个任务,就是统计两个整数x和y在相加时需要多少次进位,其中x和y满足0≤x,y≤ 109,你能帮他完成任务吗?
输入
多个样例。 每个样例输入1行,包括2个整数x和y(0≤x,y≤ 109)。
输出
每个样例输出一个整数,代表所需要的进位数。每个样例输出结果占一行。
样例输入
8 7
9 0
154 246
555 5555
123 594
样例输出
1
0
2
3
1
题目分析
先来看看进位是什么:根据人教版小学二年级数学上册第二单元《进位加》知识可得,加法列竖式的时候,每一位相加(上一位有进位的还要加上上一位的进位1)大于10就是进一次位,以此类推。
例如9234+928:(从右看到左)
注意两点:
- 后一次相加要加上前一次的进位1(如果前一次有进位),前一次没有进位可以看作加上0
- 最后一位位数不足,可以补上0思考,比如上述例子928可以看作0928
很简单对吧!我的思路就是把列竖式的过程完完整整地搬到代码中。来看看吧!
数据读入与存储
首先想到的是需求,我要把两个数字右对齐“列竖式”,然后一位一位加。此时想想用int变量存储可以吗?当然可以,但是有点麻烦——每一位的提取很麻烦!看看这些对齐的数像什么?其实实质上就是一个二维数组,当然这里只有两行(只有两个数相加),用两个长度相同的数组也是可以滴。
此时用两个数组存储两个数的每一位就是第一步。当然可以用int数组,也可以用字符串数组,根据输入,用字符串显然方便一点:scanf("%s %s", a, b)
就行了!
至于字符怎么转数字,也是一个经典用法,教材中也有提到:C语言的字符是用整型存储的,'0'
就是48(在ASCII码下),'1'
也就是49了,自然,用数字的字符减去'0'
,就是这个数的int型了!这样我们得到了两个数组,它们是这样的:
准备列竖式吧!
显然,这些数的长度不尽相同,默认读入的顺序明显是左对齐,这时候按照小学二年级的知识,我们要对它们进行右对齐:
明确一下数组的长度,题目要10位,保险起见设为100位吧!(doge)就可以写代码了(代码的功能就是上图的转换,应该是能明白的):
// 这里a1数组是a数组转换(右对齐)后的数组,b1同理
for (int i = 0; i < alen; i++) { // alen是用strlen函数获取的a数组的实际长度(也就是数字的长度,例如9234长度是4)
a1[99 - i] = a[alen - i - 1];
}
for (int i = 0; i < blen; i++) {
b1[99 - i] = b[blen - i - 1];
}
这种代码的核心就是带数字,往里带入几位试一试,就可以推出应该写什么了。比如a1的最后一位应该等于a的第4位,倒数第二位应该等于a的第3位,也就是a1[99] = a[3]
,a1[98] = a[2]
(注意数组的索引从0开始)。然后带入a、b的数组长度和变量i(i是从零开始增长的):a[99 - i] = a[alen - 1 - 1]
,数组的索引就是这样代入出来的。
好了,可以开始相加了。思考列竖式的过程,明确一下需要哪些变量:
- 存储两数再加进位1(如果有的话)的变量k(就是判断k是否大于等于10的)。
- 进位c,第一次加法c = 0,如果k大于等于0,c赋值为1,否则赋值为0,下一次算k的时候加上c就行了。
想一想,竖式从右到左的加法用什么实现?明显是for循环,循环多少次呢?观察列竖式的过程,不难看出,4位加3位,总共加了4次(包括最后一位加进位1)。想一想,4位加4位,总共加了4次;4位加10位,总共加了5次。——规律很清晰,如果两数位数相等,那就是循环次数就是两数位数;如果不相等,就是最短位数+1。
当然,你可以直接把所有列表中所有数字都循环遍历一遍,等等,是不是想到什么问题?——早该想到的!前面两个数不一样长的时候就该想到了——要保证数组中除了输入的数字外,其他都是'0'
!这就需要在最开始时初始化a1、b1两个数组。这样就能保证不漏、不多,因为0+0不可能使得进位次数增多。
初始化是这样的:
for (int i = 0; i < 100; i++){
a1[i] = '0';
}for (int i = 0; i < 100; i++){
b1[i] = '0';
}
为什么不能char a1[100] = {'0'}
?答案是这样只会把第一项令为'0'
,但是其余项会令成ASCII码为0的空字符'\0'
,这不是我们想要的。此时用循环才是保险。
竖式的加法计算
现在列好了竖式,开始计算吧!原理在上面陈述的很明白了,代码是这样的:
int k, c = 0, s = 0; // k、c上面说过了,s是存储进位次数的,也就是题目要输出的答案
for (int i = 99; i >= 0; i--) { // 从最后一位开始循环,遍历整个列表(懒了,不想做判断了)
k = a1[i] - '0' + b1[i] - '0' + c; // 计算k值,注意最后加上进位c,第一次计算的时候进位c为0
if (k >= 10) { // 如果算出来大于等于10
s++; // 统计进位次数+1
c = 1; // 进位1
} else { // 如果算出来在10以内
c = 0; // 进位0
}
}
结束了!我本题的思路就是模拟列竖式的方式进行计算。考试的时候中间犯了几次错误:
- 转换右对齐的时候没整明白,把顺序颠倒了,9234对齐到右边变成4329了
- 没有初始化a1、b1,导致错误
当然遇到错误,先不要慌张,通过分段调试代码、找出原因、修改,都是可以解决的,问题是一点点解决的嘛。
我的代码
#include <stdio.h>
#include <string.h>
int main()
{
char a[100], b[100], a1[100], b1[100];
while (scanf("%s %s", a, b) != EOF) {
for (int i = 0; i < 100; i++) {
a1[i] = '0';
}
for (int i = 0; i < 100; i++) {
b1[i] = '0';
}
int alen = strlen(a); // 注意使用strlen要#include <string.h>
int blen = strlen(b);
// 这里a1数组是a数组转换(右对齐)后的数组,b1同理
for (int i = 0; i < alen; i++) { // alen是用strlen函数获取的a数组的实际长度(也就是数字的长度,例如9234长度是4)
a1[99 - i] = a[alen - i - 1];
}
for (int i = 0; i < blen; i++) {
b1[99 - i] = b[blen - i - 1];
}
// 分位次加法
int k, c = 0, s = 0; // k、c上面说过了,s是存储进位次数的,也就是题目要输出的答案
for (int i = 99; i >= 0; i--) { // 从最后一位开始循环,遍历整个列表(懒了,不想做判断了)
k = a1[i] - '0' + b1[i] - '0' + c; // 计算k值,注意最后加上进位c,第一次计算的时候进位c为0
if (k >= 10) { // 如果算出来大于等于10
s++; // 统计进位次数+1
c = 1; // 进位1
} else { // 如果算出来在10以内
c = 0; // 进位0
}
}
printf("%d\n", s);
}
return 0;
}