Shawn摘要

1. 请试着解释其输出。

1
2
3
4
5
6
int main(int argc , char *argv[]) {
unsigned char a = 255;
char ch = 128;
a -= ch;//在进行运算的时候会把ch转换为整数进行计算所以它的值就是128不变
printf("a = %d ch = %d\n", a, ch);//127 -128
}

a的值为127,无符号字符型a为255-128=127,而ch为128超过char的最大范围,输出-128

2. 下面代码的运行输出结果是什么,并说说你的理解。

1
2
3
4
5
int main(int argc, char *argv[]) {
char *str = "Xi You Linux Group 20";
printf("%d\n", printf(str));
return 0;
}//Xi You Linux Group 2021

先打印字符串str:”Xi You Linux Group 20”,然后printf的返回值为21,外层的printf打印21,故最终结果是”Xi You Linux Group 2021”

  • priintf函数的返回值为成功打印的数字或字符;
  • scanf函数的返回值为成功输入的数据个数;

3. 这段代码的输出结果是什么?为什么会出现这样的结果?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int i = 2;
void func() {
if(i != 0) {
static int m = 0;//静态局部变量m只会初始化一次,在func函数结束后仍保留值1用作下一次运算
int n = 0; //局部变量的生命周期从函数创建到函数结束,第二次调用func函数时的值重新初始化为0
n++;
m++;
printf("m = %d, n = %d\n", m, n);
i--;
func();
} else {
return;
}
}
int main(int argc, char *argv[]) {
func(); //m = 1, n = 1
return 0; //m = 2, n = 1
}

4. 下面程序会出现什么结果?为什么会出现这样的结果?

1
2
3
4
5
6
7
8
9
10
int main(int argc, char * argv[]) {
char ch = 'A';
int i = 65; //0000 0010 0000 0000 0000 0000 0000 0001
unsigned int f = 33554433; //33554433=2^25+1右移24位相当于将第26位上的1移到第二位
*(int *)&f >>= 24; //f=2
*(int *)&f = *(int *)&f + '?'; //'?'的ASCII码是63
printf("ch = %c i = %c f = %c\n", ch, i, *(int *)&f);//ch本来就是字符A,i的ASCII码为65也为A,f=2加上 '?'的ASCII码63为65输出A
return 0; //对f取地址得到f的地址并且转换为指针变量,再取此指针指向的地址就是f
}
/* 移位操作都是针对补码进行的*/

关于运算中的类型转换有两个通用的指导原则:

1.为了防止数据精度损失,如果有必要的话,类型总是被提升为较宽的类型

2.所有含有小于整数类型的算术表达式在计算之前其类型被提升为整形数据类型

5. 下面代码的运行输出结果是什么,并说说你的理解。

1
2
3
4
5
6
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;//&a = 0x7fffffffe160 &a[0] = 0x7fffffffe160 &a[0][0] = 0x7fffffffe160
} //&a+1 = 0x7ffffffe170 &a[0]+1 = 0x7fffffffe168 &a[0][0]+1 = 0x7fffffffe164
1
2
3
4
5
6
7
8
9
/* test */
int main(int argc, char *argv[]) {
int a[2][2];
int b[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);
printf("&b = %p\t,&a[1] = %p\t,&a[0][1] = %p\n",&b,&a[1],&a[0][1]);
return 0;
} //&b = 0x7fffffffe170 &a[1] = 0x7fffffffe168 &a[0][1] = 0x7fffffffe164

&a,&a[0],&a[0] [0]地址都相同

&a代表整个数组的地址,&a[0]表示数组第一行的地址,&a[0] [0]表示首元素的地址

&a+1表示整个数组的地址加上一个数组类型大小的地址(2*2 *int),相当于&b

&a[0]+1表示第一行的地址加上一行类型大小的地址(2*int),相当于&a[1]

&a[0] [0]+1表示首元素地址加上一个元素类型大小的地址(int),相当于&a[0] [1]

6. 下列程序的功能是什么?有什么问题,你能找出问题并解决它吗?

1
2
3
4
5
6
7
8
9
10
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();
}

get_array函数原意是想返回指向数组array的指针,从而输出array数组中的值,但是array在函数结束后会自动释放内存而找不到array数组的地址,从而使p成为野指针

修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#define N 1121
int *get_array();
int main()
{
int *p=get_array();
for(int i=0;i<N;i++)
{
printf("%d ",*p);//打印0-1120
p++;
}
return 0;
}

int *get_array()
{
int *array=(int*)calloc(N,sizeof(int));//开辟一段堆空间并让指针array指向它
int *t=array;//此处用另一个指针指向这个指针,从而使两个指针指向同一个位置,从而保存此段内存空间的地址
for(int i=0;i<N;i++)
{
*array=i; //利用指针的移动来改变原本储存在这段空间的值
array++; //(若没有将新分配的内存空间中的值置为0(memset)则其中存储的都是不确定的值)
} //也可以使用calloc在分配内存的时候就初始化为0
return t;
}

7. 下面代码的运行输出结果是什么,并说说你的理解。

1
2
3
4
5
6
7
int main(int argc, char *argv[]) {
char str[] = "XiyouLinuxGroup";
char *p = str;
char x[] = "XiyouLinuxGroup\t\106F\bamily";//16 8 25 24
printf("%zu %zu %zu %zu\n", sizeof(str), sizeof(p), sizeof(x), strlen(x));
return 0;//sizeof计算str字符串的大小包括 '\0',p为指针大小,在64位系统为8,x字符串中 \t,\106的大小为1, \b的大小为1,strlen是计算字符串长度的函数,遇 '\0'结束
}

8. 如下程序,根据打印结果,你有什么思考?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int add(int *x, int y) {
return *x = (*x^y) + ((*x&y)<<1);//每进一次函数a+1,把b+1
}
int a;//全局变量初始化为0
int main(int argc, char *argv[]) {
int b = 2020;
if(add(&b, 1) || add(&a, 1)) {
printf("XiyouLinuxGroup%d\n", b);//2021
printf("Waiting for y%du!\n", a);//0 /* 说明add(&a,1)未执行,逻辑或满足第一个表达式,则计算机直接退出逻辑并返回真值,而不会判断第二个条件,以提高效率
}
if(add(&b, 1) && a++) {//并不会进入if内,逻辑和时a=0,然后再+1
printf("XiyouLinuxGroup%d\n", b);
printf("Waiting for y%du!\n", a);
}
/* test */
printf("b = %d\n",b);//2022
printf("a = %d\n",a);//1
return 0;
}

9. 在下段程序中,我们可以通过第一步打印出a的地址,假如在你的机器上面打印结果是0x7ffd737c6db4;我们在第二步用scanf函数将这个地址值输入变量c中;第三步,随机输入一个数字,请问最终输出了什么结果,你知道其中的原理吗?

10. 请问一个C语言程序从源代码到可执行文件中间会进行哪些过程,你能简单描述一下每个环节都做了什么事情吗?

(1)预处理阶段。预处理器(cpp)根据字符#开头的命令,修改原始的C程序。

(2)编译阶段。将c语言文件从高级语言转为汇编语言。

(3)汇编阶段。将汇编语言转化为二进制语言。

(4)链接阶段。将使用的头文件与本文件链接起来。

11. 请解释一下这行代码做了什么?

1
2
3
4
5
puts((char*)(int const[]){
0X6F796958,0X6E694C75,0X72477875,
0X3270756F,0X313230,0X00000A
});//把16进制数组转换为字符串输出
//XiyouLinuxGroup2021

12. 请随机输入一串字符串,你能解释一下输出结果吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int main(int argc, char *argv[]) {
char str[1121];
int key;
char t;
fgets(str, 1121, stdin);//从键盘获取标准输入读取1121个字符存储到数组str中,返回字符串中第一个字符的地址
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;
}

fgets() 虽然比 gets() 安全,但安全是要付出代价的,代价就是它的使用比 gets() 要麻烦一点,有三个参数。它的功能是从 stream 流中读取 size 个字符存储到字符指针变量 s 所指向的内存空间。它的返回值是一个指针,指向字符串中第一个字符的地址。

其中:s 代表要保存到的内存空间的首地址,可以是字符数组名,也可以是指向字符数组的字符指针变量名。size 代表的是读取字符串的长度。stream 表示从何种流中读取,可以是标准输入流 stdin,也可以是文件流,即从某个文件中读取。标准输入流就是输入缓冲区。所以如果是从键盘读取数据的话就是从输入缓冲区中读取数据,即从标准输入流 stdin 中读取数据,所以第三个参数为 stdin。

13. 用循环和递归求Fibonacci数列,你觉得这两种方式那种更好?说说你的看法。如果让你求Fibonacci数列的第100项,你觉得还可以用常规的方法求解吗?请试着求出前100项的值(tip大数运算)。

递归虽然有简洁的优点,但它同时也有显著地缺点。递归由于是函数调用自身,而函数调用是有空间和时间的消耗的:每一次函数调用,都需要在内存栈中分配空间以保存参数、返回地址及临时变量,而且往栈里压入数据和弹出数据都需要时间。

而且除了效率问题之外,递归可能引起 调用栈溢出,因为需要为每一次函数调用在内存栈中分配空间,而每个进程的栈的容量是有限的。当蒂固的层级太多,就会超出栈的容量,导致栈溢出。比如上边的代码,输入40,可以正确返回 12502500,但是输入 5000 就会出错。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1qYdVoar-1668850172001)(/home/shawn/下载/picture/20181211162634168.png)]

如果计算数列的第4个位置上(从0开始)的数(0 1 1 2 3),也就是3,上边的 printf 输出应该是 4 3 2 1 0 1 2 1 0,这是因为计算 F(4) 要计算 F(3) 和 F(2),而计算 F(3) 的时候又要计算 F(2) 和 F(1),所以会有很多重复计算。

14. Linux 实操题

请通过命令创建一个目录,在该目录中创建几个后缀为.Linux的文件,然后通过命令查询这几个文件的基本属性信息(如文件大小,文件创建时间等),之后使用命令查看该目录下文件名含有“.Linux”的文件的数量(不包括子目录下的文件),把得到的数字写入到一个文件中,最后删除此目录。