文章

【GESP】C++三级、四级练习 luogu-P1597 语句解析-系列题目2

前文回顾

在第一篇文章中(【GESP】C++三级练习 luogu-P1597 语句解析-系列题目1),我们完成P1597题目本身要求的讲解。同时,我们也留了一个扩展问题,在题目其他条件不变的情况下:

  • 变量仍然只有3个abc
  • 变量值可以是 多位整数(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个变量abc,且变量名只有一位字母
  • 虽然抽象出了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),考试认证学员交流,互帮互助

GESP/CSP 认证学习微信公众号
GESP/CSP 认证学习微信公众号
本文由作者按照 CC BY 4.0 进行授权