西邮Linux兴趣小组2021纳新面试题题解
感谢 Zhilu 重新录入题目原件。好人一生平安。
注:
- 本题目仅作
西邮Linux兴趣小组
2021纳新面试题的有限参考。- 为节省版面本试题的程序源码中省略了
#include
指令。- 本试题中的程序源码仅用于考察C语言基础,不应当作为C语言代码风格的范例。
- 题目难度与序号无关。
- 所有题目均假设编译并运行
x86_64 GNU/Linux
环境。Copyright © 2021 西邮Linux兴趣小组, All Rights Reserved.
本试题使用采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。
1. 大小和长度竟然不是一个意思
sizeof()
和strlen()
有什么异同之处?他们对于不同参数的结果有什么不同?请试举例子说明。
int main(void) {
char s[] = "I love Linux\0\0\0";//字符数组末尾自动填充\0
int a = sizeof(s);//数组大小为16
int b = strlen(s);//字符串长度为12
printf("%d %d\n", a, b);
}
strlen返回字符串长度(不包括"\0")。
sizeof返回占字节数。
2. 箱子的大小和装入物品的顺序有关
test1
和test2
都含有:1个short
、1个int
、1个double
,那么sizeof(t1)
和sizeof(t2)
是否相等呢?这是为什么呢?
struct test1 {
int a;
short b;
double c;
};
struct test2 {
short b;
int a;
double c;
};
int main(void) {
struct test1 t1;
struct test2 t2;
printf("sizeof (t1) : %d\n", sizeof(t1));
printf("sizeof(t2): %d\n", sizeof(t2));
}
相等,结构体占内存需是内部最大类型的倍数,2(short)+4(int)=6<8,补为8,两个结构体,占内存均为16.
3. 哦,又是函数
想必在高数老师的教导下大家十分熟悉函数这个概念。那么你了解计算机程序设计中的函数吗?请编写一个
func
函数,用来输出二维数组arr
中每个元素的值。
/*在这里补全func函数的定义*/
int main(void) {
int arr[10][13];
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 13; j++) {
arr[i][j] = rand();
}
}
func(arr);
}
rand():产生一个随机数。
int func (int a[10][13]){
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 13; j++) {
printf("%d ",a[i][j]);
}
}
return 0;
}
4.就不能换个变量名吗?
- 请结合下面的程序,简要谈谈
传值
和传址
的区别。- 简要谈谈你对C语言中变量的生命周期的认识。
int ver = 123;//是全局变量,生命周期为整个程序
void func1(int ver) {
ver++;
printf("ver = %d\n", ver);//输出1026
}
void func2(int *pr) {
*pr = 1234;
printf("*pr = %d\n", *pr);//输出1234
pr = 5678;
printf("ver = %d\n", ver);//输出123
}
int main() {
int a = 0;//是局部变量,生命周期为main()
int ver = 1025;//是局部变量,生命周期为main()
for (int a = 3; a < 4; a++) {
static int a = 5;//static变量存放在静态存储区,只执行一次;int a生命周期为for循环
printf("a = %d\n", a);//输出5
a = ver;
func1(ver);
int ver = 7;//生命周期为for循环
printf("ver = %d\n", ver);//输出7
func2(&ver);
}
printf("a = %d\tver = %d\n", a, ver);
}
传值:实际是把实参的值赋值给行参,相当于copy。那么对行参的修改,不会影响实参的值 。(对应func1)
传址: 是传值的一种特殊方式,只是他传递的是地址,不是普通的赋值,那么传地址以后,实参和行参都指向同一个对象,因此对形参的修改会影响到实参。(对应func2)
5. 套娃真好玩!
请说明下面的程序是如何完成求和的?
unsigned sum(unsigned n) {
return n ? sum(n - 1) + n : 0; }
int main(void) {
printf("%u\n", sum(100)); }
写了一个递归函数,当n!=0时,+n再调用sum函数,一直到sum(0),为100+99+……+0
6. 算不对的算术
void func(void) {
short a = -2;
unsigned int b = 1;
b += a;
int c = -1;
unsigned short d = c * 256;//2^8
c <<= 4;
int e = 2;
e = ~e | 6;
d = (d & 0xff) + 0x2022;
printf("a=0x%hx\tb=0x%x\td=0x%hx\te=0x%x\n", a, b, d, e);
printf("c=Ox%hhx\t\n", (signed char)c);
}
位运算:
1111111111111111111111111111110 | 1111111111111111111111111111111 |
---|---|
0000000000000000000000000000001 | d = c * 256;(c<<8)1111111100000000 |
b+=a=1111111111111111111111111111111 | c<<4=1111111111111111111111111110000 |
~e =1111111111111111111111111111101 | 1111111100000000 |
---|---|
0000000000000000000000000000110 | 0xff=11111111 |
e = ~e |6=1111111111111111111111111111110 | d & 0xff=0000000000000000 |
输出:a=0xfffe b=0xffffffff d=0x2022 e=0xffffffff
c=0xf0(强制转换成signed char型 一字节,%hhx)
%x:无符号16进制。
7. 指针和数组的恩怨情仇
int main(void) {
int a[3][3] = {
{
1, 2, 3}, {
4, 5, 6}, {
7, 8, 9}};
int(*b)[3] = a;
++b;
b[1][1] = 10;
int *ptr = (int *)(&a + 1);
printf("%d %d %d \n", a[2][1], **(a + 1), *(ptr - 1));//输出10 4 9
}
b指向数组a,++b,指向a[1][0],b[1][1]为a[2][1]的地址,**(a+1)为a[1][0]的地址
。ptr为三级指针 ,(&a+1)指向a的地址+9,*(ptr-1)地址减1,为a[2][2]
的地址。
8. 移形换位之术
下面有
a
、b
、c
三个变量和4个相似的函数。
- 你能说出使用这三个变量的值或地址作为参数分别调用这5个函数,在语法上是否正确吗?
- 请找出下面的代码中的错误。
const int
和int const
是否有区别?如果有区别,请谈谈他们的区别。//无区别const int *
和int const *
是否有区别?如果有区别,请谈谈他们的区别。//无区别:指针指向的值不可改变(只读变量)
int a = 1;
int const b = 2;
const int c = 3;
void funco(int n) {
n += 1;
n = a;
}
void func1(int *n) {
*n += 1;
n = &a;
}
void func2(const int *n) {
*n += 1;//此行错误,const int *n 指针指向的值不可改变。
n = &a;
}
void func3(int *const n) {
*n += 1;
n = &a;//此行错误,int *const n 指针的地址不可改变
}
void func4(const int *const n) {
*n += 1;//
n = &a;//这两行错误,const int *const n 类型 指针的地址和指向的值均不可改变。
}
第一个函数,都可以调用。第二个函数只有a可以调用。后三个函数均有错误。
const默认作用于其左边的东西,如果左边没东西,则作用于其右边的东西。
9. 听说翻转字母大小写不影响英文的阅读?
请编写
convert
函数用来将作为参数的字符串中的大写字母转换为小写字母,将小写字母转换为大写字母。返回转换完成得到的新字符串。
char *convert(const char *s);
int main(void) {
char *str = "XiyouLinux Group 2022";
char *temp = convert(str);
puts(temp);
}
解:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *convert(const char *s) {
char *a = (char *)malloc(sizeof(char) * 40);
strcpy(a,s);
for (int i = 0; i < 40; i++) {
if (a[i] >= 'A' && a[i] <= 'Z') {
a[i] += 32;
}else{
if (a[i] >= 'a' && a[i] <= 'z') {
a[i] -= 32;
}
}
}
return a;
}
int main(void) {
char *str = "XiyouLinux Group 2022";
char *temp = convert(str);
puts(temp);
}
10. 交换礼物的方式
- 请判断下面的三种
Swap
的正误,分别分析他们的优缺点。- 你知道这里的
do {...} while(0)
的作用吗?- 你还有其他的方式实现
Swap
功能吗?
#define Swap1(a, b, t) \
do {
\
t = a; \
a = b; \
b = t; \
} while (0)
#define Swap2(a, b) \
do {
\
int t = a; \
a = b; \
b = t; \
} while (0)
void Swap3(int a, int b) {
int t = a;
a = b;
b = t;
}
1.Swap3有错误,传值使交换仅在Swap3中进行,对调用函数的实参无影响。
应改为
void Swap3(int *a, int *b) {
int t = *a;
*a = *b;
*b= t;
}
#define 宏定义在预编译时将程序中的标识符替换为后面的语句。(若要换行应在末尾加上 \ )
**优点:**少了函数调用,提高程序运行效率。
缺点:不会检查参数、类型是否合法。
作用:防止语法错误,若宏函数不用 do{}while() 即:
#define Swap2(a, b) \
{ \
int t = a; \
a = b; \
b = t; \
}
int main(int argc, char *argv[]) {
int a=1,b=2,t=0;
Swap2(a, b);
printf("%d %d %d", a,b,t);
}
展开后,为
int main(int argc, char *argv[]) {
int a=1,b=2,t=0;
{
int t = a;
a = b;
b = t;
};//此处语法错误
printf("%d %d %d", a,b,t);
}
11. 据说有个东西叫参数
你知道
argc
和argv
的含义吗?请解释下面的程序。你能在不使用
argc
的前提下,完成对argv
的遍历吗?//不能
int main(int argc, char *argv[]) {
printf("argc = %d\n", argc);//输出参数1,即这个文件
for (int i = 0; i < argc; i++)
printf("%s\n", argv[i]);//输出了包含了程序所在的完整路径。
}
argc 表示传入main函数的参数个数;即这个文件
argv 表示传入main函数的参数序列或指针,并且第一个参数argv[0]一定是程序的名称,并且包含了程序所在的完整路径。
12. 人去楼空
这段代码有是否存在错误?谈一谈静态变量与其他变量的异同。
int *func1(void) {
static int n = 0;
n = 1;
return &n;
}
int *func2(void) {
int *p = (int *)malloc(sizeof(int));//堆内存,不会随函数的结束被释放。程序在运行的时候用 malloc 申请任意内存,动态内存的生存期由程序员自己决定在何时用 free释放内存,使用非常灵活。
*p = 3;
return p;
}
int *func3(void) {
//函数不能通过返回指向栈内存的指针
int n = 4;
return &n;//栈内存,函数调用结束后该局部自动变量被抛弃,这个指针指向一个不再存在的对象,是无意义的。
}
int main(void) {
*func1() = 4;
*func2() = 5;
*func3() = 6;
}
func3错误。
静态(static)变量存放在静态存储区,只执行一次。生命周期是从对象定义到程序结束的。
13. 奇怪的输出
int main(void) {
int data[] = {
0x636c6557, 0x20656d6f, 0x78206f74,
0x756f7969, 0x6e694c20, 0x67207875,
0x70756f72, 0x32303220, 0x00000a31};
puts((const char*)data);
}
大小端:对于一个由2个字节组成的16位整数,在内存中存储这两个字节有两种方法:一种是将低序字节存储在起始地址,这称为小端字节序;另一种方法是将高序字节存储在起始地址,这称为大端字节序。
即:
大端是高字节存放到内存的低地址。
小端是高字节存放到内存的高地址。
操作系统一般为小端。
通讯协议一般为大端。
14. 请谈谈对从「C语言文件到可执行文件」的过程的理解
1.先预处理,然后编译,把c语言代码翻译成汇编语言的代码。(.c->.s)
2.再汇编,将之前编译得到的汇编语言代码翻译为二进制机器码。(.s->.o)
3.最后链接,每个目标文件.o合并成为一个可执行程序。(.o->.out)
15. (选做) 堆和栈
你了解程序中的栈和堆吗?它们在使用上有什么区别呢?请简要说明。
1、栈区(stack): 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。
2、堆区(heap) : 由程序员分配释放。(malloc)
16. (选做) 多文件
一个程序在不使用任何头文件的情况下,如何使用另一个文件中的函数。
extern函数:可以引用另一个文件中的变量,函数。
a.c
#include<stdio.h>
int sum(int a,int b){
int c;
c=a+b;
return c;
}
main.c
#include<stdio.h>
int main(){
extern int sum;
int x=1,y=2,z;
z=sum(x,y);
printf("%d",z);
}
链接
17. (选做) GNU/Linux
与文件
- 你知道如何在
GNU/Linux
下如何使用命令行创建文件与文
件夹吗?- 你知道
GNU/Linux
下的命令ls 的每一列的含义吗?- 你知道
GNU/Linux
下文件的访问时间、修改时间、创建时间如何查看吗?并简单说说他们的区别。
创建文件夹:mkdir
创建文件:touch