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

西邮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";
    int a = sizeof(s);
    int b = strlen(s);
    printf("%d %d\n", a, b);
}
  • strlen()包含于<string,h>中,一般用于返回字符串长度,是一个函数;而sizeof()是一个运算符,括号中可以加入变量类型或者变量,返回该变量类型所占字节大小。若加入数组名,则返回数组所占字节数,即数组长度*数组元素所占空间的大小。
  • 对于本函数来说,sizeof返回15,strlen返回10(sizeof全部读取,strlen不读取特殊符号和空字符,且读到空字符就停止)。

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

test1test2都含有:1个short、1个int、1个double,那么sizeof(t1)sizeof(t2)是否相等呢?这是为什么呢?

struct test1 {
    int a;//0-3
    short b;//4-5
    double c;//8-15
};
struct test2 {
    short b;//0-1
    int a;//4-7
    double c;//8-15
};
int main(void) {
    struct test1 t1;
    struct test2 t2;
    printf("sizeof(t1): %d\n", sizeof(t1));
    printf("sizeof(t2): %d\n", sizeof(t2));
}
  • 内存占用如图所示,相同。
  • 每个元素的对齐数为编译器默认对齐数与该成员大小的较小值(在Linux系统中,并没有一个默认的全局字节对齐设置,字节对齐通常是由编程语言、编译器或特定的数据结构来定义和控制的。字节对齐是指数据在内存中存储时按照一定的规则对齐到内存地址的倍数。)。
  • 整个结构体的对齐数为最大对齐数的整数倍。

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

补全后的函数如下

#include<stdio.h>
#include <stdlib.h>
void func (int**);
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);
}
void func (int**arr)
{
  for (int i = 0; i < 10; i++) 
    {
        for (int j = 0; j < 13; j++) 
        {
            printf("%d",*((arr+j)+i));
        }
    }
    return 0;
}
  • 如图,在func函数中输入二维数组arr,然后使用两个for循环来打印数组。

  • 函数中使用了指针表示法来表示数组。

  • rang()是一个随机赋予数字的函数,stdlib.h头文件中有宏#define RAND_MAX 0x7fff,这表示rand产生一个0-0x7fff的随机数,即最大是32767的一个数。

  • 由于arr为二维数组,所以使用二级指针。

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
  • 传值是将参数生成一个副本传递,对其做任意修改不会影响原值。
  • 传址是将内存地址传递,对其做任意修改会对原始数据造成影响,因为读的是相同的地址。
  • 在该函数中,现在main函数中定义两个变量,然后再for循环中定义一个静态局部变量a=5,然后打印该值,然后将ver的值赋给a,在func1函数中递增一次ver的值后打印出来,再定义一个局部变量ver=7(当局部变量与静态变量名字相同时,局部变量会隐藏全局变量)然后将ver的指针传入func2之后改变其值为1234并打印出来,然后将指针pr的值改变一次,再打印全局变量ver的值。
  • 最后打印的a值是main函数局部变量a,ver同理。

5. 套娃真好玩!

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

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

sum函数使用自身递归,每次输入一个数后判断参数是否为0,若为0,则返回0;若不为0.则将n-1作为参数传递给下一级递归,并返回下一级递归的返回值加本级的参数。

由此构成了一个从n到0的加法。

6. 算不对的算术

#include <stdio.h>
int main()
{
    short a = -2;//a的补码为1111 1111 1111 1110
    unsigned int b = 1;//b的补码为11111111 11111111 11111111 11111111
    b += a; 
    int c = -1;//c补码11111111 111111111 11111111 11110000
    unsigned short d = c * 256; 
    c <<= 4;                    
    int e = 2;
    e = ~e | 6;//e补码为11111111 11111111 11111111 11111111
    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);
}

%x输出十六进制数,%hx 输出十六进制short类型,%hhx输出十六进制char类型。

最终结果为a=0xfffe b=0xffffffff d=0x2022 e=0xffffffff c=Oxf0

7. 指针和数组的恩怨情仇

#include <stdio.h>>
int main(void)
{
    int a[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
    int(*b)[3] = a;//将b指向二维数组第一个元素
    ++b;//b指向二维第二个元素
    b[1][1] = 10;//此时将8改为10
    int *ptr = (int *)(&a + 1);//ptr指向一个空的地址
    printf("%d %d %d \n", a[2][1], **(a + 1), *(ptr - 1));//a+1指向二维第二个元素,解引用两次为4,ptr-1指向数组最后一个元素
}

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;
}//const限定参数的值不能改变
void func3(int *const n) {
    *n += 1;
    n = &a;
}//同上
void func4(const int *const n) {
    *n += 1;
    n = &a;
}//const限制参数的值与地址都不能改变
  • int const和const int没有区别
  • const int*和int 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 *p=(char*)calloc(50,sizeof(char));
   for(int i=0;i<strlen(s);i++)
   {
    if(s[i]<='z'&&s[i]>='a')
        p[i]=s[i]-32;
    else if(s[i]<='Z'&&s[i]>='A') 
        p[i]=s[i]+32;
    else p[i]=s[i];
   }
   p[strlen(s)]='\0';
   return p;
}

首先传入一个限定修改其值的char类型指针,然后手动分配空间并传递给p指针,使用for循环,利用ascii表中大小写字母的值与其范围和关系,翻转其大小写值。

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的作用是防止宏替换后的代码不在一块,优先级乱掉。
可以定义一个指针函数来改变原值,参数也是指针。

void Superswap(int *a,int*b)
    {
        int t=*a;
        *a=*b;
        *b=t;
    }

11. 据说有个东西叫参数

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

在linux命令行中,argc值以空格分割的参数的个数,argv是字符串数组,元素为以空格分隔的参数字符串。

argc首先为1,然后打印出argv[0],即文件全路径。
由于argv最后一个元素是一个空指针null,可以这样来遍历

    int i = 1;
    while (argv[i] != NULL) {
        printf("Argument %d: %s\n", i, argv[i]);
        i++;
    }

    return 0;
}

12. 人去楼空

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

int *func1(void) {
    static int n = 0;
    n = 1;
    return &n;//n被声明为静态局部变量,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;
}

静态变量生命周期通常为整个程序,非静态变量生命周期为该块,函数结束时,其内存将会被释放,值不会保留。
静态变量的作用域为整个程序,非静态变量的作用域为该块。
静态变量值存储在静态区,非静态为栈堆区。
未初始化静态变量的话,会包含一些垃圾值,而非静态变量为变为0或空指针。

13. 奇怪的输出

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

输出为Welcome to xiyou Linux group 2021,使用小端序的方式逆向存储十六进制数对应的字符。
将两位数字为一字节来存储并按照ascii打印出来,知道遇到\0空字符停止。

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

首先创建和修改源代码,然后进行预处理,在进行编译,转化为机器语言,在进行汇编,然后链接,将源代码编译出来的各个块组合到一起,生成可执行文件。

15. (选做) 堆和栈

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

栈用来存储局部变量,内存地址由高位到低位。

堆是手动分配的内存空间,内存地址由低到高。

16. (选做) 多文件

一个程序在不使用任何头文件的情况下,如何使用另一个文件中的函数。
多文件是将一个程序分为多个源代码文件,可能含有源代码,头文件,编译并链接在一起。
若想在一个文件中引用另一个文件中的函数,只需在代码开头进行函数原型声明,并在终端上编译和链接两个文件。

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

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

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

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

我们在FZ103等你!

使用touch命令来创建一个空文件,使用mkdir命令来创建文件夹。
访问时间用ls -lu加文件名来查看,修改时间用ls -l加文件名来查看。
文件的创建时间不会存储在文件的元数据中,因此无法直接查看,ls命令等工具也无法提供文件的创建时间信息。
ls命令的列,第一列为文件类型,d表示目录文件,-表示普通文件。第二列为文件权限列,第三列为文件修改或访问时间,第四列为硬链接数或子目录数。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值