期中考试后我做完了这个学期的pta题目,得意忘形,用pta中的题目打趣老师,没想到老师在下节课上增加了题目。这题对我来说是颇有难度的一道题,完整代码在文章结尾。
这道题要求我们识别出输入语句中的宏定义,处理语句中的多余空格并将其中的宏替换。
处理语句还有额外要求:语句前的空格与双引号内的内容不能改动。
任务貌似有点多,让我们稍微拆分下。
考虑到宏的输入会有“ #define hehe "hehe"”这样的形式,按照以下顺序来进行或许更方便:
1.读取输入的语句
2.识别是否有宏
3.移除宏语句的多余空格并读取其中的宏
4.对其他语句替换宏
5.移除多余空格
6.打印结果
框架
题目规定不会在预编译语句外出现'#'!这是个好消息,我们可以依靠这个识别预编译语句。
题目要求引号内不做处理,我们可以设置状态变量quote,如果不在引号内,quote为0,否则为1。
对于第3-5步,我们暂时没有明显的思路,故可以将其中可能会用到的操作声明为函数,方便我们调试。
为了确保宏的有关数据有序,我们用结构体数组来存储宏。
typedef struct {//包含标识符与宏字符串的结构体
char name[21];
char value[31];
} Def;
Def def[10];//结构体数组
int def_count = 0;//已获取的宏数量
void remove_spaces(char* line);//移除空格
void remove_def_spaces(char* line);//移除宏语句空格
int get_def(char* line);//获取宏
void replace_def(char* line);//替换宏
int main(void) {
char line[201];//用于存储输入的c++语句
while (fgets(line, sizeof(line), stdin)) {//当读取一行语句
if (strchr(line, '#') != NULL) //如果在这条语句中含有字符'#'
if (get_def(line)) continue;//则获取其中的标识符与宏字符串
if (strcmp(line, "@@\n") == 0) break; //如果语句为@@,结束读取
replace_def(line);//替换其中的宏
remove_spaces(line);//移除多余的空格
if (strlen(line) > 0) printf("%s", line);//输出语句
}
return 0;
}
获取宏
要获取宏,我们可以识别"#define"来知道是否为宏语句,首先我们会对其移除一次多余空格。
处理空格与后面的步骤高度重合,所以假如我们读取到的都是正确格式,那么我们可以用sscanf函数来获取标识符与宏字符串,这个函数可以从字符串中读取输入。
为了保证读取到的宏合法,我们对其再进行检查。
int get_def(char* line) {//用于获取标识符与宏字符串
remove_spaces(line);//处理空格
if (strncmp(line, "#define", 7) == 0) {//如果识别到了"#define",那么判定为宏
char name[21];
char value[31];
if (sscanf(line + 7, " %20s %30s", name, value) == 2) {
//sscanf可以将字符串内容输入至其他字符串内,这里获取标识符与宏字符串
int flag = 1;//判断宏是否符合题目要求,即由数字和字母组成
if (!isalpha(name[0])) flag = 0;
for (int i = 0; name[i] != '\0'; ++i)
if (!isalnum(name[i])) flag = 0;
if (flag) {
strcpy(def[def_count].name, name);
strcpy(def[def_count].value, value);
def_count++;//正式获取一个合法宏!
}
return 1;
}
}
return 0;
}
移除多余空格
读取宏完成了,其次是处理空格,我们可以依靠双指针来进行操作,这样就不必多声明一个数组增加操作。
声明两个int变量i,j。i用于遍历原字符串,j用于向字符串中复制原字符串的内容。
如果符合条件,那么j复制i指向的字符至字符串,覆盖原先的内容,不符合条件,那么j不动,i向后移动。当i移动到字符串末尾时,我们将j之后的子串全部清空并添加空字符(否则scanf("%s")会输出乱码)。
当i遇到了引号时,quote为1,将原字符串内容复制,直到i再次遇到引号,此时quote变为0。
在调试过程中发现了换行符前出现空格的特殊情况,我们多添加一个if语句来进行处理。
函数代码如下:
void remove_spaces(char* line) {//用于移除语句的多余空格
int i = 0, j = 0;//双指针,j为用于复制新字符串的左指针,i为用于遍历原字符串的右指针
int len = strlen(line);
if(strchr(line,'#')==NULL) while (line[i] == ' ') i++, j++;//如果是预编译语句 对语句前的空格不做处理
else while (line[i] == ' ') i++;//如果是其他语句 跳过前面的空格
while (i < len) {
if (line[i] == ' ') {//如果原字符串此处为空格
line[j++] = ' ';//复制这个空格
while (line[i] == ' ') i++;//跳过其余的空格
}//这个if判断用于将连续空格变为一个空格
else if (line[i] == '"') {//如果原字符串此处是引号
line[j++] = line[i++];
while (i < len && line[i] != '"') line[j++] = line[i++];//接下来对引号内的子串不做变动
line[j++] = line[i++];//复制这个引号
continue;
}
else line[j++] = line[i++];//其他字符直接复制
}
while (j > 0 && line[j - 1] == ' ') j++;//删去尾部空格
line[j] = '\0';//字符串末尾添加结束字符
if (line[j - 2] == ' ') {//这里处理换行符前为空格的特殊情况
line[j - 2] = line[j - 1];
line[j - 1] = '\0';
}
}
替换宏
接下来是替换宏。这里我们需要注意:双引号内的宏无需替换,作为其他字段子串的宏无需替换。
用found作为标志变量后,我们依然利用双指针来进行遍历,替换则交给strcpy函数。这个函数可以将一个字符串的内容直接复制至另一个字符串。
要检查宏是否为独立的子串,用isalnum函数会方便很多,isalnum可以判断字符是否为字母或数字。
main函数最后打印的是line,所以我们最后把result复制回line中。
void replace_def(char* line) {//用于对语句中的宏进行替换
char result[201];//替换后的结果
int i = 0, j = 0;//i为原字符串的指针,j为结果字符串中的指针
int len = strlen(line);//获取原长度
while (line[i] == ' ') {//对开头的空格不做处理
i++;
result[j++] = ' ';
}
while (i < len) {
if (line[i] == '"') {//对引号内的部分不做处理
result[j++] = line[i++];
while (i < len && line[i] != '"')
result[j++] = line[i++];
if (i < len) result[j++] = line[i++];
continue;
}
int found = 0;//找到宏的标志
for (int k = 0; k < def_count; k++) {
int name_len = strlen(def[k].name);//标识符长度
if (strncmp(line + i, def[k].name, name_len) == 0 &&
(i + name_len == len || !isalnum(line[i + name_len]))
&& !isalnum(line[i - 1])) {
//保证宏为单独隔开的子串,前后没有数字或字母
strcpy(result + j, def[k].value);//替换宏
j += strlen(def[k].value);//更新指针
i += name_len;
found = 1;//找到宏了!
break;
}
}
if (!found) result[j++] = line[i++];//没找到,将整个语句原样复制
}
result[j] = '\0';//补充空字符
strcpy(line, result);//将结果复制回最终打印的字符串
}
完整代码
至此,这题的大部分难点就解决了,剩下的无非是完善指针的边界等等工作。
基于以上思路,完整ac代码如下:
#include <stdio.h>
#include <string.h>
#include <ctype.h>
//好恶心的题 不过确实锻炼了我的字符串处理能力
//功能函数化太香了 比全部堆在main里面好调试多了
typedef struct {//包含标识符与宏字符串的结构体
char name[21];
char value[31];
} Def;
Def def[10];//结构体数组
int def_count = 0;//已获取的宏数量
void remove_spaces(char* line) {//用于移除语句的多余空格
int i = 0, j = 0;//双指针,j为用于复制新字符串的左指针,i为用于遍历原字符串的右指针
int len = strlen(line);
if(strchr(line,'#')==NULL) while (line[i] == ' ') i++, j++;//如果是预编译语句 对语句前的空格不做处理
else while (line[i] == ' ') i++;//如果是其他语句
while (i < len) {
if (line[i] == ' ') {//如果原字符串此处为空格
line[j++] = ' ';//复制这个空格
while (line[i] == ' ') i++;//跳过其余的空格
}//这个if判断用于将连续空格变为一个空格
else if (line[i] == '"') {//如果原字符串此处是引号
line[j++] = line[i++];
while (i < len && line[i] != '"') line[j++] = line[i++];//接下来对引号内的子串不做变动
line[j++] = line[i++];//复制这个引号
continue;
}
else line[j++] = line[i++];//其他字符直接复制
}
while (j > 0 && line[j - 1] == ' ') j++;//删去尾部空格
line[j] = '\0';//字符串末尾添加结束字符
if (line[j - 2] == ' ') {//这里处理换行符前为空格的特殊情况
line[j - 2] = line[j - 1];
line[j - 1] = '\0';
}
}
int get_def(char* line) {//用于获取标识符与宏字符串
remove_spaces(line);//处理空格
if (strncmp(line, "#define", 7) == 0) {//如果识别到了"#define",那么判定为宏
char name[21];
char value[31];
if (sscanf(line + 7, " %20s %30s", name, value) == 2) {
//sscanf可以将字符串内容输入至其他字符串内,这里获取标识符与宏字符串
int flag = 1;//判断宏是否符合题目要求,即由数字和字母组成
if (!isalpha(name[0])) flag = 0;
for (int i = 0; name[i] != '\0'; ++i)
if (!isalnum(name[i])) flag = 0;
if (flag) {
strcpy(def[def_count].name, name);
strcpy(def[def_count].value, value);
def_count++;//正式获取一个合法宏!
}
return 1;
}
}
return 0;
}
void replace_def(char* line) {//用于对语句中的宏进行替换
char result[201];//替换后的结果
int i = 0, j = 0;//i为原字符串的指针,j为结果字符串中的指针
int len = strlen(line);//获取原长度
while (line[i] == ' ') {//对开头的空格不做处理
i++;
result[j++] = ' ';
}
while (i < len) {
if (line[i] == '"') {//对引号内的部分不做处理
result[j++] = line[i++];
while (i < len && line[i] != '"')
result[j++] = line[i++];
if (i < len) result[j++] = line[i++];
continue;
}
int found = 0;//找到宏的标志
for (int k = 0; k < def_count; k++) {
int name_len = strlen(def[k].name);//标识符长度
if (strncmp(line + i, def[k].name, name_len) == 0 &&
(i + name_len == len || !isalnum(line[i + name_len]))
&& !isalnum(line[i - 1])) {
//保证宏为单独隔开的子串,前后没有数字或字母
strcpy(result + j, def[k].value);//替换宏
j += strlen(def[k].value);//更新指针
i += name_len;
found = 1;//找到宏了!
break;
}
}
if (!found) result[j++] = line[i++];//没找到,将整个语句原样复制
}
result[j] = '\0';//补充空字符
strcpy(line, result);//将结果复制回最终打印的字符串
}
int main(void) {
char line[201];//用于存储输入的c++语句
while (fgets(line, sizeof(line), stdin)) {//当读取一行语句
if (strchr(line, '#') != NULL) //如果在这条语句中含有字符'#'
if (get_def(line)) continue;//则获取其中的标识符与宏字符串
if (strcmp(line, "@@\n") == 0) break; //如果语句为@@,结束读取
replace_def(line);//替换其中的宏
remove_spaces(line);//移除多余的空格
if (strlen(line) > 0) printf("%s", line);//输出语句
}
return 0;
}
这题缕清思路后其实框架并不复杂,但用何种办法处理字符串困扰了我很久,也迫使我学习了很多关于此的技巧,例如string.h内的大部分函数以及双指针的灵活运用。
标签:name,++,PTA,空格,75,简单,字符串,line,def From: https://blog.csdn.net/qq_37231166/article/details/143726024