一、编译过程
在计算机内部高级语言程序的处理一般有解释型和编译型两种处理方式。
解释型:
敲完一行代码点击回车,后台相关操作立刻进行处理如果有错误立刻反馈结果。不生成目标代码。源程序和编译程序参与目标程序的执行过程。效率相对低,可移植性好。
编译型:
整段代码编译完成,点击编译运行后台再进行处理,如果有错误再进行反馈。生成目标代码。源程序和编译程序不参与目标程序的执行过程。可移植性差。
编译型处理过程
词法分析
偏向于分析单词和关键字等是否拼写错误。
语法分析
偏向于这句话的结构是否正确,括号或者if-else等结构是否完整。通常有两类方法:
自底向上,是从文法开始符推文法句子。简单来说就是语法树的根推出叶子节点,如移进--约归。
自顶向下,从文法句子归约出开始符。例如递归下降分析法、预测分析法。
语义分析
是句子是否符合某个特定的类型。
编译时发现的语义错误称为静态语义错误,语法制导翻译是一种静态语义分析的方法;
运行时发现的语义错误,例如死循环等称为动态语义错误。
中间代码生成
中间代码与源程序等价。常见的中间代码有逆波兰式(后缀)、三段式、语法树、三地址码。
符号表
收集、记录、使用源程序中各个符号的类型和特征等相关信息。以表格形式进行存储,辅助语义的正确性检查和代码生成。
上述处理过程并不是每一种编译器都需要,其中词法、语法、语义分析与目标代码生成是必须的,代码优化和中间代码生成部分编译器可以不需要。
用C语言或C++写出的代码(.c/c++)→预处理(.i)→编译(.s,语法、词法分析,产生汇编语言)→汇编(.o,汇编语言转化为机器语言)→链接→可执行文件
程序注释
序言性
位于程序开头,给出程序整体说明,包括程序对硬、软件资源要求,重要变量和参数,程序作者、审查者、编程日期、修改日期,程序实现的功能描述。
功能性
嵌在源程序体中,描述程序中相关语句的作用,注释后的程序段需要实现什么功能。
关系
源程序是指用高级语言编写的程序,经过编译得到中间代码并最终得到目标代码。
中间代码是一种记号系统,不同的高级程序语言可以翻译成同一种中间代码。
目标代码就是可以在机器上执行的代码,此时源程序和编译程序都不再参与目标代码的执行过程。所以在机器上运行的目标程序是完全独立于源程序的。
反编译过程一般不能把可执行的文件还原成高级语言,一般是转换成功能上等价的汇编程序。
二、文法定义
一个文法做定义的时候是四元组的形式 G =(V,T,S,P);
注:在实际题目中,四元组中的V、T、S、P四项可能会调换位置。
S:起始符,语言最开始的符号;
T:终结符,语言最终的结果,其下再无分支;
V:非终结符,通常作为中转符作用于起始符和终结符之间,可以再推出其他符号,也可称作占位符,不被算作语言的组成部分,不是语言的最终结果。V∩T=空集
P:产生式,可理解为推导式,用终结符代替非终结符的规则;
类型 | 0型 | 1型 | 2型 | 3型 |
别称 | 短语文法 | 上下文有关文法,例:aS→ab | 上下文无关文法,例:S→ab | 正规文法 |
对于大多数通用程序设计语言,一般使用上下文无关法描述其语法。
三、语法推导树
例:
文法 G=({a,b},{S,A},S,P),其中:
产生式P为:
S→aAS|a;
A→SbA|SS|ba;
请构造句型aabAa的推导树。
解:
首先将产生式拆分:
S→aAS|a 是 S→aAS、S→a 的简写, A→SbA|SS|ba 是 A→SbA 、A→SS、A→ba的简写
故生产式P为:
S→aAS
S→a
A→SbA
A→SS
A→ba
句型aabAa可以由上述生产式推导而成:
由题意得起始符是S,S有S→aAS、S→a想要推得aabAa使用S→a并不合适,所以选用S→aAS;
推导树如下图:
例:
G=({S,A},{a,b},P,S),其中生产式P为:
S→aAS;
A→SbA;
A→SS;
S→a;
A→ba;
构造aabbaa的语法树。
句型aabbaa可以由上述生产式推导而成:
由题意得起始符是S,S有S→aAS、S→a想要推得aabba使用S→a并不合适,所以选用S→aAS;
在一个文法中,他的一个句型的语法树是唯一的,如果语法树不唯一则说明该文法有二义性,二义性会造成歧义,因此要求能够进行编译的语法必须是无二义性的。
三、有限自动机
形式:M=(S,Σ,δ,S0,Z)
S:有限集,每个元素为一个状态;
Σ:有穷字母表,每个元素为一个输入字符;
δ:转换函数,单值对照;
S0:属于S是唯一初态;
Z:一个终极态,可以空缺不写,一般图中双圈或者粗圈为终极态;
有限自动机可以用状态转换图表示
例:
设有限状态自动机:DFA=({S,A,B,C,f},{1,0},δ,S,{f}),
其中:δ(S,0)=B表示从元素S开始输入数字0可以到达元素B;δ(S,1)=A,δ(A,0)=f,δ(A,1)=C,δ(B,0)=C,δ(B,1)=f,δ(C,0)=f,δ(C,1)=f。
根据上述关系可得状态转换图如下:
例:
下图所示为一个有限自动机(其中,A是初态,C是终态),该自动机可以识别(C)。
A:0000 B:1111 C:0101 D:1010
该自动机可以识别的意思是,输入某串数字,可以从初态A到达终态C;
选项A:0000
含义是从初态A开始输入0000可以到达终态C,从初态A开始输入0到达B,再输入0在B循环,继续输入0还是在B循环,无法到达终态C,故排除A;
选项B:1111
含义是从初态A开始输入1111可以到达终态C,从初态A开始输入1在A循环,继续输入1还是在A循环、无法到达终态C,故排除B;
选项C:0101
含义是从初态A开始输入0101可以到达终态C,从初态A开始输入0到达B,再输入1从B到达C,继续输入0从C到达B,再输入1从B到达终态C,故选择C;
选项D:1010
含义是从初态A开始输入1010可以到达终态C,从初态A开始输入1循环A,再输入0到B,继续输入1到达C,最后输入0从C到达B,最终无法到达终态C,故排除D;
注
若某两个有限自动机,或者不确定有限自动机等价则说明二者可识别的记号完全一致。
四、正规式
正规式是有限自动机的另外一种表达形式。
例:
下面文法G[S]它无法识别(空(1)选D),此文法对应的正规式为(空(2)选C)。
G[S]: S→aA|bB; A→bS|b; B→aS|a;
空(1)选项A:ababab 选项B:bababa 选项C:abbaab 选项D:babba
无法识别的意思是无法用上述推导式推导出来。
选项D无法得出。
空(2)选项A:(a|b)* 选项B:(ab)* 选项C:(ab|ba)* 选项D:(ab)*(ba)*
公式
(a|b):可以解析出a,也可以解析出b
(a|b)*: *代表循环多次,从0到∞,极限小值可以表达空串;中间可以表达多个或单个a、b的各种组合,例如a,aa,aaa...;b,bb,bbb;ababbabbaa...等无序组合。
(ab)*:表示空串、单个ab、或多个ababababab组合
(ab|ba)*:表示空串、单个ab或单个ba、多个ababab或多个bababa、或者单个或多个ab于ba的任意组合。
(ab)* (ba)*:表示空串、多个或单个ab,然后多个或单个ba,无论单个或者多个此时必须ab组合在前,ba组合在后
第(2)个空的意思是,以下选项哪些正规式可以表示文法G(S)的组合,一个文法的组合一般有多种,例如空(1)中已经出现3种;而文法对应的正规式可以将所有的组合表示出来,但是范围也不宜过大,合适为准。
首先使用排除法,看以下的正规式能否表示(1)中出现的三种组合 ababab、bababa、abbaab。
(2)选项A:
(a|b)* 可以表示空串;单个或多个a;单个或多个b,以及单个或多个abbbaabb任意组合,可以表示ababab、bababa、abbaab,甚至可以表示空(1)中的选项D,可以表示但是范围过于宽泛,暂且保留选项A看后续选项是否有更合适的。
(2)选项B:
(ab)*可以表示空串;单个或多个ab,只可以表示出ababab其他2个表示不出,故排除选项B。
(2)选项C:
(ab|ba)*可以表示空串;单个或多个ab;单个或多个ba;单个或多个ab于ba的任意组合,可以表示出ababab、bababa、abbaab且无法表示出空(1)中选项D,比上述空(2)中选项A的范围要小,更精准,故排除选项A选择选项C。
(2)选项D:
(ab)* (ba)*可以表示空串、单个或多个abba,例如ba空串只有ab可以表示ababab;ab空串只有ba时可以表示bababa,当二者均不空串时只能表示abbaba不能表示abbaab,故排除选项D。
五、表达式
例:
一棵树如下图:
按照前续遍历得前缀表达式(+ab)
按照中续遍历得中缀表达式(a+b)
按照后续遍历得后缀表达式(ab+)
一般中缀表达式就是正常的式子,题目中给与的式子如果没有指明。一般为中缀表达式。
例题:
中缀表达式(a-b)*(c+5)的后缀式是(D)
A:abc5+*- B:ab-c+5* C:abc-*5+ D:ab-c5+*
首先根据其中缀表达式(a-b)*(c+5)画出树
根据树按照后续遍历可得后缀式:ab-c5+*。
六、函数调用-传值与传址
形参和实参
#include <stdio.h>
void swap1(int x,int y); //形参
void swap2(int *pa,int *pb); //形参
int main(void)
{
int a=5;
int b=6;
printf("输出原值:a=%d,b=%d\n",a,b);
swap1(a,b); //实参
printf("传值交换后的实际参数值:a=%d,b=%d\n",a,b);
swap2(&a,&b); //实参
printf("传址交换后实际参数值:a=%d,b=%d\n",a,b);
return 0;
}
void swap1(int x,int y)
{
int t;
t=x;
x=y;
y=t;
printf("传值交换结果:a=%d,b=%d\n",x,y);
}
void swap2(int *pa,int *pb)
{
int t=*pa;
*pa=*pb;
*pb=t;
printf("传址交换结果:a=%d,b=%d\n",*pa,*pb);
}
传值与传址
传递方式 | 主要特点 | 参数类型 |
传值调用 | 形参取的是实参的值,相当于把实际参数的值复制了一份作为传递给函数,因此形参的改变只是复制体的改变,不会导致调用点所传的本体实参的值发生改变 | 函数头内的参数是数值形式 |
传址调用 | 形参取的是实参得地址,相当于实际参数存储单元地址的引用,因此改变其值就相当于直接改变实际参数的值 | 函数头内的参数是指针形式 |
传值:
#include <stdio.h>
void swap(int x,int y);//传值
int main(void)
{
int a=5;
int b=6;
printf("输出原值:a=%d,b=%d\n",a,b);
swap(a,b);//将实际参数a,b的值传递给swap函数
printf("传值交换后的实际参数值:a=%d,b=%d\n",a,b);
return 0;
}
void swap(int x,int y)//传值
{
int t;
t=x;
x=y;
y=t;
printf("传值交换结果:a=%d,b=%d\n",x,y);//此处输出交换后的值
}
传址 :
#include <stdio.h>
void swap(int *pa,int *pb);
int main(void)
{
int a=5;
int b=6;
printf("输出原值:a=%d,b=%d\n",a,b);
swap(&a,&b);//将a,b的地址传递给swap函数
printf("传址交换后实际参数值:a=%d,b=%d\n",a,b);
return 0;
}
void swap(int *pa,int *pb)
{
int t=*pa;//*pa是变量a的地址所表示的值
*pa=*pb;
*pb=t;
printf("传址交换结果:a=%d,b=%d\n",*pa,*pb);
}
七、程序语言的特点
高级通用的程序设计语言一般会提供描述数据、运算、控制、数据传输的语言成分。
其中控制包括:顺序、选择、循环结构。
语言类型 | 特点 |
Fortran | 命令式设计语言,科学计算、执行效率高 |
Pascal | 命令式设计语言,为教学开发、表达能力强、衍生出Delphi |
Prolog | 逻辑推理、简洁性、表达能力、数据库和专家系统,适用于书写证明 |
Lisp | 人工智能、符号处理、函数式程序语言 |
ML | 函数式语言适用人工智能 |
Java | 面向对象、中间代码、跨平台 |
C | 命令式设计语言,指针操作能力强、高效 |
C# | 面向对象、中间代码、Net |
C++ | 面向对象、高效 |
SmallTalk | 面向对象 |
Python | 开源、跨平台、面向对象的解释型程序设计语言,是一种脚本语言,可动态编程 |
常见语言类型
Python
TensorFlow机器学习框架、pytorch机器学习库、keras人工神经网络库、matplotlip绘图库。
列表:有序的数据集合,其长度、元素类型、值皆可变。
元组:元素不可修改;
集合:元素无序、不重复。
字符串:元素不可修改。
Java
采用即时编译,基本类型、引用类型变量在栈中分配存储空间,而new创建出的对象、数组在堆中分配空间。
C/C++
C/C++语言编写的源程序需要经过:预处理---编译---链接---运行等阶段的处理。
栈区:编译器自动分配释放,存放函数参数、局部变量的值等。
堆区:由程序员分配释放或程序结束时候由OS回收。
静态数据区:存放全局变量。
程序代码区:存放程序代码。
函数
在程序执行过程中,系统用栈实现嵌套函数调用(递归调用)函数的正确返回。
低级语言
低级语言是面向机器的语言。可分为机器语言和汇编语言。 汇编语言与机器语言十分接近,使用助记符号提高程序可读性。