一、选填部分
第一题:
下面四个选项中,均是不合法的用户标识符的选项是( )
A. A P_0 do
B. float la0 _A
C. b-a sizeof int
D. b_a temp _123
思路提示:题中所问的是"不合法"的"用户标识符",要记得,C语言中的关键字是不能作为用户标识符的~
答案:C
解析:根据C语言种标识符的规定来看:A选项中的P_0是合法的,do是关键字,非法。B选项种laO,_A是合法的,float是关键字,非法。C中b-a非法,因“-”不是标识符中的有效字符,sizeof和int均是关键字,非法。D中123、temp是合法的,int是关键字非法。故只有C全错,所以选择C。
第二题:
如果x=2014,下面函数的返回值是( )
int fun(unsigned int x)
{
int n = 0;
while (x + 1)
{
n++;
x = x | (x + 1);
}
return n;
}
思路提示:此题的考点是位操作符的知识点,题中的" | "符号是"或"操作符,它的作用是:两个数字的二进制形式上各位对比,只要有1就为1。而while循环中的 x = x | (x + 1) 所代表的是什么意思?大家要仔细思考一下~
答案:23。
解析:对于解题的关键,我们需要知道 x = x | (x + 1) 所代表的意思,让我们将此函数fun里带入2014,让我们观察一下2014在fun中的各种变化是什么样的:
我们可以看出,x从第二次往后都是以2的某次方减一的形式输出,而造成此等变化的内部原因,让我们转换成二进制来看一看。
x -> 00000000 00000000 00000111 11011110 (2014)
x+1 -> 00000000 00000000 00000111 11011111
x | (x + 1) = 00000000 00000000 00000111 11011111 (2015)
x -> 00000000 00000000 00000111 11011111
x+1 -> 00000000 00000000 00000111 11100000
x | (x + 1) = 00000000 00000000 00000111 11111111 (2047)
......
由此过程我们可以看出,x = x | (x + 1) 的作用是将数字的二进制形式从右往左第一个0变成1,而while的循环条件为(x + 1),也就是说当x的二进制位数全部为1,也就是x=-1时,才能跳出循环,而最开始x=2014,有9个1,(32 - 9) = 23次转换,n = 23。
第三题:
下面程序执行后输出结果为( )
#include<stdio.h>
int main()
{
int i, j, m = 6, n = 4, * p = &n, * q = &m;
i = p == &m;
j = (-*p) / (*q) + 7;
printf("i=%d,j=%d\n", i, j);
return 0;
}
思路提示:此题重点考察运算符优先级,解题的关键在于知道 i = p == &m 的运算顺序。知道了运算顺序自然就能轻松解题。
答案:i=0,j=7。
解析:i = p == &m;
从后结合,先计算 p==&m,p 显然是n,前后两者不相等,返回结果为了false,即是0,所以i=0;
j=(-*p)/(*q)+7;
j=(-n)/m+7,先计算除法,(-n)/m=0,j=0+7=7:所以i等于0,i等于7。
第四题:
在C语言中,以下代码执行之后,*p的值为( )
void func(int* p) {
static int num = 4;
p = #
(*p)--;
}
int main()
{
int i = 5;
int* p = &i;
func(p);
printf("%d", *p);
return 0;
}
思路提示:注意观察,思考一下形参指针和实参指针之间值的关联,并且需要注意,在一些情况下函数中的值在退出函数后会被销毁的。
答案:5
解析:当调用函数的时候,形参指针p会指向实参指针p所指向的地址空间。而其实修改形参p所指向的地址空间并不会影响实参指针p和i。这是因为:最开始形参指针p指向实参指针p所指向的地址空间,但在题目的 func 函数中,形参指针p 指向了新的地址空间num,如下图:
故修改形参p所指向的地址空间的值并不会影响实参指针p和i的值。
第五题:
下面代码的输出为( )
#define MAX(a,b)((a)>(b)?a:b)
int main()
{
int a = 5, b = 0;
int c = MAX(++a, b);
int d = MAX(++a, b + 10);
printf("%d %d %d %d\n", a, b, c, d);
return 0;
}
思路提示:注意观察define定义MAX后,调用MAX(++a,b)时,在函数MAX中的自增与赋值的先后顺序是什么?
答案:8 0 7 10
解析:第一次调用MAX时,++a先执行了一次,此时a为6,由于满足宏定义中(a)>(b)的条件所以执行a,这个a就对应++a,所以a又自增了一次,变为7,由此得出c为7。第二个MAX时a又自增了一次,此时为8,由于不满足条件,所以执行的是宏定义中的b,没有执行++a,所以a最终为8所以答案是8 0 7 10。注意:(++a) > b ? (++a) : b (++a先自增,后赋值)。
二、编程题部分
第一题:币值转换
输入一个整数(位数不超过9位)代表一个人民币值(单位为元),如23108元,转换后变成“贰万叁仟壹百零捌”元。a-j代表数字0-9,S、B、Q、W、Y代表拾、百、仟、万、亿。
输入样例:
813227345
输出样例:
iYbQdBcScWhQdBeSf
输入样例:
6900
输出样例:
gQjB
思路提示:这道题大家读完后或许觉得文思泉涌,想着只要判断了位数,每位输出对应大写字母,数组输出对应小写字母就好了嘛~但实则此题并没有看起来这么简单,对于很多的细节方面还是需要多多思考揣摩的。
① 我们在读数字的时候,需要从高位往低位读,如果用整形变量来接收数字的话,那么%10/10的操作只能从后往前取各位数,访问的时候注意需要逆向访问,还需要定义一个变量储存数字的位数。如果想使用数组来接收数字的话,那么整型数组无法做到将输入的一个数字,一位一位的存在每个元素,所以还需要使用字符型数组来接受,在后续操作中需要 - '0' (或者再创建一个整型数组,将每一个字符型 - '0'并存入,能简单一点儿)。(但反正就是咋做咋麻烦...)
② 有时我们需要读零,但有时候不能够读零,比如6901,读出来是六千九百零一,打印gQjBab,这种情况需要读零,但是6900,读出来就是六千九百,打印gQjB。
③ 正常的位数应该是十,百,千,万,十万,百万,千万,亿。而其中十万,百万,千万需要使用两个大写字母来表示。注意!并不是这三个位数都要加W的,而是截止到哪位,哪位后面才加W,比如11115000,读出来是一千一百一十一万五千,打印出来就是bQbBbSbWfQ,但如果是11005000,读出来就是一千一百万零五千,输出W的位数也要往前挪一位,打印出来就是bQbBWafQ。
答案:
int main()
{
int i;
int j;
char str1[11] = "abcdefghij";
char str2[10] = "0SBQWSBQY";
int num = 0;
scanf("%d", &num);
int N1 = num;//用来算长度的 就用了一次 不重要
int N2 = num;//用来把数字传进数组的 就用了一次 不重要
int sum = 0;//此数字的长度
do
{
sum++;
N1 /= 10;
} while (N1);
int arr[sum];//用于存储数字的每一位数
int numd = 0;//记录"万"以上位数的数字个数
int num4 = 0;//记录是否输出过"W"
for (i = 0; i < sum; i++)
{
arr[i] = N2 % 10;
if (sum - i <= 6 && arr[i] != 0)//最大位数 - i <= 6 代表是"万"以上位数的数字
numd++;
N2 /= 10;
}
for (i = sum - 1; i >= 0; i--)
{
if (sum == 1 && arr[i] == 0)//应对只输入0
printf("a");
else if (arr[i] != 0)
{
printf("%c", str1[arr[i]]);
if (str2[arr[i]] != '0' && i != 0)
{
printf("%c", str2[i]);
if (str2[i] == 'W')//题中只输出一次'W',若输出过了则记录,后续不再输出
num4++;//(因为正常来说 i=4 时 才输出'W',但有时arr[4]=0 就不会输出W)
if (i >= 3)//(但是例如10001000这类数字,虽然arr[4]=0,但输出需要有W)
numd--;//(所以我们记录是否输出了W,若遍历到i=4时仍未输出,则输出W)
}
}
else if (arr[i] == 0)
{
if (arr[i - 1] != 0 && i != 0)
printf("%c", str1[arr[i]]);
if (numd <= 0 && num4 == 0)
{
printf("%c", str2[4]);
num4++;
}
}
}
return 0;
}
解析:读完上面提示出的三个注意点,我们顺着这几条思路对代码进行功能编译。
① 我们先定义一个整形变量num来接收数字,再然后创建N1,N2,用来分别计算数字长度和将数字传进整型数组中。我们存进的数字正好是从低位到高位的逆序,而我们定义的存储位数大写字母的字符串str2是"0SBQWSBQY",在进行打印的时候也是逆序打印的,两者访问顺序一致,则用for循环的逆序遍历操作即可(在遍历时顺便记录大于万的位数中,非零的个数)。
② 我们在后续要打印结果字符串的时候,可以对0的后一位进行判断,就比如60001,读作六万零一,而60000读作六万,也就是说在0后一位也是0的时候,此0不读,但如果0后一位非零,那么此0需要读,条件也就是if (arr[i - 1] != 0 && i != 0)。
③ 在进行打印的时候,W位是一个比较特殊的位,他不像其他位数,只看对应数字是否为零就能决定输出与否,在输入的数字大于10000时,此数字的输出就必须带W,正常来说 i=4 时,就会输出'W',但有时arr[4]=0,就不会输出W,所以为了稳定的输出W,我们需要定义一个变量来记录W是否输出,如果在i = 4时W还没有输出,那么就自行输出W~
第二题:阅览室
编写一个简单的图书借阅统计程序,借书时输入S键,程序开始计时。还书时输入E键,程序结束计时。书号的范围必须是[1,1000],当以0作为书号输入时表示一天的工作结束。请你编写的程序能够输出当天的读者借书次数和平均阅读时间。
输入样例:
3
1 S 08:10
2 S 08:35
1 E 10:00
2 E 13:16
0 S 17:00
0 S 17:00
3 E 08:10
1 S 08:20
2 S 09:00
1 E 09:20
0 E 17:00
输出样例:
2 196
0 0
1 60
思路提示:此题还是稍微复杂的(仅代表本编程小白的个人观点...),而对于这类需要统计多种数据,并且每种数据息息相关的题,最好的方法是使用数组去做。而此题中变量过多,我们需要按照书号存储图书,并且记录书是否借出,是否归还,以及借书的时间长短!!!并且还不是一天,而是随着输入[1,10]来按照规定天数的管理阅览室,所以我的建议是使用二维数组来解题,否则需要定义很多种判断与储存变量,而且需要多重判断,过于麻烦。
此题看似复杂,实则也并不简单...有以下几个易错点需要注意。
① 当我们做完一天的工作后,需要记得将一天的数据清空。
② 此题中会出现(未借书就还书)和(未还书就借书)的情况,在编写代码的过程中我们不能忽略,需要对应的情况做出判断,跳过相应不存在的情况(如果光借书不还书,或光还书不借书,这种情况是不算在一天的借书者中的)。
③ 注意打印结果时,需要按照题中给定的形式打印,必须是(以分钟为单位,精确到个位的整数时间),故当借书次数不为0时,我们需要将打印的结果强制转换成浮点型(小数转整数)。
答案:
int main()
{
int num = 0;
int books[1005] = { 0 };//存储书号
int h;
int m;
scanf("%d", &num);
int N = num;//用于后续打印结果
int Sum[11] = { 0 };//储存每天借书的总时间
int Jie[11] = { 0 };//储存每天借书的总人数
int jj[11][1015] = { 0 };//通过储存数据的方法来判断此书是否被借走
while (num)
{
int n;
char s;
scanf("%d %c %d:%d", &n, &s, &h, &m);
if (n == 0)
{
num--;//进入下一天
continue;
}
if (s == 'S')
{
books[n] = h * 60 + m;//存入借书时间
jj[num][n]++;
}
else if (s == 'E' && jj[num][n] != 0)//判断此书是否被借走,若没被借走则无视还书操作
{
Sum[num] += (h * 60 + m) - books[n];//将(还书时间-借书时间)加给总时间
books[n] = 0;//数据置零
jj[num][n] = 0;//数据置零
Jie[num]++;//有借有还啦,统计的借书者+1
}
}
while (N)
{
if (Jie[N] != 0)//若借书者不为0
{
//以分钟为单位的精确到个位的整数时间
printf("%d %.0lf\n", Jie[N], (double)Sum[N] / Jie[N]);
}
else if (Jie[N] == 0)
printf("%d %d\n", Jie[N], Sum[N]);
//printf("0 0\n");其实也可以,反正借书者都是0啦,理所应当其他也都是0.
N--;//下一天
}
return 0;
}
解析:就如我们上面提到的解题思路一样,我们采用创建二维数组的方式来进行解题。首先我们已知题中的变量分别有:书号,借书总时间,借书总人数,以及我们还需要判断书的借还情况(容易被忽略)。我们分别定义三个一维数组来分别储存(书号)(借书总时间)(借书总人数),而书的借还情况需要与书号相关联,所以我们用二维数组来记录书的借还情况~
随后我们创建一个while循环结构作为最外层的循环,此循环代表天数,随后我们只需要在while循环中编写完成一天的阅览室工作就好啦~每一天开始的第一件事我们需要先判断,图书管理员输入的书号是否为0,如果刚开始就输入0那么代表这一天已经过去了,之后的任何事情都不需要处理。而后再分别对输入的操作是'S'还是'E'进行判断,如果是'S'的话代表读者借书,我们需要将时间存入事先创建好的(借书时间)中,并且标记此书已经被借走。
if (s == 'S')
{
books[n] = h * 60 + m;//存入借书时间
jj[num][n]++;
}
对于'E'的处理代表读者还书,首先我们需要判断,只有此书在此刻被借走了才能被还,否则此次不进行操作。然后我们将总借书时间记录下来,借书总人数+1,并且将各种数据置零。
else if (s == 'E' && jj[num][n] != 0)//判断此书是否被借走,若没被借走则无视还书操作
{
Sum[num] += (h * 60 + m) - books[n];//将(还书时间-借书时间)加给总时间
books[n] = 0;//数据置零
jj[num][n] = 0;//数据置零
Jie[num]++;//有借有还啦,统计的借书者+1
}
最后只需要在打印的时候判断借书总人数是否为零,再将非零时刻需要打印的数据转换成浮点型就ok啦~
if (Jie[N] != 0)//若借书者不为0
{
//以分钟为单位的精确到个位的整数时间
printf("%d %.0lf\n", Jie[N], (double)Sum[N] / Jie[N]);
}
(需要注意的是,printf("%.0lf",(double)Sum[N] / Jie[N]);的这种操作,会使得小数位进一,比如题中给出的测试集的第二天,总阅读时间除去总人数2,得到的结果其实是391.几的数字,按理来说391<392,如果使用%d打印,得到的结果应该是195,而我们要得到的是196,这就体现出了我们使用(%.0lf)以及将Sum[N]强制转换成浮点型的作用了~)
int main()
{
int a;
int num;
printf("请输入一个分子:\n");
scanf("%d", &a);
printf("请输入一个分母:\n");
scanf("%d", &num);
printf("化为浮点型的输出:%.0lf\n", (double)a / num);
printf("不化为浮点型的输出:%d\n", a / num);
return 0;
}
让我们来看这段代码~让我们用这段代码更加直观的观察一下转化为浮点型和不转化为浮点型,两者的主要差别~那么到这里这题也结束啦~
第三题:整除光棍
光棍指的是全部由1组成的数字,如11,111,1111等。任何一个光棍都能被不以5结尾的奇数整除,比如111111就能被13整除。你的程序要读入一个整数x,这个整数一定是奇数并且不以5结尾。然后,经过计算,输出两个数字:第一个数字s,表示x乘以s是一个光棍,第二个数字n是这个光棍的位数。这样的解当然不是唯一的,题目要求你输出最小的解。
输入一个不以5结尾的正奇数x(< 1000)
输入样例:31
输出样例:3584229390681 15
思路提示:此题看起来或许没有那么难,但我们要注意,题中仅仅输入31,就输出了这样长的一个数字,而输入的数字范围是[1,1000),由此可见...我们使用整形变量来存储题目中需要用到的数据是存不下的。其实我们可以尝试使用前两期中讲到的"大数加法"和"大数乘法"来模拟出一个"大数除法"进行解题。
既然是除法运算,那就让我们将思路转换到正常的数学除法。当我们运算时如果无法除尽,我们会采取"将余数提下来,并且乘以10让它能够继续运算"的方法。而对于这题我们也可以采取这种思路来进行运算。既然无法用变量存储数据,而用数组存储也显得过于繁琐,不妨我们尝试对每一位数字分别除以除数,若余数不为0则取余数后继续进行下一位运算,运算的同时记得将此位数字输出.
(因为我们要求的是,与num相乘能得到"光棍"的数,取余操作后再加1)
比如我们用13来举例~
① 我们先求出大于13的最小"光棍",也就是111。
② 然后用111除以13,输出商,然后取余数。
③ 处理余数,将余数乘以十再加一,而后循环往复,直到余数为0。
答案:
#include<stdio.h>
int main()
{
int num;
scanf("%d", &num);
int n = 1;//记录大于num的最小"光棍",后续计算每一位
int i = 0;//记录1的个数
while (n < num)
{
n = n * 10 + 1;//n后续再加一个1
i++;
}
while (1)
{
i++;
printf("%d", n / num);//输出n/num的商
n %= num;//记录n/num的余数(正常除法运算的思路)
if (n % num == 0)//余数为0则除尽了
break;//退出循环
else
n = n * 10 + 1;//在余数后加一个1,继续计算下一位
}
printf(" %d", i);//输出1的个数
return 0;
}
解析:明白了刚刚的思路提示的话,其实代码已经在脑子里呼之欲出了吧~此题只要按照通常除法运算步骤进行编写就好了,需要注意的是对于数据处理的一些细节:在寻找大于num的最小"光棍"时,while内部也要使i++,在得商取余的while循环中需要先输出商和计算余数,再用新的余数取判断是否除尽,颠倒顺序的话会导致程序比正常结束要晚。
第四题:有理数均值
本题要求编写程序,计算N个有理数的平均值。
输入格式:先输入一个正整数(<=100);第二行按照a1/b1 a2/b2...的格式给出N个分数形式的有理数,(分子分母必须都是整型范围的整数,如果使负数则负号一定出现在最前面)。
输出格式:N个有理数的平均值,必须是最简形式。
输入样例:
4
1/2 1/6 3/6 -5/10
输出样例:
1/6
思路提示:按照常理来说,只需要以此将所有的有理数进行相加减,得到最后的结果后再求出最大公约数,再同时除一遍就好了。但需要注意的是,此题和上一题都是算法题,并且都要计算较多的数据,正常的求分子和分母相加减的步骤应是如下:
a1/b1 + a2/b2
fz = a1 * b2 + a2 * b1
fm = b1 * b2
如:1/4 + 3/8
fz = 1 * 8 + 3 * 4 = 20
fm = 4 * 8 = 32
结果:20/32 -> 5/8
而此题中我们需要进行运算的有理数最多能有100个!!!进行100次分子分母相乘相加的运算会导致int型甚至long long型变量都无法存储得下,所以我们的"化简"操作就不能只在最后才操作一次,而是需要每进行一次有理数相加,就需要同时进行一次化简,这样才能防止无法存放。
既然需要多次使用"化简"操作,那么我们可以构建一个函数来实现化简操作,这样可以简化主函数的代码,并且看起来更加美观,思路更加连贯。
求最大公约数 方法一:辗转相除法
int NUM(int num1, int num2)
{
int n = num2;
while (num1 % num2)
{
n = num1 % num2;
num1 = num2;
num2 = n;
}
return n;
}
原理就是通过两个数相除后来回互换,最后完全除尽时返回的就是两个数的最大公约数,举例运算:
fz = 172 fm = 128
n = fz(172) % fm(128) = 44
fz = fm(128)
fm = n(44)
fz = 128 fm = 44
n = fz(128) % fm(44) = 40
fz = fm(44)
fm = n(40)
fz = 44 fm = 40
n = fz(44) % fm(40) = 4
fz = fm(40)
fm = n(4)
fz = 40 fm = 4
n = fz(40) % fm(4) = 0;
return n(4)
方法二:枚举法
我们先求出两者的最小值,然后使用for循环查找能够同时被二者整除的数字,使用整形变量N存储最大的公约数,最后返回N
int NUM(int num1, int num2)
{
int min = num1 < num2 ? num1 : num2;
int N = 0;
for (int i = 2; i <= min; i++)
{
if (num1 % i == 0 && num2 % i == 0)
{
N = i;
}
}
return N;
}
方法三:递归法
只是使用了递归的方法,具体思路和辗转相除法是相似的。
int NUM(int num1, int num2)
{
if (num2 == 0)
return num1;
else
return NUM(num2, num1 % num2);
}
(注:递归算法题解虽然简洁,但运行效率较低,而且递归调用深度过大时可能会导致栈溢出和程序崩溃。)
答案:
int NUM(int num1, int num2)
{
int n = num2;
while (num1 % num2)
{
n = num1 % num2;
num1 = num2;
num2 = n;
}
return n;
}
int main()
{
int a = 0;
scanf("%d", &a);
int A = a;
a--;
int n, m, fz, fm, N;//因为计算新分子和新分母需要两个有理数运算
scanf("%d/%d", &n, &m);//所以先输入第一个有理数,方便第一次的运算操作
fz = n;
fm = m;
while (a)
{
scanf("%d/%d", &n, &m);
fz = fz * m + n * fm;
fm *= m;
N = NUM(fz, fm);
if (N != 0)//未求出公约数则不操作
{
fz /= N;
fm /= N;
}
a--;
}
fm *= A;
N = NUM(fz, fm);
if (N != 0)
{
fz /= N;
fm /= N;
}
if (fm == 1)//分母等于一时只输出分子即可
printf("%d", fz);
else
printf("%d/%d", fz, fm);
return 0;
}
解析:重新理清一下思路,其实这道题并不算难,只要掌握了其中分子分母运算的思路,并且记得必须要每一次运算后都及时进行分子分母的化简,然后掌握了如何多种方法求解最大公约数,那么这题就能够轻松的解出来啦~
怎么样,读到这里有没有一些收获呢?不要着急~学习就是日积月累,循序渐进的!!让我们把握好美好的现在,一天前进一小步的,走向遥远的未来吧~那么今天的刷题日记就分享到这里啦~如果有写的不清楚的地方,或者代码有需要改进的地方,还请大家多多指出哦~我也会虚心学习的!那么我们下期再见~