文章目录
- C++ IO流
- 1. C语言IO
- 2. C++IO
- 2.1 C++标准IO流
- 2.2 C++文件IO流
- 2.3 C++ IO 文件常用函数总结表
- 2.4 C++ stringstream
C++ IO流
回顾一下,C语言中IO输入输出的
1. C语言IO
C语言中常用的输入输出函数有如下几种:前者是格式化标准输入输出,后者是格式化文件输入输出,最后是格式化字符串输入输出。
函数名 | 内容 |
---|---|
scanf | 从标准输入流(键盘)读取格式化的数据 |
fscanf | 从所有输入流读取读取格式化数据 |
sscanf | 从字符串中读取格式化的数据 |
printf | 将格式化的数据输出到标准输出流(屏幕)上 |
fprintf | 将格式化数据输出到所有输出流上 |
sprintf | 将格式化的数据输出到字符串中 |
文件的输入输出需要以下几个函数:
函数名 | 功能 |
---|---|
fopen | 打开文件流 |
fclose | 关闭文件流 |
fscanf | 从所有输入流读取读取格式化数据 |
fprintf | 将格式化数据输出到所有输出流上 |
fread | 二进制输入 |
fwrite | 二进制输出 |
//文件开关
FILE* fopen (const char* filename, const char* mode);
int fclose (FILE* stream);
//格式化读写
int fprintf (FILE* stream, const char* format [, argument ]...);
int fscanf (FILE* stream, const char* format [, argument ]...);
//二进制读写
size_t fwrite (const void* buffer, size_t size, size_t count, FILE* stream);
size_t fread ( void* buffer, size_t size, size_t count, FILE* stream);
使用方式如下:
struct ServerInfo
{
char _ip[32];
int _port;
friend ostream& operator<<(ostream& os, ServerInfo& info);
friend ostream& operator>>(ostream& os, ServerInfo& info);
};
//测试C语言二进制读写
void TestC_Write_Bin() {
ServerInfo info = { "192.168.1.1",80 };
FILE* fout = fopen("info.bin", "wb");
//assert();
fwrite(&info, sizeof(ServerInfo), 1, fout);
fclose(fout);
}
void TestC_Read_Bin() {
ServerInfo info;
FILE* fin = fopen("info.bin", "rb");
fread(&info, sizeof(ServerInfo), 1, fin);
printf("%s:%d", info._ip, info._port);
fclose(fin);
}
//测试C语言字符读写
void TestC_Write_Text() {
ServerInfo info = { "192.168.1.1",80 };
FILE* fout = fopen("info.txt", "w");
fprintf(fout, "%s %d", info._ip, info._port);
fclose(fout);
}
void TestC_Read_Text() {
ServerInfo info;
FILE* fin = fopen("info.txt", "r");
fscanf(fin, "%s %d", &info._ip, &info._port);
printf("%s:%d", info._ip, info._port);
}
2. C++IO
C++ 标准库提供了4个全局流对象cin
、cout
、cerr
、clog
。cout、cerr、clog 是 ostream 类的三个不同的对象。使用 cout 进行标准输出,使用 cin 进行标准输入。同时 C++ 标准库还提供了 cerr 用来进行标准错误的输出,以及 clog 进行日志的输出。
以下是常用的标准输入输出对象:
cin
:标准输入流对象。cout
:标准输出流对象。cerr
:标准错误输出流对象,通常用于输出错误信息。clog
:标准日志流对象,用于输出日志信息。
2.1 C++标准IO流
cout
,cin
是ostream
,istream
类的对象,operator<<
,operator>>
分别是两个对象的操作符重载成员函数。
C++输出输入可直接使用cout>>
和cin>>
,因为其重载了所有内置类型,对于自定义类型需要自行重载操作符>>
和<<
。
cin >> a >> b;
operator<<
和operator>>
的返回值也是ostream&
或istream&
,因此支持连续输入输出,又是一次函数调用。
cout/cin 取代 printf/scanf 的真正原因是 cout/cin 支持自定义类型,符合面向对象的思想。
当需要循环读入数据时,可以采用如下的方式:
string str;
while (cin >> str) {
;
}
从文档中可以看到,operator>>
的返回值是istream
类型,这个对象类型是如何作真假判断的呢?
原因是istream
类的对象支持一个操作符的重载函数叫operator bool
,C++98中叫operator void*
,C++11中叫operator bool
。
这是个特殊的运算符重载函数,该函数不允许限定返回类型,当类型被当作条件判断时,自动调用并将返回值强转为内置的标识,该标识如果为真就继续,如果为假就停止读取。
2.2 C++文件IO流
采用面向对象的思想,C++中文件指针被文件输入输出流对象ofstream
、ifstream
代替。
和fopen
的调用方式类似,创建输入输出流对象,调用其构造函数传入文件地址以及打开模式。fclose
被析构函数代替,且析构函数可以自动调用。
主要的两个IO类
对象构造函数 | 解释 |
---|---|
ofstream (const char fileName, ios_base::openmode mode=ios_base::out)* | 创建输出流对象,并指定文件地址和打开模式 |
ifstream (const char fileName, ios_base::openmode mode=ios_base::in)* | 创建输入流对象,并指定文件地址 |
ofstream-写文件:
//1. 按字符写入文件内容
void writeCharByChar(const std::string& fileName, const std::string& content) {
std::ofstream ofs(fileName);
if (!ofs) {
std::cerr << "无法打开文件进行写入!" << std::endl;
return;
}
for (char c : content) {
ofs.put(c);
}
ofs.close();
}
//2. 使用 put() 方法写入文件内容
void writeUsingPut(const std::string& fileName, const std::string& content) {
std::ofstream ofs(fileName);
if (!ofs) {
std::cerr << "无法打开文件进行写入!" << std::endl;
return;
}
for (char c : content) {
ofs.put(c);
}
ofs.close();
}
//3. 按行写入文件内容
void writeLineByLine(const std::string& fileName, const std::string& content) {
std::ofstream ofs(fileName);
if (!ofs) {
std::cerr << "无法打开文件进行写入!" << std::endl;
return;
}
ofs << content << std::endl;
ofs.close();
}
//4. 使用 write() 方法写入文件内容
void writeUsingWrite(const std::string& fileName, const std::string& content) {
std::ofstream ofs(fileName, std::ios::binary);
if (!ofs) {
std::cerr << "无法打开文件进行写入!" << std::endl;
return;
}
ofs.write(content.c_str(), content.size());
ofs.close();
}
当然,请看下面的表格,它列出了C++中常用的文件打开模式及其解释:
模式 | 解释 |
---|---|
ios::in | 输入模式,用于读取文件。 |
ios::out | 输出模式,用于写入文件。 |
ios::binary | 二进制模式,用于以二进制方式读写文件。 |
ios::ate | 在文件末尾打开文件,初始位置为文件末尾。 |
ios::app | 追加模式,用于在文件末尾追加内容而不覆盖已有内容。 |
ios::trunc | 如果文件已存在,则清空文件内容。 |
ios::in | ios::binary | 同时指定输入模式和二进制模式,用于以二进制方式读取文件。 |
ios::out | ios::binary | 同时指定输出模式和二进制模式,用于以二进制方式写入文件。 |
常用的有如上几种,该变量的值以二进制位中的不同位为1来标识,也就是说使用异或|
就可以组合起来用。
ifstream-读取文件
// 1.按字符读取文件内容
void readCharByChar(const std::string& fileName) {
std::ifstream ifs(fileName);
if (!ifs) {
std::cerr << "无法打开文件进行读取!" << std::endl;
return;
}
char c;
// 逐个字符读取文件内容并输出
while (ifs.get(c)) {
std::cout << c;
}
ifs.close();
}
// 2.使用 get() 方法读取文件内容
void readUsingGet(const std::string& fileName) {
std::ifstream ifs(fileName);
if (!ifs) {
std::cerr << "无法打开文件进行读取!" << std::endl;
return;
}
char buffer[128];
// 逐块读取文件内容并输出,每次读取最多128个字符
while (ifs.get(buffer, sizeof(buffer))) {
std::cout << buffer;
}
ifs.close();
}
// 3.按行读取文件内容
void readLineByLine(const std::string& fileName) {
std::ifstream ifs(fileName);
if (!ifs) {
std::cerr << "无法打开文件进行读取!" << std::endl;
return;
}
std::string line;
// 逐行读取文件内容并输出
while (std::getline(ifs, line)) {
std::cout << line << std::endl;
}
ifs.close();
}
// 4.使用 read() 方法读取文件内容
void readUsingRead(const std::string& fileName) {
std::ifstream ifs(fileName, std::ios::binary);
if (!ifs) {
std::cerr << "无法打开文件进行读取!" << std::endl;
return;
}
// 移动到文件末尾
ifs.seekg(0, std::ios::end);
// 获取文件大小
std::streampos fileSize = ifs.tellg();
// 移动回文件开头
ifs.seekg(0, std::ios::beg);
char buffer[128];
// 逐块读取文件内容并输出,每次读取最多128个字符
while (ifs.read(buffer, sizeof(buffer))) {
std::cout.write(buffer, ifs.gcount());
}
// 处理剩余的字符
ifs.read(buffer, sizeof(buffer));
std::cout.write(buffer, ifs.gcount());
ifs.close();
}
函数:
函数 | 解释 |
---|---|
istream& read(char s, streamsize n);* | read 接口是输入流istream 对象的成员函数,参数是变量和大小。 |
ostream& write(const char s , streamsize n);* | write 接口是输出流ostream 对象的成员函数,参数是写入变量和写入大小。 |
示例
使用一个ConfigManage
类来演示几种文件读写的方式。
struct ServerInfo
{
char _ip[32];
int _port;
friend ostream& operator<<(ostream& os, ServerInfo& info);
friend ostream& operator>>(ostream& os, ServerInfo& info);
};
class ConfigManage {
public:
ConfigManage(const char* fileName)
:_fileName(fileName)
{}
//二进制写入
void WriteBin(ServerInfo& info)
{
ofstream ofs(_fileName.c_str(), ios_base::out | ios_base::binary); //创建输出流对象
ofs.write((const char*)&info, sizeof(ServerInfo)); //调用write接口
}
//二进制读取
void ReadBin(ServerInfo& info)
{
ifstream ifs(_fileName.c_str(), ios_base::in | ios_base::binary);
ifs.read((char*)&info, sizeof(ServerInfo));
cout << info << endl;
}
private:
string _fileName;
};
读写文件更常用的方式是以文本形式读写,因此就可以省略打开模式参数。
ifstream
,ofstream
文件输入输出类中还继承了iostream
的流插入<<
流提取>>
操作符,也就是对象ofs
和ifs
也可以使用<<
或>>
操作符。
struct ServerInfo {
friend ostream& operator<<(ostream& os, ServerInfo& info);
friend ostream& operator>>(ostream& os, ServerInfo& info);
char _ip[32];
int _port;
};
ostream& operator<<(ostream& os, ServerInfo& info) {
os << info._ip << " " << info._port;
return os;
}
istream& operator>>(istream& is, ServerInfo& info) {
is >> info._ip >> info._port;
return is;
}
//文本写入
void WriteText(ServerInfo& info)
{
ofstream ofs(_fileName.c_str());
//write
ofs.write((const char*)&info, sizeof(ServerInfo));
//1.
ofs << info._ip << info._port; //对象未重载<<
//2.
ofs << info; //对象已重载>>
}
//文本读取
void ReadText(ServerInfo& info)
{
ifstream ifs(_fileName.c_str());
//read
ifs.read((char*)&info, sizeof(ServerInfo));
//1.
ofs << info._ip << info._port; //对象未重载<<
//2.
ifs >> info; //对象已重载>>
cout << info << endl;
}
具体调用方式则是如下:
void TestCPP_Write_Bin() {
ServerInfo info = { "192.168.1.1",80 };
ConfigManage con("config.bin");
con.WriteBin(info);
}
void TestCPP_Read_Bin() {
ServerInfo info;
ConfigManage con("config.bin");
con.ReadBin(info);
}
void TestCPP_Write_Text() {
ServerInfo info = { "192.168.1.1",80 };
ConfigManage con("config.bin");
con.WriteText(info);
}
void TestCPP_Read_Text() {
ServerInfo info;
ConfigManage con("config.bin");
con.ReadText(info);
}
文件的输入输出流对象调用构造函数时也可能会失败,C++采取面向对象抛异常的形式。
2.3 C++ IO 文件常用函数总结表
函数及操作符 | 说明 |
---|---|
文件位置操作 | |
ifs.seekg(pos) | 设置输入位置指针到指定位置。 |
ofs.seekp(pos) | 设置输出位置指针到指定位置。 |
ifs.tellg() | 返回输入位置指针的当前位置。 |
ofs.tellp() | 返回输出位置指针的当前位置。 |
按字符读取和写入 | |
ifs.get(char& ch) | 从输入流中读取一个字符。 |
ofs.put(char ch) | 向输出流中写入一个字符。 |
按行读取和写入 | |
std::getline(ifs, std::string& str) | 从输入流中读取一行文本,存储到字符串中。 |
ofs << str | 向输出流中写入一个字符串。 |
读取和写入块数据 | |
ifs.read(char* buffer, streamsize n) | 从输入流中读取n个字符到缓冲区中。 |
ofs.write(const char* buffer, streamsize n) | 将缓冲区中的n个字符写入输出流中。 |
流状态检查 | |
ifs.eof() | 判断输入流是否到达文件末尾。 |
ifs.fail() | 判断输入流是否发生读取错误。 |
ifs.good() | 判断输入流是否处于良好状态。 |
ifs.clear() | 清除流的错误状态标志。 |
字符串流 | |
std::istringstream iss | 创建字符串输入流。 |
std::ostringstream oss | 创建字符串输出流。 |
iss.str(std::string str) | 将字符串设置为输入流的内容。 |
oss.str() | 获取输出流的字符串内容。 |
流插入和提取操作符 | |
ifs >> var | 从输入流中提取数据到变量。 |
ofs << var | 将变量的数据插入输出流。 |
2.4 C++ stringstream
在头文件 下,有三个类:istringstream、ostringstream 和 stringstream,分别用来进行字符串流的输入、输出和输入输出操作。
-
istringstream
类用于从字符串中提取数据,类似于从文件或标准输入流中读取数据。它可以将字符串内容转换为各种数据类型。 -
ostringstream
类用于将数据写入字符串,类似于将数据写入文件或标准输出流。它可以将各种数据类型转换为字符串。 -
stringstream
类同时支持字符串输入和输出操作。它可以在同一个对象中进行数据的读取和写入。
下面的示例代码展示了如何使用 ostringstream
和 istringstream
对自定义对象进行序列化和反序列化
struct PersonInfo {
std::string _name;
int _age;
friend std::ostream& operator<<(std::ostream& os, const PersonInfo& info) {
os << info._name << " " << info._age;
return os;
}
friend std::istream& operator>>(std::istream& is, PersonInfo& info) {
is >> info._name >> info._age;
return is;
}
};
int main() {
// 序列化
PersonInfo info1 = { "zhangsan", 20 };
std::ostringstream oss;
// 1. 使用对象未重载 << 操作符
oss << info1._name << " " << info1._age;
// 2. 使用对象已重载 << 操作符
oss << " " << info1;
std::string str = oss.str();
std::cout << "序列化后的字符串: " << str << std::endl;
// 反序列化
PersonInfo info2;
std::istringstream iss(str);
iss.str(str);
return 0;
}
使用 stringstream
类也可以达到同样的效果:
std::stringstream ss;
ss << info1; // 写入对象到字符串流
ss << " serialized"; // 追加一些文本
PersonInfo info3;
std::string additionalText;
ss >> info3 >> additionalText; // 从字符串流中读取对象和追加的文本
std::cout << "stringstream 读取的对象: " << info3._name << " " << info3._age << std::endl;
std::cout << "附加的文本: " << additionalText << std::endl;