学长寄语:长期以来,西邮Linux兴趣小组的面试题以难度之高名扬西邮校内。我们作为出题人也清楚的知道这份试题略有难度。请你动手敲一下代码。别担心,若有同学能完成一半的题目,就已经十分优秀。其次,相比于题目的答案,我们对你的思路和过程更感兴趣,或许你的答案略有瑕疵,但你正确的思路和对知识的理解足以为你赢得绝大多数的分数。最后,做题的过程也是学习和成长的过程,相信本试题对你更加熟悉的掌握C语言的一定有所帮助。祝你好运。我们东区逸夫楼FZ103见!
- 本题目只作为西邮Linux兴趣小组2023纳新面试的有限参考。
- 为节省版面,本试题的程序源码省去了#include指令。
- 本试题中的程序源码仅用于考察C语言基础,不应当作为C语言「代码风格」的范例。
- 所有题目编译并运行于x86_64 GNU/Linux环境。
· Q :
有1000瓶水,其中有一瓶有毒,小白鼠只要尝一点带毒的水,24小时后就会准时死亡。至少要多少只小白鼠才能在24小时内鉴别出哪瓶水有毒?
· A :
- 此题考察的知识点为二进制的灵活应用(至少需要10只小鼠);
- 首先将1000瓶水按二进制分别编号;
- 观察得最大位数为10;
- 取10只小鼠分别编号为1~10;
- 将对应位数为1的水喂给相应编号的小鼠;
- 将死去的小鼠编号记下,用二进制相应位拼凑得到有毒的那瓶水;
· Q :
按照函数要求输入自己的姓名试试~
char *welcome() {
// 请你返回自己的姓名
}
int main(void) {
char *a = welcome();
printf("Hi, 我相信 %s 可以面试成功!\n", a);
return 0;
}
· A :
- 首先函数要求返回的类型为(char*);
- 解决方法:
- 直接返回自己的姓名字符串return "自己的姓名";
- 定义一个指针字符串并返回(char *name = "自己的姓名");
· Q :
有趣的输出,为什么会这样子呢~
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);
}
· A :
- ptr0为一个指针字符串(是常量不可修改),ptr1为一个字符串数组(可后续修改);
- 对两个字符串解引用,结果均为第一个字母,故if语句条件成立;
- 由printf函数嵌套得
- 先打印最内层函数,因为最内层为空,故不打印并返回0;
- 第二层函数接收返回值0并打印"Hello, Linux Group - 20",且此字符串长度为23,故返回23;
- 最外层函数接收返回值23并打印;
- 则最后输出结果为Xiyou Linux Group - 2023;
· Q :
请尝试解释一下程序的输出。
请谈谈对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));
}
· A :
-
程序的输出解释:
- 第一个sizeof运算符内首先对arr字符串进行取地址,得到arr字符串的地址,后对&arr进行解引用,相当于arr,而一个字符占一个字节,共七个字符,故返回值为7;
- 第二个sizeof运算符内arr + 0,arr为字符串的地址,加0相当于
字符串第一个元素的指针
,在x64环境下,返回值为8; - 第三个sizeof运算符内返回值为num的类型,它的类型为
short
,故返回2; - 在
sprintf函数
内,将num的值转换为十六进制并储存在str字符串中,因字符串与num的值不相等,故返回0; - 第一个strlen函数内,对str[0]取地址并+1,表示从
字符串数组第二个元素
开始计算长度,由sprintf函数得str内为"0x208"
,故返回4; - 第二个strlen函数内,arr + 0为arr字符串内首元素的地址,故返回值为5;
-
sizeof与strlen的理解:
- sizeof是一个运算符,计算时连带'\0',且可用类型做参数(计算结果为占空间内存字节数)
- strlen是string.h中的一个函数,当计算到'\0'时停止,且只可用char*做参数(计算结果为字符串长度)
-
对sprintf的理解:
- sprintf函数用于将所定义的变量类型装入所给字符串数组中;
- 括号内依次为所存目的字符串,字符串本身以及所要存入的变量的类型,变量名称;
-
补充说明:
- 各类型变量所占字节数:char 1; short 2; int 4; long 4; float 4; long long 8; double 8;
· Q :
程序的输出结果是什么?解释一下为什么出现该结果吧~
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 :
- (十进制的数均会转换为二进制并转换为补码进行后续运算)(正数的原、反、补码相同;负数的原码符号为为1,其余位计算规则与正数相同;负数的反码符号为不变,其余位据原码按位取反;负数的补码为反码加一):
- '&'为按位与,当二进制位都为1时结果为1,否则为0,故a的值为64;
- '^'为按位异或,当二进制位不同时结果为1,否则为0,故b的值为63;
- '>>'为右移操作符,表示将操作数的二进制位向右移动指定的位数,右移1位相当于除以2(注:无符号整数移动后高位补0,有符号整数移动后高位均补符号位),故c的值为-1;
- 综上所述,ch的值为-128;
- 补充说明:
- char类型的变量范围为-128~127;
- 对有符号整数和无符号整数的范围解释:
- --注释:(此处为人工智能解答)
- 无符号整型范围
- 对于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,转换为十进制是-2^7=-128。一般地,n位有符号整型的范围是-2^{n - 1}到2^{n - 1}-1。
· Q :
“人们常说互联网凛冬已至,要提高自己的竞争力,可我怎么卷都卷不过别人,只好用一些奇技淫巧让我的代码变得高深莫测。”
这个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 :
- 首先观察func函数易发现,这是个递归函数,目的为计算a与b的和;
- if语句为若a为0,则返回b;
- 下一步的返回值为fuc函数,函数括号里第一个数为a与b的进位部分所得数,第二个数为a与b不进位加法所得数;
- 经过一次次递,直至a的值为0,开始进行归(也就是返回值开始逐步运算);
- 经过计算,得到此代码的输出值为6;
· Q :
请实现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;
}
· A :
typedef int (*Predicate)(int);
int *filter(int *array, int length, Predicate predicate,
int *resultLength)
{
int *numbers = malloc(length * sizeof(int));//为number指针数组分配适当内存大小
int j = 0;
for(int i = 0;i < length; i++)
{
if(isPositive(array[i]))//判断是否符合筛选条件
{
numbers[j] = array[i];//将符合条件的数存入指针数组
j++;
}
}
*resultLength = j;//将resultLength整型变量定义值为指针数组的长度
return numbers;//返回指针数组
}
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;
}
· Q :
如何理解关键字static?
static与变量结合后有什么作用?
static与函数结合后有什么作用?
static与指针结合后有什么作用?
static如何影响内存分配?
· A :
- 此题考察static的理解与应用
- 可控制变量或函数的作用域与链接属性;
- 与变量结合:
- 与局部变量:
- 静态存储持续性,不会像一般局部变量在函数调用结束后消失;
- 作用域与一般局部变量相同,不能被其他函数直接访问并应用;
- 再次调用时会在上一次运算基础上计算,而不会重新初始化;
- 与全局变量:
- 作用域被限制在所定义的文件内,其余文件均无法引用此变量;
- 从外部链接属性变为内部链接属性;
- 与局部变量:
- 与函数结合:
- 同样改为内部链接属性;
- 作用域被限制在定义文件内部,其余文件均无法引用此函数;
- 与指针结合:
- 由于指针本质上也为变量,故与指针结合等同于与变量结合(同样也区分全局与局部);
- static对内存分配的影响:
- static关键字修饰后,会储存在静态储存区,在整个程序运行过程中均存在;而不被修饰的,则储存在栈区,进函数时存在,出函数后销毁;
· Q :
数组指针是什么?指针数组是什么?函数指针呢?用自己的话说出来更好哦,下面数据类型的含义都是什么呢?
int (*p)[10];
const int* p[10];
int (*f1(int))(int*, int);
· A :
- 此题考察指针的概念与应用
- 首先int (*p)[10]是一个数组指针,它本质上是一个指针,而这个指针指向一个特定的数组;
- const int* p[10]是一个指针数组,其内部各元素均为一个指针,指向一个常量整型;
- int (*f1(int))(int*, int)首先为一个函数指针,返回函数的地址,函数f1的返回值为int类型的整型,函数内部接收两个参数,一个为int类型的指针,一个为整型;
· Q :
程序直接运行,输出的内容是什么意思?
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;
}
· A :
- 此题考察argc、argv以及前(后)置++与逻辑操作符
- argc是一个整数,表示命令行参数的个数(包括程序名本身);argv是一个字符指针数组,每一个元素都是一个指向字符串的指针,字符串为命令行参数(argv[0]表示程序名);
- 因为涵盖程序名本身,故argc起始值至少为1;
- while循环内,实现使argc由本身的值开始前置++直到为0循环终止;
- 后置++为先使用变量后+1,则i++判断为真,向后执行,j为0,故逻辑与判断为假;逻辑或则继续向后,k不为0,则逻辑或为真;
- 执行完上述操作后,i为0,j为1,k为2;
· Q :
#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);
}
· A :
- 此题考察宏定义与变量定义的作用域
- 首先有两个宏定义,一个将传入的参数进行三次方计算,另一个将参数a与b传入第一个宏并相加(注意:宏运算时为直接替换);
- 主函数内,定义一个变量nums并初始化为1,if语句条件内部转义过来就是16 / 2 * 2 * 2,结果不为2,则进入else语句中;
- 在else语句内部定义一个新的变量nums,而次变量出else语句后会被销毁,故输出值nums仍为1;
· Q :
试着运行一下程序,为什么会出现这样的结果?
直接将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;
}
· A :
- 此题考察对<malloc函数的理解与使用和对结构体的简单运用
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;
}
void restart(struct Student *s1,const struct Student *s2)
{
free(s1->name);
s1->name = (char *)malloc(strlen(s2->name) + 1);
if(s1->name != NULL)
strcpy(s1->name,s2->name);
}
int main(void) {
struct Student s1, s2;
initializeStudent(&s1, "Tom", 18);
initializeStudent(&s2, "Jerry", 28);
s1.age = s2.age;
restart(&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;
}
- 问题解释:因为让s1直接等于s2时,s1与s2所指向的内存变为相同,而不仅仅是值的改变,所以当程序执行到末尾时释放s1的动态内存,s2仍然指向此块内存,此时s2变为悬挂指针(野指针),可能会对系统造成伤害;
- 解决办法与思路说明:
- 思路:自定义一个函数,使s1结构体内的name只将值改变为s2中name的值,而不改变其内存指向(注意判断是否分配内存成功)(注意使用完后用free来释放内存);
- 方法:如上述代码所示;
· Q :
作为一名合格的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);
}
· A :
- 此题考察大小端与结构体对齐数
- 结构体内最长类型为8字节,故对齐数为8;
- 输出中将arr强行转化为struct structure结构体类型指针,并指向联合体node.string;
- 注意联合体所占内存为最大成员字节数,本应为11,但它位于结构体内,考虑内存对齐,联合体所占字节大小为16;
- 指针指向联合体,因为结构体内对齐数为8,故输出时应跳过前两个十六进制的数值从第三个开始输出;
- 注意十六进制下 一位数占4个bite位,故此题中一个十六进制的数占四个字节;
- 大多电脑为小端储存,故高位字节存于高地址,低位字节存于低地址,由于电脑从低地址开始接收,所以将int类型的数截断为char类型后(如0x636c6557变为0x57,0x65,0x6c,0x63),应在每段从右向左依次输出,且字符串输出之至'\0'停止;
- 注意输出的字符为将十六进制的数转换为十进制的数后对应ASCII表上的字符;
· Q :
注:嘿!你或许对Linux命令不是很熟悉,甚至你没听说过Linux。但别担心,这是选做题,了解Linux是加分项,但不了解也不扣分哦!
你知道cd命令的用法与 / . ~ 这些符号的含义吗?
请问你还懂得哪些与 GNU/Linux 相关的知识呢~
· A :
-
在Linux系统里, cd 命令用于切换当前工作目录
基本用法:
- cd 后接目标目录路径,如 cd /home/user 能进入 /home/user 目录。若目标目录在当前目录下,可直接写相对路径,像 cd Documents 。
- cd 命令还有一些特殊用法。 cd - 可在当前目录和上一次所在目录间切换,这在两个目录频繁操作时很方便。 cd.. 是回到上一级目录, cd../.. 则是回两级目录,以此类推。若不加参数直接用 cd ,会进入用户主目录,等同于 cd ~ ,在不同用户操作或回到默认工作环境时会用到。
-
在Linux系统中:
-
“/”是根目录的符号。它是整个文件系统的起始点,所有的文件和目录都在根目录下以层次结构组织起来。比如“/bin”是存储二进制可执行文件的目录,“/etc”存放系统配置文件。
-
“.”表示当前目录。在执行命令或指定路径等操作时,如果用到“.”,就代表操作是在当前所在目录进行。例如,“./script.sh”表示运行当前目录下的“script.sh”脚本。
-
“
”代表当前用户的主目录。每个用户在系统中有一个自己的主目录,用于存放个人文件等。例如,用户“user1”登录后的主目录可能是“/home/user1”,使用“”就可以方便地引用这个目录,像“cp file1 ~”就是把“file1”复制到当前用户的主目录下。
-
恭喜你攻克所有难关!迎难而上的决心是我们更为看重的。
来到这里的人已是少数,莫踌躇在成功的门槛前。
自信一点,带上你的笔记本电脑,来东区逸夫楼FZ103面试吧!