C语言中我们用到的最频繁的输入输出方式就是 scanf() 和 printf()。
scanf(): 从标准输入设备(键盘)读取数据,并将值存放在变量中。
printf(): 将指定的文字/字符串输出到标准输出设备(屏幕)。注意宽度输出和精度输出控制。
除此之外,C语言借助了相应的缓冲区来进行输入和输出。如下图所示:
对输入输出缓冲区的理解:
1.可以屏蔽掉低级 I/O 的实现,低级I/O的实现依赖操作系统本身内核的实现,所以如果能够屏蔽这部分的差异,可以很容易写出可移植的程序。
2.可以使用这部分的内容实现 “行” 读取的行为,对于计算机而言是没有 “行” 这个概念,有了这部分,就可以定义“行”的概念,然后解析缓冲区的内容,返回一个“行”。
“流”即是流动的意思,是物质从一处向另一处流动的过程,是对一种有序连续 且 具有方向性 的数据( 其单位可以是 bit,byte,packet )的抽象描述。
C++流是指信息从外部输入设备(如键盘)向计算机内部(如内存)输入和从内存向外部输出设备(显示器)输出的过程。这种输入输出的过程被形象的比喻为“流”。
它的特性是:有序连续、具有方向性
为了实现这种流动, C++定义了 I/O 标准类库,这些每个类都称为流/流类,用以完成某方面的功能
我们之前在写OJ题的时候经常可以看到如下代码
int main()
{
string s;
while (cin >> s)
{
cout << s << endl;
}
return 0;
}
目前我们认为cin是一个全局的类,cin>>s 的返回值其实就是cin这个全局类运算符重载>>返回的值,我们可以看一下
我们可以看到,重载的>>运算符返回值是一个istream的对象,不能进行while的判断,下面我们学习以下原理。
我们可以看到,内置类型可以通过隐式类型转化为自定义类型,自定义类型无法转化为内置类型
此时我们看到没有报错,c++一个特例,使用opertor重载,可以将自定义类型转化为内置类型
我们再C++中使用的cin和cout都是继承父类,父类继承ios实现的,iostream使用菱形继承实现,同时具有istream和ostream的功能。
上面提到的operator bool就是基类IOS实现的,子类都没有去重写
其中需要注意的一点是,空格和回车会被当作数据之间的分隔符,所以字符串中不能有空格,回车和空格也不能通过cin读入
如果需要读入带空格的完整一行,可以使用getline函数
为什么cin和cout可以输入输出所有类型? 因为库里面已经将所有类型通过操作符重载<< 和>>实现了,达到了自动类型识别的效果
之前将数据写入文件
#define _CRT_SECURE_NO_WARNINGS 1
#include
using namespace std;
#include
//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
#define _CRT_SECURE_NO_WARNINGS 1
#include
using namespace std;
#include
#include
//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 << " " <
return 0;
}
可以看到,我们以前学的文件IO只有二进制读写比较方便,但是二进制读写可读性差,C++中使用ifstream和ofstream无论再二进制还是文本读写都非常方便。
这个类可以将不同的类型转为字符串
这种操作被称为序列化和反序列化,在处理自定义类型的时候非常好用
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("");//清空oss
oss << d;
string strd = oss.str();
cout << strd<< endl;
oss.str("");//清空oss
Date d1(2022, 10, 11);
oss << d1;
string strdt = oss.str();
cout << strdt << endl;
istringstream iss(strdt);//反序列
Date d2;
iss >> d2;
cout << d2 << endl;
}
stringstream实现转换,实际上是维护了一个string对象实现的
我们可以使用str(“”)清空里面的string对象,设置为空字符串
多次数据类型转换的时候,需要用clear()来清空,才能正确转换。不过clear()不会清空底层的string对象
因为使用的是string对象,所以使用的时候不需要格式化控制,可以自动推导类型
stringstreams在转换结尾时(即最后一个转换后),会将其内部状态设置为badbit
因此下一次转换是必须调用clear()将状态重置为goodbit才可以继续转换
这里在第一次调用stringstream操作后,我们没有进行clear,会发现后续的double类型转换失败了