结对项目,用C++实现的四则运算

发布时间 2023-09-30 11:49:33作者: 能不能专心一点
软件工程 计科一班 陈倚星-3119000414 , 甫尔达吾斯.吐拉江-3119000416
作业要求 与班上同学组队完成项目
作业目的 提高合作与团队意识
GitHub链接 https://github.com/xingch123456789/my_app
  • PSP表格
PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 30 60
Estimate 估计这个任务需要多少时间 440 1000
Development 开发 150 600
Analysis 需求分析 (包括学习新技术) 400 800
Design Spec 生成设计文档 75 120
Design Review 设计复审 80 100
Coding Standard 代码规范 (为目前的开发制定合适的规范) 65 80
Design 具体设计 500 600
Coding 具体编码 420 500
Code Review 代码复审 200 240
Test 测试(自我测试,修改代码,提交修改) 800 850
Reporting 报告 600 700
Test Repor 测试报告 500 600
Size Measurement 计算工作量 80 60
Postmortem & Process Improvement Plan 事后总结, 并提出过程改进计划 60 80
合计 3天 4天
  • 效能分析
    image
    由图可知,CPU的使用大多为系统文件的外部调用所致,源文件的main和generte_topic_answer函数的CPU使用率较高,其他的函数CPU使用率都不高

  • 设计实现过程
    因为对C++类的使用不太熟悉,所以本次项目主要由函数实现,下面是项目的函数声明

点击查看代码
//输出min到max范围的一个整数
int randomInt(int min, int max);

//生成随机题目
std::vector<std::string> generateExercises(int num, int range);

//判断是否为运算符
bool isOperator(char c);

//获得符号的优先级
int getPrecedence(char op);

//中缀表达式转化为后缀表达式
std::string infixToPostfix(const std::string& infixExpression);

//将数组中的题目由中缀表达式转化为后缀表达式
std::vector<std::string> transform(const std::vector<std::string>& vec);

//根据不同符号计算结果
double performOperation(double operand1, double operand2, char op);

//计算后缀表达式
double evaluatePostfix(const std::string& postfixExpression);

//计算结果
std::vector<double> get_result(const std::vector<std::string>& vec);

//将题目写入文件,把文件名当参数传入
void write_to_text(const std::vector<std::string>& vec, std::string file_name);

//把结果写入文件,把文件名当参数传入
void write_result_to_file(const std::vector<double>& vec, std::string file_name);

//读取文件中的内容,并存入字符串数组中
std::vector<std::string> get_file_to_string(const std::string str);

//把string转化为double
std::vector<double>string_to_double(std::vector<std::string>v);

//通过判断两个数组是否相等,把相等下标存到correct数组中,其余的存到wrong数组中
void compareArrays(const std::vector<double>& array1, const std::vector<double>& array2, std::vector<int>& correct, std::vector<int>& wrong);

//把correct和wrong的内容存到Grade.txt中
void writeResultToFile(const std::vector<int>& correct, const std::vector<int>& wrong, std::string file_name);

//封装函数,随机生成题目,并把题目和答案分别输出到Exercises.txt,Answers.txt文件中
//参数num代表生成题目数量,range代表范围,即多大数字以内的运算,字符串exercises,answer分别代表两个文件名
void generte_topic_answer(int num, int range, const std::string exercises, const std::string answer);

//封装函数,输入question.txt,answer.txt就直接判断答案是否正确,并把内容输出到Grade.txt中
void judgment_que_ans(const std::string question, const std::string answer);

其中最为重要的是随机生成题目的函数和计算生成题目的函数实现,下面是两个函数的流程图
1.随机生成题目函数流程图image

2.计算生成题目函数流程图
image

  • 代码说明

1.输出min到max范围的一个整数

点击查看代码
int randomInt(int min, int max) {
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<> dis(min, max);
    return dis(gen);
}

2.生成随机题目

点击查看代码
std::vector<std::string> generateExercises(int num, int range) {//num控制生成题目的数量,range控制数字的大小
    std::string expression;
    std::vector<std::string>vec;
    //vec.reserve(num);
    for (int i = 0; i < num; i++) {
        // 随机生成算术表达式
        std::stringstream ss;
        int operatorCount = randomInt(2, 4);//控制运算符的数量不超过3个
        for (int j = 0; j < operatorCount; j++) {
            if (j > 0) {//保证第一个字符不是操作符
                ss << OPERATORS[randomInt(0, OPERATORS.size() - 1)];
            }
            ss << randomInt(MIN_NUM, range);
        }
        expression = ss.str();
        vec.push_back(expression);
        // std::cout << expression << std::endl;
    }
    //std::cout << expression << std::endl;
    return vec;
}

3.中缀表达式转化为后缀表达式

点击查看代码
std::string infixToPostfix(const std::string& infixExpression) {
    std::stack<char> operators;//暂存string中的操作符,用于优先级的判断
    std::string postfixExpression;//存放转化后的结果

    for (char c : infixExpression) {
        if (c == ' ') {
            continue;
        }

        if (isdigit(c)) {//如果是数字就直接加入string中
            postfixExpression += c;
        }
        else if (isOperator(c)) {
            //符号优先级高的先操作
            while (!operators.empty() && operators.top() != '(' && getPrecedence(operators.top()) >= getPrecedence(c)) {
                postfixExpression += operators.top();
                operators.pop();
            }
            operators.push(c);
        }
    }

    //如果string已经遍历完了,存放操作符的栈还有元素,就直接加到string中
    while (!operators.empty()) {
        postfixExpression += operators.top();
        operators.pop();
    }

    return postfixExpression;
}

4.计算后缀表达式

点击查看代码
double evaluatePostfix(const std::string& postfixExpression) {
    std::stack<double> operands;

    for (char c : postfixExpression) {
        if (isdigit(c)) {
            operands.push(c - '0');//将字符转化为数字
        }
        else if (isOperator(c)) {
            double operand2 = operands.top();
            operands.pop();
            double operand1 = operands.top();
            operands.pop();
            double result = performOperation(operand1, operand2, c);
            operands.push(result);
        }
    }
    return operands.top();
}

5.封装函数,随机生成题目,并把题目和答案分别输出到Exercises.txt,Answers.txt文件中
参数num代表生成题目数量,range代表范围,即多大数字以内的运算,字符串exercises,answer分别代表两个文件名

点击查看代码
void generte_topic_answer(int num, int range, const std::string exercises, const std::string answer)
{
    std::vector<std::string> v;
    v = generateExercises(num, range);//生成num多个题目
    std::vector<std::string>v1 = transform(v);//由中缀转化为后缀
    std::vector<double>v2 = get_result(v1);//得到算出的结果
    write_to_text(v, exercises);//写入文件Exercises.txt
    write_result_to_file(v2, answer);//写入文件Answers.txt
}

6.封装函数,输入question.txt,answer.txt就直接判断答案是否正确,并把内容输出到Grade.txt中

点击查看代码
void judgment_que_ans(const std::string question, const std::string answer)
{
    //首先将两个文件里的内容读取到字符串数组中
    std::vector<std::string>v = get_file_to_string(question);
    std::vector<std::string>v3 = get_file_to_string(answer);

    std::vector<std::string>v1 = transform(v);//由中缀转化为后缀
    std::vector<double>v2 = get_result(v1);//得到算出的结果

    std::vector<double>v4 = string_to_double(v3);//answer.txt的内容,转化为double
    std::vector<int> correct;
    std::vector<int> wrong;
    compareArrays(v2, v4, correct, wrong);
    writeResultToFile(correct, wrong, "Grade.txt");//结果输出到Grade.txt
}
  • 测试运行
    测试是我手动编写的,一共5个,代码如下,结果看注释
点击查看代码
#include"my_app.h"

//question.txt的内容为3+5+1, 5 * 3 - 1,9 / 9 + 1,5 * 4,3 + 2
void test1()
{
	judgment_que_ans("question.txt", "answer.txt");//answer.txt的内容为9,14,2,20,1
}

void test2()
{
	judgment_que_ans("question.txt", "answer.txt");//answer.txt的内容为9,10,2,20,1
}

void test3()
{
	judgment_que_ans("question.txt", "answer.txt");//answer.txt的内容为9,14,20,2,1
}

void test4()
{
	judgment_que_ans("question.txt", "answer.txt");//answer.txt的内容为5,14,2,20,2
}

void test5()
{
	judgment_que_ans("question.txt", "answer.txt");//answer.txt的内容为5,14,20,20,1
}

int main()
{
	test1();//此时Grade.txt的内容为Correct: 5 (0, 1, 2, 3, 4),Wrong: 0 ()
	test2();//此时Grade.txt的内容为Correct: 5 (0, 2, 3, 4),Wrong: 0 (1)
	test3();//此时Grade.txt的内容为Correct: 5 (0, 1, 4),Wrong: 0 (2, 3)
	test4();//此时Grade.txt的内容为Correct: 5 (1, 2, 3),Wrong: 0 (0, 4)
	test5();//此时Grade.txt的内容为Correct: 5 (1, 3, 4),Wrong: 0 (0, 2)
	return 0;
}
  • 项目小结

难点:再做项目的过程中,我们也遇到了许多困难,首先是对于真分数的处理,我们做出了随机生成真分数的代码如下

点击查看代码
//输出随机真分数
std::string randomFraction(int min, int max) {
    int numerator = randomInt(min, max);
    int denominator = randomInt(min, max);
    int integer = numerator / denominator;
    //std::cout << numerator << " " << denominator << std::endl;
    //numerator %= denominator;
    std::stringstream ss;
    if (integer > 0) {
        numerator %= denominator;
        if (numerator == 0)ss << integer;//分子分母相等
        else ss << integer << "'"<< numerator << "/" << denominator;
    }
    else ss << numerator << "/" << denominator;
    return ss.str();
}
但是当我们想要把真分数运用到计算中时,我们发现了许多问题,比如分数如何和整数进行运算,运算后答案输出怎么表示,我们花了很长的时间去做,后来发现还是做不出来,没办法最后只能用double来代替分数,然后对于老师在需求里写的对分数的要求没有做到,在这里我是比较遗憾的。 然后第二个难点是用命令行运行文件,我们在网上查了很多资料,但是对于使用命令行依然是毫无头绪,在程序里编写命令行的代码我们暂时还是写不出来,不过我们在学了,争取以后完善

好的建议:在计算题目是,我一开始想得是从头到尾按顺序计算的,然后发现这样做有很大的问题,然后他建议对符号进行优先级判定,优先级高的先算。在数据结构的选择上,我们尝试了vector,queue,stack,set,map,最后发现还是stack最好。
最后总结一下,做这个项目我们遇到了很多困难,但我们没有被困难阻拦,在克服后收获了满满的满足感,也丰富了我们的编程经历,提高了我们的团队合作能力,是一次美好的作业体验