① 本题目只作为西邮 Linux 兴趣小组 2023 纳新面试的有限参考。
② 为节省版面,本试题的程序源码省去了#include 指令。
③ 本试题中的程序源码仅用于考察 C 语言基础,不应当作为 C 语言「代码风格」的范例。
④ 所有题目编译并运行于 x86_64 GNU/Linux 环
0.鼠鼠我啊,要被祸害了
有 1000 瓶水,其中有一瓶有毒,小白鼠只要尝一点带毒的水,24 小时后就会准时死亡。
至少要多少只小白鼠才能在 24 小时内鉴别出哪瓶水有毒?
首先举个例子,4瓶水,给其编号00 01 10 11,两个小鼠,分别喝第一位为1的水,第二位为1的水,就可以判断出哪一瓶有毒,,8瓶水000 001 010 011 100 101 110 111,以此类推,需要三只小鼠,即2的10次方,需要10只小鼠。
1.先预测一下~
按照函数要求输入自己的姓名试试~
char *welcome() {
// 请你返回自己的姓名
}
int main(void) {
char *a = welcome();
printf("Hi, 我相信 %s 可以面试成功!\n", a);
return 0;
}
第一种
char *welcome() {
return "张三";
}
int main(void) {
char *a = welcome();
printf("Hi, 我相信 %s 可以面试成功!\n", a);
return 0;
}
第二种
char *welcome() {
char *arr="zhangsan";
return "zhangsan" ;
}
int main(void) {
char *a = welcome();
printf("Hi, 我相信 %s 可以面试成功!\n", a);
return 0;
}
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的返回值
if (*ptr0 == *ptr1) 这个条件判断的是 ptr0 和 ptr1 所指向的字符串的第一个字符是否相同。由于两个字符串完全相同,因此条件为真。
printf(“%d\n”, printf(“Hello, Linux Group - 2%d”, printf(“”)));内层的 printf(“”) 调用返回0,由于格式化字符串中的 %d 被0替换,第二个 printf 实际上会打印 “Hello, Linux Group - 20”,并返回打印的字符数,然后,外层的 printf 会打印这个返回的字符数(即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));
}
问题一:
- sizeof(*&arr) 相当于 sizeof(arr),即7
- sizeof(arr + 0) ,0对指针没有影响,sizeof取指针大小为8
- sizeof(num = num2 + 4) 是一个赋值表达式,但sizeof不计算赋值的结果,而是计算表达式类型的大小 num = num2 + 4的结果是一个int,所以sizeof的结果是short的大小,所以是2
- sprintf(str, “0x%x”, num) 将num的值以十六进制形式写入str,并返回写入的字符数(不包括结尾的’\0’),这个返回值不可能等于num,因此,sprintf(str, “0x%x”, num) == num 的结果是0(假),sprintf它像 printf 一样格式化数据,但不同之处在于 sprintf 将结果存储在字符串中,而不是输出到标准输出。
- strlen(&str[0] + 1) 相当于 strlen(str + 1),它从str的第二个字符开始计算长度0x208,结果为4
strlen(arr + 0) 相当于 strlen(arr),它计算arr中从第一个字符到’\0’之前的字符数,即5
问题二
sizeof() 是一个编译时运算符,用于计算类型或变量在内存中占用的字节数。它可以用于基本数据类型(如int、char等)、数组、结构体等,并且对于数组,它返回整个数组的大小(包括所有元素),而不是指向数组的指针的大小。
strlen() 是一个函数,用于计算C字符串(以’\0’结尾的字符数组)的长度,即从字符串的开头到第一个’\0’字符之前的字符数。它不包括结尾的’\0’字符。如果传递给strlen()的指针没有指向有效的C字符串(即没有以’\0’结尾),则行为是未定义的。
问题三:sprintf 函数是 C 语言标准库中的一个函数,用于将格式化的数据写入字符串。它类似于 printf 函数,但 printf 是将格式化的数据输出到标准输出(通常是屏幕),而 sprintf 是将格式化的数据输出到一个字符串中。sprintf 函数的原型定义在 <stdio.h> 头文件中
示例// 使用 sprintf 将格式化的数据写入 s sprintf(s, "Name: %s, Age: %d, Height: %.1f", name, age, height);
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);
}
考点:&、^、>> 分别是按位与、按位异或和右移运算符
64的二进制表示是 01000000,127的二进制表示是 01111111。按位与的结果是 01000000,即64。所以 a = 64。
64的二进制表示是 01000000,127的二进制表示是 01111111。按位异或的结果是 00111111,即63(在十进制中)。所以 b = 63。
64的二进制表示是 01000000,右移6位,相当于除6次2,所以c=-1。
由于char类型表示范围为-128~127,所以ch = a + b - c=-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
考点:函数递归,位运算
其实结果就是a+b+c=6;
(a & b) << 1表示两数相加进位,
a ^ b表示两数相加的过程,
通过递归,当函数没有进位时,返回原位
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 isPositive(int num) { return num > 0; }//返回大于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 j=0;
int *filteredNumbers=(int *)malloc(sizeof(int)*length);//由于后面有free(filteredNumbers),所以这里要申请动态内存空间
for(int i=0;i<length;i++)
{
if(isPositive(array[i]))
{
filteredNumbers[j]=array[i];//创建新数组
j++;
}
}
*resultLength=j;
return filteredNumbers;
}
7.静…态…
① 如何理解关键字 static?
② static 与变量结合后有什么作用?
③ static 与函数结合后有什么作用?
④ static 与指针结合后有什么作用?
⑤ static 如何影响内存分配?
① 如何理解关键字 static?
static 是 C 和 C++ 语言中的一个关键字,它用于指定变量的存储期为静态存储期,或用于限制函数的作用域。static 关键字的主要作用是改变变量的生命周期或函数的作用域,以及影响内存分配的方式。
② static 与变量结合后有什么作用?
当static用于修饰全局变量时,该变量的作用域会被限定在定义它的源文件内部。
当static用于修饰局部变量时,生命周期从程序启动一直持续到程序结束。
③ static 与函数结合后有什么作用?
当 static 与函数结合时,它限制了函数的作用域,使其只在定义它的文件内部可见。
④ static 与指针结合后有什么作用?
static 与指针结合时,它同样改变了指针所指向对象的生命周期。如果指针指向的是一个静态变量或静态分配的内存区域,那么该指针在程序运行期间将始终指向有效的内存地址。此外,静态指针本身也可以被声明为静态的,这意味着指针本身(而不是它所指向的对象)在程序运行期间将保持其值不变。
⑤ static 如何影响内存分配?
static 关键字对内存分配的影响主要体现在以下几个方面:
静态变量:静态变量在程序的静态存储区分配内存,而不是在栈上。这意味着静态变量的生命周期贯穿整个程序运行期间,并且它们只被初始化一次。
静态函数:静态函数本身不直接涉及内存分配,但它们限制了函数的作用域,从而可能减少了全局命名空间的污染,并有助于优化程序的内存使用(通过减少不必要的函数副本)。
静态指针:静态指针本身在静态存储区分配内存,但它所指向的对象可能位于静态存储区、堆或栈上,具体取决于指针的初始化和使用方式。如果静态指针指向静态分配的内存区域(如静态变量或动态分配但未被释放的内存),则该内存区域在程序运行期间将始终有效。
8.救命!指针!
数组指针是什么?指针数组是什么?函数指针呢?用自己的话说出来更好哦,下面数据类
型的含义都是什么呢?
int (*p)[10];
const int* p[10];
int (*f1(int))(int*, int);
int (p)[10] 是数组指针
const int p[10]指针数组,且其地址可以改变,值不可以改变
int (f1(int))(int, int)首先f1是变量名,(int)是是参数类型,int ()(int, int)是其函数的返回类型,这个函数是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至少为1,这个循环尝试通过递增argc直到它变为0来执行一些操作。然而,这个循环是无效的。
运算符的短路
x||y,如果x为真,则不再对y进行运算,x为假,则对y进行运算
x&y,如果x为假,则不再对y进行运算,x为真,则对y进行运算
i = 0, j = 1, k = 2
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);
}
16 / CAL(2)=16/222=32!=2,所以不执行后面语句;
执行else,else中定义了新的nums,但最后打印应该是1,因为最外层定义了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 = s2,其存在一个问题,就是将同一块内存释放了两次
#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 freeStudent(struct Student *student) {
free(student->name);
}
int main(void) {
struct Student s1, s2;
initializeStudent(&s1, "Tom", 18);
initializeStudent(&s2, "Jerry", 28);
// 正确的赋值方式
freeStudent(&s1); // 首先释放s1的name内存
s1.name = (char *)malloc(strlen(s2.name) + 1); // 为s1.name分配内存
strcpy(s1.name, s2.name); // 复制s2的name到s1
s1.age = s2.age; // 复制age
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);
}
Welcome to XUPT , welcome to Xiyou Linux Group [2023]
首先可以看一下结构体的大小为72字节,考察结构体和联合体的对齐规则,((struct structure *)arr)->node.string),arr表示首元素的地址,将其强转换成(struct structure *),并指向node中的string,并以%s打印出来,int foo,占0~3,union从8开始占,所以从0x636c6557开始打印,并且这些数以小端存储,对照ASCII值依次打印。
13.GNU/Linux (选做)
注:嘿!你或许对 Linux 命令不是很熟悉,甚至你没听说过 Linux。但别担心,这是选做题,了解
Linux 是加分项,但不了解也不扣分哦!
你知道 cd 命令的用法与 / . ~ 这些符号的含义吗?
请问你还懂得哪些与 GNU/Linux 相关的知识呢~
cd命令的用法
cd命令的基本语法为“cd [目录路径]”,其中[目录路径]表示要切换到的目录路径。以下是cd命令的一些常见用法:
切换到指定目录:使用cd命令后跟目录路径,如cd /var/www/html,表示切换到/var/www/html目录。如果目录路径是相对路径,如cd html,则表示切换到当前目录下的html目录。
切换到上级目录:使用cd命令后直接回车(或者cd …),表示切换到当前目录的上级目录。
切换到用户家目录:使用cd命令后加上~符号即可切换到当前登录用户的家目录,如cd ~或仅输入cd(不带参数,直接回车)。
切换到上次所在的目录:使用cd命令后加上-符号,表示切换到上次所在的目录,如cd -。
使用环境变量切换目录:使用cd命令后加上$符号,后面跟上环境变量名。例如,cd $HOME可以切换到用户家目录。
符号的含义
/:根目录/目录分隔符。在Linux文件系统中,每个文件和目录都有一个唯一的文件路径,文件路径是由“/”符号分隔的一系列目录和文件名组成的字符串。例如,“/home/user/Documents/file.txt”是一个文件路径,其中“/”表示根目录。
.:当前目录。在Linux命令中,.代表当前目录。例如,./file.txt表示当前目录下的file.txt文件。
:当前用户的主目录。在Linux中,符号代表当前用户的家目录。家目录是每个用户在Linux系统中的默认工作目录,当用户登录到系统后,终端会自动切换到该用户的家目录。例如,如果当前登录的用户是“user”,那么~符号就代表/home/user目录。
769

被折叠的 条评论
为什么被折叠?



