linux 2021

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

输出结果:16 12

  • sizeof是一个能计算输入字符串总长度的操作符(算\0)

  • strlen是一个库函数,计算\0之前的字符串长度

    结果为16、12是因为strlen遇到\0就会停下,而sizeof会一直读到最后的\0。

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

输出结果:16 16

考点:内存对齐:数据成员对齐:每个数据成员都会按照其自身类型的大小,从其自身大小的整数倍内存地址开始存储。例如,如果一个结构体中有char类型的成员,那么该成员将紧随在前面的成员之后存储,而且其存储地址是char类型大小的整数倍。
结构体成员对齐:如果结构体中包含了其他结构体类型的成员,那么这些结构体成员的存储位置将从其内部成员最大值大小的整数倍地址处开始存储。例如,如果一个结构体中有一个包含int类型成员的结构体成员,那么该结构体成员的存储位置将是int类型大小的整数倍。
结构体总大小对齐:结构体的总大小必须是其内部最大成员大小的整数倍。如果结构体的总大小不是其内部最大成员大小的整数倍,那么编译器会插入填充字节,使得结构体的总大小达到最大成员大小的整数倍。

一般情况下,类型的顺序不同,其结构体大小也会有所差别,但这道题里的计算结果是相同的,都为16;

test1int先申请4个字节,short再申请4个字节;剩下的2个字节放不下double类型,对齐数为8,此时刚好是8的倍数,所以再申请8个字节,4+4+8=16,16刚好是8的倍数,结果为16;

test2short先申请4个字节,int又申请4个字节,最后double申请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);
}

补全部分的代码:

#include <stdio.h>
void func(int arr[10][13])
{
    for(int i=0;i<10;i++)//遍历二维数组
    {
        for(int j=0;j<13;j++)
        {
            printf("%d ",arr[i][j]);//打印
        }
        printf("\n");
    }
}

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

  • 传值当一个变量被传给另一个变量时,是将该变量的值复制到新变量中,在函数内部对参数进行修改不会影响到原始变量
  • 传址当一个变量的地址被传递给另一个变量时,是将改变量的内存地址复制到新变量中,在函数内部对参数所指向的内存进行修改会影响到原始变量
  • 全局变量:在程序运行期间一直存在,进程开始时创建,进程结束时销毁
  • 局部变量:进入函数时创建,退出函数时销毁
  • static修饰的全局变量:生命周期和全局变量相同,但作用域被限制在定义文件内
  • static修饰的局部变量:生命周期和全局变量相同,但作用域被限制在函数内

5. 套娃真好玩!

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

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

利用了函数递归

第一次执行后的结果为:sum(99)+100,

第二次执行后结果为;sum(98)+99+100…

一直执行到最后一次:sum(0)+1+2+…+100=0+1+2+…+100=5050。

6. 算不对的算术

void func(void) {
    short a = -2;//a=1000 0000 0000 0010--1111 1111 1111 1101--1111 1111 1111 1110
    unsigned int b = 1;//b=0000 0000 0000 0001
    b += a;//b=1111 1111 1111 1111 1111 1111 1111 1111
    int c =-1;//c=1000 0000 0000 0000 0000 0000 0000 0001--1111 1111 1111 1111 1111 1111 1111 1110--1111 1111 1111 1111 1111 1111 1111 1111
    unsigned short d = c * 256;//d=1111 1111 0000 0000
    c <<= 4;//c=1111 1111 1111 1111 1111 1111 1111 0000
    int e = 2;//e=0000 0000 0000 0000 0000 0000 0000 0010
    e = ~e | 6;//e=1111 1111 1111 1111 1111 1111 1111 1101|0000 0000 0000 0000 0000 0000 0000 0110=1111 1111 1111 1111 1111 1111 1111 1111 
    d = (d & 0xff) + 0x2022;//d=1111 1111 0000 0000 & 0000 0000 1111 1111 + 0x2022=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);//(signed char)c=1111 0000
}

输出结果:
a=0xfffe b=0xffffffff d=0x2022 e=0xffffffff
c=0xf0(具体解析看上边代码中的注释)

应先根据类型写出对应的二进制编码,将原码转化为反码再转化为补码,最后转化成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

int(*b)[3]=a定义了一个指向一维数组的数组指针,++b之后指向a第二行的首元素,b[1][1]=10即将a第三行第二个元素赋值为10,所以a[2][1]=10;

**(a+1)中的a代表第一行所有元素,a+1跳过第一行指向第二行首元素,即a[1][0],为4;

&a代表整个数组的地址,&a+1指向整个数组后,*(ptr-1)即数组最后一个元素,为9。

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;
}
  • const intint const没有区别,都是一个不能被修改的整形变量
  • const int*int const*没有区别,都是指针指向的值不能被改变
  • func2,func3,func4中的n都被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 <string.h>
#include <stdlib.h>
char *convert(const char *s)
{
  int len=strlen(s);
  char*resuit=(char*)malloc((len+1)*sizeof(char));//分配空间
  for(int i=0;i<len;i++)
  {
    if(s[i]>='A'&&s[i]<='Z')
    {
        result[i]=s[i]+'a'-'A';
    }
    else if(s[i]>='a'&&s[i]<='z')
    {
        result[i]=s[i]-'a'+'A';
    }
  }
  return result;
}
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;
}
  • swap3错误,形参会开辟新的内存,交换形参不能改变实参
  • swap1优点:可以处理任意类型的变量(只要它们支持赋值操作);缺点:若变量类型不支持赋值操作,函数将无法编译
  • swap2优点:简单易懂;缺点:可能会增加程序的空间复杂度
    - do {......}while(0)可以提供一个占位符,使代码结构更加清晰,易于理解
  • 还可以通过指针交换,代码如下:
void swap4(int*a,int*b)
{
    int temp=*a;
    *a=*b;
    *b=tmp;
}

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:传给main函数的参数个数
  • argv:指向字符串的数组指针,每个字符串都是一个命令行参数,argv[0]是程序名
  • 若不使用argc,可以用sizeof(argv)/sizeof(argv[0])来计算数组argv的长度,代码如下:
int main(char*argv[])
{
    printf("argc=%d\n",sizeof(argv)/sizeof(argv[0]));
    for(int i=1;argv[i]!=NULL;i++)
    {
        printf("%s\n",argv[i]);
    }
    return 0;
}

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

func1func3有误

  • func1n被static修饰,是静态变量,不能被修改
  • fun3中定义的n是局部变量,出了作用域会销毁,所以会造成野指针
  • 静态变量:当程序执行完毕后,静态变量的值会被保留,下次调用该函数时,静态变量的值会保持不变
  • 普通变量:每次调用函数时,普通变量都会被重新初始化

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

计算机采取小端存储(低字节放在低地址,高字节放在高地址)的方式,将字符串存在date数组里,最后进行强制类型转换并输出

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

(1)预处理:进行文本操作,例如加载头文件,宏替换和条件编译等

(2)编译:将预处理后的文件通过编译器转换为目标文件

(3)汇编:将编译器生成的目标文件转换为汇编语言

(4)链接:将多个目标文件以及所需的库函数链接在一起,生成一个可执行文件

15. (选做) 堆和栈

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

  • 空间分配区别:堆空间一般由程序员分配释放,而栈空间则由操作系统(编译器)自动分配释放。
  • 数据结构区别:栈是一种先进后出的数据结构,而堆是一块连续的内存区域。
  • 管理方式不同:由于栈由操作系统自动分配释放,无需我们手动控制,而堆的申请和释放工作由程序员控制,这容易导致内存泄漏。

16. (选做) 多文件

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

(1)将另一个文件的内容复制到当前文件中。

(2) 在当前文件中添加一个函数声明,该函数与要调用的函数具有相同的名称和参数列表。(exturn)

(3)在当前文件中实现该函数。

(4)在需要调用该函数的地方,使用函数名加上括号来调用它。

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

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

  • 第一列:文件的类型和权限

    第二列:文件的硬链接

    第三列:文件的所有者

    第四列:文件所属的组

    第五列:文件的大小

    第六列:文件的最后修改时间

    第七列:文件的名称

  • ls -l --time=access,modification,change

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值