西邮linux兴趣小组2023纳新题解

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

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

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

10瓶
我们将1000瓶水用二进制标号,1000的二进制是111101000。一共十位,第一瓶水是0000000001,以此类推,将第一位是1的所有瓶子里的水取出来一些混合,标上1,以此类推,得到十瓶混合的水,将十瓶水分别喂给十只鼠鼠,24小时后,死掉的每只鼠鼠都对应着一个标号,对应一个十位的二进制数,相应标号位上的数为1,其他位上数为零,得到一个瓶子的标号,就是有毒的那瓶。

1.先预测一下~

按照函数要求输入自己的姓名试试~
char *welcome() {
// 请你返回自己的姓名
}
int main(void) {
char *a = welcome();
printf("Hi, 我相信 %s 可以面试成功!\n", a);
return 0;
}

最简单的方法是直接在welcome函数里return自己的名字,如下

char *welcome() {
return xxx;
}
int main(void) {
char *a = welcome();
printf("Hi, 我相信 %s 可以面试成功!\n", a);
return 0;
}

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

if语句比较了两个字符数组的首元素值,W=W,所以执行语句.
printf函数嵌套,先执行括号里的,所以最里面的括号里的先执行,
什么也不打印,返回0
然后中层的printf打印双引号里的字符并将占位符%d替换为里面函数的返回值0。
最后,最外面的printf函数打印中间函数的返回值23
最后结果就为“Hello, LinuxGroup - 2023"。
最后一个printf打印两个地址之差,由于 不指向同一个数组,c语言未定义这种情况,出现的值千奇百怪。

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

sizeof运算符计算占空间字节数,
strlen函数计算字符串长度且不计\0
sizeof是运算符,strlen是函数,sizeof接收的参数是地址,streln接收的参数是字符串

第一行printf函数打印三个 size_t类型的值,第一个解引用又取地址,相当于打印arr数组占字节数,为72;
第二个打印arr数组首元素地址+0后的字节数,不论加什么,在64位系统中,地址占字节数都为8;
第三个打印里面整个表达式的结果占字节数,结果num是short int 类型,所以为2
第二行printf函数打印sprintf函数的返回值与num相等是否为真的值。
sprintf函数至少有两个参数,若有三个,则第二个参数中有转换说明,这个sprintf函数将num的值拷贝到str数组里,然后返回拷贝的字符数,为3,与520不相等,所以printf函数打印0;
第三行,strlen函数与sizeof运算符不同的是,strlen函数接收地址,printf函数第一个参数为str从第二个元素开始计算的字符串长度,为4,第二个参数为从第一个元素开始,为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才是1其他情况都是0
  • ^运算符是同位相同则为1,不同则为0
  • 右移运算符,各二进位全部右移若干位,无符号数高位补0,负数高位补1 。

位运算,第一行为a,b,c三个位运算之后的值,分别为64,63,-1,最后一个ch:因为是char类型,它的符号位是第八位,127原码为01111111,1的原码为00000001,相加后为10000000,为-128的原码,所以答案为-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)));
}

打印出的值为6
func函数的功能是将传入的两个参数相加并返回相加结果
通过函数递归和位运算使得倒数第二次函数调用时第一个参数为,第二个参数为初始两参数和。在最后一次调用里返回两初始参数和。

该函数涉及使用位运算实现加法。原理如下
位运算中的异或运算结果与二进制加法的结果是类似的

23异或36的结果是0110011,与加法的差异是第二位没有进位

要实现进位,我们可以加上两数与之后左移一位的结果,23和46与之后左移一位是0001000。与异或结果相加和二进制加法结果0111011相同。

而如果我们可以使用加号的话,还不如直接两数相加,所以我们使用纯粹的位运算来计算两数和。

如何使两数的异或结果和与后左移一位结果相加呢,我们不妨使用函数的递归,把这两个参数传给实现加法的这个函数本身,递归以什么条件停止呢,直到最新的这两个参数相加时不用进位停止,这个时候,第一个参数为0。.

这就是这个函数实现的原理。

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 x = 0;//新数组的下标
	int *filteredNumbers = (int*)malloc(10 * sizeof(int));//为新数组分配空间
	for (int j = 0; j <= length; j++)//遍历原数组
		if (predicate(array[j]))//过滤条件
		{
			filteredNumbers[x] = array[j];//符合条件的原数组被填入新数组
			x++;//指向新数组下一个元素
		}
*resultLength=x;//通过指针将新数组长度传递给主函数
return  filteredNumbers;
}
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;
}

在这个函数实现时,我们将ispositive函数作为参数传递给filter函数,需要使用回调函数,通过函数指针来传递函数。

使用typedef函数定义函数指针类型更方便的使用了函数指针

7.静…态…

① 如何理解关键字 static?
② static 与变量结合后有什么作用?
③ static 与函数结合后有什么作用?
④ static 与指针结合后有什么作用?
⑤ static 如何影响内存分配?

变量从定义开始分配存储单元,到运行结束存储单元被回收,整个过程称为变量生命周期。

  • *static关键字可以将变量静态化,以static关键字修饰的值会被存储在静态区生命周期会延长到整个程序。

  • 与局部变量结合可以使它的生命周期扩展到整个程序

  • 与全局变量结合会让全局变量的链接属性改变,使它的作用域变小到本文件。

  • 指针也是一种变量。

  • 与函数结合也会使函数的链接属性改变,使其不能在其他文件中被调用。*

8.救命!指针!

数组指针是什么?指针数组是什么?函数指针呢?用自己的话说出来更好哦,下面数据类型的含义都是什么呢?
int (*p)[10];
const int* p[10];
int (*f1(int))(int*, int);
  • 数组指针是指向数组的指针,本质是指针
  • 指针数组是里面元素都是指针的数组本质是数组
  • 函数指针是指向函数的指针本质是指针
  • 第一个是数组指针,因为[]的优先级比\*高,所以用括号括起来保证了p先和\*结合,使得其本质为指针,但是指向一个有十个int类型元素的数组
  • 第二个是指向const的指针数组,p先和[]结合,则其本质为数组,数组有十个元素,都是指向const的指针,即指向的值无法被改变的指针。
  • 第三个是一个函数指针,这个函数指针指向的函数是返回值为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被程序唤起的命令行参数的数量,用于统计参数数量
argv是一个指向一个指针数组argv[]的指针,且这个数组储存的最后一个指针是空指针,即NULL
指针数组argv[]中的每个指针都指向一个参数。每个参数都是一个字符串第一个参数是程序的路径地址
当然,作为一个字符串,其后是有\0的。

短路与&&在前面的表达式为时,不会计算后面的表达式,
短路或||在前面的表达式为时,不会计算后面的表达式。

输出分析
分析i,j,k的值,++和--++和--的运算符优先级 高于&&和||
所以i的值先使&&短路
然后i增为0,而j的值因为argc溢出变为零
||没有短路,所以j和k都递增,j为1,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);
}

宏定义
宏定义是预处理命令的一种,在C语言源程序用一个标识符来表示一个字符串。
宏定义在被预处理时是直接替换,所以一定要注意括号的使用。

考察宏定义
if语句中,16/2×2×2的值为32而非2,所以执行else语句,
预处理时宏定义会被直接替换,所以尽量给参数都带上括号,否则可能结果和预计不符。
替换后MAGIC_CAL(++nums, 2)变为++nums×+++nums++×++nums+2×2×2,
++运算符优先级更高所以nums先被递增为4,然后运算,结果为72

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

考察深浅拷贝
*结构体的模板没有指针时,可以直接拷贝,但当结构体的模板有指针时,直接拷贝的话只拷贝了指针指向的地址,这使得原指针和新指针指向同一个内存空间,如果以后用原指针对地址进行操作可能导致新指针出现问题,需要重新分配内存空间拷贝
本题代码中有一个函数给两个成员都分配了内存,但s1=s2使得内存空间只用了一个,所以,用strcpy将名字拷贝即可,或者只是不想弹出warning的话,只free一次空间也行,解决代码如下:*
1.

struct Student {
char* name;	
int age;
};
void initializeStudent(struct Student* student, const char* name,int age) {
	student->name = (char*)malloc(sizeof name + 1);
	strcpy(student->name,name);
	student->age = age;
}
int main(void) {
	struct Student s1, s2;
	initializeStudent(&s1, "Tom\0", 18);
	initializeStudent(&s2, "Jerry\0", 28);
 s1.age=s2.age;
 strcpy(s1.name, s2.name);
	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;
}
struct Student {
	char* name;
	int age;
};
void initializeStudent(struct Student* student, const char* name,
	int age) {
	student->name = (char*)malloc(sizeof(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);
	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);
}

输出为Welcome to XUPT , welcome to Xiyou Linux Group [2023]
考察大小端和结构体的内存对齐
大端(存储)模式:是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中;

小端(存储)模式:是指数据的低位保存在内存的低地址中,而数据的高位,,保存在内存的高地址中。

x86系统采用小端的模式,所以在int类型被转化为char类型时,从后面截断。比如arr[0],依次被截断成3c,f2,0f,59。参照ASCII码表,得出结果正是输出值。

13.GNU/Linux (选做)

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

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

cd命令是切换工作目录的命令。
/是根目录.是当前目录~是主目录,以根目录来看是/home/user,这里的user替换为用户的用户名

  • 12
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

谁的友人A

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值