西邮Linux兴趣小组2021纳新题题解

注:

  • 本题目仅作西邮Linux兴趣小组2021纳新面试题的有限参考。

  • 为节省版面本试题的程序源码中省略了#include指令。

  • 本试题中的程序源码仅用于考察C语言基础,不应当作为C语言代码风格的范例。

  • 题目难度与序号无关。

  • 所有题目均假设编译并运行x86_64 GNU/Linux环境。

  • Copyright © 2021 西邮Linux兴趣小组, All Rights Reserved.
    本试题使用采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。

    一、大小和长度竟然不是一个意思

`sizeof()`和`strlen()`有什么异同之处?

他们对于不同参数的结果有什么不同?请试举例子说明。
int main(void) {
    char s[] = "I love Linux\0\0\0";
    int a = sizeof(s);//15
    int b = strlen(s);//12
    printf("%d %d\n", a, b);
}

1、sizeof 是库里自带的关键字,不需要在预编译string.h,而strlen硬性需要string.h

2、sizeof求的是占用内存的体积大小,strlen求的是字符串的(有效)长度即有多少个字符,到\0为止,因此sizeof可以跟各种数据类型,strlen只能跟char类型。string可被sizeof替换:strlen(arr)=sizeof(arr)/sizeof(arr[0])-n(n为\0个数)

二、箱子的大小和装入物品的顺序有关

`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));
}
//输出均为16,但是若将double置于short和int之间便会变为24

此处考察的是结构体的内存对齐方式

为了提取数据的效率和完整性,结构体的储存会进行内存对齐

即找到结构体中数据类型所占空间最大的成员,其数据类型大小的整数倍为结构体的大小,其他成员数据应当以其数据类型大小为单元进行填入。

e.g:t1

int
int
int
int
short
short
0
0
double
double
double
double
double
double
double
double

故输出16

三、哦,又是函数

想必在高数老师的教导下大家十分熟悉函数这个概念。那么你了解计算机程序设计中的函数吗?请编写一个`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);
}

补全如下:

/*在这里补全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);
}

四、就不能换个变量名吗?

请结合下面的程序,简要谈谈传值和传址的区别。
简要谈谈你对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

传值:是直接将b的值复制一份给a,a的变化不影响b

传址:将b的地址赋值给a,对a解引用后的操作会影响b的值

生命周期

局部变量:若变量在一对{}内被声明,则在{}的程序运行结束是该变量所获得的空间将被释放,此变量将被销毁。

全局变量:若变量在程序顶部声明,则其在程序开始时获得内存,在程序终止时销毁,可跨文件运行。

static修饰后:静态变量只会被初始化一次,函数调用后保持其值,在程序开始时获得内存,在程序终止时销毁释放内存。

五、套娃真好玩

请说明下面的程序是如何完成求和的?
unsigned sum(unsigned n) { return n ? sum(n - 1) + n : 0; }
int main(void) { printf("%u\n", sum(100)); }//从n加到零

这里考的是递归思想

当n-1不为0时函数返回的是sum(n-1)+n,n-1=0时return 0,函数终止

即n+n-1+n-2+…+1,第一次保留+n第二次保留+n-1以此类推。

六、算不对的算术

void func(void) {
    short a = -2;//1000 0000 0000 0010原码
    			 //1111 1111 1111 1110补码
    unsigned int b = 1;//0000 0000 0000 0000 0000 0000 0000 0001
    b += a;//1111 1111 1111 1111=(-1)10 
    int c = -1;//1000 0000 0000 0000 0000 0000 0000 0001原码
    		   //1111 1111 1111 1111 1111 1111 1111 1111补码
    unsigned short d = c * 256;//相当于c<<8,1111 1111 1111 1111 1111 1111 0000 0000,同时只保留最低两个字节
    c <<= 4;//1111 1111 1111 1111 1111 1111 1111 0000
    int e = 2;//0000 0000 0000 0000 0000 0000 0000 0010
    e = ~e | 6;//1111 1111 1111 1111 1111 1111 1111 1101|0110=1111 1111 1111 1111 1111 1111 1111 1111=-1=0xfffffffff
    d = (d & 0xff) + 0x2022;//1111 1111 0000 0000&0000 0000 1111 1111=0,所以相当于直接输出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

因为在内存中数据以补码的形式储存,正数是本身,负数的补码是反码加一,也以补码进行运算,因此以十六进制输出答案如上

~取反,每一位0变1,1变0

<<右移同理左移

|按位或

&按位与

%hx是short类型按十六进制输出

最后将c转换成字符型只有一个字节保留了1111 0000

七、指针和数组的恩怨情仇

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 
int main(void) {
    int a[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
    int(*b)[3] = a;//声明了一个数组指针b,指向一个有三个元素的数组,把数组a的首地址赋值给了b
    ++b;//b跳到了后续三个元素的地址
    b[1][1] = 10;//原来b[1][1]=5,++后=8,又直接赋值了10
    int *ptr = (int *)(&a + 1);//这里将a的下一个数组赋值给了指针ptr
    printf("%d %d %d \n", a[2][1], **(a + 1), *(ptr - 1));
}//a[2][1]的值通过指针b被修改为10,解引用a[1]的地址得到首元素4,ptr是a下一个地址,因此ptr上一个地址是a[2][2]=9

八、移形还位之术

下面有`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;
    n = &a;
}
void func3(int *const n) {
    *n += 1;
    n = &a;
}
void func4(const int *const n) {
    *n += 1;
    n = &a;
}

funco都可以使用,用的是内容

func1也都可以使用,用的是地址

func2第一行出错,常量指针指向的值无法修改

func3第二行出错,指针常量(?)无法修改

func4两行都出错,其地址和指向的都是常量

int const 和const int等效,因为const修饰的是这个int类型,即这个n是一个常态的int类型。

const int *n 修饰的是这个指针指向一个常量,这个常量不可修改但地址可变

int const *n修饰的是指针,这个指针是一个常量,不可修改

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

请编写`convert`函数用来将作为参数的字符串中的大写字母转换为小写字母,将小写字母转换为大写字母。返回转换完成得到的新字符串。
char *convert(const char *s);
int main(void) {
    char *str = "XiyouLinux Group 2022";
    char *temp = convert(str);
    puts(temp);
}

补全函数:

#include<string.h>
#include<stdlib.h>
char *convert(const char *s){
    int i,len;
    len=strlen(s);
    char *arr;
    arr=(char *)malloc(sizeof (char)*len);
    for(i=0;i<len;i++){
        if(s[i]==" ") continue;
        if (s[i]<91&&s[i]>64)
        {
            arr[i]=s[i]+32;
        }
        else if(s[i]<123&&s[i]>96) arr[i]=s[i]-32;
        
    }
    return arr;
}

十、交换礼物的方式

- 请判断下面的三种`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;
}

swap1与swap2正确,swap3有误

swap1通过中间量传递数据,方便快捷简单

swap2在宏定义中创建参数,t用完即销毁,节省空间

swap3函数体在运行时是将实参的值复制给形参,形参运算完应当返回给实参地址

do {…} while(0):在宏定义中能够使用语句块,实现局部作用域,保证函数运行

void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}
//即直接引用实参的地址,交换其地址

十一、听说有个东西叫做参数

你知道`argc`和`argv`的含义吗?请解释下面的程序。你能在不使用`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:访问目录下文件的路径,即文件或者文件夹或者说命令行参数的指针

#include <stdio.h>

int main(int argc, char *argv[]) {
    int i = 0;

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

    return 0;
}//argv中有一个NULL空指针

十二、人去楼空

这段代码有是否存在错误?谈一谈静态变量与其他变量的异同。
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用了static修饰int n,n的空间不会被释放

func2用了malloc也不会主动释放分配好的内存

func3中n是会被释放的,即n没有确定的地址

十三、奇怪的输出

int main(void) {
    int data[] = {0x636c6557, 0x20656d6f, 0x78206f74,
                  0x756f7969, 0x6e694c20, 0x67207875,
                  0x70756f72, 0x32303220, 0x00000a31};
    puts((const char*)data);
}
/*输出*/
Welcome to xiyou Linux group 2021

此处考察的是大小端的储存模式

见22年纳新题第十二题

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

写好的C语言文件会经历预处理、编译、汇编、链接的过程

1、预处理将预编译的内容处理、展开、替换,删除软件,包含其他文件(把人需要看的删掉)

2、编译将检查语法、语义等,将程序语言转换成汇编语言(翻译、检查)

3、汇编将把汇编语言转换成及其能够识别运算的机器语言(翻译)

4、链接将目标文件与库文件链接,确保函数、变量等引用正确,开辟入口(规划道路)

5、生成可执行文件

十五、堆和栈

栈区:系统自动划分、自动分配内存的区域,速度快,只需压入和弹出数据,类似于弹匣,生命周期受函数限制

堆区:程序员手动分配内存,手动释放内存(可防止内存泄露),生命周期无限制,储存动态分配,空间很大,速度慢因为管理复杂

十六、多文件

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

extern声明后直接使用

十七、GNU/Linux与文件

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

1、mdkir创建文件和文件夹,多层嵌套则mkdir -p xxx/xxx/xxx

可以通过使用重定向操作符 > 和echo写入内容

echo "Hello, World!" > my_file.txt

2、文件类型和权限列

硬链接数列(Hard Links)(上头有几个和下头有几个目录)

所有者列(Owner)

所属组列(Owner)

文件大小列(File Size)

修改时间列(Modification Time)

文件名列(File Name)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值