目录
- 一、C语言的输入输出
- 二、流的概念
- 三、operator bool
- 四、C++文件IO流
- ifstream和ofstream
- ostringstream和istringstream
- stringstream
一、C语言的输入输出
C语言中我们用到的最频繁的输入输出方式就是 scanf() 和 printf()。
scanf(): 从标准输入设备(键盘)读取数据,并将值存放在变量中。
printf(): 将指定的文字/字符串输出到标准输出设备(屏幕)。注意宽度输出和精度输出控制。
除此之外,C语言借助了相应的缓冲区来进行输入和输出。如下图所示:
对输入输出缓冲区的理解:
1.可以屏蔽掉低级 I/O 的实现,低级I/O的实现依赖操作系统本身内核的实现,所以如果能够屏蔽这部分的差异,可以很容易写出可移植的程序。
2.可以使用这部分的内容实现 “行” 读取的行为,对于计算机而言是没有 “行” 这个概念,有了这部分,就可以定义“行”的概念,然后解析缓冲区的内容,返回一个“行”。
二、流的概念
“流”即是流动的意思,是物质从一处向另一处流动的过程,是对一种有序连续 且 具有方向性 的数据( 其单位可以是 bit,byte,packet )的抽象描述。
C++流是指信息从外部输入设备(如键盘)向计算机内部(如内存)输入和从内存向外部输出设备(显示器)输出的过程。这种输入输出的过程被形象的比喻为“流”。
它的特性是:有序连续、具有方向性
为了实现这种流动, C++定义了 I/O 标准类库,这些每个类都称为流/流类,用以完成某方面的功能
三、operator bool
我们之前在写OJ题的时候经常可以看到如下代码
int main()
{string s;while (cin >> s){cout << s << endl;}return 0;
}
目前我们认为cin是一个全局的类,cin>>s 的返回值其实就是cin这个全局类运算符重载>>返回的值,我们可以看一下
我们可以看到,重载的>>运算符返回值是一个istream的对象,不能进行while的判断,下面我们学习以下原理。
我们可以看到,内置类型可以通过隐式类型转化为自定义类型,自定义类型无法转化为内置类型
此时我们看到没有报错,c++一个特例,使用opertor重载,可以将自定义类型转化为内置类型
此时,我们可以理解上面的demo
四、C++文件IO流
我们再C++中使用的cin和cout都是继承父类,父类继承ios实现的,iostream使用菱形继承实现,同时具有istream和ostream的功能。
上面提到的operator bool就是基类IOS实现的,子类都没有去重写
- cout为标准输出,将数据从内存流中输入到显示器上
- cin为标准输入,通过键盘输入数据到程序中
- cerr用于标准错误的输出
- clog进行日志输出
其中需要注意的一点是,空格和回车会被当作数据之间的分隔符,所以字符串中不能有空格,回车和空格也不能通过cin读入
如果需要读入带空格的完整一行,可以使用getline函数
为什么cin和cout可以输入输出所有类型? 因为库里面已经将所有类型通过操作符重载<< 和>>实现了,达到了自动类型识别的效果
之前将数据写入文件
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>using namespace std;#include<string>//IO流class Date
{
public:Date(int year,int month,int day):_year(year),_month(month),_day(day){}operator string(){string str;str += to_string(_year);str += ' ';str += to_string(_month);str += ' ';str += to_string(_day);return str;}
private:int _year;int _month;int _day;
};int main()
{Date d(2024, 2, 3);FILE* file = fopen("file.txt", "w");//fwrite(&d, sizeof(d), 1, file);//fclose(file);string str = d;fputs(str.c_str(), file);fclose(file);return 0;
}
我们会发现将数据写入文件比较容易,如果再将数据读出来发现比较费劲,这也是我们以前学习写入文件不足的地方,下面我们学习以下C++如何对文件进行IO
ifstream和ofstream
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>using namespace std;#include<string>
#include<fstream>//IO流class Date
{
public:Date() {};Date(int year,int month,int day):_year(year),_month(month),_day(day){}operator string(){string str;str += to_string(_year);str += ' ';str += to_string(_month);str += ' ';str += to_string(_day);return str;}public:int _year;int _month;int _day;
};ostream& operator<<(ostream& out, const Date& d)
{out << d._year << " " << d._month << " " << d._day;return out;
}istream& operator >>(istream& in, Date& d)
{in >> d._year >> d._month >> d._day;return in;
}struct ServerInfo
{char _address[32];int _port;Date _date;
};struct ConfigManager
{
public:ConfigManager(const char* filename):_filename(filename){}void WriteBin(const ServerInfo& info){ofstream ofs(_filename, ios_base::out | ios_base::binary);ofs.write((const char*)&info, sizeof(info));}void ReadBin(ServerInfo& info){ifstream ifs(_filename, ios_base::in | ios_base::binary);ifs.read((char*)&info, sizeof(info));}void WriteText(const ServerInfo & info){ofstream ofs(_filename);ofs << info._address << " " << info._port << " " << info._date;}void ReadText(ServerInfo& info){ifstream ifs(_filename);ifs >> info._address >> info._port >> info._date;}private:string _filename; // 配置文件
};int main()
{ServerInfo winfo = { "192.0.0.1", 80, { 2022, 4, 10 } };// 二进制读写ConfigManager cf_bin("test.bin");cf_bin.WriteBin(winfo);ServerInfo rbinfo;cf_bin.ReadBin(rbinfo);cout << rbinfo._address << " " << rbinfo._port << " "<< rbinfo._date << endl;// 文本读写//ConfigManager cf_text("test.text");//cf_text.WriteText(winfo);//ServerInfo rtinfo;//cf_text.ReadText(rtinfo);//cout << rtinfo._address << " " << rtinfo._port << " " <<rtinfo._date << endl;return 0;
}
二进制读写
文本读写
可以看到,我们以前学的文件IO只有二进制读写比较方便,但是二进制读写可读性差,C++中使用ifstream和ofstream无论再二进制还是文本读写都非常方便。
ostringstream和istringstream
这个类可以将不同的类型转为字符串
这种操作被称为序列化和反序列化,在处理自定义类型的时候非常好用
struct Date
{
public:friend ostream& operator << (ostream& out, const Date& d);friend istream& operator >> (istream& in, Date& d);Date(int y=0, int m=0, int d=0){_year = y;_month = m;_day = d;}
private:int _year;int _month;int _day;
};istream& operator >> (istream& in, Date& d)
{in >> d._year >> d._month >> d._day;return in;
}ostream& operator << (ostream& out, const Date& d)
{out << d._year << " " << d._month << " " << d._day;return out;
}void test4()
{int i = 123;double d = 44.55;ostringstream oss;//序列化oss << i;string stri = oss.str();oss.str("");//清空ossoss << d;string strd = oss.str();cout << strd<< endl;oss.str("");//清空ossDate d1(2022, 10, 11);oss << d1;string strdt = oss.str();cout << strdt << endl;istringstream iss(strdt);//反序列Date d2;iss >> d2;cout << d2 << endl;
}
stringstream
这个对象可以用于字符串拼接,也可以用来将其他类型转为str
-
stringstream实现转换,实际上是维护了一个string对象实现的
-
我们可以使用str(“”)清空里面的string对象,设置为空字符串
-
多次数据类型转换的时候,需要用clear()来清空,才能正确转换。不过clear()不会清空底层的string对象
-
因为使用的是string对象,所以使用的时候不需要格式化控制,可以自动推导类型
stringstreams在转换结尾时(即最后一个转换后),会将其内部状态设置为badbit
因此下一次转换是必须调用clear()将状态重置为goodbit才可以继续转换
这里在第一次调用stringstream操作后,我们没有进行clear,会发现后续的double类型转换失败了
执行了clear之后,转换成功