西邮Linux兴趣小组21-23年面试题题解

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

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

注:

  • 本题目仅作西邮Linux兴趣小组2021纳新面试题的有限参考。
  • 为节省版面本试题的程序源码中省略了#include指令。
  • 本试题中的程序源码仅用于考察C语言基础,不应当作为C语言代码风格的范例。
  • 题目难度与序号无关。
  • 所有题目均假设编译并运行x86_64 GNU/Linux环境。

Copyright © 2021 西邮Linux兴趣小组, All Rights Reserved.
本试题使用采用 [知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议]>(http://creativecommons.org/licenses/by-nc-sa/4.0/) 进行许可。

1. 大小和长度竟然不是一个意思

sizeof()strlen()有什么异同之处?

他们对于不同参数的结果有什么不同?请试举例子说明。

int main(void) {
    char s[] = "I love Linux\0\0\0";
    int a = sizeof(s);
    int b = strlen(s);
    printf("%d %d\n", a, b);
}

sizeof()关键字而非函数!!!

sizeof()会在编译时确定其值,故其中所有语句均不会被执行

初始化字符串时,会自动为末尾加上\0

sizeof(s) s为字符串"I love Linux\0\0\0",其中每个字母占一个字节,空格占一个字节,\0占一个字节,所以a的值应该为16

strlen(s)是一个函数,它的返回值为s的实际长度,遇到\0结束,所以输出为12


2. 箱子的大小和装入物品的顺序有关

test1test2都含有:1个short、1个int、1个double,那么sizeof(t1)sizeof(t2)是否相等呢?这是为什么呢?

struct test1 {
    int a;
    short b;
    double c;
};
struct test2 {
    short b;
    int a;
    double c;
};
int main(void) {
    struct test1 t1;
    struct test2 t2;
    printf("sizeof(t1): %d\n", sizeof(t1));
    printf("sizeof(t2): %d\n", sizeof(t2));
}

内存对齐!!!!

在结构体中,每个成员所占的内存会自动按照2 4 6 8 …字节对齐,详情见:内存对齐

test1中:

struct test1 {
  int a;//4字节
  short b;//2字节
  double c;//8字节
};//共14字节(X)应该为16字节

test2中:

struct test2 {
  short b;//2
  int a;//4
  double c;//8
};//16

所以sizeof(t1)sizeof(t2)相等,且该题输出为16 16
在这里插入图片描述


3. 哦,又是函数

想必在高数老师的教导下大家十分熟悉函数这个概念。那么你了解计算机程序设计中的函数吗?请编写一个func函数,用来输出二维数组arr中每个元素的值。

/*在这里补全func函数的定义*/
int main(void) {
    int arr[10][13];
    for (int i = 0; i < 10; i++) {
        for (int j = 0; j < 13; j++) {
            arr[i][j] = rand();
        }
    }
    func(arr);
}

根据题设来看,要求将arr中的所有数据输出,这里用两个循环嵌套的方式实现

void func(int (*arr)[13])
{
    for(int i=0;i<10;i++)//用i代表行
    {
        for(int j=0;j<13;j++)//用j代表列;
        {
            printf("%d\t",arr[i][j]);
        }//逐个输出本行元素
        printf("\n");//输出完当前行后换行
    }
}

输出:
在这里插入图片描述


4.就不能换个变量名吗?

  • 请结合下面的程序,简要谈谈传值传址的区别。
  • 简要谈谈你对C语言中变量的生命周期的认识。
int ver = 123;
void func1(int ver) {
    ver++;
    printf("ver = %d\n", ver);
}
void func2(int *pr) {
    *pr = 1234;
    printf("*pr = %d\n", *pr);
    pr = 5678;
    printf("ver = %d\n", ver);
}
int main() {
    int a = 0;
    int ver = 1025;
    for (int a = 3; a < 4; a++) {
        static int a = 5;
        printf("a = %d\n", a);
        a = ver;
        func1(ver);
        int ver = 7;
        printf("ver = %d\n", ver);
        func2(&ver);
    }
    printf("a = %d\tver = %d\n", a, ver);
}

传值:将实参的值赋给行参,不影响实参的值
传址:将实参的地址赋给行参,这时实参和行参指向同一个地址,会改变实参的值

生命周期:一个变量从声明开始,直到删除改变量,这之间就是它的生命周期。

作用域与生命周期:
自动变量(Automatic Variables):自动变量是在函数内部声明的变量,它们的生命周期与函数调用的生命周期相关联。这些变量在函数开始执行时分配内存,当函数执行完毕或离开其作用域时,这些变量的内存会自动释放。自动变量通常使用关键字 “auto” 声明,但在C语言中通常省略这个关键字,因为变量默认就是自动变量。

静态变量(Static Variables):静态变量在程序的整个生命周期内存在,它们在第一次声明时分配内存,并在程序的执行期间一直存在,直到程序结束。静态变量通常使用关键字 “static” 声明。静态变量可以在函数内部和函数外部声明。

寿命延长的静态变量(Static Variables with Extended Lifetime):有些静态变量在函数内部声明,但它们的生命周期超出了函数的执行,这是因为它们是函数的局部静态变量。这些变量在函数第一次调用时分配内存,在函数的多次调用之间保留其值,直到程序结束。

动态分配的内存(Dynamically Allocated Memory):使用动态内存分配函数如malloc()或calloc()分配的内存具有在程序运行时动态分配和释放的生命周期。这些变量的生命周期由程序员显式控制,当不再需要时,需要使用free()函数来释放内存,以避免内存泄漏。


5. 套娃真好玩!

请说明下面的程序是如何完成求和的?

unsigned sum(unsigned n) { return n ? sum(n - 1) + n : 0; }
int main(void) { printf("%u\n", sum(100)); }

该题运用了递归的知识:

在这里插入图片描述
根据代码,可以看出当n不为0时,会再次调用sum函数直到n减为0;所以当n为100时,该函数共调用101次,最终返回为0-100的累加。


6. 算不对的算术

void func(void) {
    short a = -2;//a=1111 1111 1111 1110即0xfffe
    unsigned int b = 1;//b=0000 0000 0000 0000 0000 0000 0000 0001
    b += a;//b=-1=>b=1111 1111 1111 1111 1111 1111 1111 1111(0xffffffff)(补码形式)
    int c = -1;//c=1111 1111 1111 1111 1111 1111 1111 1111(补码)
    unsigned short d = c * 256;//d=-256=>
    c <<= 4;//c左移4位得到c=1111 1111 1111 1111 1111 1111 1111 0000
    int e = 2;//
    e = ~e | 6;//e(0000 0000 0000 0000 0000 0000 0000 0010)取反后1111 1111 1111 1111 1111 1111 1111 1101按位或得:1111 1111 1111 1111 1111 1111 1111 1111 即0xffffffff
    d = (d & 0xff) + 0x2022;//d(1111 1111 0000 0000)
    printf("a=0x%hx\tb=0x%x\td=0x%hx\te=0x%x\n", a, b, d, e);//以16进制输出a、b、d、e
    printf("c=Ox%hhx\t\n", (signed char)c);//将c强制转化为sined char类型,再以16进制输出
}
- short         //2字节
- unsigned int  //4字节
- int           //4字节
- unsigned short//2字节

输出格式:%[输入数据宽度][长度]类型
其中`h`表示按短整形量输出,`l`表示按短整形量输出

d=-256原码:1000 0001 0000 0000 反码:1111 1110 1111 1111 补码为反码加1: 1111 1111 0000 0000


  • & 按位与,存0得0
  • | 按位或,存1得1
  • ^ 按位异或,同0异1
  • ~ 取反,0、1 互换(反码)
  • << 各二进制全部左移若干位,高位丢弃,低位补0
  • >> 各二进制全部右移若干位,对无符号数,高位补0;有符号数,各编译器处理方法不一样,有的补符号位(算术右移),有的补0(逻辑右移)

7. 指针和数组的恩怨情仇

int main(void) {
    int a[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
    int(*b)[3] = a;//b指向a第一行的元素
    ++b;//指向第二行元素
    b[1][1] = 10;//b[1][1]为它指向的行的下一行第二个元素,所以8被改为10
    int *ptr = (int *)(&a + 1);//&a+1使指其跳到a[3][0]的位置,所以ptr指向一个空的地址
    printf("%d %d %d \n", a[2][1], **(a + 1), *(ptr - 1));//a[2][1]被更改为10;(a+1)指向a第二行,*(a+1)指向其第二行的首元素的地址;ptr-1指向a最后一个元素的地址;
    }

在这里插入图片描述


8. 移形换位之术

下面有abc三个变量和4个相似的函数。

  • 你能说出使用这三个变量的值或地址作为参数分别调用这5个函数,在语法上是否正确吗?
  • 请找出下面的代码中的错误。
  • const intint const是否有区别?如果有区别,请谈谈他们的区别。
  • const int *int const *是否有区别?如果有区别,请谈谈他们的区别。
int a = 1;
int const b = 2;
const int c = 3;
void funco(int n) {
    n += 1;
    n = a;
}
void func1(int *n) {
    *n += 1;
    n = &a;
}
void func2(const int *n) {
    *n += 1;
    n = &a;
}
void func3(int *const n) {
    *n += 1;
    n = &a;
}
void func4(const int *const n) {
    *n += 1;
    n = &a;
}

func2 func3 func3错误

const intint const一样,表示改变量的值不能直接通过变量名来改

const int *int const *一样,表示该指针指向的值不能被修改


9. 听说翻转字母大小写不影响英文的阅读?

请编写convert函数用来将作为参数的字符串中的大写字母转换为小写字母,将小写字母转换为大写字母。返回转换完成得到的新字符串。

char *convert(const char *s);
int main(void) {
    char *str = "XiyouLinux Group 2022";
    char *temp = convert(str);
    puts(temp);
}
char *convert(const char *s){
    char *p=(char*)calloc(50,sizeof(char));
   for(int i=0;i<strlen(s);i++){
    if(s[i]<='z'&&s[i]>='a') p[i]=s[i]-32;
    else if(s[i]<='Z'&&s[i]>='A') p[i]=s[i]+32;
    else p[i]=s[i];
   }
   p[strlen(s)]='\0';
   return p;
}

这里开辟了一个新字符串用来存替换后的字符


10. 交换礼物的方式

  • 请判断下面的三种Swap的正误,分别分析他们的优缺点。
  • 你知道这里的do {...} while(0)的作用吗?
  • 你还有其他的方式实现Swap功能吗?
#define Swap1(a, b, t)   \
    do {                 \
        t = a;           \
        a = b;           \
        b = t;           \
    } while (0)
#define Swap2(a, b)      \
    do {                 \
        int t = a;       \
        a = b;           \
        b = t;           \
    } while (0)
void Swap3(int a, int b) {
    int t = a;
    a = b;
    b = t;
}
  • Swap1正确,但在应用时要声明一个中间变量t
  • Swap2正确,声明了一个局部变量t,在交换完ab值后删除
  • Swap3错误,这个函数只传入了a,b的值,并没有将地址传入,所以只在函数内交换了a,b的值

do {...} while(0)只让ab交换一次值,且在Swap2中使t成为了局部变量

其他方式:

void Swap4(int *p,int *q)
{
    
    *p=*p+*q;
    *q=*p-*q;
    *p=*p-*q;
}

此外,还有运用位运算、直接交换内存等方式


11. 据说有个东西叫参数

你知道argcargv的含义吗?请解释下面的程序。你能在不使用argc的前提下,完成对argv的遍历吗?

int main(int argc, char *argv[]) {
    printf("argc = %d\n", argc);
    for (int i = 0; i < argc; i++)
        printf("%s\n", argv[i]);
}

argc用来统计命令行传给main函数参数的个数
argv将传给main函数的参数以字符串的格式存起来,其中

  • argv[0]指向程序的全路径名
  • argv[argc]指向NULL
  • 补充->argc(int型,表示命令行参数的个数)、argv[](char数组型,指向命令行的每一个命令参数)、envp[](char数组型,指向环境变量,“名称=值”的形式,以NULL结束)。
  • main函数中可以存在无数个参数,但常用的只有argc argv[] envp[]
int main(int argc, char *argv[]) {
    printf("argc = %d\n", argc);
    for (int i = 0;argv[i]!=NULL; i++)
        printf("%s\n", argv[i]);
}


12. 人去楼空

这段代码有是否存在错误?谈一谈静态变量与其他变量的异同。

int *func1(void) {
    static int n = 0;
    n = 1;
    return &n;//因为n是静态变量,所以n的地址一直存在
}
int *func2(void) {
    int *p = (int *)malloc(sizeof(int));
    *p = 3;
    return p;//返回p的首元素地址
}//*func3错误
int *func3(void) {
    int n = 4;
    return &n;//n在函数结束时被删除了,相当于返回一个野指针
}
int main(void) {
    *func1() = 4;
    *func2() = 5;
    *func3() = 6;
}

13. 奇怪的输出

int main(void) {
    int data[] = {0x636c6557, 0x20656d6f, 0x78206f74,
                  0x756f7969, 0x6e694c20, 0x67207875,
                  0x70756f72, 0x32303220, 0x00000a31};
    puts((const char*)data);//将data强制转换成字符串形式并输出
}

在这里插入图片描述

大小端

  • 小端序低序字节存储在起始地址即高字节存放在内存的高地址
  • 大端序高序字节存储在起始地址即高字节存放在内存的低地址
    如何确定大小端->大小端

14. 请谈谈对从「C语言文件到可执行文件」的过程的理解

预处理->编译->汇编->链接

1.预处理

  • 解析所有的条件预处理指令
  • 解析宏,并替换代码中的宏
  • 删除注释
  • 解析#include并将引出的头文件拷贝到#include位置
  • 添加行号和文件标识
  • 保留#program,留给编译过程
    以.c文件生成.i文件

2.编译

以.i文件生成.s文件(以汇编语言完成)

  • 词法分析
  • 语法分析
  • 语义分析
  • 源代码优化
  • 目标代码生成
  • 目标代码优化

3.汇编

将汇编语句转化为机器码以.s文件生成.o的二进制文件

4.链接

连接器将所有二进制的目标文件和系统组件组合成一个可执行文件(.exe)



西邮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明显是一个非常大的数,这里可以转换一下思维,首先以科学计数法可知道数字可以用xEa,表示,其中a刚好是我们需要的结果,所以可以利用对数运算,求出a

#include <stdio.h>
#include <math.h>
int main()
{
    printf("%.0f",10000*log10(2));
}

在这里插入图片描述
所以答案为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("")));
}

+运算优先级高于>所以先进行加法运算,左式结果为0,右式为1,所以if()条件为假,执行else后面的语句。

这里主要考的是printf()的返回值,即printf()输出的字符的长度
所以最里面的prinf()返回0,第二个printf()的返回值为22,第三个printf的返回值为22
依次输出后结果为Xiyou Linux Group - 2022


2. 你好你好你好呀!

  • 程序的输出有点奇怪,请尝试解释一下程序的输出吧。
  • 请谈谈对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));
}

输出结果为
在这里插入图片描述

sizeof()计算的是变量所占的内存大小,strlen()计算的是字符串本身长度(遇到'\0'结束)

第一行:p0 == p1比较的是他们的地址,所以返回值为0;strcmp(p0,p2)是一个比较字符串的函数,前者大返回一个正数,两者一样返回0,后者大返回一个负数,p2只能容纳11个字符,所p0小于p2,返回负数;

第二行:p0实际占用12个字节; p1是一个指针,指针所占内存大小为8; &p2表示p2的首元素,所以大小为1

第三行:p0实际长度为11,p1实际长度也为11,所以输出为11 11


3. 换个变量名不行吗?

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

int a = 3;//a为全局变量
void test() {
    int a = 1;//a为局部变量,只在test()函数中生效
    a += 1;
    {
        int a = a + 1;//a为局部变量,只在该花括号内生效;在生命的同时由用了a,所以会给a赋1个随机值
        printf("a = %d\n", a);
    }
    printf("a = %d\n", a);
}
int main(void) {
    test();
    printf("a= %d\n", a);
}

「全局变量」「局部变量」的「生命周期」见21年面试题


4. 内存对不齐

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

见21年第2题
补充:共用体:共用同一块内存,计算的时候以其中最长的元素为准。
相关知识点: >https://blog.csdn.net/m0_57180439/article/details/120417270?ops_request_misc=&request_id=&biz_id=102&utm_term=%E8%81%94%E5%90%88%E4%BD%93%E7%9B%B8%E5%85%B3%E7%9F%A5%E8%AF%86%E7%82%B9&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduweb~default-1-120417270.142v65control,201v3control_1,213v2t3_esquery_v1&spm=1018.2226.3001.4187


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

位运算,见21年第6题
输出为:

a: 56, b: 0, c: 254, d: 48
e: 0x4

6. 英译汉

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

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

1 中p指针是只读的,即不可修改,只能指向固定的内存
2和3中指针指向的数据是只读的,即所指向的数字不可被修改

const只与*的位置有关


7. 汉译英

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

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

8. 混乱中建立秩序

你对排序算法了解多少呢?
请谈谈你所了解的排序算法的思想、稳定性、时间复杂度、空间复杂度。
提示:动动你的小手敲出来更好哦~

冒泡排序、插入排序、选择排序、快速排序、归并排序等

//冒泡排序
void bub_sort(int* p,int n)//p为需要排序的数组,n为需要排序的数组的长度
{
    for(int i=0;i<n;i++)
    {
        for(int j=0;j<n;j++)
        {
            if(p[j]>p[j+1])//从小到大排
            {
                int t=p[j];
                p[j]=p[j+1];
                p[j+1]=t;
            }
        }
    }
}

快速排序:「分治」确定一个中间值x,分别从两头开始扫,左边遇到比x大的元素停止,右边遇到小于x的元素停止,将左右两个元素调换,然后继续进行扫表,直至两边相遇。
重复上述操作即可得到从小到大排列的数列。

十大经典排序算法->https://zhuanlan.zhihu.com/p/42586566

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 words[][20]) {
    // 计算链接后字符串的长度
    int len = 0;
    for (int i = 0;i<2; i++) {
        len += strlen(words[i]);
    }
    // 申请一个新的字符串来接收拼接后的字符串
    char *str = (char*)malloc(len + 1);
    if (str == NULL) {
        return NULL;
    }
    
    int pos = 0;
    for (int i = 0; i <2; i++) {
        strcpy(str + pos, words[i]);
        pos += strlen(words[i]);
    }
    //大小写转换
   for(int i=0;i<strlen(str);i++){
    if(str[i]<='z'&&str[i]>='a') str[i]-=32;
    else if(str[i]<='Z'&&str[i]>='A') str[i]+=32;
   }
   str[strlen(str)]='\0';

    return str;
}

在这里插入图片描述


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]);
        }
        printf("\n")
    }
}

每次循环后a中元素的值,其中,temp会在每次循环开始时被声明,在该次循环结束后被释放

    //1 第一次循环,temp指向arr[0][0]遍历数组arr,变量a自增并挨个赋给数组元素
    0  1  2  3  4 
    5  6  7  8  9
    10 11 12 13 14 
    15 16 17 18 19
    20 21 22 23 24
    //2 第二次循环,temp指向arr[1][0]继续遍历,a继续自增并赋给后四行元素
    0  1  2  3  4
    25 26 27 28 29
    30 31 32 33 34
    35 36 37 38 39
    40 41 42 43 44
    //3 第三次循环,temp指向arr[2][0]
    0  1  2  3  4 
    25 26 27 28 29
    45 46 47 48 49
    50 51 52 53 54
    55 56 57 58 59 
    //4 第四次循环,temp指向arr[3][0]
    0  1  2  3  4 
    25 26 27 28 29
    45 46 47 48 49
    60 61 62 63 64
    65 66 67 68 69
    //5 第五次循环,temp指向arr[4][0]
    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;
        }
    }
}

见21年第11题
不会陷入死循环,char类型只能存0-255,当argc自增到255时,再增加就会返回0;进入if,打印argv[0],跳出循环。


12. 奇怪的字符

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

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

字符串拼接,强制类型转换,见2021年第13题


13. 小试宏刀

  • 请谈谈你对#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);//调换x y的值
    printf("x = %d, y = %d, tmp = %d\n", x, y, tmp);
    if (x>y) SWAP(x, y, tmp);//x>y成立,再次调换x y
    printf("x = %d, y = %d, tmp = %d\n", x, y, tmp);
    SWAPWHEN(x, y, tmp, SQUARE(1 + 2 + z++ + ++w) == 100);//SQUARE()=20,返回0;不调换
    printf("x = %d, y = %d\n", x, y, tmp);
    printf("z = %d, w = %d, tmp = %d\n", z, w, tmp);//z w都分别自增了两次
}

#define指令是预处理阶段执行的,它不执行实际的运行时计算,而只是在编译前进行文本替换。


14. GNU/Linux命令 (选做)

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

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

  • ls//list 目录(某个路径下的所有文件(夹))
  • rm//remove 移除
  • whoami//who am I 显示与当前的有效用户 ID 相关联的用户名 与id -un用法一样

请问你还了解哪些GNU/Linux的命令呢。
https://juejin.cn/post/6844903930166509581

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



西邮Linux兴趣小组2023年面试题解析

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

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

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

10只小鼠

将每瓶水序号转化为二进制,最高位为1的水混在一起标为1号,第二位为1的水混在一起标为2号,以此类推,总共得到十瓶混合后的水,取十只小鼠,依次标号1-10,喂给对应标号的水,一段时间后,将死亡的小鼠标为1,存活的小鼠标为0,得到的数据化为10进制即为有毒的水的编号。


1.先预测一下~

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

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

这里可以直接返回姓名,也可以运用静态变量

//1
static char* p="XXX";
return p;
//2
return "XXX";
//3
char* p=(char*)malloc(20*sizeof(char));
p="XXX";
return p;
//4
static char p[]="XXX";
return p;

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

同22年第1题

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

同22年第2题
输出:

7 8 2
0 
4 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);
}

见21年第6题
位运算->https://www.runoob.com/w3cnote/bit-operation.html


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(b, c),传入b = 9和c = -7。计算出这次调用的结果。

func(9, -7)内部,计算a & b = 9 & (-7)结果为1,以及a ^ b = 9 ^ (-7),结果为-14。
然后,它递归调用func(1 << 1, -14)

func(2, -14)内部,计算a & b = 2 & (-14),结果为2,以及a ^ b = 2 ^ (-14),结果为-12。
然后,它递归调用func(2 << 1, -12)

func(4, -12)内部,计算a & b = 4 & (-12),结果为4,以及a ^ b = 4 ^ (-12),结果为-8。
然后,它递归调用func(4 << 1, -8)

func(8, -8)内部,计算a & b = 8 & (-8),结果为0。由于此时a等于0,所以返回b,即-8。

位运算回到21年第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;
}
int *filter(int *array, int length, Predicate predicate,
            int *resultLength) {
    // 为结果数组分配内存
    int *result = malloc(length * sizeof(int));
    // 检查分配是否成功
    if (result == NULL) {
        // 返回NULL并将resultLength设为0
        *resultLength = 0;
        return NULL;
    }
    // 初始化结果索引
    int index = 0;
    // 遍历输入数组
    for (int i = 0; i < length; i++) {
        // 获取当前元素
        int element = array[i];
        // 检查它是否满足谓词
        if (predicate(element)) {
            // 将它添加到结果数组
            result[index] = element;
            // 增加结果索引
            index++;
        }
    }
    // 设置结果长度
    *resultLength = index;
    // 返回结果数组
    return result;
}
1 2 3 4 5 6//过滤正数

7.静…态…

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

static 静态的

static 修饰局部变量:可以理解为全局变量,延长变量的生命周期

static 修饰全局变量:使该变量只能在本文件中引用,其他文件无法访问,但可以声明同名变量

static 修饰函数:使该函数只能在本文件中引用,其他文件无法访问,但可以声明同名函数

指针实质上也是变量,所以与变量类似


8.救命!指针!

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

int (*p)[10];//指向十个整数数组的指针
const int* p[10];//10个指向常量整数的指针数组
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;
}

同22年11题、21年11题


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)//左式相当于16/2*2*2=32
  {
    printf("I'm TWO(ノ>ω<)ノ\n");
  } else {
    int nums = MAGIC_CAL(++nums, 2);
  }
  printf("%d\n", nums);
}

同22年第13题

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

浅拷贝:
C语言中的浅拷贝是指在拷贝过程中,对于指针型成员变量只拷贝指针本身,而不拷贝指针所指向的目标,它按字节复制的。我们分几种情况举例子来看一下。
深拷贝:
深拷贝除了拷贝其成员本身的值之外,还拷贝成员指向的动态内存区域内容,深拷贝会在堆内存中另外申请空间来储存数据。

当数据成员中有指针时,两个类中的两个指针将指向同一个地址,当对象快结束时,会调用两次free函数。
解决的思路是在释放掉被赋值指针变量的旧指向内存时,重新对其开辟新内存,这种情况下两个结构体中指针地址不同,但是指向的内容是一致的。

12.你好,我是内存

作为一名合格的 C-Coder,一定对内存很敏感吧~来尝试理解这个程序吧!

struct structure {
    int foo;//0-7
    union {
      int integer;
      char string[11];
      void *pointer;
    } node;//8-23
    short bar;//24-31
    long long baz;//32-39
    int array[7];//40-71
};//72
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);
}

按8字节对齐
同22年第4题、第12题

13.GNU/Linux (选做)

注:嘿!你或许对 Linux 命令不是很熟悉,甚至你没听说过 Linux。但别担心,这是选做题,了解

Linux 是加分项,但不了解也不扣分哦!

你知道 cd 命令的用法与 / . ~ 这些符号的含义吗?

请问你还懂得哪些与 GNU/Linux 相关的知识呢~

同22年14题

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值