Печать переменного количества аргументов в строку формата - PullRequest
1 голос
/ 03 августа 2020

Учитывая строку формата, переменную счетчика для количества спецификаторов и массив вводимых строк, как это можно напечатать?

Вот пример:

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

Итак, список строк соответствует порядку спецификаторов. При печати конечный результат будет:

"str(rts)ing(gni)"

Может ли функция быть написана для печати такой строки с любой строкой формата и любым количеством спецификаторов и соответствующих аргументов? Я пытался сделать это, используя strtok(), vsprintf, snprintf et c, но все еще не могу понять это правильно.

EDIT: Чтобы уточнить, format_str содержит count количество спецификаторов и массив specs содержит count количество строк. Поэтому предлагаемая функция должна вывести count количество строк в format_str.

Ответы [ 4 ]

2 голосов
/ 03 августа 2020

Если вы ленивы, вы можете сделать:

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 (например, как в другом ответе).

2 голосов
/ 03 августа 2020

Как говорили другие, прямого способа сделать это нет. Вы можете создать свою собственную функцию, которая выводит значения строк в правильные спецификаторы формата. Функция ниже создает временную строку формата для каждого %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 голосов
/ 03 августа 2020

Насколько мне известно, невозможно указать другое количество аргументов для printf во время выполнения.

Поэтому вам придется самостоятельно построить строку вывода.

I ' Я не буду сбрасывать весь код, а только дам вам некоторые идеи на высоком уровне.

#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, чтобы исправить эти вещи.

0 голосов
/ 03 августа 2020

Стандартная библиотека C не предоставляет функций, подобных printf, которые работают с переменным количеством аргументов, предоставленных в виде массива. Чтобы делать то, что вы хотите, вам придется свернуть свою собственную.

Если вы хотите динамически построить такую ​​строку, старый добрый for(...) realloc() l oop - это путь к go. Вот простая реализация (ее, вероятно, можно было бы еще оптимизировать).

#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. Простая проверка ошибок в случае, если количество спецификаторов недостаточно (вы можете решить, что делать внутри этих if операторов, если вы не хотите просто возвращать NULL.
...