是否有一种快速的方法在字符串之间插入一个字符?

时间:2018-01-17 07:10:57

标签: c linux string

我编写了这个函数,它将从文件列表中生成一个字符串。 (例如,如果我有一个包含FileA.txtFileB.pngFileC的文件夹,我将获得此字符串的输出:FileA.txtFileB.pngFileC)。现在我想在每个文件名之间添加一个/字符。 (例如FileA.txt/FileB.png/FileC/)有没有办法在“一击”中做到这一点而不必重复相同的操作两次?

换句话说,有没有办法做类似的事情:

original_string = append2(original_string, new_string, '/');

而不是必须

append(original_string, new_string);
append(original_string, "/");

这是我写的参考函数:

/**
 * @brief Concatenate all file names in a file list (putting a '/' between each of them)
 * @param file_list The file list to serialize.
 * @return          A string containing all files in the file list.
 */
char *file_list_tostring(struct file_list *file_list) {
    char *final_string = NULL;
    size_t final_len = 0;
    struct file_node *list_iter = file_list->first;
    while (list_iter != NULL) {
        char *tmp = list_iter->filename;
        size_t tmp_len = strlen(tmp);
        char *s = realloc(final_string, final_len + tmp_len + 1); // +1 for '\0'
        if (s == NULL) {
            perror("realloc");
            exit(EXIT_FAILURE);
        }
        final_string = s;
        memcpy(final_string + final_len, tmp, tmp_len + 1);
        final_len += tmp_len;
        list_iter = list_iter->next;
    }
    return final_string;
}

也许有一种简单的方法在两个字符串之间插入一个字符

注意:我知道重复相同的操作两次没有错,我问这个问题是否有更好的方法这样做了!

2 个答案:

答案 0 :(得分:3)

是的,你可以做sprintf:

#include <stdio.h>

int main()
{

    char var1[] = "FileA.txt";
    char var2[] = "FileB.png";
    char var3[] = "FileC";

    char result[30];

    sprintf(result, "%s/%s/%s", var1, var2,var3);
    printf("result: %s\n", result);


    return 0;
}

结果是这样的:

result: FileA.txt/FileB.png/FileC

如果需要,变量结果可以是指针,并根据您的需要分配空间。

答案 1 :(得分:1)

正如Michael Burr在对该问题的评论中所提到的,最好是两次列表/数组。在第一遍中,计算所需字符串的总长度。接下来,分配整个字符串所需的内存。在第二遍,复制内容。不要忘记对字符串终止的空字节(\0)进行说明和追加。

请考虑以下示例函数dupcat()dupcats()

#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <stdio.h>

char *dupcat(const size_t count, const char *parts[])
{
    size_t  i, len = 0;
    char   *dst, *end;

    /* Calculate total length of parts. Skip NULL parts. */
    for (i = 0; i < count; i++)
        len += (parts[i]) ? strlen(parts[i]) : 0;

    /* Add room for '\0'.
       We add an extra 8 to 15 '\0's, just because
       it is sometimes useful, and we do a dynamic
       allocation anyway. */
    len = (len | 7) + 9;

    /* Allocate memory. */
    dst = malloc(len);
    if (!dst) {
        fprintf(stderr, "dupcat(): Out of memory; tried to allocate %zu bytes.\n", len);
        exit(EXIT_FAILURE);
    }

    /* Copy parts. */
    end = dst;
    for (i = 0; i < count; i++) {
        const char  *src = parts[i];
        /* We could use strlen() and memcpy(),
           but a loop like this will work just as well. */
        if (src)
            while (*src)
                *(end++) = *(src++);
    }

    /* Sanity check time! */
    if (end >= dst + len) {
        fprintf(stderr, "dupcat(): Arguments were modified during duplication; buffer overrun!\n");
        free(dst); /* We can omit this free(), but only in case of exit(). */
        exit(EXIT_FAILURE);
    }

    /* Terminate string (and clear padding). */
    memset(end, '\0', (size_t)(dst + len - end));

    /* Done! */
    return dst;
}

char *dupcats(const size_t count, ...)
{
    size_t   i, len = 0;
    char    *dst, *end;
    va_list  args;

    /* Calculate total length of 'count' source strings. */
    va_start(args, count);
    for (i = 0; i < count; i++) {
        const char  *src = va_arg(args, const char *);
        if (src)
            len += strlen(src);
    }
    va_end(args);

    /* Add room for end-of-string '\0'.
       Because it is often useful to know you have
       at least one extra '\0' at the end of the string,
       and we do a dynamic allocation anyway,
       we pad the string with 9 to 16 '\0',
       aligning 'len' to a multiple of 8. */
    len = (len | 7) + 9;

    /* Allocate memory for the string. */
    dst = malloc(len);
    if (!dst) {
        fprintf(stderr, "dupcats(): Out of memory; tried to allocate %zu bytes.\n", len);
        exit(EXIT_FAILURE);
    }

    /* Copy the source strings. */
    end = dst;
    va_start(args, count);
    for (i = 0; i < count; i++) {
        const char  *src = va_arg(args, const char *);
        /* We could use strlen() and memcpy() here;
           however, this loop is easier to follow. */
        if (src)
            while (*src)
                *(end++) = *(src++);
    }
    va_end(args);

    /* Sanity check. */
    if (end >= dst + len) {
        fprintf(stderr, "dupcats(): Arguments were modified during duplication; buffer overrun!\n");
        free(dst); /* We can omit this free(), but only in case of exit(). */
        exit(EXIT_FAILURE);
    }

    /* Add end-of-string '\0' (filling the padding). */
    memset(end, '\0', dst + len - end);

    /* Done. */
    return dst;
}


int main(int argc, char *argv[])
{
    char *result;

    result = dupcat(argc - 1, (const char **)(argv + 1));
    printf("Arguments concatenated: '%s'.\n", result);
    free(result);

    result = dupcats(5, "foo", "/", "bar", "/", "baz");
    printf("Concatenating 'foo', '/', 'bar', '/', and 'baz': '%s'.\n", result);
    free(result);

    return EXIT_SUCCESS;
}

dupcat()dupcats()都不会返回NULL:如果发生错误,它们会将错误消息打印到标准错误并退出。

dupcat()获取一个字符串数组,并返回一个动态分配的连接副本,其中至少有8个字节的nul填充。

dupcats()获取可变数量的指针,并返回一个动态分配的连接副本,其中至少有8个字节的nul填充。

两个函数都将NULL指针视为空字符串。对于这两个函数,第一个参数是要连接的字符串数。

(由于OP未显示struct file_liststruct file_node的定义,因此我没有费心去编写基于列表的版本。但是,从两个版本中的一个版本进行调整应该是微不足道的。示出。)

在某些情况下,从固定base部分构造有效路径的变体,其中一个或多个相关文件或目录名称已连接,POSIXy ./已删除且../已回溯(但不是base子树),非常有用。

如果仔细编写,它允许程序接受相对于特定子树的不受信任的路径。 (组合路径仅限于该子树,但符号链接和硬链接仍可用于转义子树。)

一种可能的实施方式如下:

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>

char *dynamic_path(const char *const subtree,
                   const size_t      parts,
                   const char       *part[])
{
    const size_t  subtree_len = (subtree) ? strlen(subtree) : 0;
    size_t        parts_len = 0;
    size_t        total_len, i;
    char         *path, *mark, *curr;

    /* Calculate the length of each individual part.
       Include room for a leading slash.
    */
    for (i = 0; i < parts; i++)
        parts_len += (part[i]) ? 1 + strlen(part[i]) : 0;

    /* Add room for the string-terminating '\0'.
       We're paranoid, and add a bit more padding. */
    total_len = ((subtree_len + parts_len) | 7) + 9;

    /* Allocate memory for the combined path. */
    path = malloc(total_len);
    if (!path) {
        errno = ENOMEM;
        return NULL;
    }

    /* If the user specified a subtree, we use it as the fixed prefix. */
    if (subtree_len > 0) {
        memcpy(path, subtree, subtree_len);
        mark = path + subtree_len;
        /* Omit a trailing /. We enforce it below anyway. */
        if (parts > 0 && subtree_len > 1 && mark[-1] == '/')
            --mark;
    } else
        mark = path;

    /* Append the additional path parts. */
    curr = mark;
    for (i = 0; i < parts; i++) {
        const size_t  len = (part[i]) ? strlen(part[i]) : 0;
        if (len > 0) {
            /* Each path part is a separate file/directory name,
               so there is an (implicit) slash before each one. */
            if (part[i][0] != '/')
                *(curr++) = '/';
            memcpy(curr, part[i], len);
            curr += len;
        }
    }

    /* Sanity check. */
    if (curr >= path + total_len) {
        /* Buffer overrun occurred. */
        fprintf(stderr, "Buffer overrun in dynamic_path()!\n");
        free(path); /* Can be omitted if we exit(). */
        exit(EXIT_FAILURE);
    }

    /* Terminate string (and clear padding). */
    memset(curr, '\0', (size_t)(path + total_len - curr));

    /* Cleanup pass.
       Convert "/foo/../" to "/", but do not backtrack over mark.
       Combine consecutive slashes and /./ to a single slash.
    */
    {
        char *src = mark;
        char *dst = mark;

        while (*src)
            if (src[0] == '/' && src[1] == '.' && src[2] == '.' && (!src[3] || src[3] == '/')) {
                src += 3; /* Skip over /.. */
                /* Backtrack, but do not underrun mark. */
                if (dst > mark) {
                    dst--;
                    while (dst > mark && *dst != '/')
                        dst--;
                }
                /* Never consume the mark slash. */
                if (dst == mark)
                    dst++;
            } else
            if (src[0] == '/' && src[1] == '.' && (!src[2] || src[2] == '/')) {
                src += 2; /* Skip over /. */
                if (dst == mark || dst[-1] != '/')
                    *(dst++) = '/';
            } else
            if (src[0] == '/') {
                src++;
                if (dst == mark || dst[-1] != '/')
                    *(dst++) = '/';
            } else
                *(dst++) = *(src++);

        /* Clear removed part. */
        if (dst < src)
            memset(dst, '\0', (size_t)(src - dst));
    }

    return path;
}

int main(int argc, char *argv[])
{
    char *path;

    if (argc < 2) {
        fprintf(stderr, "\nUsage: %s PREFIX [ PATHNAME ... ]\n\n", argv[0]);
        return EXIT_FAILURE;
    }

    path = dynamic_path(argv[1], argc - 2, (const char **)(argv + 2));
    if (!path) {
        fprintf(stderr, "dynamic_path(): %s.\n", strerror(errno));
        return EXIT_FAILURE;
    }

    printf("%s\n", path);
    free(path);

    return EXIT_SUCCESS;
}

请注意,我从头开始编写上述版本(并将其专用于public domain (CC0)),因此您应该在依赖于生产用途之前对其进行全面测试。 (我的意图是成为一个有用的示例或基础,它将帮助您根据自己的需要编写自己的实现。)

如果你发现任何错误或问题,请在评论中告诉我,以便我可以验证并修复。