学长寄语:
长期以来,西邮Linux兴趣小组的面试题以难度之高名扬西邮校内。我们作为出题人也清楚的知道这份试题略有难度。请你手动敲一下代码
。别担心,若有同学能完成一半的题目,就已经十分优秀。 其次,相比于题目的答案,我们对你的思路和过程更感兴趣,或许你的答案略有瑕疵,但你正确的思路和对知识的理解足以为你赢得绝大多数的分数。最后,做题的过程也是学习和成长的过程,相信本试题对你更加熟悉的掌握C语言的一定有所帮助。祝你好运。我们FZ103见!
- 本题目只作为西邮 Linux 兴趣小组 2023 纳新面试的有限参考。
- 为节省版面,本试题的程序源码省去了#include 指令。
- 本试题中的程序源码仅用于考察 C 语言基础,不应当作为 C 语言「代码风格」的范例。
- 所有题目编译并运行于 x86_64 GNU/Linux 环境。
0.鼠鼠我啊,要被祸害了
有1000 瓶水,其中有一瓶有毒,小白鼠只要尝一点带毒的水,24 小时后就会准时死亡。至少要多少只小白鼠才能在24小时内鉴别出那瓶水有毒?
用二进制
代表每瓶水,在十个比特位的情况下(十个比特位可以表示1024瓶水)。
第一瓶水二进制形式为0000000001以此类推,第一千瓶水二进制为1111101000。分别用对应二进制第一位是1,第二位是1,第三位是1…的水喂给十只小鼠。
24小时后记录下死亡小鼠对应的水瓶标号,将二进制转换为十进制形式,即为有毒的水的标号。
1.先预测一下~
按照函数要求输入自己的姓名试试~
char *welcome(
//请你返回自己的姓名试试
)
int main(void){
char *a = welcome();
printf("Hi,我相信 %s 可以面试成功!\n",a);
return 0;
}
static char name[10] = {0};
scanf("%s",name);
return name;
char *name = {0};
scanf("%s",name);
return 0;
const char name[10] = {0};
scanf("%s",name);
return name;
#include<stdio.h>
#include<malloc.h>//malloc要引用的头文件
char *welcome(){
char* name = (char*)malloc(10);//动态申请内存
scanf("%s",name);
return name;
}
int main(void){
char *a = welcome();
printf("Hi,我相信 %s 可以面试成功!\n",a);
free(a);//释放动态申请的内存空间
return 0;
}
本题应该用传址的方法输出姓名
在welcome()函数内部定义的字符数组在函数结束调用时其值会自动销毁
输出其地址也就是指针会传递存储输入内容的地址
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);
}
本题考察字符串
的使用
ptr0,ptr1是字符串名称,*以及[]代表它们是指针,指向字符串首元素地址。
if判断语句解引用字符串名称比较的是字符串首元素对应的ascii码值,为真即执行printf的嵌套
循环。
printf函数返回值为输出字符个数
printf嵌套从最里层开始输出
" "为空字符什么也不输出,其返回值为0
接下来输出printf(“Hello, Linux Group - 2%d”,printf(“”))
输出结果为Hello, Linux Group - 20,其返回值为23
最后输出最外层Hello, Linux Group - 2023。
int diff = ptr0 - ptr1
字符串名称为其首元素地址,地址相减并以十进制形式输出。
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));
}
- 以下都是在32位平台上的输出结果
字符数组遇到\0就会停止
往后读入,字符数组的名字取地址解引用可以理解为抵消既sizeof(arr)
arr代表数组首元素地址,但在sizeof运算符中代表数组地址,5*4 = 20。
sizeof
是运算符计算变量所占空间的大小。
strlen
是用来求字符串长度的函数,\0之前的字符个数(不包括\0)。
sizeof(arr+0)
arr+0代表数组第一个元素地址,是地址就输出4
sizeof(num = num2+4)
sizeof()中的赋值并不会起作用。num是char型占2字节。
sprintf
函数为数据写入某个字符串,即将num写入str如果成功返回写入字符总数失败则返回负数。
==
为判断条件,不同为0。
strlen(&str[0]+1)
&str[0]+1代表第二个元素地址。&str+1代表跳过该数组。
strlen(arr)
到\0停返回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之外,其他情况均在空位处补0
-
位运算
&
与:两个位都为1时,结果才为1
|
或:两个位都为0时,结果才为0
^
异或:两个位相同为0,相异为1
~
取反:0变1,1变0
<<
左移:各二进位全部左移若干位,高位丢弃,低位补0。
>>
:各二进位全部右移若干位,无符号数高位补0。
位运算
输出:
a = 64,b = 63,c = -1
ch = -128!!!
ch = 64+63-(-1)=128,而char类型的存储大小为-128~127,当其值增为128时会从-128重新开始计数。
5.乍一看就不想看的函数
“人们常说互联网凛冬已至,要提高自己的竞争力,可我怎么卷都卷不过别人,只好用一些奇技淫巧让我的代码变得高深莫测。“
这个fun()函数的功能是什么?是如何实现的?
int func(int a,int b){
if(!a) return 0;
rerurn func((a & b) << 1,a ^ b);
}
int main(void){
int a = 4,b = 9,c = -7;
printf("%d\n",func(a,func(b,c)));
}
学会递归
的基本思想
if(!a)等同于if(a==0),是递归的控制条件。
func()函数使用递归来计算 a 和 b 的乘积,将乘法操作转化为位操作:
(1)如果 a 等于0,函数返回0,这是递归的终止条件。
(2)否则,它递归调用自身,其中:
-
(a & b) << 1 表示将 a 和 b 进行按位与操作,然后将结果左移1位,这相当于计算两者的进位
-
a ^ b 表示将 a 和 b 进行按位异或操作,这相当于计算两者的不带进位的和
其实就是通过递归来不断计算出乘积。
main() 函数中,它调用 func() 函数两次:func(b, c) 和 func(a, func(b, c)),用于计算出 abc的乘积并输出。
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;
}
typedef int (*Predicate)(int);
int *filter(int *array, int length, Predicate predicate,int *resultLength){
int *result = (int*)malloc(length);
int count = 0;
for(int i = 0;i<lehgth;i++){
if(predicate(array[i])){
result[count] = array[i];
count++;
}
}
*resultLength = count;
return result;
}
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;
}
7.静…态…
① 如何理解关键字 static?
② static 与变量结合后有什么作用?
③ static 与函数结合后有什么作用?
④ static 与指针结合后有什么作用?
⑤ static 如何影响内存分配?
作用域指程序中被定义的变量生效的区域,超过该区域就不能访问
- 修饰局部变量
改变局部变量的存储位置,使其生命周期变长而不改变其作用域(定义的函数内部)
- 修饰全局变量
改变其作用域(整个源程序),全局变量具有外部链接属性。被static修饰并为内部链接属性只能在自己原文件中使用,extern为外部声明。
- 修饰函数
静态函数和全局变量相似。
- 修饰指针
static int *p;
p这个指针是静态的,p的地址不变,而地址本身的内容可以。
- 影响内存分配
编程内存可以分为静态存储区,堆区和栈区。局部变量会存储到栈区,而全局变量存储到静态区。
当static与局部变量结合后会改变其存储的位置使其存储到静态区。
8.救命!指针!
数组指针是什么?指针数组是什么?函数指针呢?用自己的话说出来更好哦,下面数据类
型的含义都是什么呢?
int (*p)[10];
const int* p[10];
int (*f1(int))(int*, int);
数组指针
的本质是指针,用来存储数组的地址。
指针数组
的本质是数组,以数组的形式来存储指针。
函数指针
是指针用来存储函数地址。
int (*p)[10];是一维数组指针指针名为p。
const int* p[10];是一个指针数组,用const修饰来表示它的地址并不会被改变。
int (*f1(int))(int*, int)
;这个比较复杂,*f1(int)是一个函数名为f1其内容是int型返回类型为指针类型。然后将其作为一个整体,其内容为int指针类型和int型,返回值类型为int型。
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;
}
- argc是外部输入的参数个数,argv是参数的字符串数组。
argc用来统计程序运行时的命令行参数的个数,本题中外部输入参数只有执行程序的这个命令即argc的值为1。
while循环并不是死循环因为argc为int型,int的取值范围为:-2147483648 ~ 2147483647。
-
&&
短路与:如果第一个条件为 false,则第二个条件不会判断,最终结果为 false,效率高。 -
||
短路或:如果第一个条件为 true,则第二个条件不会判断,最终结果为 true,效率高。
i++ && j++ || k++
,++都为后置,i=-1非零为真即继续执行后面的j++,||
只要一个为真即为真。
i,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);
}
宏不计算其只是对文本进行简单的替换。
int main(void) {
int nums = 1;
if(16 / 2 * 2 * 2 == 2) {
printf("I'm TWO(ノ>ω<)ノ\n");
} else {
int nums = ++nums * ++nums * ++nums + 2 * 2 * 2;
}
printf("%d\n", nums);
return 0;
}
if条件为假,即执行else语句
在c语言中不能连续使用++,程序会出现无法预料的后果。
在if函数外层定义的nums是个全局变量,if语句里面定义的是个局部变量。
局部变量可以与全局变量重名,但是局部变量会屏蔽全局变量。
*注意:局部变量不能赋值给同名全局变量的值
#include<stdio.h>
int value = 1;//全局变量
int main(){
int value = value;
printf("%d",value);
return 0;
}
输出:-898993460
分析如下:
-
定义了一个全局变量value初始化值为1,同时在main函数内部定义了一个局部变量value
-
在main函数内部对局部变量value进行赋值,因为局部变量会屏蔽全局变量因此此时赋值语句中两个value都是局部变量,而value还没有初始化所以值为随机值
程序最后输出的nums值为1,在else语句中将值赋给了else中定义的nums,而最后输出的是else语句外部定义的nums的值。
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;
}
深浅拷贝
-
浅拷贝:意味着只复制了对象的表面结构,而不复制对象所引用的数据。这意味着复制一个对象时,只是复制了对象的指针或引用,而不是对象指向的内容。而如果原始对象中有指针指向另一个对象或数据,浅拷贝后的对象中的相应指针将指向相同的内存地址。这可能会出现问题,因为对其中一个对象所指向的数据的修改也会影响到另一个对象。
-
深拷贝:是一种更彻底的复制。在深拷贝中,不仅复制了对象本身的内容,还会递归复制对象所引用的所有数据,确保原始对象和新对象之间没有共享数据。这意味着即使数据结构中包含指针或引用,深拷贝也会复制指针所指向的内容,而不是简单地复制指针本身。
本题当s2赋值给s1时,实际上是将s2结构体的内容复制给了s1,包括它的name指针。
s1和s2的name指针都指向了相同的内存地址,因此共享相同的姓名字符串。所以输出s1和s2的姓名时,它们都显示为"Jerry"。
但当程序结束时调用free释放内存时,两者都会释放相同的内存地址,而内存应该只被释放一次,出现了错误。
应该实现深拷贝来复制name指针所指向的字符串,而不是直接赋值结构体。
因此要分配新的内存,并将name指针指向这个新的内存,再复制字符串的内容。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
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;
}
void copyStudent(struct Student *dest, const struct Student *src) {
dest->name = (char *)malloc(strlen(src->name) + 1);
strcpy(dest->name, src->name);
dest->age = src->age;
}
void freeStudent(struct Student *student) {
free(student->name);
}
int main(void) {
struct Student s1, s2;
initializeStudent(&s1, "Tom", 18);
initializeStudent(&s2, "Jerry", 28);
// 使用深拷贝函数复制s2到s1
copyStudent(&s1, &s2);
printf("s1 的姓名: %s 年龄: %d\n", s1.name, s1.age);
printf("s2 的姓名: %s 年龄: %d\n", s2.name, s2.age);
// 释放内存
freeStudent(&s1);
freeStudent(&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);
}
本题从arr[]输出从第三个十六进制数开始输出。
struct structure {
int foo;//4个字节
union {
int integer;
char string[11];
void *pointer;
} node;//16个字节
short bar;//2个字节
long long baz;//8个字节
int array[7];//28个字节
};
int main(void) {
int arr[] = {0x590ff23c, 0x2fbc5a4d, 0x636c6557, 0x20656d6f,
0x58206f74, 0x20545055, 0x6577202c, 0x6d6f636c,
0x6f742065, 0x79695820, 0x4c20756f, 0x78756e69,
0x6f724720, 0x5b207075, 0x33323032, 0x7825005d,//'00'对应\0,即输出结束。
0x636c6557, 0x64fd6d1d};
printf("%s\n", ((struct structure *)arr)->node.string);
}
输出:Welcome to XUPT , welcome to Xiyou Linux Group [2023]
根据结构体内存对齐的知识int foo占四个字节,union存储在其后四个字节处即int foo总共占8个字节其中后四个什么也没存。
0x590ff23c, 0x2fbc5a4d这两个存在于int foo的空间中,因为输出的是结构体中的共用体,所以从第三个十六进制开始输出。
-
大端序:数据高位字节存入低地址,数据低位字节存入高地址
-
小端序:数据低位字节存入低地址,数据高位字节存入高地址
本题用十六进制来代替每个字母和符号(一个十六进制为四个字母或符号)包括空格。
-
结构体
内存对齐
-
结构体的第一个成员对齐到相对结构体变量起始位置偏移量为0的地址处。
-
其他成员变量要对齐到对齐数的整数倍的地址处。
-
对齐数 = 编译器默认的一个对齐数与该成员变量大小的较小值。(VS中默认的值为8,Linux中没有默认对齐数)对齐数就是成员自身的大小。
-
结构体总大小为最大对齐数的整数倍。
共用体
使用了内存对齐
-
同一时刻只能保存一个成员的值,如果对新的成员赋值,就会把原来成员的值覆盖掉。
-
联合的占用内存大小只需要整除最大对齐数。
-
联合体总大小为最大对齐数的整数倍。
结构体和共用体的区别在于:结构体的各个成员会占用不同的内存,互相之间没有影响;而共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员,结构体占用的内存至少为所有成员占用的内存的总和。
(struct structure *)arr是结构体指针的强制类型转换。
13.GNU/Linux (选做)
注:嘿!你或许对 Linux 命令不是很熟悉,甚至你没听说过 Linux。但别担心,这是选做题,了解
Linux 是加分项,但不了解也不扣分哦!
你知道 cd 命令的用法与 / . ~ 这些符号的含义吗?
请问你还懂得哪些与 GNU/Linux 相关的知识呢~
恭喜你做到这里!你的坚持战胜了绝大多数看到这份试题的同学。
或许你自己对答题的表现不满意,但别担心,请自信一点呐。
坚持到达这里已经证明了你的优秀。
还在等什么,快带上你的笔记本电脑,来FZ103面试吧!