C语言

11. C语言进阶--字符串+内存函数

Posted on 2022-02-11,21 min read

3. 字符串+内存函数

3.1 字符串函数

头文件:string.h

3.1.1 strlen

  • 原型

    // 功能:返回字符串的长度
    size_t strlen ( const char * str );
    // 形参:字符指针
    // 返回值:C字符串的长度
    
    
  • 原理

    size_t strlen (
            const char * str
            )
    {
            const char *eos = str;
    
            while( *eos++ ) ;
    
            return( eos - str - 1 );
    }
    
    
  • 说明

    • 字符串以 '\0' 作为结束标志,strlen函数返回的是在字符串中 '\0' 前面出现的字符个数(不包含 '\0' )

    • 参数指向的字符串必须要以 '\0' 结束

    • 函数的返回值为size_t,无符号整形

      #include <stdio.h>
      #include <string.h>
      
      int main()
      {
        if (strlen("abc") - strlen("abcdef") > 0)
        {
          printf(">\n");
        }
        else
        {
          printf("<=\n");
        }
      }
      // 输出结果: <=
      // "abc"的长度为3; "abcdef"的长度为6
      // 而输出结果表示"abc"的长度 小于 "abcdef"的长度
      // 原因:strlen("abc") - strlen("abcdef")
      //      (无符号整形)3 - (无符号整形)6 = (无符号整形)-3
      //      -3为 unsigned int 应表示为 一个正数 (FFFFFFFF-3)(十六进制)
      
      


  • 实例

    #include <stdio.h>
    #include <string.h>
    
    int main()
    {
      char* s = "hello world!";
      printf("%u\n", strlen(s)); // 12
      return 0;
    }
    // 输出结果:12
    


3.1.2 strcpy

  • 原型

    // 功能:拷贝字符串
    char * strcpy ( char * destination, const char * source );
    // 形参:destination 将要拷贝到的地址空间的指针
    //      source 被拷贝的字符串
    // 返回值:目标空间地址destination 
    
  • 原理

    char * strcpy(char * dst, const char * src)
    {
            char * cp = dst;
    
            while((*cp++ = *src++) != '\0')
                    ;               /* Copy src over dst */
    
            return( dst );
    }
    
    
    
  • 说明

    • 源字符串必须以 '\0' 结束

    • 会将源字符串中的 '\0' 拷贝到目标空间

    • 目标空间必须足够大,以确保能存放源字符串

      // strcpy并不会检查目标空间是否大于要拷贝的字符串
      // 而是直接强行拷贝 所以strcpy不安全
      // 源文件最前面加上一行代码 #define _CRT_SECURE_NO_WARNINGS
      
      
    • 目标空间必须可变

      // C语言中数组是可变的
      char arr[20] = { 0 };
      
      // 字符串是常量,不可变的
      char* s = "     ";
      
      // 所以目标空间可以是arr,而不能是s
      // C语言中,字符串的底层就是字符串首字符地址
      
  • 实例

    #define _CRT_SECURE_NO_WARNINGS
    #include <stdio.h>
    #include <string.h>
    
    int main()
    {
      char arr[20] = { 0 };
      // arr = "hello";
      // arr表示的是数组首元素的地址,是数组的标识
      // 而字符串 "hello" 应放到arr所指的空间中
      // 所以需要使用库函数strcpy将字符串拷贝放到arr中
      // 而不能直接赋值 arr = "hello";
      strcpy(arr, "hello");
      // 字符串使用本质上是字符串首字符的地址
      printf("%s\n", arr); // hello
    }
    

3.1.3 strcat

  • 原型

    // 功能:字符串追加
    char * strcat ( char * destination, const char * source );
    // 形参:source 被追加的字符串
    //      destination 目标字符串
    // 返回值:目标字符串 destination 地址
    
  • 原理

    char * strcat (
            char * dst,
            const char * src
            )
    {
            char * cp = dst;
    
            while( *cp )
                    cp++;                   /* find end of dst */
    
            while((*cp++ = *src++) != '\0') ;       /* Copy src to end of dst */
    
            return( dst );                  /* return dst */
    
    }
    
  • 说明

    • 源字符串必须以 '\0' 结束
    • 目标空间必须有足够的大,能容纳下源字符串的内容
    • 目标空间必须可修改
  • 模拟实现

    #define _CRT_SECURE_NO_WARNINGS
    #include <stdio.h>
    #include <assert.h>
    
    char* my_strcat(char* dst, const char* src)
    {
      assert(dst && src);
      char* cp = dst;
      while (*cp)
      {
        cp++;
      } // 找到dst中的'\0'
      //while (*cp++);  问题:为什么上一条while循环不能简写成这样
      while (*cp++ = *src++); // 将src复制到dst末尾
      return dst;
    }
    
  • 实例

    #define _CRT_SECURE_NO_WARNINGS
    #include <stdio.h>
    #include <string.h>
    
    int main()
    {
      char arr1[20] = "Hello \0#########";
      char arr2[] = "world";
      strcat(arr1, arr2);
      printf("%s\n", arr1); // Hello world
      return 0;
    }
    


  • 追加字符串自己

    #define _CRT_SECURE_NO_WARNINGS
    #include <stdio.h>
    #include <string.h>
    
    int main()
    {
      char arr[20] = "abcd";
      strcat(arr, arr);
      printf("%s\n", arr); // abcdabcd
      return 0;
    }
    
    // 输出结果:abcdabcd
    
    // 而自己实现的strcat函数会出现死循环
    // dst和src指向同一块区域 追加时 '\0' 被覆盖
    

3.1.4 strcmp

  • 原型

    // 比较字符串1和字符串2
    int strcmp ( const char * str1, const char * str2 );
    // 形参:str1 str2 分别是要比较的两个字符串
    // 返回值:整形  标准规定:
    //        <0 str1<str2
    //        >0 str1>str2
    //        =0 str1=str2
    //        VS中返回值为-1 0 1
    // C语言中不能直接使用使用关系运算符确定两个字符串的大小
    // 字符串比较,实际上是比较字符的ASCII码
    
  • 原理

    int strcmp (
            const char * src,
            const char * dst
            )
    {
            int ret = 0 ;
    
            while((ret = *(unsigned char *)src - *(unsigned char *)dst) == 0 && *dst)
                    {
                    ++src, ++dst;
                    }
    
            return ((-ret) < 0) - (ret < 0); // (if positive) - (if negative) generates branchless code
    }
    
  • 模拟实现

    #include <assert.h>
    
    int my_strcmp(const char* s1, const char* s2)
    {
      assert(s1 && s2);
      while (*s1 == *s2)
      {
        if (*s1 == '\0')
        {
          return 0;
        }
        s1++;
        s2++;
      }
      return *s1 - *s2;
    }
    
    
  • 实例

    #include <stdio.h>
    #include <string.h>
    
    int main()
    {
      printf("%d\n", strcmp("abc", "abcd")); // -1
      return 0;
    }
    
    // 输出结果:-1
    // 'a' 比较 'a' 结果 =
    // 'b' 比较 'b' 结果 =
    // 'c' 比较 'c' 结果 =
    // '\0' 比较 'd' 结果 <
    // 返回 -1
    

3.1.5 strncpy

  • 原型

    // 将n个字符拷贝到另一个字符串中
    char * strncpy ( char * destination, const char * source, size_t num );
    // 形参: destination 目标字符串
    //       source 被拷贝字符串
    //       num 拷贝字符个数
    // 返回值:destination
    
  • 原理

    char * strncpy (
            char * dest,
            const char * source,
            size_t count
            )
    {
            char *start = dest;
    
            while (count && (*dest++ = *source++) != '\0')    /* copy string */
                    count--;
    
            if (count)                              /* pad out with zeroes */
                    while (--count)
                            *dest++ = '\0';
    
            return(start);
    }
    
    
  • 模拟实现

    #include <assert.h>
    
    char* my_strncpy(char* destination, const char* source, size_t num) 
    {
      // 当 num 大于 source 长度时 
      // 当 num 大于 destination 长度时  
      // 当 num 等于 0
      assert(destination && source);
      char* ret = destination;
      char* src = source;
      while (num && (*destination++ = *source++))
      {
        num--;
      }
      if (num)
      {
        return ret;
      }
      while (num--)
      {
        *destination++ = '\0';
      }
      return ret;
    }
    
  • 实例

    #define _CRT_SECURE_NO_WARNINGS
    #include <stdio.h>
    #include <string.h>
    
    int main()
    {
      char dest[20] = { 0 };
      char src[] = "i like c.";
      strncpy(dest, src, 6);
      printf("%s\n", dest); // i like
      return 0;
    }
    
    // 输出结果:
    // i like
    
    

3.1.6 strncat

  • 原型

    // 功能:将n个字符追加到另一个字符串后
    char * strncat ( char * destination, const char * source, size_t num );
    // 形参: destination 目标字符串
    //       source 被追加字符串
    //       num 追加字符个数
    // 返回值:destination
    
    
  • 原理

    char * strncat (
            char * front,
            const char * back,
            size_t count
            )
    {
            char *start = front;
    
            while (*front++)
                    ;
            front--;
    
            while (count--)
                    if ((*front++ = *back++) == 0)
                            return(start);
    
            *front = '\0';
            return(start);
    }
    
    
  • 模拟实现

    #include <assert.h>
    
    char* my_strncat( char* front, const char* back, size_t count )
    {
        // count 大于 back 的字符数(包括'\0'在内)
        // count 等于 0 
        assert(front && back); // 断言 空指针
        char* ret = front;
        while (*front++); // 找到被追加字符串的结束位置 '\0'
        front--; // 回退到 '\0' 位置
        while (count--) // 循环 count 次
            if (!(*front++ = *back++)) // 将 back 复制到 被追加字符串末尾 
                return ret; // 遇到 '\0' 返回 ret (表示 已经将back的所有字符追加到front后面)
        *front = '\0'; // 字符串末尾补 '\0'
        return ret;
    }
    
  • 实例

    #include <stdio.h>
    #include <string.h>
    
    int main()
    {
      char dest[20] = "I like ";
      char src[] = "clang and golang";
      char* ret = strncat(dest, src, 5);
      printf("%s\n", ret); // I like clang
      return 0;
    }
    
    // 输出结果:
    // I like clang
    

3.1.7 strncmp

  • 原型

    // 功能:比较两字符串前n个字符
    int strncmp ( const char * str1, const char * str2, size_t num );
    // 形参:str1 str2 分别是要比较的两个字符串
    //      num 比较的字符个数
    // 返回值:整形(规定与strcmp相同)
    
    // 相对于 strcmp函数 更安全
    
    
  • 原理

    int strncmp
    (
        const char *first,
        const char *last,
        size_t      count
    )
    {
        size_t x = 0;
    
        if (!count)
        {
            return 0;
        }
    
        /*
         * This explicit guard needed to deal correctly with boundary
         * cases: strings shorter than 4 bytes and strings longer than
         * UINT_MAX-4 bytes .
         */
        if( count >= 4 )
        {
            /* unroll by four */
            for (; x < count-4; x+=4)
            {
                first+=4;
                last +=4;
    
                if (*(first-4) == 0 || *(first-4) != *(last-4))
                {
                    return(*(unsigned char *)(first-4) - *(unsigned char *)(last-4));
                }
    
                if (*(first-3) == 0 || *(first-3) != *(last-3))
                {
                    return(*(unsigned char *)(first-3) - *(unsigned char *)(last-3));
                }
    
                if (*(first-2) == 0 || *(first-2) != *(last-2))
                {
                    return(*(unsigned char *)(first-2) - *(unsigned char *)(last-2));
                }
    
                if (*(first-1) == 0 || *(first-1) != *(last-1))
                {
                    return(*(unsigned char *)(first-1) - *(unsigned char *)(last-1));
                }
            }
        }
    
        /* residual loop */
        for (; x < count; x++)
        {
            if (*first == 0 || *first != *last)
            {
                return(*(unsigned char *)first - *(unsigned char *)last);
            }
            first+=1;
            last+=1;
        }
    
        return 0;
    }
    
    
  • 实例

    
    int main()
    {
      char str1[] = "I like";
      char str2[] = "I like c";
      int ret = 0;
      ret = strncmp(str1, str2, 5);
      printf("%d\n", ret); // 0
      ret = strncmp(str1, str2, 7);
      printf("%d\n", ret); // -1
      return 0;
    }
    
    // 输出结果:
    // 0
    // -1
    

3.1.8 strstr

  • 原型

    // 功能:在字符串 str1 中查找第一次出现字符串 str2 的位置 不包含终止符 '\0'。
    const char * strstr ( const char * str1, const char * str2 );
          char * strstr (       char * str1, const char * str2 );
    // 参数:str1 str2 字符串
    // 返回值:str2 在 str1 中第一次出现的位置--指针
    
  • 模拟实现

    • 查找原理

      假设在字符串 "acdeef" 中查找 "ef" 查找步骤如下图:


    • 代码

      #include <assert.h>
      #include <stdio.h>
      
      char* my_strstr(const char* str1, const char* str2)
      {
        assert(str1 && str2);
        const char* s = str1;
        const char* s1 = NULL;
        const char* s2 = NULL;
        // C语言规定 如果str2为空字符串,返回str1
        if (*str2 == '\0')
        {
          return (char*)str1;
        }
      
        while (*s)
        {
          s1 = s;
          s2 = str2;
          while (*s1 && *s2 && (*s1 == *s2))
          // 或者 while ((*s2 != '\0') && (*s1 == *s2))
          // *s2 != '\0' 主要是防止越界访问
          {
            s1++;
            s2++;
          }
          if (*s2 == '\0')
          {
            return (char*)s;
          }
          s++;
        }
        return NULL;
      }
      
      // 测试函数
      void test()
      {
        char str1[] = "acdeef";
        char str2[] = "ef";
        char* ret;
        ret = my_strstr(str1, str2);
        printf("%s\n", ret); // ef
      }
      
      // main主函数
      int main()
      {
        test();
        return 0;
      }
      
  • 实例

    #include <stdio.h>
    #include <string.h>
    
    int main()
    {
      char str1[] = "i like c";
      char str2[] = "like";
      char* ret = strstr(str1, str2);
      printf("%s\n", ret);
      return 0;
    }
    // 输出结果:like c
    

3.1.9 strtok

  • 原型

    // 功能:分割字符串
    char * strtok ( char * str, const char * delimiters );
    // 参数:
    //     str2 字符串 定义了用作分隔符的字符集合
    //     str1 字符串 包含0个或多个由str2中一个或多个字符分割的标记
    
    
  • 说明

    • strtok函数找到str中的下一个标记,并将其用 '\0' 结尾,返回一个指向这个标记的指针。(注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改)
    • strtok函数第一个参数不为NULL时,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置(基于static)
    • strtok函数第一个参数为NULL时,函数将在同一个字符串中保存的位置开始,查找下一个标记
    • 如果字符串中不存在更多的标记,则返回NULL指针
  • 实例

    #define _CRT_SECURE_NO_WARNINGS
    #include <stdio.h>
    #include <string.h>
    
    int main()
    {
      char str[] = "he#ll*o wo#rl*d";
      char delimiters[] = "*#";
      // strtok会修改被分割字符串,所以创建临时变量
      char tmp[20] = { 0 };
      strcpy(tmp, str);
      for (
        char* s = strtok(tmp, delimiters); // 找到str中第一个标记 strtok函数将保存它在字符串中的位置
        s != NULL;  // 当s=NULL时剩余的字符串中没有更多的标记
        s = strtok(NULL, delimiters) // strtok 函数将在同一个字符串中保存的位置开始 查找下一个标记
        )
      {
        printf("%s\n", s);
      }
      return 0;
    }
    // 输出结果:
    // he
    // ll
    // o wo
    // rl
    // d
    
    

3.1.10 strerror

  • 原型

    // 功能:将错误码转换为错误信息
    char * strerror ( int errnum );
    // 参数:errnum 整形 由C语言提供的全部变量 存在于头文件 <errno.h> 中 调用库函数失败时 都会设置全局错误码
    // 返回值:转换的错误信息字符串第一个字符地址
    
  • 实例1

    #include <stdio.h>
    #include <string.h>
    
    int main()
    {
      char* str0 = strerror(0);
      printf("%s\n", str0);
      char* str1 = strerror(1);
      printf("%s\n", str1);
      char* str2 = strerror(2);
      printf("%s\n", str2);
      char* str3 = strerror(3);
      printf("%s\n", str3);
      char* str4 = strerror(4);
      printf("%s\n", str4);
      return 0;
    }
    
    
    // 输出结果:
    // No error
    // Operation not permitted
    // No such file or directory
    // No such process
    // Interrupted function call
    
    
  • 实例2

    #include <stdio.h>
    #include <string.h>
    #include <errno.h>
    
    int main()
    {
        FILE* pFile;
        pFile = fopen("unexist.ent", "r");
        if (pFile == NULL)
            printf("Error: %s\n", strerror(errno));
        //... 文件处理
        fclose(pf);
        pf = NULL;
        return 0;
    }
    
    // 输出结果:
    // Error: No such file or directory
    

3.1.11 perror

  • 原型

    // 功能:
    //    1. 将错误码转换为错误信息 strerror
    //    2. 将错误信息打印 printf
    
    void perror ( const char * str );
    // 参数:提示信息 会自动与错误信息拼接打印
    
    // 头文件 stdio.h 中
    
  • 实例

    #include <stdio.h>
    
    int main()
    {
        FILE* pFile;
        pFile = fopen("unexist.ent", "r");
        if (pFile == NULL)
            perror("Error");
        //... 文件处理
        fclose(pf);
        pf = NULL;
        return 0;
    }
    
    // 输出结果:
    // Error: No such file or directory
    
    

3.2 字符函数

头文件:ctype.h

3.2.1 字符分类

函数 如果他的参数符合下列要求 则返回真
iscntrl 任何控制字符
isspace 空白字符:空格' ',换页 '\f',换行 '\n',回车 '\r',制表符 '\t',垂直制表符 '\v'
isxdigit 十进制数字0~9
islower 十六进制数字,包括所有十进制数字,小写字母af,大写字母AF
islower 小写字母a~z
isupper 大写字母A~Z
isalpha 字母az或AZ
isalnum 字母或者数字,a~z, A~Z, 0~9
ispunct 标点符号,任何不属于数组或者字母的图形字符(可打印)
isgraph 任何图形字符
isprint 任何可打印字符,包括图形字符和空白字符

3.2.2 字符转换

  • 原型

    // tolower:将大写字符转换为小写字符
    int tolower ( int c );
    
    
    // toupper:将小写字符转换为大写字符
    int toupper ( int c );
    
    
  • 实例

    #include <ctype.h>
    #include <stdio.h>
    
    int main()
    {
      char str[] = "hello";
      int size = sizeof(str) / sizeof(str[0]);
    
      for (int i = 0; i < size; i++)
      {
        str[i] = toupper(str[i]);
      }
      printf("%s\n", str); // HELLO
    }
    
    // 输出结果:
    // HELLO
    

3.3 内存函数

头文件:string.h

3.3.1 memcpy

  • 原型

    // 功能拷贝不重叠内存(C语言规定实现拷贝不重叠内存即可,但是VS编译器中该函数也实现了拷贝重叠内存功能)
    void * memcpy ( void * destination, const void * source, size_t num );
    // 参数:destination 拷贝目标地址
    //      source 被拷贝数据地址
    //      num 拷贝的大小 单位:字节
    // 返回值:destination
    
  • 模拟实现

    #include <assert.h>
    
    void* my_memcpy(void* dest, const void* src, size_t num)
    {
      assert(dest && src);
      void* ret = dest;
      while (num--)
      {
        *(char*)dest = *(char*)src;
        dest = (char*)dest + 1;
        src = (char*)src + 1;
        // 不能 (char*)src++ 因为(type)类型转换优先级高于后置自增++操作符
        // 或者说 单目运算符 从右向左 先自增再类型转换 而类型转换前 src时无类型指针 不能自增
        // 可以这样改写 ((char*)src)++
      }
      return ret;
    }
    
    
  • 实例

    #include <stdio.h>
    #include <string.h>
    
    int main()
    {
      char arr[] = "abcdefg";
      memcpy(arr + 4, arr, 2);
      printf("%s\n", arr); // abcdabg
      return 0;
    }
    
    // 输出结果:
    // abcdabg
    

3.3.2 memmove

  • 原型

    // 功能:可拷贝重叠内存
    void * memmove ( void * destination, const void * source, size_t num );
    // 参数:destination 拷贝目标地址
    //      source 被拷贝数据地址
    //      num 拷贝的大小 单位:字节
    // 返回值:destination
    
    
  • 模拟实现

    • 分析

      // 对于一个数组:
      int arr[] = { 1,2,3,4,5,6,7 }; 
      // 想要将数组索引为1~4的数据复制到索引为3~6的位置
      // 使用memcpy函数(C标准规定)得到的结果是 { 1,2,3,2,3,2,3 }
      // 而预期结果是{  1,2,3,2,3,4,5 } 显然结果不同
      // 原因是 memcpy在拷贝时 如果拷贝内容重叠 会发生覆盖
      

      拷贝有两种情况,分别为从前向后和从后向前

      • 情况一

        如图,将红框中内容拷贝到篮框中,如果从前往后拷贝则不会发生覆盖,是预期结果;而从后往前拷贝则会发生覆盖,非预期结果。

      • 情况二

        如图,将红框中内容拷贝到篮框中,如果从前往后拷贝则会发生覆盖,非预期结果;而从后往前拷贝则不会发生覆盖,达到预期结果。

      • 情况三

        如图,将红框中内容拷贝到篮框中,无论是从前向后拷贝还是从后向前拷贝,并不会发生覆盖,都是预期结果。

      • 总结

        当目标地址和被拷贝地址重合时,如果目标地址在被拷贝地址之前,则从前向后拷贝;如果目标地址在被拷贝地址之后,则从后向前拷贝。

          当目标地址和被拷贝地址不重合时,拷贝顺序并不影响结果。
          显然:选择适当的拷贝顺序,就可以避免发生覆盖。
          方便起见,和重合统一划分为,如果目标地址在被拷贝地址之前,则从前向后拷贝;如果目标地址在被拷贝地址之后,则从后向前拷贝。
        
        • 代码
          #include <assert.h>
          
          void* my_memmove(void* dest, const void* src, size_t num)
          {
            assert(dest && src);
            void* ret = dest;
            if (dest>src)
            { // 从前向后拷贝
              while (num--)
              {
                *(char*)dest = *(char*)src;
                dest = (char*)dest + 1;
                src = (char*)src + 1;
              }
            }
            else
            { // 从后向前拷贝
              while (num--)
              {
                *((char*)dest + num) = *((char*)src + num);
              }
            }
            return ret;
          }
          
          // 测试函数
          void test()
          {
            char arr[] = "abcdefg";
            my_memmove(arr + 2, arr, 4);
            printf("%s\n", arr); // ababcdg
          }
          
          // main主函数
          int main()
          {
            test();
            return 0;
          }
          
          // 输出结果:
          // ababcdg
          
          
  • 实例

    #include <stdio.h>
    #include <string.h>
    
    int main()
    {
      char arr[] = "abcdefg";
      memmove(arr + 2, arr, 4);
      printf("%s\n", arr); // ababcdg
      return 0;
    }
    // 输出结果:
    // ababcdg
    

3.3.3 memcmp

  • 原型

    // 功能:内存比较
    int memcmp ( const void * ptr1, const void * ptr2, size_t num );
    // 参数:ptr1 ptr2 地址
    //      num 比较小大 单位:字节
    // 返回值:整形  标准规定:
    //        <0 str1<str2
    //        >0 str1>str2
    //        =0 str1=str2
    //        VS中返回值为-1 0 1
    
    
  • 实例

    #include <stdio.h>
    #include <string.h>
    
    int main()
    {
      int arr1[] = { 1,2,3 };
      int arr2[] = { 1,3,5 };
      int ret = 0;
      
      // 比较arr1和arr2内存空间中的前4字节
      ret = memcmp(arr1, arr2, 4);
      printf("%d\n", ret); // 0
      
      // 比较arr1和arr2内存空间中的前8字节
      ret = memcmp(arr1, arr2, 8);
      printf("%d\n", ret); // -1
      return 0;
    }
    
    // 输出结果:
    // 0
    // -1
    

3.3.4 memset

  • 原型

    // 功能:内存设置
    void * memset ( void * ptr, int value, size_t num );
    // 参数:ptr 地址
    //      value 值
    //      num 大小 单位:字节
    // 返回值:ptr
    
  • 实例

    #include <stdio.h>
    #include <string.h>
    
    int main()
    {
      char s[10] = { 0 };
      memset(s, 'a', 10);
      for (int i = 0; i < 10; i++)
      {
        printf("%c ", s[i]); // a a a a a a a a a a
      }
      return 0;
    }
    
    // 输出结果:
    // a a a a a a a a a a
    

下一篇: 2022→