Shawn摘要

感谢 Zhilu 重新录入题目原件。好人一生平安。

  • 本题目只作为Xiyou Linux兴趣小组2022纳新面试的有限参考。
  • 为节省版面,本试题的程序源码省去了#include指令。
  • 本试题中的程序源码仅用于考察C语言基础,不应当作为C语言「代码风格」的范例。
  • 题目难度随机排列。
    所有题目编译并运行于x86_64 GNU/Linux环境。

学长寄语:
长期以来,西邮Linux兴趣小组的面试题以难度之高名扬西邮校内。我们作为出题人也清楚的知道这份试题略有难度。请别担心。若有同学能完成一半的题目,就已经十分优秀。 其次,相比于题目的答案,我们对你的思路和过程更感兴趣,或许你的答案略有瑕疵,但你正确的思路和对知识的理解足以为你赢得绝大多数的分数。最后,做题的过程也是学习和成长的过程,相信本试题对你更加熟悉的掌握C语言的一定有所帮助。祝你好运。我们FZ103见!

Copyright © 2022 西邮Linux兴趣小组, All Rights Reserved.
本试题使用采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。

0. 我的计算器坏了?!

2^10=1024对应于十进制的4位,那么2^10000对应于十进制的多少位呢?

计算一个数十进制的位数,我们以10为底求该数的对数,再取整+1,同理计算一个数对应的二进制的位数,以2为底求该数的对数再+1,[10000*log2]+1大概是3011位

1. printf还能这么玩?

尝试着解释程序的输出。

1
2
3
4
5
6
int main(void) {
if ((3 + 2 < 2) > (3 + 2 > 2))
printf("Welcome to Xiyou Linux Group\n");
else
printf("%d\n", printf("Xiyou Linux Group - 2%d", printf("")));
}

3+2=5<2判断结果为0,3+2>2判断结果为1,0>1判断结果为0,所以执行第二句,先执行printf(“”)然后返回值为0,执行printf(“Xiyou Linux Group - 20”),返回值为22然后执行printf(“22”)

结果就是Xiyou Linux Group -2022

2. 你好你好你好呀!

  • 程序的输出有点奇怪,请尝试解释一下程序的输出吧。
  • 请谈谈对sizeof()strlen()的理解吧。**
1
2
3
4
5
6
7
8
9
10
11
int main(void)
{
char p0[] = "Hello,Linux";
char *p1 = "Hello,Linux";
char p2[11] = "Hello,Linux";
printf("p0==p1: %d, strcmp(p0,p2): %d\n", p0 == p1, strcmp(p0, p2));
printf("sizeof(p0): %zu, sizeof(p1): %zu, sizeof(*p2): %zu \n",
sizeof(p0), sizeof(p1), sizeof(*p2));
printf("strlen(p0): %zu, strlen(p1): %zu\n", strlen(p0), strlen(p1));
}

p0是一个字符数组,地址在栈区,p1是一个指向数据区字符串的指针,p2是栈区的另一个字符串数组的首地址,所以p0和p1的值肯定不相等,

p0==p1的值为零,strcmp(p0,p2)的值为0,sizeof(p0)的大小是12(包括’/0’),sizeof(p1)的大小是指针大小在(linux下是8),

sizeof(*p2)是第一个字符的首地址,大小为1,strlen计算的是长度所以大小都为11

3. 换个变量名不行吗?

请结合本题,分别谈谈你对C语言中「全局变量」和「局部变量」的「生命周期」理解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int a = 3;
void test()
{
int a = 1;
a += 1;
{
int a = a + 1;
printf("a = %d\n", a);
}
printf("a = %d\n", a);
}
int main(void)
{
test();
printf("a= %d\n", a);
}

第一行打印a=1(全局变量初始化为0,然后a+1),第二行打印a=2(局部变量a=1然后+1),第三行打印a=3(全局变量int a=3)

4. 内存对不齐

unionstruct各有什么特点呢,你了解他们的内存分配模式吗。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
typedef union
{
long l;
int i[5];
char c;
} UNION;
typedef struct
{
int like;
UNION coin;
double collect;
} STRUCT;
int main(void)
{
printf("sizeof (UNION) = %zu \n", sizeof(UNION));
printf("sizeof (STRUCT) = %zu \n", sizeof(STRUCT));
}

union共用一块内存空间,union大小为24,strict大小为40

5. Bitwise

  • 请使用纸笔推导出程序的输出结果。
  • 请谈谈你对位运算的理解。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main(void)
{
unsigned char a = 4 | 7;
a <<= 3;
unsigned char b = 5 & 7;
b >>= 3;
unsigned char c = 6 ^ 7;
c = ~c;
unsigned short d = (a ^ c) << 3;
signed char e = -63;
e <<= 2;
printf("a: %d, b: %d, c: %d, d: %d \n", a, b, c, (char)d);
printf("e: %#x \n", e);
}

|是或,两个位都为0,结果才为0,4是0100,7是0111,4|7结果是0111=7,左移三次7*2=14,14 *2=28,28 *2=56所以a=56

&是与,两个位都为1时结果才为1,5是0101,7是0111,5&7结果是0101=5,5右移三次5/2=2,2/2=1,1/2=0

所以b=0

^是异或,两个位相同为0,相异为1,6是0110,7是0111,结果是0001,~是取反,结果是1111 1110,所以c=254

a是0011 1000,c是1111 1110,a^c的结果是0000 0000 1100 0110左移3位是0000 0110 0011 0000转换为char是0011 0000=48,e是1011 1111补码是1100 0001左移2位是0000 0100=4,所以e=4

6. 英译汉

请说说下面数据类型的含义,谈谈const的作用。

  1. char *const p
  2. char const *p
  3. const char *p

char *const p是指针常量 ,指针p本身是个常量,不能再指向其他地址

char const *p和const char *p的是常量指针,不能通过p改变变量的值,但指针本身可以指向其他的地址

区分常量指针和指针常量的关键就在于星号的位置,我们以星号为分界线,如果const在星号的左边,则为常量指针,如果const在星号的右边则为指针常量

7. 汉译英

请用变量p给出下面的定义:

  1. 含有10个指向int的指针的数组。
  2. 指向含有10个int数组的指针。
  3. 含有3个「指向函数的指针」的数组,被指向的函数有1个int参数并返回int

明确一个优先级顺序()>[]>*

1.指针数组___装着指针的数组.

1
int* p[10]

指针数组是多个指针变量,以数组的形式存储在内存中,占有多个指针的存储空间

2.数组指针___指向数组的指针.

1
int (*p)[10]

数组指针是一个指针变量,占有内存中一个指针的存储空间

3.[]的优先级高于*,pf先与[]结合,说明它是一个数组,再与 *结合说明数组中的元素的类型是指针

1
int(*p[3])(int)//定义一个有3个指针的数组,指针指向一个函数,该函数有一个整形参数并返回一个整形

8. 混乱中建立秩序

你对排序算法了解多少呢?
请谈谈你所了解的排序算法的思想、稳定性、时间复杂度、空间复杂度。

提示:动动你的小手敲出来更好哦~

9. 手脑并用

请实现ConvertAndMerge函数:
拼接输入的两个字符串,并翻转拼接后得到的新字符串中所有字母的大小写。

提示:你需要为新字符串分配空间。

1
2
3
4
5
6
7
8
9
char* convertAndMerge(/*补全签名*/);
int main(void) {
char words[2][20] = {"Welcome to Xiyou ", "Linux Group 2022"};
printf("%s\n", words[0]);
printf("%s\n", words[1]);
char *str = convertAndMerge(words);
printf("str = %s\n", str);
free(str);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include<stdio.h>
#include<string.h>
char* convertAndMerge(char words[2][20]);
char dst[50];
int main(void)
{
char words[2][20] = { "Welcome to Xiyou ","Linux Group 2022" };
printf("%s\n", words[0]);
printf("%s\n", words[1]);
char* str = convertAndMerge(words);
printf("str=%s\n", str);
free(str);
return 0;
}
char* convertAndMerge(char words[2][20])
{
strcpy(dst, words[0]);
strcat(dst, words[1]);
for (int i = 0; dst[i] != '\0'; i++)
{
if (dst[i] >= 'A' && dst[i] <= 'Z')
{
dst[i] += 32;
}
else if (dst[i] >= 'a' && dst[i] <= 'z')
{
dst[i] -= 32;
}
}
return dst;
}

10. 给你我的指针,访问我的心声

  程序的输出有点奇怪,请尝试解释一下程序的输出吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
int main(int argc, char **argv) {
int arr[5][5];
int a = 0;
for (int i = 0; i < 5; i++) {
int *temp = *(arr + i);
for (; temp < arr[5]; temp++) *temp = a++;
}
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++) {
printf("%d\t", arr[i][j]);
}
}
}

11. 奇怪的参数

你了解argc和argv吗?
直接运行程序argc的值为什么是1?
程序会出现死循环吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
int main(int argc, char **argv)
{
printf("argc = %d\n", argc);
while (1)
{
argc++;
if (argc < 0)
{
printf("%s\n", (char *)argv[0]);
break;
}
}
}

argc即arguments count表示传入main函数的参数个数

argv即arguments value/vector参数值,表示传入main函数的参数序列或指针,并且第一个参数argv[0]一定是程序的名称

argc为1,即该程序的路径名argc为int类型,不断+1,会数值溢出从而argc<0,break跳出循环

12. 奇怪的字符

程序的输出有点奇怪,请尝试解释一下程序的输出吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main(int argc, char **argv)
{
int data1[2][3] = {{0x636c6557, 0x20656d6f, 0x58206f74},
// u o y i n i L \0
{0x756f7969, 0x6e694c20, 0x00000000}};
int data2[] = {0x47207875, 0x70756f72, 0x32303220, 0x00000a32};
char *a = (char *)data1;
char *b = (char *)data2;
char buf[1024];
strcpy(buf, a);
strcat(buf, b);
printf("%s \n", buf);
if(*buf='W') printf("LE");
else printf("BE");
}

输出结果为Welcome to Xiyou Linux Group 2022

int类型的数据被强制转换成char

如0x636c6557转化为二进制为0110 0011 0110 1100 0110 0101 0101 0111最后的0101 0111对应87刚好是W对应的ASCII码,以此类推

13. 小试宏刀

  • 请谈谈你对#define的理解。
  • 请尝试着解释程序的输出。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#define SWAP(a, b, t) t = a; a = b; b = t
#define SQUARE(a) a *a
#define SWAPWHEN(a, b, t, cond) if (cond) SWAP(a, b, t)
int main() {
int tmp;
int x = 1;
int y = 2;
int z = 3;
int w = 3;
SWAP(x, y, tmp);
printf("x = %d, y = %d, tmp = %d\n", x, y, tmp);
if (x > y) SWAP(x, y, tmp);
printf("x = %d, y = %d, tmp = %d\n", x, y, tmp);
SWAPWHEN(x, y, tmp, SQUARE(1 + 2 + z++ + ++w) == 100);
printf("x = %d, y = %d,tmp=%d\n", x, y, tmp);
printf("z = %d, w = %d ,tmp = %d\n", z, w, tmp);
}

注意:#define 是原封不动的替换,第一行输出x=2,y=1,tmp=1,x>y所以会做SWAP,下一行输出x=1,y=2,tmp=2下一行SQUARE会变成1+2+z++ + ++w*1+2+z++ + ++w=1+2+3+4+2+4+5=21!=100

然而,SWAPWHEN会变成if(0) tmp=x;x=y;y=tmp只是不执行第一条,x=y和y=tmp都会执行所以x=2,y=2,tmp的值仍是2,z=5,w=5

14. GNU/Linux命令 (选做)

你知道以下命令的含义和用法吗:

注:

嘿!你或许对Linux命令不是很熟悉,甚至你没听说过Linux。
但别担心,这是选做题,不会对你的面试产生很大的影响!
了解Linux是加分项,但不了解也不扣分哦!

  • ls
  • rm
  • whoami

请问你还了解哪些GNU/Linux的命令呢。

恭喜你做到这里!你的坚持战胜了绝大多数看到这份试题的同学。
或许你自己对答题的表现不满意,但别担心,请自信一点呐。
坚持到达这里已经证明了你的优秀。
还在等什么,快带上你的笔记本电脑,来FZ103面试吧!