C语言K&R圣经笔记 1.5字符输入和输出

本文介绍了C语言中处理字符数据的基本操作,包括使用getchar和putchar进行逐个字符的输入输出,以及如何编写程序实现文件拷贝、字符计数、行数计算和单词数统计,强调了标准库提供的输入输出模型和字符处理技巧。
摘要由CSDN通过智能技术生成

1.5字符输入和输出

现在考虑一组处理字符数据相关的程序。可以发现很多只是前面原型程序的扩展版本。

标准库支持的输入输出模型是简单的。文本输入和输出,不管它从哪里来到哪里去,是作为“字符流”处理的。“文本流”是分行的字符序列;每行包含0个或多个字符,最后跟着换行符。标准库的责任是使每个输入输出流遵循这个模型;使用标准库的C程序员不用担心程序外的行如何表示。

标准库提供几个函数来每次读或者写一个字符,其中getchar和putchar是最简单的。每调用一次,getchar就从文本流中读入“下一个输入的字符”,并将它作为返回值。就是说在 c = getchar()  之后,变量c包含了下一个输入的字符串。字符通常是从键盘输入的,从文件输入在第7章讨论。

putchar在每次调用时输出一个字符。puchar(c)   将整型变量c作为字符输出,通常是在屏幕上。putchar和printf可以是交替的,输出以它们被调用的顺序来显示。

 1.5.1 文件拷贝

有了getchar和putchar,就能在不知道更多关于输入输出的知识时写出大量有用的程序。最简单的就是每次逐个字符地把输入拷贝给输出:

        读一个字符
        当 (字符不是结束符时)
                输出刚读入的字符
                读入一个字符

转换成C语言就是:

#include<stdio.h>

/* 拷贝输入到输出,第一版 */
main()
{
    int c;

    c = getchar();
    while (c != EOF) {
        putchar(c);
        c = getchar();
    }

}

关系运算符!= 意思是 不等于。

在键盘上或者屏幕上表现为一个字符的东西,(在计算机领域)当然和其他东西一样,内部是以若干bit位来存储的。char类型是专门用来储存这样的字符数据的,但任何整型也可以用。我们这里使用int 是出于一个微妙但很重要的理由。

问题在于要把“输入结束”同合法的数据区分开。解决方法是当没有更多输入的值时,getchar返回一个独特的值,这个值不可能与任何真实的字符搞混。这个值叫做 EOF,代表“文件结束”。我们必须把c定义成可容纳getchar所有返回值的类型。不能用char是因为除了任何可能的char,c还必须能够包含EOF。

EOF是定义在stdio.h中的一个整数,但具体值是多少我们不关心,只要它不等于任何一个可能的char值。通过使用符号常量,我们保证程序不依赖特定的值。

有经验的C程序员可以把程序写的更精炼。在C中,任何赋值操作,如 c = getchar() 是一个表达式,有自己的值,即赋值完成后等号左边的值。这意味着赋值可以作为任何一个更大的表达式的一个部分。如果将c的赋值放在while循环的测试部分中,程序可以写成:

#include<stdio.h>

/* 拷贝输入到输出,第2版 */
main()
{
    int c;

    while ((c = getchar()) != EOF)
        putchar(c);

}

while得到一个字符,赋给c,然后测试这个字符是否文件结束标记。如果不是,执行while主体,打印字符。然后重复while。当最终到达输入结束时,while终止,main也终止。

这个版本以输入为中心——现在只引用一次getchar——且减小了程序。新程序更紧凑,且一旦掌握了这种写法,也会更易读。会经常看到这样的风格。(有可能会过度使用导致不可读的代码,我们要尽量避免)

条件测试中,赋值两边的括号是必须的,因为 != 的优先级高于 =,如果无括号,则关系测试 != 会在赋值 =之前发生。故 c  = getchar()  != EOF 就等价于   c = (getchar() != EOF),这就使c得到0 或 1 的值,取决于getchar()是否遇到文件结束符。(第二章有更多说明)

 

 1.5.2 字符计数

下个程序计算字符个数,它类似于拷贝程序

#include<stdio.h>

/*计算输入的字符数,第一版*/
main()
{
    long nc;

    nc = 0;
    while (getchar() != EOF)
        ++nc;
    printf("%ld\n", nc);

}

++nc; 语句中显示了新的一个操作符++,意思是“递增1”。你可以写 nc = nc+1,但++nc更精炼也通常更高效。对应的还有--运算符表示递减1。++和--可以前置也可以后置(++nc或nc++)。这两种方式在表达式中有不同的值,第二章会说明,但++nc和nc++的效果都是使nc加1。现在我们先一直用前缀方式。

这个程序用长整型long代替int。long至少是32个bit。尽管一些机器上int和long一样长,但有些机器上int是16位,最大值32767,更容易使int计数器溢出。打印语句中的 %ld 告诉printf对应的参数是长整型。

可以使用double(双精度浮点)来处理更大的数字。并用for语句代替while,来说明写循环的另一种方式:

#include<stdio.h>

/*计算输入的字符数,第2版*/
main()
{
    double nc;

    for (nc = 0; getchar() != EOF;  ++nc)
        ;
    printf("%.0f\n", nc);

}

在printf中,float和double都是用%f来打印的;%.0f表示不打印小数点和小数部分,后者在这个例子里固定是0。

for循环后面的主体是空的,因为所有工作都在测试和递增部分做完了。但C语法要求for一定要有一个主体。这个单独的分号,称为“空语句”,可以满足这个条件。将其单独放一行会比较显眼。

最后请注意,如果不输入字符,while和for的第一次测试会在调用getchar()时失效,程序会得到0,正确的答案。这很重要。while和for的一个好处是它们在循环顶部进行条件测试,在进入主体之前。如果不用做什么,则什么都不会做,甚至意味着不进入循环主体。程序应当在无输入时正确地工作。while和for循环保证了程序在边界条件下做合理的事情。

 

 1.5.3 计算行数

下一个程序计算输入行数。如前所述,标准库保证一个输入文本流看起来是行的序列,每行由一个换行符结束。因此,计算行数就是计算换行符:

#include <stdio.h>

main()
{
    int c, nl;

    nl = 0;
    while ((c = getchar()) != EOF)
        if (c == '\n')
            ++nl;
    printf("%d\n", nl);
}

while主体包含一个if,而if控制递增即++nl。if语句测试括号内的条件,如果为真则执行其后跟着的一条语句(或是大括号内的一组语句)。

双等于号==在C语言中的意思是“等于”(类似Pascal中的=和FORTRAN中的EQ)。这符号用来区分C的赋值符号=。注意:新手容易把==写成=,第二章会讲到,这也是符合语法的,所以得不到编译器的警告。

在单引号之间的一个字符代表一个整数值,它等于机器字符集中该字符的数值。这被称为“字符常量”,不过这只是写小整数的另一种方式而已。例如,'A'是一个字符常量;在ASCII字符集中它的值是65,即字符A的内部形式。当然用'A'比用65好:它的含义是明显的,也不依赖特定的字符集。

字符串常量中的转义序列在字符中也是适用的,因此 '\n'代表换行字符值,在ASCII中为10。必须注意'\n'是个单独的字符,在表达式中代表一个整数,而"\n"是一个字符串常量,正好只包含一个字符。这个主题在第二章会进一步讨论。

 1.5.4 计算词数

简单的说,单词是不包含空格、tab或换行的其他任何字符的序列。下面的程序计算行数、单词数和字符数。这是UNIX程序wc的简化版本。

#include <stdio.h>

#define IN    1   /* 在单词内 */
#define OUT   0   /* 在单词外 */

main()
{
    int c, nl, nw, nc, state;

    state = OUT;
    nl = nw = nc = 0;
    while ((c = getchar()) != EOF) {
        ++nc;
        if (c == '\n')
            ++nl;
        if (c == ' ' || c == '\n' || c == '\t') 
            state = OUT;
        else if (state == OUT) {
            state = IN;
            ++nw;
        }
    }
    printf("%d %d %d\n", nl, nw, nc);

}

每当程序遇到一个单词的首字母时,单词数加一。变量state记录程序当前是否在一个单词内;初始时不是,因此设为OUT。我们更爱用符号常量IN和OUT而不是1和0,因为这样使程序更容易读。在这种小程序里面两种写法当然差别不大,但是在更大的程序中,如果一开始就这样做,在清晰可读方面带来的收益,会远远高于多写的一点代码。你还会发现,如果“魔鬼数字”只存在于符号常量中时,对程序会做大量的修改会更轻松。

nl = nw = nc = 0; 将所有变量置为0。这并不是特例,而是基于如下事实得到的结果:赋值是一个表达式而且赋值是从右到左关联的。这写法等价于 nl = (nw = (nc = 0)); 

 操作符 || 意思是OR(或者),所以 if (c == ' ' || c == '\n' || c == '\t') 意思是,c是空格或者换行或者tab。对应地,还有个操作符&&表示 AND(且),优先级仅仅比||高。由&&或||连接的表达式是从左往右求值,而且保证在真或假确定时停止求值。如果c是空格,就没有必要再检查它是否换行或tab,所以程序也不会进行后面两个测试。这个规则在这个程序里面不重要,但在更复杂的情况下就特别重要了,我们很快会看到。

这个程序还展示了 else,即当if语句中的条件为假时采取的动作。通用形式为:

        if (表达式)
                语句1
        else
                语句2

一个if-else结构中的两个语句,有且仅有一个会被执行。若表达式为真,则语句1执行;否则执行语句2。这里的语句,可以是单条语句,也可以是大括号里的多条语句。在本例中,else之后是一个if,后者控制着大括号内的两条语句。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值