在 K&R 1.9 最长行的例子中,getchar() 是做什么的?

3

我似乎现在理解了这个程序,除了getline函数并不是很直观,因为它似乎将getchar()返回的所有内容复制到一个字符数组s[]中,但实际上从未用于任何重要的事情。

int getline(char s[], int lim)
{
    int c, i;
    for(i=0; i < lim - 1 && (c = getchar()) != EOF && c != '\n'; ++i)
        s[i] = c;
    if(c == '\n')
    {
        s[i] = c;
        ++i;
    }
    s[i] = '\0';
    return i;
}

这个函数可以很容易地忽略掉行s[i] = c;,因为这个函数真正做的就是计算字符数,直到EOF'\n'getchar()返回。

我真正不理解的是,为什么程序会按照以下主循环前进:
main()
{
    int len; /* current line length */
    int max; /* maximum length seen so far */
    char line[MAXLINE]; /* current input line */
    char longest[MAXLINE]; /* longest line saved here */

    max = 0;
    while ((len = getline(line, MAXLINE)) > 0)
        if (len > max)
        {
            max = len;
            copy(longest, line);
        }
    if (max > 0) /* there was a line */
        printf("%s", longest);
    return 0;
}

唯一的解释是,getchar()函数在用户输入完整行文本并敲击回车键后才会生效。因此,在运行时它似乎有效。程序进展方式是否如此?程序首先进入while循环,然后等待用户输入一行文本,一旦用户按下回车键,getline函数的for循环就会迭代。我觉得这应该是正确的,因为用户可以在输入期间输入backspace。我的问题是,程序究竟是如何前进的呢?全部都是由于getchar()函数吗?
当我在终端中按下ctrl-D时,会发生一些其他混乱的事情。如果我在新行的开头按下ctrl-D,程序将终止。如果我在填满一些文本的行的末尾按下ctrl-D,它不会终止,并且与按下enter键的方式不同。如果我在一行文本中多次按下ctrl-D,程序最终将终止。
这只是我的终端如何处理会话的方式,还是说这些都是我学习C时不必担心的事情?
我问这个问题的原因是我喜欢追踪程序以加深理解,但是getchar()函数让这变得棘手。
4个回答

3
getchar函数从标准输入读取一个字符。因此,如果你坐在终端前面,它会阻塞程序直到收到你键入的字符,然后才退出。但是当交互式时,标准输入是行缓冲的,所以直到按下回车之前,你键入的内容不会被程序处理。这意味着getchar可以持续读取所有你键入的字符,因为它们是从缓冲区中读取的。
你对该函数有误解。数组传递给函数,然后它将getchar读取的每个字符(除了EOF或换行符)存储在连续的元素中。这就是它的作用 - 不是计算字符的数量,而是将它们存储在数组中。
(*实际上传递的是指针,但是该函数仍然可以像处理数组一样处理它。)

1
这取决于您的系统如何处理Ctrl-D。有时您需要按两次才能发送EOF。 - teppic
@teppic:你可以暂时这样假设,但最终你会想知道s[]有多大,以及为什么sizeof s给出的是指针的大小。(我并不是在暗示你不理解这个。)严格来说,该函数将s视为指针;[]运算符是基于指针算术定义的。 - Keith Thompson
@KeithThompson - 我同意。我稍微澄清了一下答案。所谓的“处理”,我指的只是语法方面。 - teppic
这取决于您的系统如何处理Ctrl-D。有时您必须按两次才能发送EOF。这是非常明确定义的。在终端上,^D结束了“read”而不向终端缓冲区添加任何内容(与输入“enter”不同,在将\n添加到缓冲区后终止读取调用)。因此,如果您在终端上按下^D作为第一个按键,或者在输入enter之后立即按下它,或者在另一个^D之后立即按下它,“read”返回0,因为终端缓冲区为空。几乎所有POSIX软件都将其解释为EOF。一些软件可能会对不以\n结尾的行感到困惑。 - Jim Balter
@JimBalter - 再次强调,这只是简化了的说法。虽然我可能应该说“在非空行上一次不会生成EOF,但两次会”。 - teppic
显示剩余4条评论

3
在参数声明中(仅限此上下文),char s[] 实际上意味着 char *s。C标准的描述是:

将“类型数组”声明为参数应当被调整为“类型的限定指针”。

因此,s 实际上是一个指针,类型为 char*,当函数修改 s[i] 时,它正在修改 line 的第 i 个元素。

在调用时:
getline(line, MAXLINE)
line是一个数组,但在大多数情况下,数组表达式会被隐式转换为指向该数组第一个元素的指针。
这两条规则似乎阴谋一样地让数组和指针看起来像是C语言中同一种东西。实际上它们并不相同。指针对象包含某个对象的地址(或者指向任何对象都没有的空指针);而数组对象包含按顺序排列的元素。但大多数对于数组的操作是通过指向数组元素的指针进行的,在这些操作中使用指针算术运算从一个元素移动到下一个元素。
建议阅读(我经常这么说):comp.lang.c FAQ 的第6节。

+1 对于正确答案,基本上就是我所说的解决 OP 问题的方法,但文档要更好。 - Peter Wooster

1

数组被用于重要的事情:它由调用者提供并返回修改后的新内容。从合理的角度来看,填充数组是调用函数的目的。


0
那个数组(一个数组引用)实际上是一个指针,char s[]与char *s相同。因此它在该数组中构建其结果,这就是为什么它稍后在主函数中被复制的原因。在K&R中很少有任何“魔法”。

-1:数组不是指针,指针也不是数组。我建议您阅读comp.lang.c FAQ的第6节。 - pmg
我撤销了-1,但数组不是指针——在许多用法中,它们被转换为指向其第一个元素的指针。 - pmg
数组不是指针,但在参数声明的上下文中(仅限于该上下文),char s[]实际上是char *s - Keith Thompson
谢谢,数组引用,这个是因为它是一个函数参数,所以至少在K&R中是指针。 - Peter Wooster
@PeterWooster:“数组引用”是什么意思并不清楚。有两个相关的规则:将数组参数在编译时调整为指针参数,以及在大多数情况下将数组表达式隐式转换为指向数组第一个元素的指针。 - Keith Thompson
显示剩余5条评论

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