zoukankan      html  css  js  c++  java
  • 《C语言程序设计:现代方法(第2版)》第3章 格式化输入/输出

    第3章 格式化输入/输出

    在探索难以实现的问题时,问题本身的简单性只会使情况更糟。

    scanf函数和printf函数是C语言编程中使用最频繁的两个函数,它们用来格式化输入和输出。正如本章要展示的那样,虽然这两个函数功能强大,但要用好它们却不容易。3.1节描述printf函数,3.2节则介绍scanf函数。但是这两节的介绍都不完整,完整的细节将留到第22章中介绍。

    3.1 printf函数

    printf函数被设计用来显示**格式串(format string)的内容,并且在该串中的指定位置插入可能的值。调用printf函数时必须提供格式串,格式串后面的参数是需要在显示时插入到该串中的值:

    printf(格式串, 表达式1, 表达式2, ...);
    

    显示的值可以是常量、变量或者更加复杂的表达式。调用printf函数一次可以打印的值的个数没有限制。

    格式串包含普通字符和**转换说明(conversion specification),其中转换说明以字符%开头。转换说明是用来表示打印过程中待填充的值的占位符。跟随在字符%后边的信息指定了把数值从内部形式(二进制)转换成打印形式(字符)的方法,这也就是“转换说明”这一术语的由来。例如,转换说明%d指定printf函数把int型值从二进制形式转换成十进制数字组成的字符串,转换说明%f对float型值也进行类似的转换。

    格式串中的普通字符完全如在字符串中出现的那样显示出来,而转换说明则要用待显示的值来替换。思考下面的例子:

    #include <stdio.h>
    
    int main() {
        int i, j;
        float x, y;
    
        i = 10;
        j = 20;
        x = 43.2892f;
        y = 5527.0f;
    
        printf("i = %d, j = %d, x = %f, y = %f
    ", i, j, x, y);
        
        return 0;
    }
    

    这个printf函数调用会产生如下输出:

    i = 10, j = 20, x = 43.289200, y = 5527.000000
    

    格式串中的普通字符被简单复制给输出行,而变量i、j、x和y的值则依次替换了4个转换说明。

    C语言编译器不会检测格式串中转换说明的数量是否和输出项的数量相匹配。下面这个printf函数对调用所拥有的转换说明的数量就多于要显示的值的数量:

    printf("%d %d
    ",i); /*** WRONG ***/
    

    printf函数将正确显示变量i的值,接着显示另一个(无意义的)整数值。函数调用带有太少的转换说明也会出现类似的问题:

    printf("%d
    ",i,j); /*** WRONG ***/
    

    在这种情况下,printf函数会显示变量i的值,但是不显示变量j的值。

    此外,C语言编译器也不检测转换说明是否适合要显示项的数据类型。如果程序员使用不正确的转换说明,程序将会简单地产生无意义的输出。思考下面的printf函数调用,其中int型变量i和float型变量x的顺序放置错误;

    printf("%f %d
    ",i,x); /*** WRONG ***/
    

    因为printf函数必须服从于格式串,所以它将如实地显示出一个float型值,接着是一个int型值。可惜这两个值都将是无意义的。

    3.1.1 转换说明

    转换说明给程序员提供了大量对输出格式的控制方法。另一方面,转换说明很可能很复杂且难以阅读。事实上,在本节中想要完整详尽地介绍转换说明是不可能的,这里只是简要介绍一些较为重要的性能。

    在第2章中已经看到,转换说明可以包含格式化信息。具体来说,我们可以用%.1f来显示小数点后带一位数字的float型值。更一般地,转换说明可以用%m.pX格式或%-m.pX格式,这里的mp都是整数常量,而X是字母。mp都是可选的。如果省略p,m和p之间的小数点也要去掉。在转换说明%10.2f中,m是10,p是2,而X是f。在转换说明%10f中,m是10,p(连同小数点一起)省去了;而在转换说明%.2f中,p是2,m省去了。

    最小字段宽度(minimum field width)m指定了要显示的最少字符数量。如果要显示的数值所需的字符数少于m,那么值在字段内是右对齐的。(换句话说,在值前面放置额外的空格。)例如,转换说明%d将以·123的形式显示数123(本章用符号·表示空格字符)。如果要显示的值所需的字符数12345,而不会丢失数字。在m前放上一个负号会导致左对齐;转换说明%-4d将以123·的形式显示123。

    精度(precision)p的含义很难描述,因为它依赖于转换说明符(conversion specifier)X的选择。X表明在显示数值前需要对其进行哪种转换。对数值来说最常用的转换说明符有以下几个。

    • d——表示十进制(基数为10)形式的整数。p指明了待显示的数字的最少个数(必要时在数前加上额外的零);如果省略p,则默认它的值为1。
    • e——表示指数(科学记数法)形式的浮点数。p指明了小数点后应该出现的数字的个数(默认值为6)。如果p为0,则不显示小数点。
    • f——表示“定点十进制”形式的浮点数,没有指数。p的含义与在说明符e中的一样。
    • g——表示指数形式或定点十进制形式的浮点数,形式的选择根据数的大小决定。p说明可以显示的有效数字(没有小数点后的数字)的最大数量。与转换说明符f不同,g的转换将不显示尾随的零。此外,如果要显示的数值没有小数点后的数字,g就不会显示小数点。

    编写程序时无法预知数的大小或者数值变化范围很大的情况下,说明符g对于数的显示是特别有用的。在用于显示大小适中的数时,说明符g采用定点十进制形式。但是,在显示非常大或非常小的数时,说明符g会转换成指数形式以便减少所需的字符数。

    除了说明符%d、%e、%f和%g以外,还有许多其他的说明符(整型说明符7.1节、浮点型说明符7.2节、字符说明符7.3节和字符串说明符13.3节)。我们将在后续章节中陆续进行介绍。转换说明符的全部列表以及转换说明符的其他性能的完整解释见22.3节。

    程序:用printf函数格式化数

    下面的程序举例说明了用printf函数以各种格式显示整数和浮点数的方法。

    /**
     * Prints int and float values in various formats
     */
    #include <stdio.h>
    
    int main() {
        int i;
        float x;
    
        i = 40;
        x = 839.21f;
    
        printf("|%d|%5d|%-5d|%5.3d|
    ", i, i, i, i);
        printf("|%10.3f|%10.3e|%-10g|
    ", x, x, x);
    
        return 0;
    }
    

    在显示时,printf函数格式串中的字符|只是用来帮助显示每个数所占用的空格数量;不同于%或,字符|对printf函数而言没有任何特殊意义。此程序的输出如下:

    |40|   40|40   |  040|
    |   839.210|8.392e+002|839.21    |
    

    下面仔细看一下上述程序中使用的转换说明。

    • %d——以十进制形式显示变量i,且占用最少的空间。
    • %5d——以十进制形式显示变量i,且至少占用5个字符的空间。因为变量i只占两个字符,所以添加了3个空格。
    • %-5d——以十进制形式显示变量i,且至少5个字符的空间。因为表示变量i的值不需要用满5个字符,所以在后续位置上添加空格(更确切的说,变量i在长度为5的字段内是左对齐的)。
    • %5.3d——以十进制形式显示变量i,且至少占用5个字符的空间并至少有3位数字。因为变量i只有2个字符长度,所以要添加一个额外的零来保证有3位数字。现在只有3个字符长度,为了保证占有5个字符,还要添加2个空格(变量i是右对齐的)。
    • %10.3f——以定点十进制形式显示变量x,且总共用10个字符,其中小数点后保留3位数字。因为变量x只需要7个字符(即小数点前3位,小数点后3位,再加上小数点本书1位),所以在变量x前面有3个空格。
    • %10.3e——以指数形式显示变量x,且总共用10个字符,其中小数点后保留3为数字。因为变量x总共需要9个字符(包括指数),所以在变量x前面有1个空格。
    • %-10g——既可以以定点十进制形式显示变量x,也可以以指数形式显示变量x,且总共用10个字符。在这种情况下,printf函数选择用定点十进制形式显示变量x。负号的出现强制进行左对齐,所以有4个空格跟在变量x后面。

    3.1.2 转义序列

    格式串中常用的代码 被称为转义序列(escape sequence)。转义序列(7.3节)使字符串包含一些特殊字符而不会使编译器引发问题,这些字符包括非打印的(控制)字符和对编译器有特殊含义的字符(如“)。后面会提供完整的转义序列表,现在先看一组示例。

    • 警告(响铃)符:a。
    • 回退符:。
    • 换行符: 。
    • 水平制表符: 。

    当这些转义序列出现在printf函数的格式串时,它们表示在显示中执行的操作。在大多数机器上,输出a会产生一声鸣响,输出会使光标从当前位置回退一个位置,输出 会使光标跳到下一行的起始位置,输出 会把光标移动到下一个制表符的位置。

    字符串可以包含任意数量的转义序列。思考下面的printf函数示例,其中的格式串包含了6个转义序列:

    printf("Item	Unit	Purchase
    	Price	Date
    ");
    

    执行上述语句显示出一条两行的标题:

    Item    Unit    Purchase
            Price   Date
    

    另一个常用的转义序列是"",它表示字符“。因为字符”标记字符串的开始和结束,所以它不能出现在没有使用上述转义序列的字符串类。下面是一个实例:

    printf(""Hello!"");
    

    这条语句产生如下输出:

    "Hello!"
    

    附带提一下,不能在字符串中只放置单独一个字符,编译器将认为它是一个转义序列的开始。为了显示单独一个字符,需要在字符串中放置两个\字符:

    printf("\"); /* prints one  character */
    

    3.2 scanf函数

    就如同printf函数用特定的格式显示输出一样,scanf函数也根据特定的格式读取输入。像printf函数的格式串一样,scanf函数的格式串也可以包含普通字符和转换说明两部分。scanf函数转换说明的用法和printf函数转换说明的用法本质上是一样的。

    在许多情况下,scanf函数的格式串只包含转换说明,如下例所示:

    int i, j;
    float x, y;
    
    scanf("%d%d%f", &i, &j, &x, &y);
    

    假设用户录入了下列输入行:

    1 -20 ,3 -4.0e3
    

    scanf函数将读入上述行的信息,并且把这些符号转换成它们表示的数,然后分别把1、-20、0.3和-4 000.0赋值给变量i、j、x和y。scanf函数调用中像“%d%d%f%f”这样“紧密压缩”的格式串是很普遍的,而printf函数的格式串很少有这样紧挨着的转换说明。

    像printf函数一样,scanf函数也有一些不易觉察的陷阱。使用scanf函数时,程序员必须检查转换说明的数量是否输入变量的数量相匹配,并且检查每个转换是否适合相对应的变量。与用printf函数一样,编译器无法检查出可能的匹配不当。另一个陷阱与符号&有关,通常把符号&放在scanf函数调用中每个变量的前面。符号&常常(但不总是)是需要的,记住使用它是程序员的责任。

    如果scanf函数调用中忘记在变量前面放置符号&,将会产生不可预知且可能是毁灭性的结果。程序崩溃是常见的结果。最起码不会把输入读进来的值存储到变量中,变量将保留原有的值(如果没有给变量赋初值,那么这个原有值可能是没有意义的)。忽略符号&是极为常见的错误,一定要小心!一些编译器可以检查出这种错误,并产生一条类似“format argument is not a pointer”的警告信息。(术语指针将在第11章定义,符号&用于创建一个指向变量的指针。)如果获得警告消息,检查一下是否丢失了符号&。

    调用scanf函数是读数据的一种有效但不理想的方法。许多专业C程序员会避免用scanf函数,而是采用字符格式读取所有数据,然后再把它们转换成数值形式。在本书中,特别是前面的几章将相当多地用到scanf函数,因为它提供了一种读入数的简单方法。但是要注意,如果用户录入了非预期的输入,那么许多程序都将无法正常执行。正如稍后将会看到的那样,可以用程序测试scanf函数(22.3节)是否成功读入了要求的数据(若不成功,还可以试图恢复)。但是,这样做对于本书的示例是不切实际的,因为这类测试将添加太多语句而掩盖掉示例的要点。

    3.2.1 scanf函数的工作方法

    实际上scanf函数可以做的事情远远多于目前为止已经提到的这些。scanf函数本质上是一种“模式匹配”函数,试图把输入的字符组与转换说明相匹配。

    像printf函数一样,scanf函数是由格式串控制的。调用时,scanf函数从左边开始处理字符串中的信息。对于格式串中的每一个转换说明,scanf函数从输入的数据中定位适当类型的项,并在必要时跳过空额。然后,scanf函数读入数据项,并且在遇到不可能属于此项的字符时停止。如果读入数据项成功,那么scanf函数会继续处理格式串的剩余部分;如果某一项不能成功读入,那么scanf函数将不再查看格式串的剩余部分(或者余下的输入数据)而立即返回。

    在寻找数的起始位置时,scanf函数会忽略空白字符(white-space character,包括空格符、水平和垂直制表符、换页符和换行符)。因此我们可以把数字放在同一行或者分散在几行内输入。考虑下面的scanf函数调用:

    scanf("%d%d%f%f",&i,&j,&x,&y);
    

    假设用户录入3行输入:

    1
    -20 .3
        -4.0e3
    

    scanf函数会把它们看成是一个连续的字符流:

    ··1¤-20···.3¤···-4.0e3¤
    

    (这里使用符号·表示空格符,用符号¤表示换行符。)因为scanf函数在寻找每个数的起始位置时会跳过空白字符,所以它可以成功读取这些数。在接下来的图中,字符下方的s表示此项被跳过,而字符下面的r表示此项被读取为输入项的一部分:

    ··1¤-20···.3¤···-4.0e3¤
    ssrsrrrsssrrssssrrrrr
    

    scanf函数“忽略”了最后的换行符,实际上没有读取它。这个换行符将是下一次scanf函数调用的第一个字符。

    scanf函数遵循什么规则来识别整数或浮点数呢?在要求读入整数时,scanf函数首先寻找正号或负号,然后读取数字知道读到一个非数字时才停止。当要求读入浮点数时,scanf函数会寻找一个正号或负号(可选),随后是一串数字(可能含有小数点),再后是一个指数(可选)。指数由字母e(或者字母E)、可选的符号和一个或多个数字构成。在用于scanf函数时,转换说明%e、%f和%g是可以互换的,这3种转换说明在识别浮点数方面都遵循相同的规则。

    当scanf函数遇到一个不可能属于当前项的字符时,它会把此字符“放回原处”,以便在扫描下一个输入项或者下一次调用scanf函数时再次读入。思考下面(公认有问题的)4个数的排列:

    1-20.3-4.0e3¤
    

    我们使用与以前一样的scanf函数调用:

    scanf("%d%d%f%f",&i,&j,&x,&y);
    

    下面列出了scanf函数处理这组新输入的方法。

    • 转换说明%d。第一个非空的输入字符是1;因为整数可以以1开始,所以scanf函数接着读取下一个字符,即-。scanf函数识别出字符-不能出现在整数内,所以把1存入变量i中,而把字符-放回原处。
    • 转换说明%d。随后,scanf函数读取字符-、2、0和.(句点)。因为整数不能包含小数点,所以scanf函数把-20存入变量j中,而把字符.放回原处。
    • 转换说明%f。接下来scanf函数读取字符.、3和-。因为浮点数不能在数字后边有负号,所以scanf函数把0.3存入变量x中,而将字符-放回原处。
    • 转换说明%f。最后,scanf函数读取支付-、4、.、0、e、3和¤(换行符)。因为浮点数不能包含换行符,所以scanf函数把(-4.0 X 10^3)存入变量y中,而把换行符放回原处。

    在这个例子中,scanf函数能够把格式串中的每个转换说明与一个输入项进行匹配。因为换行符没有读取,它将留给下一次scanf函数调用。

    3.2.2 格式串中的普通字符

    通过编写含有普通字符和转换说明的格式串能更进一步地理解模式匹配的概念。处理格式串中的普通字符时,scanf函数采取的动作依赖于这个字符是否为空白字符。

    • 空白字符。当在格式串中遇到一个或多个连续的空白的字符时,scanf函数从输入中重复读空白字符直到遇到一个非空白字符(把该字符“放回原处”)为止。格式串中空白字符的数量无关紧要,格式串中的一个空白字符可以与输入中任意数量的空白字符相匹配。(附带提一下,在格式串中包含空白字符并不意味着输入中必须包含空白字符。格式串中的一个空白字符可以与输入中任意数量的空白字符相匹配,包括零个)。
    • 其他字符。当在格式串中遇到非空白字符时,scanf函数将把它与下一个输入字符进行比较。如果两个字符相匹配,那么scanf函数会放弃输入字符而继续处理格式串。如果两个字符不匹配,那么scanf函数会把不匹配的字符放回输入中,然后异常退出,而不进一步处理格式串或者从输入中读取支付。

    例如,假设格式串是“%d/%d"。如果输入是

    ·5·/·96
    

    在寻找整数时,scanf函数会跳过第一个空格,把%d与5相匹配,把/与/相匹配,在寻找下一个整数时跳过一个空格,并且把%d与96相匹配。另一方面,如果输入是

    ·5·/·96
    

    scanf函数就会跳过一个空格,把%d与5相匹配,然后试图把格式串中的/与输入中的空格相匹配。但是二者不匹配,所以scanf函数把空格放回原处,把字符·/·96留给下一次scanf函数调用来读取。为了允许第一个数后边有空格,应使用格式串“%d / %d”。

    3.2.3 易混淆的printf函数和scanf函数

    虽然scanf函数调用和printf函数调用看起来很相似,但两个函数之间有很大的差异,忽略这些差异就是拿程序的正确性来冒险。

    一个常见的错误是:在printf函数调用时在变量前面放置&。

    printf("%d %d
    ",&i,&j); /*** WORNG ***/
    

    幸运的是,这种错误是很容易发现的:printf函数将显示一对样子奇怪的数,而不是变量i和j的值。

    在寻找数据项时,scanf函数通常会跳过空白字符。所以除了转换说明,格式串常常不需要包含字符。假设scanf格式串应该类似于printf格式串是另一个常见错误,这种不正确的假定可能引发scanf函数行为异常。我们来看一下执行下面这个scanf函数调用时,到底发生了什么:

    scanf("%d, %d",&i,&j);
    

    scanf函数首先寻找输入中的整数,把这个整数存入变量i中;然后,scanf函数将试图把逗号与下一个输入字符相匹配。如果下一个输入的字符时空格而不是逗号,那么scanf函数将终止操作,而不再读取变量j的值。

    printf格式串经常以 结尾,但是在scanf格式串末尾放置换行符通常是一个坏主意。对scanf函数来说,格式串中的换行符等价于空格,两者都会引发scanf函数提前进入到下一个非空白字符。例如,如果格式串是“%d ”,那么scanf函数将跳过空白字符,读取一个整数,然后跳到下一个非空白字符处。像这样的格式串可能会导致交互式程序一直“挂起”直到用户输入一个非空白字符为止。

    程序:分数相加

    为了显示scanf函数的模式匹配能力,考虑读入由用户键入的分数。分数通常的形式为分子/分母。scanf函数允许读入整个分数,而不用将分子和分母视为整数分别读入。下面的分数相加程序体现了这一方法。

    addfrac.c

    /**
     * Adds two fractions
     */
    #include <stdio.h>
    
    int main() {
        int num1, denom1, num2, denom2, result_num, result_denom;
    
        printf("Enter first fraction:");
        scanf("%d/%d", &num1, &denom1);
    
        printf("Enter second fraction:");
        scanf("%d/%d", &num2, &denom2);
    
        result_num = num1 * denom2 + num2 * denom1;
        result_denom = denom1 * denom2;
        printf("The sum is %d/%d
    ", result_num, result_denom);
    
        return 0;
    }
    

    运行这个程序,可能的显示如下:

    Enter first fraction:5/6
    Enter second fraction:3/4
    The sum is 38/24
    

    注意,结果并没有化为最简分数。

  • 相关阅读:
    请求格式
    表格操作laytpl
    layui的弹出框
    layui表格
    js概念
    栈,队列
    数据结构--线性数据结构
    Symbol详情: 在不支持es6的浏览器上,通过Babel转译
    jquery中用bootstrap中的表单验证及提交
    layui的layer报错 layer is not defined at checkUse
  • 原文地址:https://www.cnblogs.com/shenhuanjie/p/11431045.html
Copyright © 2011-2022 走看看