linux 2023

0.鼠鼠我啊,要被祸害了

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

可以尝试用二进制数表示某一瓶是否有毒,第i位为1表示第i瓶水有毒,为0表示没毒,某瓶水对应的二进制编码(0或1)代表这瓶水要不要给第i只小白鼠喝(0不喝1喝)若喝了之后小白鼠死了则其对应位置为1,没死则为0,喂水之后根据几只小白鼠的生死状况会得到一个二进制编码,将其转化为10进制数字就可以看出哪瓶有毒,由2n>=1000 解得 至少需要10只小白鼠。

例如:若编码为1000100011的水有毒,则第1,2,6,10只小鼠死亡。

1.先预测一下~

按照函数要求输入自己的姓名试试~

    char *welcome(){
    //请你返回自己的姓名
    }
    int main(void){
    char *a=welcome();
    printf("Hi,我相信 %s 可以面试成功!\n",a);
    return 0;
    }
    #include <stdio.h>
    char *welcome()
    {
        char*name="XXX";\\XXX表示自己的名字
        return name;
    }
    int main(void)
    {
        char *a=welcome();
        printf("Hi,我相信 %s 可以面试成功!\n",a);
        return 0;
    }

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

输出结果:Hello,Linux Group - 2023
Pointer Difference:-318856704(随机值)
除了&数组名和sizeof(数组名)之外,其他数组名都表示首元素地址
指针的本质是地址

  • *ptr0和ptr1都指向首元素,首元素相同,执行if语句,printf返回写入的字符总数,内层printf输出空字符串并返回0,外层printf打印字符串长度23,最终打印2023。
  • 地址减地址返回地址差值。

3.一切都翻倍了吗

① 请尝试解释一下程序的输出。
② 请谈谈对 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));
    }

输出结果:7 8 2 0 4 5

  • *&arr找到整个数组,计算数组长度,为7;

    arr+0为首元素地址,64位机器下大小为8;

    sizeof内部语句不执行,只注重变量类型,num是short类型,占两个字节;

    %x将num格式化为16进制,打印0x208,比较结果不相等,返回0;

    strlen计算长度,&str[0]+1指向第二个元素,后面字符总数为4;

    arr+0是首元素地址,后面字符总数为5。

  • sizeof是一个操作符,计算变量所占内存空间的大小,单位是字节,要算上\0;
    strlen是库函数,计算\0之前出现的字符个数,不算\0。

  • sprintf()是一个字符串格式化命令,主要用于将格式化的数据写入字符串中,参数包括指向字符串缓冲区的指针、包含格式字符串的c字符串以及可选参数,它会返回写入的字符总数。

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=64 b=63 c=-1 ch=-128

64&127结果为64,a=64(&:有0取0,没0取1)

64^127结果为63,b=63( ^:相同为0,不同为1)

将-64右移6位得到-1,c=-1

a+b=128,而char类型取值范围为-128~127,a+b会溢出,回到最小值-128。

5.乍一看就不想看的函数

“人们常说互联网凛冬已至,要提高自己的竞争力,可我怎么卷都卷不过别人,只好用一些奇技淫
巧让我的代码变得高深莫测。”
这个 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)));
    }

输出结果:6
本质:加法运算
func函数内用了函数递归,a^b会得到不考虑进位的运算结果,(a&b)<<1得到进位的值,一起使用相当于两数相加。

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

需要先为数组分配空间,然后遍历数组,找到满足条件的数,补全部分代码如下:

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

7.静…态…

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

(1)static意为静态的,被static修饰时,外部链接属性变为内部链接属性,可以限制变量作用域,实现“记忆”功能,修改变量的存储位置和生命周期,使其能够在程序运行期间始终存在,并能够被所有类实例共享。
(2)与变量结合:生命周期延长:静态变量会在程序运行期间一直存在,而不仅仅是它们被声明的那个作用域。这意味着静态变量可以在函数调用之间保留它们的值,而不会在函数执行完毕后被销毁。
独立作用域:静态变量只属于它们被声明的文件或作用域,而不会在其他文件中被共享。这使得静态变量可以在不同函数或类之间提供一些隔离。
默认初始化:静态变量在第一次使用前会被自动初始化,通常为零或者 null。

(3)与函数结合:会使变量具有静态存储期,这意味着该变量在程序执行期间一直存在,而不仅仅是在函数调用期间存在。当函数执行完毕后,静态变量的值会保留下来,可以在下次函数调用时使用。

(4)与指针结合:保持指针的指向:当一个指针指向一个静态变量时,即使程序执行过程中有多个函数调用,该指针始终指向同一个静态变量。这是因为静态变量在程序执行期间一直存在,而动态分配的内存则在程序执行期间会被释放。隐藏指针的外部可见性:当一个指针被声明为静态时,它只能在声明它的文件内可见,而在其他文件中无法访问。这可以用来限制对指针的访问权限,从而保护数据的安全性。

8.救命!指针!

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

  • 指针数组:用来存放指针的数组;
  • 数组指针:指向数组的指针,用来存放数组的地址;
  • 函数指针:指向函数的指针,
  • int (*p)[10]是数组指针,p先与解引用操作符结合,说明其本质是指针;
  • const int* p[10]是指针数组,p先与[ ]结合,说明其本质是数组;
  • 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;
    }

输出结果:0 1 2
argc命令行参数的个数

argv[0]指向程序名,argv字符指针数组用于保存命令行参数

题中while循环当argc为0(循环中argc的值过大,超出int的范围时,argc会变为-1,最后一直加一加到0)时停止,i为-1(真),所以i自增为0,j=0+1=1,k++结果为2.

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

宏定义内容在预处理阶段由预处理器进行替换(会递归进行)宏定义最终在调用宏的地方把宏体原地展开

题中16/CAL(2)展开之后为16/222,结果不等于2,执行else部分

但接下来的语句(++nums*++nums*++nums)是未定义行为,应避免在一个表达式中多次进行自增操作,两次自增操作之间没有明确规定,不同编译器可能会有不同的行为,因此结果为初始赋值1。

11.克隆困境

试着运行一下程序,为什么会出现这样的结果?
直接将 s2 赋值给 s1 会出现哪些问题,应该如何解决?请写出相应代码。

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

s1=s2创建了一个新的变量,并将其地址赋给s1,但新变量没有自己的name成员,只是指向s2.name所指向的内存,所以修改s1的name时,实际上也修改了s2.name所指向的内存,会导致重复释放,而原先内存没有释放,会导致内存泄漏。

使用指针代替结构体,就可以直接操作原始变量而不是它的副本。

修改后的代码如下:

    struct Student{
        char *name;
        int age;
    };
    void initializeStudent(struct Student **student, const char *name,
    int age) {
    *student=(struct student*)malloc(sizeof(struct student));
    (*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);
    free(s1);
    free(s2);
    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);
    }

这道题首先定义了一个结构体变量,用来储存arr数组中的内容,后面将arr转换为指向structure结构体的指针并访问node.string成员。

小端存储是低字节存低地址,大端存储是高字节存低地址,一般计算机都是小端存储,因此内容反向输出,再根据对应的ASCII值得到最终结果"Welcome to XUPT,welcome to xiyou Linux Group [2023]"

13.GNU/Linux (选做)

注:嘿!你或许对 Linux 命令不是很熟悉,甚至你没听说过 Linux。但别担心,这是选做题,了解
Linux 是加分项,但不了解也不扣分哦!
你知道 cd 命令的用法与 / . ~ 这些符号的含义吗?
请问你还懂得哪些与 GNU/Linux 相关的知识呢~

  • cd命令:改变当前的工作目录;
  • /:要进行操作的目录;
  • .:当前目录;
  • ~:当前用户的主目录。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值