感谢 Zhilu 重新录入题目原件。好人一生平安。
- 本题目只作为
Xiyou Linux兴趣小组
2022纳新面试的有限参考。 - 为节省版面,本试题的程序源码省去了
#include
指令。 - 本试题中的程序源码仅用于考察C语言基础,不应当作为C语言「代码风格」的范例。
- 题目难度随机排列。
- 所有题目编译并运行于
x86_64 GNU/Linux
环境。
零、我的计算器坏了?!
`2^10 = 1024`对应于十进制的4位,那么`2^10000`对应于十进制的多少位呢?
依题可知4=[10lg2+1]
因此[10000lg2+1]=3011
2^10000对应十进制的3011位
一、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("")));//输出结果为Xiyou Linux Group - 2022
}
此题考察的是printf函数的返回值,其函数返回值是格式化输出字符串的长度包括空格,除了回车。
因此第一层输出为空,第二层输出为0,第三层输出为22,最后输出是Xiyou Linux Group - 2022
二、你好你好你好呀
- 程序的输出有点奇怪,请尝试解释一下程序的输出吧。
- 请谈谈对`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));
}
1、对于第一行的printf而言,p0,p1的地址不同故p0==p1的值为0,p0,p1的文本内容相同故strcmp(p0,p1)为1
2、对于第二行的printf而言,要解决的问题是%zu
%zu通常用于输出内存大小或对象大小,即sizeof(a),输出的类型是size_t,在库中的定义是一种无符号整型,在32位机是unsigned int,在64位机是unsigned long
因此输出分别为12(空白符占用内存),8(地址有八byte,64位机),1(char类型只占1byte)
3、对于第三行的printf而言, strlen函数的功能是求字符串长度,因此第一个输出(字符串长度)和第二个输出(字符串长度)都为11
三、换个变量名不行吗?
请结合本题,分别谈谈你对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);
}
全局变量:贯穿于整个程序,随时可调用,值随时可变,可以运行于多个文件。
局部变量:随取随用,用完即销。
生命周期:从声明获取空间到释放内存释放空间的过程。
第一行是全局变量
test函数中的a是局部变量
test函数运行的过程是a的生命周期
四、内存对不起
`union`与`struct`各有什么特点呢,你了解他们的内存分配模式吗。
typedef union {//最大的数据类型long为8,对齐后需要24
long l;
int i[5];
char c;
} UNION;
typedef struct {//最大的数据类型为double/long,对齐后需要8*3+8+8
int like;
UNION coin;
double collect;
} STRUCT;
int main(void) {
printf("sizeof (UNION) = %zu\n", sizeof(UNION)); //24
printf("sizeof (STRUCT) = %zu\n", sizeof(STRUCT));//40
}
union:所有成员共享内存空间,且需要内存对齐,因此整个联合体的空间就是所占空间最大的成员的空间的整数倍,且空间连续
struct:所有成员按顺序占用内存空间,同时为了方便提取数据和确保数据提取正确存在内存对齐的储存模式,即以所占空间最大的成员所占空间为单元储存数据,在内部嵌套时也需要将内部的数据类型考虑
因此
五、Bitwise
- 请使用纸笔推导出程序的输出结果。
- 请谈谈你对位运算的理解。
int main(void) {
unsigned char a = 4 | 7;//7
a <<= 3;//56
unsigned char b = 5 & 7;//5
b >>= 3;//0
unsigned char c = 6 ^ 7;//1
c = ~c;//254(unsigned)
unsigned short d = (a ^ c) << 3;//
signed char e = -63;//11000001
e <<= 2;//000000100,4
printf("a: %d, b: %d, c: %d, d: %d\n", a, b, c, (char)d);//d高位截断
printf("e: %#x\n", e);//十六进制输出
}
位运算是对数据每一位进行的运算,可以对数据直接进行处理,减少了算术运算的步骤
位运算可以替代算术运算、制作子网掩码等
六、英译汉
请说说下面数据类型的含义,谈谈`const`的作用。
1. `char *const p`。
2. `char const *p`。
3. `const char *p`。
1、char *const p声明了一个指针常量,即p这一个指针不能指向别的地址
2、char const *p声明了一个常量指针,即p指向一个常量
3、const char *p声明了一个指向常量的常量指针,即p的指针不能改变,指向的值也不能改变
const是将一个变量修饰为常量的修饰词,可以防止地址和内容的修改
七、汉译英
请用变量`p`给出下面的定义:
1. 含有10个指向`int`的指针的数组。
2. 指向含有10个`int`数组的指针。
3. 含有3个「指向函数的指针」的数组,被指向的函数有1个`int`参数并返回`int`。
1、int* p[10] p[10]声明了这是一个数组,*p[10]表明这个数组的内容是指针
2、int (*p)[10]) *p声明了这是一个指针,[10]声明了这是一个由10个组成的数组的指针
3、int (*p[3]) (int) 先声明一个指针数组,这个数组指向参数为int的函数
八、混乱中建立秩序
你对排序算法了解多少呢?
请谈谈你所了解的排序算法的思想、稳定性、时间复杂度、空间复杂度。
提示:动动你的小手敲出来更好哦~
排序方法有冒泡排序、直接选择排序、直接插入排序、希尔排序、堆排序等等
分为稳定排序、不稳定排序两大类,以相同元素改变顺序后结果是否改变顺序为准。
以冒泡排序为例:
#include<stdio.h>
int main(){
int a[6]={6,5,4,3,2,1};
int i,t;
for(i=1;i<6;i++){
for(t=0;t<6-i;t++){
int tmp;
if(a[t]>a[t+1]) {
tmp=a[t];
a[t]=a[t+1];
a[t+1]=tmp;
}
}
}
for(i=0;i<6;i++){
printf("%d",a[i]);
}
}
时间复杂度:o(n^2)
空间复杂度:o(1)
九、手脑并用
请实现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);
}
补全如下:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
char* convertAndMerge(char words[2][20]){
char *str=malloc(sizeof(char)*40);//为储存答案的数组申请空间
strcmp(str,words[0][20]);//复制文本
strcat(str,words[1][20]);//拼接追加函数
int i;
int len=strlen(str);
for(i=0;i<len;i++){//转换大小写
if(str[i]<=90) str[i]+=32;
else str[i]-=32;
}
}
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);
}
Welcome to Xiyou
Linux Group 2022
十、给你我的指针,访问我的心声
程序的输出有点奇怪,请尝试解释一下程序的输出吧。
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]);
}
}
}
打印结果:
0 1 2 3 4 25 26 27 28 29 45 46 47 48 49 60 61 62 63 64 70 71 72 73 74
理解如下:
#include<stdio.h>
int main(int argc, char **argv) {
int arr[5][5];
int a = 0;
for (int i = 0; i < 5; i++) {
int *temp = *(arr + i);//将第i行的地址赋值给temp
for (; temp < arr[5]; temp++) *temp = a++;//解引用temp,将a复制给那个位置的内容
}
for (int i = 0; i < 5; i++) {//打印
for (int j = 0; j < 5; j++) {
printf("%d\t", arr[i][j]);
}
}
}
十一、奇怪的参数
你了解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 = 1
/home/zyc/桌面/test
不会出现死循环,因为int类型具有上限,flowover后会从-128开始
argc
是一个表示命令行参数的计数的整数变量,它通常用于 main
函数的参数列表,表示传递给程序的命令行参数的数量,包括程序名称本身**。即可用来对命令行输入的参数和程序名计数,值至少为1.**
例如,在命令行中运行程序如下:
myprogram arg1 arg2 arg3
在这个示例中,argc
的值将为 4,表示有4个命令行参数,其中一个是程序的名称(myprogram),另外三个是arg1、arg2和arg3。
通常,argc
和 argv
一起使用,其中 argv
是一个指向字符指针数组的指针,每个指针指向一个命令行参数的字符串。可以使用 argc
来确定有多少命令行参数传递给程序,然后使用 argv
来访问这些参数的具体值。即argv可视为用来确定命令行参数储存地址。
十二、奇怪的字符
程序的输出有点奇怪,请尝试解释一下程序的输出吧。
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;//获得data1的地址赋值给指针a
char *b = (char *)data2;//获得data2的地址赋值给指针b
char buf[1024];
strcpy(buf, a);
strcat(buf, b);
printf("%s \n", buf);
}
Welcome to Xiyou Linux Group 2022//输出
此题涉及到大端法和小端法
在解释之前要先了解一个词叫做字节序,即多个字节在内存中储存的顺序,而在计算机中字节的储存可以随着地址的变大而顺序储存,也可以随着地址的变小逆序储存,顺序与逆序便是大小端的区别(数据的最左端是数据的最高位,最右端是数据的最低位。
例如:0x12345678(上一行为地址,下一行为储存的字节)
0x100 | ox101 | 0x102 | ox103 |
---|---|---|---|
12 | 34 | 56 | 78 |
储存这个数据时,随着地址的增加,数据从最高位储存到最低位,这个便是大端法
同理:
ox100 | ox101 | ox102 | ox103 |
---|---|---|---|
78 | 56 | 34 | 12 |
因此根据ascii码表(十六进制)可得Welcome to Xiyou Linux Group 2022
十三、小试宏刀(试试就逝世)
- 请谈谈你对`#define`的理解。
- 请尝试着解释程序的输出。
#define SWAP(a, b, t) t = a; a = b; b = t//数值交换,但是b=a,t=a,a=b,t的值被覆盖
#define SQUARE(a) a *a//平方运算
#define SWAPWHEN(a, b, t, cond) if (cond) SWAP(a, b, t)//以code为条件判断是否进行swap
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);
}
//输出
x = 2, y = 1, tmp = 1
x = 1, y = 2, tmp = 2
x = 2, y = 2, tmp = 2
z = 5, w = 5, tmp = 2
与我而言define宏定义就是将内容的直接替换,#预编译符号可以一次性进行替换不需要重复多次。还有undef撤销宏定义与ifdef判断是否宏定义过。
因此可以得到如下结果
int main() {
int tmp;
int x = 1;
int y = 2;
int z = 3;
int w = 3;
SWAP(x, y, tmp);//tmp=1,x=2,y=1
printf("x = %d, y = %d, tmp = %d\n", x, y, tmp);
if (x>y) SWAP(x, y, tmp);//tmp=2,x=1,y=2
printf("x = %d, y = %d, tmp = %d\n", x, y, tmp);
SWAPWHEN(x, y, tmp, SQUARE(1 + 2 + z++ + ++w) == 100);//1+2+3+4*1+2+4+5=22(整体替换后内部不带括号),其后跟随tmp=1;被视为if语句内容被逃过,于是只执行x=y=2,y=tmp=2,故a=2,y=2,tmp=2,z=5,w=5
printf("x = %d, y = %d\n", x, y, tmp);
printf("z = %d, w = %d, tmp = %d\n", z, w, tmp);//z=5,w=5
}
十四、GNU/Linux命令 (选做)
你知道以下命令的含义和用法吗:
- `ls`
- `rm`
- `whoami`
ls:即list,展示指定目录下的文件与文件夹 e.g:ls -l -R /home/peidachang
rm:即remove,删除指定文件或者文件夹e.g:rm f(文件)/ rf(文件夹) xxx(不可恢复)
whoami:即 who am i,显示用户自身名称,不会检视权限,需要id/groups