西邮Linux兴趣小组2024纳新面试题
学长寄语:长期以来,西邮 Linux 兴趣小组的面试题以难度之高名扬西邮校内。我们作为出题人也清楚的知道这份试题略有难度。请你动手敲一敲代码。别担心,若有同学能完成一半的题目,就已经十分优秀。其次,相比于题目的答案,我们对你的思路和过程更感兴趣,或许你的答案略有瑕疵,但你正确的思路和对知识的理解足以为你赢得绝大多数的分数。最后,做题的过程也是学习和成长的过程,相信本试题对你更加熟悉地掌握 C 语言一定有所帮助。祝你好运。我们东区逸夫楼 FZ103 见!
① 本题目只作为西邮 Linux 兴趣小组 2024 纳新面试的有限参考。
② 为节省版面,本试题的程序源码省去了 #include 指令。
③ 本试题中的程序源码仅用于考察 C 语言基础,不应当作为 C 语言「代码风格」的范例。
④ 所有题目编译并运行于 x86_64 GNU/Linux 环境。
0. 聪明的吗喽
· Q:
一个小猴子边上有 100 根香蕉,它要走过 50 米才能到家,每次它最多搬 50 根香蕉,(多了就拿不动了),它每走 1 米就要吃掉一根,请问它最多能把多少根香蕉搬到家里。
(提示:他可以把香蕉放下往返走,但是必须保证它每走一米都能有香蕉吃。也可以走到 n 米时,放下一些香蕉,拿着 n 根香蕉走回去重新搬 50 根。)
· A:
这道题考察思维逻辑
理解:
1.首先小猴应该一次带50根香蕉,才能实现利益最大化;
2.可以从一步开始尝试,逐步递加观察所剩香蕉个数变化;
3.找出临界值,观察哪步可以实现所剩香蕉数最大;
4.将最大香蕉数作为答案写出.
1. 西邮Linux欢迎你啊
· Q :
请解释以下代码的运行结果:
int main() {
unsigned int a = 2024;
for (; a >= 0; a--)
printf("%d\n", printf("Hi guys! Join Linux - 2%d", printf("")));
return 0;
}
· A :
- 首先逐行分析上述代码,
unsigned int代表无符号整形,不会存在a为负数的情况;for循环语句括号内,包含起始表达式、条件表达式、末尾循环体,其中语句均可省略(省略条件表达式时,循环不会自主结束),因为a为无符号整形,故a永远也不可能小于0,循环无限执行下去。
- –注释:(此处为人工智能解答)
- 无符号整型范围
- 对于n位无符号整型,它的所有位都用于表示数值大小。最小值是所有位为0,即0;最大值是所有位为1。
- 例如8位无符号整型,最大值对应的二进制是11111111,转换为十进制就是2^8 - 1=255。一般地,n位无符号整型的范围是0到2^n - 1。
- 有符号整型范围
- 对于n位有符号整型,最高位是符号位(0表示正,1表示负),剩下n - 1位用于表示数值。
- 正数最大值是符号位为0,其余位为1,例如8位有符号整型中,最大正数对应的二进制是01111111,转换为十进制是2^7 - 1 = 127。
- 负数最小值是符号位为1,其余位为0(以补码形式存储),例如8位有符号整型最小值对应的二进制(补码)是10000000,转换为十进制是-27=-128。一般地,n位有符号整型的范围是-2{n - 1}到2^{n - 1}-1。
- 由于printf函数嵌套,先执行括号里的,因为最内部的printf什么也不打印,故printf函数返回0,中间的printf函数打印Hi guys! Join Linux - 2,并且占位符%d被替换为0;最外层的printf函数打印中间循环语句括号内,包含起始表达式、条件表达式、末尾循环体,其中语句均可省略(省略条件表达式时,循环不会自主结束),因为a为无符号整形,故a永远也不可能小于0,循环无限执行下去。函数的返回值24。
- 最后输出为Hi guys! Join Linux - 2024并且无限循环。
2. 眼见不一定为实
· Q :
输出为什么和想象中不太一样?
你了解 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;
}
· A :
-
sizeof是一个运算符,计算时连带’\0’,且可用类型做参数(计算结果为占空间内存字节数)
-
strlen是string.h中的一个函数,当计算到’\0’时停止,且只可用char*做参数(计算结果为字符串长度)
-
strcmp比较p0和p1,因为p0和p1相等,故返回’0’;
对strcmp函数的解释:
- 头文件为<string.h>;
- 比较两个字符串,逐个字符进行比较(比较ASCII码值),当遇到’\0’或遇到不同字符时停止比较并返回结果,当第一个字符串小时,返回一个负数,相等时返回0,当第一个字符串大时,返回一个正数;
-
p0和p1的字符串长度不同,故不相等返回0;而p0和p1在’\0’前长度相同,故相等返回1。
3. 1.1 - 1.0 != 0.1
· Q :
为什么会这样,除了下面给出的一种方法,还有什么方法可以避免这个问题?
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");
}
· A :
- 首先输出依次为:false和true,由于浮点数在计算机中存储方式并不精确,存在精度问题,故第一个输出为false。
- 解决方案:
- 定义一个很小的精度值,用结果与其进行比较;
- 采用高精度数学库(#include<gmp.h>)(注意高精度浮点数的类型为"mpf_t",比较为"mpf_cmp",最后清理变量使用"mpf_clear")
- 将浮点型转换为整形进行比较
4. 听说爱用位运算的人技术都不太差
· Q :
解释函数的原理,并分析使用位运算求平均值的优缺点。
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);
}
· A :
- 函数接收一个整形数组,两个整形,函数内部使用递归的方法,将传入的数组分为左右两个子区间分别进行计算平均值,重复上述操作,直到数组中无法再分出子区间为止,将所得值依次计算;
- 位运算解释:
- ‘&’为按位与,当二进制位都为1时结果为1,否则为0;
- ‘|’为按位或,当二进制位至少有一个为1时结果为1,否则为0;
- ‘^’为按位异或,当二进制位不同时结果为1,否则为0;
- ‘>>’为右移操作符,表示将操作数的二进制位向右移动指定的位数,右移1位相当于除以2(注:无符号整数移动后高位补0,有符号整数移动后高位补符号位)
- 位运算求平均值:
- 优点:速度快(可能减少通过运算步骤来实现);避免除法运算(适用于特定场合);
- 缺点:精度差;理解难度大;
5. 全局还是局部!!!
· Q :
先思考输出是什么,再动动小手运行下代码,看跟自己想得结果一样不一样 >-<
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;
}
· A :
- 首先i为全局变量,j为静态全局变量(作用域仅限于定义它的源文件);
- 在函数func中新定义了局部变量i屏蔽了全局变量i(出函数则销毁)使用后在函数内部输出为i = 11, j = 15,i % j为11,故返回11;
- 在主函数中a被赋值为11,i为全局变量值为1,j仍为15;
6. 指针的修罗场:改还是不改,这是个问题
· Q :
指出以下代码中存在的问题,并帮粗心的学长改正问题。
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;
}
· A :
- 解读此代码:p为指向常量整数的指针;q为常量指针;r为指向常量整数的常量指针;
- *p = 3错误,p不可修改常量,可改为p = &b;
- q = &a错误,q为常量指针,不可修改其指向的地址,可改为*q = a;
- 因为r是一个指向常量整数的常量指针,故 *r = 4, r = &b均为错误写法,可将r的定义类型改变(去掉const);
7. 物极必反?
· Q :
你了解 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;
}
· A :
- argc是一个整数,表示命令行参数的个数(包括程序名本身);argv是一个字符指针数组,每一个元素都是一个指向字符串的指针,字符串为命令行参数(argv[0]表示程序名);
- argc起始为一个正值,进入循环后一直++,直到整形所能存放的最大数,下一次++后,argc则为整形所能储存的最小负数-2^31;
- “&&”为逻辑与,若两边条件均为真,才继续进行(若前一语句为假,则断路);“||”为逻辑或,只要一方为真,则继续进行(若前一语句为真,则短路)
- a = 1,–a为0(前置–是先给a-1后再使用),所以进行b++,因为b++不为0,但c为0,故不进行下一条语句判断;
- 最后一行输出中,a为0;b为-2^31+1;c为-1;
8. 指针?数组?数组指针?指针数组?
· Q :
在主函数中定义如下变量:
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(c), sizeof(&c)
· A :
-
- a为数组名,数组名输出为数组首元素的指针;
- a + 1为指向数组中第二个元素的指针,输出比a大一个sizeof(int);
- &a为一个指向整个数组的指针,输出应为数组首元素地址;
- &a + 1为指向下一个同类型数组的地址,输出结果应比&a大sizeof(int[2])(因为a为int*[2]);
- *(a + 1) 是对a + 1解引用,a + 1为第二个元素,故输出为8;
- *sizeof(a)*为计算a的长度,因为a为数组,int类型,所以结果为8;
- *sizeof(&a)*为计算一个指针的大小,在x64环境下,输出为8;
-
- *b为解引用指向数组的指针,得到数组a本身;
- 同理,*b + 1则与a + 1得到的结果相同;
- b为&a,输出与&a相同;同理,b + 1与&a + 1结果相同;
- *(*b + 1)则与*(a + 1)结果相同;
- *sizeof(*b)*则与sizeof(a)相同,所以结果为8;
- *sizeof(b)*则与sizeof(&a)相同,在x64环境下,输出为8;
-
- c为一个指针数组,输出为指向a的指针的地址;
- c + 1则代表c的第二个数组元素的指针,输出比c大8;
- &c是指向指针数组的指针,类型为int**,输出为指针数组的地址;
- &c + 1为指向下一个同类型指针数组的地址,若在x64环境下,指针大小为8,则&c + 1比&c大16(大了sizeof(int*[2]));
- **(c + 1) 是对*(c + 1)解引用,即为指针数组的第二个元素指向的内容,也就是a[1],输出为8;
- *sizeof©*计算c的大小,在x64环境,指针大小为8,则输出为16;
- *sizeof(&c)*计算&c的大小,因为&c为指针,在x64环境下,输出8;
9. 嘻嘻哈哈,好玩好玩
· Q :
在宏的魔法下,数字与文字交织,猜猜结果是什么?
#define SQUARE(x) x *x
#define MAX(a, b) (a > b) ? a : b;
#define PRINT(x) printf("嘻嘻,结果你猜对了吗,包%d滴\n", x);
#define CONCAT(a, b) a##b
int main() {
int CONCAT(x, 1) = 5;
int CONCAT(y, 2) = 3;
int max = MAX(SQUARE(x1 + 1), SQUARE(y2))
PRINT(max)
return 0;
}
· A :
- 首先分析代码,定义了四个宏,其中涉及到运算为:
'*'为相乘;(a > b) ? a : b;为三目运算符,若条件表达式运算结果为真,则整个结果为表达式一的运算结果,反之,则为表达式二的运算结果;a##b用于拼接两个标识符a与b;
- 主函数中x1为5, y2为3, SQUARE(x1 + 1)的结果为11, SQUARE(y2)结果为9;
10. 我写的排序最快
· Q :
写一个 your_sort 函数,要求不能改动 main 函数里的代码,对 arr1 和 arr2 两个数组进行升序排序并剔除相同元素,最后将排序结果放入 result 结构体中。
int main() {
int arr1[] = {2, 3, 1, 3, 2, 4, 6, 7, 9, 2, 10};
int arr2[] = {2, 1, 4, 3, 9, 6, 8};
int len1 = sizeof(arr1) / sizeof(arr1[0]);
int len2 = sizeof(arr2) / sizeof(arr2[0]);
result result;
your_sort(arr1, len1, arr2, len2, &result);
for (int i = 0; i < result.len; i++) {
printf("%d ", result.arr[i]);
}
free(result.arr);
return 0;
}
· A :
#include <stdio.h>
#include <stdlib.h>
typedef struct {
int len;
int* arr;
} result;
void your_sort(int* arr1, int len1, int* arr2, int len2, result* res) {
int all = len1 + len2;
int* str = (int*)malloc(all * sizeof(int));
int j = 0;
for (int i = 0; i < len1; i++) {
str[j] = arr1[i];
j++;
}
for (int i = 0; i < len2; i++) {
str[j] = arr2[i];
j++;
}
for (int i = 0; i < all; i++)
{
for (int k = 0; k < all-i-1; k++)
{
if (str[i] < str[k])
{
int p = str[i];
str[i] = str[k];
str[k] = p;
}
}
}
int c = 0;
int arr3[1000] = { 0 };
for (int i = 0; i < all; i++)
{
if (i == 0 || str[i] != str[i - 1])
{
arr3[c] = str[i];
c++;
}
}
res->len = c;
res->arr = (int*)malloc(c * sizeof(int));
if (res->arr == NULL) {
return;
}
for (int i = 0; i < c; i++) {
res->arr[i] = arr3[i];
}
free(str);
}
int main() {
int arr1[] = { 2, 3, 1, 3, 2, 4, 6, 7, 9, 2, 10 };
int arr2[] = { 2, 1, 4, 3, 9, 6, 8 };
int len1 = sizeof(arr1) / sizeof(arr1[0]);
int len2 = sizeof(arr2) / sizeof(arr2[0]);
result result;
your_sort(arr1, len1, arr2, len2, &result);
for (int i = 0; i < result.len; i++) {
printf("%d ", result.arr[i]);
}
free(result.arr);
return 0;
}
代码分析:
-
代码作用:对 arr1 和 arr2 两个数组进行升序排序并剔除相同元素,最后将排序结果放入 result 结构体中。 -
代码解释说明:-
typedf将结构体重命名为result,结构体内包含两个成员一个为整形len,另一个为指针arr;
-
在your_sort函数内部:
- 定义一个变量all为两个主函数内的数组长度相加值;
- 分配足够大的空间给str,用循环将传入的两个数组合并在str中;
- 使用冒泡排序,将str中的各数进行升序排序;
- 将相同元素剔除,并定义一个变量c和数组arr3,c用于储存剔除后的数组的长度,arr3用于储存已剔除的数组;
- 结构体指针ren将结构体成员len为c,并为结构体内的arr指针分配足够大的内存空间以便于后续储存arr3数组,if语句用于判断是否分配内存成功,若分配动态内存成功,则将arr3复制进arr内;
- 用free来释放分配了内存的str;
-
在主函数中:
- 定义两个数组并计算长度;
- 定义一个 result 结构体变量名为 result ;
- 调用your_sort函数对数组进行合并,排序与剔同;
- 用for循环将arr结果输出;
-
-
补充说明:
- malloc函数在此代码中的注意点:
- 在malloc后()内的值为所申请需要的空间大小,之后返回void*类型的指针,但void*类型的指针不可直接赋值给别的类型的指针,故应进行强制类型转化后再进行赋值;
- malloc使用完毕后需将所分配内存释放回去(使用free());
- 使用时需检查返回值是否为NULL,目的是为了检查是否成功分配内存;
- malloc函数在此代码中的注意点:
11. 猜猜我是谁
在指针的迷宫中,五个数字化身为神秘的符号,等待被逐一揭示。
· Q :
int main() {
void *a[] = {(void *)1, (void *)2, (void *)3, (void *)4, (void *)5};
printf("%d\n", *((char *)a + 1));
printf("%d\n", *(int *)(char *)a + 1);
printf("%d\n", *((int *)a + 2));
printf("%lld\n", *((long long *)a + 3));
printf("%d\n", *((short *)a + 4));
return 0;
}
· A :
- 首先定义了一个数组a,并将整形强制转化为
void*; - a是void*类型的指针数组,(char*)a是将a的首元素地址转化为
char*类型的指针,再+1相当于偏移一个字节的大小,因为默认void*为8个字节,而偏移后不与8对齐,故输出为0; - 首先将首地址转为
char*类型后又转化为int*类型,解引用后为1,故结果为2; - 首先将首元素地址转化为
int*类型,+2相当于偏移4 * 2个字节,指向第二个元素,故解引用后输出为2; - 首先将首元素地址转化为
long long*类型,+3相当于偏移8 * 3个字节,指向第四个元素,故解引用后输出为4; - 首先将首元素地址转化为
short*类型,+4相当于偏移2 * 4个字节,指向第二个元素,故解引用后输出为2;
12. 结构体变小写奇遇记
· Q :
计算出 Node 结构体的大小,并解释以下代码的运行结果。
union data {
int a;
double b;
short c;
};
typedef struct node {
long long a;
union data b;
void (*change)( struct node *n);
char string[0];
} Node;
void func(Node *node) {
for (size_t i = 0; node->string[i] != '\0'; i++)
node->string[i] = tolower(node->string[i]);
}
int main() {
const char *s = "WELCOME TO XIYOULINUX_GROUP!";
Node *P = (Node *)malloc(sizeof(Node) + (strlen(s) + 1) * sizeof(char));
strcpy(P->string, s);
P->change = func;
P->change(P);
printf("%s\n", P->string);
return 0;
}
· A :
- 首先定义了一个
联合体(共用体)data,其中包含了三个类型的成员; - 定义了一个
结构体struct node并命名为Node,结构体内含四个成员,其中b为联合体,change为函数指针; func函数的解释:- 接收值为一个指向结构体的指针;
- 引用tolower函数将string内的大写字母转换为小写字母;
主函数的解释:- 定义一个指向常量字符串的指针s;
- 定义一个结构体指针P并分配内存以便P指向的string可储存字符串s,大小为Node结构体的大小加字符串s的大小加一个字符大小;
- 使用strcpy函数将s复制进结构体内的string中;
- 将结构体内的指针change指向func函数;
- 将指针P引入func函数中;
- 输出结构体中的string所储存的字符串;
- 补充说明(查阅人工智能后理解):
-
tolower函数:- 定义在 <ctype.h> 头文件中;
- 检查传入的字符是否是大写字母(在ASCII码中,‘A’ - 'Z’的范围是65 - 90);
- 如果是大写字母,就将其ASCII码值加上32,从而转换为对应的小写字母(在ASCII码中,‘a’ - ‘z’的范围是97 - 122)。例如,大写字母’A’(ASCII码值为65)经过 tolower 函数处理后会变成小写字母’a’(ASCII码值为97);
-
strcpy函数:- 定义在 <string.h> 头文件中;
- 将一个字符串(包括结束符 ‘\0’ )从源字符串复制到目标字符串;
- 函数原型为 char *strcpy(char *dest, const char *src) ;
- 其中 dest 是目标字符串的指针,它指向要接收复制内容的字符数组。 src 是源字符串的指针,它指向被复制的字符数组;
- 注意事项(人工智能检索):目标字符数组 dest 必须有足够的空间来存储源字符串 src ,包括结束符 ‘\0’ ,如果 dest 的空间不足,会导致缓冲区溢出,这是一种严重的错误,可能会覆盖其他内存区域的数据,导致程序出现不可预测的行为,如崩溃或数据损坏,并且源字符串 src 应该是以 ‘\0’ 结尾的有效字符串;
- strcpy 函数返回目标字符串 dest 的指针;
-
13. GNU/Linux (选做)
注:嘿!你或许对Linux命令不是很熟悉,甚至没听说过Linux。
但别担心,这是选做题,了解Linux是加分项,不了解也不扣分哦!
· Q :
- 你知道 ls 命令的用法与 / . ~ 这些符号的含义吗?
- 你知道 Linux 中权限 rwx 的含义吗?
- 请问你还懂得哪些与 GNU/Linux 相关的知识呢~
· A :
- 在Linux系统中, ls 命令用于列出目录内容。
基本用法:
- 直接输入 ls ,会列出当前目录下的文件和子目录名称,但不包括隐藏文件(以“.”开头的文件)。
- ls -a 可以列出当前目录下的所有文件和子目录,包括隐藏文件。例如,在包含隐藏配置文件的目录下使用这个命令,就能看到全部文件。
- ls -l 会以长格式显示文件和目录的详细信息,像文件类型、权限、硬链接数、所有者、组、大小、修改日期和文件名。如查看某个软件安装目录下文件的详细属性时可以使用。
- ls -t 会按照文件修改时间排序,最新修改的文件排在前面,这在查找最近操作的文件时很有用。
- 也可以组合使用这些参数,如 ls -alt ,它会以长格式列出所有文件并且按修改时间排序。
- 另外,如果要查看指定目录的内容,在 ls 后加上目录路径即可,如 ls /home/user/Documents 。
-
在Linux系统中:
-
“/”是根目录的符号。它是整个文件系统的起始点,所有的文件和目录都在根目录下以层次结构组织起来。比如“/bin”是存储二进制可执行文件的目录,“/etc”存放系统配置文件。
-
“.”表示当前目录。在执行命令或指定路径等操作时,如果用到“.”,就代表操作是在当前所在目录进行。例如,“./script.sh”表示运行当前目录下的“script.sh”脚本。
-
“”代表当前用户的主目录。每个用户在系统中有一个自己的主目录,用于存放个人文件等。例如,用户“user1”登录后的主目录可能是“/home/user1”,使用“”就可以方便地引用这个目录,像“cp file1 ~”就是把“file1”复制到当前用户的主目录下。
-
-
在Linux中,rwx用于表示文件或目录的权限。
-
“r”代表“read”(读取)权限。对于文件而言,具有读取权限意味着可以查看文件内容,比如用“cat”命令查看文本文件内容。对于目录来说,有读取权限才能列出目录中的内容,像使用“ls”命令查看该目录下有哪些文件和子目录。
-
“w”代表“write”(写入)权限。就文件来讲,有写入权限可以修改文件内容或删除文件。对于目录,此权限可以在目录中创建、删除或重命名文件。
-
“x”代表“execute”(执行)权限。如果是文件,特别是脚本文件或二进制文件,有执行权限才能运行这个文件。对于目录,有执行权限才能够进入目录,如使用“cd”命令进入该目录。
-
(注:关于linux的答案为查阅人工智能所得)
结语:
🎉 恭喜你成功完成了所有挑战!(▽)/!这是一项了不起的成就。👏
无论结果如何,相信这个过程已经让你对 C 语言和 Linux 有了更深入的了解。
记住,编程是一个持续学习的过程。
唯有脚踏实地,笃行不怠,方能拨云雾而见青天!
西邮Linux兴趣小组2024纳新C语言面试题解析

被折叠的 条评论
为什么被折叠?



