西邮linux兴趣小组2023面试题

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

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

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

首先举个例子,4瓶水,给其编号00 01 10 11,两个小鼠,分别喝第一位为1的水,第二位为1的水,就可以判断出哪一瓶有毒,,8瓶水000 001 010 011 100 101 110 111,以此类推,需要三只小鼠,即2的10次方,需要10只小鼠。

1.先预测一下~

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

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

第一种

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

第二种

  
char *welcome() {  
    char *arr="zhangsan";
    return "zhangsan" ;  
}  
  
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);
}

考点:printf的返回值
if (*ptr0 == *ptr1) 这个条件判断的是 ptr0 和 ptr1 所指向的字符串的第一个字符是否相同。由于两个字符串完全相同,因此条件为真。
printf(“%d\n”, printf(“Hello, Linux Group - 2%d”, printf(“”)));内层的 printf(“”) 调用返回0,由于格式化字符串中的 %d 被0替换,第二个 printf 实际上会打印 “Hello, Linux Group - 20”,并返回打印的字符数,然后,外层的 printf 会打印这个返回的字符数(即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));
}

问题一

  1. sizeof(*&arr) 相当于 sizeof(arr),即7
  2. sizeof(arr + 0) ,0对指针没有影响,sizeof取指针大小为8
  3. sizeof(num = num2 + 4) 是一个赋值表达式,但sizeof不计算赋值的结果,而是计算表达式类型的大小 num = num2 + 4的结果是一个int,所以sizeof的结果是short的大小,所以是2
  4. sprintf(str, “0x%x”, num) 将num的值以十六进制形式写入str,并返回写入的字符数(不包括结尾的’\0’),这个返回值不可能等于num,因此,sprintf(str, “0x%x”, num) == num 的结果是0(假),sprintf它像 printf 一样格式化数据,但不同之处在于 sprintf 将结果存储在字符串中,而不是输出到标准输出。
  5. strlen(&str[0] + 1) 相当于 strlen(str + 1),它从str的第二个字符开始计算长度0x208,结果为4
    strlen(arr + 0) 相当于 strlen(arr),它计算arr中从第一个字符到’\0’之前的字符数,即5
    问题二
    sizeof() 是一个编译时运算符,用于计算类型或变量在内存中占用的字节数。它可以用于基本数据类型(如int、char等)、数组、结构体等,并且对于数组,它返回整个数组的大小(包括所有元素),而不是指向数组的指针的大小。
    strlen() 是一个函数,用于计算C字符串(以’\0’结尾的字符数组)的长度,即从字符串的开头到第一个’\0’字符之前的字符数。它不包括结尾的’\0’字符。如果传递给strlen()的指针没有指向有效的C字符串(即没有以’\0’结尾),则行为是未定义的。
    问题三:sprintf 函数是 C 语言标准库中的一个函数,用于将格式化的数据写入字符串。它类似于 printf 函数,但 printf 是将格式化的数据输出到标准输出(通常是屏幕),而 sprintf 是将格式化的数据输出到一个字符串中。sprintf 函数的原型定义在 <stdio.h> 头文件中
    示例
    // 使用 sprintf 将格式化的数据写入 s
    sprintf(s, "Name: %s, Age: %d, Height: %.1f", name, age, height);  
    
    

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

考点:&、^、>> 分别是按位与、按位异或和右移运算符

64的二进制表示是 01000000,127的二进制表示是 01111111。按位与的结果是 01000000,即64。所以 a = 64。
64的二进制表示是 01000000,127的二进制表示是 01111111。按位异或的结果是 00111111,即63(在十进制中)。所以 b = 63。
64的二进制表示是 01000000,右移6位,相当于除6次2,所以c=-1。
由于char类型表示范围为-128~127,所以ch = a + b - c=-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

考点:函数递归,位运算
其实结果就是a+b+c=6;
(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 isPositive(int num) { return num > 0; }//返回大于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;
}
int *filter(int *array, int length, Predicate predicate,int *resultLength) /*补全函数*/
{
    int j=0;
    int *filteredNumbers=(int *)malloc(sizeof(int)*length);//由于后面有free(filteredNumbers),所以这里要申请动态内存空间
    for(int i=0;i<length;i++)
    {
        if(isPositive(array[i]))
        {
            filteredNumbers[j]=array[i];//创建新数组
            j++;
        }
    }
    *resultLength=j;
    return filteredNumbers;
}

7.静…态…

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

① 如何理解关键字 static?
static 是 C 和 C++ 语言中的一个关键字,它用于指定变量的存储期为静态存储期,或用于限制函数的作用域。static 关键字的主要作用是改变变量的生命周期或函数的作用域,以及影响内存分配的方式。

② static 与变量结合后有什么作用?
当static用于修饰全局变量时,该变量的作用域会被限定在定义它的源文件内部。
当static用于修饰局部变量时,生命周期从程序启动一直持续到程序结束。

③ static 与函数结合后有什么作用?
当 static 与函数结合时,它限制了函数的作用域,使其只在定义它的文件内部可见。

④ static 与指针结合后有什么作用?
static 与指针结合时,它同样改变了指针所指向对象的生命周期。如果指针指向的是一个静态变量或静态分配的内存区域,那么该指针在程序运行期间将始终指向有效的内存地址。此外,静态指针本身也可以被声明为静态的,这意味着指针本身(而不是它所指向的对象)在程序运行期间将保持其值不变。

⑤ static 如何影响内存分配?
static 关键字对内存分配的影响主要体现在以下几个方面:

静态变量:静态变量在程序的静态存储区分配内存,而不是在栈上。这意味着静态变量的生命周期贯穿整个程序运行期间,并且它们只被初始化一次。
静态函数:静态函数本身不直接涉及内存分配,但它们限制了函数的作用域,从而可能减少了全局命名空间的污染,并有助于优化程序的内存使用(通过减少不必要的函数副本)。
静态指针:静态指针本身在静态存储区分配内存,但它所指向的对象可能位于静态存储区、堆或栈上,具体取决于指针的初始化和使用方式。如果静态指针指向静态分配的内存区域(如静态变量或动态分配但未被释放的内存),则该内存区域在程序运行期间将始终有效。

8.救命!指针!

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

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

int (p)[10] 是数组指针
const int
p[10]指针数组,且其地址可以改变,值不可以改变
int (f1(int))(int, int)首先f1是变量名,(int)是是参数类型,int ()(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;
}

argc至少为1,这个循环尝试通过递增argc直到它变为0来执行一些操作。然而,这个循环是无效的。
运算符的短路
x||y,如果x为真,则不再对y进行运算,x为假,则对y进行运算
x&y,如果x为假,则不再对y进行运算,x为真,则对y进行运算
i = 0, 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);
}

16 / CAL(2)=16/222=32!=2,所以不执行后面语句;
执行else,else中定义了新的nums,但最后打印应该是1,因为最外层定义了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;
}  

这串代码错误在s1 = s2,其存在一个问题,就是将同一块内存释放了两次

#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 freeStudent(struct Student *student) {
    free(student->name);
}
int main(void) {
    struct Student s1, s2;
    initializeStudent(&s1, "Tom", 18);
    initializeStudent(&s2, "Jerry", 28);
    // 正确的赋值方式
    freeStudent(&s1); // 首先释放s1的name内存
    s1.name = (char *)malloc(strlen(s2.name) + 1); // 为s1.name分配内存
    strcpy(s1.name, s2.name); // 复制s2的name到s1
    s1.age = s2.age; // 复制age
    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);
}
Welcome to XUPT , welcome to Xiyou Linux Group [2023]

首先可以看一下结构体的大小为72字节,考察结构体和联合体的对齐规则,((struct structure *)arr)->node.string),arr表示首元素的地址,将其强转换成(struct structure *),并指向node中的string,并以%s打印出来,int foo,占0~3,union从8开始占,所以从0x636c6557开始打印,并且这些数以小端存储,对照ASCII值依次打印。

13.GNU/Linux (选做)

注:嘿!你或许对 Linux 命令不是很熟悉,甚至你没听说过 Linux。但别担心,这是选做题,了解
Linux 是加分项,但不了解也不扣分哦!
你知道 cd 命令的用法与 / . ~ 这些符号的含义吗?
请问你还懂得哪些与 GNU/Linux 相关的知识呢~

cd命令的用法
cd命令的基本语法为“cd [目录路径]”,其中[目录路径]表示要切换到的目录路径。以下是cd命令的一些常见用法:

切换到指定目录:使用cd命令后跟目录路径,如cd /var/www/html,表示切换到/var/www/html目录。如果目录路径是相对路径,如cd html,则表示切换到当前目录下的html目录。
切换到上级目录:使用cd命令后直接回车(或者cd …),表示切换到当前目录的上级目录。
切换到用户家目录:使用cd命令后加上~符号即可切换到当前登录用户的家目录,如cd ~或仅输入cd(不带参数,直接回车)。
切换到上次所在的目录:使用cd命令后加上-符号,表示切换到上次所在的目录,如cd -。
使用环境变量切换目录:使用cd命令后加上$符号,后面跟上环境变量名。例如,cd $HOME可以切换到用户家目录。
符号的含义
/:根目录/目录分隔符。在Linux文件系统中,每个文件和目录都有一个唯一的文件路径,文件路径是由“/”符号分隔的一系列目录和文件名组成的字符串。例如,“/home/user/Documents/file.txt”是一个文件路径,其中“/”表示根目录。
.:当前目录。在Linux命令中,.代表当前目录。例如,./file.txt表示当前目录下的file.txt文件。
:当前用户的主目录。在Linux中,符号代表当前用户的家目录。家目录是每个用户在Linux系统中的默认工作目录,当用户登录到系统后,终端会自动切换到该用户的家目录。例如,如果当前登录的用户是“user”,那么~符号就代表/home/user目录。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值