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

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

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

通过二进制数表示来解决。每瓶水都可以用一个编号来标记,从000…000到111…111,这样每一位的0或1可以表示一只小白鼠是否应该喝这瓶水。如果有n只小白鼠,可以测试2^n瓶水。第一只小白鼠负责测试最低位是1的所有水,第二只测试倒数第二位是1的所有水,以此类推。如果一瓶水有毒,可以根据24小时后死亡的小白鼠确定哪瓶水有毒。因为有1000瓶水,需要足够的小白鼠来表示1000这个数字。1000转换为二进制是1111101000,需要10位,因此至少需要10只。

1.先预测一下~

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

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

只需要将 return "名字"中的 “名字” 替换为实际名字,然后就可以在输出中看到实际名字。

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

ptr0 指向的字符串字面量和 ptr1 数组初始化时复制的字符串具有相同的第一个字符’W’,所以 if 的条件为真。

先调用 printf(“”),返回字符数 0。然后,在字符串中插入0,再调用 printf 打印整个字符串。因此,外部的 printf 打印 “Hello, Linux Group - 20”,最外部的 printf 返回打印的字符数 23。

Hello, Linux Group - 2023

最后,计算 ptr0 和 ptr1 指针之间的差。因为 ptr0 是一个指向字符串字面量的指针,而 ptr1 是指向数组的指针,它们指向的是不同类型的存储,结果可能是任何整数值。

3.一切都翻倍了吗

  • 请尝试解释一下程序的输出。

  • 请谈谈对 sizeof()和 strlen()的理解吧。

  • 什么是 sprintf(),它的参数以及返回值又是什么呢?

    #include<stdio.h>
    #include<string.h>
    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));
        return 0;
    }
    
    

sizeof() :返回一个对象或类型所占的字节数。
strlen():返回一个以 null 终止符结束的字符串的长度,不包括 null 终止符。
sprintf() 是一个函数,它和 printf() 类似,不过它将输出格式化后的字符串写入一个字符串数组而不是写到标准输出。它的参数是一个指向字符数组的指针,这将是字符串的目的地,后面跟着一个格式字符串,然后是一系列要格式化的值。sprintf() 返回写入的字符数量(也不包括结尾的 null 字符)。

sizeof(*&arr) 计算 arr 第一个元素的大小,因为 *&arr 相当于 arr, arr 在这里解引用为其第一个元素,返回 sizeof(char)是1。
sizeof(arr + 0) 计算的是 arr 的指针加上 0 的结果的大小,这里的 arr + 0 实际上是 arr 的地址,这将返回指针的大小 8 。
sizeof(num = num2 + 4) 会先计算表达式 num = num2 + 4,但 sizeof 是编译时操作,不会执行赋值操作,只返回 num 的大小,即sizeof(short)。
sprintf(str, “0x%x”, num) 将 num 的值格式化为十六进制并存储在 str 中。sprintf 返回的是写入 str 的字符数量。由于 num 没有被 sizeof 运算符的赋值操作改变,它仍然是 520。十六进制中,520 对应的是 0x208,因此 sprintf 会返回 5。5 不等于 520,sprintf(str, “0x%x”, num) == num 为假,所以 打印出 0。
strlen(&str[0] + 1) 计算 str 中第一个字符后面的字符串长度。假设 str 的内容是 “0x208”,那么 &str[0] + 1 就是 “x208” 的长度 4。
strlen(arr + 0) 计算 arr 指向的字符串即"Linux"的长度,所以它的长度是 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);
    }
    
    
    a = 64 b = 63 c = -1
    ch = -128
    

char a = 64 & 127;
按位与操作。二进制中,64是01000000,127是01111111。结果是01000000,即64。
char b = 64 ^ 127;
按位异或操作。二进制中,64是01000000,127是01111111。结果是00111111,即63。
char c = -64 >> 6;
这是右移操作。c的值是-1。
char ch = a + b - c;
根据上面的计算,a + b - c 是 64 + 63 - (-1),结果是 64 + 63 + 1 = 128。

由于char类型的取值范围是-128到127,因此结果会发生溢出。128超出了char可以表示的正数范围。128对应的二进制10000000的补码形式,即-128。因此,ch的值是-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)));
    }
    
    

这个func函数实际上实现了两个整数的加法操作,但是没有使用加法运算符。它使用了递归和位运算来实现这一点。
函数func不断地递归,将按位异或的结果(a ^ b)和进位((a & b) << 1)相加,直到a变为0,此时返回b作为最终的和。
这段代码的最终输出取决于func(b, c)的结果,然后再将该结果与a相加。

6.自定义过滤

  • 请实现 filter()函数:过滤满足条件的数组元素。
    提示:使用函数指针作为函数参数并且你需要为新数组分配空间。

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

先遍历数组来确定满足条件的元素数量,然后分配足够的空间,并再次遍历数组将满足条件的元素复制到新分配的空间中。

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

typedef int (*Predicate)(int);

int *filter(int *array, int length, Predicate predicate, int *resultLength) {
    // 第一次遍历数组,计算满足条件的元素数量
    *resultLength = 0;
    for (int i = 0; i < length; ++i) {
        if (predicate(array[i])) {
            (*resultLength)++;
        }
    }
    // 根据满足条件的元素数量分配空间
    int *result = (int *)malloc(*resultLength * sizeof(int));
    if (result == NULL) {
        // 内存分配失败
        *resultLength = 0;
        return NULL;
    }
    // 第二次遍历数组,复制满足条件的元素到新分配的空间中
    int j = 0;
    for (int i = 0; i < length; ++i) {
        if (predicate(array[i])) {
            result[j++] = array[i];
        }
    }
    return result;
}
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;
}

当main函数调用filter函数时,它会打印出所有正数元素,即1 2 3 4 5 6。

7.静…态…

  • 如何理解关键字 static?

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

  • 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];
一个指针,指向一个有10个整数的数组。
const int
p[10];
一个数组,包含10个指向 const int 类型的指针。const int* 表示指针指向的整数值不能被修改(即指向常量整数的指针)。
int (f1(int))(int, int);
一个函数指针的声明。f1 是一个函数,接受一个整数参数,并返回一个指向函数的指针。返回的函数指针指向的函数接受一个整数指针和一个整数作为参数,并返回一个整数。

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

printf(“[%d]\n”, argc);
打印出传递给程序的命令行参数的数量(包括程序本身的名称)。

while (argc) { ++argc; }
这里有一个无限循环,它不断地增加argc的值。因为argc在程序开始时是正数,它会一直自增到溢出。

10.到底是不是 TWO

#include<stdio.h>
#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);
}

CAL(a) 宏定义,宏替换会直接替换文本,而不是进行数学运算。所以 16 / CAL(2) 会被替换为 16 / 2 * 2 * 2,而不是 16 / (2 * 2 * 2)。

在 else 块中,在一个新的局部作用域中声明了一个名为 nums 的变量,它会隐藏外部作用域的 nums 变量。因此,在 printf(“%d\n”, nums); 中引用的是内部作用域中的 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; }

s1 和 s2 是两个Student结构体,都有一个指向字符数组(字符串)的指针 name 和一个整型 age。initializeStudent 函数初始化这些结构体,为 name 分配内存,并复制给定的名字到这块内存中。

问题是在执行 s1 = s2; 时。将 s2 的所有值复制到 s1 中。对age 没问题,但对于 name 指针,这意味着 s1.name 现在指向 s2.name 相同的内存地址。这将导致两个问题:
当调用 free(s1.name); 释放 s1.name 指向的内存后,s2.name 仍然指向那块已经被释放的内存。当程序试图通过 free(s2.name); 再次释放这块内存时,将会发生未定义的行为,通常是程序崩溃,因为同一块内存不应该被释放两次。
s1 原本指向的内存(包含字符串 “Tom”)现在没有任何指针指向它,造成了内存泄露,因为这块内存没有被释放。
要解决这个问题,不能简单地赋值指针,而是需要为 s1.name 分配新的内存并复制 s2.name 的内容。这样,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;
}

在这段修正的代码中,首先释放了s1原本的name指针指向的内存,然后为s1.name重新分配内存,并从s2.name复制字符串,最后复制年龄。这样做可以确保每个学生结构体都有其自己的内存空间,并且在释放内存时不会互相影响。

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,0x4c20756f,0x78756e69,
               0x6f724720,0x5b207075,0x33323032,0x7825005d,
               0x636c6557,0x64fd6d1d};
    printf("%s\n",((struct structure *)arr)->node.string);
    return 0;
}

通过将 arr 数组的地址转换为 struct structure 类型的指针,并访问 node.string 字段来打印这个字符串。
结构体的第一个成员的地址就是结构体本身的地址,所以foo 成员紧接着是 node 联合体。因此,((struct structure *)arr)->node.string 实际上是解释 arr 数组的内容作为 node 联合体中的 string 字段。
这个程序将会打印出由 arr 数组中的十六进制数解码成的字符串。由于这些数是以小端格式存储的ASCII码,所以读取时需要将字节顺序翻转过来。例如,0x636c6557 会被读为 “Welc”(小端序意味着最低有效字节在前)。

程序输出将是这些十六进制值对应的 ASCII 字符串。

结束

  • 恭喜你攻克所有难关!迎难而上的决心是我们更为看重的。
    来到这里的人已是少数,莫踌踏在成功的门槛前。
    自信一点,带上你的笔记本电脑,来东区逸夫楼FZ103面试吧!
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值