我假设您知道或能够查找在此要描述的功能,并对C ++编程有一定的了解。
费耶尽管我将本文称为“如何使用C ++解析文件”,但实际上我们主要是对文件进行词法化,即将流分解成其组成部分,而不考虑流包含的语法。 解析实际上包括语法以使其有意义。
认为词汇化是一堆单词中的阅读,而解析则是一句话中的阅读。 每个单词都具有某种含义,但是如果没有句子的上下文,就意味着没有任何非常有用的含义。
我没有使用“如何在C ++中进行词法分析”这一标题,因为你们中的大多数人可能都不知道这意味着什么。 如果您这样做,那么我道歉。
介绍嗨,上次我向大家展示了如何使用C解析文件。在本文中,我现在将介绍如何使用C ++解析文件。
对于那些尚未阅读该文章的人,请在
流和文件 ,因为C ++和C ++相同。但是,使用C ++流时,分别使用cin,cout和cerr而不是stdin,stdout和stderr。 缓冲和双缓冲双缓冲意味着在处理/显示之前从一个缓冲区转储到另一个缓冲区。 在C ++中,所有流库都被缓冲。
解析文件使用描述的缓冲技术可以非常简单地完成文件解析。
没有双缓冲的解析在没有双缓冲的情况下解析文件并不总是可能的。 唯一的方法是只读取和存储数字。
例如,这是一个示例文件:
1, 2, 3, 4, 5
6, 7, 8, 9, 10
要在没有双重缓冲的情况下读入,可以循环以下操作:
// CODE FRAGMENT 1
int itemsParsed = 0;
int items[5];
for (itemsParsed = 0; itemsParsed < 5 && cin.good(); ++itemsParsed) {
cin >> items[itemsParsed];
if (itemsParsed != 4 && cin.peek() == ’,’) {
cin.ignore(1); // clear out comma
}
}
if (!cin.good()) {
--itemsParsed;
// check what flag was set and act appropriately
//...
if (!cin.eof()) {
cin.clear(); // Clear the error flag (unless it is eof)
}
}
请注意,每个数字后的输入流中都必须包含逗号。
逗号后可以有0个或多个空格。
空格可以是常规空格,制表符,垂直制表符(很少使用),回车符或换行符。
代码片段1a有点简单,因为它使用C ++异常处理将普通代码流与例外代码流分开。
// CODE FRAGMENT 1A
int itemsParsed = 0;
int items[5];
cin.exceptions(~ios::goodbit); // turn on exceptions
try {
for (itemsParsed = 0; itemsParsed < 5; ++itemsParsed) {
cin >> items[itemsParsed];
if (itemsParsed != 4 && cin.peek() == ’,’) {
cin.ignore(1); // clear out comma
}
}
}
catch(ios_base::failure failure) {
assert (!cin.good());
// check what flag was set and act appropriately
//...
if (!cin.eof()) {
cin.clear(); // Clear the error flag (unless it is eof)
}
}
在如何解析C中的文件的 代码片段1之后 , 对 代码片段1和代码片段1A都进行了模式化。
有人可能会认为C代码更具可读性。
在某些情况下可能是这样,但是C代码缺少一件事,它仅适用于基本类型。
在C ++中,
提取运算符 ('>>')允许您执行其他操作。 您可以重载该运算符,并以所需的任何方式读取它,就像它是语言的一部分一样。 实际上,这只是对函数的调用。 一个人可以用C做类似的事情,但是看起来像一个函数调用。 运算符重载的全部是语法糖,使运算符只是一个可调用的函数。 有人说这是没有必要的,有人说这使它更清洁。 我的意见是我什么都没有。 这只是做同一件事的另一种方式。 我认为俗话说“铲子一样”。 ;)下面的代码片段仅显示了如何使用这种简化的语法来发挥自己的优势。
#include <iostream>
#include <assert.h>
using namespace std;
// CODE 1
class Point2D
{
int x, y;
public:
Point2D() : x(0), y(0) {}
int getX() { return x; }
int getY() { return y; }
void setX(int x) { this->x = x; }
void setY(int y) { this->y = y; }
};
istream& operator>>(istream& is, Point2D& point) throw (ios_base::failure)
{
ios_base::iostate oldIOState = is.exceptions();
cin.exceptions(~ios::goodbit); // turn on exceptions
try {
int val;
is >> val;
point.setX(val);
if (is.peek() == ’,’) {
is.ignore(1);
}
else {
is.setstate(ios_base::failbit);
throw ios_base::failure(“Missing comma separator”);
}
is >> val;
point.setY(val);
}
catch(ios_base::failure failure) {
assert (!is.good());
// check what flag was set and act appropriately
//...
is.exceptions(oldIOState); // restoring old IO exception handling
throw; // there is no way to recover the stream without more info
}
is.exceptions(oldIOState); // restoring old IO exception handling
return is;
}
int main()
{
Point2D point;
try {
cin >> point;
cout << “(“ << point.getX() << “, “ << point.getY() << “)” << endl;
}
catch(ios_base::failure failure) {
if (cin.bad()) {
cout << “cin bad” << endl;
}
if (cin.fail()) {
cout << “cin failed” << endl;
}
if (cin.eof()) {
cout << “cin hit eof” << endl;
}
if (!cin.eof()) {
cin.clear(); // Clear the error flag (unless it is eof)
}
}
}
现在,CODE 1的作用是创建一个类并重载提取运算符,从而使您可以从流中提取数据并将其放入类中。
您无需再编写此代码。
另外,您可以重载插入运算符 ('<<')并使它像我一样输出,而不必每次都像我一样显式地写出来。
我将其作为练习留给读者。
您应该注意,我正在使用接口函数来写入类。 如果我想紧密耦合
类的提取运算符 ,那么我可能不想将数据读取到临时变量中,然后将其复制到类中。 为此,需要让提取运算符成为该类的朋友。 修改如下:
// CODE 1A
// SEE NOTE BELOW regarding next two lines
class Point2D;
istream& operator>>(istream& is, Point2D& point) throw (ios_base::failure);
class Point2D
{
int x, y;
friend
istream& operator>>(istream& is, Point2D& point) throw (ios_base::failure);
public:
Point2D() : x(0), y(0) {}
int getX() { return x; }
int getY() { return y; }
void setX(int x) { this->x = x; }
void setY(int y) { this->y = y; }
};
istream& operator>>(istream& is, Point2D& point) throw (ios_base::failure)
{
ios_base::iostate oldIOState = is.exceptions();
cin.exceptions(~ios::goodbit); // turn on exceptions
try {
is >> point.x;
if (is.peek() == ’,’) {
is.ignore(1);
}
else {
is.setstate(ios_base::failbit);
throw ios_base::failure(“Missing comma separator”);
}
is >> point.y;
}
catch(ios_base::failure failure) {
assert (!is.good());
// check what flag was set and act appropriately
//...
is.exceptions(oldIOState); // restoring old IO exception handling
throw; // there is no way to recover the stream without more info
}
is.exceptions(oldIOState); // restoring old IO exception handling
return is;
}
注意:代码1顶部的第4行和第5行非常重要。
声明朋友时,必须在将其声明为朋友的类之前声明或定义函数调用。否则,编译器可能会抱怨朋友注入是不推荐使用的功能,或者该函数是没有声明。
要对流进行正则表达式解析,您将需要一个正则表达式库。 这超出了本文档的范围。 在标准C ++中,没有等效的scanf字符类。 您必须自己实现它们或类似的东西,或者下载别人创建的非标准库。 为此,我建议将BOOST.org作为一个很好的资源。 他们制作的库将提交给C ++委员会,以便可能包含在下一个标准修订版中。
使用双缓冲进行解析在上一节中,我展示了如何不对数据进行双重缓冲。 但是,有时候这是不可能的。
读取字符串或一系列字符本质上要求使用双缓冲。 数据被读取到内部缓冲区,然后复制到程序的数据空间。 然后,可以通过进一步处理或显示它来以任何希望的方式对其进行处理。
要读取以空格分隔的字符串,您仍然可以使用
提取运算符,但在字符串或char数组上使用它注意:在char数组上使用Extraction运算符时,请在输入流上使用width()函数,否则您可能会溢出缓冲区,其参数包括终止NULL。 或者,可以使用setw(),但必须包含<iomanip>头文件。要读入一行,请使用字符串库(#include <string>)中的getline()函数。 它也会为您分配资源。 或者,使用本机istream :: getline(),但必须指定缓冲区的大小。
使用三重缓冲进行解析是的,您可以缓冲该缓冲区的缓冲区。 你为什么想做这个? 我能想到的一个原因是将部分代码与流分离。 但是,与在C中需要定义传递c字符串的函数不同,可以使用已经接受istream和ostream的调用函数。 为此,请使用字符串流(双向),istringstream(仅输入)或ostringstream(仅输出)。 这可以简化您的设计,并允许您轻松调试使用C ++流的现有系统。
以下是使用双向字符串流的简单示例。
#include <sstream>
#include <iostream>
using namespace std;
int main()
{
stringstream ss;
char buffer1[20] = {}, buffer2[20] = {};
ss << "hello there";
ss >> buffer1 >> buffer2;
cout << buffer1 << endl;
cout << buffer2 << endl;
}
由于stringstream继承自istream和ostream,因此可以像使用其中一个类一样使用它。
此外,istringstream从istream继承,而ostringstream从ostream继承。
二进制文件
与“如何使用C解析”文档中一样,二进制文件不在此文档范围内。 如果有足够的兴趣,我将在另一个文档中进行介绍。
结论解析文件不是很困难,但肯定不同于C中的文件,尽管您可以像C中那样读取char数组,但不建议这样做,除非您有充分的理由这样做并采取适当的预防措施。
如果您有任何疑问或不清楚的地方。 随时发布消息,我会尽快与您联系和/或更新文档。
阿德里安
本文档受知识共享保护
知识共享署名-非商业性共享相同3.0许可 。修订记录:
2007年5月25日11:06日- 最初的职位
- 使用了错误的标签来关闭接近末端的代码块。 固定
- 粗体在代码块中不起作用,需要改用注释和参考行。
- 在CODE 1和CODE 1A中找不到逗号时,忘记设置状态并引发异常。 固定。
- 已修复标题为“如何使用C解析文件”的标题错误。
- 引用BOOST.org作为图书馆的好资源。
- 试图使使用三重缓冲的解析更加清晰,并突出显示与C相比的差异。
- 更新结论 。
- 删除了对stdio缓冲区的引用,并仅替换为缓冲区。
- 在文档开头添加了仅供参考。
本文档受知识共享保护
知识共享署名-非商业性共享相同3.0许可 。From: https://bytes.com/topic/c/insights/652951-how-parse-file-c