【问题标题】:Join strings in C with a separator用分隔符连接 C 中的字符串
【发布时间】:2019-09-30 01:57:59
【问题描述】:

我正在尝试编写一个函数来使用分隔符连接字符串。这是我目前所拥有的:

int main(void) {

    char * strings[] = {"A", "B", NULL};
    char ** copied_strings = malloc(sizeof strings);

    // Join strings with a separator
    char * separator = "XXX";
    size_t num_array_elements = (sizeof strings / sizeof * strings) - 1; // because last element is NULL
    size_t len_separator = strlen(separator);
    size_t len_strings = 0;
    for (int i=0; strings[i] != NULL ;i++) len_strings += strlen(strings[i]);
    size_t malloc_buffer_size = len_strings + (len_separator * (num_array_elements -1)) + 1;
    printf("Separator: %s | Len Array: %lu | Len Strings: %lu | Malloc Buffer Size: %lu\n", separator, num_array_elements, len_strings, malloc_buffer_size);
    char * joined_string_buffer = malloc(malloc_buffer_size);
    join_strings(joined_string_buffer, copied_strings, separator);

}

void join_strings(char * joined_string_buffer, char ** src, char * separator) {

    size_t sep_len = strlen(separator);

    while (*src) {
        size_t string_len = strlen(*src);
        for (int i=0; i<string_len; i++)
            *joined_string_buffer++ = (*src)[i];
        for (int i=0; i<sep_len; i++)
            *joined_string_buffer++ = separator[i];
        *src++;
    }

    *joined_string_buffer = '\0';

}

但是,我似乎没有正确地将字符复制到*joined_string_buffer。我如何在这里正确地加入字符串?

【问题讨论】:

  • I'm not properly copying the characters - 你根本没有复制字符
  • @qrdl 对,是的,如果你能解释为什么它没有复制以及我做错了什么或应该做的事情,那将非常有帮助。
  • 你从来没有在copied_strings里放过任何东西,你到底想加入什么?此外,在while(*src) 中没有对src*src 进行更改,你希望这个循环如何结束?
  • @HotLicks 这不是真的。
  • @TagC198 恐怕你弄错了。 *src 没有达到任何目标。 它永远不会改变

标签: c string function


【解决方案1】:

假设如果您要添加 分隔符,您只想将分隔符 between 放置在您的 strings 数组中,您将需要向您的 join_stings 函数添加条件逻辑,以便仅在您的 strings 数组中的第二个(和后续)字符串之前添加分隔符。

有很多方法可以解决这个问题,但鉴于您的 strings 数组有一个 sentinel NULL,您可以执行以下操作:

char *joinstr (const char **s, const char *sep)
{
    char *joined = NULL;                /* pointer to joined string w/sep */
    size_t lensep = strlen (sep),       /* length of separator */
        sz = 0;                         /* current stored size */
    int first = 1;                      /* flag whether first term */

    while (*s) {                        /* for each string in s */
        size_t len = strlen (*s);
        /* allocate/reallocate joined */
        void *tmp = realloc (joined, sz + len + (first ? 0 : lensep) + 1);
        if (!tmp) {                     /* validate allocation */
            perror ("realloc-tmp");     /* handle error (adjust as req'd) */
            exit (EXIT_FAILURE);
        }
        joined = tmp;                   /* assign allocated block to joined */
        if (!first) {                   /* if not first string */
            strcpy (joined + sz, sep);  /* copy separator */
            sz += lensep;               /* update stored size */
        }
        strcpy (joined + sz, *s++);     /* copy string to joined */
        first = 0;                      /* unset first flag */
        sz += len;                      /* update stored size */
    }

    return joined;      /* return joined string */
}

添加一个简短的main() 来测试上面的joinstr 函数,您可以这样做:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
...    
int main (void) {

    const char *strings[] = {"A", "B", NULL},
        *sep = "XXX";
    char *joined = joinstr (strings, sep);  /* join strings */

    printf ("%s\n", joined);    /* output joined string */
    free (joined);              /* free memory */
}

使用/输出示例

$ ./bin/joinwsep
AXXXB

内存使用/错误检查

在您编写的任何动态分配内存的代码中,对于分配的任何内存块,您都有 2 个职责:(1)始终保留指向起始地址的指针内存块,因此,(2) 当不再需要它时可以释放

您必须使用内存错误检查程序来确保您不会尝试访问内存或写入超出/超出分配块的边界,尝试读取或基于未初始化的值进行条件跳转,最后,以确认您释放了已分配的所有内存。

对于 Linux,valgrind 是正常的选择。每个平台都有类似的内存检查器。它们都易于使用,只需通过它运行您的程序即可。

$ valgrind ./bin/joinwsep
==17127== Memcheck, a memory error detector
==17127== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==17127== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==17127== Command: ./bin/joinwsep
==17127==
AXXXB
==17127==
==17127== HEAP SUMMARY:
==17127==     in use at exit: 0 bytes in 0 blocks
==17127==   total heap usage: 2 allocs, 2 frees, 8 bytes allocated
==17127==
==17127== All heap blocks were freed -- no leaks are possible
==17127==
==17127== For counts of detected and suppressed errors, rerun with: -v
==17127== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

始终确认您已释放已分配的所有内存并且没有内存错误。

查看一下,如果您还有其他问题,请告诉我。

【讨论】:

    【解决方案2】:
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    
    void join_strings(char* joined_string_buffer, const char* src[], const char* separator);
    
    int main(void) {
    
        const char* strings[] = { "A", "B", NULL };
        char** copied_strings = (char**) malloc(sizeof strings);
    
        // Join strings with a separator
        const char* separator = "XXX";
        size_t num_array_elements = (sizeof strings / sizeof * strings) - 1; // because last element is NULL
        size_t len_separator = strlen(separator);
        size_t len_strings = 0;
        for (int i = 0; strings[i] != NULL;i++) len_strings += strlen(strings[i]);
        size_t malloc_buffer_size = len_strings + (len_separator * (num_array_elements - 1)) + 1;
        printf("Separator: %s | Len Array: %lu | Len Strings: %lu | Malloc Buffer Size: %lu\n", separator, num_array_elements, len_strings, malloc_buffer_size);
        char* joined_string_buffer = (char*) malloc(malloc_buffer_size);
    
        join_strings(joined_string_buffer, strings, separator);
    
        // Result is AXXXBXXX
        printf("%s\n", joined_string_buffer);
    
    }
    
    void join_strings(char* joined_string_buffer, const char* src[], const char* separator) {
    
        size_t sep_len = strlen(separator);
    
        while (*src) {
            size_t string_len = strlen(*src);
            for (int i = 0; i < string_len; i++)
                *joined_string_buffer++ = (*src)[i];
            for (int i = 0; i < sep_len; i++)
                *joined_string_buffer++ = separator[i];
            *src++;
        }
    
        *joined_string_buffer = '\0';
    
    }
    

    我想你错误地选择了“join_strings”的第二个参数

    【讨论】:

    • 相当不错——它可以工作,尽管编译器设置很挑剔会给出一些警告。 copied_strings 变量没有被使用,所以它根本不应该存在。您可以在join_strings() 的循环中明智地使用size_t i 而不是int i,以避免出现有符号与无符号比较警告。 *src++ 应更改为 src++。您可以在打印后释放joined_string_buffer。但是您所做的确实解决了原始代码中最严重的问题。
    【解决方案3】:

    代码有很多问题,但大多是细节。不幸的是,在编程中,即使是细节也必须是正确的。这段代码修复了大部分问题——其中大部分已在 cmets 中发现。

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    static void join_strings(char *joined_string_buffer, char **src, char *separator);
    
    int main(void)
    {
        char *strings[] = { "A", "B", NULL };
        char *separator = "XXX";
        size_t num_array_elements = (sizeof strings / sizeof *strings) - 1;  // because last element is NULL
        size_t len_separator = strlen(separator);
    
        size_t len_strings = 0;
        for (int i = 0; strings[i] != NULL; i++)
            len_strings += strlen(strings[i]);
    
        size_t malloc_buffer_size = len_strings + (len_separator * (num_array_elements - 1)) + 1;
        printf("Separator: '%s' | Len Array: %zu | Len Strings: %zu | Malloc Buffer Size: %zu\n",
               separator, num_array_elements, len_strings, malloc_buffer_size);
        char *joined_string_buffer = malloc(malloc_buffer_size);
        if (joined_string_buffer == 0)
        {
            fprintf(stderr, "failed to allocate %zu bytes of memory\n", malloc_buffer_size);
            exit(EXIT_FAILURE);
        }
    
        join_strings(joined_string_buffer, strings, separator);
    
        printf("[[%s]]\n", joined_string_buffer);
        free(joined_string_buffer);
        return 0;
    }
    
    static void join_strings(char *joined_string_buffer, char **src, char *separator)
    {
        size_t sep_len = strlen(separator);
    
        while (*src)
        {
            size_t string_len = strlen(*src);
            for (size_t i = 0; i < string_len; i++)
                *joined_string_buffer++ = (*src)[i];
            for (size_t i = 0; i < sep_len; i++)
                *joined_string_buffer++ = separator[i];
            src++;
        }
    
        *joined_string_buffer = '\0';
    }
    

    示例输出

    Separator: 'XXX' | Len Array: 2 | Len Strings: 2 | Malloc Buffer Size: 6
    [[AXXXBXXX]]
    

    请注意,“分隔符”更严格地说是“终止符”——它出现在列表中最后一项之后以及两者之间。

    显示的代码或多或少是对问题中代码的直接修正。但是main()join_strings() 函数中的代码之间的工作分工并不好。这是更好的职责分离:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    static char *join_strings(char **src, char *separator);
    
    int main(void)
    {
        char *strings[] = { "A", "B", NULL };
        char *separator = "XXX";
        char *result = join_strings(strings, separator);
    
        printf("[[%s]]\n", result);
        free(result);
    
        return 0;
    }
    
    static char *join_strings(char **src, char *separator)
    {
        size_t len_sep = strlen(separator);
        size_t num_str = 0;
    
        for (size_t i = 0; src[i] != NULL; i++)
            num_str++;
    
        size_t len_str = 0;
        for (int i = 0; src[i] != NULL; i++)
            len_str += strlen(src[i]);
    
        size_t buf_len = len_str + (len_sep * num_str) + 1;
        printf("Separator: '%s' | Len Array: %zu | Len Strings: %zu | Malloc Buffer Size: %zu\n",
               separator, num_str, len_str, buf_len);
        char *result = malloc(buf_len);
        if (result == 0)
        {
            fprintf(stderr, "failed to allocate %zu bytes of memory\n", buf_len);
            exit(EXIT_FAILURE);
        }
    
        char *dst = result;
        for (size_t i = 0; src[i] != NULL; i++)
        {
            char *str = src[i];
            for (size_t j = 0; str[j] != '\0'; j++)
                *dst++ = str[j];
            for (size_t i = 0; i < len_sep; i++)
                *dst++ = separator[i];
        }
    
        *dst = '\0';
        return result;
    }
    

    此输出与以前相同 - 与之前 w.r.t 'separator' vs 'terminator' 相同的疣。

    【讨论】:

      【解决方案4】:

      一般来说,自己进行字节摆弄不是一个好主意。你的代码就是证明。以下是我看到的最明显的问题:

      • join_strings() 将结果字符串写入由调用者分配的缓冲区。调用者如何知道需要多少空间?它不知道。它只会做一个猜测,如果这个猜测太小,一切都会崩溃。

      • strlen() 调用需要迭代其整个参数字符串以找到终止的空字节。然后您的代码再次迭代相同的字节序列,将数据重新加载到 CPU 中。其中一个对输入字符串的传递可以被优化掉,但只有你自己,我认为还没有足够聪明的编译器来进行这种优化。

      • 您的join_strings() 实现是不必要的复杂。复杂性使代码难以思考,而难以思考的代码会吸引错误。

      • 复制main() 中的输入字符串是没有意义的。它只会增加复杂性,使代码难以推理,并吸引错误。没有它你会更好。

      好的,那你怎么能做得更好呢?好吧,通过使用适当的标准库函数。在这种情况下,最好的解决方案是使用流:

      void write_strings(FILE* stream, int count, char** src, char* separator) {
          for(int i = 0; i < count; i++) {
              fprintf(stream, "%s%s", src[i], separator);
          }
      }
      

      三行,一个循环,一个简单的fprintf()调用。

      此函数可用于向stderr 写入一些内容,例如:write_strings(stderr, 2, (char*){"Streams", "Rock"}, ". ") 将向错误流打印消息“Streams. Rock.”。

      但是如何将结果转换为字符串?简单的。使用open_memstream()

      char* join_strings(int count, char** src, char* separator) {
          char* result = NULL;
          size_t length = 0;
          FILE* stream = open_memstream(&result, &length);
          write_strings(stream, count, src, separator);
          fclose(stream);
          return result;
      }
      

      这个函数甚至没有循环,它基本上只是一个对write_strings()的调用。

      返回的字符串由open_memstream()自动分配,意味着溢出缓冲区的危险为零。仅此一项就足以使用。这也使得这个join_strings() 版本非常易于使用:调用者不需要决定分配多少空间,它只需放入字符串,然后取出连接的字符串。正是它所需要的。

      您可能会注意到我已经更改了函数的签名以包含一个计数参数。此更改不是必需的,但我的经验告诉我,它比NULL 终止列表更好:您很容易忘记添加NULL 终止符,但您不能忘记提供必需的函数参数。并且预先计算允许以更直接的方式实现许多算法。所以我默认总是将数组与相应的大小参数相关联,即使我可以根据需要避免它。

      无论如何,这一切如何改变函数的使用方式?不是真的那么多。最重要的变化是,我们不需要为main() 中的结果缓冲区计算大小。由于我们还可以剥离输入字符串的副本,更新后的main() 归结为这段简短的代码:

      int main(void) {
          char * strings[] = {"A", "B"};    //no terminator necessary
          size_t num_array_elements = sizeof strings / sizeof * strings;
          char * separator = "XXX";
      
          printf("Separator: %s | Len Array: %lu\n", separator, num_array_elements);
          char * joined_string_buffer = join_strings(num_array_elements, strings, separator);
      
          //Added by me: Print the result to stdout
          printf("Resulting string: \"%s\" (%d characters)\n", joined_string_buffer, strlen(joined_string_buffer));
      
          //Cleanup: Free the buffer allocated by `open_memstream()`
          free(joined_string_buffer);
      }
      

      请注意,整个代码(所有三个函数)中没有一个大小计算,也没有一个显式的malloc() 调用。有一个free(),但这就是所需的内存管理,繁重的工作由标准库函数执行。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-10-09
        • 2015-09-26
        • 1970-01-01
        • 1970-01-01
        • 2016-06-04
        • 1970-01-01
        • 2017-09-29
        • 2013-11-06
        相关资源
        最近更新 更多