A. 编译原理
C语言编译过程详解
C语言的编译链接过程是要把我们编写的一个C程序(源代码)转换成可以在硬件上运行的程序(可执行代码),需要进行编译和链接。编译就是把文本形式源代码翻译为机器语言形式的目标文件的过程。链接是把目标文件、操作系统的启动代码和用到的库文件进行组织形成最终生成可执行代码的过程。过程图解如下:
从图上可以看到,整个代码的编译过程分为编译和链接两个过程,编译对应图中的大括号括起的部分,其余则为链接过程。
一、编译过程
编译过程又可以分成两个阶段:编译和汇编。
1、编译
编译是读取源程序(字符流),对之进行词法和语法的分析,将高级语言指令转换为功能等效的汇编代码,源文件的编译过程包含两个主要阶段:
第一个阶段是预处理阶段,在正式的编译阶段之前进行。预处理阶段将根据已放置在文件中的预处理指令来修改源文件的内容。如#include指令就是一个预处理指令,它把头文件的内容添加到.cpp文件中。这个在编译之前修改源文件的方式提供了很大的灵活性,以适应不同的计算机和操作系统环境的限制。一个环境需要的代码跟另一个环境所需的代码可能有所不同,因为可用的硬件或操作系统是不同的。在许多情况下,可以把用于不同环境的代码放在同一个文件中,再在预处理阶段修改代码,使之适应当前的环境。
主要是以下几方面的处理:
(1)宏定义指令,如 #define a b。
对于这种伪指令,预编译所要做的是将程序中的所有a用b替换,但作为字符串常量的 a则不被替换。还有 #undef,则将取消对某个宏的定义,使以后该串的出现不再被替换。
(2)条件编译指令,如#ifdef,#ifndef,#else,#elif,#endif等。
这些伪指令的引入使得程序员可以通过定义不同的宏来决定编译程序对哪些代码进行处理。预编译程序将根据有关的文件,将那些不必要的代码过滤掉
(3) 头文件包含指令,如#include "FileName"或者#include <FileName>等。
在头文件中一般用伪指令#define定义了大量的宏(最常见的是字符常量),同时包含有各种外部符号的声明。采用头文件的目的主要是为了使某些定义可以供多个不同的C源程序使用。因为在需要用到这些定义的C源程序中,只需加上一条#include语句即可,而不必再在此文件中将这些定义重复一遍。预编译程序将把头文件中的定义统统都加入到它所产生的输出文件中,以供编译程序对之进行处理。包含到C源程序中的头文件可以是系统提供的,这些头文件一般被放在/usr/include目录下。在程序中#include它们要使用尖括号(<>)。另外开发人员也可以定义自己的头文件,这些文件一般与C源程序放在同一目录下,此时在#include中要用双引号("")。
(4)特殊符号,预编译程序可以识别一些特殊的符号。
例如在源程序中出现的LINE标识将被解释为当前行号(十进制数),FILE则被解释为当前被编译的C源程序的名称。预编译程序对于在源程序中出现的这些串将用合适的值进行替换。
预编译程序所完成的基本上是对源程序的“替代”工作。经过此种替代,生成一个没有宏定义、没有条件编译指令、没有特殊符号的输出文件。这个文件的含义同没有经过预处理的源文件是相同的,但内容有所不同。下一步,此输出文件将作为编译程序的输出而被翻译成为机器指令。
第二个阶段编译、优化阶段。经过预编译得到的输出文件中,只有常量;如数字、字符串、变量的定义,以及C语言的关键字,如main,if,else,for,while,{,}, +,-,*,\等等。
编译程序所要作得工作就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码。
优化处理是编译系统中一项比较艰深的技术。它涉及到的问题不仅同编译技术本身有关,而且同机器的硬件环境也有很大的关系。优化一部分是对中间代码的优化。这种优化不依赖于具体的计算机。另一种优化则主要针对目标代码的生成而进行的。
对于前一种优化,主要的工作是删除公共表达式、循环优化(代码外提、强度削弱、变换循环控制条件、已知量的合并等)、复写传播,以及无用赋值的删除,等等。
后一种类型的优化同机器的硬件结构密切相关,最主要的是考虑是如何充分利用机器的各个硬件寄存器存放的有关变量的值,以减少对于内存的访问次数。另外,如何根据机器硬件执行指令的特点(如流水线、RISC、CISC、VLIW等)而对指令进行一些调整使目标代码比较短,执行的效率比较高,也是一个重要的研究课题。
2、汇编
汇编实际上指把汇编语言代码翻译成目标机器指令的过程。对于被翻译系统处理的每一个C语言源程序,都将最终经过这一处理而得到相应的目标文件。目标文件中所存放的也就是与源程序等效的目标的机器语言代码。目标文件由段组成。通常一个目标文件中至少有两个段:
代码段:该段中所包含的主要是程序的指令。该段一般是可读和可执行的,但一般却不可写。
数据段:主要存放程序中要用到的各种全局变量或静态的数据。一般数据段都是可读,可写,可执行的。
UNIX环境下主要有三种类型的目标文件:
(1)可重定位文件
其中包含有适合于其它目标文件链接来创建一个可执行的或者共享的目标文件的代码和数据。
(2)共享的目标文件
这种文件存放了适合于在两种上下文里链接的代码和数据。
第一种是链接程序可把它与其它可重定位文件及共享的目标文件一起处理来创建另一个 目标文件;
第二种是动态链接程序将它与另一个可执行文件及其它的共享目标文件结合到一起,创建一个进程映象。
(3)可执行文件
它包含了一个可以被操作系统创建一个进程来执行之的文件。汇编程序生成的实际上是第一种类型的目标文件。对于后两种还需要其他的一些处理方能得到,这个就是链接程序的工作了。
二、链接过程
由汇编程序生成的目标文件并不能立即就被执行,其中可能还有许多没有解决的问题。
例如,某个源文件中的函数可能引用了另一个源文件中定义的某个符号(如变量或者函数调用等);在程序中可能调用了某个库文件中的函数,等等。所有的这些问题,都需要经链接程序的处理方能得以解决。
链接程序的主要工作就是将有关的目标文件彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够被操作系统装入执行的统一整体。
根据开发人员指定的同库函数的链接方式的不同,链接处理可分为两种:
(1)静态链接
在这种链接方式下,函数的代码将从其所在地静态链接库中被拷贝到最终的可执行程序中。这样该程序在被执行时这些代码将被装入到该进程的虚拟地址空间中。静态链接库实际上是一个目标文件的集合,其中的每个文件含有库中的一个或者一组相关函数的代码。
(2) 动态链接
在此种方式下,函数的代码被放到称作是动态链接库或共享对象的某个目标文件中。链接程序此时所作的只是在最终的可执行程序中记录下共享对象的名字以及其它少量的登记信息。在此可执行文件被执行时,动态链接库的全部内容将被映射到运行时相应进程的虚地址空间。动态链接程序将根据可执行程序中记录的信息找到相应的函数代码。
对于可执行文件中的函数调用,可分别采用动态链接或静态链接的方法。使用动态链接能够使最终的可执行文件比较短小,并且当共享对象被多个进程使用时能节约一些内存,因为在内存中只需要保存一份此共享对象的代码。但并不是使用动态链接就一定比使用静态链接要优越。在某些情况下动态链接可能带来一些性能上损害。
我们在linux使用的gcc编译器便是把以上的几个过程进行捆绑,使用户只使用一次命令就把编译工作完成,这的确方便了编译工作,但对于初学者了解编译过程就很不利了,下图便是gcc代理的编译过程:
从上图可以看到:
预编译
将.c 文件转化成 .i文件
使用的gcc命令是:gcc –E
对应于预处理命令cpp
编译
将.c/.h文件转换成.s文件
使用的gcc命令是:gcc –S
对应于编译命令 cc –S
汇编
将.s 文件转化成 .o文件
使用的gcc 命令是:gcc –c
对应于汇编命令是 as
链接
将.o文件转化成可执行程序
使用的gcc 命令是: gcc
对应于链接命令是 ld
总结起来编译过程就上面的四个过程:预编译、编译、汇编、链接。了解这四个过程中所做的工作,对我们理解头文件、库等的工作过程是有帮助的,而且清楚的了解编译链接过程还对我们在编程时定位错误,以及编程时尽量调动编译器的检测错误会有很大的帮助的。
是否可以解决您的问题?
B. 编译原理问题:求解
E是文法开头。ε代表终结符号(推理中代表终点或结果,程序语言中代表常量等)。E T 这些大写字母一般代表非终结符号(这些代表中间过程,非结果。程序中代表函数等等)。开始是E。因为有个G(E)。E就是文法开始符号。推导就有E开始,它也是一个非终结符(代表函数、或者一个推导过程,类似于程序中的main(c++)、winmain(vc++)、dllmain(dll)等主函数)。
1算术表达式文法:这个文法是一个递归文法。计算机进行逻辑推导时会走很多弯路(类似于遍历一颗树的过程)。为了不让计算机走弯路(提高效率的目的),可以变换为第二种文法。这种文法消除了递归(消除了歧义,类似于后缀表达式),使计算机可以一条直线走到底儿推导出结果。
我也很久没看编译原理了。 呵呵
C. 【编译原理】第二章:语言和文法
上述文法 表示,该文法由终结符集合 ,非终结符集合 ,产生式集合 ,以及开始符号 构成。
而产生式 表示,一个表达式(Expression) ,可以由一个标识符(Identifier) 、或者两个表达式由加号 或乘号 连接、或者另一个表达式用括号包裹( )构成。
约定 :在不引起歧义的情况下,可以只写产生式。如以上文法可以简写为:
产生式
可以简写为:
如上例中,
可以简写为:
给定文法 ,如果有 ,那么可以将符号串 重写 为 ,记作 ,这个过程称为 推导 。
如上例中, 可以推导出 或 或 等等。
如果 ,
可以记作 ,则称为 经过n步推导出 ,记作 。
推导的反过程称为 归约 。
如果 ,则称 是 的一个 句型(sentential form )。
由文法 的开始符号 推导出的所有句子构成的集合称为 文法G生成的语言 ,记作 。
即:
例
文法
表示什么呢?
代表小写字母;
代表数字;
表示若干个字母和数字构成的字符串;
说明 是一个字母、或者是字母开头的字符串。
那么这个文法表示的即是,以字母开头的、非空的字符串,即标识符的构成方式。
并、连接、幂、克林闭包、正闭包。
如上例表示为:
中必须包含一个 非终结符 。
产生式一般形式:
即上式中只有当上下文满足 与 时,才能进行从 到 的推导。
上下文有关文法不包含空产生式( )。
产生式的一般形式:
即产生式左边都是非终结符。
右线性文法 :
左线性文法 :
以上都成为正则文法。
即产生式的右侧只能有一个终结符,且所有终结符只能在同一侧。
例:(右线性文法)
以上文法满足右线性文法。
以上文法生成一个以字母开头的字母数字串(标识符)。
以上文法等价于 上下文无关文法 :
正则文法能描述程序设计语言中的多数单词。
正则文法能描述程序设计语言中的多数单词,但不能表示句子构造,所以用到最多的是CFG。
根节点 表示文法开始符号S;
内部节点 表示对产生式 的应用;该节点的标号是产生式左部,子节点从左到右表示了产生式的右部;
叶节点 (又称边缘)既可以是非终结符也可以是终结符。
给定一个句型,其分析树的每一棵子树的边缘称为该句型的一个 短语 。
如果子树高度为2,那么这棵子树的边缘称为该句型的一个 直接短语 。
直接短语一定是某产生式的右部,但反之不一定。
如果一个文法可以为某个句子生成 多棵分析树 ,则称这个文法是 二义性的 。
二义性原因:多个if只有一个else;
消岐规则:每个else只与最近的if匹配。
D. 编译原理有有符号un-1.u=un吗
编译程序把源程序翻译为目标程序。根据源程序的语言种类,翻译程序可以分为汇编程序与编译程序。与之相对,解释程序是对源程序进行解释执行的程序。相应的可以将高级语言分为
编译型 C/C++, Swift, etc.
解释型 Python, javascript, etc.
混合型 Java, etc.
本文重点放在编译程序的设计上。典型的编译程序具有 7 77 个逻辑部分
对源程序扫描一次被称为一遍 (pass)。典型的一遍扫描编译程序有如下形式
通常将中间代码生成前的分析部分称为编译器的前端,其后的综合部分则被称为后端。这样就把一个编译程序分为了与源语言相关和与目标机有关的两个独立的部分,降低了程序的耦合。假设 llvm 编译器 支持 M MM 种源语言到 N NN 种目标语言的编译
传统的编译器如 gcc 可能需要开发 M × N M \times NM×N 个不同的子模块。而 llvm 使用统一的中间语言 llvm Intermediate Representation 只需要 M MM 个前端与 N NN 个后端,大大降低了开发成本。
文法
设非空有穷集合 Σ \SigmaΣ 为一字母表,则其上的符号串为 ∀ s ∈ Σ ∗ \forall s \in \Sigma^*∀s∈Σ
∗
,其中 ∗ *∗ 表示集合的闭包。特别的记 Σ 0 = ε \Sigma^0 = {\varepsilon}Σ
0
=ε 为空串组成的集合。规则通常写作
U : : = x or U → x , ∣ U ∣ = 1 , ∣ x ∣ ≥ 0 U ::= x\text{ or }U\rightarrow x,\quad |U| = 1, |x| \ge 0U::=x or U→x,∣U∣=1,∣x∣≥0
其中左部 U UU 是符号,右部 x xx 是有穷符号串。规则的集合 P PP 即可确定一个文法 G GG
<程序> ::= <常量说明><变量说明><函数说明>
<常量说明> ::= {const<常量定义>;}
<常量定义> ::= int<标识符>=<整数>{,<标识符>=<整数>}|char<标识符>=<字符>{,<标识符>=<字符>}
<变量说明> ::= {<类型标识符><变量定义>;}
<变量定义> ::= <标识符>[<下标>]{,<标识符>[<下标>]}
<下标> ::= '['<无符号整数>']' // <无符号整数>表示数组元素的个数,其值需大于0
<函数说明> ::= {(<类型标识符>|void)<函数定义>}void<主函数>
<函数定义> ::= <标识符>'('<参数表>')'<复合语句>
<参数表> ::= [<类型标识符><标识符>{,<类型标识符><标识符>}]
<主函数> ::= main'('')'<复合语句>
<复合语句> ::= '{'<常量说明><变量说明>{<语句>}'}'
<语句> ::= <条件语句>|'{'{<语句>}'}'|<函数调用语句>;|<赋值语句>;|<读语句>;|<写语句>;|<返回语句>;|;
<条件语句> ::= <if语句>|<while语句>|<do语句>|<for语句>
<if语句> ::= if'('<条件>')'<语句>[else<语句>]
<while语句> ::= while'('<条件>')'<语句>
<do语句> ::= do<语句>while'('<条件>')'
<for语句> ::= for'('<标识符>=<表达式>;<条件>;<标识符>=<标识符><加法运算符><无符号整数>')'<语句>
<条件> ::= <表达式>[<关系运算符><表达式>] // 表达式为0条件为假,否则为真
<函数调用语句> ::= <标识符>'('[<表达式>{,<表达式>}]')'
<赋值语句> ::= <标识符>['['<表达式>']']=<表达式>
<读语句> ::= scanf'('<标识符>{,<标识符>}')'
<写语句> ::= printf'('<字符串>[,<表达式>]')'|printf'('<表达式>')'
<返回语句> ::= return['('<表达式>')']
<表达式> ::= [<加法运算符>]<项>{<加法运算符><项>} // [+|-]只作用于第一个<项>
<项> ::= <因子>{<乘法运算符><因子>}
<因子> ::= <标识符>['['<表达式>']']|'('<表达式>')'|<整数>|<字符>|<函数调用语句>
<整数> ::= [<加法运算符>]<无符号整数>
<标识符> ::= <字母>{<字母>|<数字>}
<无符号整数> ::= <非零数字>{<数字>}|0
<数字> ::= 0|<非零数字>
<非零数字> ::= 1|...|9
<字符> ::= '<加法运算符>'|'<乘法运算符>'|'<字母>'|'<数字>'
<字符串> ::= "{十进制编码为32,33,35-126的ASCII字符}"
<类型标识符> ::= int|char
<加法运算符> ::= +|-
<乘法运算符> ::= *|/
<关系运算符> ::= <|<=|>|>=|!=|==
<字母> ::= _|a|...|z|A|...|Z
复制
上述文法使用扩充的 BNF 表示法进行描述
符号 定义 说明
∣ \vert∣ 或 作用域由括号限定
{ t } n m \{t\}^m_n{t}
n
m
将 t tt 重复连接 n ∼ m n \sim mn∼m 次 缺省时 m = ∞ , n = 0 m = \infin,\ n = 0m=∞, n=0
[ t ] [t][t] 符号串 t tt 可有可无 等价于 { t } 1 \{t\}^1{t}
1
( t ) (t)(t) 局部作用域 主要用于限定 ∣ \vert∣ 范围
相关概念有
概念 符号 定义 示例
识别符号 Z ZZ 文法中第一条规则的左部符号 <程序>
字汇表 V VV 文法中出现的全部符号 { <程序>, <常量说明>, …, 0, 1, … }
非终结符号集 V n V_nV
n
全部规则的左部组成的集合 { <程序>, <常量说明>, <变量说明>, … }
终结符号集 V t V_tV
t
V − V n V - V_nV−V
n
{ 0, 1, …, _, a, b, … }
设 U : : = u ∈ P U ::= u \in PU::=u∈P 则对于 ∀ x , y ∈ V ∗ \forall x, y \in V^*∀x,y∈V
∗
有直接推导 x U y ⇒ x u y xUy \Rightarrow xuyxUy⇒xuy 。如果 y ∈ V t ∗ y \in V_t^*y∈V
t
∗
则 x U y ⤃ x u y xUy\ ⤃\ xuyxUy ⤃ xuy 称为规范推导。直接推导序列 u 0 ⇒ u 1 ⇒ ⋯ ⇒ u n u_0 \Rightarrow u_1 \Rightarrow \cdots \Rightarrow u_nu
0
⇒u
1
⇒⋯⇒u
n
可简记为
{ u 0 ⇒ + u n n > 0 u 0 ⇒ ∗ u n n ≥ 0 \begin{cases} u_0 \mathop\Rightarrow\limits^+ u_n & n > 0\\ u_0 \mathop\Rightarrow\limits^* u_n & n \ge 0\\ \end{cases}{
u
0
⇒
+
u
n
u
0
⇒
∗
u
n
n
>
0
n
≥
0
进一步定义
句型 V ∗ ∋ x ⇐ ∗ Z V^* \ni x \mathop\Leftarrow\limits^* ZV
∗
∋x
⇐
∗
Z
句子 V t ∗ ∋ x ⇐ + Z V_t^* \ni x \mathop\Leftarrow\limits^+ ZV
t
∗
∋x
⇐
+
Z
语言 L ( G ) = { x ∣ x is sentence } L(G) = \{ x| x\text{ is sentence} \}L(G)={x∣x is sentence}
如果文法 G GG 和 G ′ G'G
′
有 L ( G ) = L ( G ′ ) L(G) = L(G')L(G)=L(G
′
) ,则称这两个文法等价。设 w = x u y w=xuyw=xuy 为一句型,称 u uu 为一个相对于 U ∈ V n U \in V_nU∈V
n
的
w ww 的短语 如果 Z ⇒ ∗ x U y ∧ U ⇒ + u Z \mathop\Rightarrow\limits^* xUy \land U \mathop\Rightarrow\limits^+ uZ
⇒
∗
xUy∧U
⇒
+
u
w ww 的简单短语 如果 u uu 是短语且 U ⇒ u U \mathop\Rightarrow\limits uU⇒u
句型的最左简单短语称为句柄。
二义性
文法 G GG 是二义性的,如果 ∃ x ∈ L ( G ) \exist x \in L(G)∃x∈L(G) 使下列条件之一成立
x xx 可以对应两颗不同的语法树
x xx 有两个不同的规范推导
E. java中的正则表达式跟编译原理有什么联系
首先,正则表达式不仅在Java里有,其它语言里面也有,它是一个数学上的概念,各个语言中的正则表达式是它的不同形式的实现。
其次,编译原理的词法分析里,会用到正则表达式去匹配源程序中的各种token(记号),比如说
int a = 8;
里识别出:
类型名:int
变量名:a
运算符:=
数字:8
结尾分号:;
总之,二者有联系,但不是一回事。
F. 试编程确定使得整个算式成立的数字组合,如有多种情况,请给出所有可能的答案。 l 计算24是流行的扑克游戏
/*
程序主要使用了正则表达式(编译原理的内容)和计算用字符串表示的表达式两种技术。
算24点的一般形式,用正则表达式表示有一下几种:
( ( ( E O E ) O E ) O E ) = 24
( ( E O ( E O E ) ) O E ) = 24
( ( E O E ) O ( E O E ) ) = 24
( ( E O E ) O ( E O E ) ) = 24
( E O ( ( E O E ) O E ) ) = 24
( E O ( E O ( E O E ) ) ) = 24
其中 E 表示数字,O表示操作符 。
程序的思想就是穷举法,把上面六个式子中的 E 用合法的数字替换,
O 用合法的操作符替换,看是否能得出结果。显然计算结果时还要
计算字符串表示的表达式。
例如:( ( ( 1 + 2 ) + 3 ) * 4 ) = 24 就是一种替换 ,等号左边是一个
用字符串表示的表达式。
这种方法的【优点】是思路简单,而且容易扩展 。
(如不是用四个数字而是任意个,并且可以使用加减乘除以外的运算)
当然这种方法的【缺点】是产生大量我们一般认为重复的式子。
如 ( ( ( 1 + 2 ) + 3 ) * 4 ) = 24 和 ( ( 1 + ( 2 + 3 ) ) * 4 ) = 24
去掉不必要的括号和,都能化成:
(1 + 2 + 3 )*4 = 24 ,因此一般我们认为上面这两个式子是相同的。
*/
#include <iostream>
#include <cstring>
#include <cmath>
#include <string>
#include <ctime>
#include <cstdlib>
using namespace std;
const int MAX_AVAILABAL_NUMBER_NUM = 20;
const int MAX_AVAILABAL_OPERATOR_NUM = 10;
const int MAX_REGULAR_EXPRESSION_NUM = 100;
const int MAX_LEAGAL_EXPRESSION_NUM = 4000;
const int MAX_LEN = MAX_AVAILABAL_NUMBER_NUM * 10;
#define SAVE 0
#define PRINT 1
#define EMPTY -1
// 将整数 num 装换成字符串保存着 s 中
char * Num_To_Str(char s[],int num)
{
int len = (int)log10(num)+1;
s[len--]='\0';
do
{
s[len--]=(num%10)+'0';
num/=10;
}while(num);
return s;
}
// 替换字符 s[p] 为字符串 t
char* Replace(char s[],int p,char t[])
{
int ls,lt,i,j,k;
ls = strlen(s);
lt = strlen(t);
for(i=ls+lt-1;i>=(p+lt);i--)
s[i]=s[i-lt+1];
for(j=p,k=0;t[k]!='\0';j++,k++)
s[j]=t[k];
return s;
}
typedef struct
{
int number;
int appear_time;
}Node;
//记录产生表达式所需的信息
class Expression_Imformation
{
public:
void Init();
void Init_All_Answer();
void Init_Calculate();
void Init_Guess();
void Generate_Expression(char start[],char rule[],int T,int t);
void Calculate(int num_of_chose_number,int num_of_chose_operator);
int Precedence(char op);
double Calculate_Expression(char *S1,bool & Is_A_Leagal_Expression );
char* Transform(char *S1 ,bool & Is_A_Leagal_Expression);
private:
int num_of_used_number; // 产生表达式需要数据个数,默认为 4 个数据
double reslut; // 表达式的结果,默认为 24
// 可使用的数据范围,默认 1-13,每个数据可出现多次
int num_of_available_number;
Node available_number[MAX_AVAILABAL_NUMBER_NUM];
// 可使用的符号类型,默认加减乘除
int num_of_available_operator;
char available_operator[MAX_AVAILABAL_OPERATOR_NUM][5];
// 正则表达式
// 如 ( ( E O E ) O ( E O E ))
// 其中 E 表示数据,O 表示操作符
int num_of_regular_expression;
char regular_expression[MAX_REGULAR_EXPRESSION_NUM][MAX_LEN];
// 符合结果的表达式
int num_of_leagal_expression;
char leagal_expression[MAX_LEAGAL_EXPRESSION_NUM][MAX_LEN];
//临时变量,Generate_Expression 函数使用
int chose_number[MAX_AVAILABAL_NUMBER_NUM];
char chose_operator[MAX_AVAILABAL_NUMBER_NUM-1][5];
int save_or_print; // 控制结果直接输出还是保存
};
void Expression_Imformation::Init()
{
num_of_used_number = 4;
reslut = 24.0;
num_of_available_operator = 4;
strcpy(available_operator[0],"+");
strcpy(available_operator[1],"-");
strcpy(available_operator[2],"*");
strcpy(available_operator[3],"/");
//产生正则表达式
num_of_regular_expression = 0;
char start[MAX_LEN] = "E";
char rule[MAX_LEN] = "( E O E )";
Generate_Expression(start,rule,num_of_used_number-1,0);
// cout<<num_of_regular_expression<<endl;
// for(int i=0;i<num_of_regular_expression;i++)
// cout<<i+1<<" "<<regular_expression[i]<<endl;
num_of_leagal_expression = 0;
save_or_print = PRINT;
}
void Expression_Imformation::Init_All_Answer()
{
Init();
cout<<"**********All_AnswerS**********"<<endl;
num_of_available_number = 13;
for(int i=0;i<num_of_available_number;i++)
{
available_number[i].number = i+1;
available_number[i].appear_time = num_of_used_number;
}
save_or_print = PRINT;
num_of_leagal_expression = 0;
Calculate(0,0);
}
void Expression_Imformation::Init_Calculate()
{
int i,j,temp;
char option[MAX_LEN];
Init();
while(1)
{
cout<<"***********Calculate***********"<<endl;
num_of_available_number = 0;
for(int i=0;i<num_of_used_number;i++)
{
cout<<"Input the "<<i+1<<"th number : ";
cin>>temp;
for(j=0;j<num_of_available_number;j++)
if(temp == available_number[j].number)
break;
if(j<num_of_available_number)
available_number[j].appear_time++;
else
{
available_number[j].number = temp;
available_number[j].appear_time = 1;
num_of_available_number++;
}
}
save_or_print = PRINT;
num_of_leagal_expression = 0;
Calculate(0,0);
if(num_of_leagal_expression == 0)
cout<<"No Solution!"<<endl;
cout<<"continue?(Y/N)"<<endl;
cin>>option;
if(!( strcmp(option,"Y")==0 || strcmp(option,"y")==0 ))
return ;
}
}
void Expression_Imformation::Init_Guess()
{
char option[MAX_LEN];
char answer[MAX_LEN];
int i,j,temp;
bool Is_A_Leagal_Expression;
int try_time = 3;
Init();
srand(time(NULL));
while(1)
{
cout<<"*************Guess*************"<<endl;
num_of_leagal_expression = 0;
while( num_of_leagal_expression == 0)
{
num_of_available_number = 0;
for(i=0;i<num_of_used_number;i++)
{
temp = rand()%13+1;
for(j=0;j<num_of_available_number;j++)
if(temp == available_number[j].number)
break;
if(j<num_of_available_number)
available_number[j].appear_time++;
else
{
available_number[j].number = temp;
available_number[j].appear_time = 1;
num_of_available_number++;
}
}
save_or_print = SAVE;
Calculate(0,0);
}
cout<<"Question : ";
for(i=0;i<num_of_available_number;i++)
for(j=0;j<available_number[i].appear_time;j++)
cout<<available_number[i].number<<" ";
cout<<endl;
try_time = 3;
while(try_time--)
{
cout<<"Your answer: ";
cin>>answer;
if(Calculate_Expression(answer,Is_A_Leagal_Expression) == reslut && Is_A_Leagal_Expression == true)
{
cout<<"Right!"<<endl;
break;
}
else
cout<<"Wrong!"<<endl;
}
for(i=0;i<num_of_leagal_expression;i++)
cout<<"Solution #"<<i+1<<" :"<<endl<<leagal_expression[i]<<endl<<endl;
cout<<"continue?(Y/N)"<<endl;
cin>>option;
if(!( strcmp(option,"Y")==0 || strcmp(option,"y")==0 ))
return ;
}
}
void Expression_Imformation::Generate_Expression(char start[],char rule[],int T,int t)
{
int i;
char temp[MAX_LEN];
if(t==T)
{
strcpy(regular_expression[num_of_regular_expression++],start);
return;
}
else
{
for(i=0;start[i]!='\0';i++)
if(start[i]=='E')
{
strcpy(temp,start);
Replace(start,i,rule);
Generate_Expression(start,rule,T,t+1);
strcpy(start,temp);
}
}
}
void Expression_Imformation::Calculate(int num_of_chose_number,int num_of_chose_operator)
{
int i,j,p,counter;
int p_chose_number,p_chose_operator;
char temp[MAX_LEN],int_to_num[MAX_LEN];
bool Is_A_Leagal_Expression;
if(num_of_chose_number == num_of_used_number)
{
if(num_of_chose_operator == (num_of_chose_number - 1))
{
//枚举所有正则表达式
for(p=0;p<num_of_regular_expression;p++)
{
strcpy(temp,regular_expression[p]);
p_chose_number = p_chose_operator = 0;
// 替换正则表达式中的 E (数字)和 O(操作符)
for(i=0;temp[i]!='\0';i++)
{
if(temp[i]=='E')
{
Replace(temp,i,Num_To_Str(int_to_num,chose_number[p_chose_number]));
i+=strlen(int_to_num);
p_chose_number++;
}
else if(temp[i]=='O')
{
Replace(temp,i,chose_operator[p_chose_operator]);
i+=strlen(chose_operator[p_chose_operator]);
p_chose_operator++;
}
}//for(i)
if(reslut == Calculate_Expression(temp,Is_A_Leagal_Expression))
{
if(save_or_print == PRINT)
cout<<"Soultion #"<<(num_of_leagal_expression+1)<<" : "<<endl<<temp<<endl<<endl;
else
strcpy(leagal_expression[num_of_leagal_expression],temp);
num_of_leagal_expression++;
}
}//for(p)
}
else
{
for(i=0;i<num_of_available_operator;i++)
{
strcpy(chose_operator[num_of_chose_operator],available_operator[i]);
Calculate(num_of_chose_number,num_of_chose_operator+1);
}
}
}
else
{
for(i=0;i<num_of_available_number;i++)
{
counter=0;
for(j=0;j<num_of_chose_number;j++)
if(chose_number[j] == available_number[i].number)
counter++;
if(counter<available_number[i].appear_time)
{
chose_number[num_of_chose_number] = available_number[i].number;
Calculate(num_of_chose_number+1,num_of_chose_operator);
}
}
}
}
// 运算符优先级
int Expression_Imformation::Precedence(char op)
{
switch(op)
{
case'+':
case'-':
return 1;
case'*':
case'/':return 2;
case'^':return 3;
case'(':
case'@':
default:
return 0;
}
}
// 将字符串 S1 中序表达式转化成前序表达式
char* Expression_Imformation::Transform(char *S1 ,bool & Is_A_Leagal_Expression)
{
char stack[MAX_LEN];
int stack_top = EMPTY;
char * S2 = new char[strlen(S1)];
int i=0,j=0;
char ch=S1[i];
stack[++stack_top] = '@';
Is_A_Leagal_Expression = true;
while(ch)
{
if(ch==' ') ch=S1[++i];
else if(ch=='(')
{
stack[++stack_top] = ch;
ch=S1[++i];
}
else if(ch==')')
{
while(stack_top!=EMPTY && stack[stack_top]!='(')
S2[j++]=stack[stack_top--];
if(stack_top == EMPTY)
{
Is_A_Leagal_Expression = false;
goto Label;
}
stack_top--;
ch=S1[++i];
}
else if(ch=='+'||ch=='-'||ch=='*'||ch=='/'||ch=='^')
{
if(stack_top == EMPTY)
{
Is_A_Leagal_Expression = false;
goto Label;
}
char w=stack[stack_top];
while(Precedence(w)>=Precedence(ch))
{
S2[j++]=w;
stack_top--;
if(stack_top == EMPTY)
break;
w=stack[stack_top];
}
stack[++stack_top] = ch;
ch=S1[++i];
}
else
{
if((ch<'0'||ch>'9')&&ch!='.')
{
Is_A_Leagal_Expression = false;
goto Label;
}
while((ch>='0'&&ch<='9')||ch=='.')
{
S2[j++]=ch;
ch=S1[++i];
}
S2[j++]=' ';
}
}//end while
if(stack_top == EMPTY)
{
Is_A_Leagal_Expression = false;
goto Label;
}
ch=stack[stack_top--];
while(ch!='@')
{
if(ch=='(')
{
Is_A_Leagal_Expression = false;
goto Label;
}
else
{
S2[j++]=ch;
ch=stack[stack_top--];
}
}
S2[j++]='\0';
strcpy(S1,S2);
Label:
delete []S2;
return S1;
}
// 计算表达式 S1 的值
double Expression_Imformation::Calculate_Expression(char *S1,bool & Is_A_Leagal_Expression )
{
double S[MAX_LEN];
int S_top = EMPTY;
double x,y;
int i=0,k;
Node check[MAX_AVAILABAL_NUMBER_NUM];
int check_p = 0;
char *str = new char [strlen(S1)];
strcpy(str,S1);
Transform(str,Is_A_Leagal_Expression);
if(Is_A_Leagal_Expression == false)
return 0.0;
while(str[i])
{
if(str[i]==' ')
{
i++;
continue;
}
switch(str[i])
{
case '+':
if(S_top<1)
{
Is_A_Leagal_Expression = false;
goto Label;
}
x=S[S_top]+S[S_top-1];
S_top-=2;
i++;
break;
case '-':
if(S_top<1)
{
Is_A_Leagal_Expression = false;
goto Label;
}
x=S[S_top-1] - S[S_top];
S_top-=2;
i++;
break;
case '*':
if(S_top<1)
{
Is_A_Leagal_Expression = false;
goto Label;
}
x=S[S_top-1] * S[S_top];
S_top-=2;
i++;
break;
case '/':
if(S_top<1)
{
Is_A_Leagal_Expression = false;
goto Label;
}
x=S[S_top-1] / S[S_top];
S_top-=2;
i++;
break;
case '^':
if(S_top<1)
{
Is_A_Leagal_Expression = false;
goto Label;
}
x=pow(S[S_top-1],S[S_top]);
S_top-=2;
i++;
break;
default:
x=0;
while(str[i]>='0'&&str[i]<='9')
{
x=x*10+str[i]-'0';
i++;
}
if(str[i]=='.')
{
i++;
y=0;
double j=10.0;
while(str[i]>='0'&&str[i]<='9')
{
y=y+(str[i]-'0')/j;
i++;
j*=10;
}
x+=y;
// 记录输入的数字
for(k=0;k<check_p;k++)
if(check[k].number == (int)x)
check[k].appear_time++;
if(k==check_p)
{
check[check_p].number = (int)x;
check[check_p].appear_time =1;
check_p++;
}
}
}//switch
S[++S_top]=x;
}//end while
if(S_top == EMPTY)
{
Is_A_Leagal_Expression = false;
goto Label;
}
// 检查输入数字的合法性
for(k=0;k<check_p;k++)
{
for(i=0;i<num_of_available_number;i++)
if(available_number[i].number == check[k].number && available_number[i].appear_time >= check[check_p].appear_time)
break;
if(i==num_of_available_number)
break;
}
if(k < check_p)
{
Is_A_Leagal_Expression = false;
goto Label;
}
x=S[S_top--];
Label:
return x;
}
int main(int argc, char *argv[])
{
class Expression_Imformation game;
// 用户输入数字,系统求解(给出全部解)
game.Init_Calculate();
// 系统给出数字,用户求解(保证有解) 。用户必须用系统给出的数字求解,最多试3次
game.Init_Guess();
// 给定数据范围(如1-13)和可使用的操作符(如加减乘除),求出所有解
game.Init_All_Answer();
return 0;
}