一、选填部分
第一题:
以下的变量定义语句中,合法的是( )
A. byte a = 128;
B. boolean b = null;
C. long c = 123L;
D. float d = 0.9239;
思路提示:观察选项时不要马虎,思考一下各种类型变量的取值范围,以及其初始化的形式是否合法。
答案:C
解析:
A byte能表示的范围[-128,127]
B boolean的取值只能是true或false
C 正确的选项
D 0.9239为double类型
注:使用float定义小数时,需要在最后加一个字母f。(float a = 0.9239f(√) float a = 0.9239(×))
第二题:
以下程序,程序运行后的输出结果是( )
void ss (char *s,char t) {
while (*s) {
if(*s==t) *s = t - 'a' + 'A';
s++;
}
}
int main() {
char str1[100]="abcddfefdbd",c='d';
ss (str1,c);
printf ("%s\n",str1);
}
思路提示:仔细思考,ss函数中的 ( - 'a' + 'A' ) 是什么意思?
答案:abcDDfefDbD
解析:将字符串str1和字符c传进ss函数中,在ss函数中会查找str1中出现的,与字符c相同的字符,将此字符-'a'再+'A',此操作代表将此小写字符转换成大写字符,而此段函数中传入的是'd',故str1中小写的d将变成大写的D,所以答案为abcDDfefDbD。
第三题:
以下程序的输出结果是( )
#include<stdio.h>
#include<string.h>
char* fun(char* t) {
char* p = t;
return(p + strlen(t) / 2);
}
int main(void) {
char* str = " abcdefgh";
str = fun(str);
puts(str);
return 0;
}
思路提示:这题有个坑...注意仔细看看字符串str到底是什么!!!
答案:defgh
解析:fun函数的作用是将传入函数中的字符串跳过一段长度(字符串的一半),而str的长度为9(前面有一个空格!!!坑!!!),之后返回的地址就是跳过了(strlen(t) / 2)的长度,也就是四个字符,则此时打印出的字符串为defgh。
第四题:
以下代码的输出是什么?( )
void main (void)
{
int a[]={4,5,6,7,8};
int *p = a;
*p++ += 100;
printf(" %d %d \n" , *p,*(++p));
}
思路提示:*p++ += 100是将数组a中的哪个元素增加了100?而打印元素时是否有这个元素呢?打印的是哪一个元素?
答案:6,6
解析:创建一个指针p用来接收数组a的地址,而*p++ += 100代表的是将p地址指向的元素加100,也就是此时a = {104,5,6,7,8},随后p指向第二个元素。printf执行顺序为从右向左,于是先*(++p),指向第三个元素,打印的就是6,6。
第五题:
以下代码的输出结果是?( )
int main()
{
char c, s[20];
strcpy(s, "Hello,World");
printf("s[]=%6.9s\n", s);
return 0;
}
答案:s[]= Hello,
解析:%6.9s 表示显示一个长度不小于6且不大于9的字符串。若大于9, 则第9个字符以后的内容将被删除。%m.n表示场宽为m的浮点数, 其中小数位为n, 整数位为m-n-1,小数点占1位,不够m位,左右对齐。
二、编程题部分
第一题:查验身份证
一个合法的身份证号码由17位地区、日期编号和顺序编号加1位校验码组成。校验码的计算规则如下:
输入:
4
320124198808240056
12010X198901011234
110108196711301866
37070419881216001X
输出:
12010X198901011234
110108196711301866
37070419881216001X
输入:
2
320124198808240056
110108196711301862
输出:
All passed
首先对前17位数字加权求和,权重分配为:{7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2};然后将计算的和对11取模得到值Z;最后按照以下关系对应Z值与校验码M的值:
Z: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
M: | 1 | 0 | X | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 |
思路提示:对于前17位数组加权求和,权重分配的过程可以通过这个小表格来对比,这样会更加的直观~大家照着这个表格去打代码,思路也能大概更加通畅一些~
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
*7 | *9 | *10 | *5 | *8 | *4 | *2 | *1 | *6 | *3 | *7 | *9 | *10 | *5 | *8 | *4 | *2 |
表格上面的0~16分别代表第1~17个身份证号,而计算和时需要将身份证号与对应的权重相乘,将所有得到的结果都相加后再对11取模,然后再将取模得到的值Z转换成对应的校验码M。
提示:
① 因为输入的时候也会输入字符,所以不能直接定义整数输入,而是需要定义一个char类型的数组来接收输入的值~
② 由于接收输入的值的是char型数组,所以存在里面的也不是数字,而是数字型的字符。对于我们计算时,需要将此字符 - '0',将其变换成数字类型再进行运算。
答案:
int main()
{
int num = 0;
scanf("%d\n", &num);
int i = 0;
int j = 0;
int arr0[20] = { 7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2 };//存储身份证号码的权重
char str[20] = { '1','0','X','9','8','7','6','5','4','3','2' };//存储数值对应校验码
char arr[num + 1][20];//存储每个人的身份证号码
int arrsum[101] = { 0 };//用于存储身份证号码的加权求和
for (i = 0; i < num; i++)
{
gets(arr[i]);
}
int sum = 0;
for (i = 0; i < num; i++)
{
for (j = 0; j < 17; j++)
{
arrsum[i] += (arr[i][j] - '0') * arr0[j];//将十七身份证号码加权求和
}
int r = arrsum[i] % 11;//将此和转换成校验码
if (arr[i][17] != str[r])//判断是否有误,若有误则输出此号码
{
puts(arr[i]);
sum++;
}
}
if (sum == 0)//如果没有错误号码,则输出
printf("All passed");
return 0;
}
解析:先定义四个数组,一个int[]型数组用来存储身份证号码的权重,另一个char[]型数组用来存储校验码,还有一个char[][]型数组来分别存储每一个人的身份证号码,int arrsum[]用来存储身份证号码加权求和后的结果。我们使用for循环对不同的人,分别输入其对应的身份证号,然后再使用for循环对每一位号码都- '0',从而转换成数字,再使其乘以对应的权重,最后将这些值都存储在arrsum中,得到此身份证号码的加权求和的结果。之后对此值对11取模,最后与最后一位的校验码进行对比,查看是否正确就好啦~
第二题:连续因子
一个正整数 N 的因子中可能存在若干连续的数字。例如 630 可以分解为 3×5×6×7,其中 5、6、7 就是 3 个连续的数字。给定任一正整数 N,要求编写程序求出最长连续因子的个数,并输出最小的连续因子序列。
输入格式:
输入在一行中给出一个正整数 N(1<N<231)。
输出格式:
首先在第 1 行输出最长连续因子的个数;然后在第 2 行中按 (因子1* 因子2* ……* 因子k) 的格式输出最小的连续因子序列,其中因子按递增顺序输出,1 不算在内。
输入:
630
输出:
3
5*6*7
思路提示:当我们想知道一个数字是否存在连续因子时,首先我们要知道,它是不是一个素数~如果不判断是否为素数就直接进行操作,那么如果它是素数,一切就都白费啦~
而判断一个数字是否为素数,我们可以对一个数字,使用从2开始,到此数字的平方根结束的循环,如果此循环内的数都不能被要判断的数字整除,那么这个数字就是素数,反之如果有能整除的数字那么判断此数字就不是素数。
int isPrime(int num)
{
for (int i = 2; i < sqrt(num); i++)
if (num % i == 0)
return 0;
return 1;
}
而我们想要做到打印连续因子,首先要知道连续因子的个数,我们可以定义一个整形变量len来进行对这个因子个数的存储,而且就算我们记录了因子个数,我们也还需要知道第一个起始因子数字是几,才能进行对连续因子的打印。
(我们分别定义两个整型变量,使用两个for循环嵌套,外围循环代表查找起始因子数字,内层循环用于检测连续因子,定义一个s代表乘积,s*=j,当s超过数字还未找到连续因子时则退出此次循环,外层循环数+1,再次进行查找。如果找到连续因子,则使用len记录连续因子的长度,len1记录起始因子的数字~)
答案:
int isPrime(int num)
{
for (int i = 2; i < sqrt(num); i++)
if (num % i == 0)
return 0;
return 1;
}
int main()
{
int num;
scanf("%d", &num);
int i = 0;
int j = 0;
int s = 1;
int len = 0;
int len1 = 0;
if (isPrime(num))//判断此数字是否为素数
printf("1\n%d\n", num);//若为素数则直接进行打印
else
for (i = 2; i < sqrt(num); i++)//外层循环,代表开始查找的起始因子
{
s = 1;//每次重新查找,乘积s置1
for (j = i; j * s <= num; j++)//内层循环,代表从起始因子开始向后查找连续因子
{
s *= j;
if (num % s == 0 && j - i + 1 > len)//num % s == 0代表此时的s是连续因子相乘的结果
{
len = j - i + 1;//因为要输出最长连续因子,所以使len存储最长数
len1 = i;//len1记录起始因子
}
}
}
if (len1 != 0)
printf("%d\n", len);
while (len--)
{
printf("%d", len1++);
if (len)
printf("*");
}
return 0;
}
解析:首先创造一个isPrime函数用来判断输入的数字是否为素数,若为素数则不需要再进行判断,若不是素数,则继续进行对此数字的连续因子查找。使用for外层循环代表开始查找的起始因子,因为1不算,所以i从2开始进行记录,每次查找失败后退出内层循环,外层循环++再重新进行查找。与此同时len和len1也一直对最长的连续因子进行记录,直到找到最长的连续因子,退出循环,然后进行相对应的格式打印~
第三题:谁先倒
划拳是古老中国酒文化的一个有趣的组成部分。酒桌上两人划拳的方法为:每人口中喊出一个数字,同时用手比划出一个数字。如果谁比划出的数字正好等于两人喊出的数字之和,谁就输了,输家罚一杯酒。两人同赢或两人同输则继续下一轮,直到唯一的赢家出现。
下面给出甲、乙两人的酒量(最多能喝多少杯不倒)和划拳记录,请你判断两个人谁先倒。
输入格式:
输入第一行先后给出甲、乙两人的酒量(不超过100的非负整数),以空格分隔。下一行给出一个正整数N(≤100),随后N行,每行给出一轮划拳的记录,格式为:
甲喊 甲划 乙喊 乙划
其中喊是喊出的数字,划是划出的数字,均为不超过100的正整数(两只手一起划)。
输出格式:
在第一行中输出先倒下的那个人:A
代表甲,B
代表乙。第二行中输出没倒的那个人喝了多少杯。题目保证有一个人倒下。注意程序处理到有人倒下就终止,后面的数据不必处理。
输入:
1 1
6
8 10 9 12
5 10 5 10
3 8 5 12
12 18 1 13
4 16 12 15
15 1 1 16
输出:
A
1
思路提示:首先我们输入的两个整型数字用两个整型变量进行存储,而这两个整形变量在整个函数中都至关重要,他们的作用是存储甲和乙的酒量,所以需要将两者的定义在主函数中进行,使得它的生命周期较长。随后输入的划拳记录也需要在主函数中就进行输入,而后的循环就需要用到这个划拳记录的数字,划拳记录的大小为多少,我们的循环也就进行多少次~
之后每次猜拳,按照题中的格式进行输入"甲喊 甲划 乙喊 乙划",就需要在循环中进行输入,因为每次的猜拳,甲和乙喊的和划的都会发生变化,如果在循环中进行输入,那么每一次输入都会将数值替换掉,就不需要顾虑上次喊和划的记录~之后我们只需要分别统计两者的失败(喝酒)次数,当A大于甲的酒量时,A失败,打印A,并打印B此时喝酒次数,相反如果B大于乙的酒量,则B失败,打印B,并打印A此时喝酒次数。
(此题看似复杂困难,但其实只要理清思路,就会发现这也是一道相对比较简单的题~)
答案:
//}
int main()
{
int A = 0;
int numa = 0;
int B = 0;
int numb = 0;
scanf("%d %d\n", &A, &B);
int N = 0;
scanf("%d\n", &N);
while (N--)//循环变量N递减
{
int ahan; int ahua;
int bhan; int bhua;
scanf("%d %d %d %d", &ahan, &ahua, &bhan, &bhua);
if (ahua == ahan + bhan && bhua != ahan + bhan)//甲划出的数字正好等于两人喊出的数字之和
{
numa++;
}
if (bhua == ahan + bhan && ahua != ahan + bhan)//乙划出的数字正好等于两人喊出的数字之和
{
numb++;
}
if (numa > A)
break;
if (numb > B)
break;
}
if (numa > A)
printf("A\n%d\n", numb);
else
printf("B\n%d\n", numa);
return 0;
}
解析:只需要理清此游戏的规则,将两个if语句填写正确那么此题就没有什么过于麻烦的地方~要注意的是,此题中两者同时赢或者同时输,记作平局,故不用管。
(注:此题中输入的格式是有换行符号\n的,为了避免输入下面的数据时误接收换行符号\n,我们需要在输入甲乙酒量A和B,以及划拳记录次数时,在最后一个%d后添加\n,以避免误接收~)
第四题:小q的数列
小q最近迷上了各种好玩的数列,这天,他发现了一个有趣的数列,其递推公式如下:
f[0]=0 f[1]=1;
f[i]=f[i/2]+f[i%2];(i>=2)
现在,他想考考你,问:给你一个n,代表数列的第n项,你能不能马上说出f[n]的值是多少,以及f[n]所代表的值第一次出现在数列的哪一项中?(这里的意思是:可以发现这个数列里某几项的值是可能相等的,则存在这样一个关系f[n'] = f[n] = f[x/2]+f[x%2] = f[x]...(n'<n<x) 他们的值都相等,这里需要你输出最小的那个n'的值)(n<10^18)
输入第一行一个t
随后t行,每行一个数n,代表你需要求数列的第n项,和相应的n'(t<4*10^5)
输出每行两个正整数
f[n]和n',以空格分隔
输入:
2
0
1
输出:
0 0
1 1
思路提示:大家刚刚读完题后或许会感觉有点懵,不太懂需要求的东西到底是什么(反正我有点...)。其实很简单,这个题的大体意思就是,我们需要先输入一个整形变量t代表行数,而后我们在每一行输入一个数字n,而我们编写的程序需要做到求出n所对应的f[n]和n'(n'代表的是值第一次出现在数列的哪一项)。
那么想要求出f[n],就需要我们对题中所给出的公式进行分析并且理解透彻。让我们在看一眼...f[i]=f[i/2]+f[i%2];(i>=2).../2...%2...?感觉有一些熟悉呀,原来这个意思就是转换二进制~至于此式子是怎么求出二进制的,在这里通过一段小小的运算来帮助大家理解~
f[i]=f[i/2]+f[i%2];(i>=2)
f[2] = f[1] + f[0]; 2->10(2指十进制数,10指对应的二进制数,以下同理)
f[3] = f[1] + f[1]; 3->11
f[4] = f[2] + f[0] = f[1] + f[0] + f[0]; 4->100(二次运算是因为运算后[]中仍有大于2的数)
(100是结果的三个数[]中的系数)
f[5] = f[2] + f[1] = f[1] + f[0] + f[1]; 5->101
f[6] = f[3] + f[0] = f[1] + f[1] + f[0]; 6->110
f[7] = f[3] + f[1] = f[1] + f[1] + f[1]; 7->111
f[8] = f[4] + f[0] = f[2] + f[0] + f[0] = f[1] + f[0] + f[0] + f[0]; 8->1000
知道了此式子的功效,那么我们就向前迈进一大步啦~
而想要知道n'(f[n]第一次出现在数列的哪一项)是多少,我们首先需要先求出f[n],而f[n]作为二进制数字不能当作通常的整形变量计算,所以我们可以创建一个数组arr来存储f[n]的值,然后再在函数中定义一个sum用来存储1的个数,对此二进制数字的每一位进行判断,如果出现1那么我们的计数器就++。
读到这里,有人会问啦~那么n'到底应该怎么求???
我们通过这个表格来看一下在不同的n情况下,相应的f[n],n',n的二进制数,分别都是什么~帮助大家理解以下~
n | f[n] | n' | n二进制 |
0 | 0 | 0 | 0 |
1 | 1 | 1 | 1 |
2 | 1 | 1 | 10 |
3 | 2 | 3 | 11 |
4 | 1 | 1 | 100 |
5 | 2 | 3 | 101 |
6 | 2 | 3 | 110 |
7 | 3 | 7 | 111 |
8 | 1 | 1 | 1000 |
由此表我们可以看出,每一次出现新的n'时,n的值都是2的次方-1。
n | 二进制 | f[n] | n' |
1 | 1 | 1 | 1 |
3 | 11 | 2 | 3 |
7 | 111 | 3 | 7 |
15 | 1111 | 4 | 15 |
31 | 11111 | 5 | 31 |
63 | 111111 | 6 | 63 |
127 | 1111111 | 7 | 127 |
255 | 11111111 | 8 | 255 |
511 | 1 11111111 | 9 | 511 |
由此表我们能知道,出现新的n'的规律是什么。最简单的思路其实就是将n存在一个数组中,求n'的时候遍历查找,但是此题中行不通,因为测试是一个非常大的数字,如果使用数组会造成超时。
大家看这个式子:
((long long)1 << n) - 1)
“ << ”是左移操作符,代表将此数字的二进制数整体向左移一位,右边补0。将1向右移动一位,1就变成了2,移动两位,1就变成了4...移动几位1就变成2的几次方。我们可以通过统计出的"二进制中1的个数",来进行相应的位移,最后再将得到的2的某次方-1就能得到我们的n'啦~怎么样!!!是不是很简单~
(如果想具体的了解位移操作符与位操作符,可以到这篇博客看看,或许会有收获~:C语言的二进制及其相关操作符_c 二进制-CSDN博客)
答案:
#include<stdio.h>
int main()
{
long long k = 0;
long long i = 2;//循环变量
long long j = 0;//循环变量
long long m = 0;//循环变量
long long z = 0;
long long num;
long long* arr;
arr = (long long*)calloc(400000 * sizeof(long long), sizeof(long long));
long long n;
scanf("%lld", &k);
for (i = 0; i < k; i++)
{
long long sum = 0;//记录1的个数
scanf("%lld", &n);
int N = n;
num = 0;
while (n)
{
arr[z++] = n % 2;
n /= 2;
num++;
}
for (j = 0; j < num; j++)
{
if (arr[j] == 1)
sum++;
}
int nn = sum;
printf("%lld %lld", sum, ((long long)1 << nn) - 1);
printf("\n");
z = 0;
}
free(arr);
return 0;
}
解析:因为此题的试验集数字非常大,所以在定义变量时我将几乎所有的变量都定义成了long long型,这样能够稍微有效的防止溢出~而在定义arr的时候,我并没有直接使用int arr[400000],因为定义过大的数组也会导致内存溢出,所以我使用了calloc函数~那么我们来说一下这个函数的大体作用~calloc的两个参数分别为size_t类型的num,以及size_类型的size。它们分别代表的是:
- num -- 要被分配的元素个数。
- size -- 元素的大小。
而calloc函数的作用是:开辟出一个分配所需的内存空间,并返回一个指向它的指针。
(calloc和malloc是一样的东西,在这里使用calloc是因为calloc创建出的"数组"中会自动初始化为0,省去了初始化的步骤)
给指针calloc分配空间不等于数组,但是可以认为它是个数组一样的使用而不产生任何问题。
当我们直接使用long long arr[400000]时,就会出现这种报错,而此时我们使用的calloc就不会发生这种情况,这是因为直接定义数组时,仅仅只是声明了一个数组,但是,请注意要点:系统这时并没有为其分配内存。只是在使用时才分配。如果在后续为其分配内存时系统没有足够多的内存使用,那么就会导致指向的数变成其他的数,或者把其他数覆盖,这就导致了bug的出现!!!
而使用calloc(或malloc)来申请空间就会避免这种情况发生,因为使用calloc申请空间是事先分配好了内存空间的,就不会出现在后续再进行分配,而内存不充足的情况了。
而calloc也并不是完全完美的,calloc和定义数组一样都会开辟相应的空间,既然开辟了空间相应的就需要释放空间。而直接定义的数组在函数结束或者程序结束后会被自动释放,这是calloc所没办法做到的。想要随心所欲的使用calloc我们就需要在了解下一个函数:
free的作用是:用于释放由malloc(), calloc(), realloc()等动态分配函数分配的内存。当动态分配的内存不再需要时,调用free()函数可以避免内存泄漏,确保程序有效地管理内存。
free中只有一个void*型的参数ptr,它的作用是:指针指向一个要释放内存的内存块,该内存块之前是通过调用 malloc、calloc 或 realloc 进行分配内存的。如果传递的参数是一个空指针,则不会执行任何动作。
只要使用calloc分配内存后,最后使用free来手动释放内存,这样就不怕内存泄漏啦~
最后看一下大佬代码T A T
#include <stdio.h>
long long func(long long y) {
if (y == 0) {
return 0;
}
if (y == 1) {
return 1;
}
return func(y / 2) + (y % 2);
}
int main() {
long long t = 0;
scanf("%lld", &t);
for (long long i = 0; i < t; i++) {
long long y = 0;
scanf("%lld", &y);
long long n = func(y);
long long sum = ((long long)1 << n) - 1;
printf("%lld %lld", n, sum);
printf("\n");
}
}
直接将数字传到func函数中,通过函数递归对数字多次操作,最后能够得到此数字的二进制形式的1和0,并且返回1的个数,在直接用1<<n的形式左移相应次数就好了~确实厉害呀。
那么这次的C语言刷题日记就分享到这里啦~如果有什么讲的不清楚,或者出现错误的地方,还请大家多多指出,我也会多多吸取教训的!那么我们下期再见啦~