类的定义,在编程语言中作为用户自定义的类型,是数据结构的主要实现方式。
C语言没有类,用结构体代替。
C的结构体与C++的类,主要差别无非是没有成员函数。
因为函数和变量的声明都是以类型关键字加名字标志符开始,实际上允许结构体里定义函数时,语法分析时反而要更简单。
class Point {
int x;
int y;
int distance() { return sqrt(x * x + y * y); }
};
struct Point {
int distance() { return sqrt(x * x + y * y);
int x, y; };
如果不允许在结构体里定义函数,那么在把结构体内容当作大括号表示的顺序块处理时,分析代码发现是函数定义时就要报错。
但实际上,代码要连续检查好几个词才能发现这一点。
而且,这么一来用于结构体内容的分析代码,与普通的语句块分析代码就有了差别,不得不准备两套了。
还不如允许结构体定义函数来得简单,这样准备一套代码就行。
所以,C++干脆把struct 和 class(除了默认访问权限之外)当作一样的。
类的定义分析,实际上并不难。最难分析的,还是表达式。
它以关键字class开始,有关键字就容易分析的多。
如果语法分析写不出来时,就给这门语言加个关键字,语法分析的难度立马降低一大截(笑)。
(前提是这门语言是你发明的,你才能随便加关键字)
类定义的模块数据如上图,我们不允许类的嵌套定义,即不能这么写:
class A {
class B {
}; };
如果给dfa_module_class模块加个栈作为模块数据,就能比较容易的支持嵌套定义。为了简单,这里暂时不支持。
class和struct关键字都当作一样的,见25、26行。
它的dfa节点见上图:
_class:class或struct关键字,
identity:类名的标志符,
lb和rb:大括号,表示类的主体内容,
semicolon:分号,表示结尾,
end:作为一个hook节点,检查类的主体内容(它是个顺序块)的分析是否完成。
类定义的语法编辑见上图:
class关键字加类名标志符作为开始,然后可以跟分号表示类的声明。例如class A;
如果跟大括号表示类的定义,例如 class A {};
左右大括号直接相连,是空类(287行)。
以类型开始的入口,是类的成员定义(成员函数或成员变量)。这里的类型有可能是基本类型,或者类类型,不管是变量还是函数都是以这种方式开始定义。
最后以右大括号之后跟着分号,表示结尾(291行)。
dfa节点end,用于捕获类的主体内容分析时的DFA_OK。如果是变量定义或函数声明,会在分号结束时返回DFA_OK,如果是函数定义则是发现函数的右大括号时返回DFA_OK。
这个end的hook节点,有可能截获多次DFA_OK,需要检查左右大括号是否匹配来判断类定义的真正结束。
class关键字的action函数:
它去语法树上查询是否存在同名的类类型,如果没有就申请一个。然后,挂载一个hook,用来检查类的主体定义的结束。
左大括号的action函数:
它标示着类的主体内容开始,要把语法树的current_block切换到当前类的scf_type_t结构,后续的内容都添加在这个结构里,直到类的定义结束。
右大括号的action函数:
它只调用一个函数,即计算类所占的字节数。
hook end的action函数:
它每次触发时都比较大括号是否匹配,如果匹配就结束。否则,切换到它这里继续分析。它唯一的子节点就是类的member节点,即继续分析类的成员函数或成员变量。
分号的action函数:
最后的分号表示类的定义结束,直接返回DFA_OK。
计算类的字节数的函数:
类的成员变量,要按所占的字节数对齐。
类的成员函数,不占用对象的内存空间,在编译时只保留一份数据在代码段里。
如果要支持虚函数,虚函数表的指针还要占用sizeof(void*)个字节,64位机上是8字节。一般把它放在对象的最前面。
举报/反馈