我可以将打印输出分配给变量吗?

Can I assign a printed output into a variable?

我正在弄清楚是否可以从打印输出中创建一个变量。例如

#include <stdio.h>

void main()
{
    char n[4] = "2445";
    int i;

    for (i = 0; i <= 4; i++)
    {
        printf("%c", n[i] + 1);
    }
}

输出是3556

我可以从输出中创建一个变量吗?

void main(void)
{
    char n[4]="2445";
    char result[5];
    size_t i;

    for(i = 0; i < sizeof(n); i++)
    {
        result[i] = n[i] + 1; 
    }
    result[i] = 0;

    printf("result = `%s`\n", result);
}

如果您只想输出单个字符,那么将数据放入 char 缓冲区,正如另一个答案所暗示的那样,是正确的答案。简单,快速,无非就是善良。

但是,如果您需要将不同类型格式化为字符串,并且希望将这些字符串保存在缓冲区中,那么您需要查看 sprintf 或更安全的 snprintf。使用此函数(它是 C99 的一部分,因此现在应该随处可用),您可以将格式化的字符串写入缓冲区。

当然,如果你不小心,你可能会在缓冲区的边界之外写入,这就是为什么 snprintf 函数比 sprintf 更好,如果你重复写入缓冲区,您需要跟踪下一个数据应该写入的位置以及缓冲区的长度。前者很容易跟踪,因为 snprintf returns 它写了多少(不包括零终止),所以你可以在每次写的时候用那个数量更新游标。

snprintf 与缓冲区一起使用,您的示例程序可能如下所示:

#include <assert.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>

struct buf
{
    size_t cursor, len;
    char data[];
};

struct buf *new_buf(size_t len)
{
    struct buf *buf = malloc(offsetof(struct buf, data) + len);
    assert(buf); // handle allocation error
    buf->cursor = 0;
    buf->len = len;
    return buf;
}

int main(void)
{
    char n[] = "2445";
    struct buf *buf = new_buf(sizeof n);

    for (size_t i = 0; i < sizeof n; i++)
    {
        buf->cursor +=
            snprintf(buf->data + buf->cursor, // where to write
                     buf->len - buf->cursor,  // how much you can write (snprintf takes care of '[=10=]')
                     "%c", n[i] + 1);         // what to write
    }

    printf("result = `%s`\n", buf->data);

    free(buf);
    return 0;
}

每次用 snprintf 写入后,buf->data + buf->cursor 指向 snprintf 放置零终端字节的位置,因此如果我们从该地址开始写入下一个数据,那么我们将添加一个下一个序列化后的数据值。如果您想要 spaces 或逗号或数据之间的任何其他内容,您可以修改格式字符串。

当然,使用该代码,如果 运行 超出缓冲区 space,就会出现问题。并不是说 snprintf 会越界写(就是 snprintf 中的 nsprintf 做的更好),只是不会写所有的数据你给它。它只会写入缓冲区的末尾,不会再写了。

如果要确保获取所有数据,则必须在缓冲区填满时增大缓冲区。

我们很幸运,如果我们用零调用它作为最大长度(我们在上面使用 buf->len - buf->cursor 的地方),我们可以获得格式化字符串的大小。如果我们这样做,并且我们不需要提供缓冲区,那么我们就会得到我们需要的长度。所以,在我们写入缓冲区之前,我们可以请求必要的长度,然后我们可以扩展缓冲区以获得足够的长度 space.

#include <assert.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>

#define MAX(A, B) (((A) > (B)) ? (A) : (B))

struct buf
{
    size_t cursor, len;
    char data[];
};

struct buf *resize_buf(struct buf *buf, size_t len)
{
    // don't allow len == 0; it will mess up doubling of lengths.
    len = len ? len : 1;

    struct buf *new_buffer = realloc(buf, offsetof(struct buf, data) + len);
    assert(new_buffer);

    // if it's a new buffer, set the cursor (otherwise it is already set)
    new_buffer->cursor = buf ? new_buffer->cursor : 0;
    new_buffer->len = len;
    return new_buffer;
}

struct buf *new_buf(size_t len)
{
    return resize_buf(0, len);
}

int main(void)
{
    char n[] = "2445";
    struct buf *buf = new_buf(0); // empty now, but we will resize

    for (size_t i = 0; i < sizeof n; i++)
    {
        // I've changed the formatting string to %d, so now you get the ASCII
        // codes plus one. They are no longer a single character long.
        // the +1 after snprintf() is for the zero sentinel '[=11=]'
        size_t needed = snprintf(0, 0, "%d ", n[i] + 1) + 1;
        if (needed > buf->len - buf->cursor)
        {
            buf = resize_buf(buf, MAX(buf->len + needed, 2 * buf->len)); // double the length
        }
        buf->cursor +=
            snprintf(buf->data + buf->cursor, // where to write
                     buf->len - buf->cursor,  // how much you can write (snprintf takes care of '[=11=]')
                     "%d ", n[i] + 1);        // what to write
    }

    printf("result = `%s`\n", buf->data);

    free(buf);
    return 0;
}

我将缓冲区增加两倍以获得线性时间增长,如果每次只以所需的大小增加缓冲区,它最终会以二次方 运行ning 时间结束。除此之外,没有什么复杂的;我们询问我们需要多少space,然后我们通过在需要时增加缓冲区来确保我们拥有它,然后我们像以前一样写入缓冲区。

不过,这并不是一个特别好的解决方案。我们绝对必须确保我们在第一次和第二次调用 snprintf 时创建的格式化字符串是相同的,否则我们真的会搞砸,缓冲区实现的细节从根本上暴露给用户。最好把它包在一个函数里。

如果我们想处理一般的格式化字符串,我们需要一个可变参数函数,如果你不习惯它们,它们可能看起来有点奇怪。但它们是这样工作的:你使用 ... 作为函数的最后一个参数,然后你可以使用 va_start() 获得指向附加参数的指针,你需要再次释放一些保存参数的数据结构va_end().

为了我们的目的,我们不需要对参数做任何特殊的事情,因为我们可以用它们调用 vsnprintf。该函数等同于 snprintf 接受这些结构而不是参数。我认为 vsnprintf 来自 C99,所以你也应该拥有它,但我承认它可能直到 C11 才存在(但你真的应该也拥有它)。

我们需要调用vsnprintf两次,我们需要设置参数并在每次调用前再次释放它们,但除此之外没有任何其他事情。

#include <assert.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>

#define MAX(A, B) (((A) > (B)) ? (A) : (B))

struct buf
{
    size_t cursor, len;
    char data[];
};

struct buf *resize_buf(struct buf *buf, size_t len)
{
    // don't allow len == 0; it will mess up doubling of lengths.
    len = len ? len : 1;

    struct buf *new_buffer = realloc(buf, offsetof(struct buf, data) + len);
    assert(new_buffer);

    // if it's a new buffer, set the cursor (otherwise it is already set)
    new_buffer->cursor = buf ? new_buffer->cursor : 0;
    new_buffer->len = len;
    return new_buffer;
}

struct buf *new_buf(size_t len)
{
    return resize_buf(0, len);
}

struct buf *write_to_buf(struct buf *buf, const char *restrict fmt, ...)
{
    va_list args;

    va_start(args, fmt);
    size_t needed = vsnprintf(0, 0, fmt, args);
    va_end(args);

    if (needed > buf->len - buf->cursor)
    {
        buf = resize_buf(buf, MAX(buf->len + needed, 2 * buf->len));
    }

    va_start(args, fmt);
    buf->cursor +=
        vsnprintf(buf->data + buf->cursor,
                  buf->len - buf->cursor,
                  fmt, args);
    va_end(args);

    return buf;
}

int main(void)
{
    char n[] = "2445";
    struct buf *buf = new_buf(0); // empty now, but we will resize

    for (size_t i = 0; i < sizeof n; i++)
    {
        buf = write_to_buf(buf, "%d ", n[i] + 1);
    }

    printf("result = `%s`\n", buf->data);

    free(buf);
    return 0;
}

这应该会让您了解如何创建一个可以写入通用格式字符串的缓冲区。我并没有声称此代码是稳定的或正确的接口类型或其他任何东西,我只是很快地完成了它,但它应该是开始的地方。