#2020面试试题题解
1. 请试着解释其输出。
int main(int argc , char *argv[]) {
unsigned char a = 255;
char ch = 128;
a -= ch;
printf("a = %d ch = %d\n", a, ch);
}
- solution
- 运行结果:a = 127 ch = -128
- 分析:
- 利用limits.h头文件,可以计算出各个数据类型的范围:
- UCHAR_MAX=255,CHAR_MAX=127
- 而超出的部分便从最小值再依次增加
2. 下面代码的运行输出结果是什么,并说说你的理解。
int main(int argc, char *argv[]) {
char *str = "Xi You Linux Group 20";
printf("%d\n", printf(str));
return 0;
}
solution
- 运行结果:Xi You Linux Group 2021
- 这道题是对于printf的返回值与输出顺序的考察:
- ①返回值:若成功则返回输出的字符数,输出出错则返回负值
- ②输出顺序:printf函数从左往右读取,然后将先读取放到栈底,最后读取的放在栈顶,处理时候是从栈顶开始的.所我们看见的结果是,从右边开始处理的.
3. 这段代码的输出结果是什么?为什么会出现这样的结果?
int i = 2;
void func() {
if(i != 0) {
static int m = 0;
int n = 0;
n++;
m++;
printf("m = %d, n = %d\n", m, n);
i--;
func();
} else {
return;
}
}
int main(int argc, char *argv[]) {
func();
return 0;
}
solution
- 运行结果:
- m = 1, n = 1
- m = 2, n = 1
- ①全局变量 :
- 存储位置 : 存放在静态存储区中。因此他们的生存周期是固定的,存在于程序的整个运行过程中。
- 全局变量的作用范围 : 一般是从定义位置开始到本程序文件的末尾。在此作用域内,全局变量可以为程序中各个函数所引用。
- ②局部变量 :
- 局部变量的有效范围是局限于函数内部的,形参也是局部变量局部变量的改变无法影响到主调函数
4. 下面程序会出现什么结果?为什么会出现这样的结果?
int main(int argc, char * argv[]) {
char ch = 'A';
int i = 65;
unsigned int f = 33554433;
*(int *)&f >>= 24;
*(int *)&f = *(int *)&f + '?';
printf("ch = %c i = %c f = %c\n", ch, i, *(int *)&f);
return 0;
}
solution
- 运行结果:ch = A,i = A,f = A
- 分析:
- ①强制类型转换:
- unsigned 为4字节,int*为8字节
- 对于*(int *)&f
- &f是f的地址
- (int *)&f使其强制转换为成指向int 类型数据的指针
- *便是求解该指针所指向对象的值
- ②位运算:
- f>>=24
- 在f的二进制代码下,左移24位,高位补0,低位去掉
- 最后得到f=2,?的ASCII为63,i的ASCII为65
5. 下面代码的运行输出结果是什么,并说说你的理解。
int main(int argc, char *argv[]) {
int a[2][2];
printf("&a = %p\t&a[0] = %p\t&a[0][0] = %p\n", &a, &a[0], &a[0][0]);
printf("&a+1 = %p\t&a[0]+1 = %p\t&a[0][0]+1= %p\n", &a+1, &a[0]+1, &a[0][0]+1);
return 0;
}
solution
- 运行结果:&a = 0x7fffffffdcd0 ,&a[0] = 0x7fffffffdcd0
- &a[0][0] = 0x7fffffffdcd0 , &a+1 = 0x7fffffffdce0
- &a[0]+1 = 0x7fffffffdcd8 , &a[0][0]+1= 0x7fffffffdcd4
分析:
数组名 | 对应地址 |
---|---|
arr[0] | &arr[0][0] |
arr[1] | &arr[1][0] |
…… | …… |
arr[4] | &arr[4][0] |
其次的有关于指针与数组名的运算
- 进行+,-的运算,每次移动所指向对象所占的字节乘以+与-的大小的距离
- 指针可以进行单目++,–运算
- 数组名不可以进行单目运算,否则首地址位置一变,所指向对象就变了
6. 下列程序的功能是什么?有什么问题,你能找出问题并解决它吗?
int* get_array() {
int array[1121];
for (int i = 0; i < sizeof(array) / sizeof(int); i++) {
array[i] = i;
}
return array;
}
int main(int argc, char *argv[]) {
int *p = get_array();
}
solution
- 问题:函数返回了局部变量的地址
- 解决:
- array定义为全局变量
7. 下面代码的运行输出结果是什么,并说说你的理解。
int main(int argc, char *argv[]) {
char str[] = "XiyouLinuxGroup";
char *p = str;
char x[] = "XiyouLinuxGroup\t\106F\bamily";
printf("%zu %zu %zu %zu\n", sizeof(str), sizeof(p), sizeof(x), strlen(x));
return 0;
}
solution
- 运行结果:16 8 25 24
- 分析:
- sizeof与strlen函数
- 共同点:都是以字节为单位返回大小
- 区别 :strlen是函数,而sizeof是算符
- strlen:返回字符串的大小,所以以\0为截止
- sizeof:返回对象所占内存的大小
- \t(水平制表符),\b(退格),\106(ASCII为106的字符)
8. 如下程序,根据打印结果,你有什么思考?
int add(int *x, int y) {
return *x = (*x^y) + ((*x&y)<<1);
}
int a;
int main(int argc, char *argv[]) {
int b = 2020;
if(add(&b, 1) || add(&a, 1)) {
printf("XiyouLinuxGroup%d\n", b);
printf("Waiting for y%du!\n", a);
}
if(add(&b, 1) && a++) {
printf("XiyouLinuxGroup%d\n", b);
printf("Waiting for y%du!\n", a);
}
return 0;
}
solution
- 运行结果:
- XiyouLinuxGroup2021
- Waiting for y0u!
- 分析:
- a为全局变量,若未初始化,则自动赋0
- 主要考察位运算:
符号 | 描述 | 运算规则 | 实例 |
---|---|---|---|
& | 与 | 两个都为1,结果为1 | 0001 & 0001 = 1 |
| | 或 | 两个位都为0时,结果才为0。 | 0000|0001=0001 |
^ | 异或 | 两个位相同为0,相异为1 | 0001 ∧ 0001 = 0000 |
~ | 取反 | 0变1,1变0。 | ∼ 0 = 1 , ∼ 1 = 0 |
<< | 左移 | 各二进位全部左移若干位,高位丢弃,低位补0。 | 0001 < < k = 0100 |
>> | 右移 | 各二进位全部右移若干位,对无符号数,高位补0,有符号数,右移补1 11。 | 0100 > > k = 0001 |
9. 在下段程序中,我们可以通过第一步打印出a
的地址,假如在你的机器上面打印结果是0x7ffd737c6db4
;我们在第二步用scanf
函数将这个地址值输入变量c
中;第三步,随机输入一个数字,请问最终输出了什么结果,你知道其中的原理吗?
void func() {
int a = 2020;
unsigned long c;
printf("%p\n", &a);
printf("我们想要修改的地址:");
scanf("%lx", &c);
printf("请随便输入一个数字:");
scanf("%d", (int *)c);
printf("a = %d\n", a);
}
- solution
- 分析
- 原理:人工指针!!!
- 第一步得到了a的地址
- 第二步把a的地址赋值给c,那么c的值便是a的地址
- 第三步,先进行强制类型转换,使数据类型相匹配,然后把输入的值存储到a的地址上去
- 结果a的值变为了输入的值
10. 请问一个C语言程序从源代码到可执行文件中间会进行哪些过程,你能简单描述一下每个环节都做了什么事情吗?
- 1.预处理
- 预处理负责对源代码进行文本处理。主要处理代码中以字符#开头的命令。
- 有以下几个处理的地方:
- (一)解析所有的条件预处理指令。
- (二)解析定义的宏,将代码中使用的宏进行替换。
- (三)删除注释
- (四)解析 #include,将引入的头文件拷贝到当前命令位置
- 完成这些后,以从c为后缀的文本文件生成了以.i为后缀的文本文件。
- 2.编译
- 由.i的文本文件生成.s的文本文件。此文件为以汇编语句完成的代码
- 3.汇编
- 经过汇编阶段把汇编语句转换为机器指令,由.s的文本文件生成.o的二进制文件。
- 4.链接
- 链接器将所有二进制的目标文件和系统组件组合成一个可执行文件(.exe)
- 汇编结束后每个源文件都会生成一个.o文件,这里的目标文件就是这些.o文件
11. 请解释一下这行代码做了什么?
puts((char*)(int const[]){
0X6F796958,0X6E694C75,0X72477875,
0X3270756F,0X313230,0X00000A
});
solution
- 运行结果:XiyouLinuxGroup2021
- 分析:
- ①int const []是一个指向{……}的指针
- ②强制类型转换为(char*)型
- ③puts(地址)为输出函数
- ④
对于一段16进制的解析:
对于一个,如(0X6F96958)16进制数,在计算机中以二进制来储存:0000 0110 1111 1001 0110 1001 0101 1000
二进制代码 | ASCII编码 | 字符 |
---|---|---|
00000110 | 111 | o |
11111001 | 121 | y |
01101001 | 105 | i |
01011000 | 88 | X |
12. 请随机输入一串字符串,你能解释一下输出结果吗?
int main(int argc, char *argv[]) {
char str[1121];
int key;
char t;
fgets(str, 1121, stdin);
for(int i = 0; i < strlen(str) - 1; i++) {
key = i;
for(int j = i + 1; j < strlen(str); j++) {
if(str[key] > str[j]) {
key = j;
}
}
t = str[key];
str[key] = str[i];
str[i] = t;
}
puts(str);
return 0;
}
solution
- ①fgets为字符串输入函数,输入一个字符串存到str
- ②两个for循环,依次比较第i与i+1的大小,然后选出asci码最小字符所对应的数组下标
- ③把最小的字符与倒数第二个字符交换
- ④得到新的数组str
13. 用循环和递归求Fibonacci
数列,你觉得这两种方式那种更好?说说你的看法。如果让你求Fibonacci
数列的第100项,你觉得还可以用常规的方法求解吗?请试着求出前100项的值(tip大数运算)。
#include <stdio.h>
int fib(int n){
if (n == 1){
return 1;
}
if (n == 2){
return 1;
}
return fib(n - 1) + fib(n - 2);
}
int main()
{
int n = 0;
printf("请输入要求第几个数字:");
scanf("%d", &n);
printf("%d\n", fib(n));
return 0;
}
- 由于int为4字节,即使用long long 100位也会超出范围
- 所以此时应该用高精度运算,即创建一个数组,来模拟加法运算
14. Linux 实操题
请通过命令创建一个目录,在该目录中创建几个后缀为
.Linux
的文件,然后通过命令查询这几个文件的基本属性信息(如文件大小,文件创建时间等),之后使用命令查看该目录下文件名含有“.Linux
”的文件的数量(不包括子目录下的文件),把得到的数字写入到一个文件中,最后删除此目录。
- 创建目录:mkdir
- 创建文件:touch 1.lunxu
- 查看命令:ls
- 删除:rm