来自bzoj(传统题)
目录
题面:
宏是 C/C++ 语言的一项特性,它根据预先定义的规则进行文本替换(也被称为 “宏展开”),能够实现定义常量、简化代码重复输入等功能。例如:
#define PI 3.14159
double area = PI * r * r;
以上代码经过宏展开后变为:double area = 3.14159 * r * r;
其中,宏定义命令变成了空行,而其他行中的宏被展开成了规则定义的文本。
C/C++ 语言代码在编译时对宏的处理由预处理器完成。你的任务是实现一个简化版的预处理器,要求如下:
代码由行组成,每行除行末的换行符外,均由可打印 ASCII 字符(ASCII 码范围 32∼126)组成。每行要么是预处理命令(以
#
开头),要么是普通文本(其他情况)。预处理器逐行处理代码,
- 如果是预处理命令,执行该命令,并输出一个空行。
- 如果是普通文本,对其进行宏展开并输出结果。
预处理命令有两种,分别是宏定义命令
#define
和取消宏定义命令#undef
。
- 宏定义命令的格式为
#define <name> <content>
,其中第一部分#define
是命令名,第二部分<name>
是要定义的宏的名字,第三部分<content>
是要定义的宏的展开内容。- 取消宏定义命令的格式为
#undef <name>
,其中第一部分#undef
是命令名,第二部分<name>
是要取消的宏的名字。以上两种预处理命令中,相邻两部分之间都严格用一个空格分隔。
<name>
是由大小写字母和数字以及下划线组成的标识符(一个或多个字符),<content>
可以包含任意可打印 ASCII 字符(零个或多个字符)。一个宏定义的有效范围是从它定义所在行开始到后续最近的宏名匹配的取消定义所在行为止(如果没有对应的取消定义,则有效范围一直覆盖到文件结束)。对普通文本进行宏展开时,将一行文本中每段连续极长的大小写字母和数字以及下划线视为标识符(而不是其中一部分),其余为其他字符。从左到右依次对文本中的标识符进行宏展开:
- 如果该标识符是有效的宏名,则用对应的展开内容替换它,此时该宏名进入正在展开的状态,直到本流程结束;否则原样保留宏名。例如,若宏
A
定义为b
,则文本A
展开结果为b
(发生替换),文本B
展开结果仍然为B
(未定义,不替换),文本AA
展开结果仍然为AA
(AA
是不同于A
的另一个标识符,未定义),而文本A*B
展开结果为b*B
。- 替换发生后,如果展开内容中包含标识符,重复应用以上的展开操作,称为 “多次展开”。例如,若宏
A
定义为B
,宏B
定义为c
,则文本A
的展开结果为c
。- 如果待展开的宏名与正在进行展开的某个宏名相同,称为 “递归展开”,此时该宏名不再展开。本规则用来防止无限递归展开。例如,若宏
A
定义为B+a
,宏B
定义为A+b
,则文本A
展开结果为A+b+a
,由于最初的A
处于正在展开状态,因此A+b+a
里的A
不再展开。- 其他字符原样保留。
注意:出于简化的目的,本题的要求与 C/C++ 语言标准里的描述不完全一致,请以上面的要求为准。最明显的区别是本题只有标识符和其他字符两类词法单元,没有数值、字符串、注释等。
非常复杂的模拟题,毕竟是省赛题啊(作者已肝硬化)。
思路:
输入:
看过上期程序分析机应该就能知道用getline(instruction),但是,这次额外输入了一个行数,所以输入行数后,要再进行一次getline消除回车。由于输入的代码和标准C/C++不一致,所以define不会向后查找宏,所以只需一个变量即可。
宏的实现:
定义宏:
定义一个以字符串做索引,存放字符串的map(macro),每次getline之后,如果instruction的第1(索引为0)个字符为#且第2个字符为d,说明是个define语句,跳过#define和其后的空格(将下标设为8),按照空格分成name和content两个字符串,把macro[name]设为content。
void define()
{
string name = "",content = "";
int location = 8;
while(instruction[location] != ' ') name += instruction[location],location++;
location++;
while(location < len) content += instruction[location],location++;
macro[name] = content;
return;
}
删除宏:
如果instruction的第1个字符为#但第2个字符为u,说明是个undef语句,跳过#undef和其后的空格(将下表设为7),剩余的字符存为name,把macro[name]设为空。
void undef()
{
string name = "";
int location = 7;
while(location < len) name += instruction[location],location++;
macro[name] = "";
}
展开的实现:
直接展开:
从左往右按照非标准字符(非字母、非数字(无此要求))分块,若macro[某一区块]不为空,则把这一区块替换为macro。
bool judge(char character)
{
if((character >= 'a' && character <= 'z') || (character >= 'A' && character <= 'Z')) return true;
return false;
}
string launch(string instruction)
{
string ans,str;
int r = 0;
while(r < instruction.size())
{
while(r < instruction.size() && judge(instruction[r])) ans += instruction[r],r++;
if(macro[ans] != "") str += macro[ans];
else str += ans;
while(r < instruction.size() && !judge(instruction[r])) r++;
}
return str;
}
连续展开:
通过递归,在找出ans且ans是个宏之后,以ans为参数再进行launch判断。
string launch(string instruction)
{
string ans,str;
int right = 0;
while(right < instruction.size())
{
while(right < instruction.size() && judge(instruction[right])) ans += instruction[right],right++;
if(macro[ans] != "") str += launch(macro[ans]);
else str += ans;
while(right < instruction.size() && !judge(instruction[right])) right++;
}
return str;
}
递归展开:
上面的代码,如果出现递归展开,会死循环,最后内存RE。
我们再开一个以字符串为索引,存放布尔值的map(change),在进行递归操作之前,把change[ans]设为true,并且判断ans是否为宏时,判断change[ans]必须不为true,执行完递归后还原change[ans]。
string launch(string instruction)
{
string ans,str;
int right = 0;
while(right < instruction.size())
{
while(right < instruction.size() && judge(instruction[right])) ans += instruction[right],right++;
if(macro[ans] != "" && !change[ans])
{
change[ans] = 1;
str += launch(macro[ans]);
change[ans] = 0;
}
else str += ans;
while(right < instruction.size() && !judge(instruction[right])) right++;
}
return str;
}
综合:
在进行合并和优化、美化之后,代码长这样:
string launch(string instruction_copymain)
{
string instruction_launch = "",instruction_copy2 = "";
int len_copymain = instruction_copymain.size();
int left = 0,right = 0;
while(left < len_copymain)
{
while(right < len_copymain && judge(instruction_copymain[right])) right++;
instruction_copy2 = "";
for(int i = left;i < right;i++) instruction_copy2 += instruction_copymain[i];
if(macro[instruction_copy2].size() < 1 || change[instruction_copy2]) instruction_launch += instruction_copy2;
else
{
change[instruction_copy2] = 1;
instruction_launch += launch(macro[instruction_copy2]);
change[instruction_copy2] = 0;
}
while(right < len_copymain && !judge(instruction_copymain[right])) instruction_launch += instruction_copymain[right],right++;
left = right;
}
return instruction_launch;
}
代码:
#include<iostream>
#include<cstring>
#include<map>
using namespace std;
int lines,len;
string instruction;
map <string,string> macro;
map <string,bool> change;
bool judge(char character)
{
if((character >= 'a' && character <= 'z') || (character >= 'A' && character <= 'Z')) return true;
return false;
}
void define()
{
string name = "",content = "";
int location = 8;
while(instruction[location] != ' ') name += instruction[location],location++;
location++;
while(location < len) content += instruction[location],location++;
macro[name] = content;
return;
}
void undef()
{
string name = "";
int location = 7;
while(location < len) name += instruction[location],location++;
macro[name] = "";
}
string launch(string instruction_copymain)
{
string instruction_launch = "",instruction_copy2 = "";
int len_copymain = instruction_copymain.size();
int left = 0,right = 0;
while(left < len_copymain)
{
while(right < len_copymain && judge(instruction_copymain[right])) right++;
instruction_copy2 = "";
for(int i = left;i < right;i++) instruction_copy2 += instruction_copymain[i];
if(macro[instruction_copy2].size() < 1 || change[instruction_copy2]) instruction_launch += instruction_copy2;
else
{
change[instruction_copy2] = 1;
instruction_launch += launch(macro[instruction_copy2]);
change[instruction_copy2] = 0;
}
while(right < len_copymain && !judge(instruction_copymain[right])) instruction_launch += instruction_copymain[right],right++;
left = right;
}
return instruction_launch;
}
signed main()
{
cin >> lines;
getline(cin,instruction);
for(int i = 1;i <= lines;i++)
{
getline(cin,instruction);
len = instruction.size();
if(instruction[0] == '#')
{
if(instruction[1] == 'd') define();
else undef();
}
else cout << launch(instruction) << endl;
}
}