西邮Linux兴趣小组2023纳新面试题

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

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

0.鼠鼠我啊,要被祸害了

有1000 瓶水,其中有一瓶有毒,小白鼠只要尝一点带毒的水,24 小时后就会准时死亡。至少要多少只小白鼠才能在24小时内鉴别出那瓶水有毒?

二进制代表每瓶水,在十个比特位的情况下(十个比特位可以表示1024瓶水)。

第一瓶水二进制形式为0000000001以此类推,第一千瓶水二进制为1111101000。分别用对应二进制第一位是1,第二位是1,第三位是1…的水喂给十只小鼠。

24小时后记录下死亡小鼠对应的水瓶标号,将二进制转换为十进制形式,即为有毒的水的标号。

1.先预测一下~

按照函数要求输入自己的姓名试试~

char *welcome(
    //请你返回自己的姓名试试
    )
    int main(void){
    char *a = welcome();
    printf("Hi,我相信 %s 可以面试成功!\n",a);
    return 0;
    }
static char name[10] = {0};
scanf("%s",name);
return name;
char *name = {0};
scanf("%s",name);
return 0;
const char name[10] = {0};
scanf("%s",name);
return name;
#include<stdio.h>
#include<malloc.h>//malloc要引用的头文件
    char *welcome(){
    char* name = (char*)malloc(10);//动态申请内存
    scanf("%s",name);
    return name;
    }
    int main(void){
    char *a = welcome();
    printf("Hi,我相信 %s 可以面试成功!\n",a);
    free(a);//释放动态申请的内存空间
    return 0;
    }

本题应该用传址的方法输出姓名

在welcome()函数内部定义的字符数组在函数结束调用时其值会自动销毁

输出其地址也就是指针会传递存储输入内容的地址

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);
    } 

本题考察字符串的使用

ptr0,ptr1是字符串名称,*以及[]代表它们是指针,指向字符串首元素地址。

if判断语句解引用字符串名称比较的是字符串首元素对应的ascii码值,为真即执行printf的嵌套循环。

printf函数返回值为输出字符个数

printf嵌套从最里层开始输出

" "为空字符什么也不输出,其返回值为0

接下来输出printf(“Hello, Linux Group - 2%d”,printf(“”))

输出结果为Hello, Linux Group - 20,其返回值为23

最后输出最外层Hello, Linux Group - 2023。

int diff = ptr0 - ptr1字符串名称为其首元素地址,地址相减并以十进制形式输出。

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));
    }
  • 以下都是在32位平台上的输出结果

字符数组遇到\0就会停止往后读入,字符数组的名字取地址解引用可以理解为抵消既sizeof(arr)

arr代表数组首元素地址,但在sizeof运算符中代表数组地址,5*4 = 20。

sizeof是运算符计算变量所占空间的大小。

strlen是用来求字符串长度的函数,\0之前的字符个数(不包括\0)。

sizeof(arr+0)arr+0代表数组第一个元素地址,是地址就输出4

sizeof(num = num2+4)sizeof()中的赋值并不会起作用。num是char型占2字节。

sprintf函数为数据写入某个字符串,即将num写入str如果成功返回写入字符总数失败则返回负数。

==为判断条件,不同为0。

strlen(&str[0]+1)&str[0]+1代表第二个元素地址。&str+1代表跳过该数组。

strlen(arr)到\0停返回5。

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);
}
  • <<>> 两种运算中,符号位均参与移动,除了负数右移高位补1之外,其他情况均在空位处补0
    在这里插入图片

  • 位运算

& 与:两个位都为1时,结果才为1

| 或:两个位都为0时,结果才为0

^ 异或:两个位相同为0,相异为1

~ 取反:0变1,1变0

<<左移:各二进位全部左移若干位,高位丢弃,低位补0。

>>:各二进位全部右移若干位,无符号数高位补0。

位运算
输出:

a = 64,b = 63,c = -1

ch = -128!!!

ch = 64+63-(-1)=128,而char类型的存储大小为-128~127,当其值增为128时会从-128重新开始计数。

5.乍一看就不想看的函数

“人们常说互联网凛冬已至,要提高自己的竞争力,可我怎么卷都卷不过别人,只好用一些奇技淫巧让我的代码变得高深莫测。“

这个fun()函数的功能是什么?是如何实现的?

int func(int a,int b){
    if(!a) return 0;
    rerurn func((a & b) << 1,a ^ b);
}
int main(void){
    int a = 4,b = 9,c = -7;
    printf("%d\n",func(a,func(b,c)));
}

学会递归的基本思想

if(!a)等同于if(a==0),是递归的控制条件。

func()函数使用递归来计算 a 和 b 的乘积,将乘法操作转化为位操作:

(1)如果 a 等于0,函数返回0,这是递归的终止条件。

(2)否则,它递归调用自身,其中:

  • (a & b) << 1 表示将 a 和 b 进行按位与操作,然后将结果左移1位,这相当于计算两者的进位

  • a ^ b 表示将 a 和 b 进行按位异或操作,这相当于计算两者的不带进位的和

其实就是通过递归来不断计算出乘积。

main() 函数中,它调用 func() 函数两次:func(b, c) 和 func(a, func(b, c)),用于计算出 abc的乘积并输出。

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 *result = (int*)malloc(length);
    int count = 0;
    for(int i = 0;i<lehgth;i++){
        if(predicate(array[i])){
            result[count] = array[i];
            count++;
        }
    }
    *resultLength = count;
    return result;
}
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;
}

7.静…态…

① 如何理解关键字 static?

② static 与变量结合后有什么作用?

③ static 与函数结合后有什么作用?

④ static 与指针结合后有什么作用?

⑤ static 如何影响内存分配?

作用域指程序中被定义的变量生效的区域,超过该区域就不能访问

  • 修饰局部变量

改变局部变量的存储位置,使其生命周期变长而不改变其作用域(定义的函数内部)

  • 修饰全局变量

改变其作用域(整个源程序),全局变量具有外部链接属性。被static修饰并为内部链接属性只能在自己原文件中使用,extern为外部声明。

  • 修饰函数

静态函数和全局变量相似。

  • 修饰指针

static int *p;

p这个指针是静态的,p的地址不变,而地址本身的内容可以。

  • 影响内存分配

编程内存可以分为静态存储区,堆区和栈区。局部变量会存储到栈区,而全局变量存储到静态区。

当static与局部变量结合后会改变其存储的位置使其存储到静态区。

8.救命!指针!

数组指针是什么?指针数组是什么?函数指针呢?用自己的话说出来更好哦,下面数据类
型的含义都是什么呢?

int (*p)[10];
const int* p[10];
int (*f1(int))(int*, int);

数组指针的本质是指针,用来存储数组的地址。

指针数组的本质是数组,以数组的形式来存储指针。

函数指针是指针用来存储函数地址。

int (*p)[10];是一维数组指针指针名为p。

const int* p[10];是一个指针数组,用const修饰来表示它的地址并不会被改变。

int (*f1(int))(int*, int);这个比较复杂,*f1(int)是一个函数名为f1其内容是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;
}
  • argc是外部输入的参数个数,argv是参数的字符串数组。

argc用来统计程序运行时的命令行参数的个数,本题中外部输入参数只有执行程序的这个命令即argc的值为1。

while循环并不是死循环因为argc为int型,int的取值范围为:-2147483648 ~ 2147483647。

  • &&短路与:如果第一个条件为 false,则第二个条件不会判断,最终结果为 false,效率高。

  • ||短路或:如果第一个条件为 true,则第二个条件不会判断,最终结果为 true,效率高。

i++ && j++ || k++,++都为后置,i=-1非零为真即继续执行后面的j++,||只要一个为真即为真。

i,j,k全部加一。

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);
}

宏不计算其只是对文本进行简单的替换。

int main(void) {
int nums = 1;
if(16 / 2 * 2 * 2 == 2) {
printf("I'm TWO(ノ>ω<)ノ\n");
} else {
int nums = ++nums * ++nums * ++nums + 2 * 2 * 2;
}
printf("%d\n", nums);
return 0;
}

if条件为假,即执行else语句

在c语言中不能连续使用++,程序会出现无法预料的后果。

在if函数外层定义的nums是个全局变量,if语句里面定义的是个局部变量。

局部变量可以与全局变量重名,但是局部变量会屏蔽全局变量。

*注意:局部变量不能赋值给同名全局变量的值

#include<stdio.h>

int value = 1;//全局变量

int main(){
	int value = value;
	printf("%d",value);
	return 0;
}

输出:-898993460

分析如下:

  1. 定义了一个全局变量value初始化值为1,同时在main函数内部定义了一个局部变量value

  2. 在main函数内部对局部变量value进行赋值,因为局部变量会屏蔽全局变量因此此时赋值语句中两个value都是局部变量,而value还没有初始化所以值为随机值

程序最后输出的nums值为1,在else语句中将值赋给了else中定义的nums,而最后输出的是else语句外部定义的nums的值。

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;
}

深浅拷贝

  • 浅拷贝:意味着只复制了对象的表面结构,而不复制对象所引用的数据。这意味着复制一个对象时,只是复制了对象的指针或引用,而不是对象指向的内容。而如果原始对象中有指针指向另一个对象或数据,浅拷贝后的对象中的相应指针将指向相同的内存地址。这可能会出现问题,因为对其中一个对象所指向的数据的修改也会影响到另一个对象。

  • 深拷贝:是一种更彻底的复制。在深拷贝中,不仅复制了对象本身的内容,还会递归复制对象所引用的所有数据,确保原始对象和新对象之间没有共享数据。这意味着即使数据结构中包含指针或引用,深拷贝也会复制指针所指向的内容,而不是简单地复制指针本身。

本题当s2赋值给s1时,实际上是将s2结构体的内容复制给了s1,包括它的name指针。

s1和s2的name指针都指向了相同的内存地址,因此共享相同的姓名字符串。所以输出s1和s2的姓名时,它们都显示为"Jerry"。

但当程序结束时调用free释放内存时,两者都会释放相同的内存地址,而内存应该只被释放一次,出现了错误。

应该实现深拷贝来复制name指针所指向的字符串,而不是直接赋值结构体。

因此要分配新的内存,并将name指针指向这个新的内存,再复制字符串的内容。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

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 copyStudent(struct Student *dest, const struct Student *src) {
    dest->name = (char *)malloc(strlen(src->name) + 1);
    strcpy(dest->name, src->name);
    dest->age = src->age;
}

void freeStudent(struct Student *student) {
    free(student->name);
}

int main(void) {
    struct Student s1, s2;
    initializeStudent(&s1, "Tom", 18);
    initializeStudent(&s2, "Jerry", 28);

    // 使用深拷贝函数复制s2到s1
    copyStudent(&s1, &s2);

    printf("s1 的姓名: %s 年龄: %d\n", s1.name, s1.age);
    printf("s2 的姓名: %s 年龄: %d\n", s2.name, s2.age);

    // 释放内存
    freeStudent(&s1);
    freeStudent(&s2);

    return 0;
}

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);
}

本题从arr[]输出从第三个十六进制数开始输出。

struct structure {
int foo;//4个字节
union {
int integer;
char string[11];
void *pointer;
} node;//16个字节
short bar;//2个字节
long long baz;//8个字节
int array[7];//28个字节
};
int main(void) {
int arr[] = {0x590ff23c, 0x2fbc5a4d, 0x636c6557, 0x20656d6f,
0x58206f74, 0x20545055, 0x6577202c, 0x6d6f636c,
0x6f742065, 0x79695820, 0x4c20756f, 0x78756e69,
0x6f724720, 0x5b207075, 0x33323032, 0x7825005d,//'00'对应\0,即输出结束。
0x636c6557, 0x64fd6d1d};
printf("%s\n", ((struct structure *)arr)->node.string);
}

输出:Welcome to XUPT , welcome to Xiyou Linux Group [2023]

根据结构体内存对齐的知识int foo占四个字节,union存储在其后四个字节处即int foo总共占8个字节其中后四个什么也没存。

0x590ff23c, 0x2fbc5a4d这两个存在于int foo的空间中,因为输出的是结构体中的共用体,所以从第三个十六进制开始输出。

  • 大端序:数据高位字节存入低地址,数据低位字节存入高地址

  • 小端序:数据低位字节存入低地址,数据高位字节存入高地址

    本题用十六进制来代替每个字母和符号(一个十六进制为四个字母或符号)包括空格。

  • 结构体内存对齐

  1. 结构体的第一个成员对齐到相对结构体变量起始位置偏移量为0的地址处。

  2. 其他成员变量要对齐到对齐数的整数倍的地址处。

  3. 对齐数 = 编译器默认的一个对齐数与该成员变量大小的较小值。(VS中默认的值为8,Linux中没有默认对齐数)对齐数就是成员自身的大小。

  4. 结构体总大小为最大对齐数的整数倍。

  • 共用体使用了内存对齐
  1. 同一时刻只能保存一个成员的值,如果对新的成员赋值,就会把原来成员的值覆盖掉。

  2. 联合的占用内存大小只需要整除最大对齐数。

  3. 联合体总大小为最大对齐数的整数倍。

结构体和共用体的区别在于:结构体的各个成员会占用不同的内存,互相之间没有影响;而共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员,结构体占用的内存至少为所有成员占用的内存的总和。

(struct structure *)arr是结构体指针的强制类型转换。

13.GNU/Linux (选做)

注:嘿!你或许对 Linux 命令不是很熟悉,甚至你没听说过 Linux。但别担心,这是选做题,了解
Linux 是加分项,但不了解也不扣分哦!

你知道 cd 命令的用法与 / . ~ 这些符号的含义吗?
请问你还懂得哪些与 GNU/Linux 相关的知识呢~

命令行链接

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值