一、递归函数的非递归实现
递归的本质是函数调用,而函数调用是通过栈实现的。因此,递归可以用栈消除。
(1)函数调用的实现
设置一个栈模拟函数调用。当发生函数调用时,将当前的函数的现场进栈。
(2)递归
l 递归是一种特殊的函数调用,是在一个函数中又调用了函数本身。
l 递归程序的本质是函数调用,而函数调用时要花费额外的时间和空间
l 在系统内部,函数调用时用栈来实现,如果程序员可以自己控制这个栈,就可以消除递归调用。
二、快速排序
①快速排序的递归实现
void quicksork(int a[],int low,int high)
{
int mid;
if(low>=high)
return;
mid = divide(a,low,high);
quicksort(a.low.mid-1);
quicksort(a,mid+1,high);
}
②快速排序的非递归实现
设置一个栈,记录要做的工作,即要排序的数据段。
先将整个数组进栈,然后重复下列工作,直到栈空:
—从栈中弹出一个元素,即一个排序区间
—将排序区间分成两半
—检查每一半。如果多于两个元素,则进栈。
栈元素的格式:
struct node
{
int left;
int right;
}
例如:
Void quicksort(int a[],int size)
{
seqStack<node>st;
int mid,start,finish;
node s;
if(size<=1) return;
//排序整个数组
s.left=0;
s.right =size-1;
st.push(s);
while(!st.isEmpty())
{
s=st.pop();
start =s.left;
finish = s.right;
mid =divide(a,start,finish);
if(mid-start>1)
{
s.left =start;
s.right = mid-1;
st.push(s);
}
if(finish - mid>1)
{
s.left =mid +1;
s.right =finish;
st.push(s);
}
}
}
二、括号配对检查
(1)基本概念
编译程序的任务之一,就是检查括号是否配对。如:括号(、【和{后面必须依次跟随相应的}】),“后面必须有”。
简单地用开括号和比括号的数量是否相等来判断开括号与闭括号是否配对是不行的。;例如。符号串【()】是正确的,而符号串(【)】是不正确的,因为当遇到)那样的闭括号时,它与最近遇到开括号匹配。
(2)使用栈解决符号配对
使用栈,遇到左括号进栈,见到右括号,则出栈,并比较二者是否配对
如判断(3+(5-2)*(6+4)/2)
(3+((5-2)*(6+4)/2)
“abc”
“n”
(a[3]+a[3]-’a’)
(3)算法
① 首先创建一个空栈
② 从源程序中读入符号
③ 如果读入的符号是开符号,那么就将其进栈
④ 如果读入的符号是一个闭符号但栈是空的,出错。否则,将栈中的符号 出栈。
⑤ 如果出栈的符号和读入的符号不匹配,出错。
⑥ 继续从文件中读入下一个符号,非空则转向三,否则执行七。
⑦ 如果栈非空,报告出错,否则括号匹配成功
(4)细节问题
如果对c++的源程序使用此算法,至少需要考虑三种括号:圆括号、方括号和花括号
但有时又不需要考虑圆括号、花括号、方括号是否匹配的问题。例如,当括号出现在注释、字符串常量。字符常量中时,就不需要考虑它的匹配问题。
在C++中有很多转义字符,因此在读入一个字符时,还必须考虑如何识别转义字符。
(5)设计一个类balance
l 对象初始化时传给它一个源文件名
l 这个类提供一个成员函数checkBalance检查源文件中的符号是否配对,输出所有不匹配的符号及所在的行号。
①类的定义
Class balance
{
Private:
Ifstream fin;//待检查的文件流
Int currentLine;
Int Errors;
Struct Symbol
{
Char Token;
Int TheLine;
};
Enum CommentType
{
SlashSlash,
SlashStar;
};
Public:
Balance(const char *s);
Int CheckBalance();
Private:
Bool CheckMatch(char Symb1,char Symb2,
Int Line1,int Line2);
Char GetNextSymbol();
Void PutBackChar(char ch);
Void SkipComment(enum CommentType type);
Void SkipQuote(char type);
Char NextChar();
};
Class noFile{};
Balance::balance(const char*s)
{
Fin.open(s);
If(!fin) throw noFile();
currentLine =1;
Errors = 0;
}
CheckBalance的实现
l 检查输入流对象中的括号是否匹配,并返回错误数
l 算法的实现需要用到一个栈,我们可以用本章中的栈类,如seqStack.
l 采用逐步精细化的方法分解这个函数
I.自顶向下的分解
初始化栈为空:
While(lastChar = 读文件,直到读入一括号)
Switch(lastChar)
{
Case‘{’,‘【’,‘(’:进栈
Case ‘}’,‘】’,‘)’:
If(栈空)
输出某行符号不匹配,出错数加1;
Else
{
Match =出栈的符号;
检查lastChar与match是否匹配;
如不匹配,输出出错信息,出错数加1;
}
}
If(栈非空)
栈中元素均没有找到匹配的闭符号,输出这些错误
Return 出错数
II.进一步需要细化
l 读文件,知道读入一括号
l 输出某行某符号不匹配;出错数加1;
l 检查lastChar与match是否匹配。如不匹配,输出出错信息,出错数加1;
l 栈中元素均没有找到匹配的闭符号,输出这些错误
III.进一步抽取子函数
第一项工作:GetNextSymbol
l 功能:从文件的当前位置开始,跳过所有的非括号,读到第一个括号后返回。在遇到文件结束时,返回一个待定的符号,如NULL
l 函数原型:函数有一个字符型的返回值。执行该函数必须有一个输入流,在读输入流的过程中,当前处理的行号会变,在读的过程中也可能遇到异常情况,因此出错数也会变。这三个信息:文件流对象,当前处理的行号,出错个数都是对象的数据成员。因此函数原型为:char GetNextSymbol()。
第二项工作:CheckMatch
l 功能:比较两个指定位置的待比较的符号是否匹配
l 函数原型:bool balance::CheckMatch(char Symb1,char Symb2,int Line1,int Line2)
③CheckBalance的定义
CheckBalance函数不需要参数,因此输入流对象是类的数据成员。返回值是一个整型数,表示出错个数。
int balance::CheckBalance()
{
struct Symbol node;
seqStack<Symbol>st;
char LastChar,Match;
while(LastChar)
{
case’(’:case’[’:case’{’:
node.Token=LastChar;
node.TheLine = currentLine;
st.push(node);
break;
case’)’:case’]’:case”}”:
if(st.isEmpty())
{
Errors++;
cout<<”在第”<<currentLine<<
”有一个多余的”<<LastChar<<endl
}
else
{
node = st.pop();
Match = node.Token;
if(!CheckMatch(Match,LastChar,
node.TheLine,currentLine))
++Errors;
}
break;
}
while(!st.isEmpty())
{
Errors++;
node=st.pop();
cout<<”第”<<node.TheLine<<”行的符号”<<node.Tolen<<”不匹配”<<endl;
}
return Errors;
}
④CheckMatch定义
bool balance::CheckMatch(char Symb1,char Symb2,int Line1,int Line2)
{
if(Symb1 ==’(’&&Symb2!=’)’||ymb1 ==’[’&&Symb2!=’]’||ymb1 ==’{’&&Symb2!=’}’)
{
cout<<”发现第”<<Line2<<”的符号”<<Symb2<<”与第”<<Line1
<<”的符号”<<Symb1<<”不匹配”<<endl;
return false;
}
else
return true;
}
⑤GetNextSymbol定义
l 在读文件时要跳过其他符号,提取出各类括号
l 注释中的括号不用考虑,字符串常量和字符常量中的括号也不用考虑。
l C++中的注释又有两种形式。一种是以“//”开始到本行结束。另一种是以“/*”开始到“*/”结束,可以跨行。不管是用哪一种注释,都要判断两个符号才能确定
Char GetNextSymbol()
{
while(ch = 从文件中读入下一字符)
{
if(ch ==’/’)
{
if(Ch=从文件中读入下一字符)
if(Ch ==’*’)
跳过C的注释
else if(Ch==’/’)
跳过C++的注释
else
不是注释,把ch放回输入流
}
else if(ch == ‘\’||ch == ‘”’)
跳过字符常量或字符串常量
else if(ch ==’{’||ch ==’[||ch ==’(||ch ==’)’||ch ==’]’||ch ==’}’)
return ch;
}
return 0;
}
进一步提取子函数
l 从文件读入下一字符:char NextChar();
l 跳过的注释:void SkipComment(enum CommentType type);
l 把ch放回输入流:void PutBackChar(char ch);
l 跳过字符常量或字符串常量:void SkipQuote(char type);
GetNextSymbol函数的实现
Char balance::GetNextSymbol()
{
char ch;
while (ch ==NextChar())
{
if(ch ==’/’)
{
Ch=NextChar();
if(Ch == ‘*’)
SkipComment(SlashStar);
else if(Ch == ‘/’)
SkipComment(SlashSlash);
else
PutBackChar(ch);
}
else if(ch ==’\’||ch ==’”’)
SkipQuote(ch);
else if(ch ==’{’||ch ==’[||ch ==’(||ch ==’)’||ch ==’]’||ch ==’}’)
return ch;
}
return null;
}
I.NextChar函数的实现
l NextChar函数从输入文件流中读入一个字符,返回给调用程序,遇到文件结束符,返回NULL。如遇到换行符,则当前处理行加1。
Char balance::NextChar()
{
char ch;
if((ch =fin.get()) == EOF)
return NULL;
if(ch == ‘\n’)
currentLine++;
return ch;
}
II.PutBackChar函数的实现
l PutBackChar函数将传入的字符放回输入流,这是通过条用输入流对象的成员行数putback实现的。如放回的字符时回车,当前行处理号减1.
void balance::PutBackChar(char ch)
{
fin.putback(ch);
if(ch == ‘\n’)
currentLine--;
}
III.SkipQuote
l 读文件,知道读到一个和参数值相同的符号。
l 注意几个特殊情况:
Ø 字符或数字串常量是不允许跨行的。也就是说,如果在读文件的过程中遇到了回车,则表示字符或字符串常量的开始和结束符不配,输出出错信息。
Ø 在字符或字符串常量中可能会包含单引号或双引号,此时不能讲这个单引号或双引号作为结束符。如何知道这个单引号或双引号代表的事普通的字符而不是结束符呢?C++采用了转义字符来表示,因此当读到一个表示转义字符开始的标记(\)时,不管它后面是什么符号,都不用检查。
Void balance::SkipQuote(char type)
{
Char ch;
While((ch=NextChar()))
{
If(ch==type)
Return ;
Else
{
Errors++;
Cout<<”missing closing quote at line”<<currentLine<<endl;
Return ;
}
Else if(ch==’\\’)
Ch=NextChar();
}
}
IV.SkipComment
l SkipComment有一个表示注释类型的参数
l 该函数会根据不同的注释类型完成跳过注释的任务:
Ø 如果是以“//”开始的注释,则不断地读入文件知道晕倒换行或文件结束符。
Ø 如果是以“/*”开头的注释,则必须读到“*/”为止。
l 在第二种注释中,判断注释是否结束要判断连续的两个符号,因此用一个变量flag保存前一次读到的符号。
SkipComment的伪代码
Void SkipComment(enum CommentType type)
{
If(type == SlashSlash)
读文件,直到遇到换行符或文件结束符,返回
Flag = ‘’;
While(ch == 从文件中读入一符号)
{
晕倒的事“&/”,则注释被正常跳过了,返回。
Flag = ch;
}
没有遇到“*/”,Errors++,输出出错信息
}
SkipComment函数实现
Void balance::SkipComment(enum CommentType type)
{
Char ch,flag;
If(type == SlashSlash)
{
While((ch == NextChar())&&(ch!=’\n’));
Return ;
}
Flag = ‘’;
While((ch = NextChar())!=NULL)
{
If(flag == ‘*’&&ch == ‘/’)
Return ;
}
Errors++;
Cout<<”Comment is unterminated!”<<endl;
}
(6)balance类的使用
l 设计一程序,利用balance类检查源文件中的额括号是否配对
l 它有两种执行方式:
Ø 程序运行后,用户通过键盘输入要检查的文件名;
Ø 将要检查的源文件名作为命令行的参数,程序一次对这些参数构造balance类的对象,检查它们中的括号配对情况。
#include <iostream>
using namespace std;
#include “balance.h”
int main(int argc,const char **argv)
{
char filename[80];
balance *p;
try
{
if(argc == 1)
{
cout<<”请输入文件名:”;
cin>>filename;
p = new balance(filename);
result = p->CheckBalance();
delete p;
cout<<”共”<<result<<”个错”<<endl;
return 0;
}
while(--argc)
{
cout<<”检查文件”<<*++argv<<endl;
p = new balance(*argv);
result = p->CheckBalance();
delete p;
cout<<”共”<<result<<”个错”<<endl;
}
catch(noFile)
cout<<”no such file”<<endl;
}
return 0;
}