一起来康康C++中的文件IO操作吧
文章目录
- 1.operator bool
- 2.C++文件IO流
- 3.文件操作
- 3.0 关于按位与的说明
- 3.1 ifstream
- 3.2 ofstream
- 流插入文本
- 3.3 ostringstream/istringstream
- 3.4 stringstream
- 3.5使用stringstream的注意事项
- 结语
1.operator bool
之前写OJ的时候,就已经用过上面这种方式来获取多组测试用例
string s;
while(cin>>s)
{
cout << s << endl;
}
不过之前一直没有去了解这里的底层原理是什么,借此机会一并说明
io流可以进行while判断的依据,是因为库的源码中重载了operator bool
没错,operator
不仅可以重载一个操作符,它还可以重载一个类型!即将这个类转换为bool类型,return
一个bool类型的值用于while的判断
同理,重载int/double
这些类型都是可行的!
另外,要想停止上面的多组输入,在VS下可以用
ctrl+z
的方式解决,而不要用ctrl+c
直接杀掉进程
2.C++文件IO流
C++的文件io类设计的较为复杂,其中还出现了菱形继承,也就是我们最常用的iostream
上面提到的operator bool
就是基类IOS
实现的,子类都没有去重写
- cout为标准输出,将数据从内存流中输入到显示器上
- cin为标准输入,通过键盘输入数据到程序中
- cerr用于标准错误的输出
- clog进行日志输出
其中需要注意的一点是,空格和回车会被当作数据之间的分隔符,所以字符串中不能有空格,回车和空格也不能通过cin读入
如果需要读入带空格的完整一行,可以使用getline
函数
- 为什么cin和cout可以输入输出所有类型?
因为库里面已经将所有类型通过操作符重载<<
和>>
实现了,达到了自动类型识别的效果
3.文件操作
C++标准库中提供的打开方式如下,我们可以根据不同情况传入不同的值,或者一次性用按位或|
传入多个打开方法
同时因为C++类和对象会自动调用析构函数,所以我们也不需要手动close文件
3.0 关于按位与的说明
这里为何可以用按位与传入多个方法?
假设这些方法就是简单的数字2/4/8
(必须是2的倍数)我们可以通过按位与了之后,在按位或,判断某一个数字是否在其中
如果或了之后的数字等于它本身,说明数据在其中!
- 为什么需要是2的倍数呢?
这种方法在linux中常见,比如linux系统的文件接口
3.1 ifstream
这个对象是用于读取文件的,默认情况下,传入的打开方法为in
void test1()
{
ifstream ifs("test.txt");
while (ifs)
{
char ch = ifs.get();
cout << ch;
}
}
因为重载了bool,所以可以很方便的直接用while来判断结束,成功输出了文件中的内容
ifstream ifs("test.txt");
char ch;
while (ifs >> ch)
{
cout << ch;
}
第二种读取方法采用了流插入>>
上面提到过,流提取和插入的时候,会把空格和换行当作数据的分隔符,所以它是不能打印出空格和换行
3.2 ofstream
写文件的方式同上,用out方法打开文件就可以了(默认传的就是out)
ofstream ifs2("test1.txt",ios::out);
char str[] = "i love u\n";
ifs2.write(str,sizeof(str));
不过这个和C语言的w方法一样,写入的时候会覆盖文件中已有的内容。如果想进行追加,则需要在后面加上app
;如果是执行二进制读写,则需要与上ios::binary
ofstream ifs2("test1.txt",ios::out|ios::app);
char str[] = "i love u";
ifs2.write(str,sizeof(str));
运行成功后会在文件尾部追加内容
注意,这里的字符串不能用string进行处理,因为string内部只存了一个指向字符串空间的指针,写入string相当于把指针写入进文件中,是么有用的!
流插入文本
但是,如果我们用<<
进行提取的时候,就可以用string了
void test2()
{
ifstream ifs("test.txt");
ofstream ofs("test1.txt");
char ch;
ch = ifs.get();
while (~ch)
{
ofs << ch;
cout << ch;
ch = ifs.get();
}
string s("i love you");
ofs << s;
}
程序运行后,会把test.txt
中的内容输出到控制台,同时写入到test1.txt
最后还会写入一个string
的内容
这是因为我们调用<<
的时候,就和cin<<s
一样,调用的是string
重载的流提取操作符
底层实现的时候会将其转为C语言的字符串,从而写入到了文件中
所以当我们需要写入一个自定义类型的时候,可以重载流提取操作符,不仅可以更方便的打印,还可以写入到文件中
3.3 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("");//清空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;
}
3.4 stringstream
这个对象可以用于字符串拼接,也可以用来将其他类型转为str
stringstream sstream;
// 将多个字符串放入 sstream 中
sstream << "first" << " " << "string,";
sstream << " second string";
cout << "strResult is: " << sstream.str() << endl;
// 清空 sstream
sstream.str("");
sstream << "third string";
cout << endl;
cout << "After clear, strResult is: " << sstream.str() << endl;
3.5使用stringstream的注意事项
-
stringstream实现转换,实际上是维护了一个string对象实现的
-
我们可以使用
str("")
清空里面的string对象,设置为空字符串 -
多次数据类型转换的时候,需要用
clear()
来清空,才能正确转换。不过clear()
不会清空底层的string对象 -
因为使用的是string对象,所以使用的时候不需要格式化控制,可以自动推导类型
stringstreams
在转换结尾时(即最后一个转换后),会将其内部状态设置为badbit
因此下一次转换是必须调用clear()
将状态重置为goodbit
才可以继续转换
这里在第一次调用stringstream
操作后,我们没有进行clear,会发现后续的double类型转换失败了
执行了clear之后,转换成功!
结语
关于C++IO流操作的基本认识到这里就over了。
因为在实际中用的并不算多,所以这部分的内容大多数是了解一二,知道如何使用,以及3.0
中提到的按位与操作是怎么实现的就OK了!