引言
有时候我们需要自定义文件内容格式,然后从文本文件中读取数据或控制信息,在程序里面进行相应的处理。例如用文本文件存储空间点的坐标,设定文件的每一行有三个数,分别表示x y z,比如
0 0 0
1 2 3
2 3 3
表示(0, 0, 0), (1, 2, 3), (2, 3, 3)三个点。
由于文件里面的数据组织格式是自己定义的,别人不一定能看得懂,甚至过段时间之后自己都忘了。为了增强输入文件的可读性,最好能在文件中加入注释。如上面的坐标点文件加入注释后变成
#x y z, 单位:m
0 0 0 #第一个点 (0,0,0)
1 2 3 #意义同上
2 3 3
这时候,在程序中读取文本文件的数据时就需要进行适当的处理,以达到跳过注释的效果。此外,文本文件中的空行有时候也会被错误的当作有效数据,因此读取文件时也要考虑跳过空行。
解决思路
读取文件时,以行为单位进行处理,用getline读取,如果有注释则先把注释删掉再进行处理。
- 先读一行
- 把该行的注释和开头的连续多个空格(以及tab)删掉
- 读取数据
- 读下一行
- 处理注释,空格,空行
- …
代码示例
用程序读取如下包含注释的文本文件,文件名为file
#这是一行注释,下面有两行空行
#x y z
0 0 2#第一个点
9, 9, 10 #第二个点
11, 10 10 #第三个点
100, 0.9 3.1415926 #第四个点
10; 20; 30#第5个点,用分号作分隔符
程序代码如下:
#include <iostream>
#include<iomanip>
#include <string>
#include <sstream>
#include <fstream>
#include <cassert>
#include<vector>
using namespace std;
//处理注释,空格,和空行的函数
//line,表示一行文本内容
//comment_str,表示注释前导字符串,默认设置为#,也可以用//或者%
//string删除字串,http://www.cplusplus.com/reference/string/string/erase/
//string查找字串,http://www.cplusplus.com/reference/string/string/find_first_not_of/
void line_process(std::string &line, const std::string comment_str = "#")
{
for (char &c : line)//C++11以上版本的语法
{
//制表符 tab,逗号,分号都当作有效的分隔符,统一转成空格
//为了避免错误,回车符和换行符也转为空格(否则无法处理空行)
if (c == '\t' || c == ',' || c == ';' || c == '\r' || c == '\n')
c = ' ';
}
line.erase(0, line.find_first_not_of(" "));//删除行首空格
line.erase(line.find_last_not_of(" ") + 1);//删除行末空格
//调用的string& erase (size_t pos = 0, size_t len = npos);
//len为默认参数
//查找注释符所在位置,如果不存在,则得到string::npos
int n_comment_start = line.find_first_of(comment_str);
if (n_comment_start != std::string::npos)//这一句必须的
line.erase(n_comment_start); //删除注释
//处理完毕。如果这一行只有空格,制表符 tab,注释,那么处理后line为空;
//如果行首有多个空格(或者空格和tab交错),行尾为注释,那么处理后字符串line的
//行首多个空格(和tab)和行尾注释被删掉,只保留有意义的内容。
}
//举个栗子
int main()
{
ifstream is("file");
vector<double>x,y,z;
x.clear();
y.clear();
z.clear();
string line;
while (std::getline(is, line))
{
line_process(line);//把行首和行尾的多个空格, tab去掉,把注释文字也去掉
if (line.empty()) continue;//line为空则继续
//根据实际需求处理
std::istringstream iss(line);
double x_temp,y_temp,z_temp;
iss>>x_temp>>y_temp>>z_temp;
x.push_back(x_temp);
y.push_back(y_temp);
z.push_back(z_temp);
}
//输出结果看看
for(int i=0;i<x.size();i++){
cout<<setw(12)<<x[i]
<<setw(12)<<y[i]
<<setw(12)<<z[i]<<endl;
}
return 0;
}
由于用到了C++11语法,编译器选项上要指定C++标准为C++11。如果上面程序文件名为test.cpp,所用编译器为g++,则在终端输入:
g++ -std=c++11 test.cpp -o test
得到名为test的可执行程序,然后运行:
./test
结果:
0 0 2
9 9 10
11 10 10
100 0.9 3.14159
10 20 30
可以看到,程序读取时,成功地跳过了注释和空行,并且把分号,逗号,和tab都当成分隔符