• C++输入输出流 cin/cout 及格式化输出简介


      C++ 可通过流的概念进行程序与外界环境( 用户、文件等 )之间的交互。流是一种将数据自源( source )推送至目的地( destination )的管道。在 C++ 中,与标准输入/输出相关的流可通过头文件 <iostream> 使用,与文件读写相关的流可以通过头文件 <fstream> 使用。这里即主要介绍 C++ 中与标准输入/输出相关的流 cin / cout .

    头文件与命令空间

      引入头文件

      在 C++ 中,想要使用相应的标准库功能,需要包括对应的库的头文件。故而想要使用标准输入/输出,程序中首先需要包含对应的头文件 iostream.

        #include <iostream>        //包含对应的头文件

      iostream 头文件中主要包括 4 个流对象的声明( 注意是对象而不是类型,实际是这些流对象的 extern 声明 )。在包含 iosteam 头文件后,用户可以直接使用标准输入流 cin、标准输出流 cout 、标准错误流 cerr 和标准日志流 clog. 其中 cin 为输入流对象,后三个为输出流对象。上述流对象在标准库中已经定义,故而后续用户在程序中可以直接使用上述对象进行操作而不需要另行定义。这里主要介绍的是标准输入/输出流( 即 cin / cout )的使用。

      引入名字

      C++ 中使用命名空间的概念加强程序的作用域和命名管理能力,标准库定义的所有名字都在命令空间 std 中( 只有声明命名空间后才能使用 )。若在程序中直接使用 cin 和 cout 进行输入输出操作而不包含对应的命名空间声明,在编译过程中会因无法找到对应的名字而报错。C++ 中使用 cin / cout 时有以下几种方法。

      1.在程序开始处( 头文件之后 )直接引入整个命名空间中的名字。所有 C++ 标准库的名字均位于命名空间 std 中,包括 cin 和 cout 。如下所示的语句即导入了命名空间 std 中的所有名字。则后续程序中可以直接使用命名空间 std 中所定义的名字,包括可以直接使用 cin 和 cout 进行输入/输出操作。直接导入整个命名空间中的所有名字书写起来十分简单,但在较复杂的程序中可能需要解决由于所有命名空间中名字的引入所产生的与其他已有名字的冲突问题。这种方法推荐在较为简单的程序设计/练习中使用。

        using namespace std;     //使得命令空间 std 中的名字均可直接使用

      2.在程序开始处( 头文件之后 )一次引入一个命名空间中的一个成员。通过 using 关键字 + 命名空间 :: 成员名字 的方式一次引入一个指定命名空间中的成员。若后续程序中需要同时使用标准输入和输出流,则需要分别引入这两个名字,如下所示。注意在这种方法中,每引入一个名字,均需要一条单独的语句。 

        using std::cin;    //引入命名空间 std 中的名字 cin,后续可以直接使用名字 cin 进行操作
        using std::cout;    //引入命名空间 std 中的名字 cout,后续可以直接使用名字 cout 进行操作

      3.直接在程序中通过 命名空间 :: 成员名字 的方式去引用某个名字,而不需做额外的声明。如在程序中需要使用 cin,则可以直接使用如下语句。其缺点在于程序中每一次使用 cin ,均要通过 std::cin 的方式进行使用。

        std::cin >> value ;            //直接通过 命名空间::名字 的方式使用 cin 

      后续的示例中,均默认已经导入了对应的头文件并使用上述方法2的方式引入了对应的名字,后续不再重复。

    基本输入/输出操作

      输入/输出运算符

      与流相关的输入输出操作通过操作符 "<<" 和 ">>" 完成。

      >> : 输入运算符,其左侧应该为输出流( ostream )类型的对象,右侧为需读取的数据,运算符的操作结果( 可理解为语句的返回值 )为左侧的流对象;

      << : 输出运算符,其左侧应该为输入流( istream )类型的对象,右侧为需输出的数据,运算符的操作结果( 可理解为语句的返回值 )为左侧的流对象;

      这里需要说明的是,cin 实际上即为 istream 类型的对象,而 cout 即为 ostream 类型的对象。故而 cin 和 cout 可以直接通过上述的输入/输出运算符进行输入/输出操作。cin/cout 的类型关系可以参考这里。  

      

      基本运算

      通过输入/输出运算符可以进行基本的输入/输出操作,cin / cout 可自动对 C++ 的基本数据类型如数值类型、字符(串)类型进行支持。下列示例即读入一个整型数据,并将其输出。这里注意,代码中的 endl 并不代表实际的数据,而是起到结束该行( 从而换行 )的作用.

        int value ;          //存放数据的整形
        cin >> value ;        //从标准输入读取一个整型,并存储在变量 value 中,输入使用 cin 和 >> 运算符
        cout << value << endl ;  //将 value 的值输出至标准输出,并结束该行,输出使用 cout 对象和 << 运算符

      输入/输出运算符也可以进行连续的输入/输出操作。以输入为例,可通过如下语句读取两个输入整型。  

        int a, b;
        cin >> a >> b;    //将读取的数据分别存储在 a 和 b 中

      这里主要解释第二行的语句。语句由左向右执行,首先执行 "cin >> a" 部分,读取一个整型数据至变量 a 中,由于 ">>" 运算符的返回值为其左侧对象,即返回 cin ,则后续继续执行的语句等价于 "cin >> b",即读取另外一个整型数据至变量 b 中。 

      使用 cin 读取键盘输入时,其与 c 中实现的 scanf 类似,一般采用行缓冲的方式处理用户数据。当缓冲区 stdin 中存在数据时,cin 直接从 stdin 中读取数据,而当 stdin 中没有数据时,cin 即等待用户的输入。具体而言,用户的键盘的输入首先被存储在临时缓冲区中,当临时缓冲区满或用户输入回车键,则上述输入数据被缓冲至 stdin 中,cin 即可从 stdin 中获取数据,未被读取的数据会一直存放在该缓冲区中,在下一次 cin 读取时,会直接从上述非空的 stdin 中读取数据,而不再等待用户输入。当 stdin 为空时,cin 会再次等待用户的输入。

      另注,cin 在读取目标数据时,会自动略过有效数据之前的空白字符( 回车、换行、制表符等 )。如用户输入为 "       32",通过 cin 读取到的整形为 32.在读取有效数据时,遇到第一个空白字符即认为当前读取的数据结束,如用户输入"32          ",cin 在读取数据 32 后遇到空格,即认为当前输入的整型数据为 32 。

      输入错误处理

       在类型匹配的情况下,cin 可通过 ">>" 运算符方便地针对不同的数据类型进行输入操作。如上面的例子中,cin 可以将用户输入的整型数据提取到整型变量 value 中。但当出现 a)数据类型不匹配,b)数据读取至结束位置( 用户输入的结束标记时 ) ,cin 会进入错误的状态。如程序中需要通过语句 cin >> value 将数据读取至整型的变量 value 中,而用户实际输入为一个字符串"abc"时,会产生输入错误。具体而言,会有以下几个问题:  

      1.使用的输入流 cin 会进入错误状态,流的错误状态可以通过 fail() 方法进行检测。当流进入错误状态时,cin.fail() 返回值为 true .

        int value;
        cin >> value;
        if( !cin.fail() )         //确保读取过程正确,再进行输出
        {
            cout << value << endl; //output        
        }            

      流对象本身也可以用作判断条件,当 cin 成功读取数据时,cin 在判断条件中表现为 true,当 cin 进入错误状态时,其在判断条件中表现为 false .例如,通过如下语句读取用户输入的任意多的数据.只要用户输入合法的整型值,cin 即可正确读取,>> 运算符返回的是其左侧的对象,也就是 cin .成功读取的 cin 在条件判断中的表现为 true,则可以进一步进行处理操作。当用户在终端中输入结束标记时( Linux 下的 Ctrl + d , Windows 下的 Ctrl + z 加回车 ),cin 即进入错误状态,从而使得 while 循环的条件为假,程序不再进行后续操作。

        int value;
        while( cin >> value )    
        {
            //process value
        }

      2.处于错误状态的流对象( 这里即 cin )会使得后续使用该流的语句的结果均无法预料。除对流对象状态的检测外,若后续程序流程中仍然使用了同一个流对象,则程序首先需对错误进行处理( 报告错误、清空缓冲区数据等 ),之后通过 clear 方法将流对象恢复至正常状态,后续即可继续使用。注意 clear 方法并不会清除缓冲区中的数据。如例子中,由于 "abc" 和整型不匹配的错误所造成的问题,字符串"abc"仍留在缓冲区中,若不做任何处理直接通过 cin.clear() 恢复流的状态,后续通过 cin 的读取会直接读取缓冲区中的 "abc" ,进而影响后续的程序执行流程.

    格式化输出

      与 C 语言提供的输出函数类似,除基本的数据交互功能外,C++ 的输出流还可通过输入/输出操作符( input/output manipulators )来满足数据输出的格式化需求。

      结束当前行——endl

      默认情况下,将数据输出时并不会自动换行,需要显式的通过 endl 来进行数据的换行。输出 endl 的效果是结束当前行( 也就是换行),并将设备管理的缓冲区中的内容刷新到设备中( 也就是保证数据真正的输出了,而不是停留在内存等缓冲位置中 )。 另外转义字符 ' ' 也可以满足换行的功能。下列代码均在数据输出后进行了换行,不同的是 endl 会保证将数据刷新到目的设备中( 而不是暂存在缓冲区中 )。

        cout << "hello world!" << '
    ';
        cout << "hello world!" << endl;

      设置输出间距( 宽度 )——setw

      需包含头文件 <iomanip> . setw 函数以一个整型( int )作为参数,设置进行输出时的流的宽度。默认情况下,使用流进行输出操作时流的宽度为0,亦即保持数据的原始长度。使用 setw( n ) 设置流宽度后,输出的数据至少会占有 setw( n ) 所设置的字节数 n。具体而言,当数据宽度不足 n 时,即通过填充( 默认使用空格填充 )使其宽度保持为 n 。注意,setw 函数的设置在调用 "<<" 或 ">>" 运算符后即恢复默认,也就是仅在其设置后的下一次输出操作中起作用。( setw 可设置输出流进行输出的数据的最小字节宽度,也可在输入流中设置进行输入的最大字节宽度 )

        int value = 45;
        cout << setfill( 'c' ) << setw( 10 ) << value << endl;    //输出的 45 将占用 10 个字节的宽度,使用 'c' 填充    =>  "cccccccc32"

      设置默认填充字符——setfill

      需包含头文件 <iomanip> . setfill 函数仅用于输出流中,其使用一个字符作为参数,并将其设置为输出流的填充字符( 默认为空格 )。在调用了 setfill 函数后,后续所有需要进行填充的场合( 如使用 setw 设置了输出的最小字节宽度 )均使用设置的字符进行填充。与 setw 函数不同的是,setfill 在设置完成后一直有效,除非重新设置。

        cout << setfill( '-' ) << setw( 10 ) << "" << endl;    //输出空字符串,由于输出的最小宽度经过 setw 设置为 10 ,故输出连续十个 ‘-’   =>  "----------"
        cout << setfill( ' ' )                       //恢复填充为空格

      设置填充字符位置——left / right

      设置经过填充后,原始数据的位置。设置为 left / right 时,原始数据位于填充后数据的左/右侧( 默认输出为 right ).  

        int value = 32;
        cout << left << setfill( 'c' ) << setw( 10 ) << value << endl;    //输出宽度为 10,数据位于输出的左侧       =>    "32cccccccc"

      参考 :

      Input/Output —— cppreference.com

      Input/output manipulators —— cppreference.com

  • 相关阅读:
    Delphi代码获取网卡物理地址三种方法 本文来自Delphi之窗,原文地址:http://www.52delphi.com...
    SQL SERVER 中实现公历到农历的转换
    cxgrid相同列合并显示
    rzCheckList.ItemChecked[Index]就可以判断指定节点地状态.
    为什么PING域名得到IP与实际公网IP不符
    如何让sql server2005和sql server2000共存安装在同一机器上
    如何编译通过 Raize 5.3 中的 RzBorder.pas
    u6升级到u890的过程
    技术部门到底该如何管理才能“和谐”
    在CXGRID中如何让字段能以0.00的格式显示
  • 原文地址:https://www.cnblogs.com/yhjoker/p/10942970.html
Copyright © 2020-2023  润新知