解析四則表達(dá)式的編譯過(guò)程及生成匯編代碼
更新時(shí)間:2013年06月26日 15:05:28 作者:
本篇文章是對(duì)四則表達(dá)式的編譯過(guò)程及生成匯編代碼進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下
1、前序
這是編譯原理的實(shí)驗(yàn),自認(rèn)為是上大學(xué)以來(lái)做過(guò)的最難的一個(gè)實(shí)驗(yàn)。
實(shí)驗(yàn)用到的基礎(chǔ)知識(shí):C語(yǔ)言、數(shù)據(jù)結(jié)構(gòu)、匯編(只需簡(jiǎn)單的了解)。
開發(fā)工具:VC
2、問(wèn)題描述
編譯整數(shù)四則運(yùn)算表達(dá)式,將整數(shù)四則運(yùn)算表達(dá)式翻譯為匯編語(yǔ)言代碼。
消除左遞歸后的文法:
E→TE'
E'→+TE' |ε
T→FT'
T'→*FT' |ε
F→(E) | i
消除左遞歸后的翻譯模式:
E ::= T {E'.i:=T.nptr}
E' {E.nptr:=E'.s}
E'::= + T {E'1.i:=mknode(‘+',E'.i,T.nptr)}
E'1 {E'.s:=E1.s}
E'::= - T {E'1.i:=mknode(‘-',E'.i,T.nptr)}
E'1 {E'.s:=E1.s}
E'::= ε {E'.s:= E'.i}
T ::= F {T'.i:=F.nptr}
T' {T.nptr:=T'.s}
T'::= * F {T'1.i:=mknode(‘*',T'.i,F.nptr)}
T'1 {T'.s:=T1.s}
T'::= / F {T'1.i:=mknode(‘/',T'.i,F.nptr)}
T'1 {T'.s:=T1.s}
T' ::= ε {T'.s:= T'.i}
F ::= (E) {F.nptr:=E.nptr}
F ::= num {F.nptr:=mkleaf(num,num.val)}
3、全局定義
test.c文件
#ifndef TEST_C
#define TEST_C
/**
* 全局變量和全局函數(shù)文件
**/
#include<stdio.h>
#include<ctype.h>
#include<string.h>
#include<stdlib.h>
/************************* 以下是全局變量(函數(shù))的定義 *******************/
//輸入的表達(dá)式最大長(zhǎng)度,可以看做是緩沖區(qū)的長(zhǎng)度
#define MAX_EXPRESSION_LENGTH 50
//存放輸入的表達(dá)式
char expression[MAX_EXPRESSION_LENGTH];
//表達(dá)式字符數(shù)組的下標(biāo)
int expression_index=0;
//存放一個(gè)單詞符號(hào)
char strToken[MAX_EXPRESSION_LENGTH/2];
//判斷是否是數(shù)字
int isNum(char * strToken)
{
int i=0;
while(strToken[i]){
if(!isdigit(strToken[i]))
break;
i++;
}
return strToken[i]==0;
}
//錯(cuò)誤處理程序
void error(char* errerMessage)
{
printf("\nERROR:%s\n",errerMessage);
exit(0);
}
/************************* 以上是全局變量(函數(shù))的定義 ******************/
#endif
4、詞法分析
詞法分析的要求是:接受一個(gè)表達(dá)式,輸出該表達(dá)式中的各類單詞符號(hào)
一般有兩種方法來(lái)進(jìn)行詞法分析,一種是用狀態(tài)圖來(lái)實(shí)現(xiàn),一種是用狀態(tài)轉(zhuǎn)換表。下面采用狀態(tài)圖實(shí)現(xiàn)
首先定義單詞符號(hào)的種類和所屬類型
typedef enum Symbol { ERR = -1, END, NUM, PLUS, MINUS, TIMES, SLASH, LPAREN, RPAREN } Symbol;
然后轉(zhuǎn)態(tài)轉(zhuǎn)換圖如下所示:
#ifndef TEST1_C
#define TEST1_C
/**
* 采用狀態(tài)圖進(jìn)行詞法分析以及測(cè)試詞法分析
*
**/
#include"test.c"
//枚舉類型
typedef enum Symbol { ERR = -1, END, NUM, PLUS, MINUS, TIMES,
SLASH, LPAREN, RPAREN } Symbol;
//獲取一個(gè)單詞符號(hào),該單詞符號(hào)存放在strToken中。返回該單詞符號(hào)的枚舉類型
Symbol getToken();
//根據(jù)傳入的枚舉類型輸出對(duì)應(yīng)的單詞符號(hào)
void printToken(Symbol i);
//測(cè)試詞法分析
void testLexAnalyse();
//獲取一個(gè)單詞符號(hào),該單詞符號(hào)存放在strToken中。返回該單詞符號(hào)的枚舉類型
Symbol getToken()
{
char ch;
int state=0;//每次都是從狀態(tài)0開始
int j=0;
//表達(dá)式遍歷完成,單詞符號(hào)為'#'
if(expression[expression_index]=='\0'){
strToken[0]='#';
strToken[1]='\0';
return END;
}
while(1){
switch(state){
case 0:
//讀取一個(gè)字符
ch=strToken[j++]=expression[expression_index++];
if(isspace(ch)){
j--;//注意退格
state=0;
}
else if(isdigit(ch))
state=1;
else if(ch=='+')
state=2;
else if(ch=='-')
state=3;
else if(ch=='*')
state=4;
else if(ch=='/')
state=5;
else if(ch=='(')
state=6;
else if(ch==')')
state=7;
else
return ERR;
break;
case 1:
ch=strToken[j++]=expression[expression_index++];
if(isdigit(ch))
state=1;
else{
expression_index--;
strToken[--j]=0;
return NUM;
}
break;
case 2:
strToken[j]=0;
return PLUS;
case 3:
strToken[j]=0;
return MINUS;
case 4:
strToken[j]=0;
return TIMES;
case 5:
strToken[j]=0;
return SLASH;
case 6:
strToken[j]=0;
return LPAREN;
case 7:
strToken[j]=0;
return RPAREN;
}
}
}
//根據(jù)傳入的枚舉類型輸出對(duì)應(yīng)的單詞符號(hào)
void printToken(Symbol i){
switch(i){
case -1:printf("ERR\n");break;
case 0:printf("END\n");break;
case 1:printf("NUM %s\n",strToken);break;
case 2:printf("PLUS %s\n",strToken);break;
case 3:printf("MINUS %s\n",strToken);break;
case 4:printf("TIMES %s\n",strToken);break;
case 5:printf("SLASH %s\n",strToken);break;
case 6:printf("LPAREN %s\n",strToken);break;
case 7:printf("RPAREN %s\n",strToken);break;
}
}
//測(cè)試詞法分析
void testLexAnalyse()
{
Symbol tokenStyle;//單詞符號(hào)類型
expression_index=0;
puts("\n詞法分析結(jié)果如下:");
while(1){
tokenStyle=getToken();
printToken(tokenStyle);
if(tokenStyle == ERR){
error("詞法分析錯(cuò)誤!");
}
if(tokenStyle == END){
break;
}
}
}
//主函數(shù)
int main()
{
gets(expression);
testLexAnalyse();
return 0;
}
#endif
運(yùn)行結(jié)果

5、語(yǔ)法分析
要求:接受一個(gè)表達(dá)式,分析該表達(dá)式,并根據(jù)輸入正確與否給出相應(yīng)信息
主要是根據(jù)無(wú)左遞歸文法寫出對(duì)應(yīng)的各個(gè)子程序
test2.c
#ifndef TEST_2
#define TEST_2
/**
* 語(yǔ)法分析以及測(cè)試語(yǔ)法分析
**/
#include"test1.c"
/*
消除左遞歸后的文法:
E→TE'
E'→+TE' |ε
T→FT'
T'→*FT' |ε
F→(E) | i
*/
//每個(gè)非終結(jié)符有對(duì)應(yīng)的子程序函數(shù)聲明
void E();
void E1();
void T();
void T1();
void F();
//測(cè)試語(yǔ)法分析
void testSyntaxAnalyse();
//每個(gè)非終結(jié)符有對(duì)應(yīng)的子程序
void E()
{
T();
E1();
}
void E1()
{
if(strcmp(strToken,"+")==0 || strcmp(strToken,"-")==0){
getToken();
T();
E1();
}
//Follow(E1)={#,)}
else if(strcmp(strToken,"#")!=0 && strcmp(strToken,")")!=0){
error("語(yǔ)法分析錯(cuò)誤!");
}
}
void T()
{
F();
T1();
}
void T1()
{
if(strcmp(strToken,"*")==0 || strcmp(strToken,"/")==0){
getToken();
F();
T1();
}
//Follow(T1)={+,#,)},如果考慮-號(hào)的話要加上-號(hào)
else if(strcmp(strToken,"-")!=0 &&strcmp(strToken,"+")!=0 && strcmp(strToken,"#")!=0 && strcmp(strToken,")")!=0){
error("語(yǔ)法分析錯(cuò)誤!");
}
}
void F()
{
if(isNum(strToken)){
getToken();
}
else{
if(strcmp(strToken,"(")==0){
getToken();
E();
if(strcmp(strToken,")")==0)
getToken();
else
error("語(yǔ)法分析錯(cuò)誤!");
}
else
error("語(yǔ)法分析錯(cuò)誤!");
}
}
//測(cè)試語(yǔ)法分析
void testSyntaxAnalyse()
{
expression_index=0;
getToken();
E();
puts("\n語(yǔ)法分析結(jié)果如下:");
if(strcmp(strToken,"#")!=0)
error("語(yǔ)法分析錯(cuò)誤!");
else{
puts("語(yǔ)法分析正確!");
}
}
//主函數(shù)
int main()
{
gets(expression);
testLexAnalyse();
testSyntaxAnalyse();
return 0;
}
#endif
運(yùn)行時(shí)要?jiǎng)h掉test1.c中的主函數(shù),運(yùn)行結(jié)果

6、語(yǔ)義分析
要求:需要實(shí)現(xiàn)的語(yǔ)義分析程序的功能是,接受一個(gè)表達(dá)式,分析該表達(dá)式,并在分析的過(guò)程中建立該表達(dá)式的抽象語(yǔ)法樹。由于四則運(yùn)算表達(dá)式的抽象語(yǔ)法樹可基本上看作是二叉樹,因此中序遍歷序列應(yīng)該和輸入的表達(dá)式一樣——除了沒(méi)有括號(hào)之外??奢敵鲋行虮闅v序列檢測(cè)程序功能是否正確。如果每個(gè)分支節(jié)點(diǎn)用一個(gè)臨時(shí)變量標(biāo)記,則對(duì)四則運(yùn)算表達(dá)式的抽象語(yǔ)法樹進(jìn)行后序遍歷,可以得到輸入表達(dá)式所對(duì)應(yīng)的四元式序列
test3.c文件
#ifndef TEST3_C
#define TEST3_C
/**
* 語(yǔ)義分析以及測(cè)試語(yǔ)義分析
* 其實(shí)這個(gè)實(shí)驗(yàn)是在test2的代碼上進(jìn)行修改
**/
#include"test1.c"
/*
消除左遞歸的翻譯模式:
E ::= T {E'.i:=T.nptr}
E' {E.nptr:=E'.s}
E'::= + T {E'1.i:=mknode('+',E'.i,T.nptr)}
E'1 {E'.s:=E1.s}
E'::= - T {E'1.i:=mknode('-',E'.i,T.nptr)}
E'1 {E'.s:=E1.s}
E'::= ε {E'.s:= E'.i}
T ::= F {T'.i:=F.nptr}
T' {T.nptr:=T'.s}
T'::= * F {T'1.i:=mknode('*',T'.i,F.nptr)}
T'1 {T'.s:=T1.s}
T'::= / F {T'1.i:=mknode('/',T'.i,F.nptr)}
T'1 {T'.s:=T1.s}
T' ::= ε {T'.s:= T'.i}
F ::= (E) {F.nptr:=E.nptr}
F ::= num {F.nptr:=mkleaf(num,num.val)}
*/
#define MAX_LENGTH 20 //四元式中操作數(shù)的最大長(zhǎng)度
typedef int ValType;
//結(jié)點(diǎn)類型
typedef struct ASTNode {
Symbol sym;//類型
ValType val;//值
struct ASTNode * left, *right;//左、右孩子
}ASTNode, *AST;
//四元式類型定義如下
typedef struct Quaternion{
char op;
char arg1[MAX_LENGTH];
char arg2[MAX_LENGTH];
char result[MAX_LENGTH];
}Quaternion;
//四元式數(shù)組,存放產(chǎn)生的四元式
Quaternion quaternion[MAX_LENGTH*2];
//統(tǒng)計(jì)四元式的個(gè)數(shù)
int count=0;
//后序遍歷抽象語(yǔ)法樹時(shí)存放操作數(shù)和臨時(shí)變量,這里當(dāng)作一個(gè)棧來(lái)使用
char stack[MAX_LENGTH*2][MAX_LENGTH];
//stack棧的下標(biāo)
int index=0;
//內(nèi)存中臨時(shí)數(shù)據(jù)存儲(chǔ)地址的偏移量
int t=-4;
//函數(shù)聲明
ASTNode* E();
ASTNode* E1(ASTNode* E1_i);
ASTNode* T();
ASTNode* T1(ASTNode* T1_i);
ASTNode* F();
void error(char* errerMessage);
ASTNode *mknode(Symbol op, ASTNode *left, ASTNode *right);
ASTNode *mkleaf(Symbol sym, ValType val);
void yuyi_analyse();
void print_node(ASTNode *root);
void middle_list(ASTNode *root);
void last_list(ASTNode *root);
//測(cè)試語(yǔ)義分析
void testYuyiAnalyse();
//創(chuàng)建運(yùn)算符結(jié)點(diǎn)
ASTNode *mknode(Symbol op, ASTNode *left, ASTNode *right);
//創(chuàng)建操作數(shù)結(jié)點(diǎn)
ASTNode *mkleaf(Symbol sym, ValType val);
//輸出結(jié)點(diǎn)
void printNode(ASTNode *root);
//中序遍歷二叉樹
void middle_list(ASTNode *root);
//后序遍歷二叉樹
void last_list(ASTNode *root);
/*
E ::= T {E'.i:=T.nptr}
E' {E.nptr:=E'.s}
*/
//為右邊的每個(gè)非終結(jié)符定義一個(gè)屬性,返回E的綜合屬性
ASTNode* E()
{
ASTNode * E_nptr;
ASTNode * E1_i;
E1_i=T();
E_nptr=E1(E1_i);
return E_nptr;
}
/*
E'::= + T {E'1.i:=mknode('+',E'.i,T.nptr)}
E'1 {E'.s:=E1.s}
E'::= - T {E'1.i:=mknode('-',E'.i,T.nptr)}
E'1 {E'.s:=E1.s}
E'::= ε {E'.s:= E'.i}
*/
//返回的是綜合屬性,傳遞的是繼承屬性
ASTNode * E1(ASTNode *E1_i)
{
ASTNode * E11_i;
ASTNode * E1_s;
ASTNode * T_nptr;
char oper;
if(strcmp(strToken,"+")==0 || strcmp(strToken,"-")==0){
oper=strToken[0];
getToken();
T_nptr=T();
if(oper=='+')
E11_i=mknode(PLUS,E1_i,T_nptr);
else
E11_i=mknode(MINUS,E1_i,T_nptr);
E1_s=E1(E11_i);
}
//Follow(E1)={#,)},可以匹配空串
else if(strcmp(strToken,"#")==0 || strcmp(strToken,")")==0){
E1_s=E1_i;
}else{
error("語(yǔ)法分析錯(cuò)誤!");
}
return E1_s;
}
/*
T ::= F {T'.i:=F.nptr}
T' {T.nptr:=T'.s}
*/
ASTNode* T()
{
ASTNode * T_nptr;
ASTNode * T1_i;
T1_i=F();
T_nptr=T1(T1_i);
return T_nptr;
}
/*
T'::= * F {T'1.i:=mknode('*',T'.i,F.nptr)}
T'1 {T'.s:=T1.s}
T'::= / F {T'1.i:=mknode('/',T'.i,F.nptr)}
T'1 {T'.s:=T1.s}
T' ::= ε {T'.s:= T'.i}
*/
ASTNode* T1(ASTNode* T1_i)
{
ASTNode* F_nptr;
ASTNode* T11_i;
ASTNode* T1_s;
char oper;
if(strcmp(strToken,"*")==0 || strcmp(strToken,"/")==0){
oper=strToken[0];
getToken();
F_nptr=F();
if(oper=='*')
T11_i=mknode(TIMES,T1_i,F_nptr);
else
T11_i=mknode(SLASH,T1_i,F_nptr);
T1_s=T1(T11_i);
}
//Follow(T1)={+,#,)},如果考慮-號(hào)的話要加上-號(hào)
else if(strcmp(strToken,"-")==0 || strcmp(strToken,"+")==0 || strcmp(strToken,"#")==0 || strcmp(strToken,")")==0){
T1_s=T1_i;
}else{
error("語(yǔ)法分析錯(cuò)誤!");
}
return T1_s;
}
/*
F ::= (E) {F.nptr:=E.nptr}
F ::= num {F.nptr:=mkleaf(num,num.val)}
*/
ASTNode* F()
{
ASTNode* F_nptr;
ASTNode* E_nptr;
if(isNum(strToken)){
F_nptr=mkleaf(NUM,atoi(strToken));
getToken();
}
else{
if(strcmp(strToken,"(")==0){
getToken();
E_nptr=E();
if(strcmp(strToken,")")==0)
getToken();
else
error("語(yǔ)法分析錯(cuò)誤!");
F_nptr=E_nptr;
}
else {
error("語(yǔ)法分析錯(cuò)誤!");
}
}
return F_nptr;
}
//創(chuàng)建運(yùn)算符結(jié)點(diǎn)
ASTNode *mknode(Symbol op, ASTNode *left, ASTNode *right)
{
ASTNode* p=(ASTNode*)malloc(sizeof(ASTNode));
p->left=left;
p->right=right;
p->sym=op;
p->val=0;
return p;
}
//創(chuàng)建操作數(shù)結(jié)點(diǎn)
ASTNode *mkleaf(Symbol sym, ValType val)
{
ASTNode* p=(ASTNode*)malloc(sizeof(ASTNode));
p->sym=sym;
p->val=val;
p->left=NULL;
p->right=NULL;
return p;
}
//輸出結(jié)點(diǎn)
void printNode(ASTNode *root)
{
if(root->sym==NUM)
printf("%d ",root->val);
else if(root->sym==PLUS)
printf("+ ");
else if(root->sym==MINUS)
printf("- ");
else if(root->sym==TIMES)
printf("* ");
else if(root->sym==SLASH)
printf("/ ");
}
//中序遍歷二叉樹
void middle_list(ASTNode *root)
{
if(root==NULL)
return ;
middle_list(root->left);
printNode(root);
middle_list(root->right);
}
//后序遍歷二叉樹
void last_list(ASTNode *root)
{
char temp[MAX_LENGTH];
if(root==NULL)
return ;
last_list(root->left);
last_list(root->right);
if(root->sym == NUM){//如果是數(shù)字,則直接存入棧中
sprintf(temp,"%d\0",root->val);
strcpy(stack[index++],temp);
}
else if(root->sym == PLUS){//如果是+號(hào),產(chǎn)生一個(gè)四元式
//給四元式賦值
quaternion[count].op='+';
strcpy(quaternion[count].arg2,stack[--index]);
strcpy(quaternion[count].arg1,stack[--index]);
sprintf(quaternion[count].result,"t+%d\0",t+=4);
strcpy(stack[index++],quaternion[count].result);
//輸出該四元式
printf("%-4c%-8s%-8s%-8s\n",quaternion[count].op,quaternion[count].arg1,quaternion[count].arg2,quaternion[count].result);
count++;
}else if(root->sym == MINUS){//如果是+號(hào),產(chǎn)生一個(gè)四元式
quaternion[count].op='-';
strcpy(quaternion[count].arg2,stack[--index]);
strcpy(quaternion[count].arg1,stack[--index]);
sprintf(quaternion[count].result,"t+%d\0",t+=4);
strcpy(stack[index++],quaternion[count].result);
printf("%-4c%-8s%-8s%-8s\n",quaternion[count].op,quaternion[count].arg1,quaternion[count].arg2,quaternion[count].result);
count++;
}else if(root->sym == TIMES){//如果是*號(hào),產(chǎn)生一個(gè)四元式
quaternion[count].op='*';
strcpy(quaternion[count].arg2,stack[--index]);
strcpy(quaternion[count].arg1,stack[--index]);
sprintf(quaternion[count].result,"t+%d\0",t+=4);
strcpy(stack[index++],quaternion[count].result);
printf("%-4c%-8s%-8s%-8s\n",quaternion[count].op,quaternion[count].arg1,quaternion[count].arg2,quaternion[count].result);
count++;
}else if(root->sym == SLASH){
quaternion[count].op='/';
strcpy(quaternion[count].arg2,stack[--index]);
strcpy(quaternion[count].arg1,stack[--index]);
sprintf(quaternion[count].result,"t+%d\0",t+=4);
strcpy(stack[index++],quaternion[count].result);
printf("%-4c%-8s%-8s%-8s\n",quaternion[count].op,quaternion[count].arg1,quaternion[count].arg2,quaternion[count].result);
count++;
}
}
//測(cè)試語(yǔ)義分析
void testYuyiAnalyse()
{
ASTNode *root;
expression_index=0;
getToken();
root=E();
puts("\n語(yǔ)義分析結(jié)果如下:");
printf("中序遍歷:");
middle_list(root);
putchar('\n');
printf("后序遍歷得到的四元式:\n");
last_list(root);
putchar('\n');
}
//主函數(shù)
int main()
{
gets(expression);
testYuyiAnalyse();
return 0;
}
#endif
運(yùn)行結(jié)果

7、代碼生成
要求:以實(shí)驗(yàn)3的語(yǔ)義分析程序的四元式輸出作為輸入,輸出匯編語(yǔ)言程序。
test4.c
#ifndef TEST4_C
#define TEST4_C
/**
* 生產(chǎn)匯編代碼
**/
#include"test3.c"
//傳人一個(gè)四元式,輸出對(duì)應(yīng)的匯編代碼
void print_code(Quaternion qua)
{
putchar('\n');
/*
mov eax, 3
add eax, 4
mov t+0, eax
*/
if(qua.op == '+'){
printf(" mov eax,%s\n",qua.arg1);
printf(" add eax,%s\n",qua.arg2);
printf(" mov %s,eax\n",qua.result);
}else if(qua.op == '-'){
printf(" mov eax,%s\n",qua.arg1);
printf(" sub eax,%s\n",qua.arg2);
printf(" mov %s,eax\n",qua.result);
}
/*
mov eax, 2
mov ebx, t+0
mul ebx
mov t+4, eax
*/
else if(qua.op == '*'){
printf(" mov eax,%s\n",qua.arg1);
printf(" mov ebx,%s\n",qua.arg2);
printf(" mul ebx\n");
printf(" mov %s,eax\n",qua.result);
}else if(qua.op == '/'){//除法的時(shí)候不考慮余數(shù)
printf(" mov eax,%s\n",qua.arg1);
printf(" mov ebx,%s\n",qua.arg2);
printf(" div ebx\n");
printf(" mov %s,eax\n",qua.result);
}
}
//輸出全部匯編代碼
void testCode()
{
int i=0;
puts("生成的匯編代碼如下:\n");
puts(".386");
puts(".MODEL FLAT");
puts("ExitProcess PROTO NEAR32 stdcall, dwExitCode:DWORD");
puts("INCLUDE io.h ; header file for input/output");
puts("cr EQU 0dh ; carriage return character");
puts("Lf EQU 0ah ; line feed");
puts(".STACK 4096 ; reserve 4096-byte stack");
puts(".DATA ; reserve storage for data");
puts("t DWORD 40 DUP (?)");
puts("label1 BYTE cr, Lf, \"The result is \"");
puts("result BYTE 11 DUP (?)");
puts(" BYTE cr, Lf, 0");
puts(".CODE ; start of main program code");
puts("_start:");
//遍歷實(shí)驗(yàn)3中的四元式,輸出對(duì)應(yīng)的匯編代碼
for(;i<count;i++)
print_code(quaternion[i]);
puts(" dtoa result, eax ; convert to ASCII characters");
puts(" output label1 ; output label and sum");
puts(" INVOKE ExitProcess, 0 ; exit with return code 0");
puts("PUBLIC _start ; make entry point public");
puts("END ; end of source code");
}
//主函數(shù)
int main()
{
gets(expression);
testLexAnalyse();
testYuyiAnalyse();
testCode();
return 0;
}
#endif
運(yùn)行結(jié)果

8、點(diǎn)擊下載源代碼
這是編譯原理的實(shí)驗(yàn),自認(rèn)為是上大學(xué)以來(lái)做過(guò)的最難的一個(gè)實(shí)驗(yàn)。
實(shí)驗(yàn)用到的基礎(chǔ)知識(shí):C語(yǔ)言、數(shù)據(jù)結(jié)構(gòu)、匯編(只需簡(jiǎn)單的了解)。
開發(fā)工具:VC
2、問(wèn)題描述
編譯整數(shù)四則運(yùn)算表達(dá)式,將整數(shù)四則運(yùn)算表達(dá)式翻譯為匯編語(yǔ)言代碼。
消除左遞歸后的文法:
E→TE'
E'→+TE' |ε
T→FT'
T'→*FT' |ε
F→(E) | i
消除左遞歸后的翻譯模式:
E ::= T {E'.i:=T.nptr}
E' {E.nptr:=E'.s}
E'::= + T {E'1.i:=mknode(‘+',E'.i,T.nptr)}
E'1 {E'.s:=E1.s}
E'::= - T {E'1.i:=mknode(‘-',E'.i,T.nptr)}
E'1 {E'.s:=E1.s}
E'::= ε {E'.s:= E'.i}
T ::= F {T'.i:=F.nptr}
T' {T.nptr:=T'.s}
T'::= * F {T'1.i:=mknode(‘*',T'.i,F.nptr)}
T'1 {T'.s:=T1.s}
T'::= / F {T'1.i:=mknode(‘/',T'.i,F.nptr)}
T'1 {T'.s:=T1.s}
T' ::= ε {T'.s:= T'.i}
F ::= (E) {F.nptr:=E.nptr}
F ::= num {F.nptr:=mkleaf(num,num.val)}
3、全局定義
test.c文件
復(fù)制代碼 代碼如下:
#ifndef TEST_C
#define TEST_C
/**
* 全局變量和全局函數(shù)文件
**/
#include<stdio.h>
#include<ctype.h>
#include<string.h>
#include<stdlib.h>
/************************* 以下是全局變量(函數(shù))的定義 *******************/
//輸入的表達(dá)式最大長(zhǎng)度,可以看做是緩沖區(qū)的長(zhǎng)度
#define MAX_EXPRESSION_LENGTH 50
//存放輸入的表達(dá)式
char expression[MAX_EXPRESSION_LENGTH];
//表達(dá)式字符數(shù)組的下標(biāo)
int expression_index=0;
//存放一個(gè)單詞符號(hào)
char strToken[MAX_EXPRESSION_LENGTH/2];
//判斷是否是數(shù)字
int isNum(char * strToken)
{
int i=0;
while(strToken[i]){
if(!isdigit(strToken[i]))
break;
i++;
}
return strToken[i]==0;
}
//錯(cuò)誤處理程序
void error(char* errerMessage)
{
printf("\nERROR:%s\n",errerMessage);
exit(0);
}
/************************* 以上是全局變量(函數(shù))的定義 ******************/
#endif
4、詞法分析
詞法分析的要求是:接受一個(gè)表達(dá)式,輸出該表達(dá)式中的各類單詞符號(hào)
一般有兩種方法來(lái)進(jìn)行詞法分析,一種是用狀態(tài)圖來(lái)實(shí)現(xiàn),一種是用狀態(tài)轉(zhuǎn)換表。下面采用狀態(tài)圖實(shí)現(xiàn)
首先定義單詞符號(hào)的種類和所屬類型
typedef enum Symbol { ERR = -1, END, NUM, PLUS, MINUS, TIMES, SLASH, LPAREN, RPAREN } Symbol;
然后轉(zhuǎn)態(tài)轉(zhuǎn)換圖如下所示:
test1.c文件用代碼表示如下:
復(fù)制代碼 代碼如下:
#ifndef TEST1_C
#define TEST1_C
/**
* 采用狀態(tài)圖進(jìn)行詞法分析以及測(cè)試詞法分析
*
**/
#include"test.c"
//枚舉類型
typedef enum Symbol { ERR = -1, END, NUM, PLUS, MINUS, TIMES,
SLASH, LPAREN, RPAREN } Symbol;
//獲取一個(gè)單詞符號(hào),該單詞符號(hào)存放在strToken中。返回該單詞符號(hào)的枚舉類型
Symbol getToken();
//根據(jù)傳入的枚舉類型輸出對(duì)應(yīng)的單詞符號(hào)
void printToken(Symbol i);
//測(cè)試詞法分析
void testLexAnalyse();
//獲取一個(gè)單詞符號(hào),該單詞符號(hào)存放在strToken中。返回該單詞符號(hào)的枚舉類型
Symbol getToken()
{
char ch;
int state=0;//每次都是從狀態(tài)0開始
int j=0;
//表達(dá)式遍歷完成,單詞符號(hào)為'#'
if(expression[expression_index]=='\0'){
strToken[0]='#';
strToken[1]='\0';
return END;
}
while(1){
switch(state){
case 0:
//讀取一個(gè)字符
ch=strToken[j++]=expression[expression_index++];
if(isspace(ch)){
j--;//注意退格
state=0;
}
else if(isdigit(ch))
state=1;
else if(ch=='+')
state=2;
else if(ch=='-')
state=3;
else if(ch=='*')
state=4;
else if(ch=='/')
state=5;
else if(ch=='(')
state=6;
else if(ch==')')
state=7;
else
return ERR;
break;
case 1:
ch=strToken[j++]=expression[expression_index++];
if(isdigit(ch))
state=1;
else{
expression_index--;
strToken[--j]=0;
return NUM;
}
break;
case 2:
strToken[j]=0;
return PLUS;
case 3:
strToken[j]=0;
return MINUS;
case 4:
strToken[j]=0;
return TIMES;
case 5:
strToken[j]=0;
return SLASH;
case 6:
strToken[j]=0;
return LPAREN;
case 7:
strToken[j]=0;
return RPAREN;
}
}
}
//根據(jù)傳入的枚舉類型輸出對(duì)應(yīng)的單詞符號(hào)
void printToken(Symbol i){
switch(i){
case -1:printf("ERR\n");break;
case 0:printf("END\n");break;
case 1:printf("NUM %s\n",strToken);break;
case 2:printf("PLUS %s\n",strToken);break;
case 3:printf("MINUS %s\n",strToken);break;
case 4:printf("TIMES %s\n",strToken);break;
case 5:printf("SLASH %s\n",strToken);break;
case 6:printf("LPAREN %s\n",strToken);break;
case 7:printf("RPAREN %s\n",strToken);break;
}
}
//測(cè)試詞法分析
void testLexAnalyse()
{
Symbol tokenStyle;//單詞符號(hào)類型
expression_index=0;
puts("\n詞法分析結(jié)果如下:");
while(1){
tokenStyle=getToken();
printToken(tokenStyle);
if(tokenStyle == ERR){
error("詞法分析錯(cuò)誤!");
}
if(tokenStyle == END){
break;
}
}
}
//主函數(shù)
int main()
{
gets(expression);
testLexAnalyse();
return 0;
}
#endif
運(yùn)行結(jié)果

5、語(yǔ)法分析
要求:接受一個(gè)表達(dá)式,分析該表達(dá)式,并根據(jù)輸入正確與否給出相應(yīng)信息
主要是根據(jù)無(wú)左遞歸文法寫出對(duì)應(yīng)的各個(gè)子程序
test2.c
復(fù)制代碼 代碼如下:
#ifndef TEST_2
#define TEST_2
/**
* 語(yǔ)法分析以及測(cè)試語(yǔ)法分析
**/
#include"test1.c"
/*
消除左遞歸后的文法:
E→TE'
E'→+TE' |ε
T→FT'
T'→*FT' |ε
F→(E) | i
*/
//每個(gè)非終結(jié)符有對(duì)應(yīng)的子程序函數(shù)聲明
void E();
void E1();
void T();
void T1();
void F();
//測(cè)試語(yǔ)法分析
void testSyntaxAnalyse();
//每個(gè)非終結(jié)符有對(duì)應(yīng)的子程序
void E()
{
T();
E1();
}
void E1()
{
if(strcmp(strToken,"+")==0 || strcmp(strToken,"-")==0){
getToken();
T();
E1();
}
//Follow(E1)={#,)}
else if(strcmp(strToken,"#")!=0 && strcmp(strToken,")")!=0){
error("語(yǔ)法分析錯(cuò)誤!");
}
}
void T()
{
F();
T1();
}
void T1()
{
if(strcmp(strToken,"*")==0 || strcmp(strToken,"/")==0){
getToken();
F();
T1();
}
//Follow(T1)={+,#,)},如果考慮-號(hào)的話要加上-號(hào)
else if(strcmp(strToken,"-")!=0 &&strcmp(strToken,"+")!=0 && strcmp(strToken,"#")!=0 && strcmp(strToken,")")!=0){
error("語(yǔ)法分析錯(cuò)誤!");
}
}
void F()
{
if(isNum(strToken)){
getToken();
}
else{
if(strcmp(strToken,"(")==0){
getToken();
E();
if(strcmp(strToken,")")==0)
getToken();
else
error("語(yǔ)法分析錯(cuò)誤!");
}
else
error("語(yǔ)法分析錯(cuò)誤!");
}
}
//測(cè)試語(yǔ)法分析
void testSyntaxAnalyse()
{
expression_index=0;
getToken();
E();
puts("\n語(yǔ)法分析結(jié)果如下:");
if(strcmp(strToken,"#")!=0)
error("語(yǔ)法分析錯(cuò)誤!");
else{
puts("語(yǔ)法分析正確!");
}
}
//主函數(shù)
int main()
{
gets(expression);
testLexAnalyse();
testSyntaxAnalyse();
return 0;
}
#endif
運(yùn)行時(shí)要?jiǎng)h掉test1.c中的主函數(shù),運(yùn)行結(jié)果

6、語(yǔ)義分析
要求:需要實(shí)現(xiàn)的語(yǔ)義分析程序的功能是,接受一個(gè)表達(dá)式,分析該表達(dá)式,并在分析的過(guò)程中建立該表達(dá)式的抽象語(yǔ)法樹。由于四則運(yùn)算表達(dá)式的抽象語(yǔ)法樹可基本上看作是二叉樹,因此中序遍歷序列應(yīng)該和輸入的表達(dá)式一樣——除了沒(méi)有括號(hào)之外??奢敵鲋行虮闅v序列檢測(cè)程序功能是否正確。如果每個(gè)分支節(jié)點(diǎn)用一個(gè)臨時(shí)變量標(biāo)記,則對(duì)四則運(yùn)算表達(dá)式的抽象語(yǔ)法樹進(jìn)行后序遍歷,可以得到輸入表達(dá)式所對(duì)應(yīng)的四元式序列
test3.c文件
復(fù)制代碼 代碼如下:
#ifndef TEST3_C
#define TEST3_C
/**
* 語(yǔ)義分析以及測(cè)試語(yǔ)義分析
* 其實(shí)這個(gè)實(shí)驗(yàn)是在test2的代碼上進(jìn)行修改
**/
#include"test1.c"
/*
消除左遞歸的翻譯模式:
E ::= T {E'.i:=T.nptr}
E' {E.nptr:=E'.s}
E'::= + T {E'1.i:=mknode('+',E'.i,T.nptr)}
E'1 {E'.s:=E1.s}
E'::= - T {E'1.i:=mknode('-',E'.i,T.nptr)}
E'1 {E'.s:=E1.s}
E'::= ε {E'.s:= E'.i}
T ::= F {T'.i:=F.nptr}
T' {T.nptr:=T'.s}
T'::= * F {T'1.i:=mknode('*',T'.i,F.nptr)}
T'1 {T'.s:=T1.s}
T'::= / F {T'1.i:=mknode('/',T'.i,F.nptr)}
T'1 {T'.s:=T1.s}
T' ::= ε {T'.s:= T'.i}
F ::= (E) {F.nptr:=E.nptr}
F ::= num {F.nptr:=mkleaf(num,num.val)}
*/
#define MAX_LENGTH 20 //四元式中操作數(shù)的最大長(zhǎng)度
typedef int ValType;
//結(jié)點(diǎn)類型
typedef struct ASTNode {
Symbol sym;//類型
ValType val;//值
struct ASTNode * left, *right;//左、右孩子
}ASTNode, *AST;
//四元式類型定義如下
typedef struct Quaternion{
char op;
char arg1[MAX_LENGTH];
char arg2[MAX_LENGTH];
char result[MAX_LENGTH];
}Quaternion;
//四元式數(shù)組,存放產(chǎn)生的四元式
Quaternion quaternion[MAX_LENGTH*2];
//統(tǒng)計(jì)四元式的個(gè)數(shù)
int count=0;
//后序遍歷抽象語(yǔ)法樹時(shí)存放操作數(shù)和臨時(shí)變量,這里當(dāng)作一個(gè)棧來(lái)使用
char stack[MAX_LENGTH*2][MAX_LENGTH];
//stack棧的下標(biāo)
int index=0;
//內(nèi)存中臨時(shí)數(shù)據(jù)存儲(chǔ)地址的偏移量
int t=-4;
//函數(shù)聲明
ASTNode* E();
ASTNode* E1(ASTNode* E1_i);
ASTNode* T();
ASTNode* T1(ASTNode* T1_i);
ASTNode* F();
void error(char* errerMessage);
ASTNode *mknode(Symbol op, ASTNode *left, ASTNode *right);
ASTNode *mkleaf(Symbol sym, ValType val);
void yuyi_analyse();
void print_node(ASTNode *root);
void middle_list(ASTNode *root);
void last_list(ASTNode *root);
//測(cè)試語(yǔ)義分析
void testYuyiAnalyse();
//創(chuàng)建運(yùn)算符結(jié)點(diǎn)
ASTNode *mknode(Symbol op, ASTNode *left, ASTNode *right);
//創(chuàng)建操作數(shù)結(jié)點(diǎn)
ASTNode *mkleaf(Symbol sym, ValType val);
//輸出結(jié)點(diǎn)
void printNode(ASTNode *root);
//中序遍歷二叉樹
void middle_list(ASTNode *root);
//后序遍歷二叉樹
void last_list(ASTNode *root);
/*
E ::= T {E'.i:=T.nptr}
E' {E.nptr:=E'.s}
*/
//為右邊的每個(gè)非終結(jié)符定義一個(gè)屬性,返回E的綜合屬性
ASTNode* E()
{
ASTNode * E_nptr;
ASTNode * E1_i;
E1_i=T();
E_nptr=E1(E1_i);
return E_nptr;
}
/*
E'::= + T {E'1.i:=mknode('+',E'.i,T.nptr)}
E'1 {E'.s:=E1.s}
E'::= - T {E'1.i:=mknode('-',E'.i,T.nptr)}
E'1 {E'.s:=E1.s}
E'::= ε {E'.s:= E'.i}
*/
//返回的是綜合屬性,傳遞的是繼承屬性
ASTNode * E1(ASTNode *E1_i)
{
ASTNode * E11_i;
ASTNode * E1_s;
ASTNode * T_nptr;
char oper;
if(strcmp(strToken,"+")==0 || strcmp(strToken,"-")==0){
oper=strToken[0];
getToken();
T_nptr=T();
if(oper=='+')
E11_i=mknode(PLUS,E1_i,T_nptr);
else
E11_i=mknode(MINUS,E1_i,T_nptr);
E1_s=E1(E11_i);
}
//Follow(E1)={#,)},可以匹配空串
else if(strcmp(strToken,"#")==0 || strcmp(strToken,")")==0){
E1_s=E1_i;
}else{
error("語(yǔ)法分析錯(cuò)誤!");
}
return E1_s;
}
/*
T ::= F {T'.i:=F.nptr}
T' {T.nptr:=T'.s}
*/
ASTNode* T()
{
ASTNode * T_nptr;
ASTNode * T1_i;
T1_i=F();
T_nptr=T1(T1_i);
return T_nptr;
}
/*
T'::= * F {T'1.i:=mknode('*',T'.i,F.nptr)}
T'1 {T'.s:=T1.s}
T'::= / F {T'1.i:=mknode('/',T'.i,F.nptr)}
T'1 {T'.s:=T1.s}
T' ::= ε {T'.s:= T'.i}
*/
ASTNode* T1(ASTNode* T1_i)
{
ASTNode* F_nptr;
ASTNode* T11_i;
ASTNode* T1_s;
char oper;
if(strcmp(strToken,"*")==0 || strcmp(strToken,"/")==0){
oper=strToken[0];
getToken();
F_nptr=F();
if(oper=='*')
T11_i=mknode(TIMES,T1_i,F_nptr);
else
T11_i=mknode(SLASH,T1_i,F_nptr);
T1_s=T1(T11_i);
}
//Follow(T1)={+,#,)},如果考慮-號(hào)的話要加上-號(hào)
else if(strcmp(strToken,"-")==0 || strcmp(strToken,"+")==0 || strcmp(strToken,"#")==0 || strcmp(strToken,")")==0){
T1_s=T1_i;
}else{
error("語(yǔ)法分析錯(cuò)誤!");
}
return T1_s;
}
/*
F ::= (E) {F.nptr:=E.nptr}
F ::= num {F.nptr:=mkleaf(num,num.val)}
*/
ASTNode* F()
{
ASTNode* F_nptr;
ASTNode* E_nptr;
if(isNum(strToken)){
F_nptr=mkleaf(NUM,atoi(strToken));
getToken();
}
else{
if(strcmp(strToken,"(")==0){
getToken();
E_nptr=E();
if(strcmp(strToken,")")==0)
getToken();
else
error("語(yǔ)法分析錯(cuò)誤!");
F_nptr=E_nptr;
}
else {
error("語(yǔ)法分析錯(cuò)誤!");
}
}
return F_nptr;
}
//創(chuàng)建運(yùn)算符結(jié)點(diǎn)
ASTNode *mknode(Symbol op, ASTNode *left, ASTNode *right)
{
ASTNode* p=(ASTNode*)malloc(sizeof(ASTNode));
p->left=left;
p->right=right;
p->sym=op;
p->val=0;
return p;
}
//創(chuàng)建操作數(shù)結(jié)點(diǎn)
ASTNode *mkleaf(Symbol sym, ValType val)
{
ASTNode* p=(ASTNode*)malloc(sizeof(ASTNode));
p->sym=sym;
p->val=val;
p->left=NULL;
p->right=NULL;
return p;
}
//輸出結(jié)點(diǎn)
void printNode(ASTNode *root)
{
if(root->sym==NUM)
printf("%d ",root->val);
else if(root->sym==PLUS)
printf("+ ");
else if(root->sym==MINUS)
printf("- ");
else if(root->sym==TIMES)
printf("* ");
else if(root->sym==SLASH)
printf("/ ");
}
//中序遍歷二叉樹
void middle_list(ASTNode *root)
{
if(root==NULL)
return ;
middle_list(root->left);
printNode(root);
middle_list(root->right);
}
//后序遍歷二叉樹
void last_list(ASTNode *root)
{
char temp[MAX_LENGTH];
if(root==NULL)
return ;
last_list(root->left);
last_list(root->right);
if(root->sym == NUM){//如果是數(shù)字,則直接存入棧中
sprintf(temp,"%d\0",root->val);
strcpy(stack[index++],temp);
}
else if(root->sym == PLUS){//如果是+號(hào),產(chǎn)生一個(gè)四元式
//給四元式賦值
quaternion[count].op='+';
strcpy(quaternion[count].arg2,stack[--index]);
strcpy(quaternion[count].arg1,stack[--index]);
sprintf(quaternion[count].result,"t+%d\0",t+=4);
strcpy(stack[index++],quaternion[count].result);
//輸出該四元式
printf("%-4c%-8s%-8s%-8s\n",quaternion[count].op,quaternion[count].arg1,quaternion[count].arg2,quaternion[count].result);
count++;
}else if(root->sym == MINUS){//如果是+號(hào),產(chǎn)生一個(gè)四元式
quaternion[count].op='-';
strcpy(quaternion[count].arg2,stack[--index]);
strcpy(quaternion[count].arg1,stack[--index]);
sprintf(quaternion[count].result,"t+%d\0",t+=4);
strcpy(stack[index++],quaternion[count].result);
printf("%-4c%-8s%-8s%-8s\n",quaternion[count].op,quaternion[count].arg1,quaternion[count].arg2,quaternion[count].result);
count++;
}else if(root->sym == TIMES){//如果是*號(hào),產(chǎn)生一個(gè)四元式
quaternion[count].op='*';
strcpy(quaternion[count].arg2,stack[--index]);
strcpy(quaternion[count].arg1,stack[--index]);
sprintf(quaternion[count].result,"t+%d\0",t+=4);
strcpy(stack[index++],quaternion[count].result);
printf("%-4c%-8s%-8s%-8s\n",quaternion[count].op,quaternion[count].arg1,quaternion[count].arg2,quaternion[count].result);
count++;
}else if(root->sym == SLASH){
quaternion[count].op='/';
strcpy(quaternion[count].arg2,stack[--index]);
strcpy(quaternion[count].arg1,stack[--index]);
sprintf(quaternion[count].result,"t+%d\0",t+=4);
strcpy(stack[index++],quaternion[count].result);
printf("%-4c%-8s%-8s%-8s\n",quaternion[count].op,quaternion[count].arg1,quaternion[count].arg2,quaternion[count].result);
count++;
}
}
//測(cè)試語(yǔ)義分析
void testYuyiAnalyse()
{
ASTNode *root;
expression_index=0;
getToken();
root=E();
puts("\n語(yǔ)義分析結(jié)果如下:");
printf("中序遍歷:");
middle_list(root);
putchar('\n');
printf("后序遍歷得到的四元式:\n");
last_list(root);
putchar('\n');
}
//主函數(shù)
int main()
{
gets(expression);
testYuyiAnalyse();
return 0;
}
#endif
運(yùn)行結(jié)果

7、代碼生成
要求:以實(shí)驗(yàn)3的語(yǔ)義分析程序的四元式輸出作為輸入,輸出匯編語(yǔ)言程序。
test4.c
復(fù)制代碼 代碼如下:
#ifndef TEST4_C
#define TEST4_C
/**
* 生產(chǎn)匯編代碼
**/
#include"test3.c"
//傳人一個(gè)四元式,輸出對(duì)應(yīng)的匯編代碼
void print_code(Quaternion qua)
{
putchar('\n');
/*
mov eax, 3
add eax, 4
mov t+0, eax
*/
if(qua.op == '+'){
printf(" mov eax,%s\n",qua.arg1);
printf(" add eax,%s\n",qua.arg2);
printf(" mov %s,eax\n",qua.result);
}else if(qua.op == '-'){
printf(" mov eax,%s\n",qua.arg1);
printf(" sub eax,%s\n",qua.arg2);
printf(" mov %s,eax\n",qua.result);
}
/*
mov eax, 2
mov ebx, t+0
mul ebx
mov t+4, eax
*/
else if(qua.op == '*'){
printf(" mov eax,%s\n",qua.arg1);
printf(" mov ebx,%s\n",qua.arg2);
printf(" mul ebx\n");
printf(" mov %s,eax\n",qua.result);
}else if(qua.op == '/'){//除法的時(shí)候不考慮余數(shù)
printf(" mov eax,%s\n",qua.arg1);
printf(" mov ebx,%s\n",qua.arg2);
printf(" div ebx\n");
printf(" mov %s,eax\n",qua.result);
}
}
//輸出全部匯編代碼
void testCode()
{
int i=0;
puts("生成的匯編代碼如下:\n");
puts(".386");
puts(".MODEL FLAT");
puts("ExitProcess PROTO NEAR32 stdcall, dwExitCode:DWORD");
puts("INCLUDE io.h ; header file for input/output");
puts("cr EQU 0dh ; carriage return character");
puts("Lf EQU 0ah ; line feed");
puts(".STACK 4096 ; reserve 4096-byte stack");
puts(".DATA ; reserve storage for data");
puts("t DWORD 40 DUP (?)");
puts("label1 BYTE cr, Lf, \"The result is \"");
puts("result BYTE 11 DUP (?)");
puts(" BYTE cr, Lf, 0");
puts(".CODE ; start of main program code");
puts("_start:");
//遍歷實(shí)驗(yàn)3中的四元式,輸出對(duì)應(yīng)的匯編代碼
for(;i<count;i++)
print_code(quaternion[i]);
puts(" dtoa result, eax ; convert to ASCII characters");
puts(" output label1 ; output label and sum");
puts(" INVOKE ExitProcess, 0 ; exit with return code 0");
puts("PUBLIC _start ; make entry point public");
puts("END ; end of source code");
}
//主函數(shù)
int main()
{
gets(expression);
testLexAnalyse();
testYuyiAnalyse();
testCode();
return 0;
}
#endif
運(yùn)行結(jié)果

8、點(diǎn)擊下載源代碼
相關(guān)文章
基于C++浮點(diǎn)數(shù)(float、double)類型數(shù)據(jù)比較與轉(zhuǎn)換的詳解
本篇文章是對(duì)C++中浮點(diǎn)數(shù)(float、double)類型數(shù)據(jù)比較與轉(zhuǎn)換進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05Opencv 馬賽克和毛玻璃效果與圖片融合的實(shí)現(xiàn)
這篇文章主要為大家詳細(xì)介紹了通過(guò)OpenCV實(shí)現(xiàn)馬賽克和毛玻璃濾鏡效果與圖片的融合,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11C語(yǔ)言深入探究選擇排序與基數(shù)排序使用案例講解
算法中排序是十分重要的,而每一個(gè)學(xué)習(xí)計(jì)算機(jī)的都會(huì)在初期的時(shí)候接觸到這種排序,下面這篇文章主要給大家介紹了關(guān)于c語(yǔ)言選擇排序與基數(shù)排序使用的相關(guān)資料,需要的朋友可以參考下2022-05-05C++中實(shí)現(xiàn)保存數(shù)據(jù)到CSV文件
這篇文章主要介紹了C++中實(shí)現(xiàn)保存數(shù)據(jù)到CSV文件方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-08-08