我目前正在Bjarne Stroustup的“使用C++编程原理和实践”中进行练习,并且完成了第八章,他在其中讨论标题和 namespace 。利用这些知识和其他在线资源,我厌倦了将简单计算器代码重构为多个 header 和源文件,以便在该文件“外部”使用计算器功能。我收到了230多个错误,我真的不知道为什么。这是一个很长的文件,非常感谢任何愿意花时间研究此问题的人。我将在下面提供所有代码段(相当长)
The project's structure
计算器
#pragma once
void calculate(Token_stream& ts, Symbol_table& st);
double statement(Token_stream& ts, Symbol_table& st);
double declaration(Token_stream& ts, Symbol_table& st);
double square_root(Token_stream& ts, Symbol_table& sts);
double powerf(Token_stream& ts, Symbol_table& st);
double expression(Token_stream& ts, Symbol_table& st);
double term(Token_stream& ts, Symbol_table& st);
double factorial(Token_stream& ts, Symbol_table& st);
double primary(Token_stream& ts, Symbol_table& st);
double variable(Token_stream& ts, Symbol_table& st);
void intro_message();
void cleanup(Token_stream&);
constants.h:#pragma once
namespace Constants
{
//Constant declarations and initializations-------------------------------------
const char number = '8'; //representation of a number type for a Token
const char sroot = 'S';
const char let = 'L'; //represents the "let" term in declaration()
const char name = 'a'; //name token
const char power = 'P';
const char vconst = 'C';
const string decl_key = "let"; //declaration keyword
const string sroot_key = "sqrt"; //keyword for calling sqare_root() function
const string power_key = "pow"; //keyword for calling power() function
const string constant_key = "const";
const char quit_key = '@';
const char print_key = ';';
const char help_key = '$';
const char show_vars = '&';
const string prompt = ">>";
const string result = "="; //used to indicate that what follows is a result
const char recover = '~'; //used as an argument for the keep_window_open() functions in catch statements
}
token.h:#pragma once
class Token {
public:
char type;
double value;
string name; // used for a Token of type name
Token(char ch) :type{ ch }, value{ 0 } {};
Token(char ch, double val) :type{ ch }, value{ val } {};
Token(char ch, string n) :type{ ch }, name{ n } {};
};
class Token_stream {
public:
Token_stream();
Token get();
void putback(Token t);
void ignore(char c);
private:
bool isFull = false;
Token buffer;
};
variable.h:#pragma once
class Variable
{
public:
string name;
double value;
bool isConst;
Variable(string st, double v, bool b) : name{ st }, value{ v }, isConst{ b } {}
Variable(string st, double v) : name{ st }, value{ v }, isConst{ false } {}
};
class Symbol_table
{
public:
double get_value(string s);
void set_value(string s, double n);
bool is_declared(string var);
double define_variable(string var, double val, bool isConst);
void show_variables();
private:
vector <Variable> var_table;
};
Calculator.cpp:#include "calculator.h"
#include "token.h"
#include "variable.h"
#include "constants.h"
#include "std_lib_facilities.h"
//Grammar implementation---------------------------------------------------------
using namespace Constants;
void calculate(Token_stream& ts, Symbol_table& st)
{
//double val;
while (cin)
try {
cout << prompt;
Token t = ts.get();
while (t.type == print_key) t = ts.get(); // "eat" print_key characters
if (t.type == quit_key) return; //NOTE : In void functions, you can do an empty return.
if (t.type == help_key) intro_message();
if (t.type == show_vars) st.show_variables();
else {
ts.putback(t);
cout << result << statement(ts,st) << "\n\n";
}
//ts.putback(t);
//cout << result << statement() << "\n\n";
//val = statement();
}
catch (exception& e)
{
cout.clear();
cerr << "error: " << e.what() << "\n";
cerr << "Enter " << recover << " to continue.\n";
cleanup(ts);
}
catch (...)
{
cerr << "Unknown error.\n";
cerr << "Enter " << recover << " to continue.\n";
}
}
double statement(Token_stream& ts, Symbol_table& st)
{
Token t = ts.get();
switch (t.type)
{
case let:
return declaration(ts, st);
default:
ts.putback(t);
return expression(ts,st);
}
}
double declaration(Token_stream& ts, Symbol_table& st)
{
// assume we already saw "let" (in statement())
// handle: name = expression
// declare a variable called "name" with initial value "expression"
Token t = ts.get();
bool isConst = false;
if (t.type == vconst)
{
t = ts.get();
isConst = true;
if (t.type != name) error("name expected in declaration");
string var_name = t.name;
}
else if (t.type != name) error("name expected in declaration");
string var_name = t.name;
Token t2 = ts.get();
if (t2.type != '=') error("= missing in declaration of ", var_name);
double d = expression(ts,st);
st.define_variable(var_name, d, isConst);
return d;
}
double square_root(Token_stream& ts, Symbol_table& st)
{
// get a token, assuming that we've already used the string "sqrt" in get()
Token t = ts.get();
if (t.type != '(') error("sqrt: '(' expected");
double e = expression(ts,st);
if (e < 0) error("sqrt: cannot calculate square root of negative number");
t = ts.get();
if (t.type != ')') error("sqrt: ')' expected");
return sqrt(e);
}
double powerf(Token_stream& ts, Symbol_table& st)
{
Token t = ts.get();
if (t.type != '(') error("power: '(' expected");
double t1 = expression(ts,st);
t = ts.get();
if (t.type != ',') error("power: arguments must be separated by a ','");
double t2 = expression(ts, st);
if (t2 < 0) error("power: negative exponent");
t = ts.get();
if (t.type != ')') error("power: ')' expected");
return pow(t1, t2);
}
double expression(Token_stream& ts, Symbol_table& st)
{
double left = term(ts, st);
Token t = ts.get();
while (true)
{
switch (t.type)
{
case '+':
left += term(ts, st);
t = ts.get();
break;
case '-':
left -= term(ts, st);
t = ts.get();
break;
default:
ts.putback(t);
return left; // if there's no more + and -, return the result
}
}
return left;
}
double term(Token_stream& ts, Symbol_table& st)
{
double left = factorial(ts, st);
Token t = ts.get();
while (true)
{
switch (t.type)
{
//ou de paste :)
case '*':
left *= factorial(ts, st);
t = ts.get();
break;
case '/':
{
double d = factorial(ts, st);
if (d == 0) error("term: division: cannot divide by 0");
left /= d;
t = ts.get();
break;
}
case '%': //Only works for integers
{
int i1 = narrow_cast<int>(left);
int i2 = narrow_cast<int>(factorial(ts, st));
if (i2 == 0) error("term: modulo: cannot divide by 0");
left = i1 % i2;
t = ts.get();
break;
}
default:
ts.putback(t);
return left;
}
}
return left;
}
double factorial(Token_stream& ts, Symbol_table& st)
{
double left = primary(ts, st);
Token t = ts.get();
switch (t.type)
{
case '!':
{
int lcopy = narrow_cast<int>(left);
if (left == 0) return 1; // 0! = 1
if (left < 0) error("factorial: cannot calculate factorial of a negative number");
while (lcopy > 1)
{
--lcopy;
left *= lcopy;
}
t = ts.get();
if (t.type == '!') error("factorial: unexpected '!' operator");
}
default:
ts.putback(t);
return left;
}
return left;
}
double primary(Token_stream& ts, Symbol_table& st)
{
Token t = ts.get();
switch (t.type)
{
case '(':
{
double e = expression(ts, st);
t = ts.get();
if (t.type != ')') error("primary: ')' expected");
return e;
}
case '{':
{
double e = expression(ts, st);
Token b = ts.get();
if (b.type != '}') error("primary: '}' expected");
return e;
}
case '-':
return -primary(ts, st);
case '+':
return primary(ts, st);
case number:
return t.value;
case name:
ts.putback(t);
return variable(ts, st);
case power:
return powerf(ts, st);
case sroot:
return square_root(ts, st);
default:
error("primary expexted");
}
}
double variable(Token_stream& ts, Symbol_table& st) {
Token t = ts.get();
switch (t.type)
{
case name:
{
Token t2 = t;
t = ts.get();
// check to see if it's an assignment or just a usage of the variable
if (t.type == '=')
{
double e = expression(ts, st);
st.set_value(t2.name, e);
return e;
}
else
{
ts.putback(t);
return st.get_value(t2.name);
}
}
}
}
//-------------------------------------------------------------------------------
//Additional functions-----------------------------------------------------------
void intro_message() //print a custom "banner"
{
cout << "---------------------------------------\n"
<< "|Simple calculator - V1.0 |\n"
<< "| by BIBAN|\n"
<< "---------------------------------------\n\n"
<< "Supported operators : +, -, *, /, % (for ints only), (), !-factorial\n"
<< "Supported functions :\n"
<< " - sqrt(expression) - calculates square root of any expression\n"
<< " - pow(base, exponent) - calculate a base at the power of exponent\n"
<< " --> base and exponent are expressions\n\n"
<< "Variables can be defined and used as expressions:\n"
<< " - let variable_name = value - define a variable\n"
<< " - let const constant_name = value - define a constant\n"
<< " - variable_name = new_value - assign a new value to a non-constant variable\n"
<< " - " << show_vars << " - display all variables\n\n"
<< "Use " << quit_key << " to quit the program, " << print_key << " to end an ecuation and " << help_key << " to display this message.\n"
<< "If an error occurs, type in " << recover << " to continue.\n\n";
}
void cleanup(Token_stream& ts)
{ //recover from an error
ts.ignore(recover);
}
//-------------------------------------------------------------------------------
token.cpp:#include "token.h"
#include "constants.h"
#include "std_lib_facilities.h"
using namespace Constants;
Token_stream() :isFull(false), buffer(0) {}
Token Token_stream::get()
{
if (isFull)
{
isFull = false;
return buffer;
}
else
{
char ch;
cin >> ch;
switch (ch)
{
case '+':
case '-':
case '!':
case '*':
case '/':
case '%':
case '{':
case '}':
case '(':
case ')':
case '=': //for Variable declaration and assignment
case ',': //used for separating arguments in functions
case quit_key:
case print_key:
case help_key:
case show_vars:
return Token(ch);
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
cin.putback(ch);
double d;
cin >> d;
return Token(number, d);
default:
//test if the next token is a string and return a Token if it's valid
if (isalpha(ch)) // is ch a letter ?
{
string s;
s += ch;
while (cin.get(ch) && (isalpha(ch) || isdigit(ch) || ch == '_')) s += ch;
cin.putback(ch);
if (s == decl_key) return Token{ let };
if (s == sroot_key) return Token{ sroot };
if (s == power_key) return Token{ power };
if (s == constant_key) return Token{ vconst };
return Token{ name,s }; //Token of type name (for Variable) and value s(the name for the Variable)
}
runtime_error("Bad token.");
}
}
}
void Token_stream::putback(Token t)
{
if (isFull) runtime_error("buffer already full");
isFull = true;
buffer = t;
}
//Used to recover from errors by ignoring all characters, except char c
void Token_stream::ignore(char c) // c represents the type for the Token
{
// look in buffer
if (isFull && c == buffer.type)
{
isFull = false;
return;
}
isFull = false;
//search input
char ch = 0;
while (cin >> ch) if (ch == c) return;
}
variable.cpp:#include "std_lib_facilities.h"
#include "variable.h"
double Symbol_table::get_value(string s)
{
// return the value of the Variable named s
for (const Variable& v : var_table)
if (v.name == s) return v.value;
error("get: undefined variable ", s);
}
void Symbol_table::set_value(string s, double n)
{
// set the variable named s to n
for (Variable& v : var_table)
{
if (v.name == s && v.isConst == false)
{
v.value = n;
return;
}
else if (v.name == s && v.isConst) error("set_value: cannot change value of a constant variable");
}
error("set: undefined variable ", s);
}
bool Symbol_table::is_declared(string var)
{
//is var in var_table already?
for (const Variable& v : var_table)
if (v.name == var) return true;
return false;
}
double Symbol_table::define_variable(string var, double val, bool isConst)
{
// add {var,val,isConst} to var_table
if (is_declared(var)) error(var, " declared twice.");
var_table.push_back(Variable{ var,val,isConst });
return val;
}
void Symbol_table::show_variables()
{
for (int i = 0; i < var_table.size(); ++i)
cout << var_table[i].name << " = " << var_table[i].value << "\n";
}
和main.cpp文件,只有main()函数:#include "calculator.h"
#include "token.h"
#include "variable.h"
Token_stream ts;
Symbol_table st;
int main()
{
calculate(ts, st);
return 0;
}
最佳答案
问题在于,在其中包括calculator.h
的情况下,编译器尚不知道Token_stream
和Symbol_table
是什么。这是一个错误,编译器不会在代码中向前寻找那些符号是什么,它只会发出错误。
从最坏到最好的三种可能的解决方案(IMHO)
#include "calculator.h"
。这样,当编译器进入Calculator.h时,它知道Token_stream
和Symbol_table
是什么,所以没有错误。#include "token.h"
和#include "variable.h"
添加到Calculator.h的开头。这样,编译器被迫在读取其余部分calculator.h之前先读取token.h和variable.h。添加代码
// forward declarations
class Token_stream;
class Symbol_table;
到Calculator.h的开头。这告诉编译器这些符号是类的名称。并且(在这种情况下)足以进行编译。关于c++ - 我如何相应地构造代码(在 header 和源文件中),这样我就不会出现编译器和链接器错误?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/64870120/