- 1. C中的文件读写示例:使用fscanf和fprintf
- 2. ifstream/ofstream/fstream使用示例
- 3. 总结
- 4. 改进:既不要_CRT_SECURE_NO_WARNINGS,又要格式化输入输出
在C语言中,使用fscanf
和fprintf
操作由fopen
打开的文件指针,进行文件读写,其格式化读写方式使用起来相当便利。但是在C++中,文件读写通常使用fstream
方式,对于C里面的两个函数以及fopen
开始强调安全性问题,推荐使用fopen_s
,fscanf_s
和fprintf_s
代替,在最新的编译器立面通常需要在“项目-属性-C/C++-预处理器-预处理器定义”中加入_CRT_SECURE_NO_WARNINGS
才能保证不报错。那么如何使文件流的方式用起来和格式化读写一样便利呢?本文进行探究。
1. C中的文件读写示例:使用fscanf和fprintf
例如有一些点保存在数组中,使用fscanf和fprintf可以很方便地按照一定的格式(一行保存两个坐标)将它们保存到文本文件中,如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| void savePoints(vector<Point>& pts, string fileName) { FILE* fp; fp = fopen(fileName.c_str(), "w");
if (!fp) { cout << "Open file failed!" << endl; return; }
for (size_t i = 0; i < pts.size(); i++) { fprintf(fp, "%d %d\n", pts[i].x, pts[i].y); }
if (pts.size() == 0) { cout << "[WARNING] point number is 0" << endl; }
fclose(fp); }
void readPoints(vector<Point>& pts, string fileName) { FILE* fp; fp = fopen(fileName.c_str(), "r");
if (!fp) { cout << "Open file failed!" << endl; return; }
while (!feof(fp)) { Point pt; fscanf(fp, "%d %d\n", &pt.x, &pt.y); pts.push_back(pt); }
if (pts.size() == 0) { cout << "[WARNING] point number is 0" << endl; }
fclose(fp); }
|
2. ifstream/ofstream/fstream使用示例
C++ 文件和流 | 菜鸟教程
将读写点的代码用fstream
改写,大致如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| void savePoints(vector<Point>& pts, string fileName) { fstream file; file.open(fileName, ios::out | ios::trunc);
for (size_t i = 0; i < pts.size(); i++) { file << pts[i].x << " " << pts[i].y << endl; }
file.close(); }
void readPoints(vector<Point>& pts, string fileName) { fstream file; file.open(fileName, ios::in);
Point pt; char ch; while (!file.eof()) { file >> pt.x >> pt.y; pts.push_back(pt); }
file.close(); }
|
其中open()
函数的第二个参数可以参考下表。
模式标志 |
描述 |
ios::app |
追加模式。所有写入都追加到文件末尾。 |
ios::ate |
文件打开后定位到文件末尾。 |
ios::in |
打开文件用于读取。 |
ios::out |
打开文件用于写入。 |
ios::trunc |
如果该文件已经存在,其内容将在打开文件之前被截断,即把文件长度设为 0。 |
但是用改写的代码执行后存在一个问题,由于采用的是流的方式进行读写,因此更偏向于单个字符或单个数字的操作,读入时会自动跳过空格和换行符,当输出到文件时使用file << pt.x << " " << pt.y << endl;
,那么对应输入时只需要用file >> pt.x >> pt.y;
即可。这就导致两个问题:
- 无法自定义分隔符。如果使用逗号或多个逗号作为分隔符,那么必须将其作为单个字符读入到变量中:
file >> pt.x >> ch >> pt.y;
,或者已知分隔符的长度,控制文件指针跳转(用到seek get):file >> pt.x; file.seekg(n, ios::cur); file >> pt.y;
;
- 文件尾难以判断。假设文件中保存了1000个点,那么在保存时,通常会记录1000行的x、y坐标,最后一行为空。在使用
fscanf
格式化输入时,会自动跟到数据和换行符(例如fscanf(fp, "%d %d\n")
),而在使用文件流时,最后一个换行符的存在导致读取完最后一个数据行之后,file.eof()
函数返回值仍为false
,因此会额外执行一次循环,而此时file >> pt.x >> pt.y
根本无法读入数据,因此会导致最后一个数据项在数组的末尾出现两次。因此,需要设计额外的逻辑来判断真实的文件尾。
3. 总结
fstream采用流的方式读取文件,更加安全,也符合读写逻辑,但损失了格式化的便捷性。
针对流的操作只能用流的方式来应对,改进一下上面的例子,当数据之间的分隔符为空格时,可以不考虑分隔符直接读入;在确保数据文件的最后一项数据之后有一个换行符的前提下,可以采用读完数据再判断是否到达文末的方式打断循环,这种方法可以解决当前问题,但是如果最后一行没有换行符,那么这种方法就会少读取一行数据。而相对地,fscanf只会提示错误,而不会导致数据缺失。综合来看,格式化输入输出要比纯粹的流方法来得方便,流方式无法达到格式化输入的便利性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| void savePoints(vector<Point>& pts, string fileName) { fstream file; file.open(fileName, ios::out | ios::trunc);
for (size_t i = 0; i < pts.size(); i++) { file << pts[i].x << " " << pts[i].y << endl; }
file.close(); }
void readPoints(vector<Point>& pts, string fileName) { fstream file; file.open(fileName, ios::in);
Point pt; while (true) { file >> pt.x >> pt.y; if (file.eof()) break; pts.push_back(pt); }
file.close(); }
|
4. 改进:既不要_CRT_SECURE_NO_WARNINGS,又要格式化输入输出
解决方案:使用fopen_s
,fscanf_s
,fprintf_s
。使用上的区别主要在fopen_s
上,三个函数的使用如下所示。使用这样的代码就可以避免VS报错和警告。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| void savePoints(vector<Point>& pts, string fileName) { FILE* fp; if (!fopen_s(&fp, fileName.c_str(), "w") != 0 || !fp) { cout << "File open failed!" << endl; return; }
for (size_t i = 0; i < pts.size(); i++) { fprintf_s(fp, "%d %d\n", pts[i].x, pts[i].y); } fclose(fp); }
void readPoints(vector<Point>& pts, string fileName) { FILE* fp; if (fopen_s(&fp, fileName.c_str(), "r") != 0 || !fp) { cout << "File open failed!" << endl; return; }
Point pt; while (!feof(fp)) { fscanf_s(fp, "%d %d\n", &pt.x, &pt.y); pts.push_back(pt); }
fclose(fp); }
|