我如何相应地构建代码(在 headers 和源文件中),这样我就不会出现编译器和链接器错误?

How can I structure code accordingly (in headers and source files) so I don't get compiler and linker errors?

我目前正在做 Bjarne Stroustup“使用 C++ 的编程原理和实践”中的练习,我完成了第 8 章,他在其中讨论了 header 和名称空间。利用这些知识和其他在线资源,我厌倦了将简单计算器代码重组为多个 header 和源文件,以便在该文件“外部”使用计算器功能。我收到 230 多个错误,我真的不知道为什么。这是一个相当长的文件,我非常感谢任何花时间查看它的人。我将在下面提供所有代码片段(很长) The project's structure

NOTE: std_lib_facilites.h is provided by the book and contains only simple declarations and functions that eases the understanding of concepts.

calculator.h

#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_streamSymbol_table 是什么。这是一个错误,编译器不会在代码中期待找出这些符号是什么,它只会发出错误。

三种可能的解决方案(恕我直言)

  1. 只需确保 #include "calculator.h" 在 token.h 和 variable.h 之后。这样,当编译器到达 calculator.h 时,它知道 Token_streamSymbol_table 是什么,所以没有错误。

  2. 在calculator.h的开头添加#include "token.h"#include "variable.h"。这样编译器在读取 calculator.h

    的其余部分之前被迫读取 token.h 和 variable.h
  3. 前向声明添加到calculator.h。

添加代码

// forward declarations
class Token_stream;
class Symbol_table;

到 calculator.h 的开头。这告诉编译器这些符号是 类 的名称。并且(在这种情况下)这足以让编译继续进行。

此外,对于任何有链接器错误的人,主要是 Linux,请三重确保您在 G++ 命令中包含了所有相关的 .cpp 文件