编译原理之算符优先分析语法程序
声明:本程序只是笔者的第一次构造结果,存在非常多需要改进的地方,笔者会在github上持续重构,大家可以在下面地址中找到最新的重构代码。https://github.com/Ggmatch/The-principle-to-compile
体验算法优先分析法能达到的效果
算符优先分析法只考虑算符(广义为终结符)之间的优先关系,例如若有文法G为:
(1)E->E+E
(2)E->E*E
(3)E->i
对输入串i1+i2*i3的规约过程如表所示:
步骤 | 栈 S | 当前输入符 | 输入串剩余部分 | 动作 |
---|---|---|---|---|
(1) | # | i1 | +i2+i3# | 移进 |
(2) | #i1 | + | i2*i3# | 规约(3) |
(3) | #E | + | i2*i3# | 移进 |
(4) | #E+ | i2 | *i3# | 移进 |
(5) | #E+i2 | * | i3# | 规约(3) |
(6) | #E+E | * | i3# | 移进 |
(7) | #E+E* | i3 | # | 移进 |
(8) | #E+E*i3 | # | 规约(3) | |
(9) | #E+E*E | # | 规约(2) | |
(10) | #E+E*E | # | 规约(1) | |
(11) | #E | # | 接受 |
算符优先文法的定义
定义1:设有一文法G,如果G中没有形如A->…BC…的产生式,其中B和C为非终结符,则称G为算符文法也称OG文法。
定义2:设有一不含空产生式的算符文法G,如果对任意两个终结符对a,b之间至多只有优先级高于、低于和等于三种关系中的一种成立,则称G是一个算符优先文法。
算符优先关系表的构造
- 首先定义两个集合FIRSTVT(B)和LASTVT(B)
FIRSTVT(B)={b|B+=>b…或B+=>Cb…},其中…表示V*中的符号串。
LASTVT(B)={a|B+=>…a或B+=>…aC} - 计算每个终结符之间的优先关系
对于A->…ab… A->…aBb… 则有a 优先级等于 b 成立。
对于A->…aB… 对于每一个b属于FIRSTVT(B) 有a优先级低于b成立。
对于A->…Bb… 对于每一个a属于LASTVT(B) 有a优先级高于b成立。 - 构造优先关系矩阵
利用每个终结符之间的优先关系即可构造出来。
构造FIRSTVT集和LASTVT集的算法
定义一个布尔数组F[m,n](m为非终结符个数,n为终结符个数)和一个后进先出栈STACK。将所有的非终结符排序,用iA表示非终结符A的序号,再将所有的终结符排序,用iA表示非终结符A的序号,再将所有的终结符排序,用ja表示终结符a的序号。算法的目的是要使数组每一个元素最终取值满足:F[iA,ja]的值为真,当前仅当a属于FIRSTVT(A)。
构建FIRSTVT集的伪代码(LASTVT集类似)如下:
PROCEDURE INSERT(A,a);IF NOT F[iA,ja] THENBEGINF[iA,ja] := TRUEPUSH[A,a] ONTO STACKEND
/*主程序*/
BEGIN (MAIN)FOR i从1到m,j从1到nDO F[iA,ja] := FALSE;FOR 每个形如 A->a... 或 A->Ba...的产生式DO INSERT(A,a)WHILE STACK非空 DOBEGIN 把STACK的顶项记为(B,a)弹出去FOR 每个形如 A->B...的产生式弹出去INSERT(A,a)ENDEND (MAIN)
构造优先关系表的算法
FPR 每个产生式 A->X1X2...Xn DOFOR i := 1 TO n-1 DOBEGIN IF Xi和X(i+1)均为终结符THEN 置 Xi优先级等于X(i+1);IF i<=n-2 且Xi和X(i+2)都为终结符,但X(i+1)为非终结符THEN 置 Xi优先级等于X(i+2);IF Xi为终结符而X(i+1)为非终结符THEN FOR FIRSTVT(X(i+1))中的每个b DO 置 Xi优先级低于b;IF Xi为非终结符而X(i+1)为终结符THEN FOR LASTVT(Xi)中的每个a DO 置 a优先级高于X(i+1);END
优先函数f和g的构建
- 对于每个终结符a属于VT(包括#号在内)令f(a) = g(a) = 1(也可以是其他整数)。
对每一终结符对逐一比较
如果a优先级高于b,而f(a)<=g(b)则令f(a)=g(b)+1。
如果a优先级低于b,而f(a)>=g(b)则令g(b)=f(a)+1。
如果a优先级等于b,而f(a)!=g(b)则令min{f(a),g(b)}=max{f(a),g(b)}。
重复第二步直到过程收敛。算符优先分析算法
最左素短语
设有文法G[S],其句型的素短语是一个短语,它至少包含一个终结符,并除自身外不包含其他素短语,最左边的素短语称最左素短语。形如NiaiN(i+1)a(i+1)…ajN(j+)满足:
(1)a(i-1)优先级低于ai
(2)ai/a(i+1)/…/aj优先级相同
(3)aj优先级高于a(j+1)- 算法
k := 1; //栈的深度
S[k] := '#';
REPEAT把下一个输入符号读进a中;IF S[k]属于VT THEN j:=k ELSE j:=k-1;WHILE S[j]优先级高于a DOBEGINREPEATQ:=S[j];IF S[j-1]属于VT THEN j:=j-1 ELSE j:=j-2UNTIL S[j]优先级低于Q;把S[j+1]...S[k]规约为某个N;k:=j+1;S[k]:=N;END OF WHILE;IF S[j]优先级低于a OR S[j]优先级等于a THENBEGIN k:=k+1; S[k]:=a ENDELSE ERROR; /*调用出错诊察程序*/
UNTIL a='#'
完整代码
#include <iostream>
#include <string>
#include <fstream>
#include <stack>
#include <algorithm>
#include <sstream>
using namespace std;#define P_Amount 20
#define RMAX 20
#define CMAX 20
#define NT_AMOUNT 20
#define T_AMOUNT 20
//文法结构体
class G
{
public:string T; //终结符的集合string NT; //非终结符的集合string begin; //文法开始符号string P[P_Amount]; //产生式的集合int P_Length; //产生式的个数bool FIRSTVT[RMAX][CMAX]; //非终结符的FIRSTVT集合bool LASTVT[RMAX][CMAX]; //非终结符的FIRSTVT集合int PRIORITY_TABLE[NT_AMOUNT][NT_AMOUNT]; //优先表,表内元素只有4种类型的值,-1代表行<列,1代表行>列,0代表行=列,2代表出错int FR; //记录F的有效行数int FC; //记录F的有效列数int f[T_AMOUNT]; //符号栈内的优先级,f优先函数int g[T_AMOUNT]; //符号栈外的优先级,g优先函数G(){//初始化T = "";NT = "";begin = "";P_Length = 0;for (int i = 0; i < P_Amount; i++)P[i] = "";for (int i = 0; i < RMAX; i++){for (int j = 0; j < CMAX; j++){FIRSTVT[i][j] = false;}}for (int i = 0; i < RMAX; i++){for (int j = 0; j < CMAX; j++){LASTVT[i][j] = false;}}for (int i = 0; i < NT_AMOUNT; i++){for (int j = 0; j < NT_AMOUNT; j++){PRIORITY_TABLE[i][j] = 2; //默认为出错状态}}//初始状态下f和g内的元素都置为1for (int i = 0; i < T_AMOUNT; i++){f[i] = 1; g[i] = 1;}}~G(){}
};class Temp
{
public:char NT;char T;
};void findor(string &str, char c, int *pos,int cap,int &len) //在str中寻找出所有|的位置,pos记录其位置,cap为位置数组的容量,len为pos的有效长度
{for (int i = 0; i < cap; i++){//初始化pos[i] = 0;}int temp=0;for (int i = 0; i < str.length(); i++){int j=str.find(c, temp);if (j == -1){break;}pos[i] = j;len++;temp = j+1;}
}
//根据产生式得到终结符集合与非终结符集合
void getSyms(string *strs, int len, string &T,string &NT) //strs为产生式集合,len为产生式个数
{string strTemp=""; for (int i = 0; i < len; i++){if (strTemp.find(strs[i][0]) != -1) //若是找到已存在的符号,跳过continue;strTemp += strs[i][0];}NT = strTemp;for (int i = 0; i < len; i++){for (int j = 3; j < strs[i].length(); j++){if (strs[i][j] >= 'A' && strs[i][j] <= 'Z') //非终结符{if (NT.find(strs[i][j]) != 0) //该符号已存在,跳过continue;NT += strs[i][j];}else //终结符,可能是算符或i{if (T.find(strs[i][j]) != -1) //该符号已存在,跳过continue;T += strs[i][j];}}}
}void zero(string *strs, int &len) //strs为输入的文法表达式,len为表达式的个数
{string StrTemp[P_Amount];int lens,count=0; //count记录新生成表达式的个数,lens记录每个int pos[10]; for (int i = 0; i < len; i++) //将所有产生式按|隔开,生成多个产生式,存储到StrTemp中{lens = 0;findor(strs[i], '|', pos, 10, lens); //寻找这条表达式中所有“|”的位置,存放到pos中,10为其最大容量,lens为“|”个数for (int z = 0; z < lens+1; z++) //将一个产生式按|隔开,生成多个产生式,存储到StrTemp中,(len+1)代表要生成的表达式个数{if (z == 0){StrTemp[z + count] = strs[i].substr(0, 3) + strs[i].substr(3, (pos[z]-2 - 1));continue;}StrTemp[z + count] = strs[i].substr(0, 3) + strs[i].substr(pos[z - 1]+1, (pos[z] - pos[z - 1] - 1));if (z == lens){StrTemp[count + lens] = strs[i].substr(0, 3) + strs[i].substr(pos[lens - 1]+1, (strs[i].length() - pos[lens - 1] - 1));}}count += lens + 1;}//把StrTemp赋给strslen = count;for (int i = 0; i < count; i++){strs[i] = StrTemp[i];}
}
void test1(G &gm); //声明
void first(G &grammer,string strs[],int len) //grammer为需要构造的文法对象,strs为经过zero后的文法表达式,len为表达式的个数
{grammer.begin = strs[0][0]; //开始符号grammer.P_Length = len; //产生式的个数for (int i = 0; i < len; i++) //得到产生式{grammer.P[i] = strs[i];}getSyms(strs, len, grammer.T, grammer.NT); //从产生式中得到非终结符集合与终结符集合}void test1(G &gm) //检测函数,这里检查文法数据结构的正确性
{cout << endl << "检测:" << endl;cout << "文法的开始符号:" << gm.begin << endl;cout << "文法的非终结符集合:" << gm.NT << endl;cout << "文法的终结符集合:" << gm.T << endl;cout << "文法的产生式:" << endl;for (int i = 0; i < gm.P_Length; i++){cout << gm.P[i] << endl;}cout << endl;
}void insert(G &gm, stack<Temp> &stck, char A, char a) //
{int iA = gm.NT.find(A);int ia = gm.T.find(a);Temp tmp;tmp.NT = A;tmp.T = a;if (!gm.FIRSTVT[iA][ia]){gm.FIRSTVT[iA][ia] = true;stck.push(tmp);}
}void insert1(G &gm, stack<Temp> &stck, char A, char a) //
{int iA = gm.NT.find(A);int ia = gm.T.find(a);Temp tmp;tmp.NT = A;tmp.T = a;if (!gm.LASTVT[iA][ia]){gm.LASTVT[iA][ia] = true;stck.push(tmp);}
}void test2(G &gm); //测试gm的FIRSTVT和LASTVT集合void second(G &gm) //gm为文法的四元组
{gm.FR = gm.NT.length();gm.FC = gm.T.length();stack<Temp> stck; Temp tmpBa;//得到G的FIRSTVT集合for (int i = 0; i < gm.P_Length; i++){if (gm.P[i].length() - 4 == 0) //产生式右边只有一个符号{if (gm.P[i][3] < 'A' || gm.P[i][3]>'Z') //这个符号是终结符{insert(gm, stck, gm.P[i][0], gm.P[i][3]);}}else if (gm.P[i][3] >= 'A' && gm.P[i][3] <= 'Z') //右部的第一个符号为非终结符,那么加入右部的第二个符号{insert(gm, stck, gm.P[i][0], gm.P[i][4]);}else //右部的第一个符号为终结符{insert(gm, stck, gm.P[i][0], gm.P[i][3]);}}while (!stck.empty()){tmpBa = stck.top();stck.pop();for (int i = 0; i < gm.P_Length; i++){if (gm.P[i][0] != tmpBa.NT&&gm.P[i][3] == tmpBa.NT) //找到形如A->B···的产生式,FIRSTVT(B)必然FIRSTVT(A){insert(gm, stck, gm.P[i][0], tmpBa.T);}}}//得到G的LASTVT集合for (int i = 0; i < gm.P_Length; i++){if (gm.P[i].length() - 4 == 0) //产生式右边只有一个符号{if (gm.P[i][3] < 'A' || gm.P[i][3]>'Z') //这个符号是终结符{insert1(gm, stck, gm.P[i][0], gm.P[i][3]);}}else if (gm.P[i][3] >= 'A' && gm.P[i][3] <= 'Z') //右部的最后一个符号为非终结符,那么加入右部的倒数第二个符号{insert1(gm, stck, gm.P[i][0], gm.P[i][gm.P[i].length()-2]);}else //右部的最后一个符号为终结符{insert1(gm, stck, gm.P[i][0], gm.P[i][gm.P[i].length()-1]);}}while (!stck.empty()){tmpBa = stck.top();stck.pop();for (int i = 0; i < gm.P_Length; i++){if (gm.P[i][0] != tmpBa.NT&&gm.P[i][gm.P[i].length()-1] == tmpBa.NT) //找到形如A->···B的产生式,LASTVT(B)必然LASTVT(A){insert1(gm, stck, gm.P[i][0], tmpBa.T);}}}}void test2(G &gm)
{cout << "G的FIRSTVT集合:" << endl;cout << "\t";for (int i = 0; i < gm.T.length(); i++){if (i == gm.T.length() - 1){cout << gm.T[i] << endl;break;}cout << gm.T[i] << "\t";}for (int i = 0; i < gm.FR; i++){cout << gm.NT[i] << "\t";for (int j = 0; j < gm.FC; j++){if (j == gm.FC - 1){cout << gm.FIRSTVT[i][j] << endl;break;}cout << gm.FIRSTVT[i][j] << "\t";}}cout << "G的LASTVT集合:" << endl;cout << "\t";for (int i = 0; i < gm.T.length(); i++){if (i == gm.T.length() - 1){cout << gm.T[i] << endl;break;}cout << gm.T[i] << "\t";}for (int i = 0; i < gm.FR; i++){cout << gm.NT[i] << "\t";for (int j = 0; j < gm.FC; j++){if (j == gm.FC - 1){cout << gm.LASTVT[i][j] << endl;break;}cout << gm.LASTVT[i][j] << "\t";}}cout << endl;
}void test3(G &gm); //测试third函数void third(G &gm) //gm为文法的数据结构
{for (int i = 0; i < gm.P_Length; i++){int lens = gm.P[i].length();for (int j = 3; j <= lens-2; j++){if ((gm.P[i][j]<'A'||gm.P[i][j]>'Z')&&(gm.P[i][j + 1]<'A'||gm.P[i][j + 1]>'Z')) //Xi和X(i+1)都为终结符{int temp = gm.T.find(gm.P[i][j]);int temp1 = gm.T.find(gm.P[i][j + 1]);gm.PRIORITY_TABLE[temp][temp1] = 0; //置为同等优先级}if (j <= (lens - 3) && (gm.P[i][j]<'A' || gm.P[i][j]>'Z') && (gm.P[i][j + 2]<'A' || gm.P[i][j + 2]>'Z') && (gm.P[i][j + 1] >= 'A'&&gm.P[i][j + 1] <= 'Z')) //Xi和X(i+2)为终结符,但X(i+1)为非终结符{int temp = gm.T.find(gm.P[i][j]);int temp1 = gm.T.find(gm.P[i][j + 2]);gm.PRIORITY_TABLE[temp][temp1] = 0; //置为同等优先级}if ((gm.P[i][j]<'A' || gm.P[i][j]>'Z') && (gm.P[i][j + 1] >= 'A' && gm.P[i][j + 1] <= 'Z')) //Xi为终结符而X(i+1)为非终结符{for (int z = 0; z < gm.T.length(); z++){if (gm.FIRSTVT[gm.NT.find(gm.P[i][j + 1])][z]){int temp = gm.T.find(gm.P[i][j]);int temp1 = z;gm.PRIORITY_TABLE[temp][temp1] = -1; //置于列优先于行}}}if ((gm.P[i][j+1]<'A' || gm.P[i][j+1]>'Z') && (gm.P[i][j] >= 'A' && gm.P[i][j] <= 'Z')) //X(i+1)为终结符而Xi为非终结符{for (int z = 0; z < gm.T.length(); z++){if (gm.LASTVT[gm.NT.find(gm.P[i][j])][z]){int temp = z;int temp1 = gm.T.find(gm.P[i][j+1]);gm.PRIORITY_TABLE[temp][temp1] = 1; //置于行优先于列}}}}}
}void test3(G &gm)
{cout << "G的优先分析表:" << endl;cout << "\t";for (int i = 0; i < gm.T.length(); i++){if (i == gm.T.length() - 1){cout << gm.T[i] << endl;break;}cout << gm.T[i] << "\t";}int lens = gm.T.length();for (int i = 0; i < lens; i++){cout << gm.T[i] << "\t";for (int j = 0; j < lens; j++){if (j == gm.T.length() - 1){cout << gm.PRIORITY_TABLE[i][j] << endl;break;}cout << gm.PRIORITY_TABLE[i][j] << "\t";}}
}void test4(G &gm); //检测优先函数f和gvoid four(G &gm) //构造优先函数f和g
{bool isChanged = true; //isChanged代表上一次迭代f和g函数有没有发生变化,有为trueint lens = gm.T.length();while (isChanged){isChanged = false;for (int i = 0; i < lens; i++){for (int j = 0; j < lens; j++){if (gm.PRIORITY_TABLE[i][j] == 2)continue;if (gm.PRIORITY_TABLE[i][j] == 1 && (gm.f[i] <= gm.g[j])){gm.f[i] = gm.g[j] + 1;isChanged = true;continue;}if (gm.PRIORITY_TABLE[i][j] == -1 && (gm.f[i] >= gm.g[j])){gm.g[j] = gm.f[i] + 1;isChanged = true;continue;}if (gm.PRIORITY_TABLE[i][j] == 0 && (gm.f[i] != gm.g[j])){if (gm.f[i] > gm.g[j]){gm.g[j] = gm.f[i];}else{gm.f[i] = gm.g[j];}isChanged = true;continue;}}}}
}void test4(G &gm) //检测four函数
{cout << "\t";for (int i = 0; i < gm.T.length(); i++){if (i == gm.T.length() - 1){cout << gm.T[i] << endl;break;}cout << gm.T[i] << "\t";}int lens = gm.T.length();cout << "f函数: ";for (int i = 0; i < lens; i++){if (i == lens - 1){cout << gm.f[i] << endl;break;}cout << gm.f[i] << "\t";}cout << "g函数: ";for (int i = 0; i < lens; i++){if (i == lens - 1){cout << gm.g[i] << endl;break;}cout << gm.g[i] << "\t";}
}void test5(bool b); //检测GuiYueQi是否识别出待规约串,b为true,则说明能识别bool GuiYueQi(G &gm, string str) //gm为文法的数据结构,str为待规约串,能识别为true,否则为false
{string strStck=""; //用string对象做符号栈,方便取值strStck.append("#"); //初始化int k = 0,num=0;int j; //j表示最左素短语的开头位置string a=""; //读入单元string Q = ""; //作为待审查的最左素短语的开头符号while (a!="#") //到了输入串的最后一个字符,跳出{a = str[num++]; //把下一个输入符号读进a中if (gm.T.find(strStck[k]) != gm.T.npos) //如果栈顶的符号为终结符{j = k;}else{j = k - 1;}while (gm.f[gm.T.find(strStck[j])] > gm.g[gm.T.find(a)]) //往下找,直到找到栈内符号优先级小于或等于当前输入符号的优先级{do{Q = strStck[j];if (gm.T.find(strStck[j - 1]) != gm.T.npos) //{j = j - 1;}else{j = j - 2;}} while (gm.f[gm.T.find(strStck[j])] >= gm.g[gm.T.find(Q)]);//把strStck[j+1]~strStck[k]规约为某个NstrStck.erase(strStck.begin() + j + 1, strStck.begin() + k); //把规约的符号退栈strStck.append("N"); //压入某个非终结符Nk = j + 1;break;}if (gm.f[gm.T.find(strStck[j])] <= gm.g[gm.T.find(a)]) {k = k + 1;strStck.append(a);}else{return false;}}return true; //
}void test5(bool b)
{if (b){cout << "acc!" << endl;}else{cout << "fail!" << endl;}
}int main()
{G gm;string str[10];int len; //输入的表达式个数int i = 0;//从文件读入文法ifstream ifile("wenfa.txt"); //打开wenfa.txt文件while (ifile){ifile >> str[i++]; //读入文法}ifile.close(); //关闭文件len = i - 1;zero(str, len); //依据‘|’,拆分表达式first(gm, str, len); //得到文法数据结构//test1(gm); //检查zero()、first()函数second(gm); //得到每个非终结符的FIRSTVT和LASTVT集合//test2(gm); //检测second函数third(gm); //得到优先分析表//test3(gm); //检测third函数four(gm); //得到优先函数f和g//test4(gm); //检测four函数string guiyuestr; //规约串ifile.open("guiyuechuan.txt"); stringstream io;io <<ifile.rdbuf();io >> guiyuestr;cout << "待规约串为:"<< guiyuestr <<endl;bool b = GuiYueQi(gm, guiyuestr); //规约器,规约strtest5(b); //检测待规约串被规约器处理后的结果return 0;
}
上述代码中wenfa.txt与guiyuechuan.txt的内容如下,自己构建,并放在项目里c++文件所在处,要符合相对路径。
wenfa.txt:
S->#E#
E->E+T
E->T
T->T*F
T->F
F->P!F|P
P->(E)
P->i
guiyuechuan.txt:
i+i#
本文理论部分引用于 《编译原理(第二版)——张素琴——清华大学出版社》
编译原理之算符优先分析语法程序相关推荐
- 编译原理 词法分析 算符优先分析法
编译原理 词法分析 算符优先分析法 实验目的 加深对语法分析器工作工程的理解,加强对算符优先分析法实现语法分析程序的掌握:能够采用一种编程语言实现简单的语法分析程序:能够使用自己辨析的分析程序对简单的 ...
- 编译原理:算符优先分析实验
什么是算符优先分析法 算符优先分析法是一种简单.直观的自下而上分析法 算符优先分析法就是仿照算术表达式的四则运算过程而设计的一种语法分析方法. 这种分析方法首先要规定运算符之间(确切地说终结符之间)的 ...
- 算符优先分析java程序_算符优先分析
1. 已知算符优先关系矩阵如下表: + * i ( ) # + > < < < > > * > > < < > > i > ...
- c++自底向上算符优先分析_PHP程序员从入门到佛系第十弹:PHP 运算符
PHP 运算符 本章节我们将讨论 PHP 中不同运算符的应用. 在 PHP 中,赋值运算符 = 用于给变量赋值. 在 PHP 中,算术运算符 + 用于把值加在一起. PHP 算术运算符 以下实例演示了 ...
- 算符优先文法编写java语法分析器,编译原理课程设计实验报告——基于算符优先分析方法的表达式语法分析器...
内容简介: 一.设计目的 了解用算符优先法对表达进行语法分析的方法,掌握自顶向下的预测语法分析程序的手工构造方法. 二.设计内容 对简单表达式文法构造算符优先分析器. 三.设计要求 1.对下列简单表达 ...
- 编译原理(五)自底向上分析之算符优先分析法
自底向上分析之算符优先分析法 说明:以老师PPT为标准,借鉴部分教材内容,AlvinZH学习笔记. 基本过程 1. 一般方法:采用自左向右地扫描和分析输入串,从输入符号串开始,通过反复查找当前句型的句 ...
- java编程实现算符优先分析法,编译原理实验三-算符优先分析法
编译原理实验3-算符优先分析法 #include #include #include #include #define SIZE 128 char priority[6][6]; //算符优先关系表数 ...
- 算符优先分析法代码 java_编译原理——算符优先分析文法(附源代码)
1 源代码2 模块一:3 /****************#include"firstVT_lastVT.h"************************/ 4 5 //程序 ...
- 编译原理系列之五 自底向上优先分析(2)-算符优先分析法
算符优先分析法 1.基本概念 算符文法(OG):文法G中没有形如A=>···BC···的产生式,其中B.C为非终结符,则G为算符文法(operator grammar). 也就是说产生式的右部不 ...
最新文章
- Linux下JDK环境的配置
- autoware定位:gnss定位与lidar定位(四)
- C# 功能完整的单表增删改查程序
- 【转】SQL Server 2005 数据类型和.Net数据类型的对应关系
- 面试题之--实现取余
- 2020笔记本性价比之王_什么笔记本性价比高?2020性价比最高的笔记本电脑
- java post返回xml数据类型_Java 通过HttpURLConnection Post方式提交xml,并从服务端返回数据...
- java foxmail 附件_使用java实现与foxmail邮箱的接受,发送,转发等功能!!求精啊...
- Directx11学习笔记【七】 游戏定时器的实现
- 漫画:寻找无序数组的第k大元素
- 最适合Java初学者练手的项目【JavaSE项目-图书管理系统】
- DM6437的中断和事件
- Machine Learning-L13-频繁模式挖掘
- c语言md5函数 linux,【转】MD5校验C语言实现源代码
- 优质的国外程序员网站
- java syslog-ng_syslog-ng详细安装配置
- EDI在物流行业中的应用
- 基于 Amazon CodeCommit 可规模化的敏捷开发实践
- css3D制作立体旋转小方块
- 后台收费通知单打印问题