学长寄语:长期以来,西邮Linux兴趣小组的面试题以难度之高名扬西邮校内。我们作为出题人也清楚的知道这份试题略有难度。请你动手敲一下代码。别担心,若有同学能完成一半的题目,就已经十分优秀。其次,相比于题目的答案,我们对你的思路和过程更感兴趣,或许你的答案略有瑕疵,但你正确的思路和对知识的理解足以为你赢得绝大多数的分数。最后,做题的过程也是学习和成长的过程,相信本试题对你更加熟悉的掌握C语言的一定有所帮助。祝你好运。我们东区逸夫楼FZ103见!
本题目只作为西邮Linux兴趣小组2023纳新面试的有限参考。
为节省版面,本试题的程序源码省去了#include指令。
本试题中的程序源码仅用于考察C语言基础,不应当作为C语言「代码风格」的范例。
所有题目编译并运行于x86_64 GNU/Linux环境。
0. 鼠鼠我啊,要被祸害了
有1000瓶水,其中有一瓶有毒,小白鼠只要尝一点带毒的水,24小时 后就会准时死亡。至少要多少只小白鼠才能在24小时内鉴别出哪瓶水有毒?
这个题考察二进制,用0,1表示死和生,也是喝与没喝,如果有一个小鼠,它可以表示为0,1,也就是2瓶水,那两只为4瓶水,我们可以给每瓶水编号,从0到999。用二进制表示,这需要10位(因为2^10=1024),即十只小鼠,每瓶水中位数是1,就让它喝,比如0001000101,让是1的小鼠喝水,24小时后看每只小鼠存活情况,就可以知道哪瓶水有毒了。
1. 先预测一下~ 按照函数要求输入自己的姓名试试~
char *welcome () { }int main (void ) { char *a = welcome(); printf ("Hi, 我相信 %s 可以面试成功!\n" , a); return 0 ; }
char *welcome () { char *name = "hhh" ; return name; }int main (void ) { char *a = welcome(); printf ("Hi, 我相信 %s 可以面试成功!\n" , a); return 0 ; }
当你执行 char * name = “hhh”; 时,name 被赋值为指向字符串的首地址,即第一个字符 ‘h’ 的地址,通过这个指针,你可以访问整个字符串。这里返回的是第一个h的地址,所以当后面使用 %s 来读取整个字符串。
char *welcome () { return 'hhh' ;
char *welcome () { static char name[] = "hhh" ; return name;
这是另外的两个方法。
2. 欢迎来到Linux兴趣小组 有趣的输出,为什么会这样子呢~
int main (void ) { char *ptr0 = "Welcome to Xiyou Linux!" ; char ptr1[] = "Welcome to Xiyou Linux!" ; if (*ptr0 == *ptr1) { printf ("%d\n" , printf ("Hello, Linux Group - 2%d" , printf ("" ))); } int diff = ptr0 - ptr1; printf ("Pointer Difference: %d\n" , diff); }
Hello, Linux Group - 2023 Pointer Difference: 1431667956
if语句内指针解引用前一个指向常量字符串的‘w’。ptr1是数组,数组名即是首元素的地址,解引用ptr1是字符数组(字符串)的‘w’,都是字符’w’,执行if下面的语句。此处为printf函数嵌套,用到了printf的返回值,printf返回值为打印元素的个数.并且进入嵌套时从嵌套外进去嵌套,读取时从最内侧的开始向外侧读取,由于最里面没有东西,所以返回值是0,最外面的那个看的是里面printf的字符个数,一共有23个字符,所以最后会打印2023。后面是两个地址相减,虽然两个解引用的东西一样,但地址不一样。所以值是随机的。
3. 一切都翻倍了吗
请尝试解释一下程序的输出。 请谈谈对sizeof()和strlen()的理解吧。 什么是sprintf(),它的参数以及返回值又是什么呢?
int main (void ) { char arr[] = {'L' , 'i' , 'n' , 'u' , 'x' , '\0' , '!' }, str[20 ]; short num = 520 ; int num2 = 1314 ; printf ("%zu\t%zu\t%zu\n" , sizeof (*&arr), sizeof (arr + 0 ), sizeof (num = num2 + 4 )); printf ("%d\n" , sprintf (str, "0x%x" , num) == num); printf ("%zu\t%zu\n" , strlen (&str[0 ] + 1 ), strlen (arr + 0 )); }
第一个是对arr数组先取地址,然后再进行解引用,得到的是数组的大小,这里有七个char类型的字符,所以这里大小是7;这里是arr + 0,就是arr的首地址,这里是指针 num是short类型所以是两个字节;后面用到了sprintf的相关知识,这里是将num转化为16进制然后再写入str这个数组,由于与num不相等,所以这里输出为0;后面+1,从第二个地址开始算,一共5个字符,所以为4,arr数组有\0前面有5个元素,所以为5。
sprintf详解
4. 奇怪的输出 程序的输出结果是什么?解释一下为什么出现该结果吧~
int main (void ) { char a = 64 & 127 ; char b = 64 ^ 127 ; char c = -64 >> 6 ; char ch = a + b - c; printf ("a = %d b = %d c = %d\n" , a, b, c); printf ("ch = %d\n" , ch); }
位运算详解
a = 64 b = 63 c = -1 ch = -128
5. 乍一看就不想看的函数
“人们常说互联网凛冬已至,要提高自己的竞争力,可我怎么卷都卷不过别人,只好用一些奇技淫巧让我的代码变得高深莫测。”
这个func()函数的功能是什么?是如何实现的?
int func (int a, int b) { if (!a) return b; return func((a & b) << 1 , a ^ b); }int main (void ) { int a = 4 , b = 9 , c = -7 ; printf ("%d\n" , func(a, func(b, c))); }
这个函数是计算两个值的加法运算,&:计算进位
;^:计算不带进位的和
。a & b取相同元素, << 1 相当于进位,a ^ b取不同元素,通过不断的递归,直到不再进位,最终求出加法最后的结果。
6. 自定义过滤 请实现filter()函数:过滤满足条件的数组元素。
提示:使用函数指针作为函数参数并且你需要为新数组分配空间。
typedef int (*Predicate) (int ) ;int *filter (int *array , int length, Predicate predicate, int *resultLength) ; int isPositive (int num) { return num > 0 ; }int main (void ) { int array [] = {-3 , -2 , -1 , 0 , 1 , 2 , 3 , 4 , 5 , 6 }; int length = sizeof (array ) / sizeof (array [0 ]); int resultLength; int *filteredNumbers = filter(array , length, isPositive, &resultLength); for (int i = 0 ; i < resultLength; i++) { printf ("%d " , filteredNumbers[i]); } printf ("\n" ); free (filteredNumbers); return 0 ; }
typedef int (*Predicate) (int ) ;int *filter (int *array , int length, Predicate predicate, int *resultLength) { int *filteredNumbers = (int *)malloc (length * sizeof (int )); if (filteredNumbers == NULL ) { cout << "error" ; exit (1 ); } int x = 0 ; for (int i = 0 ; i < length; i++) { if (predicate(array [i])) { filteredNumbers[x++] = array [i]; } } *resultLength = x; return filteredNumbers; }int isPositive (int num) { return num > 0 ; }
7. 静…态…
如何理解关键字static? static与变量结合后有什么作用? static与函数结合后有什么作用? static与指针结合后有什么作用? static如何影响内存分配?
static是 C/C++中的关键字之一,是常见的函数与变量的修饰符,它常被用来控制变量的存储方式和作用范围。 1.
如果静态变量没有显式初始化,编译器会自动将其初始化为零,用 static 声明的变量在程序的整个运行期间存在。2.
无论变量是在函数内还是外部,静态变量在程序开始时分配内存,并在程序结束时释放。3.
静态变量通常存储在静态区,这意味着它们不会随着函数调用的结束而被销毁。4.
在函数内部声明的静态变量,其作用域仅限于该函数,但生命周期却是全程有效的,全局静态变量的作用域限制在定义它的文件中,其他文件无法访问。
修饰局部变量(称为静态局部变量)
修饰全局变量(称为静态全局变量)
修饰函数(称为静态函数)
static的详解
8. 救命!指针!
数组指针是什么?指针数组是什么?函数指针呢?用自己的话说出来更好哦,下面数据类型的含义都是什么呢?
int (*p)[10 ];const int * p[10 ];int (*f1(int ))(int *, int );
第一个是数组指针,是指向数组的指针,里面存放的是地址; 第二个是常量指针数组,是一个数组,其中每个元素都是指向变量的指针,且值无法更改。 第三个是函数指针,返回的函数将返回一个 int 类型的值,并接受一个 int 和一个 int 类型的参数。 *
9. 咋不循环了 程序直接运行,输出的内容是什么意思?
int main (int argc, char * argv[]) { printf ("[%d]\n" , argc); while (argc) { ++argc; } int i = -1 , j = argc, k = 1 ; i++ && j++ || k++; printf ("i = %d, j = %d, k = %d\n" , i, j, k); return EXIT_SUCCESS; }
==argv[]是命令行参数,argc是从命令行传给程序的参数个数(至少为1)== 在没有传入参数时 argc = 1,先给argc加,会让他从1加到最大,直到溢出为-2147483648,然后会继续执行,直到argc = 0,跳出while循环。i++,i = 0,j = argc = 0,j++,j = 1,k++,k = 2.
10. 到底是不是TWO #define CAL(a) a * a * a #define MAGIC_CAL(a, b) CAL(a) + CAL(b) int main (void ) { int nums = 1 ; if (16 / CAL(2 ) == 2 ) { printf ("I'm TWO(ノ>ω<)ノ\n" ); } else { int nums = MAGIC_CAL(++nums, 2 ); } printf ("%d\n" , nums); }
这里是宏定义没加括号,所以变成了
16 / 2 * 2 * 2 = 32
所以这里不会执行I’m TWO(ノ>ω<)ノ,else里重新定义了一个新的nums,==出了else,生命周期就结束了==,所以不会影响结果,即nums = 1;
11. 克隆困境 试着运行一下程序,为什么会出现这样的结果?
直接将s2赋值给s1会出现哪些问题,应该如何解决?请写出相应代码。
struct Student { char *name; int age; };void initializeStudent (struct Student *student, const char *name, int age) { student->name = (char *)malloc (strlen (name) + 1 ); strcpy (student->name, name); student->age = age; }int main (void ) { struct Student s1 , s2 ; initializeStudent(&s1, "Tom" , 18 ); initializeStudent(&s2, "Jerry" , 28 ); s1 = s2; printf ("s1的姓名: %s 年龄: %d\n" , s1.name, s1.age); printf ("s2的姓名: %s 年龄: %d\n" , s2.name, s2.age); free (s1.name); free (s2.name); return 0 ; }
Student s3 = s1; s1 = s2; s2 = s3;
直接将s1 = s2,会使s1本来指向的内存空间,使其无法正常释放。
12. 你好,我是内存 作为一名合格的C-Coder,一定对内存很敏感吧~来尝试理解这个程序吧!
struct structure { int foo; union { int integer; char string [11 ]; void *pointer; } node; short bar; long long baz; int array [7 ]; };int main (void ) { int arr[] = {0x590ff23c , 0x2fbc5a4d , 0x636c6557 , 0x20656d6f , 0x58206f74 , 0x20545055 , 0x6577202c , 0x6d6f636c , 0x6f742065 , 0x79695820 , 0x4c20756f , 0x78756e69 , 0x6f724720 , 0x5b207075 , 0x33323032 , 0x7825005d , 0x636c6557 , 0x64fd6d1d }; printf ("%s\n" , ((struct structure *)arr)->node.string ); }
union 的对齐数是由其成员中最大的对齐
要求决定的。所以此时的union 的对齐数是8,中间还留了4个字节,从后开始转化,即0x636c6557
,此时为小端序,所以是57
65
6c
63
-> W e l c直到0x7825005d
,的5d
,00
结束。
Welcome to XUPT , welcome to Xiyou Linux Group [2023]
13. GNU/Linux (选做)
注:嘿!你或许对Linux命令不是很熟悉,甚至你没听说过Linux。但别担心,这是选做题,了解Linux是加分项,但不了解也不扣分哦! 你知道cd命令的用法与 / . ~ 这些符号的含义吗?
1.切换到指定目录:cd /路径
2.返回上一级目录:cd ..
3.返回上上一级目录 :cd ../..
4.切换到当前用户的主目录:cd ~
5.切换到之前的目录:cd -