西邮Linux兴趣小组2022纳新题

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

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

0. 我的计算器坏了?!

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

我们先看二进制的位数是如何计算的,例如1024为2的10次方,但1024对于二进制有11位.11为log2[1024]+1得到.
所以对应十进制的位数即为以10为底求导,然后+1,例如1000位数为4,lg[1000]=3.
所以2的10000次方的十进制位数为lg[2^10000]+1.

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

在这里插入图片描述

  • 首先进行3+2<2的运算,5>2所以该式的结果为假,即为0,然后后面3+2>5为真,默认为1.
  • 所以if判断即为if(0>1),为假,所以程序执行else内的语句.
  • 多层printf函数嵌套,先执行最内部的printf,返回值为0,作为第二个函数%d的值,然后打印Xiyou Linux Group - 20,最后打印第二个printf的返回值22,得到最终结果.
  • printf返回值详解

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

在这里插入图片描述

  • 第一行输出p0==p1: 0,即为假,说明p0!=p1,因为p0是栈区一字符数组的首元素地址,p1为一个指向常量字符串的指针,所以结果为0,strcmp是字符串比较函数,由于p2数组长度的限制,并未能存储最后一位的’\0’,所以最后比较的时候为p0’\0’与p2后的一个值进行比较,这里也存在一个未定义行为,这里返回-1,说明p2后的值大于’\0’.
  • p0为指定数组大小,所以最后会存储一个’\0’,为12,p1是一个指针,指针的大小是固定的,在32/64上分别为4/8,本试题所有题目均在X86_64位下编译运行,所以为8,*p2对字符串首元素解引用,得到一个字符,所以为1.
  • strlen遇到’\0’停止计数,p0,p1字符串结尾都有’\0’,均为11.
  • size of与strlen

3. 换个变量名不行吗?

请结合本题,分别谈谈你对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);
}
  • 变量的生命周期指的是变量的创建到变量的销毁之间的一个时间段

      1. 局部变量的生命周期是:进入作用域生命周期开始,出作用域生命周期结束。
      2. 全局变量的生命周期是:整个程序的生命周期
    
  • 作用域为简单理解为创建该变量的大括号内.
int a = 3;
void test() {
    int a = 1;//局部变量a,
    a += 1;//a=2.
    {
        int a = a + 1;//a=3,创建新的局部变量,仅限该大括号.
        printf("a = %d\n", a);//所以打印a=3.
    }
    printf("a = %d\n", a);//该处打印a=2.
}
int main(void) {
    test();
    printf("a= %d\n", a);//这打印开始定义的全局变量a=3.
}

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));
}
  • struct 结构体内存对齐
  1. 第一个成员在与结构体变量偏移量为0的地址处。存放
    2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址
    处。对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小
    值。 VS中默认的值为8,Linux中没有默认对齐数,对齐数就是成员自身的大小
    3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
    4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整
    体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

union为联合体,可以理解为这些类型共用一块内存,那么联合体的大小为最大成员的大小,但也需要对齐到对齐数的整数倍处.

sizeof (UNION) = 24
sizeof (STRUCT) = 40
typedef union {
    long l;
    int i[5];//联合体内最大成员.
    char c;
} UNION;
typedef struct {
    int like;//起始存放占4个字节
    UNION coin;//对齐数为8,对齐到地址为8的位置,占20个字节,此时公用28个字节
    double collect;//对齐数为8,对齐到地址为32的位置,占8个字节,共用40个字节.
} STRUCT;
int main(void) {
    printf("sizeof (UNION) = %zu\n", sizeof(UNION));//24
    printf("sizeof (STRUCT) = %zu\n", sizeof(STRUCT));//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);
}

在这里插入图片描述

int main(void) {
    unsigned char a = 4 | 7;          //00000000 00000000 00000000    00000100
	                                  //00000000 00000000 00000000    00000111
									  //00000000 00000000 00000000    00000111
    a <<= 3;                          // 00111000           56   char为1字节
    unsigned char b = 5 & 7;          //00000000 00000000 00000000    00000101
	                                  //00000000 00000000 00000000    00000111
									  //00000000 00000000 00000000    00000101
    b >>= 3;                          //00000000           0 
    unsigned char c = 6 ^ 7;          //00000000 00000000 00000000    00000110
	                                  //00000000 00000000 00000000    00000111
									  //00000000 00000000 00000000    00000001
    c = ~c;                           // 11111111    11111110           254  short为两字节
    unsigned short d = (a ^ c) << 3;  // 00000000 00000000    00000000 00111000
	                                  //11111111 11111111    11111111 11111110
									  //11111111 11111111    11111111 11000110
									  //11111111 11111111 11111110    00110000           48
    signed char e = -63;              //11111111 11111111 11111111 10111111
                                      //11111111 11111111 11111110 11111100
    e <<= 2;
    printf("a: %d, b: %d, c: %d, d: %d\n", a, b, c, (char)d);
    printf("e: %#x\n", e);
}
  • 计算时char及short均会提升至4字节计算,最后截断为对应的类型,主要是为了避免精度缺失.

6. 英译汉

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

  1. char *const p
  2. char const *p
  3. const char *p
  •   const修饰p,p指向的地址是固定的,不可改变.
    
  •   const修饰*p, *p指向的字符不可改变,不可对p解引用赋值.
    
  •   同2,const修饰*p.
    

7. 汉译英

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

  1. 含有10个指向int的指针的数组。
  2. 指向含有10个int数组的指针。
  3. 含有3个「指向函数的指针」的数组,被指向的函数有1个int参数并返回int
  •   int *p[10],[]的优先级大于*,所以这是一个数组,数组每个元素都是int *。
    
  •   int (*p)[10],给*p带上括号,所以是一个指针,指向一个含有10int的数组.
    
  •   int (*p[3])(int),*p[3]是一个指针指向数组,数组每一个元素都是函数指针.
    

8. 混乱中建立秩序

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

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

  • 冒泡排序.
void bubble(int num[], int sz)
{
	for (int i = 0; i < sz - 1; i++)
	{
		for (int j = 0; j < sz - 1 - i; j++)
		{
			if (num[j] > num[j + 1])
			{
				num[j] = num[j] ^ num[j + 1];
				num[j+1] = num[j] ^ num[j + 1];
				num[j] = num[j] ^ num[j + 1];
			}
		}
	}
}

int main()
{
	int num[10] = { 9,5,4,3,1,2,7,8,10,6 };
	int sz = sizeof(num) / sizeof(num[0]);
	bubble(num, sz);
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", num[i]);
	}
	return 0;
}
  • 插入排序
void sort(int num[], int sz)
{
	int min = 0;
	for (int i = 0; i < sz - 1; i++)
	{
		min = i;
		for (int j = i+1; j < sz; j++)
		{
			if (num[min] > num[j])
			{
				min = j;
			}
		}
		if (min != i)
		{
			num[i] = num[i] ^ num[min];
			num[min] = num[i] ^ num[min];
			num[i] = num[i] ^ num[min];
		}
	}
}

int main()
{
	int num[10] = { 9,5,4,3,1,2,7,8,10,6 };
	int sz = sizeof(num) / sizeof(num[0]);
	sort(num, sz);
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", num[i]);
	}
	return 0;
}
void insertsort(int num[], int sz)
{
	int i = 0;
	int j = 0;
	int flag = 0;
	for (int i = 1; i < sz; i++)
	{
		flag = num[i];
		for (j = i - 1; j >= 0 && num[j] > flag; j--)
		{
			num[j + 1] = num[j];
		}
		num[j + 1] = flag;
	}
}
int main()
{
	int num[10] = { 9,5,4,3,1,2,7,8,10,6 };
	int sz = sizeof(num) / sizeof(num[0]);
	insertsort(num, sz);
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", num[i]);
	}
	return 0;
}

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

c语言字符串函数

char* convertAndMerge(char words[][20])
{
    char* str = (char*)malloc(sizeof(char) * 40);
    strcpy(str, words[0]);//strcpy字符串拷贝函数
    strcat(str, words[1]);//strcat字符串追加函数
    int len = (int)strlen(str);
    for (int i = 0; i < len; i++)
    {
        if (islower(str[i]))//判断小写
        {
            str[i] = toupper(str[i]);//转为大写
        }
        else if (isupper(str[i]))//判断大写
        {
            str[i] = tolower(str[i]);//转为小写.
        }
    }
    return str;//返回str.
}

这里不用考虑函数传数组进去没有改变实参的问题,因为在函数内部用malloc为数组开辟了空间,且返回的是新开辟数组的地址,相当于是将原数组转变大小放入新的数组,最后打印新创建的数组.

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

在这里插入图片描述

  • 定义了个5行5列的二维数组,数组最后一行的数组下标为a[4],第一个for循环为初始化数组,定义了个整形指针,指向数组的a[i]行,解引用得到a[i]行的首元素地址.然后通过a的自增对数组进行初始化.
  • 嵌套的for循环,判断条件为temp<a[5],二维数组在内存中的存放也是连续的因此a[5]即为数组a[4][4]的下一位,即每次都会初始化到a[4][4]的位置,后续赋值会覆盖之前的赋值,因此每一行之间的数字相差一定的值,并且越差越小.

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为传入主函数的一个指针.
  • while(1)会是该循环一直持续下去,但argc为一个整形,会不断自增到符号为为1,此刻就变为负数,然后打印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);
}

c语言为字符串提供了许多方便的库函数,例如本题的strcpy,stecat,strstr,strcmp……等等,感兴趣的可以自行查询学习

int main(int argc, char **argv) {
    int data1[2][3] = {{0x63  6c  65  57, 0x20  65  6d  6f, 0x58  20  6f  74},
                          c    l    e  w         e   m  o     X        o   t
                       {0x75  6f  79  69, 0x6e  69  4c  20, 0x00  00  00  00}};
                          u    o   y   i    n    i   l             \0
    int data2[] = {0x47  20  78  75, 0x70  75  6f  72, 0x32  30  32  20, 0x00  00  0a  32};
                     G        x  u     p    u   o   r     2   0   2                     2
    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);//打印Welcome to Xiyou Linux Group 2022
}

该题与2021年13题考点相同。
21年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);
    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);
}

#define 定义宏,后面的SWAP,SQUARE,SWAPWHEN,均为名称,括号内为参数,后面的式子为对应的参数运算程序.
但宏只是简单的替换.

1.|x = 2, y = 1, tmp = 1
2.|x = 1, y = 2, tmp = 2
3.|x = 2, y = 2
4.|z = 5, w = 5, tmp = 2
#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);该式子被替换为
    tmp=x=1;
    x=y=2;
    y=tmp=1;
    printf("x = %d, y = %d, tmp = %d\n", x, y, tmp);// 2  1  1
    if (x>y) //该处x=2,y=1,SWAP(x, y, tmp);
    //被替换为
    tmp=x=2;
    x=y=1;
    y=tmp=2;
    printf("x = %d, y = %d, tmp = %d\n", x, y, tmp);//1  2  2
    //SWAPWHEN(x, y, tmp, SQUARE(1 + 2 + z++ + ++w) == 100);
    //这里多了一个判断,但很明显括号内不能满足,所以不执行后面的内容.
    //这样想没有问题,但宏只是简单的替换,所以替换后的内容为
    if (1+2+z++ + ++w * 1+2+z++ + ++w)==100)//在这里存在一个ub(未定义行为)
    //这里显然不满足条件,但后面没有大括号,所以紧接着不执行下面第一句.
    该句不执行.//tmp=x=1;
    x=y=2;
    y=tmp=2
    printf("x = %d, y = %d\n", x, y, tmp);2  2
    printf("z = %d, w = %d, tmp = %d\n", z, w, tmp);5  5  2//上面运算中z,w自增了两次.
}

未定义行为

14. GNU/Linux命令 (选做)

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

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

  • ls
  • rm
  • whoami

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

ls查看工作目录所含文件及子文件.

rm用来删除一个文件.rm+文件名.
  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值