在上篇博客中展示的是单头文件的组织架构,而这篇博客将展示的是每个.cpp文件都有对应的.h文件。即.h文件定义它所提供的功能;.cpp包含.h文件用于实现。或许还需包含其他的.h文件,大项目大程序为了方便和整洁,会为了实现部分所用的interface(书中翻译为界面)放在另外的以_impl.h为后缀的文件里。其中就以parser._impl.h为例,如下图,箭头表示 使用 。
在这里必须要着重说明的是parser_impl.h。为什么有了一个parser.h,还要一个parser_impl.h文件?这是为了尽可能的降低各个文件的相互依赖性,保证文件里的函数、变量等有最小的作用域。parser.h文件提供的是用户使用的interface,而parser_impl.h文件提供的是分析器的函数共享的环境。
需要另外提一下的是,_impl.h的写法不是标准,也不是一种通用的约定,只是Bjarne Stroustrup所喜欢的命名方式。作为学习者可以参考这样的降低文件的相互依赖性的方式。
在书中作者还提到parser.c使用了table.h。在它的处理上会有很多种的可能。可以放入parser_impl.h文件中;由于只有parser.c中的prim()需要table.h,可以放入一个新建的prim.c文件中(如下图所示);如果项目非常大,可以考虑又新建一个_impl.h文件。
其各个文件代码如下(在这里.c和.cpp文件是一样的):
由于每个文件的代码都很少,所以就把.h的文件放在一起,.cpp文件放在一起。
.h文件:
/*************************************************************************/
//error.h
namespace Error{
struct Zero_divide{
};
struct Syntax_error{
const char* p;
Syntax_error(const char* q)
{
p = q;
}
};
}
/*************************************************************************/
/*************************************************************************/
//lexer.h
#include<string>
namespace Lexer{
enum Token_value{
NAME, NUMBER, END,
PLUS='+', MINUS='-', MUL='*', DIV='/',
PRINT=';', ASSIGN='=', LP='(', RP=')'
};
extern Token_value curr_tok;
extern double number_value;
extern std::string string_value;
Token_value get_token();
}
/*************************************************************************/
/*************************************************************************/
//parser.h
//分析器的用户界面
namespace Parser{
double expr(bool get);
}
/*************************************************************************/
/*************************************************************************/
//parser_impl.h
//分析器的函数所共享的环境
#include"parser.h" //将这个包含进来,是为了是编译器能检查一致性
#include"error.h"
#include"lexer.h"
namespace Parser{
double prim(bool get);
double term(bool get);
double expr(bool get);
using Lexer::get_token;
using Lexer::curr_tok;
}
/*************************************************************************/
/*************************************************************************/
//table.h
#include<map>
#include<string>
extern std::map<std::string,double>table;
/*************************************************************************/
.cpp文件:
/*************************************************************************/
//lexer.cpp
#include"lexer.h"
#include"error.h"
#include<iostream>
#include<cctype>
Lexer::Token_value Lexer::curr_tok;
double Lexer::number_value;
std::string Lexer::string_value;
Lexer::Token_value Lexer::get_token()
{
/*...*/
}
/*************************************************************************/
/*************************************************************************/
//parser.cpp
#include"parser_impl.h"
#include"table.h"
double Parser::prim(bool get){/*...*/}
double Parser::term(bool get){/*...*/}
double Parser::expr(bool get){/*...*/}
/*************************************************************************/
/*************************************************************************/
//table.cpp
#include"table.h"
std::map<std::string,double>table;
/*************************************************************************/
/*************************************************************************/
//main.cpp
#include"parser.h"
#include"lexer.h"
#include"error.h"
#include"table.h"
namespace Driver{ //可以将它分出来做成一个driver.h,并用#include包含进来
int no_of_errors;
std::istream* input;
void skip();
}
#include<sstream>
int main(int argc,char* argv[])
{
/*...*/
}
/*************************************************************************/
它们的工程截图:
总结,使用什么的组织架构看项目的大小以及个人的喜好。对于项目较小,可以使用单头文件的方式;对于大型的项目可以使用多头文件的方式实现,而多文件的文件划分的精细程度需要掌握好,太细难以管理,太粗糙出错debug难、后期维护复杂等。当然,单头文件和多头文件的方式并不冲突,在必要的时候可以结合的使用。