• 【C++】借助cout进行控制台文本输出的基本原理及复杂格式控制方法


    建议老师们将本文收藏,待学生的需要时发给学生,免费口舌。

    本文引用自作者编写的下述图书; 本文允许以个人学习、教学等目的引用、讲授或转载,但需要注明原作者"海洋饼干叔
    叔";本文不允许以纸质及电子出版为目的进行抄摘或改编。
    1.《Python编程基础及应用》,陈波,刘慧君,高等教育出版社。免费授课视频 Python编程基础及应用
    2.《Python编程基础及应用实验教程》, 陈波,熊心志,张全和,刘慧君,赵恒军,高等教育出版社Python编程基础及应用实验教程
    3. 《简明C及C++语言教程》,陈波,待出版书稿。免费授课视频

    在C++里,我们使用cout进行控制台文本输出。这在学习编程的阶段很常用,但在真实的工作场合却极少使用,毕竟大部分的应用程序都是基于图形界面,而不是终端的。甚至,在C/C++的某些应用场合,比如单片机编程里,嵌入式设备甚至连屏幕都没有。

    考虑到部分OJ系统中的在线编程题可能对输出格式作出精细要求,这里我们对cout控制输出进行“详细”讨论:包括cout的基本工作原理,以及通过cout进行精细格式输出的方法。

    对工作原理不感兴趣,只想快速知道HOW的有编程经验的读者请直接阅读本文的第6部分。

    1. cout基本工作原理

    下述代码可以帮助我们理解通过cout的插入操作符(insertion operator<<)进行控制台文本输出的基本原理。

    //Project - COUT
    #include 
    using namespace std;
    
    int main() {
        cout << "pi = " << 3.14159 << endl;
        operator<<(cout,"pi = ").operator<<(3.14159).operator<<(endl);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    上述代码的执行结果为:

    pi = 3.14159
    pi = 3.14159
    
    • 1
    • 2

    在iostream头文件中,很容易找到cout的定义。cout是一个类型为ostream的对象,其被连接到了标准输出流,即控制台。

    extern ostream cout;    /// Linked to standard output
    
    • 1

    上述程序的第6行与第7行完全等价。相关代码的执行过程如下:

    • cout << “pi = “的实质是执行了一个名为operator<<( )的函数,其中,第1个参数是cout对象,第2个参数是”pi = “。这个函数把第2个参数的字符串输出到第1个参数所代表的控制台中。同时,该函数返回了cout的引用作为函数执行的结果。
    • … << 3.141519则以前述函数调用所返回的cout引用作为基础,执行其成员函数operator<<( )。该成员函数存在多个函数名重载的版本,其中一个版本接受一个double作为参数,并将double的值输出到cout。同样地,本次函数调用也返回了cout的引用作为结果。
    • 类似地,… << endl同样对应cout.operator<<( )函数的一次执行,该成员函数的一个重载版本接受endl作为参数,并向cout所代表的控制台输出一个换行符。

    <<操作符在C语言里用作左移位操作,C++的标准模板库通过定义与该操作符“同名”的函数,扩展了该操作符的功能:向cout输出对象内容。

    总结:iostream定义了多个重载的operator<<()操作符函数,这些不同版本的函数接受不同类型的参数,包括int, char, float, double, const char*, string等,并将这些参数对象转换成字符串,并输出到控制台屏幕上。

    2. 改变进制

    //Project - HexOct
    #include 
    #include 
    using namespace std;
    
    int main(){
        int b = 0x17;          //十六进制 hexadecimal
        int c = 017;           //八进制   octal
        int d = 0b01111110;    //二进制   binary
    
        cout << "0x17 = " << b << ", 017 = " << c << ", 0b01111110 = " << d << endl;
        printf("17: %x, %d, %o\n", 17, 17, 17);
        cout << "17: " << hex << 17 << ", " << dec << 17 << ", " << oct << 17 << endl;
    
        hex(cout);
        cout << "17: " << 17 << ", ";
        dec(cout);
        cout << 17 << ", ";
        oct(cout);
        cout << 17 << endl;
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    上述代码的执行结果为:

    0x17 = 23, 017 = 15, 0b01111110 = 126
    17: 11, 17, 21
    17: 11, 17, 21
    17: 11, 17, 21
    
    • 1
    • 2
    • 3
    • 4

    上述代码可以看出,通过执行cout << hex,可以改变cout的内部状态,使用在后续输出数值时使用16进制。cout << dec (十进制),cout << oct (八进制)同理。

    事实上,这里的hex, dec, oct是一种被称之为操作算子(manipulator)的特殊函数。下述3行代码事实上等价:

    cout << hex;
    cout.operator<<(hex);
    hex(cout);
    
    • 1
    • 2
    • 3

    在ios_base.h中我们可以找到hex()函数的定义:

    inline ios_base&
    hex(ios_base& __base)
    {
      __base.setf(ios_base::hex, ios_base::basefield);
      return __base;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在形式上,cout << hex被解释成多轮函数调用,首先是:

    cout.operator<<(hex);
    
    • 1

    这个被重载的operator<<()函数将hex函数名当成一个函数指针,然后通过这个函数指针调用hex()函数:

    hex(cout);
    
    • 1

    而hex()函数又通过cout的setf()函数发挥作用。读者或许会疑惑说,这么多层的函数调用是否会降低代码的执行效率,事实上,由于相关函数多是内联(inline)的,编译器的优化会消除这些“形式”上的不必要的函数调用。

    3. 输出宽度控制

    #include 
    #include 
    using namespace std;
    
    int main(){
        cout << "12345678901234567890" << endl;
        cout << "--------------------" << endl;
    
        cout.width(5);
        cout << "N";
        cout.width(15);
        cout << "2**N" << endl;
        cout << "--------------------" << endl;
    
        for (auto n=0;n<=10;n++){
            cout.width(5);
            cout << n;
            cout.width(15);
            cout << pow(2,n) << endl;
        }
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    上述代码的执行结果为:

    12345678901234567890
    --------------------
        N           2**N
    --------------------
        0              1
        1              2
        2              4
        3              8
        4             16
        5             32
        6             64
        7            128
        8            256
        9            512
       10           1024
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    cout的成员函数width( )可以设定下一个输出项的宽度,但其作用范围仅限后一个输出项。当输出项的实际宽度小于设定宽度时,其左侧以空格填充。可以看到,上述程序借助于width( )函数,输出了一个严格右对齐的表格,第一列的宽度为5,第2列的宽度为15。

    通过fill( )成员函数,可以修改填充字符。请参见下述程序及其执行结果。

    #include 
    using namespace std;
    
    int main(){
        cout.fill('*');  //修改填充字符为*
        cout.width(5);
        cout << "idx";
        cout.width(15);
        cout << "content";
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    上述程序的执行结果为:

    **idx********content
    
    • 1

    4. 浮点数的输出格式

    通过cout的precision( )函数可以设置其输出浮点数时的精度。见下述代码。

    #include 
    using namespace std;
    
    int main(){
        float v1 = 17.90f;
        float v2 = 3.1415926535798932f;
        cout << "before .precision(2)" << endl;
        cout << "v1 = " << v1 << endl;
        cout << "v2 = " << v2 << endl;
    
        cout.precision(2);
        cout << "after .precision(2)" << endl;
        cout << "v1 = " << v1 << endl;
        cout << "v2 = " << v2 << endl;
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    执行结果为:

    before .precision(2)
    v1 = 17.9
    v2 = 3.14159
    after .precision(2)
    v1 = 18
    v2 = 3.1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    上述执行结果与我们的期望有很大不同。

    在默认情况下,cout输出浮点数的精度为6,且这个精度并不是指小数点后的位数,而是所有的位数。cout会借助于四舍五入的方法输出指定“精度”的字符串,同时会舍弃末尾多余的0。

    执行结果的第2行显示,17.90被舍弃掉末尾的0,输出为17.9。

    执行结果的第3行显示,3.1415926535798932被四舍五入为3.14159,正好6位数字。

    接下来,cout.precision(2)设定浮点数输出精度为2,此时,17.90被四舍五入输出为18,3.1415926535798932被四舍五入输出为3.1,都是两位数字。

    如果期望cout输出的浮点数的位数确定,当位数不足时用0补齐,可以执行cout.setf(ios_base::showpoint)。

    #include 
    using namespace std;
    
    int main(){
        float v1 = 17.90f;
        float v2 = 3.1415926535798932f;
    
        cout.setf(ios_base::showpoint);
        cout.precision(4);
        cout << "v1 = " << v1 << endl;
        cout << "v2 = " << v2 << endl;
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    执行结果为:

    v1 = 17.90
    v2 = 3.142
    
    • 1
    • 2

    多数人更愿意”精度“被定义为小数点后的位数。

    cout.setf(ios_base::floatfield, ios_base::fixed)将cout设置为定点小数模式,在该模式下,”精度”表示小数点后的位数,见下述程序。

    #include 
    using namespace std;
    
    int main(){
        float v1 = 17.90f;
        float v2 = 3.1415926535798932f;
    
        cout.precision(4);
        cout.setf(ios_base::floatfield,ios_base::fixed);
        cout << "v1 = " << v1 << endl;
        cout << "v2 = " << v2 << endl;
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    执行结果为:

    v1 = 17.9000
    v2 = 3.1416
    
    • 1
    • 2

    cout.setf(ios_base::floatfield, ios_base::scientific)将cout设置为科学计数法模式,在该模式下,“精度”也表示小数点后的位数,见下述程序。

    #include 
    using namespace std;
    
    int main(){
        float v1 = 17.90f;
        float v2 = 3.1415926535798932f;
    
        cout.precision(4);
        cout.setf(ios_base::floatfield,ios_base::scientific);
        cout << "v1 = " << v1 << endl;
        cout << "v2 = " << v2 << endl;
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    执行结果为:

    v1 = 1.7900e+001
    v2 = 3.1416e+000
    
    • 1
    • 2

    5. 应用setf( )进行格式控制

    cout的setf( )成员函数有两个原型。

    原型1:fmtflags setf(fmtflags)

    fmtflags常量用途
    ios_base::boolalpha将bool型输出为true或flase
    ios_base::showbase显示进制(八进制 - 0, 十六进制 - 0x)
    ios_base::showpoint总显示小数点,位数不足时补0
    ios_base::uppercase对16进制输出或者科学计数法中的E使用大写字母
    ios_base::showpos为正数输出显示+号

    请结合下述代码及其执行结果来理解上述fmtflags常量的用途。

    #include 
    using namespace std;
    
    int main(){
        int v = 17;
    
        cout << "true = " << true << endl; //true被输出为"1"
        cout.setf(ios_base::boolalpha);    //按"true"/"false"输出布尔型
        cout << "false = " << false << endl; //false被输出为"false"
    
        cout.setf(ios_base::showpos);  //正数显示+号,但通常只在10进制时有效
        cout << "v1 = " << v << endl;
    
        cout << "v2 = " << hex << v << endl;
        cout.setf(ios_base::showbase); //显示0,0x等进制符号
        cout.setf(ios_base::uppercase);//相关字母大写
        cout << "v3 = " << hex << v << endl;
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    上述代码的执行结果为:

    true = 1            //ios_base::boolalpha设置前,true被输出为1
    false = false       //ios_base::boolalpha设置后,false被输出为false
    v1 = +17            //ios_base::showpos导致十进制正数前出现+号
    v2 = 11             //16进制输出
    v3 = 0X11           //ios_base::showbase导致0x的添加,ios_base::uppercase使得0x变成0X
    
    • 1
    • 2
    • 3
    • 4
    • 5

    对于8进制和16进制,ios_base::showpos通常无效,因为在多数的应用场景下,人们只会使用8进制或者16进制来表达无符号的整数,而无符号整数,是没有正负概念的。

    在完成某项格式设置之后,如果期望还原该项设置,可以使用unsetf( )函数。举例如下。

    #include 
    using namespace std;
    
    int main(){
        int v = 17;
    
        cout.setf(ios_base::showpos);
        cout << v << endl;
    
        cout.unsetf(ios_base::showpos);
        cout << v << endl;
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    上述代码的执行结果为:

    +17
    17
    
    • 1
    • 2

    原型2:fmtflags setf(fmtflags, fmtflags)

    参数1参数2用途
    ios_base::decios_base::basefield使用10进制
    ios_base::oct使用8进制
    ios_base::hex使用16进制
    ios_base::fixedios_base::floatfield使用定点小数格式
    ios_base::scientific使用科学计数法
    ios_base::leftios_base::adjustfield左对齐
    ios_base::right右对齐
    ios_base::internal符号(+, -, 0, 0x等)左对齐,值右对齐

    下述代码及其执行结果演示了通过setf( )函数的第2个原型来设置输出内容左/右对齐的方法。

    #include 
    #include 
    using namespace std;
    
    int main(){
        cout << "12345678901234567890" << endl;
        cout << "--------------------" << endl;
    
        cout.width(5);
        cout.setf(ios_base::left,ios_base::adjustfield);
        cout << "N";
        cout.width(15);
        cout.setf(ios_base::right,ios_base::adjustfield);
        cout << "2**N" << endl;
        cout << "--------------------" << endl;
    
        cout.setf(ios_base::showpos);
        for (auto n=-2;n<=10;n++){
            cout.width(5);
            cout.setf(ios_base::left,ios_base::adjustfield);
            cout << n;
            cout.width(15);
            cout.setf(ios_base::right,ios_base::adjustfield);
            cout << pow(2,n) << endl;
        }
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    上述代码的执行结果为:

    12345678901234567890
    --------------------
    N               2**N
    --------------------
    -2             +0.25
    -1              +0.5
    +0                +1
    +1                +2
    +2                +4
    +3                +8
    +4               +16
    +5               +32
    +6               +64
    +7              +128
    +8              +256
    +9              +512
    +10            +1024
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    从执行结果可见,上述表格的第1列为左对齐,第2列则为右对齐。

    6. 通过操作算子(manipulator)简化格式设置

    下述代码的第5行与第6~7行效果相同,它们都按照16进制格式输出17,结果为11。

    #include 
    using namespace std;
    
    int main(){
        cout << hex << 17 << endl;
        cout.setf(ios_base::hex,ios_base::floatfield);
        cout << 17 << endl;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    上述代码的执行结果为:

    11
    11
    
    • 1
    • 2

    如本文的第2部分所述,hex事实上是一个被称为**操作算子(manipulator)**的函数,该函数事实上通过调用setf(ios_base::hex, ios_base::floatfield)发挥作用。显然,操作算子更加用户友好。

    下表介绍了常用的操作算子。

    操作算子等价setf( )调用用途
    cout << boolalphasetf(ios_base::boolalpha)用true/false表达布尔型
    noboolalphaunsetf(ios_base::boolalpha)还原前项设置
    showbasesetf(ios_base::showbase)使用0/0x表达8/16进制
    noshowbaseunsetf(ios_base::showbase)还原前项设置
    showpointsetf(ios_base::showpoint)总显示小数点,位数不足时补0
    noshowpointunsetf(ios_base::showpoint)还原前项设置
    showpossetf(ios_base::showpos)在10进制正数前显示+号
    noshowposunsetf(ios_base::showpos)还原前项设置
    uppercasesetf(ios_base::uppercase)对16进制输出或者科学计数法中的E使用大写字母
    nouppercaseunsetf(ios_base::uppercase)还原前项设置
    internalsetf(ios_base::internal,ios_base::adjustfield)符号(+, -, 0, 0x等)左对齐,值右对齐
    leftsetf(ios_base::left,ios_base::adjustfield)左对齐
    rightsetf(ios_base::right,ios_base::adjustfield)右对齐
    octsetf(ios_base::oct,ios_base::basefield)8进制
    decsetf(ios_base::dec,ios_base::basefield)10进制
    hexsetf(ios_base::hex,ios_base::basefield)16进制
    fixedsetf(ios_base::fixed,ios_base::floatfield)定点小数
    scientificsetf(ios_base::scientific,ios_base::floatfield)科学计数法

    此外,通过头文件iomanip还可以引入另外几个重要的操作算子。iomanip是input & output manipulator的缩写。

    操作算子等效于用途
    setw(n)cout.width(n)设置输出宽度
    setprecision(n)cout.precision(n)设置输出”精度”
    setfill(x)cout.fill(x)设置填充字符

    下述代码简单演示了操作算子的使用方法。

    #include 
    #include 
    #include 
    using namespace std;
    
    int main(){
        cout << fixed;
        cout << setw(6) << left << "N"
             << setw(12) << left << "log2N"
             << setw(12) << right << "log10N" << endl;
        cout << "-----------------------------------------" << endl;
        for (auto n = 1; n<=10; n++){
            cout << setw(6) << setfill('-') << left << n << setfill(' ');
            cout << setw(12) << setprecision(4) << left << log2(n);
            cout << setw(12) << setprecision(5) << right << log10(n) << endl;
        }
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    上述代码的执行结果为:

    N     log2N             log10N
    -----------------------------------------
    1-----0.0000           0.00000
    2-----1.0000           0.30103
    3-----1.5850           0.47712
    4-----2.0000           0.60206
    5-----2.3219           0.69897
    6-----2.5850           0.77815
    7-----2.8074           0.84510
    8-----3.0000           0.90309
    9-----3.1699           0.95424
    10----3.3219           1.00000
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    为了帮助更多的年轻朋友们学好编程,作者在B站上开了两门免费的网课,一门零基础讲Python,一门零基础C和C++一起学,拿走不谢!

    简洁的C及C++
    由编程界擅长教书,教书界特能编程的海洋饼干叔叔打造
    Python编程基础及应用
    由编程界擅长教书,教书界特能编程的海洋饼干叔叔打造

    如果你觉得纸质书看起来更顺手,目前Python有两本,C和C++在出版过程中。

    Python编程基础及应用

    Python编程基础及应用实验教程
    在这里插入图片描述

  • 相关阅读:
    【分页】常见两种SpringBoot项目中分页技巧
    选择HAL库还是标准库
    前端各种布局
    爱创科技携手洽洽食品,探索渠道数字化最优解!
    OpenCV自学笔记十八:模板匹配
    「设计模式」抽象工厂模式
    计算机毕业设计Java本地助农产品销售系统(源码+系统+mysql数据库+lw文档)
    OpenWRT搭建个人web站点并结合内网穿透实现公网远程访问
    【vulhub】MySql身份认证绕过漏洞复现(CVE-2012-2122)
    1109 Group Photo
  • 原文地址:https://blog.csdn.net/SeaBiscuitUncle/article/details/126594820