【问题标题】:Printing a variable number of arguments to a format string将可变数量的参数打印到格式字符串
【发布时间】:2020-11-23 15:51:55
【问题描述】:

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

这是一个例子:

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

【问题讨论】:

  • with any format string & any number of specifiers & respective arguments? 我不明白。 “任何”是什么意思?不,%s 需要 char *,它不能采用其他类型。你能提供更多的例子吗?你能提供你尝试过的东西吗?即使它失败了,它也可以作为其他人的样板代码。您能否提供一个函数声明,说明您将如何看待界面?输入应该是什么? how could this be printed? printf(format_str, specs[0], specs[1]) 还不够吗?
  • 字符串是问题的一部分还是只是一个例子?
  • 格式字符串中%s的个数是否总是等于count
  • @4386427 是的。这才是重点。 format_str 包含 个说明符,**specs 包含 个字符串。因此,该函数会将 个字符串打印到 format_str。
  • 所以count 信息是多余的——元素的数量已经可以通过解析格式化字符串获得?老实说,这看起来像 XY 问题。

标签: c formatting printf variadic-functions


【解决方案1】:

正如其他人所说,没有直接的方法可以做到这一点。您可以构建自己的函数,以正确的格式说明符转储字符串的值。下面的函数为每个%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] = '\0';
        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

【讨论】:

    【解决方案2】:

    据我所知,无法在运行时为 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 个字符。如果这还不够,您需要在添加新字符时检查可用空间,并在空间不足时使用realloc

    由于 '\' ,代码将无法处理“hello\%s%s”等格式字符串

    我将把它作为 OP 修复这些问题的练习。

    【讨论】:

    • strcat(outStr, ...) 是一个糟糕的主意,因为它会使这样一个简单函数的成本与最终字符串的长度成二次方。
    【解决方案3】:

    如果你很懒,你可以这样做:

    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格式化字符串(例如在另一个答案中)。

    【讨论】:

      【解决方案4】:

      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] = '\0';
      
          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. 在说明符数量不足的情况下进行简单的错误检查(如果您不想只返回 NULL,您可以决定在这些 if 语句中执行什么操作。

      【讨论】:

        猜你喜欢
        • 2017-12-15
        • 2019-10-26
        • 1970-01-01
        • 2022-01-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-12-19
        相关资源
        最近更新 更多