一、概述
网上关于使用 C++ 读写文件的内容不是所期待的,所以来写一下。
无论是读文件还是写文件,首先需要打开文件,需要使用两个类
- ifstream (Input File stream) 用于读取
- ofstream (Output File stream) 用于写入
这两个类继承自 std::io_base 用于处理 io 流。
需要包含头文件
#include <fstream>
二、打开文件
2.1 - ifstream 打开文件
两种打开文件的方式,
使用成员函数 open,有两个参数:
- 待打开的文件名
- 文件打开的模式
ifs.open("file.extension", openMode);
不使用成员函数 open ,在构造时直接指定文件名与打开模式
ifstream ifs("file.extension", openMode);
指定文件路径有多种方式
- 绝对路径 (完整路径,从硬盘的根目录出发),大概为以下格式:
// Windows 下 "C:/Documents and Settings/User/Desktop/file.txt" // Linux 下 "/home/user/file.txt"
- 相对路径 (从可执行文件 / exe 文件开始的路径),如果文件位于可执行文件下子目录 subdirectory 下则为
"subdirectory/file.txt"
- 如文件位于可执行文件所在的目录,则需要直接指定文件名即可
打开模式也来自 ios_base 类,需要指定命名空间 ios_base::
或 ios::
,由于我们需要使用读取的模式所以为 ios::in
表示 input,输入。
打开后,需要确定是否打开,if (ifs)
。
操作结束后,需要关闭,使用成员函数 close,ifs.close()
。
代码示例
#include <iostream>
#include <fstream>
using namespace std;
int main(int argc, char* argv[])
{
// 以读取方式打开
ifstream ifs("test.txt", ios::in);
if (ifs) // 确保文件是否打开成功
{
// 语句
ifs.close(); // 关闭文件
}
else // 否则
cerr << "Unable to open the file !" << endl;
return 0;
}
当我们使用 ifstream 打开文件时,打开模式 ios::in
为可选内容,此选项为默认选项。
2.2 - ofstream 打开文件
使用 ofstream 以写入的方式打开文件,同样有两种方式,一种直接声明,一种使用函数 open ,与 ifstream 相同。
对于写入,文件的打开方式有很多。
文件打开方式 | 速记 | 说明 |
---|---|---|
ios::out | output (输出) | 指定文件的打开方式为写入,一般为必要参数,但在使用 ofstream 对象时,为默认参数 |
ios::app | append (添加) | 当打开文件用于写入时,会在已有数据之后写入,不覆盖原有内容。使用此种打开方式,每次写入都会置于文件末尾,即便之前更改了位置。 |
ios::trunc | truncate (截断) | 当打开文件时,如果文件存在内容则清空文件内容 |
ios::ate | at end (在结束处) | 以写入方式打开文件,并将文件指针置于末尾。与 ios::app 不同之处为,如果改变了指针位置,写入不一定会在文件末尾 |
对于这些写入文件的打开方式,如果文件不存在则会创建。
用于指定多个打开模式,使用操作符或 |
。英文发音为 pipe 。
代码示例:
#include <iostream>
#include <fstream>
using namespace std;
int main(int argc, char* argv[])
{
ofstream ofs("test.txt", ios::out | ios::trunc); // 声明流与打开方式
if (ofs) // 文件是否打开
{
// 操作语句
ofs.close(); // 关闭文件
}
else // 否则
cerr << "Error to open file !" << endl;
return 0;
}
2.3 - fstream 以读取和写入的方式打开
使用 fstream 可以以读写一起的方式打开文件,工作原理与 ifstream 和 ofstream 相同。
原型为
fstream flux("file.extension", ios::in|ios::out | [ios::trunc|ios::ate]);
中括号表示,需要使用两者中之一的打开方式
- 这是唯一的办法用以读取和写入的方式打开文件
- 中括号在实际代码中是不需要写的
- ios::app 是不可以选择的打开方式,此种文件打开方式,只允许在写入时使用,而此处 fstream 用于读取和写入
使用此种打开方式,文件必须存在!ios::in|ios::out
必须指定,由于文件已经存在,打开时需要指定 ios::ate
用于保留原始内容,或指定 ios::trunc
用于清空内容。
ifsteam, ofstream, fstream 都支持多种字符串格式
// const char *
explicit basic_fstream(const char* _Filename, ios_base::openmode _Mode = ios_base::in | ios_base::out, int _Prot = (int) ios_base::_Openprot);
// const string &
explicit basic_fstream(const string& _Filename, ios_base::openmode _Mode = ios_base::in | ios_base::out, int _Prot = (int) ios_base::_Openprot);
// const wchar_t *
explicit basic_fstream(const wchar_t* _Filename, ios_base::openmode _Mode = ios_base::in | ios_base::out, int _Prot = (int) ios_base::_Openprot);
// const wstring &
explicit basic_fstream(const wstring& _Filename, ios_base::openmode _Mode = ios_base::in | ios_base::out, int _Prot = (int) ios_base::_Openprot);
//....
ios_base::_Openprot 为打开保护
#include <fstream>
#include <string>
using namespace std;
int main(int argc, char* argv[])
{
string my_file = "text.txt";
fstream fs(my_file.c_str(), ios::in);
if (fs) // 如果打开文件成功
{
// 操作语句
fs.close(); // 关闭文件
}
else // 否则
cerr << "Error to open file !" << endl;
return 0;
}
三、读取和写入文本文件
3.1 - 读取文件内容
对于文件的读取,有多种不同的方法,如下
- getline(stream, 字符串) :用于读取完整一行内容
- stream.get(字符) :用于读取一个字符
- stream >> 变量 :用于从文件中获取内容知道遇到分隔符 (空格,换行,...)
3.1.1 - getline
getline 需要两个参数,使用 ifstream 或 fstream 创建的流,和 用于存储内容的“目标”,通常为一个字符串,需要包含头文件 <string>
示例:
#include <iostream>
#include <string>
#include <fstream>
using namespace std;
int main(int argc, char* argv[])
{
ifstream ifs("test.txt", ios::in); // 以读取模式打开
if (ifs)
{
string context;
getline(ifs, context);
cout << context; // 显示行
ifs.close();
}
else
cerr << "Unable to open the file !" << endl;
return 0;
}
为了读取完整的文件,需要一行一行的读取,需要一个重复的循环,getline 会读取到文件结束
if (ifs)
{
string line;
while (getline(ifs, line))
{
cout << line << endl;
}
}
getline 有一个重载方法,第三个参数为结束字符,这个结束字符的默认值为换行符 \n
,因此,我们可以一行一行的读取内容。
getline 只有在使用 ifstream 和 fstream 时有效,在 ofstream 时不再可用。
3.1.2 - get
get 函数用于读取一个字符,当然在使用循环的情况下,也可以读取完整的文件。
语法为
stream.get(character);
这个方法读取文件中的一个字符,然后将其存储在 char 类型的 character 变量中。
示例
#include <iostream>
#include <fstream>
using namespace std;
int main(int argc, char* argv[])
{
ifstream ifs("test.txt", ios::in);
if (ifs)
{
char character; // 字符型变量用于存储读取的字符
ifs.get(character);
cout << character;
ifs.close();
}
else
cerr << "Unbale to open the file !" << endl;
return 0;
}
为了读取完整的文件,循环与 getline 相同。
3.1.3 - >>
这个符号应该不陌生,应该见到过与 cin 一起使用的情况,对于文件的运行原理也一样。这个操作符从文件中读取内容,直到遇到分隔符如空格或者换行符等。
比如一个文件中有以下内容
12 345
test
SDZ
可见,文件中存在两个整数和两个字符串,若我们需要全部获取到,需要声明两个 int
和 两个 string
示例:
#include <iostream>
#include <string>
#include <fstream>
using namespace std;
int main(int argc, char* argv[])
{
ifstream ifs("test.txt", ios::in);
if (ifs)
{
int integer1, integer2;
string string1, string2;
ifs >> integer1 >> integer2 >> string1 >> string2;
ifs.close();
}
else
cerr << "Unable to open the file !" << endl;
return 0;
}
3.2 - 写入文件
写入文件也有多种方式
- stream << 需要写入的内容; 向文件中写入一个任意的元素 (string, int ...)
- stream.put(字符); 向文件中写入单个字符
3.2.1 - <<
这个符号对于了解 C++ 的人来说不应算陌生,它被用于 cout ,对于文件功能与显示到标准输出基本一致。
此操作符允许向文件中写入字符,或字符串,或整数...
操作符的使用语法为
stream << element1 << element2 << ...;
如代码所示与 cout 的使用并无太多区别。代码示例:
#include <iostream>
#include <string>
#include <fstream>
using namespace std;
int main(int argc, char* argv[])
{
ofstream ofs("test.txt", ios::out | ios::trunc);
if (ofs)
{
string name = "Zhangsan";
int age = 23;
ofs << "Birthday: " << 25 << '/' << 6 << '/' << 1998 << endl;
ofs << "Hello, " << name << ", you are " << age << " years old.";
ofs.close();
}
else
cerr << "Unable to open the file !" << endl;
return 0;
}
我们向文件中写入 一些字符串,字符,一些整数和换行符。不需要显式声明类型,在使用时不用刻意区分,文本文件中内容如下:
Birthday: 25/6/1998
Hello, Zhangsan, you are 23 years old
3.2.2 - put
put 被较少使用,由于不像 << 一样,只接受一个字符
使用时,只需要替换上述写入部分的代码
if (ofs)
{
char chr = 'X';
ofs.put(chr);
// 等价于 ofs.put('X');
ofs.close();
}
四、文件中的位置
与 C 语言相似,也存在文件读写指针的概念,用于处理文件中的写入位置。
4.1 - 知悉指针当前的位置
为了知道我们当前在文件中所处的位置,需要知道打开的方式
如果使用 ifstream 打开,存在方法 tellg()
如果使用 ofstream 打开, 存在方法 tellp()
这两种方法,返回当前文件指针所在的位置,从文件开始的字节编号。
stream.tell[g | p];
4.2 - 移动指针位置
为了移动文件读写指针的位置,同样依赖文件的打开方式
如果使用 ifstream 打开
存在方法 seekg
,这个方法包含两个参数,第一个为字节的编号,第二个为从哪里开始计算这个编号,第二个参数可以为以下数值
- ios::beg ,从文件开始处
- ios::cur , 从当前文件读写指针处
- ios::end , 从文件结尾处
默认值为 ios::beg
如果使用 ofstream 打开
方法几乎为同样的名称 seekp
,它的工作方式与 seekg
一致
stream.seek[g | p](10, ios::beg);
五、一些实用的方法
stream.eof()
,为了得知文件读写指针是否到达了文件尾;stream.ignore(num, characterEnd)
,用于忽略 num 个字符,或忽略所有直到遇到指定的 characterEnd 字符;stream.clear()
,用于将所有的标志位状态置为初始状态;stream.fail()
,用于测试文件流的打开是否正常,可用于检测一个文件是否存在
标志位
英语为 flag ,即一个位,可以有两种值,0 或 1,对于 fstream 来讲,存在四个标志位,
- goodbit
- eofbit
- failbit
- badbit
5.1 - eof
这个方法返回一个布尔值,它会检查标志位 eofbit 是否为 true,即是否到达文件尾,否则为 false
eofbit 变为 true 的情况,要么是没有更多数据可以读取,要么是因为不能继续写入。
5.2 - ignore
这个方法需要两个参数,需要忽略的字符数量以及一个终止的字符。它将忽略掉第一个参数数量个字符,直到遇到第二个参数指定的字符。此种方法可以用作计算文件的行数。
但需要注意两点
- 用于计算行数,需要知道一行的字符数量
- 文件必须使用读取的模式打开
如果不提前知道文件的行数,也可以使用一种简便的方法,需要包含:
#inlcude <limits>
using namespace std;
在第一个参数处可以设置
numeric_limits<int>::max()
这个方法返回一个 int
最大能存储达的数值。一般为 2147483647
使用举例
stream.ignore(numeric_limits<int>::max(), '\n');
5.3 - clear
此方法用于重新置位 fstream 的四个标志位。
在文件读写指针到达文件尾时,eofbit 变为 true,如果此时需要回到文件首,如果不重新置位,则 eofbit 会一直处于 true 的状态,这时就会出问题
5.4 - fail
fail 方法用于实现 faibit 和 badbit 的检测,如果结果为 false,则表示文件流被正确创建,且打开成功。也保证了文件的存在