• Leecode刷题 412. Fizz Buzz——二级指针、字符串数组、malloc


    题目:

    给你一个整数 n ,找出从 1 到 n 各个整数的 Fizz Buzz 表示,并用字符串数组 answer(下标从 1 开始)返回结果,其中:

    answer[i] == “FizzBuzz” 如果 i 同时是 3 和 5 的倍数。
    answer[i] == “Fizz” 如果 i 是 3 的倍数。
    answer[i] == “Buzz” 如果 i 是 5 的倍数。
    answer[i] == i (以字符串形式)如果上述条件全不满足。

    来源:力扣(LeetCode)
    链接:https://leetcode.cn/problems/fizz-buzz
    著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

    知识点

    字符串数组

    C语言字符串数组——非常详细

    !!数组的存储—— 为什么要使用malloc申请内存呢

    这里就有一个很关键的问题,是为什么要使用malloc呢???
    为什么最开始没有想到使用malloc呢??
    何时使用或何时不使用malloc函数

    悬浮指针

    这里我们首先使用悬浮指针的概念来进行引入:
    在C语言中能否直接给指针指向的数据赋值?

    • 错误示例
    int * i;
    *i=1;
    
    • 1
    • 2

    这样不行,因为“能直接给指针指向的数据”赋值,而这里的p还没有指向,所以不能赋值,这种指针有个名字叫“悬浮指针”,是不能给它赋值的。

    • 正确示例:
    int a,*p=&a;
    *p=7;
    
    • 1
    • 2

    这样,p有指向了,指向a,这就相当于是给a赋值,所以可以。也就是说,p只有,指向了具体的地址的时候,才可以*p这样赋值。

    • 正确示例2
    int * i;i=(int*);
    i=malloc(sizeof(int));
    *i=1;
    
    • 1
    • 2
    • 3
    回到题目

    这里我们要返回的数组,是没有具体的变量给他去指定的,就需要开辟一块内存,才能在后续的过程中为其赋值。

    数组的存储——malloc 与calloc的区别

    calloc函数

    数组长度——strlen 与 sizeof

    实例比较

    关键字 sizeof 是一个单目运算符,而不是一个函数。与函数 strlen 不同,它的参数可以是数组、指针、类型、对象、函数等。
    同时,对 sizeof 而言,因为缓冲区已经用已知字符串进行了初始化,其长度是固定的,所以 sizeof 在编译时计算缓冲区的长度。也正是由于在编译时计算,因此 sizeof 不能用来返回动态分配的内存空间的大小。

    常用运算符sizeof()和strlen()函数这两种方式来计算字符串的长度。
    sizeof()的值是在编译时计算得到的,因此不能用于计算动态分配的内存空间大小。sizeof()可用于基本类型、结构体以及数组等静态分配的对象所占空间大小的计算,其返回值与内存中存储的内容无关。
    例如,在32位系统中,char类型变量占用的空间为一个字节 ,即sizeof(char)的值为1。而字符型指针char *的本质是一个int型变量,所以其占用的空间大小为四个字节,即sizeof(char *)的值为4。
    函数strlen()的函数原型为size_t __cdecl strlen(const char *); ,其声明位于头文件string.h中。 strlen()是在运行时计算的,其返回值为从给定的地址开始到遇见的第一个NULL之间的长度。 返回的长度并不包含NULL所占用的空间。在这里插入图片描述

    数据类型 size_t

    size_t 数据类型
    size_t 是一些C/C++标准在stddef.h中定义的,size_t 类型表示C中任何对象所能达到的最大长度,它是无符号整数。

    它是为了方便系统之间的移植而定义的,不同的系统上,定义size_t 可能不一样。size_t在32位系统上定义为 unsigned int,也就是32位无符号整型。在64位系统上定义为 unsigned long ,也就是64位无符号整形。size_t 的目的是提供一种可移植的方法来声明与系统中可寻址的内存区域一致的长度。

    size_t 在数组下标和内存管理函数之类的地方广泛使用。例如,size_t 用做sizeof 操作符的返回值类型,同时也是很多函数的参数类型,包括malloc 和strlen。

    字符串操作的函数

    字符串赋值——使用strcpy
    整数转换为字符串——法1:sprintf

    该函数包含在stdio.h的头文件中
    简单用法讲解
    细致用法讲解

    sprintf(*p, "%d", num); //将num转为字符串输入到 p 中
    
    • 1

    注意,这里在第二次编程,使用这个函数的时候,刚开始出错了,是执行出错,而不是编译出错

    在这里插入图片描述
    通过下面的图可以看出,是内存访问出错。
    在这里插入图片描述
    最后问题出现在,(下面是错误语句)

    sprintf(str[i-1],"%s",i);//将这里的%s改为%d之后通过
    
    • 1

    这句应该改为

    sprintf(str[i-1],"%d",i);//将这里的%s改为%d之后通过
    
    • 1

    区别就在于第二个参数,%d 和 %s 上,
    %% 印出百分比符号,不转换。
    %c 整数转成对应的 ASCII 字元。整数转为字符串时,使用的格式,十进制用%c,十六进制%x
    %d 整数转成十进位。
    %f 倍精确度数字转成浮点数。
    %o 整数转成八进位。
    %s 整数转成字符串。这里的说法可能不对,应该是字符串直接输出的时候,会用这个,整数想要输出,而且用十进制,还是要用%c
    %x 整数转成小写十六进位。
    %X 整数转成大写十六进位。

    第三个参数可以省略,下面是例子

    
    #include 
    
    int main()
    {
        char buf[1024] = { 0 };
    
        sprintf(buf,"www.codersrc.com\n");
        printf("%s", buf);
    
        sprintf(buf,"www.codersrc.com age:%d\n",17);
        printf("%s", buf);
    
        sprintf(buf,"www.codersrc.com age:%d name:%s\n",17, "zhangsan");
        printf("%s", buf);
    
        sprintf(buf,"www.codersrc.com age:%d name:%s height:%f\n",17, "zhangsan",1.75);
        printf("%s", buf);
    }
    
    /*
     输出:
    
     www.codersrc.com
     www.codersrc.com age:17
     www.codersrc.com age:17 name:zhangsan
     www.codersrc.com age:17 name:zhangsan height:1.750000
    
     */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    整数转换为字符串——法2:自己编写
    char* n2s(int num){
      	int size=1;
      	for(int i=num; i/=10; size++);
        char *s=(char*)malloc(size+1);
        s[size]='\0';
        for(int j=num;s[--size]=j%10+'0',j/=10;);//注意这里第二个for循环的第二个参数使用了两个表达式,用逗号隔开
        return s;
    }
    
    作者:intelligent-snyder3wp
    链接:https://leetcode.cn/problems/fizz-buzz/solution/by-intelligent-snyder3wp-o2dt/
    来源:力扣(LeetCode)
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    for(int i=num; i/=10; size++);这句其实就是判断位数,每次都去掉num的最后一位,然后让size++,就可以得到位数了,当i/10等于0的时候,就终止循环。例如:num=1234,第一次之后,num = 123,第二次之后12,第三次1,第四次0,此时,size = 4,然后终止。接下来申请内存,size+1,因为最后还要存一个/0,然后开始逆向,从个位开始,将整数转为字符串。注意这里第二个for循环的第二个参数使用了两个表达式,用逗号隔开。

    代码

    法1:规范申请内存

    /**
     * Note: The returned array must be malloced, assume caller calls free().
     */
    char ** fizzBuzz(int n, int* returnSize){
        char **str = (char **)malloc(sizeof(char *)*n);//开辟一段内存,存储二级指针所指向的一级指针的值,函数返回值是这段内存的首地址,作为二级指针的初值
    
        *returnSize = n;
        for(int i = 1;i <= n;i++)
        {
            str[i - 1] = malloc(sizeof(char)*9);//开辟内存,存储一级指针所指向的字符串的值(*9是因为fizzbuzz\0是9)
            if(i % 3 ==0 && i % 5 ==0)
                strcpy(str[i-1],"FizzBuzz");
            else if(i % 3 == 0)
                strcpy(str[i-1],"Fizz");
            else if(i % 5 == 0)
                strcpy(str[i-1],"Buzz");
            else    
                sprintf(str[i-1],"%d",i);//将这里的%s改为%d之后通过,
        }
        return str;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    二级指针与malloc

    需要注意的是,要想到使用malloc创建内存,存储二级指针所指向的一级指针的值,函数返回值是这段内存的首地址,作为二级指针的初值。然后,只是这段内存还不够,还需要利用二级指针的值,来继续创建内存,二级指针的变量名称为str,那么通过str[i-1],就是寻址到这个二级指针的内容,也就是一级指针,继续使用malloc函数,创建一个用于存储一级指针所指向的字符串的内存,返回值是这段内存的首地址,存到二级指针指向的第一个单元,也就是一级指针本身的值,利用一级指针的值去内存中寻址,得到的就是存在这个地址的字符串

    法2:自己编写整数转字符串函数

    
    /**
     * Note: The returned array must be malloced, assume caller calls free().
     */
    char * n2s(int num)
    {
        int size = 1;
        for(int i = num;i /= 10;size++);//计算字符串的位数
        char* s = (char*)malloc(size+1);
        s[size] = '\0';
        for(int i = num;s[--size] = i%10 + '0',i /= 10;);//注意这里第二个参数使用了两个表达式,用逗号隔开
        return s;
    }
    
    char ** fizzBuzz(int n, int* returnSize){
        char **str = (char **)malloc(sizeof(char *)*n);//开辟一段内存,存储二级指针所指向的一级指针的值
    
        *returnSize = n;
        for(int i = 1;i <= n;i++)
        {
            //str[i - 1] = malloc(sizeof(char)*9);//开辟内存,存储一级指针所指向的字符串的值
            if(i % 3 ==0 && i % 5 ==0)
                // strcpy(str[i-1],"FizzBuzz");
                str[i-1] = "FizzBuzz";
            else if(i % 3 == 0)
                // strcpy(str[i-1],"Fizz");
                str[i-1] = "Fizz";
            else if(i % 5 == 0)
                // strcpy(str[i-1],"Buzz");
                str[i-1] = "Buzz";
            else    
                str[i-1] = n2s(i);
        }
        return str;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    法3:提前定义好字符串,直接用地址赋值,减少sprintf的复制次数,不过并没有减少时间

    char** fizzBuzz(int n, int* returnSize){
        char ** str = (char**)malloc(sizeof(char*)*n);
        // char * fizz = malloc(5);
        // char * buzz = malloc(5);
        // char * fizzbuzz = malloc(9);
        // sprintf(fizz,"%s","Fizz");
        // sprintf(buzz,"%s","Buzz");
        // sprintf(fizzbuzz,"%s","FizzBuzz");
        char * fizz = "Fizz";
        char * buzz = "Buzz";
        char * fizzbuzz = "FizzBuzz";
        *returnSize = n;
        for(int i = 1;i <= n;i++)
        {
            if(i % 15 == 0)
                str[i-1] = fizzbuzz;
            else if(i % 3 == 0)
                str[i-1] = fizz;
            else if(i % 5 == 0)
                str[i-1] = buzz;
            else
            {
                str[i-1] = malloc(10);
                sprintf(str[i-1],"%d",i);
            }
        } 
        return str;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
  • 相关阅读:
    安全远程访问工具
    golang学习笔记——接口和继承比较1
    java计算机毕业设计springboot+vue企业的信息管理系统
    怎么管理好精力,让自己每天精力充沛
    【Hadoop】HDFS 原理
    react redux(一)
    【附源码】计算机毕业设计JAVA移动在线点菜系统服务端服务端
    leetcode-1964:找出到每个位置为止最长的有效障碍赛跑路线
    我要写整个中文互联网界最牛逼的JVM系列教程 | 「JVM与Java体系架构」章节:Java及JVM历史上的重大事件
    递归经典例题 --- 青蛙跳台阶(图文详解)
  • 原文地址:https://blog.csdn.net/lgyLGY35/article/details/126770505