前言
我们C语言已经学习的差不多了,但是C语言之中存在的一些问题与难点我们还不一定能够又快又好地解决,为了夯实我们的基础,我们来练习几道稍微有点难度的C语言习题吧
例题一
题目
int main(void)
{unsigned char i = 7;int j = 0;for (; i > 0; i -= 3){++j;}printf("%d\n", j);return 0;
}
题解
在开始解题之前,我们需要知道 unsigned char 类型的取值范围是 0~255
所以 i 的取值不可能是一个负数;
所以跳出循环的条件只有可能是 i=0 ;
我们来画一个图分析一下 unsigned char 类型的变量的取值范围的变化过程:
已知 i 的初始值是7,所以当 i 减到1的时候,下一次执行-3操作的时候得到的值是254;
因为:254 / 3 = 84……2,此时 i 在减去了84个3之后执行 -3 操作得到的值仍然不是0而是2,所以我们还需要向下执行 -3 操作,此时得到的值是255;
因为:255 / 3 = 85 ,此时255是3的倍数,在执行了85次 -3 操作以后i的值成功变成了0,此时也就跳出了循环;
所以 j 的值就应该是 2 + 1 + 84 + 1 + 85 = 173 次
例题二
题目
以下那个选项一定可以将flag的第二个bit位置0?
A.flag &=~2
B.flag |= 2
C.flag ^= 2
D.flag >>= 2
题解
我们发现:原题目中与flag执行操作的数都是2;
因为2是一个正数,所以2的原码就是补码;
所以2的二进制补码为:
0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
而2的反码就是:
1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 |
我们直接带入一个特殊的值就可以解决问题:
假设我们有一个二进制的数:
1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
假设我们要把第二个bit位置为0,我们必须要保证与这个二进制数按位与(&)的二进制数的第二个bit位必须是0
而且其它位上的值不能改变,所以除了第二个bit位,其它位置上的数都必须是1;
此时这个二进制数为:
1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 |
这个二进制数的二进制形式刚好符合2的反码,所以我们知道这个题目选 A
题目三
题目
struct One
{double d;char c;int i;
};struct Two
{char c;double d;int i;
};
计算在#pragma pack(4)和#pragma pack(8)的情况下,结构体大小分别是?
题解
该题目只需要把图画出来,就可以很轻松的解决问题
由图片可以知道:
在#pragma pack(4)和#pragma pack(8)的情况下,结构体大小分别是:
16 16 16 24
例题四
题目
在上下文和头文件均正常的情况下,下列程序的输出结果是:
int x = 1;
do
{printf("%2d\n".x++);
} while (x--);
A.1
B.无任何输出
C.2
D.陷入死循环
题解
因为题目中打印的是x++,x++是先使用后+1,所以我们就可以知道先打印出的值是1,打印完成以后,x执行++操作变成了2;
接着我们进入while循环之中的判断,此时条件是x--,因为x是先使用后--,所以判断时x的值是2,而判断完以后x执行--操作变成了1;
我们通过分析知道了,x的取值会一直在1和2之中跳动,所以循环永远不会结束
故选:D
例题五
题目
下列程序执行后c输出的结果为?(32位)
void main()
{int a = -3;unsigned int b = 2;long c = a + b;printf("%ld\n", c);
}
A.-1
B.4294967295
C.0x7FFFFFFF
D.0xFFFFFFFF
题解
在解题的时候,我们需要知道 -3 的原码、反码、补码
-3 的原码:10000000000000000000000000000011
1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 |
-3 的补码:11111111111111111111111111111101
1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 |
2的补码:0000000000000000000000000000010
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
c的二进制序列:11111111111111111111111111111111
1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
c的补码:10000000000000000000000000000001
1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
此时打印出来的值就是-1
所以答案选:A
例题6
题目
int fun(int a)
{a ^= (1 << 5) - 1;return a;
}
fun(21)的运行结果是?
A.10
B.5
C.3
D.8
题解
我们知道,1的补码是:
0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
所以1向左移动5位之后为:
0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
该二进制位-1得到的是:
0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 |
因为我们传入的参数a是21,所以我们应该用21去异或这个值:
0 | 0 | 0 | 1 | 0 | 1 | 0 | 1 |
0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 |
此时得到的二进制序列为:
0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 |
所以打印出来的值是:10
故答案是:A
例题7
题目
下列关于C/C++的宏定义,不正确的是?
A.宏定义不检查函数正确性,会有安全隐患
B.宏定义的常量更容易理解,如果可以使用宏定义常量的话,要避免使用const常量
C.宏的嵌套定义过多会影响程序的可读性,而且很容易出错
D.相对于函数调用,宏定义可以提高程序的运行效率
题解
A、C、D三个选项正确
我们知道,定义一个常量有两种方法:
#define M 10
const int m = 10;
在C语言之中,const修饰的变量叫常变量;
在C++中const修饰的对象就是一个常量;
所以在C++中能使用const定义常量就尽量使用const修饰常量,C语言中则相反
所以答案是:B
例题8
题目
#define N 3+1
#define Y(n)((N+1)*n)
执行 z=2*(N+Y(5+1)) 后,z的值为?
A.60
B.190
C.248
D.上述答案都不对
题解
在解决这个问题的时候,我们需要注意:不能提前把N的值和表达式的值计算出来,需要将表达式整体代入,然后按照运算的先后顺序进行运算
此时z可以转化为:z = 2 * (3+1+( (5)*5 +1)
这样计算出来的值就是60
所以答案就是:D
例题9
题目
char a; int b; float c; double d;
则表达式 a * b + d - c 的值的类型是?
A.float
B.int
C.char
D.double
题解
我们知道:a是char类型的,b是int类型的;
所以在计算的时候,char类型的a需要整型提升为int类型;
int类型的值乘一个int类型的值结果也是一个int类型的值;
当a*b得到的int类型的值与double类型的d计算的时候:
int类型的值会被强制算术转换为double类型的值;
所以a*b+d得到的值就是double类型的值;
同理,float类型的c在与double类型的值计算的时候也会算术转换为double类型的值;
所以 a*b+d-c 的值就是double类型的
所以答案选:D
编程题1:
题目
对于一个较大的整数N(1<=N<=2,000,000,000)
比如说489275936,我们常常需要一位一位的数这个数是几位数,但是如果在这个数每三位加上一个“,”,它就会更加易于朗读:489,275,936
请写出一个程序来完成这个内容
题解
我们先来分析一下:(以12345为例子)
1.我们需要采取从右往左处理的顺序,因为如果使用从左往右处理的顺序还需要判断
2.因为我们采取的是从右往左处理的顺序,所以我们需要先提取出数的最后一位5
我们知道,对数采取 %10可以提取出最后一个位的数字,对一个数采取 /10 操作可以去掉最后一个位上面的数字
一直采取 %10 和 /10 操作,最后n的取值就会变成0
3.当我们每次处理完3个数字的时候就加上一个“,”
4.我们可以把提取出来的数放到一个字符数组里面去,当n=0跳出循环的时候再倒着打印数组里面的内容
5.我们每次%10之后的取值是数字5,因为数组是字符数组,此时我们只需要给数字5加上一个'0',就可以变成字符'5'
6.每放三位,就添加一个“,”,此时我们需要定义一个变量k,用来判断是否处理到了3个数。注意:k == 0的时候 %3 也是0,要排除这个情况
7.当我们处理完数组的时候,此时i指向数组最后一个元素的下一个元素,所以我们还要加入一个i--操作,让它指向数组里面的最后一个元素
有了这些思路,我们就可以很轻松的写出代码:
int main(void)
{char arr[20] = { 0 };int N = 0;scanf("%d", &N);int k = 0;int i = 0;while (N){if (k != 0 && k % 3 == 0){arr[i++] = ',';}arr[i++] = N % 10 + '0';N = N / 10;k++;}for (i--; i >= 0; i--){printf("%c", arr[i]);}return 0;
}
编程题2
题目
题解
我们先来分析一下:
1.再输入字符串的时候最好不使用scanf函数,因为scanf函数在遇到空格的时候就不会再往后读取了,此时我们需要用到gets函数;
2.我们需要逐步判断arr2字符数组里面的每一个元素,如果arr1数组里面有这个元素,就把它删除
思路一:在删除完一个元素以后,我们要把这个元素以后的所有元素全部往前挪
思路二:我们遍历arr1数组,看arr1数组里面的每个元素在不在arr2数组中存在,如果在arr2数组中存在,就不打印出来,反之则打印
(因为思路二的效率更高,所以我们采用思路二)
3.当arr1数组遍历到 \0 的时候,跳出循环
有了这些理论基础,我们就可以写出代码如下:
int is_exist(char ch, char arr[])
{int i = 0;while (arr[i]){if (ch == arr[i]){return 1;}i++;}return 0;
}int main(void)
{char arr1[200] = { 0 };char arr2[200] = { 0 };gets(arr1);gets(arr2);int i = 0;while (arr1[i]){if(is_exist(arr1[i], arr2) == 0){printf("%c", arr1[i]);}i++;}return 0;
}
上述代码中的is_exist其实可以用库函数实现,库函数中有一个函数叫做strchr
如果出现了某个字符,就返回这个字符的地址,如果没有出现这个字符,就会返回NULL
strchr的头文件是<string.h>
所以此时我们还有一种写法:
#include <stdio.h>
#include <string.h>
int main(void)
{char arr1[200] = { 0 };char arr2[200] = { 0 };gets(arr1);gets(arr2);int i = 0;while (arr1[i]){if(strchr(arr2, arr1[i]) == NULL){printf("%c", arr1[i]);}i++;}return 0;
}
如果此时我们使用scanf也可以实现,只不过格式有点复杂:
int main(void)
{char arr1[200] = { 0 };char arr2[200] = { 0 };scanf("%[^\n]s", arr1);getchar();scanf("%[^\n]s", arr2);int i = 0;while (arr1[i]){if(strchr(arr2, arr1[i]) == NULL){printf("%c", arr1[i]);}i++;}return 0;
}
此时需要用getchar清除缓冲区,会比较复杂,难以理解,所以不建议使用
结尾
本节我们练习了9个选择题和2个编程题,有助于我们夯实C语言的基础,查漏补缺,那么本节的内容就到此结束了,希望可以给您带来帮助,谢谢您的浏览!!!