1.Streams

cin,cout

  1. 何时提示用户输入?

    当位置指针达到文件结尾(EOF)并且超过了缓冲区中的最后一个标记时,程序会挂起并等待用户输入。

  2. 为什么 cout 操作不会立即将输出打印到控制台?输出什么时候才会被打印?

    因为 cout 的输出只是输出到了缓冲区,需要对缓冲区进行刷新才会打印到控制台。

    输出刷新的三种情况:

    1. 使用 cout << endl;
    2. 使用 cout << flush;
    3. 当 cin 正在等待用户输入时。
  3. 在 >> 操作中,位置指针是在 token 前还是 token 后跳过空白字符?位置指针是否总是读取到空白字符?如果不是,请提供一个反例。

    位置指针执行以下操作:

    1. 在读取前跳过所有空白字符(空格、换行符等)。
    2. 读取字符,直到满足以下条件之一:
      • 遇到空白字符
      • 对于原始数据类型,读取足够多的字符以形成有效的变量
    3. 例如:如果从字符串 "86.2" 中提取一个整数,将得到 86,位置指针停在小数点之后。

读写文件

头文件和类型

  • 头文件:<fstream>
  • 类型:
    • ifstream:用于从文件读取。
    • ofstream:用于向文件写入。
    • fstream:可用于读写。

创建和使用流

  • 创建 ifstream 实例:
1
2
3
ifstream myStream("myFile.txt");
int myInteger;
myStream >> myInteger; // 从 myFile.txt 中读取一个整数
  • 使用 open 方法打开文件:
1
2
ifstream myStream; // 注意:没有指定文件
myStream.open("myFile.txt"); // 现在从 myFile.txt 中读取

文件打开检查

  • 检查文件是否成功打开:
1
2
3
ifstream input("myfile.txt");
if (!input.is_open())
cerr << "无法打开文件 myfile.txt" << endl;

注意事项

  • 使用 ofstream 写入不存在的文件时,会创建新文件;若文件已存在,则覆盖内容。
  • 由于 open 函数较早,因此使用 C++ 字符串时,需将其转换为 C 风格字符串(.c_str())。

流操纵符(Stream Manipulators)

头文件和常用操纵符

  • 头文件:<iomanip>
  • setw:设置输出宽度。
  • setfill:设置填充字符。

示例

1
2
cout << setw(10) << 137 << endl; // 设置宽度为10
cout << setfill('0') << setw(8) << 1000 << endl; // 使用 '0' 填充

其他流操纵符

  • boolalpha:布尔值的文字输出。
  • hex, dec, oct:设置数值的输出进制。

处理流错误

  • 流错误可能发生在类型不匹配或读取失败的情况下。
  • 流错误会使后续操作失败。

检查错误状态

  • 使用 .fail() 检查流的错误状态。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void PrintTableBody()
{
ifstream input("table-data.txt"); /* 遍历文件中的行并读取数据。 */
int rowNumber = 0;
while (true) {
int intValue;
double doubleValue;
input >> intValue >> doubleValue;
if (input.fail())
break;
cout << setw(COLUMN_WIDTH) << (rowNumber + 1) << " | ";
cout << setw(COLUMN_WIDTH) << intValue << " | ";
cout << setw(COLUMN_WIDTH) << doubleValue << endl;
rowNumber++;
}
}

上述的 while (true) 写法有些复杂,可以使用以下形式进行简化:

1
2
3
4
5
int intValue;
double doubleValue;
while (input >> intValue >> doubleValue) {
/* ... 处理值 ... */
}

cin和>>导致的错误

常见问题

  • cin 读取整行数据但仅提取空白分隔的标记。
  • 缓冲区中的残留数据可能导致 cin 在错误的时机等待输入。
  • cin 失败后,所有后续操作也会失败。

示例和问题解释

1
2
3
4
5
6
7
8
9
10
11
12
13
int age;
cout << "请输入您的年龄:";
cin >> age; // 输入 "2.71828",age 为 2
string password;
cout << "输入管理员密码:";
cin >> password; // 如果输入为 "password y",将自动读取 'y'
if (password == "password") {
cout << "是否要擦除硬盘(y 或 n)?";
char yesOrNo;
cin >> yesOrNo;
if (yesOrNo == 'y')
EraseHardDrive();
}

getline 的使用

问题与解决方案

  • 使用 >> 读取可能导致问题,而 getline 可以避免这些问题。
  • getline 读取直到换行符,并存储在字符串中。

示例

1
2
string myStr;
getline(cin, myStr); // 读取一整行

流操作符混用错误

与流提取操作符不同,getline 不会跳过流中仍然存在的空白字符。

1
2
3
4
5
int dummyInt;
string dummyString;

cin >> dummyInt; // 输入 10回车
getline(cin, dummyString); //dummyString的结果为空串

因此getline会找到cin输入中剩下的换行符,读取并丢弃,最后返回空字符串。

stringstream

概述和用法

  • 头文件:<sstream>
  • 用于在内存中的字符串缓冲区中读写数据。

示例

1
2
3
stringstream myStream;
myStream << "Hello!" << 137; // 写入数据
cout << myStream.str(); // 输出数据

另一个示例:

1
2
3
4
5
6
7
stringstream myConverter;
int myInt;
string myString;
double myDouble;

myConverter << "137 Hello 2.71828"; // 插入字符串数据
myConverter >> myInt >> myString >> myDouble; // 提取混合数据

综合实例:编写 getInteger 函数

在编写函数 getInteger 时,需要解决用户输入可能引发的两种错误:

  1. 用户可能会输入非整数,导致 cin 失败。
  2. 用户可能会输入过多的数据,例如 "137 246",导致操作成功,但留下额外的数据可能会干扰后续读取。

为解决这些问题,可以采用以下方法:

  • 使用 getline 从输入中读取整行数据,以便完整接受用户输入。
  • 将所读数据保存在 stringstream 对象中,然后再从中使用 >> 进行数据提取,同时检查 stringstream 对象的状态。
  • 读取完数据后,再次使用 >>stringstream 对象中尝试读取数据。如果能够读取数据,说明用户输入过多。

下面是示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int getInteger(const string &prompt) {
while (true) {
cout << prompt;
string line;
if (!getline(cin, line))
throw domain_error("读取失败");

istringstream iss(line);
int result;
char trash;
if (iss >> result && !(iss >> trash))
return result;
}
}

示例代码

点击下载示例代码


1.Streams
https://ci-tz.github.io/2024/02/02/1-Streams/
作者
次天钊
发布于
2024年2月2日
许可协议