将可变数量的参数打印到格式字符串

Printing a variable number of arguments to a format string

给定格式字符串、说明符数量的计数器变量和要输入的字符串数组,如何打印?

这是一个例子:

char *format_str = "str(%s)ing(%s)";
int count = 2;
char **specs = { [0] = "rts", [1] = "gni" };

因此,字符串列表分别与说明符的顺序对齐。打印时,最终结果将是:

"str(rts)ing(gni)"

是否可以编写一个函数来打印具有任何格式字符串和任意数量的说明符以及相应参数的字符串?我已经尝试使用 strtok()vsprintfsnprintf 等来做到这一点,但我仍然无法完全正确。

编辑:澄清一下,format_str 包含 count 个说明符,数组 specs 包含 count 个字符串。因此,建议的函数应该将 count 个字符串打印到 format_str.

据我所知,无法在 运行 时为 printf 提供不同数量的参数。

因此您必须自己构建输出字符串。

我不会转储所有代码,只会给你一些高层次的想法。

#define OUT_STR_SIZE 8192

char* outStr = calloc(OUT_STR_SIZE, 1);   // Allocate an output buffer
assert(outStr  != NULL);
char* tmp = format_str;  // tmp pointer to track how much of the format string
                         // that has been handled
size_t idx = 0;          // next position in output buffer to write
size_t str_idx = 0;      // index of next string to copy when %s is found

while(*tmp)  // Loop the whole format string
{
    if (*tmp = '%' && *(tmp+1) == 's')
    {
        // Copy a string to output buffer
        strcpy(&outStr[idx], specs[str_idx]);  // Append a string from specs
        idx = idx + strlen(str_idx);
        ++str_idx;
        tmp += 2;
    }
    else
    {
        // Copy a single char to output buffer
        outStr[idx] = *tmp;
        ++idx;
        ++tmp;
    }
}
assert(count == str_idx);  // Just checking that all %s was handled

printf("%s", outStr);

free(outStr);

代码有问题需要修复

输出字符串大小固定为 8192 个字符。如果这还不够,您需要在添加新字符时检查可用的 space,并在 space 中使用 运行 时使用 realloc

由于“\”

,代码将无法处理诸如“hello\%s%s”之类的格式字符串

我将把它留作 OP 的练习来解决这些问题。

如果你比较懒,可以这样做:

int func(const char *fmt, int count, const char **specs) {
    switch(count) {
    case 1: return printf(fmt, specs[0]);
    case 2: return printf(fmt, specs[0], specs[1]);
    case 3: return printf(fmt, specs[0], specs[1], specs[2]);
    // etc. for as many args you want to support
    }
}

如果你不懒惰,你应该自己解析 %s 格式化字符串(例如在其他答案中)。

正如其他人所说,没有直接的方法可以做到这一点。您可以构建自己的函数,以正确的格式说明符转储字符串的值。下面的函数为每个 %s 创建一个临时格式字符串,并使用 snprintf().

将其附加到较早的构建字符串
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAXBUF      4096

char *strmaker(char* format, int num_args, char** strings)
{
    char* prnt = calloc(sizeof(char), MAXBUF);
    int prnt_ct = 0;
    char* tmp_fmt = malloc(strlen(format) + 1); // Prepare for the worst case (format == tmp_fmt).
    int fmt_ct = 0;

    /* Append the strings to the prnt buffer */

    for (int i = 0; i < num_args; i++) {
        char* s_loc = strstr(format + fmt_ct, "%s");    // Search the format-string for string specifier (%s)
        if (s_loc == NULL)
            return prnt;

        int tmp_fmt_len = (int) (s_loc + 2 - format - fmt_ct);  // +2 for %s
        strncpy(tmp_fmt, format + fmt_ct, tmp_fmt_len); // Make tmp_fmt
        tmp_fmt[tmp_fmt_len] = '[=10=]';
        fmt_ct = fmt_ct + tmp_fmt_len;

        int p_return = snprintf(prnt + prnt_ct, MAXBUF - prnt_ct, tmp_fmt, strings[i]);   // If no error, return the number characters printed excluding nul (man page)

        if (p_return >= MAXBUF - prnt_ct)   // If buffer overflows (man page)
            return prnt;

        prnt_ct = prnt_ct + p_return;   // Update the index location.
    }

    return prnt;
}

int main(int argc, char *argv[]) // Pass format and arguments
{
    if (argc <= 1)
       return -1;

    char *s = strmaker(argv[1], argc - 2, argv + 2);
    printf("%s\n", s);
    free(s);

    return 0;
}

终端会话:

$ ./a.out '%s %s %s' 1 2 3 
1 2 3
$ ./a.out 'one %s two %s three %s' 1 2 3 
one 1 two 2 three 3
$ ./a.out 'one %s two %s three' 1 2 3 
one 1 two 2
$ ./a.out 'one %s two %s three %s' 1 2 
one 1 two 2

C 标准库不提供类似于 printf 的函数,这些函数处理作为数组提供的可变数量的参数。为了做你想做的事,你必须自己动手。

如果你想动态构造这样一个字符串,一个很好的旧 for(...) realloc() 循环是可行的方法。这是一个简单的实现(它可能会被进一步优化)。

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

char *my_sprintf(const char *fmt, size_t n, char *const *strings) {
    const char *fmt_start, *fmt_end;
    size_t i, len, prev_len, fmt_len, spec_len;
    char *res, *tmp;

    fmt_start = fmt;
    len = 0;
    res = NULL;

    for (i = 0; i < n; i++) {
        // Find position of next %s format specifier.
        fmt_end = strstr(fmt_start, "%s");
        if (fmt_end == NULL) {
            // Error out if not found.
            free(res);
            return NULL;
        }
        
        // Do some math...
        fmt_len = fmt_end - fmt_start; // Length of current format specifier segment.
        spec_len = strlen(strings[i]); // Length of current string.
        prev_len = len;            // Previous total length.
        len += fmt_len + spec_len; // New total length.

        // Increase the size of the final string.
        tmp = realloc(res, len + 1);
        if (tmp == NULL) {
            // Error out if realloc() fails.
            free(res);
            return NULL;
        }

        res = tmp;
        
        // Copy specifier segment and i-th string at the end of the final string.
        memcpy(res + prev_len, fmt_start, fmt_len);
        memcpy(res + prev_len + fmt_len, strings[i], spec_len);
        
        // Skip current specifier.
        fmt_start = fmt_end + 2;
    }

    // Copy last specifier segment (if needed).
    
    fmt_len = strlen(fmt_start);
    prev_len = len;
    len += fmt_len;

    tmp = realloc(res, len + 1);
    if (tmp == NULL) {
        free(res);
        return NULL;
    }

    res = tmp;
    memcpy(res + prev_len, fmt_start, fmt_len);
    res[len] = '[=10=]';

    return res;
}

int main(int argc, char **argv) {
    char *res = my_sprintf(argv[1], argc - 2, argv + 2);

    if (res != NULL) {
        puts(res);
        free(res);
    } else {
        puts("ERR");
    }

    return 0;
}

我特别喜欢这种方法,因为它有几个优点:

  1. 不需要事先知道结果字符串的长度,最终字符串是动态分配的。
  2. 无需修改作为参数提供的任何字符串。
  3. 使用 memcpy(),遍历整个最终字符串两次(一次检查长度,一次复制),这与涉及 strlen() + strcpy() 的解决方案不同,后者可能会迭代结果 三次 次(strcpy(dst, src) 的常见实现是 memcpy(dst, src, strlen(src) + 1))。
  4. 简单的错误检查以防说明符的数量不够(如果你不想 return NULL,你可以决定在 if 语句中做什么.