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

目录

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

 1.先预测一下~

2.欢迎来到Linux兴趣小组

 3.一切都翻倍了吗

 4.奇怪的输出

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

6.自定义过滤 

7.静...态... 

 8.救命!指针!

 9.咋不循环了

10.到底是不是TWO 

11.克隆困境 

 12.你好,我是内存

13.GUN/Linux(选做) 


 

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

有1000瓶水,其中有一瓶有毒,小白鼠只要尝到一点带毒的水,24小时后就会准时死亡。

至少要多少小白鼠才能在24小时内鉴别出哪一瓶水有毒?

二进制数表示位数,1000可以用10位二进制数字表示,给每杯水二进制编号,对应每一位数字分为一组,共分为十组,从右到左依次为1-10;

一个组对应一个小鼠,让每组的小鼠去喝对应位数是1的水,这样每一杯水都会有小鼠喝,最后小鼠死亡,就去找对应位数是1的;

例如,1,3组的小鼠死亡,就是第五杯水有毒;

 1.先预测一下~

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

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

 这样运行的结果就会是Hi, 我相信 xxx 可以面试成功!

2.欢迎来到Linux兴趣小组

有趣的输出,为什么会这样子呢~

#include<stdio.h>
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语句比较*ptr0和*ptr1,此时*ptr0和*ptr1都代表W。条件为真,执行if语句内部,内部的代码,考察printf的返回值,我的理解是printf返回值是内部双引号的字符长度,所以这个语句的打印结果开始为Hello, Linux Group - 2然后最右边%d的值是对应右边printf的返回值双引号内部无字符,%d为0打印出0,然后再到外部的%d此时内部printf双引号中有23个字符%d此时是0,所以算一个字符,最后输出结果是Hello, Linux Group - 2023。

 3.一切都翻倍了吗

#include<stdio.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));
    //7       8       2
    printf("%d\n", sprintf(str, "0x%x", num) == num);
    //0
    printf("%zu\t%zu\n", strlen(&str[0] + 1), strlen(arr + 0));
    //4       5
    return 0;
}

第一行printf :\t是补齐8位,%zu是标准输出sizeof()计算字节大小的形式;第一个sizeof计算结果是7,*&arr是取到了整个字符串,里面的字符数是7,第二个sizeof计算结果是8,arr是一个字符数组字符数是7,arr+0相当于多加一个char型字符,所以相对于第一个sizeof计算结果大1.第三个sizeof计算结果是2.这个计算的是短整型变量占用字节数是2。另外sizeof()括号内部的计算和赋值不会被进行,相当于sizeof不是一个函数,是一个编译的时候就会起作用的单目运算符,所以在这一布结束之后,num的值还是520;                                                                                                                第二行printf:sprintf是一个函数,%x是以16进制形式输出,所以打印到num中的是0x208,五位,返回值也就是5,与num的520不等所以第二行printf输出的结果是0。                                                 第三行printf:strlen计算的是字符长度,从字符的初地址开始计算到'\0'停止,'\0'不计入长度,第一个strlen是从'i'开始到'\0'结束,共四个字符所以输出的数值是4.第二个strlen是从'L'开始读取的长度是5.对应的第二个printf的输出值是5.

 4.奇怪的输出

#include<stdio.h>
int main(void)
{
    char a = 64 & 127;
    char b = 64 ^ 127;
    char c = -64 >> 6;
    char ch = a + b - c;
    printf("a =  %d\nb = %d\nc = %d\n", a, b, c);
    //           64      63      -1
    printf("ch = %d\n", ch);
    //           -128
    return 0;
}

 本题涉及到了二进制数的按位运算以及原码反码补码的相关知识,第一行main函数中第一行a是对64和127的二进制补码进行按位与运算,这个运算的逻辑是(1&1=1)(1&0=0)(0&0=0)所以a是对01000000(64的补码)和01111111(127的补码)进行按位与,结果是01000000(十进制64),第二行是对64和127的二进制补码进行按位异或,这个运算的逻辑是(1^1=0)(1^0=1)(0&0=0),第三行是移位运算,按位左移和按位右移分两个部分,左侧是被移动的数和右侧移动的位数,右移的右边溢出部分丢弃,左边补符号位,是对于二进制补码进行运算,对于负数右移的话左边部分就补1,对二进制补码左移的话右边补0,左侧溢出舍弃.-64的二进制码是11000000,补码是反码加1是11000000,右移6位之后是11111111,这是二进制补码,转为原码是10000001,十进制的数是-1.第四行运算全部为二进制补码运算分别为(64)补+(63)补+(-1)补=01000000+00111111+10000001=(10000000)补,结果这个补码是-128的补码,这串二进制数在原码表示是0,在补码表示中是-128.

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

#include<stdio.h>
int func(int a, int b)
{
    if(!a) return b;//当a是0时候返回b的值;
    //-2147483648 -2147483646
    //0 6
    return func((a & b) << 1, a ^ b);//int型数据正向超限时,再一次会从最小数值重新开始;
    //负向超限会从最大值重新开始;
}
int main(void)
{
    int a = 4, b = 9, c = -7;
    printf("%d\n",func(a, func(b, c)));
    //6
    return 0;
}

这个函数其中还嵌套了函数本身,函数中的if语句只有当a是0的时候才会执行,在main函数中printf要输出的值是两个函数在一块的嵌套从内向外看第一次传进函数的数a和b分别是9和-7,然后返回函数fun((a&b)<<1,a^b)这两个逻辑运算符号的详细解读在23年的题4,下面重复的知识点不会再过多重复,所以函数第一次执行完毕之后函数fun中的(a,b)分别是18,-16第三次之后变成了32和-30,会循环下去,在这里有一个补充的知识点,在代码中的注释关于数据超出限制之后的变化,int型是8字节相当于二进制数32位最小的数值是-2147483648它的二进制补码为1后面跟31个0,而且在32位数中只存在补码没有原码和反码,这时候观察规律可以发现b是比a大2,用32位二进制表示的话是31个1和1个0,再一次进行函数的话a变成0,b变成2,此时if语句成立可以得到main函数中printf中的最内的函数fun运算结果是2,再一次进行函数运算的时候a是4,b是2,第一次运算结果是0和6,满足if语句所以再一次进行函数的时候会返回b的值6.最终代码的运行结果也就是6

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

代码想实现的目标是过滤负数留下正数 

#include<stdio.h>
#include<stdlib.h>
typedef int (*Predicate)(int);
int *filter(int *array, int length, Predicate predicate,int *resultLength)//补全函数
{
    int *RESULT=(int *)malloc(length*sizeof(int));//申请动态内存
    int COUNT=0,i;
    for(i=0;i<length;i++)
    {
        if(isPositive(array[i]))//参考返回正数的注释
        {
            RESULT[COUNT++]=array[i];//存入正数
        }
    }
    *resultLength = COUNT;
    return RESULT;
}
int isPositive(int num) { return num > 0; }//返回正数,当>0时候返回,1<=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);//释放malloc的内存
    return 0;
}

 以上是相关代码和注释解析。

7.静...态... 

1.static主要作用是改变作用对象的作用域和生命周期 

2.static与变量结合后可以增大变量的作用域和延长生命周期,使其在程序运行期间一直存在,不会因为进出函数而丢失本体数值

3.static与函数结合,它会改变函数的作用域,使其它文件不能调用该函数,只可以在该文件中使用.

4.当static与指针结合时,它可以用于定义静态指针,使指针不会随着函数的调用结束而销毁,使指针的数据在多个函数之间共享。

5.static使变量从原来的栈(函数运行时存在,结束时候释放)中存放改为静态存储区,不会因块作用域的消失而销毁.

 8.救命!指针!

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

 第一行本质是一个指针,指向数组首元素的地址。

第二行本质是一个数组,包含10个指针,且由于const使得数据不能被改变。

第三行比较复杂,是一个函数指针,f1是函数名后面括号里面是函数的两个参数第一个参数是int型的指针,第二个参数是int型变量,函数的返回是int型。

 9.咋不循环了

#include<stdio.h>
#include<stdlib.h>
int main(int argc, char* argv[])
{
    printf("[%d]\n", argc);
    //[1]
    while(argc)
    {
        ++argc;
    }
    int i = -1, j = argc, k = 1;
    i++ && j++ || k++;
    printf("i = %d, j = %d, k = %d\n", i, j, k);
    //           0       1       2
    return EXIT_SUCCESS;
}

argc是argument count的缩写,表示传入main函数参数的个数,在这段代码中argc的数值为1,所以第一个printf的输出结果是[1],while循环会进行很多轮直到argc超出范围然后数值再一次加到0,此时下一行i=-1,j=0,k=1.然后进行运算i++ && j++的顺序是先判断i && j结果是0然后i变成1,j变成0,因为前面的运算结果是0所以后面的k++也会进行k变成2,最后输出的结果是0 1 2.这道题的return后面的EXIT_SUCCESS是c语言stdlib.h头文件库定义的一个符号常量是0,与之对应的是EXIT_FAILURE(1).

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(>w<)\n");
    } else 
        {
            int nums = MAGIC_CAL(++nums, 2);
        }
    printf("%d\n", nums);
    //       1 
    return 0;
}

#define是对于CAL(a)下定义,定义了一个宏,名为CAL,下文出现的CAL可以直接纯文本替换为a*a*a,第二个宏同理,所以代码中的第一个if语句的条件就相当于16/2*2*2,有先后顺序所以最终的结果是32!=2所以执行else语句,在else语句中有一个新的int这个新的int的作用范围只是在这个大括号里面,出了大括号之后nums的值就是1了.

11.克隆困境 

#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;
}
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);
//s1的姓名:Jerry 年龄:28
printf("s2的姓名:%s 年龄:%d\n",s2.name,s2.age);
//s2的姓名:Jerry 年龄:28
free(s1.name);
free(s2.name);
return 0;
}

 对于malloc的使用有兴趣的话可以去搜,代码中的s1=s2是对于s1地址的改变,malloc申请的内存最终要free的是申请时候的地址,此时再free(s1)等同于free(s2).要对s1和s2进行拷贝,可以直接改变s1中的数据不改变其地址,看下面的代码.

#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 mycopy(struct Student *s1,const struct Student *s2)
{
    strcpy(s1->name,s2->name);
    s1->age = s2->age;
}
int main(void)
{
    struct Student s1, s2;
    initializeStudent(&s1,"Tom",18);
    initializeStudent(&s2,"Jerry",28);
    //s1=s2;
    mycopy(&s1, &s2);
    printf("s1的姓名:%s 年龄:%d\n",s1.name,s1.age);
    //s1的姓名:Jerry 年龄:28
    printf("s2的姓名:%s 年龄:%d\n",s2.name,s2.age);
    //s2的姓名:Jerry 年龄:28
    free(s1.name);
    free(s2.name);
    return 0;
}

用一个自己的函数直接对s1和s2中的数据进行改变,不会改变s1s2的地址最终ferr也不会受到影响.

 12.你好,我是内存

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

在main函数里面的printf是将arr的强制转换为struct structure*类型,将整行数组转化为结构体指针,然后访问node.string,打印出字符串,具体是如何打印的呢,可以看到结构体的所有类型中最大类型的字节数是8(long long void *)(void *在64位操作系统为8字节,与类型无关,在32位操作系统为4字节),联合体中首先是一个int类型占4字节由于内存对齐的对齐数是8(0x590ff23c为四个字节)所以会空出四个字节(0x2fbc5a4d),现在的大部分电脑都是小端储存,对于0x636c6557会从后往前两位两位读取首先是16进制的0x57,转化为十进制是87对应字符W,等到读取到第四行5d对应字符"]"对于后面的数据不再打印.最终输出结果是Welcome to XUPT , welcome to Xiyou Linux Group [2023],

13.GUN/Linux(选做) 

 单独cd表示切换到当前用户的主目录                                                                                                 cd ...表示切换到上一级目录                                                                                                               cd 目录名称 表示切换到该目录                                                                                                         cd ~ 表示进入用户主目录                                                                                                                 cd -返回进入此目录之前所在的目录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值