71、C++ 流输入输出深入探究

C++ 流输入输出深入探究

1. istream 成员函数 peek、putback 和 ignore

1.1 ignore 函数

ignore 函数用于读取并丢弃指定数量的字符(默认丢弃一个),或者在遇到指定分隔符时终止操作(默认分隔符是文件结束符 EOF,当从文件读取时,会跳过到文件末尾)。

1.2 putback 函数

putback 函数将通过 get 从输入流获取的前一个字符放回该流中。这对于扫描输入流以查找以特定字符开头的字段的应用程序非常有用。当输入该字符后,应用程序将其放回流中,以便该字符可以包含在输入数据中。

1.3 peek 函数

peek 函数返回输入流中的下一个字符,但不会将该字符从流中移除。

2. 类型安全的 I/O

C++ 提供类型安全的 I/O。 << >> 运算符被重载以接受特定类型的数据项。如果处理了意外的数据,会设置各种错误位,用户可以测试这些错误位来确定 I/O 操作是否成功。如果 << >> 运算符没有为用户定义的类型进行重载,而你尝试输入或输出该用户定义类型的对象内容,编译器会报告错误,这使程序能够“保持控制”。

3. 无格式 I/O:使用 read、write 和 gcount

无格式输入/输出分别使用 istream ostream read write 成员函数来执行。
- read 成员函数将字节输入到内存中的内置字符数组中。
- write 成员函数从内置字符数组中输出字节。这些字节不会进行任何格式化,以原始字节的形式输入或输出。

例如:

#include <iostream>
using namespace std;

int main()
{
    const int SIZE = 80;
    char buffer[ SIZE ]; // create array of 80 characters
    cout << "Enter a sentence:" << endl;
    cin.getline( buffer, SIZE );
    // display buffer contents
    cout << "\nThe sentence entered is:" << endl << buffer << endl;
    return 0;
}

read 成员函数将指定数量的字符输入到内置字符数组中。如果读取的字符数量少于指定数量,会设置 failbit gcount 成员函数报告上一次输入操作读取的字符数量。

示例代码:

#include <iostream>
using namespace std;

int main()
{
    const int SIZE = 80;
    char buffer[ SIZE ]; // create array of 80 characters
    cout << "Enter a sentence:" << endl;
    cin.read( buffer, 20 );
    cout << endl << "The sentence entered was:" << endl;
    cout.write( buffer, cin.gcount() );
    cout << endl;
    return 0;
}

4. 流操纵符简介

C++ 提供了各种流操纵符来执行格式化任务,这些操纵符提供了诸如设置字段宽度、设置精度、设置和取消格式状态、设置字段填充字符、刷新流、插入换行符到输出流(并刷新流)、插入空字符到输出流以及跳过输入流中的空白字符等功能。

4.1 整数流基数:dec、oct、hex 和 setbase

整数通常被解释为十进制(基数为 10)值。可以通过插入 hex 操纵符将整数解释的基数设置为十六进制(基数为 16),插入 oct 操纵符将基数设置为八进制(基数为 8),插入 dec 操纵符将流基数重置为十进制。这些都是粘性操纵符。

也可以使用 setbase 流操纵符来更改流的基数,它接受一个整数值 10、8 或 16,分别将基数设置为十进制、八进制或十六进制。由于 setbase 需要一个参数,因此它被称为参数化流操纵符,使用时需要包含头文件 <iomanip> 。流的基数设置会一直保持,直到显式更改。

示例代码:

#include <iostream>
#include <iomanip>
using namespace std;

int main()
{
    int number;
    cout << "Enter a decimal number: ";
    cin >> number; // input number
    // use hex stream manipulator to show hexadecimal number
    cout << number << " in hexadecimal is: " << hex << number << endl;
    // use oct stream manipulator to show octal number
    cout << dec << number << " in octal is: " << oct << number << endl;
    // use setbase stream manipulator to show decimal number
    cout << setbase( 10 ) << number << " in decimal is: " << number << endl;
    return 0;
}

4.2 浮点数精度(precision、setprecision)

可以使用 setprecision 流操纵符或 ios_base precision 成员函数来控制浮点数的精度(即小数点右侧的数字位数)。调用其中任何一个函数都会为所有后续输出操作设置精度,直到下一次调用设置精度的函数。调用 precision 成员函数且不传递参数时,会返回当前的精度设置。

示例代码:

#include <iostream>
#include <cmath>
#include <iomanip>
using namespace std;

int main()
{
    double root2 = sqrt( 2.0 ); // calculate square root of 2
    int places; // precision, vary from 0-9
    cout << "Square root of 2 with precisions 0-9." << endl
         << "Precision set by ios_base member function "
         << "precision:" << endl;
    cout << fixed; // use fixed-point notation
    // display square root using ios_base function precision
    for ( places = 0; places <= 9; ++places ) 
    {
        cout.precision( places );
        cout << root2 << endl;
    } 
    cout << "\nPrecision set by stream manipulator " 
         << "setprecision:" << endl;
    // set precision for each digit, then display square root
    for ( places = 0; places <= 9; ++places )
        cout << setprecision( places ) << root2 << endl;
    return 0;
}

4.3 字段宽度(width、setw)

width 成员函数(属于基类 ios_base )用于设置字段宽度(即输出一个值所需的字符位置数或应输入的最大字符数),并返回之前的宽度设置。如果输出的值比字段宽度窄,会插入填充字符;如果值比指定宽度宽,不会截断,会完整打印该值。调用 width 函数且不传递参数时,会返回当前的宽度设置。

setw 流操纵符也可用于设置字段宽度。需要注意的是,宽度设置仅适用于下一次插入或提取操作(即宽度设置不是粘性的),之后宽度会隐式设置为 0。

示例代码:

#include <iostream>
using namespace std;

int main()
{
    int widthValue = 4;
    char sentence[ 10 ];
    cout << "Enter a sentence:" << endl;
    while ( cin >> sentence ) 
    {
        cin.width( 5 ); // input only 5 characters from sentence
        cout.width( widthValue++ );
        cout << sentence << endl;
    } 
    return 0;
}

4.4 用户定义的输出流操纵符

可以创建自己的流操纵符。例如创建新的非参数化流操纵符 bell carriageReturn tab endLine 。对于输出流操纵符,返回类型和参数必须是 ostream & 类型。

示例代码:

#include <iostream>
using namespace std;

// bell manipulator (using escape sequence \a)
ostream& bell( ostream& output )              
{                                             
    return output << '\a'; // issue system beep
} // end bell manipulator                     
// carriageReturn manipulator (using escape sequence \r)
ostream& carriageReturn( ostream& output )              
{                                                       
    return output << '\r'; // issue carriage return      
} // end carriageReturn manipulator                     
// tab manipulator (using escape sequence \t)
ostream& tab( ostream& output )              
{                                            
    return output << '\t'; // issue tab       
} // end tab manipulator                     
// endLine manipulator (using escape sequence \n and flush stream
// manipulator to simulate endl)
ostream& endLine( ostream& output )                              
{                                                                
    return output << '\n' << flush; // issue endl-like end of line
} // end endLine manipulator                                     

int main()
{
    cout << "Testing the tab manipulator:" << tab << 'a' << tab << 'b' << tab << 'c' << endLine;
    cout << "Testing the carriageReturn and bell manipulators:" << carriageReturn << "..........";
    cout << bell;
    cout << carriageReturn << "-----" << endLine;
    return 0;
}

5. 流格式状态和流操纵符

5.1 流操纵符列表

操纵符 描述
skipws 跳过输入流中的空白字符,使用 noskipws 重置此设置。
left 在字段中左对齐输出,必要时填充字符出现在右侧。
right 在字段中右对齐输出,必要时填充字符出现在左侧。
internal 指示数字的符号应在字段中左对齐,数字的大小应在同一字段中右对齐(即填充字符出现在符号和数字之间)。
boolalpha 指定 bool 值应显示为单词 true false ,使用 noboolalpha 将流设置回显示 bool 值为 1( true )和 0( false )。
dec 指定整数应视为十进制(基数为 10)值。
oct 指定整数应视为八进制(基数为 8)值。
hex 指定整数应视为十六进制(基数为 16)值。
showbase 指定数字的基数应在数字之前输出(八进制前导 0;十六进制前导 0x 或 0X),使用 noshowbase 重置此设置。
showpoint 指定浮点数应输出小数点,通常与 fixed 一起使用以保证小数点右侧有一定数量的数字,即使是零,使用 noshowpoint 重置此设置。
uppercase 指定十六进制整数中应使用大写字母(即 X 和 A 到 F),在科学记数法表示浮点数时应使用大写 E,使用 nouppercase 重置此设置。
showpos 指定正数应在前面加上加号(+),使用 noshowpos 重置此设置。
scientific 指定以科学记数法输出浮点数。
fixed 指定以定点记数法输出浮点数,并指定小数点右侧的数字位数。

5.2 尾随零和小数点(showpoint)

showpoint 流操纵符是一个粘性设置,它强制浮点数输出时带上小数点和尾随零。例如,浮点数 79.0 在不使用 showpoint 时打印为 79,使用 showpoint 时打印为 79.000000(或根据当前精度指定的尾随零数量)。使用 noshowpoint 可以重置 showpoint 设置。

示例代码:

#include <iostream>
using namespace std;

int main()
{
    // display double values with default stream format
    cout << "Before using showpoint" << endl
         << "9.9900 prints as: " << 9.9900 << endl
         << "9.9000 prints as: " << 9.9000 << endl
         << "9.0000 prints as: " << 9.0000 << endl << endl;
    // display double value after showpoint
    cout << showpoint
         << "After using showpoint" << endl
         << "9.9900 prints as: " << 9.9900 << endl
         << "9.9000 prints as: " << 9.9000 << endl
         << "9.0000 prints as: " << 9.0000 << endl;
    return 0;
}

5.3 对齐方式(left、right 和 internal)

  • left right 流操纵符分别用于使字段左对齐(填充字符在右侧)和右对齐(填充字符在左侧)。填充字符可以通过 fill 成员函数或 setfill 参数化流操纵符来指定。
  • internal 流操纵符指示数字的符号(或使用 showbase 时的基数)应在字段中左对齐,数字的大小应右对齐,中间的空格用填充字符填充。

示例代码(左对齐和右对齐):

#include <iostream>
#include <iomanip>
using namespace std;

int main()
{
    int x = 12345;
    // display x right justified (default)
    cout << "Default is right justified:" << endl
         << setw( 10 ) << x;
    // use left manipulator to display x left justified
    cout << "\n\nUse std::left to left justify x:\n"
         << left << setw( 10 ) << x;
    // use right manipulator to display x right justified
    cout << "\n\nUse std::right to right justify x:\n"
         << right << setw( 10 ) << x << endl;
    return 0;
}

示例代码(内部对齐):

#include <iostream>
#include <iomanip>
using namespace std;

int main()
{
    // display value with internal spacing and plus sign
    cout << internal << showpos << setw( 10 ) << 123 << endl;
    return 0;
}

5.4 填充(fill、setfill)

fill 成员函数用于指定在对齐字段时使用的填充字符,默认使用空格作为填充字符,该函数会返回之前的填充字符。 setfill 操纵符也可用于设置填充字符。

示例代码:

#include <iostream>
#include <iomanip>
using namespace std;

int main()
{
    int x = 10000;
    // display x
    cout << x << " printed as int right and left justified\n"
         << "and as hex with internal justification.\n"
         << "Using the default pad character (space):" << endl;
    // display x with base
    cout << showbase << setw( 10 ) << x << endl;
    // display x with left justification
    cout << left << setw( 10 ) << x << endl;
    // display x as hex with internal justification
    cout << internal << setw( 10 ) << hex << x << endl << endl;
    cout << "Using various padding characters:" << endl;
    // display x using padded characters (right justification)
    cout << right;
    cout.fill( '*' );
    cout << setw( 10 ) << dec << x << endl;
    // display x using padded characters (left justification)
    cout << left << setw( 10 ) << setfill( '%' ) << x << endl;
    // display x using padded characters (internal justification)
    cout << internal << setw( 10 ) << setfill( '^' ) << hex
         << x << endl;
    return 0;
}

通过以上内容,我们深入了解了 C++ 流输入输出的各种操作和格式化方法,包括 istream 成员函数、类型安全的 I/O、无格式 I/O、流操纵符以及流格式状态的控制等,这些知识对于编写高效、准确的输入输出程序非常有帮助。

5.5 流操纵符使用流程

下面是一个使用流操纵符进行格式化输出的流程:

graph TD;
    A[开始] --> B[设置流格式状态];
    B --> C{是否需要整数基数转换};
    C -- 是 --> D[使用dec、oct、hex或setbase设置基数];
    C -- 否 --> E{是否需要控制浮点数精度};
    D --> E;
    E -- 是 --> F[使用setprecision或precision设置精度];
    E -- 否 --> G{是否需要设置字段宽度};
    F --> G;
    G -- 是 --> H[使用width或setw设置宽度];
    G -- 否 --> I{是否需要自定义操纵符};
    H --> I;
    I -- 是 --> J[使用自定义操纵符];
    I -- 否 --> K[进行输出操作];
    J --> K;
    K --> L[结束];

5.6 常见错误分析

在使用流输入输出和流操纵符时,可能会遇到以下常见错误:
- 宽度设置逻辑错误 :宽度设置仅适用于下一次插入或提取操作,若假设宽度设置适用于所有后续输出,会导致逻辑错误。例如:

#include <iostream>
using namespace std;

int main()
{
    cout.width(5);
    cout << 12 << 34; // 只有12会按宽度5输出,34不会
    return 0;
}
  • 字段宽度不足 :当字段不够宽以处理输出时,输出会按实际宽度打印,可能导致输出混乱。例如:
#include <iostream>
using namespace std;

int main()
{
    cout.width(2);
    cout << 123; // 123会完整输出,不会截断
    return 0;
}

6. 综合示例

以下是一个综合使用各种流操纵符和输入输出函数的示例代码,展示了如何进行复杂的输入输出格式化:

#include <iostream>
#include <iomanip>
#include <cmath>
using namespace std;

// 自定义流操纵符
ostream& bell(ostream& output)
{
    return output << '\a';
}

ostream& carriageReturn(ostream& output)
{
    return output << '\r';
}

ostream& tab(ostream& output)
{
    return output << '\t';
}

ostream& endLine(ostream& output)
{
    return output << '\n' << flush;
}

int main()
{
    // 整数基数转换
    int num = 255;
    cout << "Decimal: " << num << endl;
    cout << "Hexadecimal: " << hex << num << endl;
    cout << "Octal: " << oct << num << endl;
    cout << dec; // 重置为十进制

    // 浮点数精度控制
    double pi = 3.1415926535;
    cout << "Default precision: " << pi << endl;
    cout << "Set precision to 3: " << setprecision(3) << pi << endl;

    // 字段宽度设置
    cout << setw(10) << "Name" << setw(10) << "Age" << endLine;
    cout << setw(10) << "John" << setw(10) << 25 << endLine;

    // 对齐方式
    int value = 123;
    cout << left << setw(10) << value << "Left aligned" << endLine;
    cout << right << setw(10) << value << "Right aligned" << endLine;
    cout << internal << showpos << setw(10) << value << "Internal aligned" << endLine;

    // 填充字符
    cout << setfill('*') << setw(10) << value << "Filled with *" << endLine;

    // 自定义流操纵符
    cout << "Testing custom manipulators: " << tab << 'A' << bell << carriageReturn << endLine;

    return 0;
}

6.1 代码解释

  • 整数基数转换 :使用 hex oct dec 操纵符分别将整数以十六进制、八进制和十进制输出。
  • 浮点数精度控制 :使用 setprecision 操纵符设置浮点数的输出精度。
  • 字段宽度设置 :使用 setw 操纵符设置输出字段的宽度。
  • 对齐方式 :使用 left right internal 操纵符设置输出的对齐方式。
  • 填充字符 :使用 setfill 操纵符设置填充字符。
  • 自定义流操纵符 :调用自定义的 tab bell carriageReturn 操纵符进行特殊输出。

7. 总结

通过对 C++ 流输入输出的深入探究,我们了解了 istream 成员函数、类型安全的 I/O、无格式 I/O 以及各种流操纵符的使用方法。流操纵符为我们提供了强大的格式化功能,能够满足不同场景下的输入输出需求。在实际编程中,我们可以根据具体需求灵活运用这些知识,编写出更加高效、准确和美观的输入输出程序。同时,要注意避免常见的错误,确保程序的正确性和稳定性。

总之,掌握 C++ 流输入输出和流操纵符的使用,对于提升编程能力和开发高质量的 C++ 程序具有重要意义。希望这些内容能帮助你更好地理解和应用 C++ 的输入输出机制。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值