C++流类库 标准输入流的安全性与成员函数 ostream 成员函数与自定义类型的IO

第二部分:标准输入流的安全性与成员函数

cinistream 类的对象,用于从标准输入(通常是键盘)读取数据。由于其缓冲特性,使用时需要注意许多细节,否则容易导致程序行为异常。

3.1 cin 的缓冲机制与潜在问题

cin带缓冲的输入流。这意味着:

  1. 用户输入的内容并不会立即被程序处理,而是先存储在一个叫输入缓冲区的内存区域。

  2. 只有当按下 回车键(Enter) 时,这一整行数据才会被送入缓冲区,程序中的 cin 或相关函数才开始从缓冲区中读取数据。

  3. 如果一次读取没有消耗完缓冲区的所有数据,剩余数据会保留,影响下一次读取。

主要问题:

  1. 类型不匹配 :如果程序期望一个整数 int,但用户输入了字母 "abc",则提取失败,错误数据留在缓冲区。

  2. 输入内容过多 :如果一次读取(如 cin >> str)只读了一个单词,但用户输入了一整句,句子的剩余部分会留在缓冲区。

  3. 空白字符处理 :默认的 >> 运算符会跳过前导空白字符 (空格、制表符、换行符),但在读取字符串时,遇到空白字符就会停止。这使得用 cin >> string 无法读取带空格的句子。

3.2 流状态(Stream State)

为了检测和处理上述错误,ios_base 类定义了一组流状态标志,用于指示流的当前状况。

cpp

复制代码
// 在 ios_base 中定义的状态位 (通常通过 ios 类访问)
std::ios::goodbit  // 一切正常,值为0
std::ios::eofbit   // 到达文件末尾 (End-Of-File),对于cin可能是Ctrl+Z/D)
std::ios::failbit  // 最近的IO操作失败,但流未损坏(例如类型不匹配)
std::ios::badbit   // 流已损坏,发生了严重的IO错误(如磁盘满)

相关成员函数:

cpp

复制代码
// 1. 获取当前状态字
iostate rdstate() const;

// 2. 便捷状态检查函数
bool good() const;  // rdstate() == 0
bool eof()  const;  // 检查 eofbit 是否被设置
bool fail() const;  // 检查 failbit 或 badbit 是否被设置
bool bad()  const;  // 检查 badbit 是否被设置

// 3. 清除/设置状态
void clear(iostate state = ios::goodbit);

clear() 函数非常重要 :当流进入 failbad 状态后,所有后续的IO操作都会被忽略,直到调用 clear() 将状态重置为 good

3.3 处理错误输入的标准模式

cin >> variable 失败时,需要执行一个**"重置-清空-重试"**的循环。

cpp

复制代码
#include <iostream>
#include <limits> // 用于 numeric_limits

int getValidInt() {
    int value;
    while (true) {
        std::cout << "请输入一个整数: ";
        std::cin >> value;

        if (std::cin.good()) {
            // 输入成功,跳出循环
            break;
        } else {
            // 输入失败,处理错误
            std::cout << "输入错误!请重新输入。\n";
            
            // 1. 清除错误状态
            std::cin.clear();
            
            // 2. 清空输入缓冲区中所有剩余的错误数据,直到遇到换行符
            // 方法一:使用 ignore 忽略缓冲区中最大数量的字符,直到遇到'\n'
            std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
            // 方法二:简单忽略256个字符,对于一般输入足够
            // std::cin.ignore(256, '\n');
        }
    }
    // 清空缓冲区中可能多余的字符(比如在正确数字后输入的字符)
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    return value;
}
3.4 istream 的关键成员函数

为了解决 >> 运算符的局限,istream 提供了一系列更灵活的成员函数。

3.4.1 字符输入:get()

cpp

复制代码
// 读取单个字符(包括空白字符)
istream& get(char& ch);
int get(); // 返回读取的字符,失败或EOF时返回EOF(-1)

示例:

cpp

复制代码
char c1, c2;
std::cin.get(c1); // 读取一个字符,可能是空格
c2 = std::cin.get(); // 读取下一个字符
3.4.2 字符串输入:getline()get()

cpp

复制代码
// 读取一行字符串,直到遇到分隔符(默认'\n'),分隔符会被从缓冲区取出但**不存储**
istream& getline(char* s, streamsize count, char delim = '\n');
istream& getline(string& str, char delim = '\n'); // 用于std::string,更安全!

// get() 的字符串版本:读取直到遇到分隔符,但分隔符**留在缓冲区**
istream& get(char* s, streamsize count, char delim = '\n');

关键区别:

  • getline():读取并丢弃分隔符。

  • get():读取但不丢弃分隔符(留在缓冲区)。

示例:读取带空格的整行输入

cpp

复制代码
#include <iostream>
#include <string>

int main() {
    // 使用字符数组(C风格字符串),不安全,容易缓冲区溢出
    char buffer[100];
    std::cout << "请输入一句话 (C风格字符串): ";
    std::cin.getline(buffer, 100); // 读取最多99个字符
    std::cout << "你说的是: " << buffer << std::endl;

    // 使用std::string,推荐!
    std::string str;
    std::cout << "请输入一句话 (std::string): ";
    std::getline(std::cin, str); // 注意:这是全局函数,不是成员函数
    std::cout << "你说的是: " << str << std::endl;

    return 0;
}
3.4.3 其他实用成员函数

cpp

复制代码
// 1. ignore(): 跳过(提取并丢弃)缓冲区中的字符
istream& ignore(streamsize count = 1, int delim = EOF);
// 示例:跳过直到换行符
std::cin.ignore(1000, '\n');
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); // 跳过尽可能多

// 2. gcount(): 返回最近一次未格式化的输入操作(如get(), getline(), read())读取的字符数
streamsize gcount() const;

// 3. putback(): 将一个字符放回输入缓冲区
istream& putback(char c);

// 4. peek(): 查看下一个字符,但不从缓冲区取出
int peek();

示例:ignore()gcount() 的使用

cpp

复制代码
#include <iostream>
#include <limits>
int main() {
    char buffer[10];
    
    std::cout << "请输入超过9个字符的字符串: ";
    std::cin.getline(buffer, 10);
    
    if (std::cin.fail()) {
        std::cout << "输入太长,只读取了: " << buffer << std::endl;
        std::cout << "实际读取了 " << std::cin.gcount() << " 个字符" << std::endl;
        
        // 清除错误状态并清空缓冲区剩余内容
        std::cin.clear();
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    } else {
        std::cout << "成功读取: " << buffer << std::endl;
    }
    
    return 0;
}

第三部分:ostream 成员函数与自定义类型的IO

4.1 ostream 的成员函数

cpp

复制代码
// 1. put(): 输出单个字符
ostream& put(char c);
std::cout.put('A').put('B').put('\n');

// 2. write(): 输出原始字节序列(常用于二进制输出)
ostream& write(const char* s, streamsize n);
char data[] = "Hello";
std::cout.write(data, 5); // 输出"Hello"(不包括结尾的'\0')

// 3. flush(): 强制刷新输出缓冲区
ostream& flush();
std::cout << "请稍等..." << std::flush;
// 做一些耗时操作
std::cout << "完成!" << std::endl;
4.2 重载 <<>> 运算符

为了让自定义类型也能像内置类型一样使用流IO,我们需要重载这两个运算符。

基本模式:

cpp

复制代码
// 输出运算符重载
ostream& operator<<(ostream& os, const MyClass& obj) {
    // 将obj的成员以适当格式输出到os
    os << obj.data1 << ", " << obj.data2;
    return os; // 必须返回ostream&以支持链式调用
}

// 输入运算符重载
istream& operator>>(istream& is, MyClass& obj) {
    // 从is读取数据并赋值给obj的成员
    is >> obj.data1 >> obj.data2;
    // 应该检查读取是否成功!
    if (!is) {
        obj = MyClass(); // 读取失败,恢复默认状态
    }
    return is;
}

示例:为复数类重载IO运算符

cpp

复制代码
#include <iostream>
class Complex {
private:
    double real, imag;
public:
    Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) {}
    
    // 声明为友元,以便直接访问私有成员
    friend std::ostream& operator<<(std::ostream& os, const Complex& c);
    friend std::istream& operator>>(std::istream& is, Complex& c);
};

// 输出实现:格式为 (real, imag)
std::ostream& operator<<(std::ostream& os, const Complex& c) {
    os << '(' << c.real << ", " << c.imag << ')';
    return os;
}

// 输入实现:期望格式为 (real, imag)
std::istream& operator>>(std::istream& is, Complex& c) {
    char ch;
    is >> ch; // 应该读到 '('
    
    if (ch == '(') {
        is >> c.real >> ch; // 读取实部和逗号
        if (ch == ',') {
            is >> c.imag >> ch; // 读取虚部和 ')'
            if (ch != ')') {
                is.setstate(std::ios::failbit); // 格式错误
            }
        } else {
            is.setstate(std::ios::failbit); // 格式错误
        }
    } else {
        // 如果不是括号格式,将字符放回,尝试只读取一个实数(虚部为0)
        is.putback(ch);
        is >> c.real;
        c.imag = 0.0;
    }
    return is;
}

int main() {
    Complex c1(3, 4), c2;
    std::cout << "c1 = " << c1 << std::endl; // 输出: c1 = (3, 4)
    
    std::cout << "请输入复数(格式如 (5, 6)): ";
    std::cin >> c2;
    
    if (std::cin.good()) {
        std::cout << "你输入的是: " << c2 << std::endl;
    } else {
        std::cout << "输入格式错误!" << std::endl;
        std::cin.clear();
        std::cin.ignore(1000, '\n');
    }
    
    return 0;
}

这部分的核心总结:

  1. cin 的缓冲区机制是许多输入问题的根源,必须理解。

  2. 流状态(goodbitfailbit等) 是检测和处理输入错误的关键工具。

  3. 标准错误处理模式 :检查 fail()clear()ignore() → 重试。

  4. 使用 getline()(特别是对于 std::string 来读取带空格的整行输入。

  5. 掌握 istream/ostream 的成员函数(get()put()write()ignore()等)。

  6. 通过重载 <<>> 运算符,可以让自定义类型无缝融入C++的流IO体系。

相关推荐
Scott9999HH7 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
2401_841495647 小时前
【操作系统】进程同步与互斥实验报告
c++·算法·操作系统·进程·并发·同步·互斥
fqbqrr8 小时前
2607C++,soui与安卓
c++·soui
码智社8 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海8 小时前
python 列表、元组、集合和字典
开发语言·python
a4493153629 小时前
iPhone Face ID 故障的分层诊断——原深感系统与主板加密配对机制
macos·ios·电脑·cocoa·iphone
萧瑟余晖9 小时前
JDK 26 新特性详解
java·开发语言
白玉cfc9 小时前
【iOS】内存五大分区
macos·ios·cocoa
马优晨9 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
fqbqrr11 小时前
2607C++,使用微软detours勾挂工具
c++