C++程序结构
C++程序结构
我在C++中如何构建程序的结构有些困惑。我正在学习C++的一本书,其中我们构建了两个类来解决一个问题。书中把这两个类以及它们的使用方式都放在一个文件中运行,这样可以正常工作。但我知道更合理结构的代码应该包括头文件,并且每个类都应该有自己的文件,当我尝试按照这种方式组织程序时,就会在编译代码时出现问题。\n我有两个类,Token和Token_Stream,根据我了解的其他编程语言,Token和Token_Stream应该有各自的文件,并且每个类都应该有一个声明文件。我的主要问题是:\nToken_Stream需要知道Token的存在。当初始化Token_Stream时,它会初始化一个Token。我原以为在Token_Stream中只需要包含Token的声明就足够了,但实际情况似乎并非如此。我对面向对象编程语言有一些了解,但Token_Stream不应该继承自Token,也不应该(我认为)它只需要知道足够的信息来初始化Token并存储它。我将在下面包含每个相关文件:\nToken.h\n
// Token.h, Token的声明 class Token { public: char kind; double value; Token(char ch); Token(char ch, double val); }; //class Token
\nToken.cpp\n
// Token.cpp #include "Token.h" using namespace std; Token::Token(char ch) :kind(ch), value(0){} Token::Token(char ch, double val) :kind(ch), value(val) {}
\nToken_Stream.h\n
// Token_Stream.h, Token_Stream的声明 class Token_Stream { public: Token_Stream(); Token get(); void putback(Token); private: bool full; // 是否已有一个token? Token buffer; // 我们持有的Token是什么? };//class Token_Stream
\nToken_Stream.cpp\n
// Token_Stream.cpp实现 #include#include #include "Token.h" // 需要知道什么是Token #include "Token_Stream.h" using namespace std; Token_Stream::Token_Stream() :buffer(0) { full = false; // 流中还没有任何内容。 }//constructor void Token_Stream::putback(Token t) { if(!full) // 如果它是空的 { buffer = t; full = true; }//if not full else throw runtime_error("buffer already full"); }// putback Token Token_Stream::get() { if(full) // 如果我们已经有了东西 { full = false; return buffer; } // 如果到达这里,我们还没有返回: char ch; cin>>ch; //获取下一个输入并切换到不同的情况: switch(ch) { // 如果输入一个有效字符: case ';': case 'q': case '(': case '+': case '*': case '-': case '/': case '%': case ')': return Token(ch); break; // 如果输入一个有效数字,或者以小数点开头,例如,.5 case '.': 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); break; }//case of valid number default: throw runtime_error("Bad Token"); }//switch }//get
\n这些就是文件,当我尝试编译时,例如在Token.cpp中放入一个空的int main(){},一切都正常工作,我编译,如果需要可以在main()中运行代码。\n但是,当我在Token_Stream.cpp中放入一个空的int main(){}并尝试编译时,它不起作用,我运行的是:\n
g++ -Wall -std=c++11 -o "Token_Stream" "Token_Stream.cpp"
\n我甚至没有得到行号错误,但它声称对Token::Token(char)等的引用未定义,以及其他Token构造函数,所以我猜这意味着Token_Stream.cpp需要看到更多的Token.cpp,我该如何做?我只需要同时编译它们吗?
C++程序结构问题的出现原因是在处理链接问题的同时,还需要解决程序内部的依赖关系。
解决方法如下:
Token_Stream.h文件应该以以下方式开始:
// Token_Stream.h, declarations #include "Token.h" // 注意这个包含语句在文件顶部 class Token_Stream ...
Token_Stream.cpp文件应该以以下方式开始:
// Token_Stream.cpp implementation. #include "Token_Stream.h" // 注意这个包含语句在文件顶部 #include#include ...
上述主要要点是:
- 每个头文件应该在其实现文件的顶部被包含。
- 每个头文件应该只包含它实际需要的内容。
这些前提条件将使得客户端能够在需要的地方包含你的头文件。
原因:使用C++编译器编译一个C++程序时,如果程序中引用了其他文件中的函数或类,需要将这些文件链接到可执行文件中。在这个例子中,程序中引用了Token.cpp中的构造函数,但是没有将Token.cpp链接到可执行文件中。
解决方法:在编译命令中加入需要链接的文件,即将Token.cpp链接到可执行文件中。
文章内容如下:
你需要将Token.cpp链接到你的可执行文件中。
g++ -Wall -std=c++11 -o "Token_Stream" "Token.cpp" "Token_Stream.cpp"
否则,gcc找不到Token的构造函数的实现。
啊!好的,这样确实可以编译了!谢谢。所以这个想法是,如果你在另一个文件中有实现,你也必须将它给编译器,编译器会自动解决依赖关系?或者这里的顺序很重要吗?
实际上是链接器(linker)而不是编译器在做这些事情。是的,链接器会为你解决这些依赖关系(大部分情况下)。顺序可能很重要,但是暂时不用担心;这更多是与使用-l
标志链接的第三方库有关的问题。
当然了。是的,-o
是给链接器用的,对吗?