C++的流库

1.流的概念

“流”,即“流动”的意思,是物质从一处向另一处流动的过程。在计算机这边通常是指对一种有序连续且具有方向性的数据的抽象描述。

C++ 中的流一般指两个过程的统一:

  • 信息从外部输入设备(键盘)向计算机内部(内存)输入
  • 计算机内部(内存)向外部输出设备(显示器)输出信息

这种输入输出的过程被形象的比喻为“流”,其具有有序性、连续性、方向性。

而为了实现这种流动,C++ 定义了 I/O 标准类库,每个类都称为流类。

2.C 中的流

C 语言中最常使用流输入/输出方式就是 scanf()/printf()

  • scanf():从标准输入设备(一般指键盘)读取字符数据,并将值通过指针和一定的格式存放在变量中

  • printf():将指定的字符数据通过宽度输出和精度输出控制,来输出到标准输出设备(一般指屏幕)

C 语言借助了相应的输入/输出缓冲区(缓冲区这里涉及到一点系统的知识,您可以去看看我的 Linux 系列博文)来辅助进行输入/输出。

而在实际场景中,fscanf()fprintf() 会更为实用。

输入/输出缓冲区的意义:

  1. 屏蔽低级 I/O 的实现:低级 I/O 的实现依赖于操作系统本身内核的实现(例如:LinuxIO 系统调用),如果能屏蔽这部分在系统调用上的差异,就更容易写出可移植的程序
  2. 实现“行”读取的行为:计算机没有“行”这个概念,有了缓冲区就可以定义出“行”的概念,读取一行就是指解析缓冲区的内容返回一个“行”

但是 C 语言的输入输出在面对对象和泛型上有些吃力,因此 C++ 新构建了流的类。

3.C++ 中的流

C++ 系统实现了庞大的流类库,其中 ios 为基类,其他类都是直接或间接派生自 ios 类。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

C++ 主要提供了三个流,分别为:

  1. IO 流,和控制台。终端就行流传递
  2. 文件流,和文件进行流传递
  3. 字符流,和字符间进行流传递

3.1.C++ 的 IO 流

补充:由于 IO 流的相关类继承关系比较复杂,这里只是泛泛而谈,有机会我再结合官方文档再出另外一份博文…

C++ 标准库提供了 4 个全局流对象 cincoutcerrclog

  • 使用 cin 进行标准输入(数据通过键盘输入到内存中)
  • 使用 cout 进行标准输出(数据从内存流向显示器)
  • 使用 cerr 进行标准错误输出
  • 使用 clog 进行日志输出

从类继承的图中可以看出:coutcerrclog 是同属于 ostream 类的三个不同的对象,因此这三个对象的内部原理基本没有区别,只是应用场景和一些细节不同,这里只点出一些细节问题:

  1. cin 将键盘输入的数据保存在缓冲区中,当要提取时,就从缓冲区中拿。如果一次输入较多,会留在缓冲区中慢慢使用,如果输入发生错误,必须在回车之前修改,如果回车键按下就无法挽回了。只有把输入缓冲区中的数据取完后才会要求输入新数据。

    而输入的数据类型与要提取的数据类型不一致时出错,出错仅仅在流的状态字 state 字段中将对应位进行置位操作(置为 1 即可),但程序依旧继续运行。

    //使用 cin 过程中出错并且处理的过程 
    #include <iostream> //streamsize
    #include <string>
    #include <limits> //numeric_limitsusing namespace std;int main()
    {int number = 0;cin >> number;if (cin.fail()) //判断 cin 的状态,若 cin 为错误状态则返回 true, 正常状态则返回 false{cout << "发生错误" << endl;cin.clear(); //清除cin的错误状态(重要)in.ignore(numeric_limits<streamsize>::max(), '\n');  //忽略缓冲区中的所有字符,直到遇到换行符为止(重要)/* (1)std::numeric_limits 是一个模板类,用于获取各种数值类型(如整数、浮点数等)的特征信息 (2)std::streamsize 是一个类型,用于表示输入/输出流操作的字节数或字符数的整数类型,通常情况下等同于 std::ptrdiff_t,也就是指针之间的差值类型*/}else{cout << "number:" << number << endl;}//如果不清除状态,代码后续的 cin 都不会被执行,并且原先的输入数据仍然留在缓冲区中//如果清除状态后,没有清除缓冲区,则原先留在缓冲区的数据会被 cin 继续读取char character = 0;cin >> character;cout << "character:" << character << endl;return 0;
    }
    
  2. 空格和回车都可以作为数据之间的分格符,所以多个数据可以在一行输入,也可以分行输入。但如果是字符型或字符串,则空格(ASCII 码为 32)无法用 cin 输入,字符串中也不能有空格,回车符也无法被读入(这里可以了解一下 C++ 的原始字符串)。

    //cin 无法直接获取空格字符和回车字符
    #include <iostream>
    #include <string>
    using namespace std;int main()
    {string str;cin >> str;cout << "str:" << str; //输入空格字符和换行字符无效return 0;
    }
    

    这也使得读取读取带有空格的长字符串成为困难,因此可以使用 getline() 替代 >> 输入:

    注意:getline 有两个版本,还挺乱的,待补充…

  3. cincout 可以直接输入和输出内置类型数据,这是因为标准库已经将所有内置类型的输入方法和输出方法全部重载了。而对于自定义类型,若要支持 cincout 的标准输入输出,需要对 <<>> 进行重载

  4. istream 类型对象可以转换为逻辑条件判断值。使用 while(cin >> i){/*...*/} 去流中提取对象数据时, 调用的是 istream& operator >>(),但其返回值是 istream 类型的对象,为什么这里可以直接做逻辑条件呢?

    这是因为 istream 对象自动调用了 operator bool()(类似隐式类型转载)。因此如果接收流失败,或者有结束标志,则可以返回转化后的 bool 类型,值为 false,正常读取则返回 true

    operator bool() 的本质是将自定义类型转化为内置类型,这是我们第一次遇到这种情况。以往我们都是:

    (1)内置类型转内置类型的(C++ 默认支持,是自动的)

    (2)内置类型转自定义类型的(通过构造函数)

    (3)自定义类型转自定义类型的(通过构造函数)

    补充:之前关于自定义类型转自定义类型的例子

    //通过构造函数来转化两个不相干的类型
    list<int> lt;
    list<int>::const_iterator it = lt.begin();
    

    但是没有尝试过自定义类型转化为内置类型(不过内置类型转化为自定义类型是有的,构造函数就可以做到,例如:std::string str = "limou"),这种行为默认不支持,即便用户使用强制类型转化也做不到。

    但用户可以借助 operator bool() 这来达到逆向转化的目的(类似的也有 operatou int() 等),该重载可以算作是关键字的重载,没有返回值,可以让用户有更为强大的转化方法。

    //自定义类型转为内置类型
    #include <iostream>
    using namespace std;class Data
    {
    public:operator int(){return _data2;}
    private:int _data1 = 10;int _data2 = 20;
    };
    int main()
    {Data d;int number = d;cout << number << '\n';return 0;
    }
    

    有这个重置也可以玩出一些比较“奇怪”的玩法:

    //通过自定义隐式转化来控制循环
    #include <iostream>
    using namespace std;class Data
    {friend istream& operator>>(istream& in, Data& d);friend ostream& operator<<(ostream& out, const Data& d);public:operator bool() const{if (_data == 0)return false;elsereturn true;}private:int _data = 10;
    };istream& operator>>(istream& in, Data& d)
    {in >> d._data;return in;
    }ostream& operator<<(ostream& out, const Data& d)
    {cout << "save data:" << d._data << '\n';return out;
    }int main()
    {Data d;while (d == true) //如果用户的输入导致 _data 为 0,则停止循环{cin >> d;cout << d;}return 0;
    }
    

    如果在该重载前加上 explicit (意思为“明确的、坦率地”)就不允许隐式类型转化,但允许强制类型转化。

    //通过自定义显式转化来控制循环
    #include <iostream>
    using namespace std;class Data
    {friend istream& operator>>(istream& in, Data& d);friend ostream& operator<<(ostream& out, const Data& d);public:explicit operator bool() const //添加 explicit 防止隐式类型转化{if (_data == 0)return false;elsereturn true;}private:int _data = 10;
    };istream& operator>>(istream& in, Data& d)
    {in >> d._data;return in;
    }ostream& operator<<(ostream& out, const Data& d)
    {cout << "save data:" << d._data << '\n';return out;
    }int main()
    {Data d;//while (d == true) //这里会报错//while ((bool)d == true) //这样写就不会报错//while (bool(d) == true) //或者这样写也不会报错while (static_cast<bool>(d) == true) //或者这样写也不会报错{cin >> d;cout << d;}return 0;
    }
    

    需要注意的是,库内不是所有类都有这个重载的…

    补充:早期的 C++operator void*() const 来实现上述类似的转化…

  5. 关于 sync_with_stdio(bool sync = true) 的使用:在 C++sync_with_stdio(false) 是一种提升 cincout 效率的手段。

    尽可能在使用 cincout 时,调用成员函数 sync_with_stdio(false),因为 cincout 的输入输出缓冲区和 C 是两套缓冲区逻辑,而 C++ 又需要兼容 C,因此以下代码可以正常运行:

    //两套输入方法之间的兼容关系
    #define _CRT_SECURE_NO_WARNINGS 1
    #include <iostream>
    #include <cstdio>
    using namespace std;int main()
    {int number = 0;//下面两种输入互不不影响,等同于用两次 cin >> number 或 scanf("%d", &number)cin >> number;cout << number << '\n';scanf("%d", &number);printf("%d", number);//前者输入兼容后者return 0;
    }
    

    而调用成员函数 sync_with_stdio(false) 即可关闭这个兼容(告诉编译器不用考虑 C 语言标准库的输入输出流的兼容问题)。

    //使用 sync_with_stdio(false) 关闭兼容
    #include <iostream>
    using namespace std;int main()
    {cin.sync_with_stdio(false);cout.sync_with_stdio(false);int value;cin >> value;cout << value << endl;return 0;
    }
    

    而调用 sync_with_stdio(false) 后,scanf()printf()cincout 等混用将存在输入顺序/输出顺序与调用顺序不一致的问题(因为它们不再共享相同的缓冲区),且在代码中调用 sync_with_stdio(false) 属于不可逆操作。

    注意:我简单测试了一些平台,暂时没有遇到哪些调用顺序混乱的情况,以后遇到了再补充上来…

  6. 终止字符输入的方法主要有两种,比较暴力的就使用 [ctrl+c],而更为正常的操作是 [ctrl+z+enter],相当于流读取到结束标志。

3.2.C++ 的文件流

C++ 根据文件内容的数据格式分为二进制文件和文本文件。采用文件流对象操作文件的一般步骤:

  1. 定义一个文件流对象
    • ifstream ifile(只输入)
    • ofstream ofile(只输出)
    • fstream iofile(既输入又输出)
  2. 使用文件流对象的成员函数打开磁盘文件,使得文件流对象和磁盘文件之间建立联系
  3. 使用流提取运算符和流插入运算符对文件进行读写操作,或直接使用成员函数进行读写
  4. 关闭文件对象,释放资源(有析构函数的存在,因此可以不显示调用)
//文件流接口演示
#include <string>
#include <fstream>
#include <iostream>
using namespace std;//描述数据的类
struct Data
{int _data1;int _data2;int _data3;
};//描述服务器的类
struct Serverinfo
{char _address[32]; //地址//这里的 _address 不能使用 string 类型,否则二进制写入就回变成写入指向字符串的“指针信息”,而不是“字符串”本身(string 本身的成员有:指向字符串的指针、字符串的大小、存储的容量),//在 string 释放后就会导致指向字符串的指针变成“野指针”,因此读取的时候就会变成野指针解引用,这有可能导致程序奔溃(本质上就是浅拷贝问题)//(1)在同一个进程下,可能只是浅拷贝问题(输出结果有可能对)//(2)但是在两个进程的情况下,就必然会出现野指针解引用(写入的进程将指针指向的资源销毁了)//因此二进制读写的时候,最好不要用容器,否则很容易出现类似的问题//但是文本输入不会在这方面出问题,因为 string 对象会被提前转化为“字符串”后再写入,不会简单进行浅拷贝int _port; //端口Data _data; //数据
};//描述操作的管理器
class ConfigManager
{/* 封装各种文件流的操作 */
public:ConfigManager(const char* fileName): _fileName(fileName){}void WriteBin(const Serverinfo& info){ofstream ofs(_fileName, ofstream::out | ofstream::binary); //二进制写入(ofstream::out 可忽略,ofstream::binary 表示二进制操作)ofs.write((char*)&info, sizeof(info));}void ReadBin(Serverinfo& info){ifstream ifs(_fileName, ofstream::in | ofstream::binary); //二进制读取(ofstream::in 可忽略,ofstream::binary 表示二进制操作)ifs.read((char*)&info, sizeof(info));}//文本写入/读取时,C 语言需要将数据不断转化为字符串再读写,但是 C++ 通过运算重载自动化了这个过程void WriteText(const Serverinfo& info){ofstream ofs(_fileName); //文本写入,直接使用流操作符即可ofs << info._address << " "<< info._port << " "<< info._data._data1 << " "<< info._data._data2 << " "<< info._data._data3 << '\n';}void ReadText(Serverinfo& info){ifstream ifs(_fileName); //文本读取,直接使用流操作符即可ifs >> info._address>> info._port>> info._data._data1>> info._data._data2>> info._data._data3;}private:string _fileName;
};int main()
{Serverinfo winfo = {"190.0.0.0", 80, {1, 2, 3}}; //服务器传递的信息//模拟程序 A(二进制读写)Serverinfo rinfo1; //读取服务器的信息ConfigManager cmb("limouBin"); //信息文件路径cmb.WriteBin(winfo); //写入信息cmb.ReadBin(rinfo1);cout << rinfo1._address << "-" << rinfo1._port << ":"<< rinfo1._data._data1 << "-"<< rinfo1._data._data2 << "-"<< rinfo1._data._data3 << '\n';//模拟程序 B(文本读写)Serverinfo rinfo2; //读取服务器的信息ConfigManager cmt("limouText.txt"); //信息文件路径cmt.WriteText(winfo); //写入信息cmt.ReadText(rinfo2);cout << rinfo2._address << "-" << rinfo2._port << ":"<< rinfo2._data._data1 << "-"<< rinfo2._data._data2 << "-"<< rinfo2._data._data3 << '\n';return 0;
}

补充:可以看到,写入文件的字符默认使用空格分割,但是有些时候我们需要输入带有空格的数据,这种情况可以了解一下 fstream 下的成员函数 getline(),这个接口和之前 IO 流的功能类似…

3.3.C++ 的字符流

C 中若想要将一个“自定义类型数据转化为字符串类型数据”或者“字符串类型数据转化为自定义类型数据”,有以下常见方式:

  • 使用 itoa()、使用 sprintf() 输出字符串数据
  • 使用 sscanf() 读取字符串数据

但都需要给出保存结果的空间大小,而空间的大小不容易界定,且转化格式不匹配时,还会得到错误的结果甚至是程序崩溃,因此 C++ 又设计了新的 sstream 头文件,该文件包含四个类:

  1. istringstream:进行流的输入
  2. ostringstream:进行流的输出
  3. stringstream:进行流的输入输出操作

我们主要介绍 stringstream,该类在其底层维护了一个 string 类型的对象用来保存结果。

注意:这个类用起来还是很不错的,推荐使用…

3.3.1.将数值类型数据格式化为字符串

//将数值类型数据格式化为字符串
#include <sstream> //stringstream
#include <iostream>
#include <string>
using namespace std;int main()
{string str;stringstream ss;//将一个整形变量转化为字符串,存储到 string 类对象中int int_a = 114514;ss << int_a; //输入数据ss >> str; //输出数据cout << str << '\n';cout << ss.str() << '\n'; //str() 返回 stringsteam 中管理的 string 成员//stringstreams 在转换结尾时(即最后一次转换输出后),会将其内部状态设置为 badbit//因此如果我们需要多次转换,就必须使用 clear() 将上次转换状态清空掉,将状态重置为 goodbit 才可以转换ss.clear();//但是 clear() 不会将 stringstreams 底层字符串清空掉(第一次使用很容易误解 clean() 的作用)//如果不将 stringstream 底层管理 string 对象设置成 "",则多次转换时,就会将结果全部累积在底层 string 对象中ss.str("");//将一个浮点变量转化为字符串,存储到 string 类对象中double d = 3.14159;ss << d; //输入数据ss >> str; //输出数据cout << str << '\n';cout << ss.str() << '\n'; //str() 返回 stringsteam 中管理的 string 成员return 0;
}

3.3.2.字符串拼接

//字符串拼接
#include <sstream>
#include <iostream>
using namespace std;int main()
{stringstream ss;//将多个字符串放入 sstream 中sstream << "first" << " " << "string,";sstream << " second string";cout << "strResult is: " << sstream.str() << endl;//清空 sstreamsstream.str("");sstream << "third string";cout << "After clear, strResult is: " << sstream.str() << endl;return 0;
}

3.3.3.序列化和反序列化数据

//序列化和反序列化数据
#include <sstream>
#include <iostream>
using namespace std;struct Date
{int _year = 2023;int _month = 12;int _day = 19;
};
istream& operator>>(istream& in, Date& d)
{return in >> d._year >> d._month >> d._day;
}
ostream& operator<<(ostream& out, const Date& d)
{return out << d._year << " " << d._month << " " << d._day;
}struct ChatInfo
{string _name = "name";      //名字int _id = 0;                //idDate _date = { 2000,1,1 };  //时间string _msg = "msg";        //聊天信息
};int main()
{//1.结构信息序列化为字符串ChatInfo winfo = { "limou", 114514, { 2023, 12, 19 }, "今天晚上吃什么?" };ostringstream oss;oss << winfo._name << " "<< winfo._id << " " << winfo._date << " "<< winfo._msg;string str = oss.str();cout << str << '\n';//假设通过网络,将这个字符串发送给对象//一般会选用 JSON、XML 等方式进行更好的序列化支持(是描述信息用的文件)//2.字符串解析成结构信息ChatInfo rInfo;istringstream iss(str); //将字符传递给 ississ >> rInfo._name >> rInfo._id >> rInfo._date >> rInfo._msg;cout << "-------------------------------------------------------" << '\n';cout << "姓名:" << rInfo._name << "(" << rInfo._id << ") " << '\n';cout << "时间:" << rInfo._date << '\n';cout << "消息:" << rInfo._name << ":>" << rInfo._msg << endl;cout << "-------------------------------------------------------" << '\n';return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/422585.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

设计模式篇---中介者模式

文章目录 概念结构实例总结 概念 中介者模式&#xff1a;用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显示地相互引用&#xff0c;从而使其耦合松散&#xff0c;而且可以独立地改变它们之间的交互。 就好比世界各个国家之间可能会产生冲突&#xff0c;但是当产…

以太坊账户地址与比特B地址生成方法对比

作者 张群&#xff08;赛联区块链教育首席讲师&#xff0c;工信部赛迪特聘资深专家&#xff0c;CSDN认证业界专家&#xff0c;微软认证专家&#xff0c;多家企业区块链产品顾问&#xff09;关注张群&#xff0c;为您提供一站式区块链技术和方案咨询。 以太坊和比特B地址在生成方…

Hugo使用且部署GitHubPages

hugo的使用 20201121 Hugo是由Go语言实现的静态网站生成器。简单、易用、高效、易扩展、快速部署。 安装Hugo 0.windows安装(releases) 下载地址&#xff1a;https://github.com/spf13/hugo/releases。 配置环境变量 验证测试是否安装成功 hugo help1. 二进制安装&#xf…

mybatis----动态Sql

1.if标签 通过if标签构建动态条件&#xff0c;通过其test属性的true或false来判断该添加语句是否执行。 mapper接口 public interface AccountMapper {List<Account> selectAllByCondition(Account account); } 映射文件 <select id"selectAllByCondition&q…

前台vue配置

前台 vue环境 1.傻瓜式安装node: 官网下载&#xff1a;https://nodejs.org/zh-cn/2.安装cnpm: >: npm install -g cnpm --registryhttps://registry.npm.taobao.org3.安装vue最新脚手架: >: cnpm install -g vue/cli注&#xff1a;如果2、3步报错&#xff0c;清除缓…

竞赛保研 机器视觉的试卷批改系统 - opencv python 视觉识别

文章目录 0 简介1 项目背景2 项目目的3 系统设计3.1 目标对象3.2 系统架构3.3 软件设计方案 4 图像预处理4.1 灰度二值化4.2 形态学处理4.3 算式提取4.4 倾斜校正4.5 字符分割 5 字符识别5.1 支持向量机原理5.2 基于SVM的字符识别5.3 SVM算法实现 6 算法测试7 系统实现8 最后 0…

C++版QT:电子时钟

digiclock.h #ifndef DIGICLOCK_H #define DIGICLOCK_H ​ #include <QLCDNumber> ​ class DigiClock : public QLCDNumber {Q_OBJECT public:DigiClock(QWidget* parent 0);void mousePressEvent(QMouseEvent*);void mouseMoveEvent(QMouseEvent*); public slots:voi…

报告正式发布!RTE 开发者是搞音视频的那波儿人么?以及大家关心的薪资、岗位、职业发展路径...

前言&#xff1a; 哈喽各位 RTE 开发者社区的小伙伴&#xff0c;**《实时互动行业人才洞察 2024》**已经正式发布。 RTE 开发者是搞音视频的那波儿人么&#xff1f;RTE builder 又是什么含义&#xff1f;RTE 行业从业者的薪资范围和职业发展路径是什么样的&#xff1f; 关注…

【开放原子校园行】开发者投身开源项目的能够获得什么?

目录 前言开源软件不仅是免费&#xff0c;更是一种创新和共享的精神开发者投身开源项目的收获番外篇结束语 前言 作为开发者&#xff0c;编程不仅是工作和饭碗&#xff0c;也是兴趣爱好的体现。虽然说有一部分是为了生活才选择了编程开发&#xff0c;但是大部分开发者是因为兴…

一本满是错误的Go语言书,凭什么1000万人都在读

犯错是每个人生活的一部分。正如爱因斯坦曾说过&#xff1a;一个从未犯过错的人从未尝试过新东西。 最重要的不是我们犯了多少错误&#xff0c;而是我们从错误中学到了多少东西。 这个观点同样适用于编程领域。 我们从一门编程语言中获取经验不是一个神奇的过程&#xff0c;…

020-信息打点-红蓝队自动化项目资产侦察企查产权武器库部署网络空间

020-信息打点-红蓝队自动化项目&资产侦察&企查产权&武器库部署&网络空间 #知识点&#xff1a; 1、工具项目-红蓝队&自动化部署 2、工具项目-自动化侦查收集提取 3、工具项目-综合&网络空间&信息 演示案例&#xff1a; ➢自动化-武器库部署-F8x ➢自…

Supervised Contrastive 损失函数详解

有什么不对的及时指出&#xff0c;共同学习进步。(●’◡’●) 有监督对比学习将自监督批量对比方法扩展到完全监督设置&#xff0c;能够有效地利用标签信息。属于同一类的点簇在嵌入空间中被拉到一起&#xff0c;同时将来自不同类的样本簇推开。这种损失显示出对自然损坏很稳…