【C语言进阶】题目练习
目录
1.箭形图案
思路:
代码:
2. 公务员面试
分析:
代码 :
3. 判断结构体大小(1)
答案:
分析:
4.判断结构体大小(2)
答案:
分析:
5.宏定义+计算位段大小的选择题
分析:
答案:
6.位段与指针
分析:
答案:
7. 结构体大小判别
分析:
答案:
8.联合体的大小
分析:
答案:
9.大小端字节序
分析:
答案:
10.枚举选择题
答案:
11. 编程题:找出只出现一次的数字
分析1:
代码1:
思路2:
代码2:
12.atoi的实现
13.文件读写的选择题(1)
14.看代码说功能
15. 文件读写的选择题(2)
16.预处理的选择题
17.预处理的分析题
18.feof函数的选择题
19.宏替换的选择题(1)
20.宏替换的选择题(2)
21.写一个宏将一个整数的二进制位的奇数和偶数进行交换
思路:
1.箭形图案
KiKi学习了循环,BoBo老师给他出了一系列打印图案的练习,该任务是打印用“*”组成的箭形图案。
输入描述:
本题多组输入,每行一个整数(2~20)。
输出描述:
针对每行输入,输出用“*”组成的箭形。
输入
2
输出
*** ******
思路:
可以把图形分为上下两部分,如果输入n上面就是n行,下面就是n+1行;
上半部分:n行
空格:第一行有四个空格,第二行有两个空格,我们可以把2个空格当做一组,第一行打印两组,第二行打印一组;每一行需要递减,所以内部循环减去外面的行数。
*:和行数有关,第一行是一个,第二行是两个。
下半部分:n+1行
空格:两个空格为一组,第一行是0组空格,第二行是1组空格,第三行是2组空格,就是行数-1。
*:第一行是3个*,第二行是2个*,第3行是1个*,每次从n+1开始,需要再减去行数。
代码:
#include<stdio.h>int main()
{int n = 0;while(scanf("%d",&n)){
// 上半部分for (int i = 0; i < n; i++){for (int j = 0; j < n - i; j++){printf(" "); // 先打印空格,两个为一组}for (int k = 0; k <= i; k++) // 第一行一个*,第二行两个*,跟行号有关{printf("*");}printf("\n");}// 下半部分for (int i = 0; i < n + 1; i++){// 空格for (int j = 0; j < i; j++){printf(" ");}for (int k = 0; k < n + 1 - i; k++){printf("*");}printf("\n");}}return 0;
}
2. 公务员面试
描述
公务员面试现场打分。有7位考官,从键盘输入若干组成绩,每组7个分数(百分制),去掉一个最高分和一个最低分,输出每组的平均成绩。
(注:本题有多组输入)
输入描述:
一行,输入7个整数(0~100),代表7个成绩,用空格分隔。
输出描述:
一行,输出去掉最高分和最低分的平均成绩,小数点后保留2位,每行输出后换行。
示例1
输入:
99 45 78 67 72 88 60
输出:
73.00
分析:
本身的逻辑不难,难的是如何oj,我们之前是每次读取一个数字,我们这道题可以一次读取一个数字,也能读取一个数字。
若读取一个数字,我们需要一个变量n来记录读取数字的个数,每次读取n需要++;
定义最大最小值并设立处置,每次读取一个数字就需要判断是否是最大或者最小值,以此保证最小最大值保持更新,一旦n ==7,说明数字够了,就开始计算平均值,需要将这些变量进行重新初始化以待下一次实例的调用。
代码 :
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>int main() {int score = 0;int max = 0;int min = 100; int n = 0;int ret = 0;while (scanf("%d", &score) == 1) {n++;if(score > max){max = score;}if (score < min ) {min = score;}ret += score;if (n == 7) {printf("%.2lf\n",(ret - min - max)/5.0);n = 0;ret = 0;max = 0;min = 100;}}return 0;
}
3. 判断结构体大小(1)
判断以下两个结构体的大小:
#include<stdio.h>struct A
{int a;short b;int c;char d;
};struct B
{int a;short b;char c;int d;
};int main()
{struct A a = { 0 };struct B b = { 0 };printf("%d\n",sizeof(a)); // 16printf("%d\n",sizeof(b)); // 12return 0;
}
答案:
16,12
分析:
A:a占4个字节偏移量0-3;b占2个字节,对齐数是2,偏移量是4-5;c占4个字节,偏移量是4,6不是4的倍数,所以偏移量是8-11;最后一个d是占1个字节,所以偏移量是12;总共占用13个字节,13不是最大对齐数(4)的倍数,那么最近的倍数是16;
B: a占4个字节偏移量0-3;b占2个字节,对齐数是2,偏移量是4-5;c占1个字节,偏移量是6;d占4个字节,最小对齐数是4,7不是4的倍数,所以偏移量是8,占用8-11;总共占用12个字节,12是最大对齐数的倍数,所以答案是12;
4.判断结构体大小(2)
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#pragma pack(4) // 4字节对齐
struct S1
{short a; // 0-2char d; // 3long b; // 4-7 long c;// 8-11
};struct S2
{long b;// 0-3short c;// 4-5char d;// 6long a;// 8-11
};
struct S3
{short c;// 0-1long b;// 4-7char d;// 8long a;// 12-15
};
int main()
{printf("%d\n", sizeof(struct S1));printf("%d\n", sizeof(struct S2));printf("%d\n", sizeof(struct S3));}
答案:
12 12 16
分析:
见注释,这里需要注意的是32位系统重long占4个字节。
5.宏定义+计算位段大小的选择题
分析:
①首先需要判断位段占用的bit位数,首先第一个成员变量开辟一个字节的空间,8bit,第一个成员+第二个成员用了6bit,剩余2bit,此时第三个成员变量需要1个字节,需要单独开辟,第四个成员变量虽然只需要1bit,但仍然要开辟1字节,总共3字节。
②注意宏定义:3* 2 + 3 = 9
答案:
D
6.位段与指针
分析:
首先定义一个char类型的数组,有四个元素;定义一个位段指针指向这个数组;将数组全部初始化为0,;
位段的第一个成员是占用1个字节,剩下三个成员占用一个字节,所以这个位段整体占用两个字节。
需要给成员赋值,第一个成员赋值为2,8bit足以存下;
第二个成员只有1bit需要存3,所以只能取低1位bit位;
第三个成员只有2bit需要存4,所以只能取低2位bit位;
第四个成员只有3bit需要存5,刚好可以存下。
此时内存分布如下图所示:
此时按照16进制打印两位来输出每一个字节那么就是:
答案:
0000 0010 -》 02
0010 1001 -》 29
0000 0000 -》 00
0000 0000 -》 00
7. 结构体大小判别
分析:
总大小是10B,最大对齐数是4,所以必须是4的倍数12B。
答案:
12字节
8.联合体的大小
union Un
{short s[7];int n;
};
分析:
联合体的奥义是成员公用内存,所以s占用14个字节,n占用4个字节,此时14个字节够用了,最后需要考虑最大对齐数是4,所以最终应该是4的倍数,16字节。
答案:
16
9.大小端字节序
注:32位cpu平台
分析:
首先联合体是2B,这里分别访问数组的第一个元素和第二个元素,这里其实就是给两个字节填充数据;这里需要打印k变量,由于是两个字节,这里就存在字节序大小端的问题,在vs编译器中是采用小端存储,即低字节存在低地址高字节存在高地址(倒着存)那么输出就是0x3839(还原数据,先打印高地址再打印低地址)
答案:
3839
10.枚举选择题
答案:
枚举从0开始,依次递增1,中途可以修改,再依次递增1;
B
11. 编程题:找出只出现一次的数字
一个数组中有两个数字出现一次其余数字出现了两次,找到这两个只出现了一次的数字。
力扣原题
分析1:
暴力求解,每个元素都要对n个元素进行比较,如果标记到两个元素相等cnt++,如果cnt是1,那么说明只和自己相等,那就是单独的数字了。
代码1:
#include<stdio.h>void find_dog(int arr[], int sz)
{for (int i = 0; i < sz; i++){int cnt = 0;for (int j = 0; j < sz; j++){if (arr[i] == arr[j]){cnt++;}}// cnt = 1的时候需要记录下来if (cnt == 1) {printf("%d是单独的数字!\n",arr[i]);}}
}int main()
{int arr[10] = { 1,2,3,4,5,1,2,3,4,6 };int sz = sizeof(arr) / sizeof(arr[0]);find_dog(arr, sz);return 0;
}
思路2:
可以利用异或的特性,两个数字异或如果相同结果就是0,相异结果就是1,所以将这所有的数字全部异或,最后的结果一定不为0,我们假定最后异或出来的结果的最后一位是1,那么相当于最后一位是相异的,那么我们可以按照最后一位是0或者1将所有数字分成两组,这两组分别进行异或,就能得到最终相异的数字。
①所有数字异或得到不为0的结果。
②从结果中找到二进制的某一位是1。
③旨在按照此位进行分组,组内进行异或,最后的结果就是其中一个单独的数字。
核心思想:按照根据异或的原理将两个单独的数字分别分为两组,组内进行异或(其余数字都是成对,异或就是0),最终每组只剩下那个单独的数字。
代码2:
/*** Note: The returned array must be malloced, assume caller calls free().*/
int* singleNumber(int* nums, int numsSize, int* returnSize)
{int sum = 0;int* ret = malloc(2 * sizeof(int));int pos = 0;int dog1 = 0;int dog2 = 0;for(int i = 0;i < numsSize;i++){// 1.全部异或得到一个数字sum ^= nums[i];}// 2. 根据这个数字的第n位为1进行分组// 3.计算第n位为1for(int i = 0;i < 32;i ++){if(((sum >> i)&1) == 1){pos = i;break;} }//4. 按照第pos位进行分组,组内进行异或for(int i = 0;i < numsSize;i++){if((nums[i] >> pos)&1 == 1){dog1 ^= nums[i];}else{dog2 ^= nums[i];}}ret[0] = dog1;ret[1] = dog2;*returnSize = 2;return ret;
}
12.atoi的实现
即字符串转换成整数,例如“123456” -》 123456、“-123456” -》 -123456,遇到非数字的时候停止转化,“-123abc456” -》 -123;
需要考虑以下几点:
①空指针。assert判断。
②空字符串。字符串只有\0,如果返回0,那就会和“0”产生歧义。
③空格。使用isspace判断是否是空格,是空格那么str++跳过空格。
④+-号。定义一个正负号的变量,如果遇到+变量置为1,遇到-变量置为-1,最后返回只需要结果*flag即可。
⑤非数字字符。若字符串为123ABC,应该输出123
正常处理:需要将每一位的数字转换成整型,存入变量,接下来变量只需要*10 + 新这一位的数字即可。
⑥越界问题:当字符串内的数字非常大,此时就可能发生越界,此时我们需要判断是正数越界还是负数越界
做到这里,我们可以将简易版的代码写出:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<assert.h>
#include<ctype.h>
#include <limits.h>
// 判断这个字符串是否合法
enum STATUS
{VALID,INVALID
}status = INVALID;int my_atoi(const char* str)
{// 正负标记int flag = 1;long long ret = 0;assert(str);// 空字符串,返回非法0if (*str == '\0'){return 0;}// 判断空白字符,跳过while (isspace(*str)){str++;}// 遇到+-号if (*str == '+'){flag = 1;str++;}else if (*str == '-'){flag = -1;str++;}// 正常处理数字字符串while (*str != '\0'){if (isdigit(*str)) {ret = ret * 10 + flag * (*str - '0');// *str是字符,需要-字符'0'转换成对应的数字// 判断是否越界if (ret > INT_MAX || ret < INT_MIN) {status = INVALID;return 0;}}else{// 不是数字字符status = INVALID;return str;}str++;}// 没有提前返回说明是正常处理完毕if(*str == '\0'){status = VALID;}return ret;
}int main()
{char arr[100] = "123456";int ret = my_atoi(arr);if (status == INVALID){printf("数字不合法!:%d\n", ret);}else if (status == VALID){printf("数字为:%d\n", ret);}return 0;
}
测试:
含非数字字符:
负数:
溢出:
13.文件读写的选择题(1)
B:getchar适用于标准输入流。
14.看代码说功能
统计文件的字符个数
15. 文件读写的选择题(2)
D:sprintf是把格式化的数据写入字符串中。
16.预处理的选择题
C:链接阶段会查找符号表,看这个函数是否存在。
17.预处理的分析题
判断变量的类型
#define INT_PTR int*
typedef int* int_ptrINT_PTR a,b;
int_ptr c,d;
替换完毕后是:int *a,b,那么a是int*类型,b是int类型;
下面不一样,把int*当做一个整体的类型,c,d都是int*类型。
18.feof函数的选择题
A:错误,解析详见B选项。
19.宏替换的选择题(1)
答案:70
直接替换计算: 2*(4 + Y(5+1)),Y(5+1)是31。
20.宏替换的选择题(2)
答案:B
21.写一个宏将一个整数的二进制位的奇数和偶数进行交换
思路:
假如红色位置是偶数位,绿色位置是奇数位;将奇数位的数字全部拿出来,向右移动一位;同理将偶数位的数字都拿出来,向左移动一位,最后相加即可。
怎么拿?以偶数位举例,与一个偶数位都是1,奇数位都是0的32位数字即可。
#define SWAP_BIT(n) (((n&0x55555555)<<1) + ((n&0xaaaaaaaa)>>1))int main()
{int n = 10;printf("%d\n", SWAP_BIT(n));return 0;
}
这里用10进行测试,1010 -> 0101(5)