西邮 Linux 兴趣小组 2025 纳新面试题
学长寄语:长期以来,西邮 Linux 兴趣小组的面试题以难度之高名扬西邮校内。我们作为出题人也清楚地知道这份试题略有难度。请你动手敲一敲代码。别担心,若有同学能完成一半的题目,就已经十分优秀。其次,相比于题目的答案,我们对你的思路和过程更感兴趣,或许你的答案略有瑕疵,但你正确的思路和对知识的理解足以为你赢得绝大多数的分数。最后,做题的过程也是学习和成长的过程,相信本试题对你更加熟悉地掌握 C 语言一定有所帮助。祝你好运。我们东区逸夫楼 FZ103 见!
- 本题目只作为西邮 Linux 兴趣小组 2025 纳新面试的有限参考。
- 为节省版面,本试题的程序源码省去了
include指令。 - 本试题中的程序源码仅用于考察 C 语言基础,不应当作为 C 语言「代码风格」的范例。
- 所有题目编译并运行于 x86_64 GNU/Linux 环境。
0. 拼命的企鹅
一只企鹅在爬山,每隔一段路都会遇到一块石头。第 1 块石头重量是 a,每往上走一段路,石头重量就会变成上一段的平方。企鹅可以选择把某些石头捡起来,最后把捡到的石头重量相乘。
它怎样捡石头,才能得到重量乘积恰好是 a 的 b 次方的石头?(比如 b = 173 时, 要捡哪些石头?)
题解:
- 代码:
1 | #include<stdio.h> |
输出结果:
当b = 173时,结果为:1,3,4,6,8
当b = 65时,结果为:1,7解释:
- 由题意可知,捡到的石头重量相乘,即对应石头指数相加,而我们可以将石头的重量:${a^n}$可以写作$a^{2^n}$,于是在相乘的过程中,可以等效转换为是2的n次幂的相加,即2进制转10进制。
- 通过每次对2进制下的
1读取下标,便可以输出1的位置。
- 考点:
2进制与10进制转换、通过数学工具来解决实际问题的能力
1. 西邮Linux欢迎你啊
请解释以下代码的运行结果。
1 | int main() { |
题解:
输出结果:
Hi guys! Welcome to Xiyou Linux 2025解释:
if语句中执行printf函数,其返回值:打印的字符长度
10,作为判断if语句成立的条件。由于两个printf的关系为或,第一个printf函数的返回值已经使if语句成立,故不执行第二个printf函数。结果为:Hi guys!在if语句内,printf函数嵌套了其他printf函数,从内向外依次输出,对应的返回值作为输出内容,以
%d的形式输出。结果为:Welcome to Xiyou Linux 2025。
考点:
“短路”、printf函数返回值
2. 可以和 \0 组一辈子字符串吗?
你能找到成功打印的语句吗?你能看出每个 if 中函数的返回值吗?这些函数各有什么特点?
1 | int main() { |
题解:
输出结果:
Welcome to Join us!
Xiyou Linux Group解释:
- 第一个if语句判断中,
strcmp函数进行了两个字符串的比较,由于'w'比'J'在字符表中更靠前,其ASCII码差值为正(非0),则if语句成立,执行printf函数。 sizeof(p3)是对指针的大小进行计算,则其值为8,strlen(p3)是对字符串整体的大小进行计算,在第一个'\0'停止且不包括'\0',故其值为17,比较结果在if语句中成立,执行printf函数。- 两个字符串的大小相等,故其if语句成立,执行
printf函数。
- 考点:
sizeof和strlen的使用
3. 数学没问题,浮点数有鬼
这个程序的输出是什么?解释为什么会这样?
1 | int main() { |
题解:
输出结果:
a1 + b1 != sum1
a2 + b2 == sum2解释:
- 浮点数本身并不精确,实际值在对应精度下无限趋近于所给值。故不相等。
- 三个数均为16进制数,其实际值即所给值。
16进制转10进制:若无小数,则10进制数=整数*2的n次方(n为p后数值)
- 考点:
16进制转10进制、浮点数
4. 不合群的数字
在一个数组中,所有数字都出现了偶数次,只有两个数字出现了奇数次,请聪明的你帮我看看以下的代码是如何找到这两个数字的呢?
1 | void findUndercoverIDs(int nums[], int size) { |
题解:
输出结果:
两个出现奇数次的数字解释:
整体函数巧妙地利用了位运算,得到了出现奇数次的两个数字。
- 根据异或的特点,即两个相同的数字的结果为0,进行异或运算。如若有两个数字出现了偶数次,便会在异或中被消除,于是我们得到了两个出现奇数次的数字异或的结果,即
xorAll。 - 对
xorAll与其本身的相反数(反码+1,即补码)进行位与运算。于是我们便得到了最低位的1。 - 对数组进行遍历,并在if语句条件判断中进行位与操作,如果结果成立,则把对应值赋给
id_a,否则把对应值给id_b。这个操作更精妙之处,在于通过每次的异或,不仅仅进行赋值操作,更去除了出现偶数次的数。 - 分别输出两个出现奇数次的数字。
- 考点:
位运算的进阶操作
5. 会一直循环吗?
你了解 argc 和 argv 吗,程序的输出是什么?为什么会这样?
1 | int main(int argc, char* argv[]) { |
题解:
输出结果:
argc = 1
argv[0] ./文件
argc = -2147483648解释:
- 打印了argc的数量,即第一次我们调用程序时,输入的命令行(./文件名称)
- 在while循环内进行了argc的判断与自增,并且在其中加入了argc的判断。argc在不断自增的过程中,由于其本身便是int类型,故会增加到一定大小后,变成负数。当变为负数时,if语句成立,输出argv[0],跳出while循环,不会argc的自增~
- 输出argc的大小。
- 考点:
argc与argv[]的含义
6. const 与指针:谁能动,谁不能动?
1 | struct P { |
说说下列操作是否合法,并解释原因:
ptr1->x = 100, ptr2->x = 300, ptr3->x = 500
ptr1->y = 200, ptr1 = &p2, ptr2->y = 400
ptr2 = &p1, ptr3->y = 600, ptr3 = &p1
题解:
输出结果:
| 合法 | 不合法 | 不合法 |
| 不合法 | 不合法 | 不合法 |
| 合法 | 不合法 | 不合法 |
- 解释:
- 首先定义了一个结构体,在结构体内有两个变量,
x是int类型,y是const int类型,即无法通过指针改变y的值。 p1指针锁定了他指向的位置,p2指针锁定了他指向的内容,p3指针锁定了他指向的位置和内容。三个指针的区别就在于const的修饰。*在const前,在const后,在两个const中间。鉴于博主本人在学习此处时容易混淆,于是自己想了个小口诀,针对*在const的相对位置,“左位右值”(高中记物理二级结论的DNA动了)。供大家参考。
- 考点:
const限定符、结构体
7. 指针!数组!
在主函数中定义如下变量:
1 | int main() { |
说说这几个表达式的输出分别是什么?
a, *b, *b + 1, b, b + 1, * (*b + 1), c, sizeof(a), sizeof(b), sizeof(&a), sizeof(f1)
题解:
输出结果:
a:a[0]的地址
*b:a[0]的地址
*b + 1:a[1]的地址
b:a的地址
b + 1:a末尾的地址
* (*b + 1):4
c:a[0]的地址的地址
sizeof(a):3(元素个数)*4(int字节大小)=12
sizeof(b):8
sizeof(&a):8
sizeof(f1):编译错误解释:
a[3]是一个含有三个int类型的数组
(*b)[3]是一个指向含有三个int元素类型的数组的指针
* c[3]是一个含有三个指向int指针类型的数组
f1是一个函数,他接受一个int类型的参数,返回为函数指针,该函数指针又可以接受一个int类型指针与int类型的参数,返回为int类型sizeof函数不可以操作函数。
- 考点:
sizeof的使用、数组,数组指针,指针数组的区别、函数指针(十分神奇的一项操作)
8. 全局还是局部!!!
观察程序输出,思考为什么?
1 | int g; |
题解:
输出结果:
hello linux解释:
arr[0]:h
arr[1]:e
arr[2]:l
arr[3]:l
arr[4]:$o$- 在调用函数的过程中,每次
j变量被增加,返回值被赋给了对应数组元素。 static静态局部变量下,初始化只进行一次,且j变量存储在数据段。
- 考点:
静态局部变量、ASCII码
9. 宏函数指针
观察程序结果,说说程序运行的过程:
1 | #define CALL_MAIN(main, x) (*(int (*)(int))*main)(x); |
题解:
输出结果:
2 4 8 16 32 64 128 256 512 1024解释:
- 首先,(int (*)(int))是一个函数指针,他保存的是他所指函数的地址,并且在CALL_MAIN内,把main函数强制转换为一个函数指针,由于这个函数指针的地址就是他本身,main函数,即他在指向自己。在这个函数指针内,他接受一个int类型的变量,返回int类型,这个函数也恰好符合main函数的声明。
- 由于这个函数在不断的递归自己,则需要一个跳出递归的条件,即在宏定义处的DOUBLE起到了作用。通过每次的运算改变argc的值,并且加入if语句来判断是否结束该函数,便成功的解决了无限递归的问题。
- resistry在声明时也是个函数指针,但是他本身是一个存储1个元素的数组,元素类型为int类型的指针。于是在resistry[0]内,保存的是指向main函数的函数指针。
- 在CALL_MAIN处,调用main函数,进行argc的运算,实现函数递归。
- 考点:
宏定义、函数指针、函数指针数组
10. 拼接 排序 去重
本题要求你编写以下函数,不能改动 main 函数里的代码。实现对 arr1 和 arr2 的拼接、排序和去重。你需要自行定义 result 结构体并使用 malloc 手动开辟内存。
1 | int main() { |
题解:
- 代码:
1 | #include<stdio.h> |
输出结果:
6 1 2 1 9 1 3 2 6 2 4 2 2 1 6 2
1 1 1 1 2 2 2 2 2 2 3 4 6 6 6 9
1 2 3 4 6 9解释:
- 首先定义结构体,其中结构体有
int类型的指针,和我们将要进行动态操作的数组。 - 在拼接函数内,首先为结构体内指针分配内存,用两个for循环,将两个数组依次输入到我们刚刚分配内存的指针内。我们之后的操作也是基于这个数组。记得把大小也赋值给结构体内,保存长度的
size变量。 - 在排序函数内,我使用的是快速排序。
- 在去重函数内,我对数组进行for循环遍历,由于该数组已经排序完毕,所以可以依次比较。最后将去重后的数组重新分配内存,并且重新赋值数组大小。
- 记得free内存。
- 考点:
结构体、排序算法、动态分配内存
11. 指针魔法
用你智慧的眼睛,透过这指针魔法的表象,看清其本质:
1 | void magic(int(*pa)[6], int** pp) { |
题解:
输出结果:
6 612 6 810 2解释:
- 在main函数内,先分配了一个数组,并且定义了一级指针
p,和二级指针pp。此时p内为a[1]的地址,pp为a[1]的地址的地址。接下来我们在magic函数内逐步分析。 - 首先
magic函数内传入了一个指向6个元素数组的指针的参数,和一个二级指针的参数。 - 第一行,
a[2]的值和a[1]的值相加,赋给了pp。此时数组状态为{ 2, 10, 6, 8, 10, 12 }。 - 第二行,把
a[5]的地址赋给了pp。此时数组状态为{ 2, 10, 6, 8, 10, 12 }。 - 第三行,把
a[5]的值和a[0]的值相减,赋给了a[5]。此时数组状态为{ 2, 10, 6, 8, 10, 10 }。 - 第四行,先在内层进行判断,如果
a[3]的值和1位与之后,结果非0,则把1作为参数,执行外层的代码。最终结果是把a[1]的地址赋值给了pp。此时数组状态为{ 2, 10, 6, 8, 10, 10 }。 - 第五行,把
a[1]和a[0]的值相加,赋给了a[1]。此时数组状态为{ 2, 12, 6, 8, 10, 10 }。 - 第六行,把
a[2]的地址赋给了pp。此时数组状态为{ 2, 12, 6, 8, 10, 10 }。 - 最后printf函数输出时,
*p和**pp为6,p-a为2,即元素位置之差。其他对应输出相应数组元素即可。
- 考点:
数组指针、二级指针
12. 奇怪的循环
你能看明白这个程序怎样运行吗?试着理解这个程序吧!
1 | union data { |
题解:
输出结果:
Your journey begins here!2025Your journey begins here!2026Your journey begins here!2027Your journey begins here!2028解释:
- 首先在计算
sizeof(Node)时,要注意结构体和共用体的内存对齐,对于共用体来说,内存共用且为最长字节类型的最小整数倍,即24。对结构体来说,每个变量对齐时,其对齐位置为上一次对齐后的下一位,为最长字节类型的倍数,总内存为最小整数倍,即40。 use函数指针指向了func1,在进入while循环内,伴随着每一次条件的判断,在func1和func2来回交替执行,直到条件不成立。
- 考点:
结构体和共用体内存对齐、函数指针
13. GNU/Linux (选做)
注:嘿!你或许对 Linux 命令不是很熟悉,甚至你没听说过 Linux。但别担心,这是选做题,了解 Linux 是加分项,但不了解也不扣分哦!
- 你知道 cd 命令的用法与 / ~ - 这些符号的含义吗?
- 你知道 Linux 系统如何创建和删除一个目录吗?
- 请问你还懂得哪些与 GNU/Linux 相关的知识呢?
题解:
- 输出结果:
cd:更改目录
cd /:切换至根目录
cd ~:切换至用户主目录
cd -:切换至上一级目录mkdir 目录名称
rmdir 目录名称ls:列出文件
touch:创建文件
clear:清除终端页面
……
D