西邮Linux兴趣小组2023纳新题解

在这里插入图片描述

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

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

将1000转换为二进制,为1111101000,将1000瓶从一到一千分别标为0000000001,0000000002…一直到1111101000。将每一位是一的所有瓶子里的水取一些放在一个新的瓶子里,也就是放在十个新的瓶子里。然后取十只小白鼠,分别喂下十个瓶子里的水。24小时过去后验尸,死了的小白鼠贴上标签1,没死的贴上0,最后得到一个序号,把这个序号换成10进制的数字,就是有毒的那瓶水的编号。

1.先预测一下

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

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

如果我char name[]=“Tom”,因为是在welcome函数中声明的,所以name这个字符串数组是局部变量,只在该函数中存活,"Tom"这个字符串是储存在栈区的。这时有两种方法可以解决:

1.static char name[]=“Tom”

这种方式是利用了关键字static,static可以改变局部变量的存储位置,也就是将“Tom”从栈区转移到静态区。静态区是存放静态变量和全局变量的内存区,将局部变量从栈区转移到静态区,其实也就是延长了变量的生命周期,将其变为在整个程序内存活,这样才能将字符串返回到另外一个函数中。

2.char *name=“Tom”

这种方式是用指针定义字符串,这样定义的字符串是储存在常量区的,也可以在整个程序中存活,所以返回也能成功。

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函数嵌套,如printf(“%d\n”, printf(“Hello, Linux Group - 2%d”, printf(“”))),要从最里面开始运行,且执行完一个printf函数后,它的返回值为“ ”内写入的字符总数,如printf(“”)的返回值就是0,以此类推,printf(“Hello, Linux Group - 20”)的返回值就是23,这样函数按从里往外的顺序打印,就会生成Hello, Linux Group - 2023。

第二个知识点是未定义的运算,两个数组名,也就是两个数组首元素的地址相减,这种运算没有被定义,是没有意义的。

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

先来谈谈sizeof()和strlen()吧。sizeof()是一个静态运算符,意义是计算变量所占内存大小,单位是字节,由于它是静态运算符,所以在其中做的运算没有用,不会生效。strlen()是计算字符串长度的函数,被包含在<string.h>库中,所以使用时头文件一定要有<string.h>。

sprintf()函数的定义是sprintf(char* buffer,const char* format[,argument]…);这样看似乎很复杂,但它用起来并不复杂,比如要把整数打印到字符串中,sprintf(str,“0x%x”,num),就会把num的值520换算为16进制打印到str[]字符串中,即打印0x218,而sprintf()函数的返回值为写入的字符总数,与printf()函数的返回值类似。

程序的输出:

sizeof(&arr)是对数组名取地址后再解引用,还是数组名。这里有一个关于数组名的知识,我们都知道数组名是首元素的地址,但在某些特殊情况下,数组名代表的是其他含义:1.当&数组名时代表的是整个数组的地址。2.当sizeof(数组名)时得到的是整个数组所占的内存大小。所以这里sizeof(arr)的值是数组大小,即7个字节。

sizeof(arr+0)时,因为不是直接sizeof(数组名),而是sizeof(数组名+0),所以这时的数组名就是数组首元素地址,而sizeof(arr+0)的值为指针的大小,在32位系统中为4个字节,在64位系统中为8个字节。

sizeof(num=num2+4)时,由于sizeof()是静态运算符,所以计算的是short类型的字节大小,即2。

sprintf(str,“0x%x”,num)的返回值为5,显然不等于num,所以输出0。

strlen(&str[0]+1)中,&str[0]+1为数组内第二个元素的地址,而str[20]中的字符串为“0x218”,所以strlen(&str[0]+1)的返回值为从第二个元素开始数,到\0结束的字符串长度,即4。

strlen(arr+0)与strlen(arr)相同,即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);
}

这道题考察了位运算的知识:&是与,两个位都为1时,结果才为1;|是或,两个位都为0时,结果才为0;^为异或,两个位相同为0,相异为1;~是取反,0变1,1变0;<<是左移,各二进位全部左移若干位,高位丢弃,低位补0;>>是右移,各二进位全部右移若干位,对无符号数,高位补0,有符号数,右移补1。

64换算为二进制为01000000,127换算为二进制为01111111。

a=64&127为01000000,换算为十进制为64。

b=64^127为00111111,换算为十进制为63。

c=-64>>6涉及到负数的补码运算,计算机中是使用二进制补码的做运算的,正数的补码是它本身,负数的补码是符号位不变,其余按位取反,最后再加1得到的。所以-64的补码就是11000000,再右移6位,即10000001,换算为十进制为-1。

ch本应该是128,但char类型的取值范围为-128~127,超过后会从头开始,所以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)));
}

输出结果为:

6

本题考察的是递归与位运算,func()的功能是实现两个数的相加。其实不需要用+,用位运算也能实现两个数的相加,两个数相加的结果可以用位运算表示为两个数异或的结果加上两个数相与后再左移一位的结果,但是又不能出现加号,所以要不停的让这两个结果在进行这样的运算,直到两个数相与后左移一位的结果等于零时停止,这时两个数异或的结果就是最开始那两个数相加的结果,而这样的情况,就可以用递归的思维,使它不断调用自身,直到两个数相与后左移一位的结果等于零。

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 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;
}
typedef int (*Predicate)(int);
int *filter(int *array, int length, Predicate predicate,int *resultLength)//过滤的函数
{
    int j=0;
    int *filteredNumbers=(int *)malloc(sizeof(int)*length);//为过滤后的新数组分配空间
    for(int i=0;i<length;i++)
    {
        if(isPositive(array[i]))
        {
            filteredNumbers[j]=array[i];
            j++;
        }
    }
    *resultLength=j;
    return filteredNumbers;
}
 
int isPositive(int num)//判断正数
{
    if(num>0)
    {
        return 1;
    }
    return 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;
}

7.静… 态…

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

先讲讲内存,内存分为很多块区域,有栈区,用来存放局部变量和形式参数;有堆区,用来存放动态内存分配的内存;有静态区,用来存放静态变量和全局变量;有常量区,用来存放常量;有代码区,用来存放代码的二进制指令。

对于局部变量,static可以改变其存储位置,将其从栈区转移到静态区,使其生命周期变长,从原来的在其所在的函数内存活变为在整个程序中存活,但是这个局部变量的作用域没有改变,还是只能在那个局部的范围内使用。

对于全局变量,static可将其的外部链接属性变为内部链接属性,其他源文件不得再使用这个全局变量。

对于函数,与全局变量类似,static可将其的外部链接属性变为内部链接属性,其他源文件不得再使用这个函数。

对于指针,要看这个指针变量是局部变量还是全局变量。

8.救命!指针!

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

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

数组指针实质上是指针,是一个指向其他处数组的指针,这个指针代表的是整个数组的地址,而不是数组首元素的地址。数组指针的定义为:数组的元素类型(*指针名)[数组大小]=&数组名。

指针数组实质上是数组,是由许多指针组成的数组。

这里还考察了一个点,是常量指针与指针常量,如果const在前面,即const int * n或int const * n,说明是常量指针,如果const在后面,即int * const n,说明是指针常量。常量指针是指不能通过这个指针改变变量的值,即不能用 *指针=xxx来改变变量的值,但并不是说指针本身不能改变,常量指针可以指向其他的变量。指针常量是指指针本身是个常量,不能再指向其他的变量,但可以通过这个指针去改变变量的值,即可以用 *指针=xxx来改变变量的值。常量指针和指针常量正好是相反的。

函数指针实质上也是指针,只不过是指向函数的指针。再深入讲讲,函数名其实和和数组名类似,是等于函数首地址的,&函数名则是整个函数的地址。所以函数指针的定义就是:函数的返回值类型(*指针名)(函数的参数列表类型)=&函数名。

9.咋不循环了

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

int main(int argc, char* argv[]) {
 printf("[%d]\n", argc);
 while (argc) {
 ++argc;
 }
 int i = -1, j = argc, k = 1;//j=0;
 i++ && j++ || k++;//i不为假,继续进行,然后j为假,但在||处继续进行.
 printf("i = %d, j = %d, k = %d\n", i, j, k);// 0 1 2
 return EXIT_SUCCESS;

在小白的学习过程中,不会见到main函数的参数,其实main函数是有参数的,int main(int argc,char* argv[],char* envp[])才是main函数的完整形式。第一个参数argc指的的是命令行参数的个数,它至少为一,因为第一个参数始终是程序的名称第二个参数argv是一个二维的char型指针,存放命令行参数字符串,第三个参数envp也是一个二维的char型指针,存放环境变量。

题目中使argc不断循环自增,因为argc是int类型,所以它的取值上限就是2 ^ 31-1,在超过这个上限后,变为-2^31,再自增到0,循环结束。

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

这道题考的是预定义中的定义宏,定义宏有一个细节是要注意合理使用括号,不然会导致运算错误。如本题的#define CAL(a) a * a * a,需要变为((a) * (a) * (a))。

还有一个考点是运算符号优先级的问题。++优先级是很高的,所以优先运算,所以nums的值就不是1,而是自增三次的4,故CAL(++nums)的值为64。

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

这道题考了结构体整体赋值的知识点。结构体可以整体赋值,即可以使struct1=struct2。但在这道题中Student的成员name是用指针来定义的字符串,如果让两个指针相等,会使这两个指针指向同一块内存地址,之后操作任何一个指针,另一个指针也会受到同样的操作,会造成混乱。所以要复制字符串的值,就要用strcpy函数来复制。

12.你好,我是内存

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

//对齐系数为8
struct structure 
{
  int foo;//8字节
  union
  {
    int integer;
    char string[11];
    void *pointer;
  }node;//16字节
  short bar;//8字节
  long long baz;//8字节
  int array[7];//32字节
};//共72字节
int main(void) 
{
  int arr[] = {0x590ff23c, 0x2fbc5a4d,/*从这里开始打印*/ 0x636c6557, 0x20656d6f,
  0x58206f74, 0x20545055, 0x6577202c, 0x6d6f636c,
  0x6f742065, 0x79695820, 0x4c20756f, 0x78756e69,
  0x6f724720, 0x5b207075, 0x33323032, 0x7825005d,/*到5d后碰到/0打印结束*/
  0x636c6557, 0x64fd6d1d};
  printf("%s\n", ((struct structure *)arr)->node.string);
}

这道题考察了结构体和联合体的存储方式,大小端的存储,ascii码。64位电脑的对齐系数默认为8,所以结构体会按照这个来对齐内存,联合体的内存为大于其最大成员内存的对齐系数的最小整数倍,即如注释所说。

13.GNU/LINUX(选做)

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

cd是切换目录的命令,"/“表示根目录,”.“表示当前目录,”~"表示root用户的目录。

GNU是一个目标要实现开源的软件项目,linux作为开源项目的内核,GNU项目提供编辑器,编译器,调试器,汇编器,链接器等一系列构成开源系统的必要组件。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值