西邮linux兴趣小组2021——2023面试题解析


2021


(1)

int *func1(void)
{
    static int n=0;//静态变量的生命周期是从调用它开始到本函数结束
    n=1;
    return &n;//只能返回指针值也就是n的地址(此处可返回地址是因为静态变量的内存不会被释放,值会一直保存且地址一直存在)
}
 
int *func2(void)
{
    int *p=(int*)malloc(sizeof(int)); //手动给指针p分配内存
    *p=3;
    return p;//返回首地址
}
 
/*func3错误*/
int *func3(void)
{
    int n;//没初始化
    return &n;//不可返回局部变量的地址(&n),因为它作用完就被释放了,相当于返回一个野指针
}

结果是 16 12

sizeof和strlen

sizeof

是C 语言中的一个单目语算符,求得是数据类型所占的空间大小。(即给出某个类型或变量在内存中所占的字节数),用sizeof来返回类型以及静态分配的对象,结构或数组所长的空间,返回值跟对象,结构,数组所存储的内容无关;

strlen
计算字符串函数的库函数名,要在运行时才能运算;参数必须是字符型指针(char*) ,必须以'\0'  结尾。该函数实际完成的功能是从代表字符串的第一个函数开始遍历,指导遇到结束符'\0'。注意:返回的长度大小不包括'\0'。

所以你理解答案了吗?

在此题中值得注意的是代码中字符串的结尾还有一个隐形的'\0',所以sizeof值为16而不是15;

笔记走一个 


(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));
    printf("sizeof(t2): %d\n",sizeof(t2));
}

大家动一动智慧的大脑,想到答案了吗?

结果相等,都为16

这里主要考察结构体内存对齐相关的知识
  1. 第一个成员在结构体变量偏移量为0的地址处
  2. 其他成员要对齐到对齐数整数倍的地址处
  3. 结构体的总大小为最大对齐数的整数倍
  4. 对齐数=编译器默认的一个对齐数与该成员大小的较小值
  5. 不是所有的编译器都有默认对齐数,VS中默认对齐数为8,linux GCC没有默认对齐数,当编译器没有默认对齐数时,成员变量的大小就是该成员的对齐数
  6. 对于嵌套结构体,嵌套结构体成员的首地址在自己最大对齐数的整数倍的地址处,结构体的大小就是所有最大对齐数的整数倍
因此,test1三个元素内存分别为 4、2、8,其中2补2变为4,所以结果为 4+4+8=16;test2三个元素分别为2、4、8,同样2补齐2变为4,还是16。

3. 哦,又是函数

想必在高数老师的教导下大家十分熟悉函数这个概念。那么你了解计算机程序设计中的函数吗?请编写一个func函数,用来输出二维数组arr中每个元素的值。

#include<stdio.h>
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函数,用来输出二维数组arr中的每个值

void func(int n,char arr[][13]);
{
    int i;
    for(i=0;i<n;i++)
    {
        printf("%s\n",i,arr[i]);
    }
}
你还有什么好的方法吗?如果有的话,记得评论区分享哦!

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

  • 请结合下面的程序,简要谈谈传值传址的区别。

  • 简要谈谈你对C语言中变量的生命周期的认识。

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

#include<stdio.h>
int ver =123;//全局变量
void func1(int ver)//当局部函数和全局函数的变量名相同时在局部
                  //函数中取赋给局部变量的值
{
    ver++;
    printf("ver=%d\n",ver);
}//1026出了这个函数数据就被销毁
void func2(int *pr)//局部变量ver的地址给指针Pr
{
    *pr=1234;//使pr指向地址中存放的值发生了改变
    printf("*pr=%d\n",*pr);//找到地址改变值那就是真的改变
    pr=5678;
    printf("ver=%d\n",ver);//此时的ver的值为全局变量的值
}
int main()
{
    int a=0;//局部变量,只有在主函数体内有效
    int ver=1025;
    for(int a=3;a<4;a++)//for循环内a的初定义
    {
        static int a=5;
        printf("a=%d\n",a);//5
        a=ver;//1025
        func1(ver);//传入的是主函数内定义的局部变量ver
        int ver=7;
        printf("ver=%d\n",ver);//7
        func2(&ver);//输出ver=1234,因为在func2函数中通过修改*pr指针修改了ver的值
    }
    printf("a=%d\tver=%d\n",a,ver);//0 1025是主函数内刚开始定义和初始化的值
}

我这样解释,你会了吗?不懂的地方欢迎随时在评论区留言呦!

那我给大家分享一个知识点

传值和传址的区别

  1. 传值相当于单纯把值船给别人,就像是粘贴复制,传给别人后,别人修改那个值,不改变传值方本身的值
  2. 传址相当于把地址传给了人家,只要一方改变另一方就会跟着发生改变

那我再问一个问题吧

你了解过变量的生命周期吗?
变量类型主要有全局变量、局部变量和静态变量
  1. 局部变量:定义在函数体内部的变量,作用于仅限于函数体内部,离开函数体就是无效,在调用就会出错
  2. 全局变量:所有函数体外部定义的变量,他的作用域是整个程序,也就是所有的源文件。
  3. 静态变量:变量在运行区间结束后内存不释放,地址不变

5. 套娃真好玩!

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

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

结果是:5050

A?B:C

A的值为真,则调用B,A的值为假,则调用C

 递归(啥意?)

递归中必须存在一个临界点。当它满足这个临界点时就跳出递归,得到一个确定的值。

这道题函数自己调用自己,那么就是递归函数;

从n=100开始递减,当n!=0时,函数不停返回sum(n-1)调用自己,当n减到1时,返回sun(0)+1,sum(0)通过运算符判断返回0,所以就是100+99+98+...+0=5050。

(6)6. 算不对的算术

#include<stdio.h>
void func(void);
int  main()
{
    func();
    return 0;
}
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=0x%hhx\t\n",(unsigned char)c);
}

结果为:

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

原因为:

#include<stdio.h>
void func(void);
int  main()
{
    func();
    return 0;
}
void func(void)
{
    short a=-2;//a二进制1111 1111 1111 1110,十六进制fffe
    unsigned int b=1;
    b+=a;//b二进制1111 1111 1111 1111 1111 1111 1111 1111,十六进制ffffffff
    int c=-1;
    unsigned short d=c*256;
    c<<=4;//c的补码右移四位变为1111 1111 1111 1111 1111 1111 1111 0000,十进制为-16,十六进制为fffffff0
    int e=2;
    e=~e|6;//~取反后进行或运算
    d=(d&0xff)+0x2022;//与运算都为1才为1,所以他为0+0x2022
    printf("a=0x%hx\tb=0x%x\td=0x%hx\te=0x%x\n",a,b,d,e);//%hx表示输出short类型值
    printf("c=0x%hhx\t\n",(unsigned char)c);//%hhx表示输出 short short 类型
}
如果看不懂原因没关系,我会一一进行知识点分享。

一、按位于&(有0出0,全1出1)

eg:4&5    4          0000,0100

                5          0000,0101 

                            0000,0100

二、按位或(有1出1,全0出0)

eg:4|5     4            0000,0100

                5            0000,0101

                              0000,0101

三、按位异或(相同出0,不同出1)

eg:a^a=0;                                  0^0=0;

     0^1=1;                                   1^1=0

四、取反(1取0,0取1)

eg:~0000,1010

       1111,0101


五、左移运算符(左移若干位,高位去掉若干位,低位补若干位0)

eg:int a=7; a=a<<2;a=?
0000,0111将变为0001,1100

六、右移运算符(右移若干位,高位补若干位,低位去掉若干位)

注意:高位补若干位时,对无符号数,高位补0;对有符号数,正数补0,负数补1。

七、原码补码反码

原码:将一个整数转化成2进制形式,eg:short a=6,a的原码为0000 0000 0000 0110;short a=-6,a的原码为1000 0000 0000 0110,其最高位表示符号位。

反玛:对于正数,他的反码就是其原码;对于负数,反码就是将原码中除符号位以外的所有位取反,-6的反码为1111 1111 1111 1001

补码:对于正数,他的补码就是其原码;对于负数,就是其反码+1,

-6的补码为1111 1111 1111 1010

原码、反码、补码只对负数有实际意义,对于正数都是一样的。


(7)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[1][1]=10;
    int *ptr =(int*)(&a+1);
    printf("%d %d %d\n",a[2][1],**(a+1),*(ptr-1));
}

      结果:10 4 9

a是一个三维数组,b是一个指向一维数组的数组指针,此一维数组里面放的是a这个三维数组中0,1,2行的第0列

++b就是将b指向的一维数组的首地址向后移了一位,给b[1][1]赋值相当于是给a[2][1]赋值,也就是把7替换成了10

int *ptr=(int *)(&a+1)ptr是指向整个a数组下一个数组的地址

a和&a不同,a代表首元素地址,&a代表整个数组的地址

**(a+1)对数组一维+1变成第2维,(a+1)指向第2维的首地址,**(a+1)解引用后找到4

*(ptr-1)整个数组的下一个数组-1,也就是数组a中的组后一个元素9

 在这里,在给小伙伴们总结一个关于指针的知识点吧!就拿他和字符型数组举个例子吧!

#include<stdio.h>
int main()
{
    int a[]={1,2,3,4};
    printf("%zu\n",sizeof(a));
    printf("%zu\n",sizeof(a+0));
    printf("%zu\n",sizeof(*a));
    printf("%zu\n",sizeof(a+1));
    printf("%zu\n",sizeof(a[1]));
    printf("%zu\n",sizeof(&a));
    printf("%zu\n",sizeof(*&a));
    printf("%zu\n",sizeof(&a+1));
    printf("%zu\n",sizeof(&a[0]));
    printf("%zu\n",sizeof(&a[0]+1));
    return 0;
}

你知道他的结果是什么吗?又是什么原因呢?

先告诉你一个重要的知识点。

数组名一般表示数组首元素的地址,但在两种情况下表示数组,其一是sizeof( 数组名),其二是&(数组名)。
  1. 第一个就满足其一,a表示整个数组,它包含4个整形元素,每个元素占四个字节,故打印结果为16
  2. 第二个千万别以为a+0=a,所以和第一个一样,那就大错特错啦。a+0表示a数组首元素的地址向后移0位,也就表示的是a的首元素
  3. *  a是对指针变量进行解引用操作,这里a表示数组的首元素地址,*   a取数组首元素,为整形变量,大小为4byte,打印结果是4
  4. a+1 指的是数组第二个元素的地址,地址的大小,就是指针变量的大小,在32位编译器环境下为4字节,在64位编译器环境下为8字节
  5. a[1]表示数组的第二个元素,为4
  6.  &a表示整个数组元素的地址,对于指针变量,无论指向的变量为何种元素,都是4或8
  7. *&a表示对整个数组指针进行解引用,4*4=16;
  8. &a+1表示把整个元素的地址向高地址移一位,本质还是指针变量,那么打印结果还是4或8
  9. &a[0]表示a的首地址,打印结果是4或8
  10. 他表示把数组首元素向后移一位也就是数组第二个元素的地址,打印结果为4或8

8. 移形换位之术

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

  • 你能说出使用这三个变量的值或地址作为参数分别调用这5个函数,在语法上是否正确吗?

  • 请找出下面的代码中的错误。

  • const intint const是否有区别?如果有区别,请谈谈他们的区别。

  • const int *int const *是否有区别?如果有区别,请谈谈他们的区别。

#include<stdio.h>
int main()
{
    int a=1;
    int const b=2;
    const int c=3;
    void func0(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;
    }
    return 0;
}                              

上面是a,b,c三个变量和4个相似的函数。

那么他们是否有错误,如果有,你能找出来么?

先问你们几个问题,const int 和 int const有区别不?const int*  和int const *呢?

他们是没有区别的,第一个的两个表示int 类型的变量不能改变,第二个表示 值针指向的值不能改变。

然后咱们分析错误代码:

void func1(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;//同上第二个
    }

 你会了吗?


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

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

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

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

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)
#define Swap3(int a,int b)
{
    int t=a;
    a=b;
    b=t;
}
  1. 这三种交换值的方法对吗?
  2. do...while(0)是什么意思呢?
  3. 还有其他方法实现交换功能吗?

1.三错误,只传了a,b的值,没有传地址,所以只是在函数内改变了他的值,函数结束函数内交换的值也就不存在了,a,b的值依旧没有改 变。                                                                                   
假如你定义了一个宏,这个宏的作用为连续调用2个函数,宏定义如下:

2.#define DOSOMETHING()   func1(); func2()
 但使用时可能会出现以下这样的情况:

展开前:
if(judge == TRUE)
    DOSOMETHING;
 
展开后:
if(judge == TRUE)
    func1();
    func2();
 可以看到,由于没有加上括号,func2是必会执行的,已经引起了逻辑错误;如果这个if还有else等分支的话,会出现编译错误。为了避免这个错误,也许你会给宏加上括号,就像下面这样:

#define DOSOMETHING()   { func1(); func2();}
使用时再次展开宏: 

展开前:
if(judge == TRUE)
    DOSOMETHING;
 
展开后
if(judge == TRUE)
{   
    func1();
    func2();
};
可以看到,由于调用DOSOMETHING宏时,后面有个“;”号,所以展开后最后多了一个“;”号,显然如果这个符号是多余的,虽然有些编译器可以忽略多余的“;”,但如果这个if后面有个else语句,也会引起编译的异常。也许你会说,最后的“;”是多余的,那我调用的时候不加“;”不就可以了吗,就像下面这样。

if(judge == TRUE)
    DOSOMETHING
当然这是行得通的, 但是在每个语句后面加分号是一种约定俗成的习惯,应当极力避免这种使用方式,否则阅读代码将是一件痛苦的事情!这时候do {} while(0)就派上用场了!我们可以用do {} while(0)重新定义DOSOMETHING宏:

#define DOSOMETHING()   do{ func1(); func2();} while(0)
再次展开宏,就可以看到,使用上就毫无问题了! do {} while(0)真香啊!

展开前:
if(judge == TRUE)
    DOSOMETHING;
 
展开后:
if(judge == TRUE)
    do{
        func1();
        func2();
    }while(0);

do{...}while(0)只执行了一遍,使{}里的成为局部变量。

3.当然有很多啦,在这里,我给大家分享几个。

(1)int c=a;a=b;b=c;

(2)a=a^b;b=a^b;a=a^b;

(3)a=a-b;b=a-b;a=a-b;

(4)还有一种用指针将两者的地址进行交换,自己想一想,在这里我就不多说了。


11. 据说有个东西叫参数

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

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

               argc: 整数,用来统计你运行程序时送给main函数的命令行参数的个数

    * argv[ ]: 指针数组,用来存放指向你的字符串参数的指针,每一个元素指向一个参数

    argv[0] 指向程序运行的全路径名

    argv[1] 指向在DOS命令行中执行程序名后的第一个字符串

    argv[2] 指向执行程序名后的第二个字符串

    ...

    argv[argc]为NULL

(12)

int *func1(void)
{
    static int n=0;//静态变量的生命周期是从调用它开始到本函数结束
    n=1;
    return &n;//只能返回指针值也就是n的地址(此处可返回地址是因为静态变量的内存不会被释放,值会一直保存且地址一直存在)
}
 
int *func2(void)
{
    int *p=(int*)malloc(sizeof(int)); //手动给指针p分配内存
    *p=3;
    return p;//返回首地址
}
 
/*func3错误*/
int *func3(void)
{
    int n;//没初始化
    return &n;//不可返回局部变量的地址(&n),因为它作用完就被释放了,相当于返回一个野指针
}

给大家分享一点知识点

Static你有了解吗?

  1. static修饰局部变量时,会改变局部变量的存储位置,从而使得局部变量的生命周期变长。
  2. static修饰全局变量时,会改变全局变量的链接属性,从而使得全局变量的作用域变小。
  3. static对函数的修饰与修饰全局变量十分相似,修饰函数时会改变函数的链接属性,从而使得函数的作用域变小。

 这里的malloc你知道是什么吗?     

  1. malloc动态内存分配函数,用于申请一块连续的指定大小的内存块区域以void*类型返回分配的内存区域地址。
  2. malloc的头文件
    #include<malloc.h>
  3. malloc函数返回值
    如果分配成功则返回指向被分配内存的指针,否则返回空指针NULL。   
    (13)                                 

结果是   Welcome to xiyou linux group 2021


  【选做题】
 1.c语言文件是怎样成为可执行文件的?
C语言文件成为可执行文件的过程需要经过编译和链接两个步骤。编译将源代码转换为目标代码,链接将目标代码和库文件链接成可执行文件
   2.linux是用命令行怎样创建文件和文件夹
创建文件夹mkdir xxx
创建文件touch newfile.txt 

                                                                                                                                                            


 2022

(1)

加号的优先级大于<,>的优先级,3+2=5,5<2为假,表示0,第二个可判断为真,表示1,则不满足if直接跳到else.这里是printf的一个镶嵌函数,他函数的返回值为被打印的字符的个数,所以输出为Xiyou  linux Group - 2022


        `==是判断相等的字符串,p0表示整个数组,p1表示字符串H的地址 ,两个肯定不相等,所以返回0。
strcmp()是逐个比较单个字符ASCII的大小,相等返回0,前比后大返回1,后比前大返回0;
关于 sizeof(),strlen()2021年讲过,就不多赘述了

第一行printf()上面的a为初始化,所以打印出来的是电脑给的随意值

第二个sprintf()在局部函数test()内,所以a=1+1=2

第三个sprintf()打印的a为全局变量的a,a=3

在个大家分享关于变量和生命周期的知识

变量简单可分为局部变量和全局变量,局部变量它的值只在函数内体现,也就是{}内, 当他进入函数体内时,系统会自动给他分配内存空间,当函数调用结束时,给他分配的内存空间会被释放,这个变量会自动销毁;全局变量是在函数体外定义的变量,也就是在 {}外,他的作用范围是从定义他到源文件结束。生命周期通俗来讲就是他开始和结束的地方。


​​​​ 

 union和struct都是构造数据类型,struct和union都是由多个不同的数据类型成员组成, 但在任何同一时刻, union中只存放了一个被选中的成员; 而struct的所有成员都存在。在struct中,各成员都占有自己的内存空间,它们是同时存在的,一个struct变量的总长度等于所有成员长度之和,遵从字节对其原则; 在Union中,所有成员不能同时占用它的内存空间,它们不能同时存在 , Union变量的长度等于最长的成员的长度。 ,简而言之,计算占用字节长度时,共用体是算单个类型占空间最长的,这里就是itn类型的数组,里面含有5个元素,所以是20;而结构体的是算所有类型占用的总内存,要把它们都加起来,4+20+8=32 .

我想这里还有一个疑问,为啥他俩前面都要加上一个typedef,他有什么用呢?

其实他就是起到了一个简化的作用,他可以给已有的数据起一个新名字,dypedef  数据类型   新的名字,这样在你多次使用数据类型,而他写起来比较繁琐时,就可以用简单的新的名字替换。举个例子,有个男生他叫咕噜咕噜芭比芭比,人们觉得叫起来横麻烦,就给他起了一个小名,叫小明,当人们叫小明时,就代表他这个人【在世界上没有重名的情况下】

这些运算符之前讲过,所以咱直接看题,char是一个字节,占8位,4用二进制表示是00000100,7是00000111,有一取一,则为00000111,左移3位,高位去3个0,地位补3个0,为00111000,表示56,后面的就按照这思路就行了,这个的输出结果是 56 0 254 48 ox4;

这里给大家补个知识点

unsigned就是无符号,无论是int类型还是char类型,他的最高位一般表示的是正负,1为负,0为正,加上他之后,最高位就可以表示大小,则说明比原来类型的正数的范围提高了一倍,加上他之后也只能表示正数范围,不能表示负数范围。

%x表示以16进制输出,x前面的#表示输出时加上前缀Ox【后面的x取决于%后x的大小】


const在谁后面谁就不可以被修改 

  第一个const作用于p,表示指针变量p指向的地址不发生改变;第二个和第三个表示指针变量指向的值不发生改变    


  1.  int *p[10];
  2. int (*p)[10];
  3. int(*p[3])(int)

给大家分享运算符的优先级

第1优先级:各种括号,如()、[]等、成员运算符 . ;

第2优先级:所有单目运算符,如++、–、!、~等;

第3优先级:乘法运算符*、除法运算符/、求余运算符%;

第4优先级:加法运算符+、减法运算符-;

第5优先级:移位运算符<<、>>;

第6优先级:大于运算符>、大于等于运算符>=、小于运算符<、小于等于运算符<=;

第7优先级:等于运算符==、不等于运算符!=;

第8优先级:按位与运算符&;

第9优先级:按位异或运算符^;

第10优先级:按位或运算符|;

第11优先级:逻辑与运算符&&;

第12优先级:逻辑或运算符||;

第13优先级:三目条件运算符 ?: ;

第14优先级:各种赋值运算符,如=、+=、-=、*=、/= 等;

第15优先级:逗号运算, 。

C语言的运算符也包括单目运算符、双目运算符、三目运算符

#直接插入排序
def insert_sort(L):
    #遍历数组中的所有元素,其中0号索引元素默认已排序,因此从1开始
    for x in range(1,len(L)):
    #将该元素与已排序好的前序数组依次比较,如果该元素小,则交换
    #range(x-1,-1,-1):从x-1倒序循环到0
        for i in range(x-1,-1,-1):
    #判断:如果符合条件则交换
            if L[i] > L[i+1]:
                temp = L[i+1]
                L[i+1] = L[i]
                L[i] = temp
#冒泡排序
def bubble_sort(L):
    length = len(L)
#序列长度为length,需要执行length-1轮交换
    for x in range(1,length):
#对于每一轮交换,都将序列当中的左右元素进行比较
#每轮交换当中,由于序列最后的元素一定是最大的,因此每轮循环到序列未排序的位置即可
        for i in range(0,length-x):
            if L[i] > L[i+1]:
                temp = L[i]
                L[i] = L[i+1]
                L[i+1] = temp

char* convertAndMerge(char words[2][20])
{
 char* str = (char*)calloc(40, sizeof(char));//在函数中分配一段空间
 int n = 0;
 if (str != NULL)
 {
  for (int i = 0; i < 2; i++)
  {
   for (int j = 0; j < 20; j++)
   {
    if (words[i][j] != 0)
    {
     if (words[i][j] >= 'a' && words[i][j] <= 'z')
     {
      str[n] = toupper(words[i][j]);
     }
     else if (words[i][j] >= 'A' && words[i][j] <= 'Z')
     {
      str[n] = tolower(words[i][j]);
     }
     else {
      str[n] = words[i][j];
     }
     n++;
    }
   }
  }
 }
 return str;
}

0       1       2       3       4
25      26      27      28      29
45      46      47      48      49
60      61      62      63      64
70      71      72      73      74

因为temp < arr[5],变量a在整个循环期间一直在递增,而temp只取它需要的5个,所以a第一次增加25,第二次增加20 等,有点思维能力的锻炼。


  1.argc是参数的个数,argv为指针数组的首元素
  2.argc中总是存在一个参数,会自动传入一个命令

3.argc为有符号int类型,给argc一直加1,当他超过了int能表示的最大范围时,就会转而跳到负数区域,你可以想象用x坐标轴来标出int能表示的范围,把最小的负数和最大的正数连起来形成一个环,这样解释应该好理解一些了吧。


 

 结果:Welcome to Xiyou Linux Group 2022

strcpy函数是字符串拷贝函数,参数为2个字符串的首地址,strcpy(字符数组s1,字符串s2),
执行该函数时将字符串s2拷贝到字符数组s1中去;字符串连接函数strcat(),strcat(字符数组1,字符串2),将字符串2连接到字符数组1中的字符串后面

                                                 

  1.  第一个通过宏定义实现了xy值的交换,输出为2 1 1
  2. 2>1为真,继续执行xy值的交换,输出为 1 2 2
  3. 注意,他是a*a而不是(a)*(a),这是个坑,就算满足第二种,他的值也不满足100,因为z和w已经不是原来的值了。第三个输出为 1 2

由a*a可知z.w都自加了俩次,所以可知输出结果为  5 5 2


2023


0.鼠鼠我啊,要被祸害了
有 1000 瓶水,其中有一瓶有毒,小白鼠只要尝一点带毒的水,24 小时后就会准时死亡。
至少要多少只小白鼠才能在 24 小时内鉴别出哪瓶水有毒?

这个题要用到二进制的思路,1000用二进制表示在1000000000和10000000000之间,也就是用10个1或0构成的二进制可以表示1到第1000瓶药水,也就是需要10只老鼠,每只老鼠就表示二进制中的0或1,用第几瓶时,用二进制表示出这个数,二进制中表示1的老鼠滴上这个药水,最后看哪几只老鼠死了,就可以找出对应的第几瓶药水。


1.先预测一下~
按照函数要求输入自己的姓名试试~
char *welcome() {
// 请你返回自己的姓名
}
int main(void) {
char *a = welcome();
printf("Hi, 我相信 %s 可以面试成功!\n", a);
return 0;
}

其中写上char *="你的名字";

a表示字符串的地址,%s用于输出字符串


2.欢迎来到 Linux 兴趣小组
有趣的输出,为什么会这样子呢~
int main(void) {
char *ptr0 = "Welcome to Xiyou Linux!";
char ptr1[] = "Welcome to Xiyou Linux!";
if (*ptr0 == *ptr1) {
printf("%d\n", printf("Hello, Linux Group - 2%d", printf("")));
}
int diff = ptr0 - ptr1;
printf("Pointer Difference: %d\n", diff);
}

ptr0指向字符串首元素的地址,ptr1指向数组的首地址,对他俩进行解引用,都为W,所以满足相等条件,为真,进入printf函数的嵌套使用,之前讲过,不再赘述,所以结果为Hello, Linux Group - 2023,diff计算两指针之间的偏移量,ptr0指向一个字符串的常量,实际上是指向只读内存的指针,是未定义行为,所以结果不确定


.一切都翻倍了吗
① 请尝试解释+一下程序的输出。
② 请谈谈对 sizeof()和 strlen()的理解吧。
③ 什么是 sprintf(),它的参数以及返回值又是什么呢?
int main(void) {
char arr[] = {'L', 'i', 'n', 'u', 'x', '\0', '!'}, str[20];
short num = 520;
int num2 = 1314;

printf("%zu\t%zu\t%zu\n", sizeof(*&arr), sizeof(arr + 0),
sizeof(num = num2 + 4));
printf("%d\n", sprintf(str, "0x%x", num) == num);
printf("%zu\t%zu\n", strlen(&str[0] + 1), strlen(arr + 0));

}

*&arr指对arr整个数组其进行解引用,arr数组里面的元素为char类型的【在内存中占1个字节】sizeof酸长度包括\0,7*1=7;arr+0表示首元素地址,4或8;注意num = num2 + 4是不用进行计算的,因为sizeof返回的是变量的类型,与变量的值无关,short类型占内存空间为两个字节,打印结果为2;sprintf函数里面表示把num=520的520以16进制保存在str字符串数组中,他的返回值为字符串的个数,肯定不等于num,所以返回0;
sprintf() 是C语言中的一个函数,用于将格式化的数据写入字符串缓冲区中,而不是将数据打印到屏幕上(这与 printf() 不同),
他的工作方式与 printf() 类似,但是它将格式化后的字符串写入到指定的字符数组中,而不是输出到标准输出流(比如屏幕)。这使得它在生成格式化字符串后,可以保存到变量中,而不是直接打印出来。
它的返回值是一个整数,表示写入到字符串中的字符数,不包括字符串的终止符(\0)


4.奇怪的输出
程序的输出结果是什么?解释一下为什么出现该结果吧~
int main(void) {
char a = 64 & 127;
char b = 64 ^ 127;
char c = -64 >> 6;
char ch = a + b - c;
printf("a = %d b = %d c = %d\n", a, b, c);
printf("ch = %d\n", ch);
}

之前讲过逻辑运算符,a,b,c的值都好理解,ch应为128,但为什么他打印的结果为-1呢,因为有符号的char类型能表示的范围位-128~127,超过了最大表示范围,127在向前走,走到了-1停止,所以结果为-1.




“人们常说互联网凛冬已至,要提高自己的竞争力,可我怎么卷都卷不过别人,只好用一些奇技淫
巧让我的代码变得高深莫测。”
这个 func()函数的功能是什么?是如何实现的?
int func(int a, int b) {
if (!a) return b;
return func((a & b) << 1, a ^ b);
}
int main(void) {
int a = 4, b = 9, c = -7;
printf("%d\n", func(a, func(b, c)));
}

func()是一个递归函数,当a=0时,返回b,否则返回func((a & b) << 1, a ^ b),


6.自定义过滤
请实现 filter()函数:过滤满足条件的数组元素。
提示:使用函数指针作为函数参数并且你需要为新数组分配空间。
typedef int (*Predicate)(int);
int *filter(int *array, int length, Predicate predicate,
int *resultLength); /*补全函数*/
int isPositive(int num) { return num > 0; }
int main(void) {
int array[] = {-3, -2, -1, 0, 1, 2, 3, 4, 5, 6};
int length = sizeof(array) / sizeof(array[0]);
int resultLength;
int *filteredNumbers = filter(array, length, isPositive,
&resultLength);
for (int i = 0; i < resultLength; i++) {
printf("%d ", filteredNumbers[i]);
}
printf("\n");
free(filteredNumbers);
return 0;
}

#include <stdlib.h>

typedef int (*Predicate)(int);

int* filter(int* array, int length, Predicate predicate, int* resultLength) {
    int* result = (int*)malloc(length * sizeof(int));
    int count = 0;

    for (int i = 0; i < length; i++) {
        if (predicate(array[i])) {
            result[count++] = array[i];
        }
    }

    *resultLength = count;
    return result;
}

int isPositive(int num) {
    return num > 0;
}

int main(void) {
    int array[] = {-3, -2, -1, 0, 1, 2, 3, 4, 5, 6};
    int length = sizeof(array) / sizeof(array[0]);
    int resultLength;
    int* filteredNumbers = filter(array, length, isPositive, &resultLength);

    for (int i = 0; i < resultLength; i++) {
        printf("%d", filteredNumbers[i]);
    }

    printf("\n");

    free(filteredNumbers);
    return 0;
}


 


7.静...态...
① 如何理解关键字 static?
② static 与变量结合后有什么作用?
③ static 与函数结合后有什么作用?
④ static 与指针结合后有什么作用?
⑤ static 如何影响内存分配?

static翻译过来就是静态的的意思,之前已讲过他与变量结合的作用,在致力就不多赘述。

static修饰函数时,这个函数的外部链接属性变为内部链接属性,是其他源文件(.c)文件就可以再使用这个函数了。则使用时我们会感觉到该函数作用域变小。

用static与指针结合,可控制指针变量的生命周期和作用域

是局部的变量或函数由栈区变为静态区 



8.救命!指针!
数组指针是什么?指针数组是什么?函数指针呢?用自己的话说出来更好哦,下面数据类
型的含义都是什么呢?
int (*p)[10];
const int* p[10];
int (*f1(int))(int*, int);

数组指针表示他是一个指向数组的指针;指针数组表示他是一个数组,里面存放的是指针类型的元素;函数指针,他是一个指针,里面存放的是函数的地址;int (*p)[10]p表示他是一个指向含有10个 int 类型元素的指针;const int* p[10]p表示他是一个含有10个指向int类型元素指针个数组,const表示数组内指针指向的地址不能发生改变;int (*f1(int))(int*, int)1. int (*f1(int)) 表示 f1 是一个函数,它接受一个整数参数,并返回一个指向函数的指针,这个被指向的函数接受两个参数,一个是 int* 类型的指针,另一个是int 类型的整数,这个被指向的函数返回一个 int 类型的整数。



9.咋不循环了
程序直接运行,输出的内容是什么意思?
int main(int argc, char* argv[]) {
printf("[%d]\n", argc);
while (argc) {
++argc;
}
int i = -1, j = argc, k = 1;
i++ && j++ || k++;
printf("i = %d, j = %d, k = %d\n", i, j, k);
return EXIT_SUCCESS;
}

● printf("[%d]\n", argc); 打印程序的命令行参数数量 ( argc)。·接下来是一个 while 循环,但是它的循环条件是 argc ,如果 argc 不为零,就会进入循环。
. 在循环中, argc 会被递增,但argc属于int型,所以他一直增大到int类型能表示的最大范围后就会绕带最小的,然后再递增直到遇到0,后面2021年讲过,可类比。



10.到底是不是 TWO
#define CAL(a) a * a * a
#define MAGIC_CAL(a, b) CAL(a) + CAL(b)

int main(void) {
int nums = 1;
if(16 / CAL(2) == 2) {
printf("I'm TWO(ノ>ω<)ノ\n");
} else {
int nums = MAGIC_CAL(++nums, 2);
}
printf("%d\n", nums);
}

#define这里易出错的点之前已经讲过了,16/2*2*2!=2,则不会打印I'm TWO(ノ>ω<),++nums=2,2*2*2+2*2*2=16,但是输出结果是16吗,你可别忘了,num=16他是局部变量,出{}时就自动销毁了,所以num还是等于2。


11.克隆困境
试着运行一下程序,为什么会出现这样的结果?
直接将 s2 赋值给 s1 会出现哪些问题,应该如何解决?请写出相应代码。
void initializeStudent(struct Student *student, const char *name,
int age) {
student->name = (char *)malloc(strlen(name) + 1);
strcpy(student->name, name);
student->age = age;
}
int main(void) {
struct Student s1, s2;
initializeStudent(&s1, "Tom", 18);
initializeStudent(&s2, "Jerry", 28);
s1 = s2;
printf("s1 的姓名: %s 年龄: %d\n", s1.name, s1.age);
printf("s2 的姓名: %s 年龄: %d\n", s2.name, s2.age);
free(s1.name);
free(s2.name);
return 0;
}

输出结果为Jerry  28Jerry  28

可改为

struct Student {
    char *name;
    int age;
};
void initializeStudent(struct Student *student, const char *name,
                       int age) {
    student->name = (char *)malloc(strlen(name) + 1);
    strcpy(student->name, name);
    student->age = age;
}
int main(void) {
    struct Student s1, s2;
    initializeStudent(&s1, "Tom", 18);
    initializeStudent(&s2, "Jerry", 28);
    s1 = s2;
    printf("s1 的姓名: %s 年龄: %d\n", s1.name, s1.age);
    printf("s2 的姓名: %s 年龄: %d\n", s2.name, s2.age);
    free(s1.name);
    free(s2.name);
    return 0;
}


12.你好,我是内存
作为一名合格的 C-Coder,一定对内存很敏感吧~来尝试理解这个程序吧!
struct structure {
int foo;
union {
int integer;
char string[11];
void *pointer;
} node;
short bar;
long long baz;
int array[7];
};
int main(void) {
int arr[] = {0x590ff23c, 0x2fbc5a4d, 0x636c6557, 0x20656d6f,
0x58206f74, 0x20545055, 0x6577202c, 0x6d6f636c,
0x6f742065, 0x79695820, 0x4c20756f, 0x78756e69,
0x6f724720, 0x5b207075, 0x33323032, 0x7825005d,
0x636c6557, 0x64fd6d1d};
printf("%s\n", ((struct structure *)arr)->node.string);
}

输出结果为 Welcome to XUPT ,welcome to Xiyou Linux Group [2023]

结构体与联合体的应用,对内存对齐的理解,大小端的判断和十六进制和二进制之间的关系




13.(选做)
你知道 cd 命令的用法与 / . ~ 这些符号的含义吗?

  1. cd /  表示进入根目录
  2. cd . 表示当前目录
  3. cd ,/文件名表示进入当前目录的某个文件中,也可表示为cd 文件名
  4. cd ..表示返回到上一级目录
  5. cd ../.. 表示退回上两级目录
  6. cd ~表示进入用户默认的目录
  7. cd -返回到进入此目录之前的目录

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值