C语言中的fgets函数能否被用于处理非文件的字符串?

9
具体来说,这里的代码示例非常好用,但只有在字符串存储在文件中时才能正常运行。
有时我需要处理生成的字符串(存储在字符串变量中),但我无法让fgets的第三个参数与字符串变量一起使用,因为它是指向FILE结构的指针
或者可能有一个功能等效于fgets的函数可以用于字符串?
有什么建议吗?谢谢!

1
你介意你的字符串被修改吗? - Alok Singhal
我想是这样的。我只需要一种方法,使我链接的第一个代码示例可以从文件或字符串接受输入。 - Monte Hurd
7个回答

10

为了快速获得答案的精神,这是我刚写的“sgets”。它试图使用字符串输入来模拟fgets。

编辑:修复了Monte指出的一个错误(谢谢)。疯狂地打出一个实用程序,同时相信至少还有15个人有完全相同的想法正在疯狂做着同样的事情,并不会导致经过充分测试的代码。我做得不好。原始版本在后续调用中包含换行符。

char *sgets( char * str, int num, char **input )
{
    char *next = *input;
    int  numread = 0;

    while ( numread + 1 < num && *next ) {
        int isnewline = ( *next == '\n' );
        *str++ = *next++;
        numread++;
        // newline terminates the line but is included
        if ( isnewline )
            break;
    }

    if ( numread == 0 )
        return NULL;  // "eof"

    // must have hit the null terminator or end of line
    *str = '\0';  // null terminate this tring
    // set up input for next call
    *input = next;
    return str;
}


int main( int argc, char* argv[] )
{
    // quick and dirty test
    char *str = "abc\ndefghitjklksd\na\n12345\n12345\n123456\nabc\n\n";
    char buf[5];

    while ( sgets( buf, sizeof( buf ), &str ))
        printf( "'%s'\n", buf );
}

1
在你的示例main中的字符串文字上收到一个奇怪的编译器警告:“warning: deprecated conversion from string constant to ‘char*’”。Google是我的好朋友,但我还没有解决它。编辑:看起来我将其编译为C ++,因此出现了这个警告。但是,出于好奇心,我仍然对如何解决此问题感兴趣。 - Monte Hurd
听起来没问题。你说得对;我将其编译为 C 测试应用程序,所以没有注意到那些警告。 - Mark Wilkins
我建议使用第二个指针来指向原始字符串。否则,由于sgets修改了str,您将失去引用。 - Judge Maygarden
我发现了一个小的兼容性问题,到目前为止还没有解决 - 根据fgets文档,"换行符使fgets停止读取,但它被视为有效字符,因此它包含在复制到str的字符串中。" 问题是这段代码包括开头的换行符,而fgets包括末尾的换行符。我已经努力进行调整,但都无济于事 - 你有什么想法吗? - Monte Hurd
如果你把"char buf[5];"改成"char buf[32];",那么我上面提到的问题就很容易看出来了。 - Monte Hurd
显示剩余4条评论

5

标准的C库不提供该功能。

但AT&T的安全/快速I/O库确实可以启用内存流,并且还提供了包装器代码,以使用文件API及其扩展。最后更新是从2005年2月,因此他们可能已经解决了所有错误或者现在无法负担维护费用,因为卢克·威尔逊(Luke Wilson)在薪水单上:-(

该软件包可以在此处下载


sfio很棒!Joe Bob说要去看看。 - Norman Ramsey

3

使用管道,然后用fdopen打开管道以获取FILE *,然后从中读取。


#include <stdio.h>

int main (int argc, char *argv[])
{
    int pipes[2];
    FILE *write;
    FILE *read;
    char buffer[1000];

    pipe (pipes);

    read = fdopen (pipes[0], "r");
    write = fdopen (pipes[1], "w");
    fputs ("My\nlong\nstring\nin\nmany\nlines\n", write);
    fclose (write);

    while (fgets (buffer, sizeof(buffer), read) != NULL)
    {
        printf ("Found a line: %s", buffer);
    }

    fclose (read);

    return 0;
}

1
没有错误检查,使用模糊的变量名,很糟糕,非常糟糕!(-1) - dreamlax
当我的长字符串超过管道缓冲区时会发生什么? - jhfrontz
1
@jhfrontz:当调用 fputs 时,你的程序可能会阻塞。 - dreamlax
产出 "发现一行:单手拍掌的声音。" - jhfrontz

3

sscanf 可以实现这个功能,当然语义是不同的。


2
sscanf不会在任何空格上停止,而不仅仅是换行符吗? - Judge Maygarden

2
如果字符串已经存在于内存中,您可以在换行符上进行标记化(如果您愿意更改字符串并且不需要担心可重入性,则可以使用strtok,或者手动使用strchr并将其复制到单独的缓冲区中)。
但是,您将无法获得stdio函数通常提供的平台相关的换行符转换,因此如果内存中的字符串使用例如CRLF行终止符,则需要额外小心。

1

你所需要做的就是在字符串中执行线性搜索以查找行尾。以下是一个小程序,可以帮助你开始编写自己的字符串流类。

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

typedef struct StringStream StringStream;

struct StringStream {
    const char *data;
    const char *position;
};

StringStream *
stringstream_new(const char *data)
{
    StringStream *self = malloc(sizeof (StringStream));

    self->data = self->position = data;

    return self;
}

void
stringstream_delete(StringStream *self)
{
    free(self);
}

char *
stringstream_gets(char *s, int n, StringStream *self)
{
    const char * eol;
    int i, len;

    if (NULL == self->position || '\0' == *self->position)
        return NULL;

    eol = strchr(self->position, '\n');

    if (eol) {
        len = eol - self->position + 1;
        len = len <= n ? len : n - 1;

        for (i = 0; i < len; ++i)
            s[i] = *self->position++;

    } else {
        for (i = 0; *self->position && i < n; ++i)
            s[i] = *self->position++;
            if ('\0' == *self->position)
                self->position = NULL;
            else
                ++self->position;
    }

    s[i] = '\0';

    return s;
}

int
main(int argc, char * argv[])
{
    static const int LEN = 100;
    static const char TEST_STRING[] =
        "line 0\n"
        "line 1\n"
        "line 2\n"
        "line 3\n"
        "line 4\n"
        "line 5\n"
        "line 6\n"
        "line 7\n"
        "line 8\n"
        "line 9\n";

    StringStream *stream;
    char buf[LEN];

    stream = stringstream_new(TEST_STRING);

    while (stringstream_gets(buf, LEN, stream))
        printf("gets: %s\n", buf);

    stringstream_delete(stream);

    return 0;
}

0

我修改了fgets函数的源代码:

size_t  my_fgets( inBuf , n , outBuf )
unsigned char *inBuf;
size_t n;
unsigned char *outBuf;
{
    size_t len = 0;
    unsigned char *s;
    unsigned char *p, *t;

    if (n <= 0)             /* sanity check */
            return (-1);

    p =  inBuf;
    s = outBuf;

    n--;                    /* leave space for NUL */

    while (n != 0) {

        len = n;
        t = memchr((void *)p, '\n', strlen(p));

        //printf ("'p' found at position %d.\n", t -p + 1);

        if (t != NULL) {
            len = ++t -p;
            (void)memcpy((void *)s, (void *)p, len);
            s[len] = 0;
            return len;
        }

        (void)memcpy((void *)s, (void *)p, len);
        s += len;
        n -= len;

    }

    *s = 0;

    return len;

}

和主函数:

int main(void)
{
    char *inBuf = "this \n"
                  "is \n"
                  "test \n";

    char outBuf[1024];

    my_fgets(inBuf,strlen(inBuf),outBuf);
    printf("outBuf:%s \n",outBuf);

    return 0;
}

并输出:

outBuf:this 

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接