zoukankan      html  css  js  c++  java
  • 简单的C语言编译器--语义制导翻译

      语法分析是最难写的,而这部分确实最伤脑的。大量的语义动作分析差点把我逼疯。

      简而言之,这部分的作用就是在每次归约之后,都进行一些语义动作,最终让我们得到测试程序的三地址码,即中间代码。

    1. 新的数据结构和函数

    为了得到中间代码,我引进了几个struct,如下:

    //用于标识一个变量的类型
    enum TYPE
    {
    	INT,
    	BOOL,
    	ARRAY
    };
    
    //状态栈中最重要的信息
    struct Info {
    	std::string name = ""; //变量名或者运算符
    	int quad;
    	int val;
    	int width; //不同类型的所占偏移
    	TYPE t;
    	std::unordered_set<int> truelist, falselist, nextlist;
    	int array_offset = 0;
    	std::vector<std::string> code;
    	std::string other = ""; //辅助
    };
    
    //每次归约时用到的状态栈
    struct stack_node {
    	int status;
    	Word token;
    	Info nodeInfo;
    };
    
    
    //符号表表项
    struct Symbol {
    	TYPE t;
    	int n = 0 ;
    	int offset; //该作用域下的偏移
    };
    

      当然还定义了新的类进行状态栈分析,如下:

    class Parser {
    	public:
    		typedef std::unordered_map<int, std::unordered_map<std::string, std::string>> Atable;
    		typedef std::unordered_map<int, std::unordered_map<std::string, int>> Gtable;
    		typedef std::unordered_map<std::string, Symbol> STable;
    
    		Parser();
    		void getTable(std::ifstream &i1, std::ifstream &i2);
    		void get_grammer(std::ifstream &in); 
    		bool stack_parser(std::ifstream &in);  //主要分析过程
    		void print(std::ofstream &out);
    		void run();
    		Symbol hasKey(std::string str);
    		void gen_code(std::string str); //生成三地址码
    		void gen_math_code(std::vector<stack_node> p);
    		void backpatch(std::unordered_set<int> l, int addr); //回填
    		std::unordered_set<int> merge(std::unordered_set<int> s1, std::unordered_set<int> s2);
    
    	private:
    		std::stack<stack_node> stk;
    		std::vector<int> parser_result, parser_line;	
    		Atable action;
    		Gtable goTo;
    		std::vector<Grammer> G;
    		std::vector<std::string> code; //三地址码
    		std::stack<STable *> Utable; //符号表
    		int offset_record[MAX_LAY] = {0};
    		int tmp_count; //临时变量的下标
    };
    

      有了这些东西,我们就可以进行语义动作设计了。

    2. 语义动作设计

      直接上代码,每个状态都有各自的语义动作,我是用switch-case写的,如下:

    #include "myParser.h"
    
    Parser::Parser() {
    	stack_node pNode;
    	pNode.status = 0;
    	tmp_count = 0;
    	pNode.token.setTag(70);
    	pNode.token.setLexeme("#");
    
    	stk.push(pNode);
    	run();
    }
    
    
    void Parser::getTable(std::ifstream &i1, std::ifstream &i2) {
    	std::string tmp2,atmp3;
    	int tmp1,gtmp3;
    	while(i1 >> tmp1, i1 >> tmp2, i1 >> atmp3) {
    		action[tmp1][tmp2] = atmp3;
    	}
    
    	while(i2 >> tmp1, i2 >> tmp2, i2 >> gtmp3) {
    		goTo[tmp1][tmp2] = gtmp3;
    	}	
    
    }
    
    void Parser::get_grammer(std::ifstream &in) {
    	std::string tmp;
    	Grammer gtmp;
    	gtmp.left = "S";
    	gtmp.right = {"Program"};
    	G.push_back(gtmp);
    
    	while(in >> tmp) {
    		gtmp.left = tmp;
    		gtmp.right.clear();
    		in >> tmp >> tmp;
    		while(tmp != "#") {
    			gtmp.right.push_back(tmp);	
    			in >> tmp;
    		}
    		G.push_back(gtmp);
    	}
    }
    
    
    inline
    void Parser::gen_code(std::string str) {
    	code.push_back(str);
    }
    
    
    void Parser::gen_math_code(std::vector<stack_node> p) {
    	std::string left, right, op;	
    
    	left = p[2].nodeInfo.name;
    	op = p[1].nodeInfo.name;
    	right = p[0].nodeInfo.name;
    
    	code.push_back("t"+std::to_string(tmp_count++)+" = "+left + " "+op+" "+right);
    
    
    }
    
    
    void Parser::backpatch(std::unordered_set<int> l, int addr) {
    	for(auto t : l)
    		code[t] = code[t] + std::to_string(addr);
    }
    
    
    std::unordered_set<int> Parser::merge(std::unordered_set<int> s1, std::unordered_set<int> s2) {
    	std::unordered_set<int> result = s1;
    	result.insert(s2.begin(),s2.end());
    	return result;
    }
    
    Symbol Parser::hasKey(std::string str) {
    	Symbol result ;
    	std::stack<STable *> stack_tmp = Utable;
    	STable st_tmp;
    
    	while(!stack_tmp.empty()) {
    		st_tmp = *stack_tmp.top();
    		if(st_tmp.find(str) != st_tmp.end())
    			return st_tmp[str];
    		stack_tmp.pop();
    	}
    
    	result.n = ERROR;
    	return result;
    }
    
    bool Parser::stack_parser(std::ifstream &in) {
    
    	in >> std::noskipws;
    	Lexer mylex;
    
    	Word w;
    	w = mylex.scan(in);
    
    	STable *stable;
    	Symbol sym_tmp;
    	stack_node stk_top;
    
    	std::string left = "", right = "";
    
    	while(!stk.empty()) {
    		stk_top = stk.top();
    		int top_status = stk_top.status;
    		int next_status;
    		std::string act; //将要执行的动作
    		std::string T;
    		if(w.getTag() == 50) 
    			T = "Num";
    		else if(w.getTag() == 60) 
    			T = "Identifier";
    		else
    			T = w.getLexeme();
    
    		if(action[top_status].find(T) == action[top_status].end()) 
    			return false;
    		act = action[top_status][T];
    		stack_node tmp;
    		if(act[0] == 's') {
    			//规定作用域
    			if(w.getLexeme() == "{") {
    				offset_record[Utable.size()] = 0;
    				stable = new STable();
    				Utable.push(stable);
    			}
    			else if(w.getLexeme() == "}") {
    				Utable.pop();
    				if(!Utable.empty())
    					stable = Utable.top();
    			}
    			//移进动作
    			next_status = std::stoi(act.substr(1));
    			tmp.status = next_status;
    			tmp.token = w;
    			tmp.nodeInfo.name = w.getLexeme();
    			if(w.getTag() == 50)
    				tmp.nodeInfo.val = std::stoi(w.getLexeme());
    			stk.push(tmp);
    			w = mylex.scan(in);
    		} else if(act[0] == 'r') {
    			std::vector<stack_node> right_tmp;
    			next_status = std::stoi(act.substr(1));
    			if(G[next_status].right[0] != "ε") {
    				for(int i = 0; i < static_cast<int>(G[next_status].right.size());i++) {
    					right_tmp.push_back(stk.top());
    					stk.pop();
    				}		
    			}
    			stk_top = stk.top();
    			top_status = stk_top.status;
    			if(goTo[top_status].find(G[next_status].left) == goTo[top_status].end())
    				return false;
    			tmp.status = goTo[top_status][G[next_status].left];
    			tmp.token.setLexeme(G[next_status].left);
    			tmp.token.setTag(next_status);
    
    			//std::cout << next_status << " ";
    
    			switch(next_status) {
    				case 0:
    					break;
    				case 1:
    					break;
    				case 2:
    					tmp.nodeInfo.t = INT;
    					tmp.nodeInfo.width = 4;
    					break;
    				case 3:
    					tmp.nodeInfo.t = BOOL;
    					tmp.nodeInfo.width = 1;
    					break;
    				case 4:
    					backpatch(right_tmp[4].nodeInfo.nextlist, code.size());
    					break;
    				case 5:
    					tmp.nodeInfo = right_tmp[1].nodeInfo;
    					sym_tmp = hasKey(tmp.nodeInfo.name);
    					if(stable->find(tmp.nodeInfo.name) == stable->end()) {
    						//创建符号表并且初始化为0
    						Symbol s_tmp;
    						int size_tmp = 1;
    						if(tmp.nodeInfo.array_offset > 0) {
    							size_tmp *= tmp.nodeInfo.array_offset;
    							//向符号表里添加数据
    							size_tmp *= right_tmp[2].nodeInfo.width;
    							s_tmp.t = ARRAY;
    							s_tmp.offset = offset_record[Utable.size()-1];
    							s_tmp.n = tmp.nodeInfo.array_offset;
    							offset_record[Utable.size()-1] += size_tmp;
    
    							stable->insert({tmp.nodeInfo.name,s_tmp});
    						} else {
    							size_tmp = right_tmp[2].nodeInfo.width;
    							//向符号表添加数据
    							s_tmp.t = tmp.nodeInfo.t;
    							s_tmp.offset = offset_record[Utable.size()-1];
    							offset_record[Utable.size()-1] += size_tmp;
    
    							stable->insert({tmp.nodeInfo.name,s_tmp});
    						}
    					} else 
    						std::cout <<"hh" ;
    						;//重复声明,error;
    
    					break;
    				case 6:
    					backpatch(right_tmp[2].nodeInfo.nextlist,right_tmp[1].nodeInfo.quad);
    					tmp.nodeInfo.nextlist = right_tmp[0].nodeInfo.nextlist;
    					break;
    				case 7:
    					tmp.nodeInfo.nextlist = right_tmp[0].nodeInfo.nextlist;
    					break;
    				case 8:
    					tmp.nodeInfo = right_tmp[3].nodeInfo;
    					//符号表中不存在,则创建
    					sym_tmp = hasKey(tmp.nodeInfo.name);
    					if(stable->find(tmp.nodeInfo.name) == stable->end() && stk_top.token.getLexeme() == "Type") 
    						tmp.nodeInfo.array_offset = right_tmp[1].nodeInfo.val;
    					else {
    						if(sym_tmp.t == ARRAY) {
    							std::string code_tmp;
    							code_tmp = "t" + std::to_string(tmp_count++) + " = " + std::to_string(sym_tmp.offset);
    							gen_code(code_tmp);
    							code_tmp= "t" + std::to_string(tmp_count++) + " = ";
    							code_tmp = code_tmp + "t"+std::to_string(tmp_count-2)+"["+ std::to_string(right_tmp[1].nodeInfo.val)+"]";
    							gen_code(code_tmp);
    							tmp.nodeInfo.name = "t"+std::to_string(tmp_count - 1);
    						}
    						else
    							;//普通变量引用数组
    					}
    					break;
    				case 9:
    					tmp.nodeInfo = right_tmp[3].nodeInfo;
    					sym_tmp = hasKey(tmp.nodeInfo.name);
    					if(sym_tmp.n >= 0) {
    						if(sym_tmp.t == ARRAY){
    							Symbol sym_tmpp = hasKey(right_tmp[1].nodeInfo.name);
    							if(sym_tmpp.n >= 0) {
    								std::string code_tmp;
    								code_tmp = "t" + std::to_string(tmp_count++) + " = " + std::to_string(sym_tmp.offset);
    								gen_code(code_tmp);
    								code_tmp = "t" + std::to_string(tmp_count++) + " = ";
    								code_tmp = code_tmp + "t"+std::to_string(tmp_count-2)+"["+right_tmp[1].nodeInfo.name+"]";
    								gen_code(code_tmp);
    								tmp.nodeInfo.name = "t"+std::to_string(tmp_count-1);
    							}
    							else
    								;//error
    						}		
    						else
    							;//普通变量引用数组
    					} else
    						;//声明数组用变量
    					break;
    				case 10:
    					tmp.nodeInfo.name = right_tmp[0].nodeInfo.name;
    					break;
    				case 11:
    					tmp.nodeInfo.name = "1";
    					tmp.nodeInfo.truelist.insert(code.size());
    					break;
    				case 12:
    					tmp.nodeInfo.name = "0";
    					tmp.nodeInfo.falselist.insert(code.size());
    					break;
    				case 13:
    					//计算左右边的值
    					if(right_tmp[2].nodeInfo.other == "") 
    						left = right_tmp[2].nodeInfo.name;
    					else 
    						left = right_tmp[2].nodeInfo.name +"["+right_tmp[2].nodeInfo.other+"]";
    
    					right = right_tmp[0].nodeInfo.name;
    
    					gen_code(left+" = "+right);
    
    					break;
    				case 14:
    					tmp.nodeInfo = right_tmp[1].nodeInfo;
    					if(tmp.nodeInfo.other != "") {
    						std::string code_tmp;
    						code_tmp="t"+std::to_string(tmp_count++)+" = ";
    						left = tmp.nodeInfo.name+"["+tmp.nodeInfo.other+"]";
    						code_tmp += left;
    						gen_code(code_tmp);
    						right = "t"+std::to_string(tmp_count-1);
    						code_tmp = right +" = "+right+" "+right_tmp[0].nodeInfo.name + " 1";
    						gen_code(code_tmp);
    						gen_code(left+" = "+ right);
    					} else 
    						gen_code(tmp.nodeInfo.name + " = "+tmp.nodeInfo.name+" "+right_tmp[0].nodeInfo.name+" 1");
    					break;
    				case 15:
    					tmp.nodeInfo = right_tmp[0].nodeInfo;
    					if(tmp.nodeInfo.other != "") {
    						std::string code_tmp;
    						code_tmp="t"+std::to_string(tmp_count++)+" = ";
    						left = tmp.nodeInfo.name+"["+tmp.nodeInfo.other+"]";
    						code_tmp += left;
    						gen_code(code_tmp);
    						right = "t"+std::to_string(tmp_count-1);
    						code_tmp = right +" = "+right+" "+right_tmp[1].nodeInfo.name + " 1";
    						gen_code(code_tmp);
    						gen_code(left+" = "+ right);
    					} else 
    						gen_code(tmp.nodeInfo.name + " = "+tmp.nodeInfo.name+" "+right_tmp[1].nodeInfo.name+" 1");
    					break;
    				case 16:
    					backpatch(right_tmp[3].nodeInfo.falselist,right_tmp[1].nodeInfo.quad);
    					tmp.nodeInfo.falselist = right_tmp[0].nodeInfo.falselist;
    					tmp.nodeInfo.truelist = merge(right_tmp[0].nodeInfo.truelist,right_tmp[3].nodeInfo.truelist);
    					break;
    				case 17:
    					tmp.nodeInfo = right_tmp[0].nodeInfo;
    					break;
    				case 18:
    					backpatch(right_tmp[3].nodeInfo.truelist,right_tmp[1].nodeInfo.quad);
    					tmp.nodeInfo.truelist = right_tmp[0].nodeInfo.truelist;
    					tmp.nodeInfo.falselist = merge(right_tmp[0].nodeInfo.falselist,right_tmp[3].nodeInfo.falselist);
    					break;
    				case 19:
    					tmp.nodeInfo = right_tmp[0].nodeInfo;
    					break;
    				case 20:
    					tmp.nodeInfo.truelist.insert(code.size());
    					tmp.nodeInfo.falselist.insert(code.size()+1);
    					gen_code("if "+right_tmp[2].nodeInfo.name+" "+right_tmp[1].nodeInfo.name+" "+right_tmp[0].nodeInfo.name+" goto ");
    					gen_code("goto ");
    					break;
    				case 21:
    					tmp.nodeInfo = right_tmp[0].nodeInfo;
    					break;
    				case 22:
    					gen_math_code(right_tmp);
    					tmp.nodeInfo.name = "t"+std::to_string(tmp_count-1);
    					break;
    				case 23:
    					tmp.nodeInfo = right_tmp[0].nodeInfo;
    					break;
    				case 24:
    					gen_math_code(right_tmp);
    					tmp.nodeInfo.name = "t"+std::to_string(tmp_count-1);
    					break;
    				case 25:
    					tmp.nodeInfo = right_tmp[0].nodeInfo;
    					break;
    				case 26:
    					tmp.nodeInfo.truelist = right_tmp[1].nodeInfo.falselist;
    					tmp.nodeInfo.falselist = right_tmp[1].nodeInfo.truelist;
    					break;
    				case 27:
    					tmp.nodeInfo = right_tmp[1].nodeInfo;
    					break;
    				case 28:
    					tmp.nodeInfo = right_tmp[0].nodeInfo;
    					break;
    				case 29:
    					tmp.nodeInfo = right_tmp[0].nodeInfo;
    					tmp.nodeInfo.name = right_tmp[0].token.getLexeme();
    					break;
    				case 30:
    					tmp.nodeInfo = right_tmp[0].nodeInfo;
    					break;
    				case 31:
    					tmp.nodeInfo.name = "+";
    					break;
    				case 32:
    					tmp.nodeInfo.name = "-";
    					break;
    				case 33:
    					break;
    				case 34:
    					break;
    				case 35:
    					tmp.nodeInfo.name = "+";
    					break;
    				case 36:
    					tmp.nodeInfo.name = "-";
    					break;
    				case 37:
    					tmp.nodeInfo.name = "*";
    					break;
    				case 38:
    					tmp.nodeInfo.name = "/";
    					break;
    				case 39:
    					tmp.nodeInfo.name = "%";
    					break;
    				case 40:
    					tmp.nodeInfo.name = "==";
    					break;
    				case 41:
    					tmp.nodeInfo.name = "!=";
    					break;
    				case 42:
    					tmp.nodeInfo.name = ">=";
    					break;
    				case 43:
    					tmp.nodeInfo.name = "<=";
    					break;
    				case 44:
    					tmp.nodeInfo.name = ">";
    					break;;
    				case 45:
    					tmp.nodeInfo.name = "<";
    					break;
    				case 46:
    					break;
    				case 47:
    					backpatch(right_tmp[3].nodeInfo.truelist,right_tmp[1].nodeInfo.quad);
    					tmp.nodeInfo.nextlist = merge(right_tmp[3].nodeInfo.falselist,right_tmp[0].nodeInfo.nextlist);
    					break;
    				case 48:
    					backpatch(right_tmp[7].nodeInfo.truelist,right_tmp[5].nodeInfo.quad);
    					backpatch(right_tmp[7].nodeInfo.falselist,right_tmp[1].nodeInfo.quad);
    					tmp.nodeInfo.nextlist = merge(right_tmp[4].nodeInfo.nextlist,merge(right_tmp[3].nodeInfo.nextlist,right_tmp[0].nodeInfo.nextlist));
    					break;
    				case 49:
    					break;
    				case 50:
    					backpatch(right_tmp[0].nodeInfo.nextlist,right_tmp[5].nodeInfo.quad);
    					backpatch(right_tmp[3].nodeInfo.truelist,right_tmp[1].nodeInfo.quad);
    					tmp.nodeInfo.nextlist = right_tmp[3].nodeInfo.falselist;
    					gen_code("goto "+std::to_string(right_tmp[5].nodeInfo.quad));
    					break;
    				case 51:
    					backpatch(right_tmp[0].nodeInfo.nextlist,right_tmp[6].nodeInfo.quad);
    					backpatch(right_tmp[5].nodeInfo.truelist,right_tmp[1].nodeInfo.quad);
    					tmp.nodeInfo.nextlist = right_tmp[5].nodeInfo.falselist;
    					for(auto cd : right_tmp[3].nodeInfo.code)
    						gen_code(cd);
    					gen_code("goto "+std::to_string(right_tmp[6].nodeInfo.quad));
    					break;
    				case 52:
    					tmp.nodeInfo = right_tmp[0].nodeInfo;
    					break;
    				case 53:
    					break;
    				case 54:
    					tmp.nodeInfo = right_tmp[0].nodeInfo;
    					break;
    				case 55:
    					break;
    				case 56:
    					tmp.nodeInfo = right_tmp[1].nodeInfo;
    					break;
    				case 57:
    					tmp.nodeInfo = right_tmp[3].nodeInfo;
    					//符号表中不存在,则创建
    					sym_tmp = hasKey(tmp.nodeInfo.name);
    
    					if(sym_tmp.t == ARRAY) {
    						std::string code_tmp;
    						code_tmp = "t" + std::to_string(tmp_count++) + " = " + std::to_string(sym_tmp.offset);
    						gen_code(code_tmp);
    						tmp.nodeInfo.other = std::to_string(right_tmp[1].nodeInfo.val);
    						tmp.nodeInfo.name = "t"+std::to_string(tmp_count-1);
    					}
    					else
    						;//普通变量引用数组
    					break;
    				case 58:
    					tmp.nodeInfo = right_tmp[3].nodeInfo;
    					sym_tmp = hasKey(tmp.nodeInfo.name);
    					if(sym_tmp.t == ARRAY){
    						Symbol sym_tmpp = hasKey(right_tmp[1].nodeInfo.name);
    						if(sym_tmpp.n >= 0) {
    							std::string code_tmp;
    							code_tmp = "t" + std::to_string(tmp_count++) + " = " + std::to_string(sym_tmp.offset);
    							gen_code(code_tmp);
    							tmp.nodeInfo.other = right_tmp[1].nodeInfo.name;
    							tmp.nodeInfo.name = "t"+std::to_string(tmp_count-1);
    						}
    						else
    							;//error
    					} else
    						;//声明数组用变量
    					break;
    				case 59:
    					tmp.nodeInfo.name = right_tmp[0].nodeInfo.name;
    					break;
    				case 60:
    					tmp.nodeInfo.quad = code.size();
    					break;
    				case 61:
    					tmp.nodeInfo.nextlist.insert(code.size());
    					gen_code("goto ");
    					break;
    				case 62:
    					//计算左右边的值
    					if(right_tmp[2].nodeInfo.other == "") 
    						left = right_tmp[2].nodeInfo.name;
    					else 
    						left = right_tmp[2].nodeInfo.name +"["+right_tmp[2].nodeInfo.other+"]";
    
    					right = right_tmp[0].nodeInfo.name;
    
    					tmp.nodeInfo.code.push_back(left+" = "+right);
    
    					break;
    				case 63:
    					tmp.nodeInfo = right_tmp[1].nodeInfo;
    					if(tmp.nodeInfo.other != "") {
    						std::string code_tmp;
    						code_tmp="t"+std::to_string(tmp_count++)+" = ";
    						left = tmp.nodeInfo.name+"["+tmp.nodeInfo.other+"]";
    						code_tmp += left;
    						tmp.nodeInfo.code.push_back(code_tmp);
    						right = "t"+std::to_string(tmp_count-1);
    						code_tmp = right +" = "+right+" "+right_tmp[0].nodeInfo.name + " 1";
    						tmp.nodeInfo.code.push_back(code_tmp);
    						tmp.nodeInfo.code.push_back(left+" = "+right);
    					} else 
    						tmp.nodeInfo.code.push_back(tmp.nodeInfo.name + " = "+tmp.nodeInfo.name+" "+right_tmp[0].nodeInfo.name+" 1");
    					break;
    				case 64:
    					tmp.nodeInfo = right_tmp[0].nodeInfo;
    					if(tmp.nodeInfo.other != "") {
    						std::string code_tmp;
    						code_tmp="t"+std::to_string(tmp_count++)+" = ";
    						left = tmp.nodeInfo.name+"["+tmp.nodeInfo.other+"]";
    						code_tmp += left;
    						tmp.nodeInfo.code.push_back(code_tmp);
    						right = "t"+std::to_string(tmp_count-1);
    						code_tmp = right +" = "+right+" "+right_tmp[1].nodeInfo.name + " 1";
    						tmp.nodeInfo.code.push_back(code_tmp);
    						tmp.nodeInfo.code.push_back(left+" = "+right);
    					} else 
    						tmp.nodeInfo.code.push_back(tmp.nodeInfo.name + " = "+tmp.nodeInfo.name+" "+right_tmp[1].nodeInfo.name+" 1");
    					break;
    				case 65:
    					break;
    				case 66:
    					break;
    				default: 
    					;
    			}
    			stk.push(tmp);
    		} else 
    			return true;
    		if(in.eof()) {
    			w.setLexeme("#");
    			w.setTag(0);
    		}
    	}
    	return false;
    }
    
    
    
    void Parser::print(std::ofstream &out) {
    	for(int i = 0; i < static_cast<int>(code.size());i++) {
    		out << i << " : " << code[i] << std::endl;
    	}	
    }
    
    
    void Parser::run() {
    
    	std::ifstream gin("grammer.txt");
    	std::ifstream lin("/root/C++/Compiler/test.txt");
    	std::ifstream action_in("action.out");
    	std::ifstream goto_in("goto.out");
    	std::ofstream parser_out("/root/C++/Compiler/parser.out");
    
    
    
    
    	getTable(action_in,goto_in);
    	get_grammer(gin);	
    	if(stack_parser(lin))
    		print(parser_out);
    	gin.close();
    	lin.close();
    	action_in.close();
    	goto_in.close();
    
    }
    
  • 相关阅读:
    可伸缩的菜单
    Microsoft.AspNet.Identity: UserID用整型数据表示, 而不是GUID
    需要了解的技术
    微信公众号开发一些链接
    SQL Server Connection Strings for ASP.NET Web Applications https://msdn.microsoft.com/en-us/library/jj653752.aspx
    ASP.NET MVC 4 (三) 过滤器
    quick Cocos 2dx 学习网站
    获取SQL server 中的表和说明
    在 fragment 里面调用 findViewById
    gravity 和 layout_gravity
  • 原文地址:https://www.cnblogs.com/vachester/p/6885562.html
Copyright © 2011-2022 走看看