实验二、语法分析
一、实验目的
根据某一文法编制调试递归下降分析程序,以便对任意输入的符号串进行分析。本次实验的目的主要是加深对递归下降分析法的理解。
注:也可以采用预测分析方法、算符优先分析方法来进行分析。具体参照课本上的说明,以下是递归下降分析法的介绍。
二、实验预习提示
1、递归下降分析法的功能
语法分析器的功能是利用函数之间的递归调用模拟语法树自上而下的构造过程。
2、递归下降分析法的前提
改造文法:消除二义性、消除左递归、提取左因子,判断是否为LL(1)文法。
3、递归下降分析法实验设计思想及算法
为G的每个非终结符号U构造一个递归过程,不妨命名为U。U的产生式的右边指出这个过程的代码结构:
(1)若是终结符号,则和向前看符号对照,若匹配则向前进一个符号;否则出错。
(2)若是非终结符号,则调用与此非终结符对应的过程。当A的右部有多个产生式时,可用选择结构实现, 具体为:(1)对于每个非终结符号U->u1|u2|…|un处理的方法如下
U( )
{
ch=当前符号;
if(ch可能是u1字的开头) 处理u1的程序部分;
else if(ch可能是u2字的开头)处理u2的程序部分;
……
else error();
}
(2)对于每个右部u1->x1x2…xn的处理架构如下:
处理x1的程序;
处理x2的程序;
……
处理xn的程序;
(3)如果非终结符U有空产生式:Uε ,则还需考虑ch属于Follow(U)的情况。
三、实验过程和指导
(一)准备:
1.阅读课本有关章节。
2.初步编制好程序。
3.准备好多组测试数据。
(二)上机:
(三)程序要求:
对算术表达式文法,用递归下降分析法(或预测分析方法、算符优先分析方法等)对任意输入的符号串进行分析,如合法给出相应信息,如果不合法,最好能给出在哪个产生式出现的问题。
算术表达式至少包含+、-、*、/、()。例如:i1 + i2 * ( 34 - i3 / 2 )
提示:先做词法分析,然后语法分析。
四、实验原理
本实验为给定文法:
E→E+T| E-T| T
T→T*F| T/F| F
F→(E)| i
对用户输入的算术表达式先进行词法分析,再用递归下降法进行语法分析,因为递归下降法的前提是给定文法是LL(1)文法,下面给出判断是否是LL(1)文法的分析过程:
通过以上分析可知所给文法满足梯度下降法的前提条件,可用梯度下降法进行分析。
梯度下降核心代码:
def E(): # E->TE'
if T() and G():
return True
else:
return False
def T(): # T->FT'
if F() and S():
return True
else:
return False
def G(): # E'->+TE'|-TE'|&
global i
if line[i]=='+' or line[i]=='-':
i=i+1
if T() and G():
return True
else:
return False
else:
return True
def S(): # T'->*FT|/FT|&
global i
if line[i]=='*' or line[i]=='/':
i=i+1
if F() and S():
return True
else:
return False
else:
return True
def F(): # F->(E)|i
global i
if line[i].isdigit():
i=i+1
return True
if line[i]=='(':
i=i+1
if E() and line[i]==')':
i=i+1
return True
return False
程序界面(效果图)
和实验一一样选择文件进行语法分析或手动输入算术表达式进行语法分析:
首先展示词法分析结果,在点击进行语法分析后对算数表达式进行语法分析:
如上图所示,当算数表达式是合法的时候,会显示算是表达式合法,并将结果算出来,当算式不合法时提示算术表达式不合法,并将算式中错误的位置打印出来,如下:
程序代码
main.py
from fastapi import FastAPI, Request, Form, File, UploadFile
from fastapi.templating import Jinja2Templates
import uvicorn
app = FastAPI()
templates = Jinja2Templates(directory="templates")
from fastapi import Response
from pydantic import BaseModel
reserved_words = [
'main','if','int','for','while','do','return','break','continue',
'auto',
'break',
'case',
'char',
'const',
'continue',
'default',
'do',
'double',
'else',
'enum',
'extern',
'float',
'for',
'goto',
'if',
'int',
'long',
'register',
'return',
'short',
'signed',
'sizeof',
'static',
'struct',
'switch',
'typedef',
'union',
'unsigned',
'void',
'volatile',
'while'
]
# C语言词法分析器状态机
class LexerStateMachine:
def __init__(self):
self.current_state = 'start'
self.current_token = ''
self.tokens = []
# 状态转移函数
def transition(self, char):
#首先在start状态时遇到各种字符应该怎么跳转
if self.current_state == 'start':
if char in ['#',' ','\t', '\n', '\r']:
pass # 空格、制表符、换行符和回车符不是单词的一部分,忽略它们
elif char.isalpha() or char == '_':
self.current_state = 'identifier'
self.current_token += char
elif char.isdigit():
self.current_state = 'constant'
self.current_token += char
#小数浮点数
elif char in ['.']:
self.current_state = 'float'
self.current_token += char
elif char in ['~', '?', ':', ',', ';', '.', '(', ')', '[', ']', '{', '}']:
self.tokens.append(('operator', char))
self.current_state = 'start'
self.current_token = ''
#此后为对运算符的详细状态转换
elif char == '+':
self.current_state = '加'
self.current_token += char
elif char == '-':
self.current_state = '减'
self.current_token += char
elif char == '*':
self.current_state = '乘'
self.current_token += char
elif char == '/':
self.current_state = '除'
self.current_token += char
elif char == '&':
self.current_state = '&'
self.current_token += char
elif char == '|':
self.current_state = '或'
self.current_token += char
elif char == '!':
self.current_state = '非'
self.current_token += char
elif char == '^':
self.current_state = '幂'
self.current_token += char
elif char == '<':
self.current_state = '小于'
self.current_token += char
elif char == '>':
self.current_state = '大于'
self.current_token += char
elif char == '%':
self.current_state = '百分号'
self.current_token += char
elif char == '=':
self.current_state = '等于'
self.current_token += char
#以上是对运算符的详细
else:
self.tokens.append(('error', char))
self.current_state = 'start'
self.current_token = ''
#以下是对运算符的二次判断
elif self.current_state == '加':
if char =='+':
self.current_token += char
self.tokens.append(('operator', self.current_token))
self.current_state = 'start'
self.current_token = ''
elif char =='=':
self.current_token += char
self.tokens.append(('operator', self.current_token))
self.current_state = 'start'
self.current_token = ''
# elif char.isdigit():
# self.current_token += char
# self.current_state = 'constant'
else:
self.tokens.append(('operator', self.current_token))
self.current_state = 'start'
self.current_token = ''
#处理一个空格防止前后连一起
self.transition(' ')
self.transition(char) # 处理当前字符
elif self.current_state == '减':
if char =='-':
self.current_token += char
self.tokens.append(('operator', self.current_token))
self.current_state = 'start'
self.current_token = ''
elif char =='=':
self.current_token += char
self.tokens.append(('operator', self.current_token))
self.current_state = 'start'
self.current_token = ''
# elif char.isdigit():
# self.current_token += char
# self.current_state = 'constant'
else:
self.tokens.append(('operator', self.current_token))
self.current_state = 'start'
self.current_token = ''
#处理一个空格防止前后连一起
self.transition(' ')
self.transition(char) # 处理当前字符
elif self.current_state == '乘':
if char =='=':
self.current_token += char
self.tokens.append(('operator', self.current_token))
self.current_state = 'start'
self.current_token = ''
else:
self.tokens.append(('operator', self.current_token))
self.current_state = 'start'
self.current_token = ''
#处理一个空格防止前后连一起
self.transition(' ')
self.transition(char) # 处理当前字符
elif self.current_state == '除':
if char =='=':
self.current_token += char
self.tokens.append(('operator', self.current_token))
self.current_state = 'start'
self.current_token = ''
elif char == '/':
self.current_state = 'line_comment'
elif char == '*':
self.current_state = 'block_comment'
else:
self.tokens.append(('operator', self.current_token))
self.current_state = 'start'
self.current_token = ''
#处理一个空格防止前后连一起
self.transition(' ')
self.transition(char) # 处理当前字符
elif self.current_state == '与':
if char =='&':
self.current_token += char
self.tokens.append(('operator', self.current_token))
self.current_state = 'start'
self.current_token = ''
else:
self.tokens.append(('operator', self.current_token))
self.current_state = 'start'
self.current_token = ''
#处理一个空格防止前后连一起
self.transition(' ')
self.transition(char) # 处理当前字符
elif self.current_state == '或':
if char =='|':
self.current_token += char
self.tokens.append(('operator', self.current_token))
self.current_state = 'start'
self.current_token = ''
else:
self.tokens.append(('operator', self.current_token))
self.current_state = 'start'
self.current_token = ''
#处理一个空格防止前后连一起
self.transition(' ')
self.transition(char) # 处理当前字符
elif self.current_state == '幂':
if char =='=':
self.current_token += char
self.tokens.append(('operator', self.current_token))
self.current_state = 'start'
self.current_token = ''
else:
self.tokens.append(('operator', self.current_token))
self.current_state = 'start'
self.current_token = ''
#处理一个空格防止前后连一起
self.transition(' ')
self.transition(char) # 处理当前字符
elif self.current_state == '非':
if char =='=':
self.current_token += char
self.tokens.append(('operator', self.current_token))
self.current_state = 'start'
self.current_token = ''
else:
self.tokens.append(('operator', self.current_token))
self.current_state = 'start'
self.current_token = ''
#处理一个空格防止前后连一起
self.transition(' ')
self.transition(char) # 处理当前字符
elif self.current_state == '百分号':
if char =='=':
self.current_token += char
self.tokens.append(('operator', self.current_token))
self.current_state = 'start'
self.current_token = ''
else:
self.tokens.append(('operator', self.current_token))
self.current_state = 'start'
self.current_token = ''
#处理一个空格防止前后连一起
self.transition(' ')
self.transition(char) # 处理当前字符
elif self.current_state == '小于':
if char =='<':
self.current_token += char
self.tokens.append(('operator', self.current_token))
self.current_state = 'start'
self.current_token = ''
elif char =='=':
self.current_token += char
self.tokens.append(('operator', self.current_token))
self.current_state = 'start'
self.current_token = ''
else:
self.tokens.append(('operator', self.current_token))
self.current_state = 'start'
self.current_token = ''
#处理一个空格防止前后连一起
self.transition(' ')
self.transition(char) # 处理当前字符
elif self.current_state == '大于':
if char =='>':
self.current_token += char
self.tokens.append(('operator', self.current_token))
self.current_state = 'start'
self.current_token = ''
elif char =='=':
self.current_token += char
self.tokens.append(('operator', self.current_token))
self.current_state = 'start'
self.current_token = ''
else:
self.tokens.append(('operator', self.current_token))
self.current_state = 'start'
self.current_token = ''
#处理一个空格防止前后连一起
self.transition(' ')
self.transition(char) # 处理当前字符
elif self.current_state == '等于':
if char =='=':
self.current_token += char
self.tokens.append(('operator', self.current_token))
self.current_state = 'start'
self.current_token = ''
else:
self.tokens.append(('operator', self.current_token))
self.current_state = 'start'
self.current_token = ''
#处理一个空格防止前后连一起
self.transition(' ')
self.transition(char) # 处理当前字符
#以上是对运算符的二次判断
#当处于inentifier状态时转换
elif self.current_state == 'identifier':
if char.isalnum() or char == '_':
self.current_token += char
else:
if self.current_token in reserved_words:
self.tokens.append(('reserved_words', self.current_token))
self.current_state = 'start'
self.current_token = ''
#处理一个空格防止前后连一起
self.transition(' ')
self.transition(char) # 处理当前字符
else:
self.tokens.append(('identifier', self.current_token))
self.current_state = 'start'
self.current_token = ''
#处理一个空格防止前后连一起
self.transition(' ')
self.transition(char) # 处理当前字符
#当处于constant状态时转换
elif self.current_state == 'constant':
if char.isdigit() or char in ['e', 'E']:
self.current_token += char
elif char in ['.']:
self.current_state = 'float'
self.current_token += char
elif char in ['f', 'F', 'l', 'L', 'u', 'U']:
self.current_token += char
self.tokens.append(('constant', self.current_token))
self.current_state = 'start'
self.current_token = ''
else:
self.tokens.append(('constant', self.current_token))
self.current_state = 'start'
self.current_token = ''
#处理一个空格防止前后连一起
self.transition(' ')
self.transition(char) # 处理当前字符
#浮点数
elif self.current_state == 'float':
if char.isdigit() or char in ['e', 'E', '+', '-']:
self.current_token += char
# elif char in ['.']:
# self.current_state = 'error'
# self.current_token += char
else:
self.tokens.append(('float', self.current_token))
self.current_state = 'start'
self.current_token = ''
#处理一个空格防止前后连一起
self.transition(' ')
self.transition(char) # 处理当前字符
# #error
# elif self.current_state == 'error':
# if char in [' ','\t', '\n', '\r']:
# self.tokens.append(('error', self.current_token))
# self.current_state = 'start'
# self.current_token = ''
# else:
# self.current_state = 'error'
# self.current_token += char
elif self.current_state == 'line_comment':
if char == '\n':
self.current_state = 'start'
else:
self.current_state = 'line_comment'
#块注释
elif self.current_state == 'block_comment':
if char == '*':
self.current_state = 'block_comment_ending'
else:
self.current_state = 'block_comment'
#
elif self.current_state == 'block_comment_ending':
if char == '/':
self.current_state = 'start'
self.current_token = ''
else:
self.current_state = 'block_comment'
self.transition(char) # 处理当前字符
else:
self.tokens.append(('error', self.current_token))
self.current_state = 'start'
self.current_token = ''
self.transition(char) # 处理当前字符
# 分离单词
def tokenize(self, code):
for char in code:
# print(char)
self.transition(char)
return self.tokens
def evaluate_expression(expression):
tokens = tokenize2(expression)
token_index = 0
def parse_expression():
nonlocal token_index
left_operand = parse_term()
while token_index < len(tokens):
token = tokens[token_index]
if token == '+':
token_index += 1
right_operand = parse_term()
left_operand += right_operand
elif token == '-':
token_index += 1
right_operand = parse_term()
left_operand -= right_operand
else:
break
return left_operand
def parse_term():
nonlocal token_index
factor = parse_factor()
while token_index < len(tokens):
token = tokens[token_index]
if token == '*':
token_index += 1
factor *= parse_factor()
elif token == '/':
token_index += 1
divisor = parse_factor()
if divisor == 0:
raise ValueError("Division by zero")
factor /= divisor
else:
break
return factor
def parse_factor():
nonlocal token_index
token = tokens[token_index]
token_index += 1
if token == '(':
result = parse_expression()
if tokens[token_index] != ')':
raise SyntaxError("Unbalanced parentheses")
token_index += 1
return result
else:
try:
return float(token)
except ValueError:
raise ValueError("Invalid token: {}".format(token))
try:
return parse_expression()
except (IndexError, ValueError, ZeroDivisionError, SyntaxError) as e:
return str(e)
def tokenize2(expression):
# 分割表达式为单个token
tokens = []
current_token = ""
for char in expression:
if char in "+-*/()":
if current_token:
tokens.append(current_token)
tokens.append(char)
current_token = ""
else:
current_token += char
if current_token:
tokens.append(current_token)
return tokens
# 语法分析
# 语法分析
@app.get("/")
def index(request: Request):
return templates.TemplateResponse("index.html", {"request": request})
@app.post("/lex")
async def analyze(request: Request,file: UploadFile = File(None)):
# 文件类型数据
contents = await file.read()
text = contents.decode("utf-8")+" "
lexer = LexerStateMachine()
tokens = lexer.tokenize(text)
# print(tokens)
return templates.TemplateResponse("lex.html", {"request": request, "tokens": tokens,"text1":text,"file":file})
@app.post("/lex2")
async def analyze(request: Request,text: str = Form(None)):
# 文本类型数据
text1 = str(text)+"#"
# print(type(text1))
# print(text1)
lexer = LexerStateMachine()
tokens = lexer.tokenize(text1)
# print(tokens)
return templates.TemplateResponse("lex.html", {"request": request, "tokens": tokens,"text":text})
@app.post("/analyze")
async def analyze_syntax(request: Request):
data = await request.json()
text = data[0].get("value") # 获取列表中第一个元素的"value"字段
# print(text)
# 进行语法分析的逻辑
LEX=[x.get("value") for x in data]
global i
i=0
line=[]
# """
# E→E+T| E-T| T
# T→T*F| T/F| F
# F→(E)| i
# """
# """
# G’[E]:
# E → TE'
# E' → +TE'| -TE'|ε
# T → FT'
# T'→ *FT'|/FT'|ε
# F → (E)| i
# """
def E(): # E->TE'
if T() and G():
return True
else:
return False
def T(): # T->FT'
if F() and S():
return True
else:
return False
def G(): # E'->+TE'|-TE'|&
global i
if line[i]=='+' or line[i]=='-':
i=i+1
if T() and G():
return True
else:
return False
else:
return True
def S(): # T'->*FT|/FT|&
global i
if line[i]=='*' or line[i]=='/':
i=i+1
if F() and S():
return True
else:
return False
else:
return True
def F(): # F->(E)|i
global i
if line[i].isdigit():
i=i+1
return True
if line[i]=='(':
i=i+1
if E() and line[i]==')':
i=i+1
return True
return False
# print("递归下降分析程序")
for x in LEX:
line.append(x)
# print(line)
line.append("#")
if E() and line[i]=='#':
# print("true")
del line[-1]
result2 = evaluate_expression(line)
# print(result2)
result = ["分析结果:"+''.join(line)+" 为合法算式表达式","表达式计算结果为: "+str(result2)]
else:
del line[-1]
result = ["分析结果:"+''.join(line)+" 为非法的算式表达式","出错字符位于第"+str(i+1)+"处, 该字符为 "+line[i]]
return result
if __name__ == '__main__':
uvicorn.run(app)
index.html
<!DOCTYPE html>
<html>
<head>
<title>C Lexer</title>
</head>
<style>
body{
width: 1000px;
height:700px;
border: rgb(216, 168, 184) 1px solid;
margin: auto;
background-color: #02B8B6;
background-size: cover;
}
textarea {
resize: both;
}
</style>
<body>
<h1>C Lexer</h1>
<form action="/lex" method="post" enctype="multipart/form-data">
<label for="file">Choose a file to upload:</label><br>
<input type="file" id="file" name="file"><br><br>
<button type="submit">Upload File</button>
</form>
<br>
<form action="/lex2" method="post">
<label for="text">Or enter some text:</label><br>
<textarea id="text" name="text" rows="4" cols="50"></textarea><br><br>
<button type="submit">Submit Text</button>
</form>
</body>
</html>
lex.html
<!DOCTYPE html>
<html>
<head>
<title>C词法分析器</title>
<style>
.container {
display: flex;
flex-direction: row;
height: 100vh;
}
.left {
flex: 1;
min-width: 300px;
max-width: 33.33%;
overflow-y: scroll;
background-color: #608FB9;
padding: 20px;
}
.middle {
flex: 1;
overflow-y: scroll;
background-color: #608FB9;
padding: 20px;
display: flex;
flex-direction: column;
}
.right {
flex: 1;
overflow-y: scroll;
background-color: #608FB9;
padding: 20px;
}
.splitter {
position: relative;
width: 10px;
cursor: col-resize;
background-color: #ddd;
z-index: 1;
}
thead th {
position: sticky;
top: 0;
background-color: #608FB9;
}
table {
width: auto;
min-width: 400px;
table-layout: fixed;
}
td, th {
text-align: left;
}
/* 定义表格内容容器样式 */
.table-container {
max-height: calc(100vh - 40px); /* 减去上下padding的高度 */
overflow-y: scroll;
}
/* 定义按钮样式 */
.my-button {
background-color: #02B8B6; /* 设置背景颜色 */
color: black; /* 设置文本颜色 */
font-family: Arial, sans-serif; /* 设置字体 */
padding: 10px 20px; /* 设置填充 */
border-radius: 5px; /* 设置圆角 */
text-decoration: none; /* 移除下划线 */
}
/* 鼠标悬停时更改样式 */
.my-button:hover {
background-color: #0F8FAA; /* 更改背景颜色 */
color: white; /* 更改文本颜色 */
}
.result-container {
margin-top: 20px;
}
.result-container div {
margin-bottom: 10px;
overflow-wrap: break-word;
}
</style>
</head>
<body>
<div class="container">
<div class="left">
<!-- 添加按钮 -->
<a href="/" class="my-button">返回</a>
<table>
<tr>
{% if file %}
<h2>File uploaded: {{ file.filename }}</h2>
<h3><pre>{{ text1 }}</pre></h3>
{% endif %}
{% if text %}
<h2>Text submitted:</h2>
<h3><pre>{{ text }}</pre></h3>
{% endif %}
</tr>
</table>
</div>
<div class="splitter"></div>
<div class="middle">
<div class="table-container">
<table>
<thead>
<tr>
<th><h2>Type</h2></th>
<th><h2>Value</h2></th>
</tr>
</thead>
<tbody>
{% for token in tokens %}
<tr>
<td>{{ token[0] }}</td>
<td>{{ token[1] }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="splitter"></div>
<div class="right">
<button id="analyzeButton" class="my-button">进行语法分析</button>
<div class="result-container"></div>
</div>
</div>
<script>
const splitter = document.querySelectorAll('.splitter');
const leftPanel = document.querySelector('.left');
const middlePanel = document.querySelector('.middle');
const rightPanel = document.querySelector('.right');
const analyzeButton = document.getElementById('analyzeButton');
let isResizing = false;
splitter[0].addEventListener('mousedown', function (e) {
isResizing = true;
});
splitter[1].addEventListener('mousedown', function (e) {
isResizing = true;
});
document.addEventListener('mousemove', function (e) {
if (!isResizing) {
return;
}
const x = e.pageX;
const leftWidth = x - leftPanel.getBoundingClientRect().left;
const middleWidth = rightPanel.getBoundingClientRect().left - x;
const rightWidth = rightPanel.getBoundingClientRect().right - x;
leftPanel.style.flexBasis = `${leftWidth}px`;
middlePanel.style.flexBasis = `${middleWidth}px`;
rightPanel.style.flexBasis = `${rightWidth}px`;
});
document.addEventListener('mouseup', function (e) {
isResizing = false;
});
analyzeButton.addEventListener('click', function () {
const table = document.querySelector('.middle table');
const rows = table.querySelectorAll('tbody tr');
const data = [];
for (let i = 0; i < rows.length; i++) {
const columns = rows[i].querySelectorAll('td');
const type = columns[0].innerText;
const value = columns[1].innerText;
data.push({ type, value });
}
fetch('/analyze', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
})
.then(response => response.json())
.then(result => {
const resultContainer = document.querySelector('.result-container');
resultContainer.innerHTML = ''; // 清空容器
let index = 0;
const timer = setInterval(() => {
if (index >= result.length) {
clearInterval(timer);
return;
}
const item = result[index];
const listItem = document.createElement('div');
listItem.textContent = JSON.stringify(item).replace(/^"(.*)"$/, '$1');
resultContainer.appendChild(listItem);
index++;
}); // 每隔1秒显示一行语法分析结果
})
.catch(error => {
console.error('请求发生错误:', error);
});
});
</script>
</body>
</html>
实验结果分析及心得体会
通过对算术表达式文法的分析,我使用了递归下降分析法对任意输入的符号串进行了分析。这个实验让我更深入地理解了语法分析的原理和方法,并学会了如何通过递归下降分析法来构建分析器。
在实验中,我首先定义了算术表达式的文法规则,包括运算符+、-、*、/和括号()。然后,我使用递归下降分析法来逐步解析输入的符号串。
在编写分析器的过程中,我将每个文法规则转化为相应的递归函数,每个函数负责分析和处理对应的非终结符。通过递归调用这些函数,我可以逐步解析表达式,并判断其是否合法。如果遇到不合法的情况,我尽量在错误的产生式中定位问题,以便给出准确的错误信息。
通过完成这个实验,我对递归下降分析法有了更深入的了解。我认识到递归下降分析法是一种简单而有效的语法分析方法,尤其适用于上下文无关文法。通过递归下降分析法,我可以直观地理解文法规则和语法结构,并通过递归的方式逐步解析符号串。
在实验中,我也面临了一些挑战。在设计递归函数时,我需要考虑如何处理运算符的优先级和结合性,以确保表达式的正确计算顺序。此外,我还需要处理错误输入和异常情况,以提供准确的错误信息,帮助用户定位问题所在。
通过这个实验,我不仅学会了递归下降分析法,还了解了其他语法分析方法,如预测分析方法和算符优先分析方法。这些方法为我理解和构建编译器的各个阶段提供了基础。我相信这些经验将对我今后的语法分析和编译器设计工作有很大的帮助。
原创出品,如有不足,欢迎指正