2022InterviewSolution

西邮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. 我的计算器坏了?!

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

题解:

纯粹的一个数学题
对2^10000取以十为底的对数得k=10000log2
而且log2≈0.30103
则有 位数=0.3010
10^4=3010位

1. printf还能这么玩?

尝试着解释程序的输出。

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

题解:

观察if语句里面的判断条件3 + 2 < 2结果为否(0),3 + 2 > 2结果为是(1)
[表达式的结果只有1或者0]

[if判断里面除0外都为1,eg:if(2)也表示条件成立]

故等效为if(0>1),显然if判断为否,进行else部分的语句

[因为只有一行语句,所以不用{}括也行,如果有两行及以上,必须加{}]

进入else的语句后,可以看到是printf嵌套

首先先运行最里面的

[printf的返回值是printf实际控制输出的字符数]

printf(“”)

什么也没输出

其返回值为0

printf(“Xiyou Linux Group - 2%d”, printf(“”)) 的输出结果为

Xiyou Linux Group - 20

其返回值为22

最后输出上一个printf的返回值,则最终输出结果为

Xiyou Linux Group - 2022

2. 你好你好你好呀!

  • 程序的输出有点奇怪,请尝试解释一下程序的输出吧。
  • 请谈谈对sizeof()strlen()的理解吧。
    [sizeof操作符的结果类型为size_t,计算的是分配空间的实际字节数。]
    [strlen是计算的空间中字符的个数(不包括‘\0’)。]
int main(void)
{
    char p0[] = "Hello,Linux";
    char *p1 = "Hello,Linux";
    char p2[11] = "Hello,Linux";//数组存满,没有储存'\0'的空间
    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));
}

数组名不是普通的指针!

//int a[] = { 1, 2, 3, 4 };

//printf("%d\n", sizeof(a));//16 一个整形四个字节,四个元素

//printf("%d\n", sizeof(a + 0));//4 表示指针

//printf("%d\n", sizeof(*a));//4 a首元素地址,*a解引用首元素大小

//printf("%d\n", sizeof(a + 1));//4 a首元素地址加1还是地址

//printf("%d\n", sizeof(a[1]));//4数组第二个元素

//printf("%d\n", sizeof(&a));//4 整个数组地址    vc中是16 错误

//printf("%d\n", sizeof(&a + 1));//4  指向下一个数组的地址

//printf(“%d\n”, sizeof(&a[0])); //4首元素地址

//printf("%d\n", sizeof(&a[0] + 1));//4第二个元素2的地址

//printf("%d\n", sizeof(*&a));//16  &a整个数组地址 解引用为整个数组大小

题解:

输出的结果分别是
p0==p1: 0, strcmp(p0,p2): -72
shu ju
p0 != p2,因为两个都是地址,不可能相等,p2字符串内无终止’\0’,故输出的字符串越界,也就导致了无意义数据

sizeof(p0): 12, sizeof(p1): 8, sizeof(*p2): 1

p1为指针变量,占八字节,*p2指p2数组第一个元素,占1个字节

strlen(p0): 11, strlen(p1): 11

遇见‘\0’停止

3. 换个变量名不行吗?

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

在程序的开头定义的是全局变量,在函数内部定义的是局部变量。

在这里涉及2个概念,作用域与生命周期。

作用域指的是描述变量在哪段代码中有效;生命周期指的是变量什么时候被创建,什么时候被释放。

特别注意的是当全局变量和局部变量同名时,优先使用的是局部变量。

作用域:

    局部变量:作用域是变量所在的范围内,也就是变量所在的代码块。

    全局变量:作用域是整个工程。

生命周期:

   局部变量:进入作用域生命周期开始,出作用域生命周期结束。

   全局变量:生命周期是整个程序。

这里涉及另一个关于作用域与生命周期的概念,关键字 Static

关键字Statick可以修饰全局变量、局部变量、函数。

修饰全局变量:作用域发生变化,不再是整个程序,而是当前.c文件。但生命周期还是整个程序。

修饰局部变量:作用域仍然是当前代码块,但生命周期发生变化,生命周期是跟随整个程序。

修饰函数:作用域也是当前.c文件,与修饰全局变量一样。Static只能在当前源文件中使用,不能在其他源文件中使用。

int a = 3;

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

题解:

该题的知识点的概念在上文已经解释清楚了

首先看test函数内部的{}内定义局部变量a,且没有初始化,所以数值比较离谱

出了{}后int的数值就是test函数定义且初始化并进行一定运算的数值(a = 2)

刚开始定义了全局变量a = 3,且main函数内无a的定义,故最后输出全局变量的数值(a = 3)

4. 内存对不齐

unionstruct各有什么特点呢,你了解他们的内存分配模式吗。
struct 简单来说就是一些相互关联的元素的集合,说是集合,其实它们在内存中的存放是有先后顺序的,并且每个元素都有自己的内存空间。那么按照什么顺序存放的呢?其实就是按你声明的变量顺序来存放的,下面先看一个例子:

struct sTest

{

int a; //sizeof(int) = 4

char b; //sizeof(char) = 1

shortc; //sizeof(short) = 2

}x;

所以在内存中至少占用 4+1+2 = 7 byte。然而实际中占用的内存并不是7 byte,这就涉及到了字节对齐方式。

struct对齐需要满足两条原则

1.每个元素的字节数必须偏移量的整数倍

2.整体字节数必须为每个元素的整数倍

union 的不同之处就在于,它所有的元素共享同一内存单元,且分配给 union 的内存 size 由类型最大的元素 size 来确定,如下的内存就为一个 double 类型 size :

union uTest

{

int a; //sizeof(int) = 4

double b; //sizeof(double) = 8

char c; //sizeof(char) = 1

}x;

所以分配的内存 size 就是8 byte。

既然是内存共享,理所当然地,它不能同时存放多个成员的值,而只能存放其中的一个值,就是最后赋予它的值,如:

x.a = 3; x.b = 4.5; x.c = ‘A’;

这样你只看到x.c = ‘A’,而其它已经被覆盖掉,失去了意义。

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

题解:

sizeof (UNION) = 24

最大的是int i[5];占20个字节,遵守内存对齐原则,故为24个(3*8)

sizeof (STRUCT) = 40

各元素占据的字节数位(8字节对齐):4(补4个) 24 8
由两个原则得出,5*8=40

5. Bitwise

  • 请使用纸笔推导出程序的输出结果。
  • 请谈谈你对位运算的理解。
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: 56, b: 0, c: 254, d: 48
e: 0x4
& 按位与 两个数二进制相应位都为1,则该位的结果为1,否则为0
| 按位或 两个数二进制相应位有一个为1,则该位的结果为1,否则为0
^ 按位异或 两个数二进制相应位不同时,则该位的结果为1,否则为0
~ 按位取反 二进制对数的每一个位取反,即1变0,0变1
<< 左移运算 将二进制数的每个位向左移,高位丢弃,低位补0
->> 右移运算 将二进制数的每个位向右移,高位补0,低位丢弃(无-)
强制类型转换注意高字节转低字节量时的数据丢失 取后八位

6. 英译汉

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

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

const修饰符有以下的优点:

1、预编译指令只是对值进行简单的替换,不能进行类型检查

2、可以保护被修饰的东西,防止意外修改,增强程序的健壮性

const的用法:
1、修饰局部变量
2、常量指针与指针常量
3、修饰函数的参数
4、修饰函数的返回值
5、修饰全局变量

具体见:
https://blog.csdn.net/xingjiarong/article/details/47282255?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522166888169016782388060972%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=166888169016782388060972&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2alltop_positive~default-1-47282255-null-null.142v65opensearch_v2,201v3add_ask,213v2t3_esquery_v1&utm_term=const&spm=1018.2226.3001.4187

7. 汉译英

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

  1. 含有10个指向int的指针的数组。
    int *p[10];
  1. 指向含有10个int数组的指针。
    int (*p)[10]
  1. 含有3个「指向函数的指针」的数组,被指向的函数有1个int参数并返回int

该指针指向一个函数:(*a[10])();

该函数有一个整形参数: (*a[10])(int);

并返回一个整型数: int (*a[10])(int)

int *p[3]={};a是一个数组,该数组的元素是指针

阅读这种表达式的时候可以遵循以下的规则:从右向左,由近及远,括号优先

注意: 指向数组的指针和指针数组

int *a[10]: a是一个数组,该数组的元素是指针,每个指针都指向一个int型

int (*a)[10]:a是一个指针,该指针指向一个数组,数组元素是int

[]运算符比∗运算符优先级高,更多自查运算符优先级表。

8. 混乱中建立秩序

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

[稳定性指的是在排序之前元素A和元素B的相对顺序,在排序后是否改变其相对位置,不改变则称之为稳定排序。]

[时间复杂度:时间复杂度是一个函数(数学里面带有未知数的表达式),而不是去计算算法跑了多少秒,时间与机器有关,没办法计算具体时间,因此算的是基本操作的执行次数,是大概的。使用大O的渐进表示法。]

[空间复杂度:空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度,也就是额外占取的空间的大小。]

提示:动动你的小手敲出来更好哦~
冒泡排序

时间复杂度:O(n2)
空间复杂度:O(1)
稳定性:稳定

#include <stdio.h>
int main ( )
{
    //冒泡排序
    int a[]={5,6,10,2,5};
    int len=sizeof(a)/sizeof(int);
    int i=0,j;
    for (; i<len-1; i++) {	
        for (j=0; j<len-1-i; j++) {	
            if (a[j]>a[j+1]) {
                int temp = a[j];
                a[j] = a[j+1];
                a[j+1] = temp;
            }
        }
    }
    
    for (i=0; i<len; i++) {
        printf("%d  ",a[i]);
    }
    printf("\n");
    return 0;
}

更多排序看:
https://blog.csdn.net/qq_42782782/article/details/108453483?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522166890694616782425155802%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=166890694616782425155802&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduend~default-2-108453483-null-null.142v65opensearch_v2,201v3add_ask,213v2t3_esquery_v1&utm_term=c%E8%AF%AD%E8%A8%80%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95%E4%BB%A3%E7%A0%81&spm=1018.2226.3001.4187

9. 手脑并用

请实现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);
}
char* convertAndMerge(char str1[],char str2[]){
    strcat(str1,str2);
    for(int i=0;str1[i]!='\0';i++){
        if(str1[i]>='A'&&str1[i]<='Z'){
            str1[i]+=32;
        }else if(str1[i]>='a'&&str1[i]<='z'){
            str1[i]-=32;
        }
    }
    return s1;
}

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

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

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

结果:

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

11. 奇怪的参数

你了解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;
        }
    }
}

题解:

argc用来统计你运行程序时送给main函数的命令行参数的个数。

  • argv[ ]: 字符串数组,用来存放指向你的字符串参数的指针数组,每一个元素指向一个参数.

argv[0] 指向程序运行的全路径名

argv[1] 指向在DOS命令行中执行程序名后的第一个字符串

argv[2] 指向执行程序名后的第二个字符.

while循环执行过程中argc会出现数据溢出,数值从正值变为负值从而结束循环。

12. 奇怪的字符

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

int main(int argc, char **argv)
{
    int data1[2][3] = {{0x636c6557, 0x20656d6f, 0x58206f74},
                       //  u o y i     n i L            \0
                       {0x756f7969, 0x6e694c20, 0x00000000}};
    int data2[] = {0x47207875, 0x70756f72, 0x32303220, 0x00000a32};
    char *a = (char *)data1;
    char *b = (char *)data2;
    char buf[1024];
    strcpy(buf, a);//将a拷贝到buf
    strcat(buf, b);//将b连接到buf
    printf("%s \n", buf);
    if(*buf='W') printf("LE");
    else printf("BE");
}

题解:

0X 之后的是16进制数,而机器是从每一个的0X八位数从右往左,每俩个数字为一个单元进行读取,读取后转成10进制数与ascii码对应,进行输出。

[大小端序]详见:
https://blog.csdn.net/kevin_tech/article/details/113979523?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522166890803116800184187763%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=166890803116800184187763&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduend~default-1-113979523-null-null.142v65opensearch_v2,201v3add_ask,213v2t3_esquery_v1&utm_term=%E5%A4%A7%E5%B0%8F%E7%AB%AF%E5%BA%8F&spm=1018.2226.3001.4187

13. 小试宏刀

  • 请谈谈你对#define的理解。
  • 请尝试着解释程序的输出。
#include <stdio.h>
#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,tmp=%d\n", x, y, tmp);
  printf("z = %d, w = %d ,tmp = %d\n", z, w, tmp);
}

题解:

结果:
2 1 1
1 2 2
2 2
5 5 2

define 在预处理阶段进行文本替换
#define SWAP(a, b, t) t = a; a = b; b = t这条宏定义的内容包括SWAP()函数,和 a = b; b = t语句
即SWAP(a, b, t)函数体是 t = a;而不含 a = b; b = t这两条语句。

14. GNU/Linux命令 (选做)

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

注:

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

  • ls
    [列出当前目录的文件]
  • rm
    [删除文件]
  • whoami
    [查看用户名]

请问你还了解哪些GNU/Linux的命令呢。
pacman pwd cat vim reboot

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值