博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
现代编译原理——第四章:语义分析以及源码
阅读量:5080 次
发布时间:2019-06-12

本文共 22960 字,大约阅读时间需要 76 分钟。

  转自: 

  写完语义分析的代码后感觉语义分析只是为了进行类型检测(后来才发现,这只是语义分析的一部分)。词法分析注重的是每个单词是否合法,以及这个单词属于语言中的哪些部分。语法分析的上下文无关文法注重的是一个一个的推导式,是将词法分析中得到的单词按照语法规则进行组合。那么,语义分析就是要了解各个推导式之间的关系是否合法,主要体现在推导式中使用的终结符和非终结符之间的关系,也就是它们的类型。所以语义分析注重的一个方面是类型检测。

  为了将上下文无关文法中各种终结符和非终结符联系起来,以及在想要使用它们的时候得到它们相应的类型,我们使用了一种叫做符号表的东西,又称为环境。环境可以理解为对每一个ID建立一个栈,栈中存放的和这个ID相关的一些信息,这些信息称为绑定。使用栈的好处是,可以解决作用域的问题。ID始终对应于栈顶的绑定,每次进入一个新的作用域就将一个作用域标示符压入栈中。这样,在一个新的作用域中定义了一个和老作用域ID相同的变量,类型或者函数时,将新的绑定压入栈中,那么老的绑定就会失效。当出作用域时,将作用域标示符以上的所有绑定都弹出,这样就完成了老绑定的恢复。

  上面介绍的栈式环境,我们称为命令式风格。还有一种称为函数式风格,它的特点是每次都将原来的环境复制一份,将老的保存起来,对新的进行更改。当出作用域时,直接将新的抛弃,然后使用老的。

     为什么只存放变量,类型以及函数的相关信息呢?我们可以看到,一种语言其实包含四个部分类型声明,函数声明,变量声明以及表达式, 前三个就利用内置类型来创造新的东西,而表达式则是使用这些东西所要遵守的规则,这些规则都是由上下文无关文法定义好的。声明和表达式就组成了一个语言的基本部分,我们只能使用这些规则来组织我们创造的东西,最终形成我们的程序。所以在语义分析阶段,我们只用注重各种类型的检测,看看在特定的规则下是否符合要求。

   下面我们来看看tiger中语义分析时要注意的事项。 

     在tiger的语义分析过程中,我们使用了两个环境,值环境和类型环境。其中,值环境用来存放函数声明,变量声明的,类型环境是用来存放类型声明的。之所以使用两个符号表是因为在tiger中我们允许类型和变量,函数名使用相同的ID,但是不允许变量和函数使用相同的ID。并且,在tiger中,为了更快的找到ID所对应的绑定,我们使用了hash表来存储每个ID对应的栈。但是这里有个问题要解决,就是hash可能会造成冲突。例如ID1和ID2同时hash到同一个表项中,那么这个表项对应的栈是谁的?这个时候,我们要在绑定中存放这个绑定对应的ID。我们只要从栈顶开始比较,找到第一个和我们hash的ID相同的绑定,就是我们需要的绑定。

  但是,这样的话,每次查找hash表都要进行字符串的比较,是十分低效的。所以,tiger中symbol.h文件给了另一个hash表,这个hash表的作用是将一个ID映射到一个指针(就是在语法分析中使用函数S_symbol)。那么我们只要将这个指针和绑定压入栈总,比较这个指针就可以确定这个绑定是不是我需要查找的绑定。这是一个十分精巧的设计。

  知道了如何根据ID查找相应的绑定,那么我们来看看这些绑定究竟是什么。

  首先,我们向栈中压入绑定时,绑定其实使用一个void*类型的指针指向的,也就是说,我们不关心压入的绑定是什么,我们只关心压入绑定的地址以及压入到哪个表中(函数s_enter)。在读出(函数s_look)绑定的时候,我们只要把这个地址转换为我们需要的绑定(值绑定或者类型绑定)就可以了。

  对于类型环境,每一个类型ID对应一个Ty_ty_结构体定义的绑定,这个Ty_ty_在Types.h中定义。可以看出这个结构体包含的类型(kind 枚举)是很多,其中要注意的是TY_name这个枚举,这个枚举是所有由 type id = id 这种语句定义的类型。根据不同的类型,我们使用联合中不同变量来解读。后面的几个函数都是根据不同kind来创建不同Ty_ty。但是要注意到,在新建一个类型环境的时候,我们要把int和string两个ID以及对应的绑定压入(S_enter)栈中,这两个是内置类型,必须先入栈。这里的int和string是类型,也就是终结符ID。和词法分析时的INT STRING的概念不同,这两个INT STRING是整型常量和字符型常量,它们的类型是int和string。

  对于值环境,我们使用env.h中的E_enventry 结构体。这个就相对简单许多。

  知道tiger中使用的绑定是什么样子的,就来说说这里面的一些坑。注意到我们使用到很多指针,大部分都是由指针引起的。

     首先,我们在进行类型比较的时候,使用的指针。这里说说为什么,对于 type intary = array of int  以及 type strary = array of string ,如果只intary和strary所对应的绑定的kind来判断,两个都是Ty_array,但是两个的类型确实是不一样的,所以这种只使用kind判断的方式是失效的。所以这个时候我们要看看绑定中联合里的array(这是一个指针)是否一致。我们查看内置类型(int vaid nil sting),它们都是由函数(Ty_Int等)直接返回的指针,查看函数后发现,这些内置类型使用的都是相同的地址。但是其他类型的地址却有可能不同,这时就是下面这个注意事项。

     另外一个需要注意的是,因为在tiger中存在这样的类型定义 type ID1 = ID2 也就是说ID1 是 ID2 的别名,此时ID1中的绑定指向由 Ty_Name 函数返回的一个地址,该函数申请了一块新的内存。如果我们再定义type ID3 = ID2 ,此时使用指针比较ID1 和 ID3,这个时候判断两个类型是不一致的。这显然和tiger的要求的相违背。这个时候我们定义了一个新的函数actrulyTy,这个函数将返回绑定的“真实类型”,这些真是类型只可能是四个内置类型 ,数组类型或者记录类型,同时书中要求返回的任何expty中的Ty_ty必须是“真实类型”。那么,对一下代码进行类型检测就可以得到正确的结果。

type  recd = { a : int , b : string }  type  recd1 = recd   type  recd2 = recd    //测试 recd1 和 recd2 是否相等    recd1 == recd2

   这里提醒一下,我提供的代码实不支持一下类型检测的

type recd = { a : int , b : string } type recd1 = recd type recd2 = recd  type recd1ary = array of recd1  type recd2ary = array of recd2  //以下此时将返回false   recd1ary == recd2ary

     其实就是将recd1和recd2 再次进行一次actrulyTy就可以了。总之,两类型比较时,一定要求时“真实类型”。

   还有一个,可能是虎书的作者没有注意到的一个地方(或许是我的代码有问题??)。在使用词法分析器向语法分析器传送ID以及相应的字符串时,我们使用一个变量(在我上一篇文章中讲述bison和flex传值)yylval.sval  ,注意,这个sval是一个指针,指向一个字符串的开头,被指向的这个字符串是yytext。这个yytext字符串在进行词法分析时是会改变的。所以当你在语法分析器中将词法分析器传出的sval作为参数调用S_symbol时,这个sval指向的字符串yytext可能已经改变了(因为语法分析器存在移进以及规约,所以并不是和词法分析器同步工作的)。因此在使用s_symbol时要进行一些调整,如下: 

S_symbol S_Symbol(string Id){    int i = 0 ;    for ( ;  (Id[i] >= '0' && Id[i] <= '9') || ( Id[i] >= 'a' && Id[i] <= 'z')||( Id[i] >= 'A' && Id[i] <= 'Z') ;++i) ;    string name = (string)checked_malloc( sizeof(*name) * (i+1)) ;    int b = sizeof(*name) * (i+1) ;    memcpy(name , Id , sizeof(*name)*(i)) ;    name[i] = '\0' ;    int index= hash(name) % SIZE;    S_symbol syms = hashtable[index], sym;    for(sym=syms; sym; sym=sym->next)    if (streq(sym->name,name)) return sym;    sym = mksymbol(name,syms);    hashtable[index]=sym;    return sym;}

  代码比较渣渣。。。。同样的调整还出现在处理字符串常量的函数中。

  对于要处理的函数以及记录类型的递归,可以看成时c++中先处理头文件,在处理cpp。也不算太难,但是要注意,保持指针指向正确的位置。

  以下就是这次的部分代码,没有实现当有错误时,显示错误位置的功能。有些代码比较简单,就没有贴出来。这份代码已经经过随书附带的前六个测试用例测试过,没有问题:

      env.h

#ifndef ENV_H_#define ENV_H_#include "types.h"typedef struct E_enventry_ *E_enventry ;struct E_enventry_ {    enum { E_varEntry , E_funEntry } kind;    union    {        struct { Ty_ty ty ; } var;        struct { Ty_tyList formals ; Ty_ty result ;} fun ;    }u;};E_enventry E_VarEntry(Ty_ty ty) ;E_enventry E_FunEntry(Ty_tyList formals , Ty_ty reslut) ;S_table E_base_tenv() ;S_table E_base_venv() ;Ty_ty actrulyTy(Ty_ty) ; bool  isTyequTy( const Ty_ty ,const Ty_ty) ;void  tyCpy(Ty_ty dec , const Ty_ty src );#endif

    env.cpp

#include "env.h"#include 
#include
E_enventry E_VarEntry(Ty_ty ty){ E_enventry p = (E_enventry) checked_malloc(sizeof(*p)) ; p->kind = E_enventry_::E_varEntry ; p->u.var.ty = ty ; return p ;}E_enventry E_FunEntry(Ty_tyList formals , Ty_ty reslut){ E_enventry p = (E_enventry)checked_malloc(sizeof(*p)) ; p->kind = E_enventry_::E_funEntry ; p->u.fun.formals = formals ; p->u.fun.result = reslut ; return p ;}Ty_ty actrulyTy(Ty_ty ty){ if (ty == NULL ) { return NULL ; } while(ty->kind == Ty_ty_::Ty_name) { ty = ty->u.name.ty ; } return ty ;}bool isTyequTy(const Ty_ty s1 , const Ty_ty s2){ Ty_ty tmp1 = actrulyTy(s1) ; Ty_ty tmp2 = actrulyTy(s2) ; bool aryOrRec = (tmp1->kind == Ty_ty_::Ty_array || tmp2->kind == Ty_ty_::Ty_record) ; bool isnil = (tmp1->kind == Ty_ty_::Ty_nil || tmp2->kind == Ty_ty_::Ty_nil) ; if ( tmp1->kind != tmp2->kind) { if ( isnil && aryOrRec ) { return true ; } return false ; } if (aryOrRec) { if (tmp1 != tmp2 ) { return false ; } } return true ;}void tyCpy(Ty_ty dec , const Ty_ty src){ if (dec == NULL || src == NULL ) { assert(0) ; } memcpy(dec , src , sizeof(*src)) ;}

sement.h

#ifndef SENMANT_H_#define SENMANT_H_#include "types.h"#include "translate.h"#include "absyn.h"struct expty { Tr_exp exp ; Ty_ty ty; };expty expTy(Tr_exp exp , Ty_ty ty) ;expty  transVar(S_table venv , S_table tenv , A_var var) ;expty  transExp(S_table venv , S_table tenv , A_exp exp) ; void   transDec(S_table venv , S_table tenv , A_dec dec) ;Ty_ty  transTy( S_table tenv , A_ty ty) ;bool  innerIdentifiers( S_symbol sym);#endif

sement.cpp

#include "semant.h"#include 
#include "env.h"expty expTy(Tr_exp exp , Ty_ty ty){ expty e ; e.exp = exp ; e.ty = ty ; return e ;}expty transExp(S_table venv , S_table tenv , A_exp exp){ if (exp == NULL ) { assert(0); } switch(exp->kind) { case A_varExp : return transVar(venv , tenv , exp->u.var) ; case A_nilExp : return expTy(NULL ,Ty_Nil()); case A_intExp : return expTy(NULL , Ty_Int()) ; case A_stringExp : return expTy(NULL , Ty_String()) ; case A_callExp : { E_enventry tmp = (E_enventry)S_look(venv, exp->u.call.func) ; if (tmp == NULL) { assert(0) ; } Ty_tyList tylist = tmp->u.fun.formals ; A_expList explist = exp->u.call.args ; while (tylist != NULL && explist != NULL) { expty exptyp = transExp(venv , tenv , explist->head) ; if (exptyp.ty->kind == Ty_ty_::Ty_nil) { continue ; } if (!isTyequTy(tylist->head , exptyp.ty)) { assert(0) ; } tylist = tylist->tail ; explist = explist->tail ; } if (tylist != NULL || explist != NULL ) { assert(0); } return expTy(NULL , actrulyTy(tmp->u.fun.result)) ; } case A_opExp : { switch(exp->u.op.oper) { case A_plusOp : case A_minusOp : case A_timesOp : case A_divideOp : case A_ltOp : case A_leOp : case A_gtOp : case A_geOp : { if (transExp(venv , tenv, exp->u.op.left).ty->kind != Ty_ty_::Ty_int) assert(0); if (transExp(venv , tenv, exp->u.op.right).ty->kind != Ty_ty_::Ty_int) assert(0); return expTy(NULL , Ty_Int()) ; } case A_eqOp : case A_neqOp: { expty tmpleft = transExp(venv , tenv, exp->u.op.left) ; expty tmpright = transExp(venv , tenv, exp->u.op.right) ; if (tmpleft.ty->kind == Ty_ty_::Ty_int && tmpright.ty->kind == Ty_ty_::Ty_int) return expTy(NULL , Ty_Int()) ; if (tmpleft.ty->kind == tmpright.ty->kind) { if (tmpleft.ty->kind == Ty_ty_::Ty_record || tmpleft.ty->kind == Ty_ty_::Ty_array) { if ( tmpleft.ty == tmpright.ty ) { return expTy(NULL , Ty_Int()) ; } } } assert(0); } } assert(0); } case A_recordExp : { Ty_ty tmpty = (Ty_ty)S_look(tenv , exp->u.record.typ) ; tmpty = actrulyTy(tmpty) ; if (tmpty == NULL ) { assert(0) ; } if (tmpty->kind != Ty_ty_::Ty_record ) { assert(0) ; } A_efieldList tmpefield = exp->u.record.fields ; Ty_fieldList tmpfieldlist = tmpty->u.record ; while(tmpefield && tmpfieldlist) { if (tmpefield->head->name != tmpfieldlist->head->name ) { assert(0) ; } if (!isTyequTy(transExp(venv , tenv , tmpefield->head->exp).ty ,tmpfieldlist->head->ty)) { assert(0) ; } tmpefield = tmpefield->tail ; tmpfieldlist = tmpfieldlist->tail ; } if (tmpfieldlist!= NULL || tmpefield != NULL ) { assert(0) ; } return expTy(NULL ,tmpty); } case A_seqExp : { A_expList explist = exp->u.seq ; if (explist) { while(explist->tail) { transExp( venv , tenv , explist->head); explist = explist->tail ; } } else { return expTy(NULL , Ty_Void()); } return transExp(venv , tenv , explist->head); } case A_assignExp : { expty tmpV = transVar(venv , tenv , exp->u.assign.var) ; expty tmpE = transExp(venv , tenv , exp->u.assign.exp); if (tmpE.ty->kind != tmpV.ty->kind) { assert(0); } return expTy(NULL , Ty_Void()); } case A_ifExp : { expty tmptest = transExp(venv ,tenv , exp->u.iff.test) ; if(tmptest.ty->kind != Ty_ty_::Ty_int) { assert(0); } expty tmpthen = transExp(venv , tenv , exp->u.iff.then) ; if (exp->u.iff.elsee != NULL) { expty tmpelse = transExp(venv , tenv , exp->u.iff.elsee) ; if ( tmpthen.ty != tmpelse.ty ) { assert(0); } return expTy(NULL , tmpelse.ty); } if (tmpthen.ty->kind != Ty_ty_::Ty_void) { assert(0); } return expTy(NULL , Ty_Void()); } case A_whileExp : { expty test = transExp(venv , tenv , exp->u.whilee.test); if (test.ty->kind != Ty_ty_::Ty_int) { assert(0) ; } expty body = transExp(venv , tenv , exp->u.whilee.body); if (body.ty->kind != Ty_ty_::Ty_void) { assert(0); } return expTy(NULL , Ty_Void()); } case A_forExp : { expty tmplo = transExp(venv , tenv , exp->u.forr.lo); expty tmphi = transExp(venv , tenv , exp->u.forr.hi); S_beginScope(venv); S_enter(venv , exp->u.forr.var , E_VarEntry(Ty_Int())); expty tmpbody = transExp(venv , tenv, exp->u.forr.body); if (tmplo.ty->kind != Ty_ty_::Ty_int || tmphi.ty->kind != Ty_ty_::Ty_int || tmpbody.ty->kind != Ty_ty_::Ty_void) { assert(0); } S_endScope(venv); return expTy(NULL , Ty_Void()); } case A_breakExp : return expTy(NULL , Ty_Void()); case A_letExp : { S_beginScope(venv); S_beginScope(tenv); A_decList declist = exp->u.let.decs ; while(declist != NULL) { transDec(venv , tenv , declist->head); declist = declist->tail; } expty tmp ; if (exp->u.let.body) { tmp = transExp(venv , tenv , exp->u.let.body); } else { tmp = expTy(NULL , Ty_Void()) ; } S_endScope(venv); S_endScope(tenv); return tmp ; } case A_arrayExp : { Ty_ty ty = (Ty_ty)S_look(tenv , exp->u.array.typ); ty = actrulyTy(ty); if (ty == NULL || ty->kind != Ty_ty_::Ty_array) { assert(0); } expty tynum = transExp(venv , tenv , exp->u.array.size); if (tynum.ty->kind != Ty_ty_::Ty_int) { assert(0); } expty tyinit = transExp(venv , tenv, exp->u.array.init) ; if (tyinit.ty != ty->u.array ) { assert(0) ; } return expTy(NULL , ty); } } assert(0);}expty transVar(S_table venv , S_table tenv , A_var var){ switch(var->kind) { case A_simpleVar : { E_enventry tmp = (E_enventry) S_look(venv , var->u.simple) ; if (tmp != NULL && tmp->kind == E_enventry_::E_varEntry) { return expTy(NULL , actrulyTy(tmp->u.var.ty)) ; } assert(0) ; } case A_fieldVar : { expty tmpty = transVar(venv , tenv , var->u.field.var) ; if (tmpty.ty->kind != Ty_ty_::Ty_record) { assert(0); } Ty_fieldList fieldList = tmpty.ty->u.record ; while( fieldList ) { if ( fieldList->head->name == var->u.field.sym ) { return expTy(NULL , actrulyTy(fieldList->head->ty)) ; } fieldList = fieldList->tail ; } assert(0); } case A_subscriptVar : { expty tmp = transVar(venv , tenv , var->u.subscript.var) ; if (tmp.ty->kind != Ty_ty_::Ty_array ) { assert(0) ; } expty tmpexp = transExp(venv , tenv , var->u.subscript.exp) ; if (tmpexp.ty->kind != Ty_ty_::Ty_int) { assert(0) ; } return tmp ; } } assert(0) ;}void transDec(S_table venv , S_table tenv , A_dec dec){ switch(dec->kind) { case A_functionDec : { A_fundecList tmpfun = dec->u.function ; while(tmpfun) { A_fieldList tmpfeldList = tmpfun->head->params ; Ty_tyList tylist = NULL ; while(tmpfeldList) { Ty_ty ty = (Ty_ty)S_look(tenv,tmpfeldList->head->typ); tylist = Ty_TyList(ty , tylist) ; tmpfeldList = tmpfeldList->tail ; } if (innerIdentifiers(tmpfun->head->name)) { assert(0) ; } //¿‡À∆”⁄…˘√˜“ª∏ˆ∫Ø ˝ ªπ√ª”–∂®“ÂÀ¸ S_enter(venv , tmpfun->head->name , E_FunEntry(tylist , (Ty_ty)S_look(tenv ,tmpfun->head->result))) ; tmpfun = tmpfun->tail ; } tmpfun = dec->u.function ; while(tmpfun) { S_beginScope(venv) ; A_fieldList tmpfeldList = tmpfun->head->params ; while(tmpfeldList) { Ty_ty ty = (Ty_ty)S_look(tenv,tmpfeldList->head->typ); if (innerIdentifiers(tmpfeldList->head->name)) { assert(0); } S_enter(venv ,tmpfeldList->head->name, E_VarEntry(ty)) ; tmpfeldList = tmpfeldList->tail ; } transExp(venv , tenv , tmpfun->head->body) ; S_endScope(venv) ; tmpfun = tmpfun->tail ; } return ; } case A_typeDec : { A_nametyList namelist = dec->u.type ; while(namelist) { if (innerIdentifiers(namelist->head->name)) { assert(0) ; } // ¥¶¿Ìµ›πÈ ¿‡À∆”⁄ …˘√˜“ª∏ˆ¿‡–Õ µ´ «ªπ√ª”–∂®“ÂÀ¸ S_enter(tenv , namelist->head->name ,Ty_Name(namelist->head->name , NULL)) ; namelist = namelist->tail ; } namelist = dec->u.type ; while(namelist) { // ¥¶¿Ìµ›πÈ Ty_ty tmp1 = transTy(tenv , namelist->head->ty ) ; Ty_ty tmp2 = (Ty_ty)S_look(tenv , namelist->head->name) ; if ( tmp1->kind == Ty_ty_::Ty_int || tmp1->kind == Ty_ty_::Ty_string || tmp1->kind == Ty_ty_::Ty_nil || tmp1->kind == Ty_ty_::Ty_void) { //如果是内置类型 绑定指向的地方是一个固定的地方 所以 这个时候就不是替换内容那么简单了 tmp2 = (Ty_ty)S_changeBind(tenv , namelist->head->name , tmp1); tmp2 = (Ty_ty)freeTy(tmp2) ; } else { tyCpy(tmp2 , tmp1) ; tmp1 = (Ty_ty)freeTy(tmp1) ; } namelist = namelist->tail ; } namelist = dec->u.type; while(namelist) { // 避免出现 type a = b type b = a 这种没有真实类型的类型定义 Ty_ty tmp = (Ty_ty)S_look(tenv , namelist->head->name) ; if (!actrulyTy(tmp)) { assert(0) ; } namelist = namelist->tail ; } return; } case A_varDec : { if(dec->u.var.init == NULL) { assert(0) ; } expty tmp = transExp(venv , tenv , dec->u.var.init) ; if( (dec->u.var.typ != NULL) ) { if ( actrulyTy((Ty_ty)S_look(tenv ,dec->u.var.typ)) != tmp.ty) { assert(0) ; } } if (innerIdentifiers(dec->u.var.var)) { assert(0) ; } S_enter(venv , dec->u.var.var ,E_VarEntry(tmp.ty)) ; return; } } assert(0) ;} Ty_ty transTy(S_table tenv , A_ty ty) { switch(ty->kind) { case A_nameTy : { if (S_Symbol("int") == ty->u.name) { return Ty_Int(); } if (S_Symbol("string") == ty->u.name) { return Ty_String(); } Ty_ty tmp = (Ty_ty)S_look(tenv , ty->u.name) ; if ( tmp == NULL ) { assert(0) ; } return Ty_Name(ty->u.name , tmp) ; } case A_recordTy : { A_fieldList tmpfeldList = ty->u.record ; Ty_fieldList tyfdlist = NULL ; while(tmpfeldList) { Ty_ty tmp = (Ty_ty)S_look(tenv , tmpfeldList->head->typ) ; if ( tmp == NULL ) { assert(0) ; } if (innerIdentifiers(tmpfeldList->head->name)) { assert(0); } tyfdlist = Ty_FieldList(Ty_Field( tmpfeldList->head->name , tmp ) , tyfdlist) ; tmpfeldList = tmpfeldList->tail ; } return Ty_Record(tyfdlist); } case A_arrayTy : { Ty_ty tmp = (Ty_ty)S_look(tenv , ty->u.array); if ( tmp == NULL ) { assert(0); } return Ty_Array(tmp) ; } } assert(0) ; } bool innerIdentifiers( S_symbol sym) { if (sym == S_Symbol("int") || sym == S_Symbol("string") ) { return true ; } return false ; }

函数 S_changeBind 相关代码

S_symbol.cppvoid* S_changeBind(S_table t , S_symbol sym , void *value){   return TAB_changeBind(t , sym , value)  ; }S_table.cppvoid* TAB_changeBind( TAB_table t , void * key , void *value ){    int index ;    assert(t&&key) ;    binder b ;    void * tmp ;    index = ((unsigned)key) % TABSIZE ;    for ( b = t->table[index] ; b ;  b = b->next)    {       if (b->key == key )       {             tmp = b->value ;           b->value = value ;           return  tmp ;       }    }    return NULL ;}

 

转载于:https://www.cnblogs.com/jacksplwxy/p/10052775.html

你可能感兴趣的文章
Android系统--输入系统(十一)Reader线程_简单处理
查看>>
监督学习模型分类 生成模型vs判别模型 概率模型vs非概率模型 参数模型vs非参数模型...
查看>>
Mobiscroll脚本破解,去除Trial和注册时间限制【转】
查看>>
实验五 Java网络编程及安全
查看>>
32位与64位 兼容编程
查看>>
iframe父子页面通信
查看>>
ambari 大数据安装利器
查看>>
java 上传图片压缩图片
查看>>
magento 自定义订单前缀或订单起始编号
查看>>
ACM_拼接数字
查看>>
计算机基础作业1
查看>>
Ubuntu 深度炼丹环境配置
查看>>
C#中集合ArrayList与Hashtable的使用
查看>>
从一个标准 url 里取出文件的扩展名
查看>>
map基本用法
查看>>
poj-1163 动态规划
查看>>
Golang之interface(多态,类型断言)
查看>>
Redis快速入门
查看>>
BootStrap---2.表格和按钮
查看>>
Linear Algebra lecture 2 note
查看>>