西邮Linux兴趣小组2021纳新面试题

文章讨论了sizeof和strlen这两个在C语言中的函数,比较了它们对不同类型参数的不同结果,并解释了结构体内存布局和函数参数传递的原理。还涉及了预处理、编译、链接过程以及C语言中的内存区域(栈与堆)和文件操作等知识点。
摘要由CSDN通过智能技术生成

感谢 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";
    int a = sizeof(s);
    int b = strlen(s);
    printf("%d %d\n", a, b);
}

sizeof是运算符,strlen是函数,sizeof接收的参数是地址,streln接收的参数是字符串。sizeof返回目标所占空间字节数,streln返回字符串的长度,不包括‘/0’。
a的值为16,b的值为12

2. 箱子的大小和装入物品的顺序有关

test1test2都含有: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));
}

sizeof(t1)和sizeof(t2)的值相等,都是16,但是存储方式不同。
t1

0
int1
int2
int3
int4
short5
short6
7
8
double9
double10
double11
double12
double13
double14
double15
double16

t2

0
short1
short2
3
4
int5
int6
int7
int8
double9
double10
double11
double12
double13
double14
double15
double16

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);
}

二维数组作为函数参数传递的三种方法

  • 1.将形参声明为指针的指针
  • 2.将形参声明为数组的指针
  • 3.将形参声明为第二维长度给出的数组

实现如下

void fun( int**arr ){
	int i ,j;
	for( i = 0 ; i < 10 ; i++ ){
		for( j = 0 ; j < 13 ; j++ ){
			printf( "%d" , a[i][j] );
		}
	}
}

4.就不能换个变量名吗?

  • 请结合下面的程序,简要谈谈传值传址的区别。
  • 简要谈谈你对C语言中变量的生命周期的认识。
int ver = 123;
void func1(int ver) {
    ver++;
    printf("ver = %d\n", ver);
}
void func2(int *pr) {
    *pr = 1234;
    printf("*pr = %d\n", *pr);
    pr = 5678;
    printf("ver = %d\n", ver);
}
int main() {
    int a = 0;
    int ver = 1025;
    for (int a = 3; a < 4; a++) {
        static int a = 5;
        printf("a = %d\n", a);
        a = ver;
        func1(ver);
        int ver = 7;
        printf("ver = %d\n", ver);
        func2(&ver);
    }
    printf("a = %d\tver = %d\n", a, ver);
}

输出如下

  • a = 5
  • ver = 1026
  • ver = 7
  • *pr = 1234
  • ver = 123
  • a = 0 ver = 1025
    传值是将值拷贝一份,传过去得到的只是副本,传过去之后就和原来的值没有联系了,传址则使以后的操作不是对副本操作,而是对原来值的操作。

5. 套娃真好玩!

请说明下面的程序是如何完成求和的?

unsigned sum(unsigned n) { return n ? sum(n - 1) + n : 0; }
int main(void) { printf("%u\n", sum(100)); }

该函数利用函数递归条件语句完成了求和。
在sum函数里return了一个条件语句,在该条件语句中n的值在减为零之前都会不断调用自己,即为“递”。而在n的值到零之后,函数会一级一级把值返回,即为“归”最后返回和值。

6. 算不对的算术

void func(void) {
    short a = -2;
    unsigned int b = 1;
    b += a;
    int c = -1;
    unsigned short d = c * 256;
    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);
}

函数输出

a=0xfffe b=0xffffffff d=0x2022 e=0xffffffff
c=Oxf0

程序运算过程

short a = -2;//a的十六进制为fffe
    unsigned int b = 1;
    b += a;//b=-1,十六进制为ffffffff
    int c = -1;
    unsigned short d = c * 256;
    c <<= 4;//c变为-16,即f0
    int e = 2;
    e = ~e | 6;//对e按位取反再或运算,e=1111 1111 1111 1111 1111 1111 1111 1111,即ffffffff
    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);//c被截断为f0

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));
}

数组指针b指向a数组,++b之后,b[1][1]相当于a[2][1],所以a[2][1]被改为10.
对数组名a直接+1后相当于a[1][0]的地址,再解引用两次,得到4
&a在数值上虽然等于数组首元素地址,但是它的含义是整个数组的空间,所以(&a+1)的值是整个数组结束后的下一段地址。所以ptr减一后就回到了数组最后一个元素的地址。

8. 移形换位之术

下面有abc三个变量和4个相似的函数。

  • 你能说出使用这三个变量的值或地址作为参数分别调用这5个函数,在语法上是否正确吗?
  • 请找出下面的代码中的错误。
  • const intint 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;
    n = &a;
}
void func3(int *const n) {
    *n += 1;
    n = &a;
}
void func4(const int *const n) {
    *n += 1;
    n = &a;
}

func2,3,4都有语法错误。

const int 和 int const 是一样的,声明不可被改变的整型
const int和int const也是一样的,都声明指向const的指针,即无法修改指针所指向的值。

  • func2中修改了const值
  • func3中修改了const指针指向的地址
  • func4中修改了const值也修改了const指针指向的地址

9. 听说翻转字母大小写不影响英文的阅读?

请编写convert函数用来将作为参数的字符串中的大写字母转换为小写字母,将小写字母转换为大写字母。返回转换完成得到的新字符串。

char *convert(const char *s);
int main(void) {
    char *str = "XiyouLinux Group 2022";
    char *temp = convert(str);
    puts(temp);
}

实现如下

char *convert(const char *s)
{
    char *arr=(char*)malloc(100);
    char*p=arr;
for(int i=0;i<=strlen(s)+1;i++)
{
    if(s[i]>=97&&s[i]<=122)
    arr[i]=s[i]-32;
    else if(s[i]>=65&&s[i]<=90)arr[i]=s[i]+32;
    else arr[i]=s[i];
}
return p;
}

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;
}

第一二种正确,第三种错误。
第一种把临时变量在主函数中声明,第二种,把临时变量在函数中声明,更快捷方便。
第三个函数只传值,不传址,无法真正交换。

do while 在宏定义时使用可以避免错误,宏定义在预处理时会被直接替换,这可能会导致我们的一些语句无法实现我们想要的功效,我们可以使用{}来将其变为复合语句来避免错误,但这样可能出现新的错误,因为我们习惯在swap()后加上分号,而复合语句后的分号会使if语句后的else语句无法相匹配,所以我们使用do while ,dowhile整体是一条语句,后面正好需要加分号,可以避免出错

11. 据说有个东西叫参数

你知道argcargv的含义吗?请解释下面的程序。你能在不使用argc的前提下,完成对argv的遍历吗?

int main(int argc, char *argv[]) {
    printf("argc = %d\n", argc);
    for (int i = 0; i < argc; i++)
        printf("%s\n", argv[i]);
}

*argc是参数个数,argv是参数数组,argc的最小值为1,此时的argv是程序的启动路径/名称。
可以,只需 将测试表达式改为argv!=NULL即可。

12. 人去楼空

这段代码有是否存在错误?谈一谈静态变量与其他变量的异同。

int *func1(void) {
    static int n = 0;
    n = 1;
    return &n;
}
int *func2(void) {
    int *p = (int *)malloc(sizeof(int));
    *p = 3;
    return p;
}
int *func3(void) {
    int n = 4;
    return &n;
}
int main(void) {
    *func1() = 4;
    *func2() = 5;
    *func3() = 6;
}

静态变量存储的内存空间和其他变量不一样,它存储在静态区,所以它的生命周期长。func1和func2函数都是正确的,但是func3函数中返回了一个局部变量的地址,而局部变量的空间在函数调用完就被释放了,所以返回它的地址是没有意义的。

13. 奇怪的输出

int main(void) {
    int data[] = {0x636c6557, 0x20656d6f, 0x78206f74,
                  0x756f7969, 0x6e694c20, 0x67207875,
                  0x70756f72, 0x32303220, 0x00000a31};
    puts((const char*)data);
}

输出为Welcome to Xiyou Linux Group 2022
考察大小端
大端(存储)模式:是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中;

小端(存储)模式:是指数据的低位保存在内存的低地址中,而数据的高位,,保存在内存的高地址中。
x86系统采用小端的模式,所以在int类型被转化为char类型时,从后面截断。比如data[0][0],依次被截断成57,65,6c,63。参照ASCII码表,得出结果正是输出值。

14. 请谈谈对从「C语言文件到可执行文件」的过程的理解

c语言文件到可执行文件需要以下过程

  • 预处理
  • 编译
  • 汇编
  • 链接

15. (选做) 堆和栈

你了解程序中的栈和堆吗?它们在使用上有什么区别呢?请简要说明。

16. (选做) 多文件

一个程序在不使用任何头文件的情况下,如何使用另一个文件中的函数。

17. (选做) GNU/Linux与文件

  • 你知道如何在 GNU/Linux下如何使用命令行创建文件与文件夹吗?
  • 你知道GNU/Linux下的命令ls 的每一列的含义吗?
  • 你知道GNU/Linux下文件的访问时间、修改时间、创建时间如何查看吗?并简单说说他们的区别。

恭喜你做完了整套面试题,快来参加西邮Linux兴趣小组的面试吧!

西邮 Linux兴趣小组面试时间:
2021年10月25日至2021年10月31日晚8点。
听说面试来的早一点更能获得学长学姐的好感哦。

我们在FZ103等你!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

谁的友人A

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值