[debug] C++程序设计原理与实践 GUI FLTK mini lab调试:与文件流fstream、字符串流stringstream、重定向freopen的~快乐玩耍~

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(如手动移动文件指针seekpcin两个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的cppreference
putback需要file buffer emm。。暂时不是很懂,就换成stringstream不行就Vans了(手动狗头)如图
stringstream的putback示例
换成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();
}
原创文章 42 获赞 17 访问量 1525

猜你喜欢

转载自blog.csdn.net/weixin_45502929/article/details/105340090