用分隔符在C中连接字符串

时间:2019-09-30 01:57:59

标签: c string function

我正在尝试编写一个函数来使用分隔符对字符串进行连接。这是我到目前为止的内容:

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。我如何在这里正确连接字符串?

4 个答案:

答案 0 :(得分:1)

#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”的第二个参数时犯了一个错误

答案 1 :(得分:1)

代码中有很多问题,但大多数都是细节问题。不幸的是,在编程中,即使细节也必须正确。该代码解决了大多数问题,其中大部分已在注释中找到。

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

此输出与以前相同-疣与以前相同,带有“分隔符”与“终止符”。

答案 2 :(得分:0)

假设如果要添加 分隔符 ,则只想在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)

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

仔细检查一下,如果还有其他问题,请告诉我。

答案 3 :(得分:0)

通常,自己进行字节摆弄不是一个好主意。您的代码证明了这一点。这是我看到的最明显的问题:

  • 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()版本完全难以使用:调用方不需要决定要分配多少空间,它只需放入字符串,然后取出连接的字符串。正是它所需要的。

您可能会注意到,我已更改了函数的签名,以包括count参数。不需要进行此更改,但是我的经验告诉我,这是一个比NULL终止列表更好的选择:您可以轻易忘记添加一个NULL终止符,但是您不能忘记提供必需的功能参数。预先进行计数可以使许多算法更直接地实现。因此,我默认总是将数组与相应的size参数相关联,即使我愿意的话也可以避免。

无论如何,这一切如何改变功能的使用方式?并没有那么多。最重要的变化是,我们不需要为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()有一个,但这仅与所需的内存管理有关,繁重的工作由标准库函数执行。