debug要点备忘:
istream::putback
函数的使用条件苛刻,建议使用unget
;stringstream
的用法:通常用作单向流容器;- debug的可见性原则:
istreampeek()
是很好的监视工具;同时应该尽可能选择类似stringstream
这样的对象来解决问题,它们意味着; - 抽象的功能对拍法
问题:利用FLTK的GUI机制编写一个计算器。难点落在从中读取。
stringstream
为了将input_box中输入的字符串读取出来,首先我想到了stringstream。而且它也有get,putback等成员函数,但是实现不如想象的顺利。
这里要说明的是,因为先前的计算器程序中,输入全部使用了
cin
所以试图将cin
全部改成istringstream
型的可以说是很不符合科学开发的方法。同时这并不符合避生就熟的原则。
但是iss
的成员函数到底能不能用,确实还没有探明。目前认为不可行(待更新)
更新:应该把条件苛刻的putback
成员换成unget
,随后可行
在使用stringstream
前,我们把原问题抽象成这样几个输入输出:
第一张是在未知输入输出方向时所作的试验。它表明,stringstream
充当了一个虚拟控制台的角色,<<
将对象放到stringstream
中,>>
将stringstream
中的内容输入后接的变量/对象中。
这一个试验说明了stringstream
的流方向的一次性(?)问题。
了解到stringstream
类常用的方法后,估计原因在于stringstream
的流入是单向的,开始取用时就单向关闭了其输入。
常见使用情形
1. 数据类型转换
将其他类型的变量转化为string
stringstream ss;
string pi;
double tmp = 3.14;
ss << tmp;
ss >> pi;
2.多个字符串拼接。以及清空
真的是很棒的一个例子,源博文戳这!!
stringstream sstream;
// 将多个字符串放入 sstream 中
sstream << "first" << " " << "string,";
sstream << " second string";
cout << "strResult is: " << sstream.str() << endl;
// 清空 sstream
sstream.str("");
sstream << "third string";
cout << "After clear, strResult is: " << sstream.str() << endl;
fstream和freopen
前面的说完了,开始进入正题:这真的是伴随我一个八阿哥抵一天的两个界面。。。第一次成功,第二次失败。
大体猜想就是freopen
有是菜鸡我不知道的性质。
4/10 更新:事实证明,第一次成功第二次失败,一定是哪里没有清零。这个情形下是eofbit
,气煞我也。
4/11 重新尝试:利用了clear
之后仍然不能让FAIL版重新做人
? 重定向到文件,也会有一个对应的文件指针,且随读取向前移动。或许更正确的解释是:freopen
不动,cin
的缓冲区逐步释放?
#include <iostream>
#include <string>
#include <fstream>
#include <sstream>
using namespace std;
int main()
{
ostringstream oss;
cout << "ios::cur : " << cin.tellg()<< endl;
ofstream fout("cc.txt", ios::out | ios::app);
fout << "test" << endl;
// fout.close();
freopen("cc.txt", "r", stdin);
string cache_;
cin >> cache_;
oss << cache_;
cout << "cache_ " << cache_ << ", oss = " << oss.str() << ", its length: "<< oss.str().length() << endl;
fflush(stdin);//不加flush,则第二个tellg会输出-1
cout << "badbit :" << cin.bad() << endl;
cout << "failbit:" << cin.fail() << endl;
fout << "test2" << endl;
fout.close();
cout << "failbit:" << cin.fail() << endl;
cout << "ios::cur: " << cin.tellg() << endl;
string cache2_;
cin >> cache2_;
cout << "cache2_ = " << cache2_ << ", its length : " << cache2_.length() << endl;
cout << cin.bad() << cin.eof() << endl;
fclose(stdin);
}
输出情况:
d:\workspace\code>cd "d:\workspace\code\" && g++ 2.cpp -o 2 && "d:\workspace\code\"2
ios::cur : 0
cache_ test, oss = test, its length: 4
badbit :0
failbit:0
failbit:0
ios::cur: 6
cache2_ = test2, its length : 5
00
这里还有一个显而易见的错误在于,我们应该在读完最后一个字符后将cin.eof()
返回1。这是不难做到的,在这个抽象的情形中我们只需要去掉最后一个endl
即可。
即:
fout << "test2";
随后我们看到输出结果成为我们想要的。
代码片段阶段汇总
测试一:监视工具的寻找
//结论是:fflush会直接冲掉整个剩余文件,剩余文件不再读入。eof不置1
//注意getch是对控制台的函数 既然有C++,就用cin.peek好啦~
int main()
{
if (!freopen("cc.txt","r", stdin))
cout << "failed in opening " << endl;
string str;
cin >> str;
cout << str << endl;
cout << cin.peek() << endl;;
char ch = getchar();
cout << "ch = " << (int)ch << endl;
ch = getchar(); getchar();
cout << "ch = " << ch << endl;
cout << "eofbit: " << cin.eof() << endl;
getchar(); getchar();
cin >> str;
cout << str << endl;
fflush(stdin);
cout << cin.eof() << endl;
cin >> str;
cout << str << endl;
}
这个工具允许我们使用仅ios::out
模式来debug,否则不可见的文件真的难以下手。我们也不可能对freopen
的破原理做出解释。我将这种方法称作监视调试原则。
测试二:freopen
的和源码的兼容性抽象试验
int main()
{
ostringstream oss;
cout << "ios::cur : " << cin.tellg()<< endl;
ofstream fout("cc.txt", ios::out | ios::app);
fout << "test" << endl;
// fout.close();
freopen("cc.txt", "r", stdin);
string cache_;
cin >> cache_;
oss << cache_;
cout << "cache_ " << cache_ << ", oss = " << oss.str() << ", its length: "<< oss.str().length() << endl;
fflush(stdin);//不加flush,则第二个tellg会输出-1
cout << "badbit :" << cin.bad() << endl;
cout << "failbit:" << cin.fail() << endl;
fout << "test2" << endl;
fout.close();
cout << "failbit:" << cin.fail() << endl;
cout << "ios::cur: " << cin.tellg() << endl;
string cache2_;
cin >> cache2_;
cout << "cache2_ = " << cache2_ << ", its length : " << cache2_.length() << endl;
cout << cin.bad() << cin.eof() << endl;
fclose(stdin);
}
-
这个测试在代码本身是很成功的。它实现了
string
输出到文件–从文件标准输入(到string
)–从string
输出的抽象过程,是我们从input_box读取到output_box读出的可行的抽象。我把它称作抽象功能对拍。不能忽视的是,由于使用
endl
进行测试,这与计算器模型中的const char print = '\n'
相抵触。(然鹅、、就算不抵触也过不了sigh) -
但是,由于背景知识的缺乏,当且仅当:
ios::app
模式时下,重定向文件中包含至少两个expression时,才能不出现peek
返回-1的情况。几种可能的解释(真的超级疑惑):freopen
被人为操纵就会-1(如手动移动文件指针seekp
,cin
两个base串(?)感觉不太对劲www):有一定的可能性。freopen
生存期只在回调函数中:那两个expression可以重复计算第一个式子怎么解释???freopen
被定向绑定到一个文件头部,这个指针是const型,原处被改动则出错。读至EOF
时,意味着已经绑定的整条缓冲区读取结束,于是peek
返回-1。
FAIL版
/**
* 1. 采用ostringstream失败后,开始向重定向发起探索。
* 2. 在回调函数中,重定向到特定文件,第二次总会出现问题;怀疑是
*/#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cmath>
#include <cstdlib>
#include <cstdio>
#include <ctime>
#include <iomanip>
#include <string>
#include <sstream>
#include "Simple_window.h"
#include "GUI/include/Graph.h"
const char number = '8';
const char name = 'a';
const char print = '\n';
const char let = 'L';
const char quit = 'Q';
const string declkey = "let";
const string quitkey = "quit";
bool has_calc = 0;//if it has calculated, return 1
int times = 0;
//stringstream ss_tmp;
class Token
{
public:
char kind;
double value;
string name;
Token(char k, double val = 0) : kind(k), value(val) {};
Token(char ch, string n) :kind(ch), name(n) {};
};
class Token_stream
{
public:
Token_stream(bool f = false, Token buf = 0)
: full(f), buffer(buf) {};
Token get();
void putback(Token t);
void reset();
private:
bool full;
Token buffer;
};
void Token_stream::putback(Token t)
{
if (full)
cerr << "putback() into a full buffer";
full = true, buffer = t;
}
void Token_stream::reset()
{
full = false;
}
//only with the complexity of 111 on average, effectively reading tokens. also high robustness
Token Token_stream::get()
{
if (full) {
full = false;
return buffer;
}
char ch;
while (cin.get(ch) && ch == ' ') {}//spaces will be ignored, thus we (in the case of '1'' ') directly go into the '-1'
if (cin.eof())
return Token(print);
switch (ch) {
case '(': case ')':
case '+': case '-': case '*': case '/':
case '=': case print:
return Token(ch);
case '.': // numerical function modes
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
{
cin.putback(ch);
double val;
cin >> val;
return Token('8', val);
}
default:
if (isalpha(ch)) {
cin.putback(ch);
string tmp;
while (cin.get(ch) && (isalpha(ch) || isdigit(ch))) tmp = tmp + ch;
cin.putback(ch);
if (tmp == declkey)
return Token(let);
else if (tmp == quitkey)
return Token(quit);
return Token(name, tmp);
}
return 0;
}
}
//-------------------------------------------------------
Token_stream ts;
class Variable {
public:
string name;
double value;
Variable(string n, double v) : name(n), value(v) { }
};
vector<Variable> var_table;
double get_value(string s)
{
for (int i = 0; i < var_table.size(); i++)
if (var_table[i].name == s) return var_table[i].value;
}
void set_value(string s, double d)
{
for (int i = 0; i < var_table.size(); i++)
if (var_table[i].name == s) {
var_table[i].value = d;
return;
}
}
void define_name(string var, double val) {
var_table.push_back(Variable(var, val));
}
//-------------------------------------------------------
double expression();
//reading the sequence front.
double primary()
{
Token t = ts.get();
switch (t.kind) {
case '(':
{
double d = expression();
t = ts.get();
return d;
}
case number:
return t.value;
case name:
return get_value(t.name);
case '-':
return -primary();
default:
cerr << "primary expected" << endl;
}
}
// main calculating units, relatively more symbols processing
double term()
{
double left = primary();
do {
Token t = ts.get();
switch (t.kind) {
case '*':
left *= primary();
break;
case '/':
left /= primary();
break;
case '%':
{
int i1 = left;
int i2 = term();
left = i1 % i2;
t = ts.get();
break;
}
default:
ts.putback(t);
return left;
}
} while (true);
}
double expression()
{
double left = term();
do {
Token t = ts.get();
switch (t.kind) {
case '+':
left += term(); break;
case '-':
left -= term(); break;
default:
ts.putback(t);
return left;
}
} while (true);
}
//---------------------------------------------------------------
Token declaration()
{
Token t = ts.get();
string var_name = t.name;
Token t2 = ts.get(); // eat'='
double d = expression();
define_name(var_name, d);
return Token(let);
}
Token statement(int mode)
{
if (!mode)
{
declaration();
return Token(let);
}
return Token(number, expression());
}
//---------------------------------------------------------------
class Calculator : public Window
{
public:
Calculator(Point, int, int, const string&);
private:
Button calc_button;
Button quit_button;
In_box input_box;
Out_box result_box;
int width;
int height;
void calc();
void _quit();
static void cb_calc(Address, Address window);
static void cb_quit(Address, Address window);
};
Calculator::Calculator(Point base_p = Point(400, 200), int w = 400, int h = 300,
const string& title = "Calculator")
: Window(base_p, w, h, title)
, width(w), height(h)
, input_box(Point(100, 0), 270, 30, "Input: ")
, result_box(Point(50, 90), 200, 50, "Ans: ")
, calc_button(Point(10, 40), 40, 30, " = ", cb_calc)
, quit_button(Point(x_max() - 70, y_max() - 20), 70, 20, "Quit", cb_quit)
{
attach(input_box);
attach(result_box);
attach(calc_button);
attach(quit_button);
redraw();
}
void Calculator::cb_calc(Address, Address pt_window)
{
reference_to<Calculator>(pt_window).calc();
}
void Calculator::cb_quit(Address, Address pt_window)
{
reference_to<Calculator>(pt_window)._quit();
}
void Calculator::calc()
{
string file_name = "test.txt";
//string base_container;//method 2
/*stringstream ss;
ss << "test" << ++times << ".txt";// creating names with its order
ss >> file_name; // here I try to use files of different name to implement the freopen, but failed.*/
ofstream fout(file_name, ios::out | ios::app);
// I find that the ios::app mode means we can repeat the first expression in the freopen-from file.
//however, it's very peculiar that we need at least 2 expressions with a space between them, then we can go into the repeated process.
//
//if (has_calc) //invalid try...for the spaces will be ignored, only in the app mode (maybe) can we use this design
// fout << ' '; //tips: couts in this program is only used for debugging.
//fout << ' ';//method 2
fout << input_box.get_string();
//fout << " base3";//method2
fout.close();
cout << "freopen pointer to: " << freopen(file_name.c_str(), "r", stdin) << endl;
cout << "peek1: " << cin.peek() << endl;
/*++times;//this block is the remains of the method 2;
for (int i = 0; i < times; i++)*/
// cin >> base_container >> base_container;//method 2
Token t = ts.get();
ts.putback(t);
cout << "t.kind: " << t.kind << ", t.val: " << t.value << endl;
cout << "peek2: " << cin.peek()<< endl;
//mode preparation:
if (t.kind == let) ts.reset();
int tmpn;
if (t.kind - let)
tmpn = 1;
else tmpn = 0;
//start calculation
Token tmp = statement(tmpn);//calculation
cout << "tmp.kind: " << tmp.kind << ", tmp.val: " << tmp.value << endl;
cout << "peek3: " << cin.peek() << endl;
ostringstream ans_oss;
if (tmp.kind != let) // if the current try isn't a assignment sentence:
ans_oss << fixed << setprecision(6) << tmp.value;
result_box.put(ans_oss.str());
has_calc = 1;
redraw();
ts.reset();
ans_oss.str("");
//fclose(stdin);//stdin CANNOT casually closed!!!
//freopen("con", "r", stdin);//console trans-to-and-back didn't solve the problem.
cout << "peek4: " << cin.peek() << endl;
//fflush(stdin); //freopen
}
void Calculator::_quit()
{
hide();
}
//---------------------------------------------------------------------------------------------
int main()
{
//ofstream fout("test.txt", ios::out);
//fout << "base1 base2";
//fout.close();//this block is the remains of method 2. failed.
Calculator win;
//fclose(stdin);
return gui_main();
}
stringstream回归版
sstream
对象的成员函数是否能替代源计算器程序中的那些——这个问题仍然首当其冲。
没错,我们是怀疑putback
的兼容性。
putback
需要file buffer emm。。暂时不是很懂,就换成stringstream
不行就Vans了(手动狗头)如图
换成unget
或许可以?
但是我们又遇到了新的问题:还是第二次…………………
但是有了先前的监视工具,我们有了如下的debug进展
其中input和output分别是输入的字符串流和输出字符串流。
这样简单易行的监视,是我们选择
stringstream
的另一个收获。或者说,stringstream
的一个优点所在吧。
inp length = 1
input: 1
ouput:
input: 1
ouput:
input: 1
ouput:
input: 1
ouput: 1.000000
input:
ouput:
inp length = 1
input: 2
ouput:
input: 2
ouput:
primary expected
input: 2
ouput:
input: 2
ouput: -nan(ind)
input:
ouput:
都已经是2,而且length是1,为什么还是过不了????
单步到eof()
函数处终于发现,先前走过的eof()
对第二次及以后的走向造成了重大影响,调整后,我们得到如下可以过OJ题目的程序(纵然这并不是一份好代码,可是,我累了):
#include <iostream>
#include <cmath>
#include <cstdlib>
#include <cstdio>
#include <ctime>
#include <iomanip>
#include <string>
#include <sstream>
#include "Simple_window.h"
#include "GUI/include/Graph.h"
const char number = '8';
const char name = 'a';
const char print = '\n';
const char let = 'L';
const char quit = 'Q';
const string declkey = "let";
const string quitkey = "quit";
const double pi = 3.1415926535;
const double e = 2.7182818284;
stringstream inp_ss;
ostringstream ans_oss;
class Token
{
public:
char kind;
double value;
string name;
Token(char k, double val = 0) : kind(k), value(val) {};
Token(char ch, string n) :kind(ch), name(n) {};
};
class Token_stream
{
public:
Token_stream(bool f = false, Token buf = 0)
: full(f), buffer(buf) {};
Token get();
void putback(Token t);
void reset();
private:
bool full;
Token buffer;
};
void Token_stream::putback(Token t)
{
if (full)
cerr << "putback() into a full buffer";
full = true, buffer = t;
}
void Token_stream::reset()
{
full = false;
}
Token Token_stream::get()//only with the complexity of 111 on average, effectively reading tokens. also high robustness
{
if (full) {
full = false;
return buffer;
}
char ch;
while (inp_ss.get(ch) && ch == ' ') {}
if (inp_ss.eof() || ch < 0) return Token(print);//4/5:正是没有清空的eof导致了今天充满黑色幽默感的debug哈哈哈哈哈哈哈哈哈
//more: the practice is a little bit different from the plain calculator app, for it doesn't have inborn-print.
switch (ch) {
case '(': case ')':
case '+': case '-': case '*': case '/':
case '=': case print:
return Token(ch);
case '.': // numerical function modes
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
{
inp_ss.unget();
double val;
inp_ss >> val;
return Token(number, val);
}
default:
if (isalpha(ch)) {
inp_ss.unget();
string tmp;
while (inp_ss.get(ch) && (isalpha(ch) || isdigit(ch))) tmp = tmp + ch;
inp_ss.unget();//the last char doesn't fit
if (tmp == declkey)
return Token(let);
else if (tmp == "pi")
return Token(number, pi);
else if (tmp == "e")
return Token(number, e);
return Token(name, tmp);
}
return 0;
}
}
//-------------------------------------------------------
Token_stream ts;
class Variable {
public:
string name;
double value;
Variable(string n, double v) : name(n), value(v) { }
};
vector<Variable> var_table;
double get_value(string s)
{
for (int i = 0; i < var_table.size(); i++)
if (var_table[i].name == s) return var_table[i].value;
else cerr << "expression including undefined var..." << endl;
}
void set_value(string s, double d)
{
for (int i = 0; i < var_table.size(); i++)
if (var_table[i].name == s) {
var_table[i].value = d;
return;
}
}
void define_name(string var, double val) {
ans_oss << var << " is now defined valued " << val;
var_table.push_back(Variable(var, val));
}
//-------------------------------------------------------
double expression();
double primary()//reading the sequence front.
{
Token t = ts.get();
switch (t.kind) {
case '(':
{
double d = expression();
t = ts.get();
return d;
}
case number:
return t.value;
case name:
return get_value(t.name);
case '-':
return -primary();
case print:
ts.putback(print);
break;
default:
cerr << "primary expected" << endl;
}
return 0;
}
double term()// main calculating units, relatively more symbols processing
{
double left = primary();
do {
Token t = ts.get();
switch (t.kind) {
case '*':
left *= primary();
break;
case '/':
left /= primary();
break;
case '%':
{
int i1 = left;
int i2 = term();
left = i1 % i2;
t = ts.get();
break;
}
default:
ts.putback(t);
return left;
}
} while (true);
}
double expression()
{
double left = term();
do {
Token t = ts.get();
switch (t.kind) {
case '+':
left += term(); break;
case '-':
left -= term(); break;
default:
ts.putback(t);
return left;
}
} while (true);
}
//---------------------------------------------------------------
Token declaration()
{
Token t = ts.get();
string var_name = t.name;
if (t.name == "e" || t.name == "pi")
{
cout << "variable has been defined" << endl;
fflush(stdin);
return Token(let);
}
Token t2 = ts.get(); // eat'='
double d = expression();
define_name(var_name, d);
return Token(let);
}
Token statement(int mode)
{
if (!mode)
{
declaration();
return Token(let);
}
return Token(number, expression());
}
//---------------------------------------------------------------
class Calculator : public Window
{
public:
Calculator(Point, int, int, const string&);
private:
Button calc_button;
Button quit_button;
In_box input_box;
Out_box result_box;
Text example;
Text example2;
int width;
int height;
void calc();
void _quit();
static void cb_calc(Address, Address window);
static void cb_quit(Address, Address window);
};
Calculator::Calculator(Point base_p = Point(400, 200), int w = 400, int h = 200, const string& title = "Calculator")
: Window(base_p, w, h, title)
, width(w), height(h)
, input_box(Point(50, 0), 270, 40, "Input: ")
, result_box(Point(90, 70), 230, 50, "Ans: ")
, calc_button(Point(10, 80), 40, 30, " = ", cb_calc)
, quit_button(Point(x_max() - 70, y_max() - 20), 70, 20, "Quit", cb_quit)
, example(Point(30, 150), "Try like this: \"1+1\"")
, example2(Point(30, 180), "\"let x = 2\", \"x + 3\"")
{
attach(input_box);
attach(result_box);
attach(calc_button);
attach(quit_button);
example.set_color(Color::black);
example2.set_color(Color::black);
attach(example);
attach(example2);
redraw();
}
void Calculator::cb_calc(Address, Address pt_window)
{
reference_to<Calculator>(pt_window).calc();
}
void Calculator::cb_quit(Address, Address pt_window)
{
reference_to<Calculator>(pt_window)._quit();
}
void p()
{
cout << "input: " << inp_ss.str() << endl;
cout << "ouput: " << ans_oss.str() << endl;
}
void Calculator::calc()
{
inp_ss.str(input_box.get_string());
cout << "inp length = " << inp_ss.str().size() << endl;
p();
Token t = ts.get();
ts.putback(t);
p();
if (t.kind == let && t.kind != print) ts.reset();
int tmpn;
if (t.kind - let)
tmpn = 1;
else tmpn = 0;
Token tmp = statement(tmpn);
p();
if (tmp.kind != let)
ans_oss << fixed << setprecision(6) << tmp.value;
result_box.put(ans_oss.str());
p();
ans_oss.str("");
redraw();
ts.reset();
inp_ss.clear();
p();
cout << endl;
}
void Calculator::_quit()
{
hide();
}
//---------------------------------------------------------------------------------------------
int main()
{
var_table.push_back(Variable("e" , 2.7182818));
var_table.push_back(Variable("pi", 3.1415926));
Calculator win;
return gui_main();
}