【GESP】C++三级、四级练习 luogu-P1597 语句解析-系列题目2
前文回顾
在第一篇文章中(【GESP】C++三级练习 luogu-P1597 语句解析-系列题目1),我们完成P1597题目本身要求的讲解。同时,我们也留了一个扩展问题,在题目其他条件不变的情况下:
- 变量仍然只有3个
a
、b
、c
- 变量值可以是 多位整数(int范围内) 或者变量名
实现代码应如何调整?
今天分享下我和孩子的“答卷”。
原题回顾(luogu-P1597 语句解析)
题目要求
题目描述
一串长度不超过 $255$ 的 PASCAL 语言代码,只有 $a,b,c$ 三个变量,而且只有赋值语句,赋值只能是一个一位的数字或一个变量,每条赋值语句的格式是
[变量]:=[变量或一位整数];
。未赋值的变量值为 $0$ 输出 $a,b,c$ 的值。
输入格式
一串符合语法的 PASCAL 语言,只有 $a,b,c$ 三个变量,而且只有赋值语句,赋值只能是一个一位的数字或一个变量,未赋值的变量值为 $0$。
输出格式
输出 $a,b,c$ 最终的值。
输入输出样例 #1
输入 #1
1
a:=3;b:=4;c:=5;
输出 #1
1
3 4 5
说明/提示
输入的 PASCAL 语言长度不超过 $255$。
本次题目分析
对比之前题解,此次题目关键点是在处理等号右边的值是多位数字的情况,等号右边是变量时,代码逻辑不变。因此,此次主要需要调整的代码片段为:
1
2
3
4
5
6
7
8
9
10
11
...
if (isdigit(str[i + 1])) { // 如果等号后是数字
// 根据变量名将数字赋值给对应变量
if (v_name == "a") {
a = str[i + 1] - '0'; // 将数字赋值给变量a
} else if (v_name == "b") {
b = str[i + 1] - '0'; // 将数字赋值给变量b
} else {
c = str[i + 1] - '0'; // 将数字赋值给变量c
}
} ...
这里不能再简单的写死取str[i + 1]
的值,因为等号右边可能是多位数字,需要根据分号的位置来确定数字的长度。
有两种方法来确定数字的长度:
- 字符串直接遍历(手搓)
- 直接使用字符串函数。
推荐考生在具备手搓能力的情况下,多多掌握函数的使用,事半功倍。
直接遍历的逻辑如下:
- 从等号的下一个字符开始,遍历到分号前的所有字符,将这些字符拼接起来,得到一个数字字符串。
- 使用
stoi()
函数将这个数字字符串转换为整数,即可得到等号右边的数字。
直接使用字符串函数的逻辑如下:
- 使用
find()
函数从当前等号的位置开始,找到下一个分号的位置。 - 使用
substr()
函数从等号的下一个位置开始,截取到分号的位置,得到一个数字字符串。 - 使用
stoi()
函数将这个数字字符串转换为整数,即可得到等号右边的数字。
示例代码
方法一,直接遍历字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include <cctype> // 用于判断字符是否为数字
#include <iostream>
#include <string>
int main() {
std::string str; // 用于存储输入的字符串
std::cin >> str;
// 初始化三个变量的值为0
int a = 0, b = 0, c = 0; // 用于存储变量a, b, c的值
std::string v_name = ""; // 用于存储当前变量的名称
// 遍历输入的字符串,解析赋值语句
for (int i = 0; i < str.length(); i++) {
// 当遇到等号时,开始处理赋值操作
if (str[i] == '=') { // 遇到等号,判断右边是否为数字
// 处理右边为数字的情况
if (isdigit(str[i + 1])) {
int j = 1; // 用于遍历右边的数字
std::string value_str = ""; // 用于存储右边的数字字符串
// 收集分号前的所有数字字符
while (str[i + j] != ';') {
value_str += str[i + j];
j++;
}
// 将收集到的数字字符串转换为整数
int value = stoi(value_str); // 将右边的数字转换为整数
// 根据变量名进行相应赋值
if (v_name == "a") { // 若当前变量为a,则将a的值设置为右边的数字
a = value;
} else if (v_name == "b") { // 若当前变量为b,则将b的值设置为右边的数字
b = value;
} else { // 若当前变量为c,则将c的值设置为右边的数字
c = value;
}
}
// 处理右边为变量的情况
else { // 若右边不是数字,则判断右边是否为a、b或c
// 根据右边变量的类型和左边变量的类型进行相应赋值
if (str[i + 1] == 'a') {
if (v_name == "b") { // 若当前变量为b,则将b的值设置为a的值
b = a;
} else if (v_name == "c") { // 若当前变量为c,则将c的值设置为a的值
c = a;
}
} else if (str[i + 1] == 'b') {
if (v_name == "a") { // 若当前变量为a,则将a的值设置为b的值
a = b;
} else if (v_name == "c") { // 若当前变量为c,则将c的值设置为b的值
c = b;
}
} else if (str[i + 1] == 'c') {
if (v_name == "a") { // 若当前变量为a,则将a的值设置为c的值
a = c;
} else if (v_name == "b") { // 若当前变量为b,则将b的值设置为c的值
b = c;
}
}
}
}
// 处理变量名部分
else { // 若遇到的不是等号,则判断是否为冒号
if (str[i] != ':') { // 若不是冒号,则将当前变量的名称设置为该字符
v_name = str[i];
} else { // 若是冒号,则跳过该字符
continue;
}
}
}
// 按要求格式输出最终结果
std::cout << a << " " << b << " " << c << std::endl;
return 0;
}
方法二,使用字符串函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include <cctype> // 用于判断字符是否为数字
#include <iostream> // 用于输入输出
#include <string> // 用于字符串处理
int main() {
std::string str; // 存储输入的PASCAL代码字符串
std::cin >> str;
// 初始化三个变量的值为0
int a = 0, b = 0, c = 0;
std::string v_name = ""; // 存储当前处理的变量名
// 遍历输入字符串
for (int i = 0; i < str.length(); i++) {
if (str[i] == '=') { // 遇到等号,开始处理赋值操作
// 找到当前语句结束的分号位置
int spe_idx = (int)str.find(';', i);
if (isdigit(str[i + 1])) { // 如果等号后是数字
// 提取等号到分号之间的子串并转换为整数
int value = stoi(str.substr(i + 1, spe_idx - i - 1));
// 根据变量名赋值
if (v_name == "a") {
a = value;
} else if (v_name == "b") {
b = value;
} else { // v_name == "c"
c = value;
}
} else { // 如果等号后是变量
// 处理变量间的赋值
if (str[i + 1] == 'a') { // 如果右边是变量a
if (v_name == "b") {
b = a;
} else if (v_name == "c") {
c = a;
}
} else if (str[i + 1] == 'b') { // 如果右边是变量b
if (v_name == "a") {
a = b;
} else if (v_name == "c") {
c = b;
}
} else if (str[i + 1] == 'c') { // 如果右边是变量c
if (v_name == "a") {
a = c;
} else if (v_name == "b") {
b = c;
}
}
}
i = spe_idx; // 跳过已处理的语句
} else { // 处理变量名部分
if (str[i] != ':') { // 如果不是冒号,则为变量名
v_name = str[i];
} else { // 跳过冒号
continue;
}
}
}
// 按要求格式输出结果
std::cout << a << " " << b << " " << c << std::endl;
return 0;
}
测试输入数据:
1
a:=345;b:=a;c:=55678;
得到输出:
1
345 345 55678
显然,上述代码拓展了对值的支持位数,也可以通过原P1597的测试。
本次进一步拓展
不管使用哪种方法,其实我们都是做了一件事,就是从等号的位置开始,找到分号的位置,然后截取等号到分号之间的子串,最后将子串转换为整数。按照GESP四级学的函数和模块化思想,我们完全可以把这部分功能抽象成一个函数,比如:
1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 从字符串中获取等号右边的数值
* @param str 输入的PASCAL代码字符串
* @param start 等号的位置
* @return 等号右边的数值
*/
int get_value(std::string str, int start) {
// 从start位置开始查找分号的位置
int end = (int)str.find(';', start);
// 截取等号到分号之间的子串并转换为整数返回
// start + 1 跳过等号,end - start - 1 表示子串长度
return stoi(str.substr(start + 1, end - start - 1));
}
这样,我们就可以在主函数中调用这个函数,来获取等号右边的数字了。模块化后主程序的逻辑固定了,如果需要改变值的获取方式,我们只需调整函数内部实现即可。主函数的代码基本不变,保持逻辑和条理清晰稳定。调整后代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include <cctype>
#include <iostream>
#include <string>
/**
* 从字符串中获取等号右边的数值
* @param str 输入的PASCAL代码字符串
* @param start 等号的位置
* @return 等号右边的数值
*/
int get_value(std::string str, int start) {
// 从start位置开始查找分号的位置
int end = (int)str.find(';', start);
// 截取等号到分号之间的子串并转换为整数返回
// start + 1 跳过等号,end - start - 1 表示子串长度
return stoi(str.substr(start + 1, end - start - 1));
}
int main() {
// 声明字符串变量用于存储输入的PASCAL代码
std::string str;
std::cin >> str;
// 初始化三个变量a、b、c的值为0
int a = 0, b = 0, c = 0;
// 声明字符串变量用于存储当前处理的变量名
std::string v_name = "";
// 遍历输入的字符串
for (int i = 0; i < str.length(); i++) {
if (str[i] == '=') { // 遇到等号时开始处理赋值操作
if (isdigit(str[i + 1])) { // 如果等号后面是数字
// 调用get_value函数获取等号右边的数值
int value = get_value(str, i);
// 根据变量名将数值赋给对应变量
if (v_name == "a") {
a = value;
} else if (v_name == "b") {
b = value;
} else {
c = value;
}
} else { // 如果等号后面是变量
// 处理变量间的赋值操作
if (str[i + 1] == 'a') { // 右边是变量a
if (v_name == "b") {
b = a;
} else if (v_name == "c") {
c = a;
}
} else if (str[i + 1] == 'b') { // 右边是变量b
if (v_name == "a") {
a = b;
} else if (v_name == "c") {
c = b;
}
} else if (str[i + 1] == 'c') { // 右边是变量c
if (v_name == "a") {
a = c;
} else if (v_name == "b") {
b = c;
}
}
}
// 将i移动到当前语句的分号位置
i = (int)str.find(';', i);
} else { // 不是等号时处理变量名
if (str[i] != ':') { // 如果不是冒号则为变量名
v_name = str[i];
} else { // 是冒号则跳过
continue;
}
}
}
// 按要求格式输出三个变量的最终值
std::cout << a << " " << b << " " << c << std::endl;
return 0;
}
后续拓展
现在,我们在原题解的基础上额外支持了:
变量值
可以是 多位整数(int范围内) 或者变量名- 使用函数抽象了一些通用逻辑
但我们支持的场景还存在很多局限性:
- 只支持3个变量
a
、b
、c
,且变量名只有一位字母 - 虽然抽象出了
get_value
函数,但赋值部分的语句未思考是否可以抽象
因此,接下来,我们计划继续拓展:
- 支持更多的变量名,且变量名的长度可以是多位
- 进一步利用四级中的函数和模块化思想,并利用值传递、引用传递的特性,重构简化函数。
有兴趣的同学可以尝试,后续我会继续更新我和孩子的学习成果。
所有代码已上传至Github:https://github.com/lihongzheshuai/yummy-code
GESP各级别考纲、真题讲解、知识拓展和练习清单等详见【置顶】【GESP】C++ 认证学习资源汇总
“luogu-”系列题目可在洛谷题库进行在线评测。
“bcqm-”系列题目可在编程启蒙题库进行在线评测。
欢迎加入:Java、C++、Python技术交流QQ群(982860385),大佬免费带队,有问必答
欢迎加入:C++ GESP/CSP认证学习QQ频道,考试资源总结汇总
欢迎加入:C++ GESP/CSP学习交流QQ群(688906745),考试认证学员交流,互帮互助