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

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

感谢 Zhilu 重新录入题目原件。好人一生平安。

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

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

Copyright © 2022 西邮Linux兴趣小组, All Rights Reserved.
本试题使用采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。

0. 我的计算器坏了?!

· Q :

2^10 = 1024对应于十进制的4位,那么2^10000对应于十进制的多少位呢?

·A :

  • 此题考察二进制转化
  1. 二进制转换为十进制的规则为:lg2^n+1;
  2. 举例:lg2^10+1=10 * lg2 + 1(lg2约等于0.3010),故答案为4;
  3. 综上所述,2^10000转换为十进制答案是3011;

1. printf还能这么玩?

· Q :

尝试着解释程序的输出。

int main(void) {
    if ((3 + 2 < 2) > (3 + 2 > 2))
        printf("Welcome to Xiyou Linux Group\n");
    else
        printf("%d\n", printf("Xiyou Linux Group - 2%d", printf("")));
}

· A :

  1. 首先观察if语句的条件,3 + 2 < 2为假,返回0,3 + 2 > 2为真,返回1,0 > 1,为假,返回0,故if语句不执行,进入else语句;
  2. 由于printf函数嵌套,先执行括号里的,因为最内部的printf什么也不打印,故printf函数返回0,中间的printf函数打印Xiyou Linux Group - 20,(末尾%d被替换为0);最外层的printf函数打印中间循环语句括号内的字符数22;
  3. 输出结果为Xiyou Linux Group - 2022

2. 你好你好你好呀!

· Q :

  • 程序的输出有点奇怪,请尝试解释一下程序的输出吧。
  • 请谈谈对sizeof()strlen()的理解吧。
int main(void) {
    char p0[] = "Hello,Linux";
    char *p1 = "Hello,Linux";
    char p2[11] = "Hello,Linux";
    printf("p0 == p1: %d, strcmp(p0, p2): %d\n", p0 == p1, strcmp(p0, p2));
    printf("sizeof(p0): %zu, sizeof(p1): %zu, sizeof(*p2): %zu\n",
           sizeof(p0), sizeof(p1), sizeof(*p2));
    printf("strlen(p0): %zu, strlen(p1): %zu\n", strlen(p0), strlen(p1));
}

· A :

  • 对输出的解释:
    1. 首先p0为字符数组,p1为字符串指针,故p0不等于p1,返回0;
    2. p0的长度未定,故可储存进末尾的’\0’,但p2的长度为11,字符串长度也为11,故无法储存进末尾的’\0’,比较到第12位的时候,p2后一个字符的值不定(未知),故返回值未知;
    3. 由sizeof的计算规则易得,p0的长度返回值为12;
    4. 因为p1为指针,所以在x64环境下,返回值为8;
    5. *p2表示对p2解引用,为第一个元素,长度大小为1,故返回1;
    6. 由strlen的计算规则易得,p0的返回值为11;
    7. strlen函数由p1的首元素开始向后计数,遇’\0’停止,返回值为11;
  • 涉及的知识点解释:
    1. sizeof是一个运算符,计算时连带’\0’,且可用类型做参数(计算结果为占空间内存字节数)

    2. strlen是string.h中的一个函数,当计算到’\0’时停止,且只可用char*做参数(计算结果为字符串长度)

    3. strcmp比较p0和p1,因为p0和p1相等,故返回’0’;

      对strcmp函数的解释:

      • 头文件为<string.h>;
      • 比较两个字符串,逐个字符进行比较(比较ASCII码值),当遇到’\0’或遇到不同字符时停止比较并返回结果,当第一个字符串小时,返回一个负数,相等时返回0,当第一个字符串大时,返回一个正数;

3. 换个变量名不行吗?

· Q :

请结合本题,分别谈谈你对C语言中「全局变量」和「局部变量」的「生命周期」理解。

int a = 3;
void test() {
    int a = 1;
    a += 1;
    {
        int a = a + 1;
        printf("a = %d\n", a);
    }
    printf("a = %d\n", a);
}
int main(void) {
    test();
    printf("a= %d\n", a);
}

· A :

  1. 全局变量:
    • 从定义变量开始,整个程序内均存在;
  2. 局部变量:
    • 从定义变量开始,到所在函数结束时被销毁;
  3. 此题的输出:
    • 在test函数内,第一个输出未定的原因为在函数内部定义一个变量a且未初始化,则=右边的a的值不定;
    • 第二个输出为2;
    • 在主函数内的输出为3;

4. 内存对不齐

· Q :

unionstruct各有什么特点呢,你了解他们的内存分配模式吗。

typedef union {
    long l;
    int i[5];
    char c;
} UNION;
typedef struct {
    int like;
    UNION coin;
    double collect;
} STRUCT;
int main(void) {
    printf("sizeof (UNION) = %zu\n", sizeof(UNION)); 
    printf("sizeof (STRUCT) = %zu\n", sizeof(STRUCT));
}

· A :

  1. union为联合体,内部成员共用一块内存,其大小为联合体内最大成员大小;
  2. struct为结构体,有对齐数,对齐数为最大成员大小与默认对齐数的较小值(VS默认对齐数为8,linux无默认对齐数);结构体的大小为所有成员的大小之和(一定为对齐数的倍数);
  3. 程序输出:
    • struct结构体的对齐数为8,联合体大小为20,但需对其到对齐数的整数倍,则为24字节大小;故输出分别为24 40

5. Bitwise

· Q :

  • 请使用纸笔推导出程序的输出结果。
  • 请谈谈你对位运算的理解。
int main(void) {
    unsigned char a = 4 | 7;
    a <<= 3;
    unsigned char b = 5 & 7;
    b >>= 3;
    unsigned char c = 6 ^ 7;
    c = ~c;
    unsigned short d = (a ^ c) << 3;
    signed char e = -63;
    e <<= 2;
    printf("a: %d, b: %d, c: %d, d: %d\n", a, b, c, (char)d);
    printf("e: %#x\n", e);
}

· A :

  1. 位运算解释:
    1. ‘&’为按位与,当二进制位都为1时结果为1,否则为0;
    2. ‘|’为按位或,当二进制位至少有一个为1时结果为1,否则为0;
    3. ‘^’为按位异或,当二进制位不同时结果为1,否则为0;
    4. ‘>>’为右移操作符,表示将操作数的二进制位向右移动指定的位数,右移1位相当于除以2(注:无符号整数移动后高位补0,有符号整数移动后高位补符号位)
    5. ‘<<’为左移操作符,表示将操作数的二进制位向左移动指定的位数,左移一位相当于乘以2;
    6. ‘~’为按位取反操作符,表示将操作数的二进制按位取反,0变为1,1变为0;
  2. 程序输出:
    • 4的八位二进制为00000100;7的八位二进制为00000111;所以4 | 7结果为00000111(7);a <<= 3结果为00111000(56)
    • 5的八位二进制为00000101;7的八位二进制为00000111;所以5 & 7结果为00000101(5);b >>= 3结果为00000000(0)
    • 6的八位二进制为00000110;7的八位二进制为00000111;所以6 ^ 7结果为00000001(1);c = ~c结果为11111110(254)
    • (a ^ c) << 3的结果为(11111110) 00110000(48)(因为输出时转化为char类型,故为48);
    • -63的八位二进制为10111111;则e <<= 2的结果为11111100(为补码形式)转换为原码是000000100(4)

6. 英译汉

· Q :

请说说下面数据类型的含义,谈谈const的作用。

  1. char *const p
  2. char const *p
  3. const char *p

· A :

  1. 第一个p类型为指向字符的常量指针,可通过p来修改指向地址的变量的值,但不可修改p所指向的地址;
  2. 第二个p类型为指向常量字符的指针,不可通过p来修改指向变量的值,但可修改p所指向的地址;
  3. 第三个p类型为指向常量字符的指针,不可通过p来修改指向变量的值,但可修改p所指向的地址;

7. 汉译英

· Q :

请用变量p给出下面的定义:

  1. 含有10个指向int的指针的数组。
  2. 指向含有10个int数组的指针。
  3. 含有3个「指向函数的指针」的数组,被指向的函数有1个int参数并返回int

· A :

  1. (int*)p[10];
  2. int *p[10];
  3. int (*p[3])(int);

8. 混乱中建立秩序

· Q :

你对排序算法了解多少呢?
请谈谈你所了解的排序算法的思想、稳定性、时间复杂度、空间复杂度。

提示:动动你的小手敲出来更好哦~

· A :

  1. 冒泡排序:
void BubbleSort(int arr[],int sz)
{
    for(int i = 0; i < sz - 1; i++)
    {
        for(int j = 0; j < sz - i - 1; j++)
        {
            if(arr[j] > arr[j+1])
            {
                int k = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = k;
            }
        }
    }
}
int main()
{
    int arr[10] = {15,4,7,2,8,6,5,0,3,13};
    int sz = sizeof(arr) / sizeof(arr[0]);
    BubbleSort(arr,sz);
    for(int i = 0; i < sz - 1; i++)
    {
        printf("%d ",arr[i]);
    }
}
  1. 选择排序:
void SelectionSort(int arr[], int sz)
{
    int k = 0;
    for(int i = 0; i < sz; i++)
    {
        for(int j = i; j < sz; j++)
        {
            if(arr[i] > arr[j])
            {   k = arr[i];
                arr[i] = arr[j];
                arr[j] = k;
            }
        }
    }
}
int main()
{
    int arr[10] = {0,4,56,2,5,7,89,9,8,3};
    int sz = sizeof(arr) / sizeof(arr[0]);
    SelectionSort(arr,sz);
    for(int i = 0; i < sz; i++)
    {
        printf("%d ",arr[i]);
    }
}

9. 手脑并用

· Q :

请实现ConvertAndMerge函数:
拼接输入的两个字符串,并翻转拼接后得到的新字符串中所有字母的大小写。

提示:你需要为新字符串分配空间。

char* convertAndMerge(/*补全签名*/);
int main(void) {
    char words[2][20] = {"Welcome to Xiyou ", "Linux Group 2022"};
    printf("%s\n", words[0]);
    printf("%s\n", words[1]);
    char *str = convertAndMerge(words);
    printf("str = %s\n", str);
    free(str);
}

· A :

代码如下:

char* convertAndMerge(char words[2][20])
{
    int sz = strlen(words[0]) + strlen(words[1]) + 1;//计算新字符数组所需要的内存大小;
    char* num = (char*)malloc(sz);//为新字符串指针分配内存;
    strcpy(num, words[0]);//将第一行复制进字符串指针内;
    strcat(num, words[1]);//将第二行字符串复制进第一行字符串之后;
    for(int i = 0; i < sz; i++)//将字符串的大写字母转为小写字母;小写字母转为大写字母;
    {
        if(num[i] >= 'A' && num[i] <= 'Z')
        {
            num[i] = num[i] + 32;
        }
        else if(num[i] >= 'a' && num[i] <= 'z')
        num[i] = num[i] - 32;
    }
    return num;//将字符串指针返回;
}

int main(void) {
    char words[2][20] = {"Welcome to Xiyou ", "Linux Group 2022"};
    printf("%s\n", words[0]);
    printf("%s\n", words[1]);
    char *str = convertAndMerge(words);
    printf("str = %s\n", str);
    free(str);
}

10. 给你我的指针,访问我的心声

· Q :

程序的输出有点奇怪,请尝试解释一下程序的输出吧。

int main(int argc, char **argv) {
    int arr[5][5];
    int a = 0;
    for (int i = 0; i < 5; i++) {
        int *temp = *(arr + i);
        for (; temp < arr[5]; temp++) *temp = a++;
    }
    for (int i = 0; i < 5; i++) {
        for (int j = 0; j < 5; j++) {
            printf("%d\t", arr[i][j]);
        }
    }
}

· A :

  1. 输出如下:
0       1       2       3       4
25      26      27      28      29
45      46      47      48      49
60      61      62      63      64
70      71      72      73      74
  1. 原因:
    • 当进入第一个循环时,i会循环五次,而每次i循环时,循环体内的temp都会循环多次,其次数为此元素加此元素后的元素个数,而此时a的值一直递增,因此arr[0][4]与arr[1][0]的值不是相差1;
    • 第二个循环内,目的为将所存二维数组逐个打印;

11. 奇怪的参数

· Q :

你了解argc和argv吗?
直接运行程序argc的值为什么是1?
程序会出现死循环吗?

#include <stdio.h>
int main(int argc, char **argv) {
    printf("argc = %d\n", argc);
    while (1) {
        argc++;
        if (argc < 0) {
            printf("%s\n", (char *)argv[0]);
            break;
        }
    }
}

· A :

  1. argc是一个整数,表示命令行参数的个数(包括程序名本身);argv是一个字符指针数组,每一个元素都是一个指向字符串的指针,字符串为命令行参数(argv[0]表示程序名);
  2. 未提供命令行参数,故argc的值为1(1代表程序名本身);
  3. 不会。循环内argc一直递增,直到231-1时,下一次加一得到-231,if条件成立,输出程序名本身后,跳出循环;

12. 奇怪的字符

· Q :

程序的输出有点奇怪,请尝试解释一下程序的输出吧。

int main(int argc, char **argv) {
    int data1[2][3] = {{0x636c6557, 0x20656d6f, 0x58206f74},
                       {0x756f7969, 0x6e694c20, 0x00000000}};
    int data2[] = {0x47207875, 0x70756f72, 0x32303220, 0x00000a32};
    char *a = (char *)data1;
    char *b = (char *)data2;
    char buf[1024];
    strcpy(buf, a);
    strcat(buf, b);
    printf("%s \n", buf);
}

· A :

  1. 首先输出为Welcome to Xiyou Linux Group 2022;
  2. 大多电脑为小端储存,故高位字节存于高地址,低位字节存于低地址,由于电脑从低地址开始接收,所以将int类型的数截断为char类型(如0x636c6557变为0x57,0x65,0x6c,0x63),应在每段从右向左依次输出,且字符串输出之至’\0’停止;
    • 注意输出的字符为将十六进制的数转换为十进制的数后对应ASCII表上的字符;
    • 一个十六进制数占4个bite位,8个bite位为一个字节,故int截断为char类型时,0x后跟两位十六进制数;

13. 小试宏刀

· Q :

  • 请谈谈你对#define的理解。
  • 请尝试着解释程序的输出。
#define SWAP(a, b, t) t = a; a = b; b = t
#define SQUARE(a) a *a
#define SWAPWHEN(a, b, t, cond) if (cond) SWAP(a, b, t)
int main() {
    int tmp;
    int x = 1;
    int y = 2;
    int z = 3;
    int w = 3;
    SWAP(x, y, tmp);
    printf("x = %d, y = %d, tmp = %d\n", x, y, tmp);
    if (x>y) SWAP(x, y, tmp);
    printf("x = %d, y = %d, tmp = %d\n", x, y, tmp);
    SWAPWHEN(x, y, tmp, SQUARE(1 + 2 + z++ + ++w) == 100);
    printf("x = %d, y = %d\n", x, y, tmp);
    printf("z = %d, w = %d, tmp = %d\n", z, w, tmp);
}

· A :

  1. 第一个宏定义内将a, b, c交换;
  2. 第二个宏定义将a平方(注意宏运算只是简单的文本替换);
  3. 第三个宏定义判断cond是否为真,若为真,则执行宏SWAP;
  4. 输出:
    • 因为引用了宏SWAP,故输出为2,1,1;
    • if语句内判断为真,故执行宏SWAP,得到输出为1,2,2;
    • 宏SQUARE计算结果不为100,故条件为假,将SWAP宏展开替换过去,则不执行"t = a",只执行后两句;
    • 第一个输出内只接收了两个数,故输出为2,2;
    • 第二个输出为5,5,2(z和w均自增了两次);

14. GNU/Linux命令 (选做)

· Q :

你知道以下命令的含义和用法吗:

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

  • ls
  • rm
  • whoami

请问你还了解哪些GNU/Linux的命令呢。

· A :

  1. 在Linux系统中, ls 命令用于列出目录内容。

基本用法:

  • 直接输入 ls ,会列出当前目录下的文件和子目录名称,但不包括隐藏文件(以“.”开头的文件)。
  • ls -a 可以列出当前目录下的所有文件和子目录,包括隐藏文件。例如,在包含隐藏配置文件的目录下使用这个命令,就能看到全部文件。
  • ls -l 会以长格式显示文件和目录的详细信息,像文件类型、权限、硬链接数、所有者、组、大小、修改日期和文件名。如查看某个软件安装目录下文件的详细属性时可以使用。
  • ls -t 会按照文件修改时间排序,最新修改的文件排在前面,这在查找最近操作的文件时很有用。
  • 也可以组合使用这些参数,如 ls -alt ,它会以长格式列出所有文件并且按修改时间排序。
  • 另外,如果要查看指定目录的内容,在 ls 后加上目录路径即可,如 ls /home/user/Documents 。
  1. 在Linux系统里, rm  命令用于删除文件或目录。
  • 基本用法: rm [选项] 文件或目录 。例如, rm file.txt  可直接删除文件  file.txt 。若要删除目录,需加  -r  或  -R  选项,像  rm -r dirname  能递归删除  dirname  目录及其内部的所有内容。

  • 常用选项包括: -f ,代表强制删除,不提示确认,可用于删除只读文件; -i ,则是在删除每个文件或目录前都要求用户确认; -v ,用于显示详细的删除过程。

  • 同时,使用  rm  命令时要格外小心,特别是在使用递归删除和强制删除选项时,因为误删文件或目录可能会导致数据丢失且难以恢复。

  1. 在Linux系统中, whoami  命令用于显示当前用户的用户名。
  • 基本用法:它的基本用法非常简单,只需在终端输入  whoami ,然后回车,就会直接输出当前登录用户的用户名,没有其他额外的参数或选项,这个命令对于确定当前操作的用户身份以及在脚本编写中进行用户验证等方面非常实用。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值