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

文章讨论了C语言中的内存概念(如数组大小和长度、结构体内存对齐),函数的作用与变量命名,指针与数组的关系,以及从C语言源文件到可执行文件的过程,还包括堆和栈的区别,多文件编程和GNU/Linux下的文件操作。
摘要由CSDN通过智能技术生成

目录

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

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

3.哦,又是函数

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

 5.套娃真好玩

 6.算不对的算术

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

8.移形换位之术 

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

10.交换礼物的方式  

11. 据说有个东西叫参数

12. 人去楼空

13.奇怪的输出 

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

15.  (选做) 堆和栈

16.(选做) 多文件

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


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

​
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(s)读取总长度不会因为‘\0’而结束,s[]末尾应该有第四个‘\0’,第四个'\0'表示字符串的结束,strlen函数读取到‘\0’就会停止且不包含'\0',所以只考虑了字母和空格,最终打印结果是16  12

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

#include<stdio.h>
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));
    //sizeof(t1): 16
    //sizeof(t2): 16
    printf("sizeof(t2): %d\n", sizeof(t2));
    return 0;
}

涉及结构体的内存对齐原则,结构体中最大数据类型是double所以对齐数是8,总大小得是8的整数倍 所以第一个结构体的大小是4+2(->4)+8=16,第二个结构体的大小是2(->4)+4+8=16.

3.哦,又是函数

/*在这里补全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 arr[10][13])
{
    for(int i = 0;i < 10; i++ )
    {
        for(int j = 0; j < 13; j++)
        {
            printf("%d ",arr[i][j]);
        }
        printf("\n");
    }
}
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()%5;
        }
    }
    func(arr);
    return 0;
}

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

#include<stdio.h>
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);
    return 0;
}
  • 传值:在函数调用时,将实际参数的值复制给形式参数,函数内部对形式参数的修改不会影响到实际参数的值。在函数中对形式参数的修改只是对其副本的修改,不会影响到原始数据.
  • 传址:在函数调用时,将实际参数的地址传递,函数内部通过指针的形式访问实际参数。在函数中修改实际参数。
  • 生命周期指的是变量在程序运行过程中有效的时间段
#include<stdio.h>
int ver = 123;//全局变量整个代码有效
void func1(int ver)
{
    ver++;//1025++
    printf("ver = %d\n",ver);
    //ver = 1026
}
void func2(int *pr)//指针指向ver
{
    *pr = 1234;
    //对所指区域的赋值
    printf("*pr = %d\n",*pr);
    //*pr = 1234
    pr = 5678;//改变地址
    printf("ver = %d\n",ver);
    //全局变量ver = 123
}
int main()
{
    int a = 0;
    int ver = 1025;//重定义,作用域为main函数
    for(int a = 3; a < 4; a++)//对a的重定义出了for循环就销毁
    {
        static int a = 5;//静态,使a的生命周期延长,出循环也有效
        printf("a = %d\n", a);
        //a = 5
        a = ver;//此处er是main函数的1025
        func1(ver);//从此处开始看函数
        int ver = 7;//重定义,本作用域有效
        printf("ver = %d\n",ver);
        //ver = 7
        func2(&ver); //传er的地址
    }
    printf("a = %d\tver = %d\n", a, ver);
    //a = 0    ver = 1025对应main函数的int
    return 0;
}

 5.套娃真好玩

#include<stdio.h>
unsigned sum(unsigned n)
{
    return n ? sum(n-1) + n : 0 ;
}
int main(void)
{
    printf("%u\n", sum(100));
    return 0;
}

 解析在下方代码注释,最终结果是5050.

#include<stdio.h>
unsigned sum(unsigned n)
{
    return n ? sum(n-1) + n : 0 ;
    //a ? b : c;是一个语句判断a是否为真,为真则输出b,否则输出c
    //当n!=0的时候会连续的将n-1传入此函数直到n=0
    //最终结果就是从100+到0为5050
}
int main(void)
{
    printf("%u\n", sum(100));
    return 0;
}

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

 解析在注释,本题考察进制转换。

#include<stdio.h>
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\t\n", a, b);
    //a=0xfffe        b=0xffffffff
    //无符号整形最大值4294967295最小值是0,小于0之后会变成最大值转换为16进制是ffffffff
    printf("d=0x%hx\te=0x%x\n",d,e);
    //d=0x2022        e=0xffffffff
    //将负数短整形转换为无符号短整形并转化为16进制输出   
    printf("c=0x%hhx\t\n",(signed char)c);
    //c=0xf0
}
int main()
{
    func();
    return 0;
}

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

 解析写在代码里。

#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[1][1] = 10;
    int *ptr = (int *)(&a + 1);
    printf("%d %d %d \n", a[2][1], **(a + 1), *(ptr - 1));
    //       10 4  9
    //b先指向了二维数组的第一行b++之后换到了第二行然后对b[1][1]赋值等于对a[2][1]赋值
    //相当于先到二维数组的第二行然后直接输出第一个是4
    //ptr的地址相当于在整个二维数组的后一位,-1之后回到了二维数组的最后一位
    return 0;
}

8.移形换位之术 

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;
}
  •  fun2中的*n为const不可被改变,所以*n+=1是错误的。
  • func3中的*n的地址不可以被改变,所以n=&a是错误的。
  • fun4中的*n的值和地址都不可以被改变所以*n+=1与n=&a都是错误的。

 const int与int const等价都表示一个不可以被改变的整型

int *const与const int *两个语句因为*与const的先后不一,*在前表示地址不可以被改变,const在前表示指针所指向的数值不可被改变

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

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

考察打代码的实际能力 与上一篇22年第9题手脑并用相似,直接上代码。

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
char *convert(const char *s)
{
    int i,len=strlen(s);//求字符串长度
    char *RESULT=(char*)malloc((len+1)*sizeof(char));
    //申请内存
    strcpy(RESULT,s);//拷贝字符串
    for(i=0;i<len;i++)
    {
        if(RESULT[i]>='a'&& RESULT[i]<='z') RESULT[i]-=32;
        else if(RESULT[i]>='A'&& RESULT[i]<='Z') RESULT[i]+=32;
    //转换大小写
    }
    return RESULT;
}
int main(void) 
{
    char *str = "XiyouLinux Group 2022";
    char *temp = convert(str);
    puts(temp);
    free(temp);
    return 0;
}

10.交换礼物的方式  

#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是正确的使用了一个变量t来交换a和b,只不过t后面要使用的话,会使t的值改变;
  • swap2是正确的,在do while中定义了一个变量用来交换a和b的值,出了do while  t即会销毁。
  • swap3是错误的,它是一个普通函数,传入了a和b的值,在函数内部进行交换,这个交换出了函数并不会存在,不会对函数外面的a和b有改变。
  • do{}while(0)使得语句是一个整体,防止定义宏多个语句的时候出现问题。
  • 还有异或和使用指针完成交换
    #define Swap4(a, b) 
    do
    {
        a = a ^ b;
        b = a ^ b;
        a = a ^ b;
    }while(0)
    void Swap5(int *a,int *b)
    {
        int t=*a;
        *a=*b;
        *b=t;
    }

11. 据说有个东西叫参数

int main(int argc, char *argv[]) 
{
    printf("argc = %d\n", argc);
    for (int i = 0; i < argc; i++)
        printf("%s\n", argv[i]);
}
  •  argc表示传递给程序命令行参数的数量,至少是1,第一个参数始终是程序的名称。
  • argv是一个指向字符串数组的指针,每个字符串表示一个命令行参数。
#include<stdio.h>
int main(int argc, char *argv[])
{
    printf("argc = %d\n", argc);
    for (int i = 0; argv[i]!='\0'; i++)//字符串肯定以'\0'结尾
    //当读到‘\0’时候停止循环
    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;
}

 代码在函数*func3里面有一处错误,指针指向了局部变量,在函数的调用结束时候n的生命周期结束,会导致指向它的指针成为悬空指针,访问这个指针会产生未定义的行为。

静态变量与其它变量之间的相同点

  • 静态变量和其他变量都是用来存储数据的内存空间
  • 静态变量和其他变量都可以被赋值和读取

 不同点

  • 静态变量在程序运行期间只被初始化一次,而其他变量每次进入作用域都会被初始化
  • 静态变量的生命周期从其定义的位置开始,直到程序结束,而其他变量的生命周期在进入和离开其作用域时开始和结束
  • 静态变量在函数内部定义时,会在函数调用结束后仍然保留其值,而其他变量在函数调用结束后会被销毁

13.奇怪的输出 

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

 本题与大小端的存储方式,进制之间的转换有关,目前大多数电脑都是小端,数据存储的方式是从后往前,十六进制与二进制的转换是一位变四位,所以两个十六进制就是一个字节,从后往前依次读取0x57,0x65...转换为十进制对应ASCII表输出字符得到打印结果。

#include<stdio.h>
int main(void)
{
    int data[] = {0x636c6557, 0x20656d6f, 0x78206f74,
                  0x756f7969, 0x6e694c20, 0x67207875,
                  0x70756f72, 0x32303220, 0x00000a31};
    puts((const char*)data);
    //Welcome to xiyou Linux group 2021
    return 0;
}

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

 C语言文件到可执行文件的过程主要包括以下几个步骤:

  1. 预处理:预处理器对源代码文件中的宏(#开头)进行替换,生成一个中间代码文件。
  2. 编译:编译器将中间代码翻译成目标代码(汇编语言或机器代码)。
  3. 汇编:汇编器将目标代码翻译成汇编语言。
  4. 链接:链接器将汇编语言与所需的库函数和目标文件连接起来,生成一个可执行文件。

15.  (选做) 堆和栈

 堆和栈是计算机内存中两个重要的数据结构,具有不同的特性和用途。以下是堆和栈之间的主要区别:

  1. 存储方式:堆是动态分配的内存区域,可以通过malloc函数进行分配;而栈是自动分配的内存区域,由系统自动释放
  2. 申请方式:栈是由系统自动分配,例如在声明函数的一个局部变量时,系统自动在栈中为该变量开辟空间;而堆需要程序员自己申请,并指明大小,在C中用malloc函数,在C++中用new运算符。
  3. 申请后系统的响应:只要栈的剩余空间大于所申请的空间,系统将为程序提供内存,否则将报异常提示栈溢出;而堆操作系统有一个记录空间内存地址的链表,当系统收到程序的申请时,会遍历链表,寻找第一个空间大于所申请空间的堆节点,然后将节点从内存空闲节点链表中删除,并将该节点的空间分配给程序。
  4. 生命周期:栈区的数据生命周期由编译器自动管理,一般在函数调用时创建函数返回时销毁;堆区的数据则需要程序员手动管理,通过调用free函数来释放内存

16.(选做) 多文件

 在C语言中,通常我们通过.h文件来调用另一个文件中的函数。具体来说,首先在一个.c源文件中对函数进行定义,然后在.h头文件中进行声明。这样,当需要在其他.c文件中调用这个已经定义好的函数时,只需要导入声明该函数的.h文件即可

#include<要调用的函数文件头>

但是,如果您不想使用头文件,也可以直接在需要调用函数的文件中对需要的函数进行声明。

//在某个.c文件中直接对函数进行声明
int sum(int a, int b);

然后在之后的代码中就可以直接调用这个函数了。不过需要注意的是,这种做法只推荐在函数较为简单、调用处集中的情况下使用

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

  • 创建文件:touch filename
  • 创建文件夹:mkdir foldername

  • 在GNU/Linux下,ls命令的每一列的含义如下:

  1. 第一列:文件/文件夹的权限。

  2. 第二列:硬链接数目。
  3. 第三列:文件/文件夹的所有者。
  4. 第四列:文件/文件夹的所属组。
  5. 第五列:文件/文件夹的大小(以字节为单位)
  6. 第六列:最后修改的日期和时间
  7. 第七列:文件/文件夹的名称
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值