西邮linux兴趣小组2024纳新题
学长寄语:长期以来,西邮 Linux 兴趣小组的面试题以难度之高名扬西邮校内。我们作为出题人
也清楚的知道这份试题略有难度。请你动手敲一敲代码。别担心,若有同学能完成一半的题目,就
已经十分优秀。其次,相比于题目的答案,我们对你的思路和过程更感兴趣,或许你的答案略有瑕
疵,但你正确的思路和对知识的理解足以为你赢得绝大多数的分数。最后,做题的过程也是学习和
成长的过程,相信本试题对你更加熟悉地掌握 C 语言一定有所帮助。祝你好运。我们东区逸夫楼
FZ103 见!
本题目只作为西邮 Linux 兴趣小组 2024 纳新面试的有限参考。
为节省版面,本试题的程序源码省去了#include 指令。
本试题中的程序源码仅用于考察 C 语言基础,不应当作为 C 语言「代码风格」的范例。
所有题目编译并运行于 x86_64 GNU/Linux 环境。
0. 聪明的吗喽
一个小猴子边上有 100 根香蕉,它要走过 50 米才能到家,每次它最多搬 50 根香蕉,(多了就
拿不动了),它每走 1 米就要吃掉一根,请问它最多能把多少根香蕉搬到家里。
(提示:他可以把香蕉放下往返的走,但是必须保证它每走一米都能有香蕉吃。也可以走到 n
米时,放下一些香蕉,拿着 n 根香蕉走回去重新搬 50 根。)
可以这样理解,一个猴子先前面走,之后在向后走,最后再往前走,这样一个过程,猴子就吃掉了3个香蕉,当走16次时,就吃掉48根,手上剩余2+50根,这个2就可以舍弃不要,拿着50根香蕉向前面走,这样结果就是50-34=16根
1. 西邮 Linux 欢迎你啊
请解释以下代码的运行结果。
int main() {
unsigned int a = 2024;
for (; a >= 0; a–)
printf(“%d\n”, printf Hi guys! Join Linux - 2%d", printf(“”)));
return 0;
}
考点:printf的返回值,unsigned 无符号类型
首先看最里面的printf引号里面为空,然后返回0,这时为printf Hi guys! Join Linux - 20,以此类推,其字符长度为24,即打印
printf Hi guys! Join Linux - 2024
无符号类型永远大于等于0
2. 眼见不一定为实
输出为什么和想象中不太一样?
你了解 sizeof()和 strlen()吗?他们的区别是什么?
int main() {
char p0[] = “I love Linux”;
const char *p1 = “I love Linux\0Group”;
char p2[] = “I love Linux\0”;
printf(“%d\n%d\n”, strcmp(p0, p1), strcmp(p0, p2));
printf(“%d\n%d\n”, sizeof(p0) == sizeof(p1), strlen(p0) == strlen(p1));
return 0;
}
0
0
0
1
考点:strcmp,strlen,sizeof
首先先看 strcmp(p0, p1),strcmp返回值有三种,0,小于1的数,大于1的数,遇到‘\0’停止,p0与p1相同,所以返回0;p0和p2同理,p0为字符数组名,其大小14,p1为指针,8字节,返回0,strlen遇到‘\0’停止,易知返回1,
sizeof()
功能:sizeof() 是一个编译时运算符,用于获取变量或数据类型所占用的内存大小(以字节为单位)。
返回值:对于数组,sizeof() 返回整个数组所占用的内存大小;对于指针,sizeof() 返回指针本身的大小(通常是 4 或 8 字节,取决于平台)。
strlen()
功能:strlen() 是一个运行时函数,用于计算字符串的长度,直到但不包括第一个空字符 '\0'。
返回值:返回字符串的长度(不包括终止的空字符)。
3. 1.1 - 1.0 != 0.1
为什么会这样,除了下面给出的一种方法,还有什么方法可以避免这个问题?
int main() {
float a = 1.0, b = 1.1, ex = 0.1;
printf(“b - a == ex is %s\n”, (b - a == ex) ? “true” : “false”);
int A = a * 10, B = b * 10, EX = ex * 10;
printf(“B - A == EX is %s\n”, (B - A == EX) ? “true” : “false”);
方法:
- 使用double,这个题用double不能实现,只能减小误差
- 容差值
#define EPSILON 0.00001
if (fabs(b - a - ex) < EPSILON) {
// 可以认为 b - a == ex
}
浮点数在内存中的存储
浮点数的存储过程可以概括为以下步骤:
将浮点数转换为二进制表示。
使用科学计数法表示二进制浮点数,确定符号位、指数位和尾数位。
对指数位进行偏移处理,得到存储的指数值。
将符号位、存储的指数值和尾数位拼接在一起,形成浮点数在内存中的存储形式。
例如,对于浮点数19.625,其存储过程如下:
将19.625转换为二进制表示:10011.101(整数部分采用除2取余法,小数部分采用乘2取整法)。
使用科学计数法表示:1.0011101×2^4。
计算存储的指数值:4+127=131(偏移量为127)。
拼接符号位、存储的指数值和尾数位:0(符号位,正数)- 10000011(存储的指数值)- 00111010000000000000000(尾数位,补全23位)。
4. 听说爱用位运算的人技术都不太差
解释函数的原理,并分析使用位运算求平均值的优缺点。
int average(int nums[], int start, int end) {
if (start == end)
return nums[start];
int mid = (start + end) / 2;
int leftAvg = average(nums, start, mid);
int rightAvg = average(nums, mid + 1, end);
return (leftAvg & rightAvg) + ((leftAvg ^ rightAvg) >> 1);
}
考点:位运算符,对二进制的理解以及递归函数
&都为1才是1,^不同为1,相同为0,
首先如果start等于end,数组只包含一个元素,直接返回该元素作为平均值。
如果不等于,分别计算左半部分和右半部分的平均值。通过递归调用average函数实现,每次调用都将数组分成两半,最后,使用位运算来合并左右两部分的平均值。
leftAvg & rightAvg:计算左右平均值在无进位情况下的和(即只考虑二进制表示中相同位为1的情况)。然而,这一步在这个上下文中似乎并不直接有助于计算平均值,因为它忽略了进位。
二进制中只有1和0,&和^ 其实解决了相同还是相异的问题, (leftAvg ^ rightAvg) >> 1是解决不同位,使用异或运算(^)来找出左右平均值二进制表示中不同位的和,,右移一位(相当于除以2),求1和0的平均值,即0.5。&是解决相同位,相同位不用不用平均,所以不用右移,最后,将上述两个结果相加,得到最终的“平均值”。
优缺点:
优点:
理论上的效率:位运算通常比算术运算更快,因为它们直接在硬件级别上执行。然而,在这个特定的实现中,这种效率优势可能并不明显,因为算法的逻辑复杂性增加了。
缺点:
逻辑复杂性:这个算法的逻辑比简单的算术平均值计算要复杂得多,这可能会使代码更难理解和维护。
精度问题:这个算法没有正确地处理整数除法中的进位问题。在二进制表示中,简单的位运算无法准确地模拟十进制除法中的进位。因此,这个算法可能不会返回准确的平均值(特别是当左右两部分的平均值不是偶数时)。
性能问题:尽管位运算本身很快,但递归调用和不必要的位运算可能会导致性能下降,特别是对于大型数组。
5. 全局还是局部!!!
先思考输出是什么,再动动小手运行下代码,看跟自己想得结果一样不一样 >-<
int i = 1;
static int j = 15;
int func() {
int i = 10;
if (i > 5) i++;
printf(“i = %d, j = %d\n”, i, j);
return i % j;
}
int main() {
int a = func();
printf(“a = %d\n”, a);
printf(“i = %d, j = %d\n”, i, j);
return 0;
}
考点:static修饰全局变量,以及全局变量和局部变量
i = 11, j = 15
a = 11
i = 1, j = 15
首先看main函数,首先调用func函数,将返回值赋给变量a。在func函数中i的值为10,所以a的值为11。然后,打印出a = 11。最后,main函数打印出全局变量i和静态全局变量j的值。因为func函数只改变的是局部变量i,并没有影响全局变量i和静态全局变量j的值,所以打印的结果是i = 1, j = 15。
6. 指针的修罗场:改还是不改,这是个问题
指出以下代码中存在的问题,并帮粗心的学长改正问题。
int main(int argc, char **argv) {
int a = 1, b = 2;
const int *p = &a;
int * const q = &b;
*p = 3, q = &a;
const int * const r = &a;
*r = 4, r = &b;
return 0;
}
考点:两种类型,const int p和int const p
| 指针常量 | 常量指针 |
|---|---|
| 类型*const 指针名 | const 类型*指针名 |
| 数值可以改变,地址不能改变 | 数值不可以改变,地址能改变 |
const int * const r 地址和数值都不可以改变
7. 物极必反?
你了解argc和argv吗,这个程序里的argc和argv是什么?
程序输出是什么?解释一下为什么。
int main(int argc, char *argv[]) {
while (argc++ > 0);
int a = 1, b = argc, c = 0;
if (–a || b++ && c–)
for (int i = 0; argv[i] != NULL; i++)
printf(“argv[%d] = %s\n”, i, argv[i]);
printf(“a = %d, b = %d, c = %d\n”, a, b, c);
return 0;
}
argc表示命令行参数的数量。它至少为1,因为第一个命令行参数总是程序的名称。
argv是一个指向字符串数组的指针,这些字符串是传递给程序的命令行参数。argv[0]是程序的名称,argv[1]是第一个参数,依此类推。数组的最后一个元素后面跟着一个NULL指针,用于标识数组的结束。
例如test a.c b.c,argc=3,argv[0]=test,argv[1]=a.c,argv[2]=b.c
其中还有一个考点:运算符的短路
--a=0所以还会执行后面b++&c--,c--为零,所以不执行
a = 0, b = argc的原始值, c = -1
8. 指针?数组?数组指针?指针数组?
在主函数中定义如下变量:
int main() {
int a[2] = {4, 8};
int(*b)[2] = &a;
int *c[2] = {a, a + 1};
return 0;
}
说说输出分别是什么?
a, a + 1, &a, &a + 1, *(a + 1), sizeof(a), sizeof(&a)
*b, *b + 1, b, b + 1, *(*b + 1), sizeof(*b), sizeof(b)
c, c + 1, &c, &c + 1, **(c + 1), sizeof©, sizeof(&c)
6487568
6487572
6487568
6487576
8
8
8
6487568
6487572
6487568
6487576
8
8
8
6487552
6487560
6487552
6487568
8
16
8
a 的表达式
a:数组名,表示数组首元素的地址(即 &a[0])。
a + 1:指向数组第二个元素的指针(即 &a[1])。
&a:数组的地址(类型为 int ()[2])。
&a + 1:指向数组 a 后一个位置的指针(类型仍为 int ()[2],但指向了数组 a 后面的内存)。
*(a + 1):数组第二个元素的值,即 8。
sizeof(a):数组 a 的大小,即 2 * sizeof(int)。
sizeof(&a):指针 &a 的大小。
b 的表达式
b:解引用 b,得到数组 a(类型为 int[2])。
b + 1:指向数组 a 第二个元素的指针(即 &a[1])。
b:指向数组 a 的指针(类型为 int ()[2])。
b + 1:指向数组 a 后一个位置的指针(类型仍为 int ()[2],但指向了数组 a 后面的内存)。
*(b + 1):数组 a 的第二个元素的值,即 8。
sizeof(b):数组 a 的大小,即 2 * sizeof(int)。
sizeof(b):指针 b 的大小。
c 的表达式
c:数组名,表示数组首元素的地址(即 &c[0]),类型为 int。
c + 1:指向数组 c 第二个元素的指针(即 &c[1])。
&c:数组 c 的地址(类型为 int **[2])。
&c + 1:指向数组 c 后一个位置的指针(类型仍为 int *[2],但指向了数组 c 后面的内存)。
** (c + 1):数组 c 的第二个元素所指向的值,即 a[1] 的值,8。
sizeof©:数组 c 的大小,即 2 * sizeof(int)。
sizeof(&c):指针 &c 的大小。
9. 嘻嘻哈哈,好玩好玩
在宏的魔法下,数字与文字交织,猜猜结果是什么?
#define SQUARE(x) x *x

最低0.47元/天 解锁文章
1425






