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

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

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

由于小鼠的生理状态现在可看为中毒和没中毒两种

,所以对于计算机我们可以采用二进制的0和1来表示其状态,1000瓶水我们用2^10就可以表示完.
所以我们让这10位二进制分别对应一只小鼠,对于这瓶水,*若其编号在二进制编号某一位数字为1,就让其对应编号的小鼠喝这水,*反之,则不喝
24小时后,若小鼠死亡,则有毒水的编号在这一位上为1,反之,此位编号为0;将二进制转化为10进制,则能找出有毒水。

1. 先预测一下~

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

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

这个题目主要是实现这个welcome函数,这个函数返回的是char*类型,表示的是返回字符串的指针

可以如下

char *welcome()

{
   return "name";

} 

或者如下

char *welcome()

{
char* name= " ***";

return name;

}

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

本题目主要考查了printf函数的返回值;

*ptr0解引出字符串与*ptr1解引的字符串一样,所以条件为真,进入if语句,先执行最内层的printf,返回值为0,在执行下一层printf,成功打印出 Hello, Linux Group - 20,返回值为24,则最终打印结果为Hello, Linux Group - 2024

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

这里主要考查了sizeof ()和strlen()的理解和运用

  • strlen()是一个函数,其计算的是字符串之中字符的个数,遇到\0会终止,且不算\0

  • sizeof()是一个运算符,计算的是变量所站内存的字节数,要算\0

  • sizeof() 括号中的语句不执行strlen() 括号中的语句会执行 且strlen()只能用于以\0结尾的字符串

- sprintfprintf不同的是printf是打印到stdout中,但是sprintf是打印到一个字符串里。

  • sprintf函数如果成功打印,则返回写入的字符总数,不包括字符串追加在字符串末尾的空字符。如果失败,则返回一个负数。

函数原型如下,参数为字符指针或存放格式化后的数组和格式化字符串,返回值为int类型

int sprintf(char *str, const char *format, ...);

*&arr 也就是arr数组,sizeof(arr)求出的就是整个数组的大小
arr+0表明它是在数组首元素地址上一个右移了 0 个单位的指针 sizeof(arr+0) 打印出来的就是指针的大小。
因为sizeof 不运行,所以计算出来的是num的大小
strlen(&str[0] + 1)计算的是从str[1]开始的长度,到’\0’为止。
strlen(arr+0)打印的是整个字符串str的长度

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

这道题目这个主要考查的是按位异或和按位与的知识

  • 按位异或 注意相同为0,不同为1

  • 按位与是同样为1才为1,否则为0

  • 还要注意这里的char类型的范围,char 的大小为一个字节,范围为-128~127

  • 所以ch的值最终溢出

a = 64 b = 63 c = -1 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函数里,运用了递归使得当a==0时,(注意这里有(!a) 表示 将a==0转为真) return 0;

最终实现的是a+b;

所以在main函数中调用func函数后,最终printf打印的就是a+(b+c)的值 为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>
#include<assert.h>
typedef int (*Predicate)(int);//给指向 参数为int 返回值为int 的函数指针定义一个别名predicate方便使用
int *filter(int *array, int length, Predicate predicate,int *resultLength)
{
    int count = 0;//计数
    int * filteredNumbers = (int*)malloc(sizeof(int)*length);//使用malloc动态开辟内存
    assert(filteredNumbers);//判断是否开辟成功 防止传入空指针
    for(int i = 0;i<length;i++)
    {
        if((*predicate)(array[i]))//通过函数指针调用筛选的函数 如果判断条件为真 将要筛选的数放入array
        {
            filteredNumbers[count] = array[i];
            count++;
        }
    }
    *resultLength = count;      //长度变为count
    return filteredNumbers;//返回筛选后的数组地址
}
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;
}

在这里filter 函数的作用是筛选数组中符合条件的元素,并返回一个新的数组。

predicate: 函数指针,指向用于判断元素是否符合条件的函数。

count 用来记录筛选出来的元素的个数。

assert(filteredNumbers) 用来检查 malloc 成功,如果内存分配失败,程序会中止。

(*predicate)(array[i])在这里通过函数指针调用筛选函数,判断array[i]是否符合,符合则count++

7. 静…态…

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

  • static 是用来改变变量,函数或者类成员的生命周期访问范围关键字
  • static 修饰的变量具有默认的初始值0
  • 全局变量函数被static修饰会使其不能被其他文件访问,可以防止命名冲突。 static修饰的指针的生命周期会延长至整个程序
  • static修饰的变量会存储在全局区

8.救命!指针!

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

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

数组指针是一个指向数组的指针,指针数组是一个元素为指针的数组

函数指针是指向函数地址的指针

int (*p)[10]是一个数组指针 指向一个int类型的 包含10个元素的数组。

const int* p[10]是一个常量数组指针,它是每个元素都是const int 类型的数组指针

it (*f1(int))(int*, int)这是一个函数指针的声明,声明了一个名为f1的函数,该函数接收1个int类型参数,返回值是一个“函数指针”;这个返回的函数指针,又指向一个接收“int*和int”两个参数、返回int类型的函数。

9. 咋不循环了

程序直接运行,输出的内容是什么意思?

i

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

这段代码主要考查命令行的知识点

命令行参数argc 的初始值默认为1,在while循环中,argc不断自增,直到int范围溢出,则为2147483647自增到-2147483648在自增到0,出循环,i = -1 ; j = argc = 0 ; k = 1;​​​​​​​

因为&&的优先级高于|| ,并且结合短路求值准则,所以先执行 i++ && j++ ,左边为真才执行j++,

i=-1(非0)为真,执行j=0(为假),j++,
并且||前面为假,执行k++

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) {
    printf("I'm TWO(ノ>ω<)ノ\n");
  } else {
    int nums = MAGIC_CAL(++nums, 2);
  }
  printf("%d\n", nums);
}

这段代码主要考查宏定义相关知识

注意:#define定义别名是在预处理时直接在文本中替换

则有if(16 / CAL(2) == 2)会被直接替换为 if(16 / 2 * 2 * 2 == 2)​​​​​​​

结果为假,打印出的nums应该为定义的全局变量nums,为1。

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.name和s2.name分配的都是动态内存
所以在s1=s2赋值的时候,s2.name的内存地址复制过去了 ,
所以此时s1.name 和 s2.name此时共占一块内存,原s1.name的内存发生泄漏,
且在最后s1.name和s2.name的内存释放了两次,可能导致系统崩溃

解决方法就是将其深拷贝

如下

void deepCopy(struct Student *dest, const struct Student *src) {
     // 第一步:先释放dest原有name内存(避免内存泄漏)
     if (dest->name != NULL) {
         free(dest->name);
     }
     // 第二步:为dest->name重新分配独立内存
     dest->name = (char *)malloc(strlen(src->name) + 1);
     if (dest->name == NULL) {
         printf("内存分配失败!\n");
         exit(1);
     }​​​​​​​

void destroy(struct Student *student) {
     if (student->name != NULL) {
         free(student->name);    // 释放name指向的动态内存
         student->name = NULL;   // 置空指针,避免野指针
     }
 }
 int main(void) {
     struct Student s1, s2;
     // 初始化s1和s2
     initializeStudent(&s1, "Tom", 18);
     initializeStudent(&s2, "Jerry", 28);
     printf("赋值前:\n");
     printf("s1的姓名: %s 年龄: %d\n", s1.name, s1.age);  // 输出 Tom 18
     printf("s2的姓名: %s 年龄: %d\n", s2.name, s2.age);  // 输出 Jerry 28
     // 关键:用深拷贝替换直接赋值(s1 = s2)
     deepCopy(&s1, &s2);
     printf("\n赋值后:\n");
     printf("s1的姓名: %s 年龄: %d\n", s1.name, s1.age);  // 输出 Jerry 28
     printf("s2的姓名: %s 年龄: %d\n", s2.name, s2.age);  // 输出 Jerry 28
     // 销毁:释放动态内存(此时s1.name和s2.name指向独立内存,无重复释放)
     destroy(&s1);
     destroy(&s2);
     return 0;

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

*这段代码主要考查的是结构体和共同体的内存布局和小段存储的知识,(struct structure )arr 将arr强转为结构体指针类型访问联合体中的string成员

arr强转为结构体指针后访问联合体node中的成员string,相当于跳过了数组中的两个int类型元素,指向第三个元素0x636c6557

并且计算机一般采用小端存储,所以printf打印时从57开始,并且要将十六进制转为十进制,打印出对应ASCII码对应的字符,直到\0停止

最终打印出的结果为 Welcome to XUPT , welcome to Xiyou Linux Group [2023]

13. GNU/Linux (选做)

注:嘿!你或许对Linux命令不是很熟悉,甚至你没听说过Linux。但别担心,这是选做题,了解Linux是加分项,但不了解也不扣分哦!
你知道cd命令的用法与 / . ~ 这些符号的含义吗?
请问你还懂得哪些与 GNU/Linux 相关的知识呢~

ls命令用于列出目前工作目录所含的文件及子目录
Linux系统中
/表示 根目录 ,它是文件系统的起点,所有文件和文件夹都是从根目录开始的。
.表示 当前目录 ;
~:代表当前用户的主目录

**ls命令用于列出目前工作目录所含的文件及子目录
Linux系统中/表示 根目录 ,它是文件系统的起点,所有文件和文件夹都是从根目录开始的。表示 当前目录 ;

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐