用C++实现简单随机二元四则运算
让我们想看看二元四则运算都需要实现什么:
(1) 定制题目数量
(2) 是否有乘除法
(3) 题目数值范围
(4) 加减有无负数
(5) 除法有无余数
(6) 是否支持分数(真分数、假分数…)
(7) 是否支持小数(精确到多少位?)
(8) 打印中每行的间隔
为了实现以上功能,并且为了便于后期实现功能拓展,我们可以将其封装成类,通过在外界设置类的参数,实现不同的功能。可以将类定义如下:
1 class CTitle 2 { 3 public: 4 CTitle(); 5 ~CTitle(); 6 void setTitleCount(int titleCount); // 设置题量 7 void setMD(int hasMD); // 设置乘除法 8 void setBracket(int hasBracket); // 设置括号 9 void setNumRand(int numRand); // 设置数值范围 10 void setMinus(int hasMinus); // 设置负号 11 void setResidue(int hasResidue); // 设置余数 12 void setFraction(int hasFraction); // 设置分数 13 void setFractionType(int fractionType); // 设置是否支持假分数 14 void setDecimal(int hasDecimal); // 设置小数 15 void setDecimalLong(int decimalLong); // 设置小数位数 16 void newTitle(); // 新建题目 17 private: 18 int titleCount; // 题目数量 19 int hasMD; // 是否有乘除法 20 int hasBracket; // 是否有括号 21 int numRand; // 数值范围 22 int hasMinus; // 加减有无负数 23 int hasResidue; // 除法有无余数 24 int hasFraction; // 是否支持分数 25 int fractionType; // 是否支持假分数 26 int hasDecimal; // 是否支持小数 27 int decimalLong; // 小数位数 28 int decimalLongPow; // 小数位数 29 30 void newIntegerFormula(); // 新建整数运算公式 31 void newDecimalFormula(); // 新建小数运算公式 32 void newFractionFormula(); // 新建分数运算公式 33 void recordTitle(string szTitle); // 记录题目至文件 34 void recordAnswer(string szAnswer); // 记录答案至文件 35 void reduceFraction(int &nume, int &deno);// 化简分数 36 };
接下来,我们需要实现类的功能接口,并且对类的属性实现初始化
1 CTitle::CTitle() 2 { 3 titleCount = 0; // 题量 4 hasMD = 0; // 是否有乘除法 5 numRand = 0; // 数值范围 6 hasMinus = 0; // 加减有无负数 7 hasResidue = 0; // 除法有无余数 8 hasFraction = 0; // 是否支持分数 9 fractionType = 0; // 是否支持假分数 10 hasDecimal = 0; // 是否支持小数 11 decimalLong = 0; // 小数位数 12 decimalLongPow = 0; // 小数位数 13 } 14 CTitle::~CTitle() 15 { 16 } 17 // 设置行间距 18 void CTitle::setTitleCount(int titleCount) 19 { 20 this->titleCount = titleCount; 21 } 22 // 设置乘除法 23 void CTitle::setMD(int hasMD) 24 { 25 this->hasMD = hasMD; 26 } 27 // 设置数值范围 28 void CTitle::setNumRand(int numRand) 29 { 30 this->numRand = numRand; 31 } 32 // 设置负号 33 void CTitle::setMinus(int hasMinus) 34 { 35 this->hasMinus = hasMinus; 36 } 37 // 设置余数 38 void CTitle::setResidue(int hasResidue) 39 { 40 this->hasResidue = hasResidue; 41 } 42 // 设置分数 43 void CTitle::setFraction(int hasFraction) 44 { 45 this->hasFraction = hasFraction; 46 } 47 // 设置是否支持假分数 48 void CTitle::setFractionType(int fractionType) 49 { 50 this->fractionType = fractionType; 51 } 52 // 设置小数 53 void CTitle::setDecimal(int hasDecimal) 54 { 55 this->hasDecimal = hasDecimal; 56 } 57 // 设置小数位数 58 void CTitle::setDecimalLong(int decimalLong) 59 { 60 this->decimalLong = decimalLong; 61 this->decimalLongPow = pow(10, decimalLong); 62 }
接着,我们为类添加方法,让类可以按需求生成不同类型的运算题目。那么,如何才能使类可以随机生成不同类型的运算呢?通过对题目进行分析,我们发现所有可能出现的运算类型共有4中,他们分别是:
(1)整数运算
(2)整数运算 & 小数运算
(3)整数运算 & 分数运算
(4)整数运算 & 分数运算 & 小数运算
1 void CTitle::newTitle() 2 { 3 srand(time(0)); 4 int numType = 1 + hasDecimal + hasFraction; 5 int titleType = 1; 6 for (int i = 0; i < titleCount; i++) 7 { 8 switch(numType) 9 { 10 // 运算中仅有整数运算 11 case 1: 12 newIntegerFormula(); 13 break; 14 // 运算中包含包含两种运算,整数+小数 或 整数+分数 15 case 2: 16 titleType = rand() % 2 + 1; 17 if (titleType == 1) 18 { 19 newIntegerFormula(); 20 } 21 else 22 { 23 if (hasDecimal == 1) 24 { 25 newDecimalFormula(); 26 } 27 else if (hasFraction == 1) 28 { 29 newFractionFormula(); 30 } 31 } 32 break; 33 // 运算中包含小数运算和分数运算 34 case 3: 35 titleType = rand() % 3 + 1; 36 if (titleType == 1) 37 { 38 newIntegerFormula(); 39 } 40 else if (titleType == 2) 41 { 42 newDecimalFormula(); 43 } 44 else 45 { 46 newFractionFormula(); 47 } 48 break; 49 } 50 } 51 }
接下来我们需要解决的问题就是如何生成整数运算、小数运算以及分数运算并计算其结果。在本例中,我们用文件分别对运算表达式和运算结果进行存储。将表达式存储于Title.txt文件内,将运算结果存储于Answer.txt文件内。
所以,我们先要在Ctitle的构造函数内加入如下代码,以保证每次类初始化时文件内容会被清空。
1 FILE *fp = NULL; 2 errno_t err; 3 if ((err = fopen_s(&fp, "Title.txt", "w")) == 0) 4 { 5 fclose(fp); 6 } 7 if ((err = fopen_s(&fp, "Answer.txt", "w")) == 0) 8 { 9 fclose(fp); 10 }
并且增加写入表达式和写入答案的方法
1 // 记录题目至文件 2 void CTitle::recordTitle(string szTitle) 3 { 4 FILE *fp = NULL; 5 errno_t err; 6 if ((err = fopen_s(&fp, "Title.txt", "a")) == 0) 7 { 8 fprintf_s(fp, "%s\n", szTitle.c_str()); 9 fclose(fp); 10 } 11 } 12 // 记录答案至文件 13 void CTitle::recordAnswer(string szAnswer) 14 { 15 FILE *fp = NULL; 16 errno_t err; 17 if ((err = fopen_s(&fp, "Answer.txt", "a")) == 0) 18 { 19 fprintf_s(fp, "%s\n", szAnswer.c_str()); 20 fclose(fp); 21 } 22 }
接下来我们实现创建整数运算的方法,在这里与要注意以下问题:
(1)除法运算时,除数不能为0
(2)若运算不能还有余数,要保证生成的除法运算均可以整除
(3)若运算可以还有余数,要保证生成的结果为: 5 ÷ 2 = 2...1 ,而非2.5
1 // 新建整数运算公式 2 void CTitle::newIntegerFormula() 3 { 4 string szTitle = ""; 5 string szAnswer = ""; 6 char caTitle[100] = { "" }; 7 char caAnswer[100] = { "" }; 8 int num1 = rand() % numRand + 1;// 随机整数1,默认是正数 9 int num2 = rand() % numRand + 1;// 随机整数2,默认是正数 10 int op = rand() % 2 + 1; // 运算符,默认仅有加减运算 11 // 设置四则运算 12 if (hasMD == 1) 13 { 14 op = rand() % 4 + 1; 15 } 16 switch (op) 17 { 18 case 1: 19 // 加减运算中包含负数 20 if (hasMinus == 1) 21 { 22 num1 = rand() % (numRand * 2) - numRand; 23 num2 = rand() % (numRand * 2) - numRand; 24 } 25 if (num2 < 0) 26 { 27 // 记录题目 28 sprintf_s(caTitle, "%d+(%d)", num1, num2); 29 szTitle += caTitle; 30 recordTitle(szTitle); 31 // 记录答案 32 sprintf_s(caAnswer, "%d", num1+num2); 33 szAnswer += caAnswer; 34 recordAnswer(szAnswer); 35 } 36 else 37 { 38 // 记录题目 39 sprintf_s(caTitle, "%d+%d", num1, num2); 40 szTitle += caTitle; 41 recordTitle(szTitle); 42 // 记录答案 43 sprintf_s(caAnswer, "%d", num1 + num2); 44 szAnswer += caAnswer; 45 recordAnswer(szAnswer); 46 } 47 break; 48 case 2: 49 // 加减运算中包含负数 50 if (hasMinus == 1) 51 { 52 num1 = rand() % (numRand * 2) - numRand; 53 num2 = rand() % (numRand * 2) - numRand; 54 } 55 if (num2 < 0) 56 { 57 // 记录题目 58 sprintf_s(caTitle, "%d-(%d)", num1, num2); 59 szTitle += caTitle; 60 recordTitle(szTitle); 61 // 记录答案 62 sprintf_s(caAnswer, "%d", num1 - num2); 63 szAnswer += caAnswer; 64 recordAnswer(szAnswer); 65 } 66 else 67 { 68 // 记录题目 69 sprintf_s(caTitle, "%d-%d", num1, num2); 70 szTitle += caTitle; 71 recordTitle(szTitle); 72 // 记录答案 73 sprintf_s(caAnswer, "%d", num1 - num2); 74 szAnswer += caAnswer; 75 recordAnswer(szAnswer); 76 } 77 break; 78 case 3: 79 // 记录题目 80 sprintf_s(caTitle, "%d×%d", num1, num2); 81 szTitle += caTitle; 82 recordTitle(szTitle); 83 // 记录答案 84 sprintf_s(caAnswer, "%d", num1 * num2); 85 szAnswer += caAnswer; 86 recordAnswer(szAnswer); 87 break; 88 case 4: 89 // 除数为0 90 while (num2 == 0) 91 { 92 num2 = rand() % numRand + 1; 93 } 94 // 无余数的除法 95 if(hasResidue == 0) 96 { 97 // 保证可以整除 98 while (num1 % num2 != 0) 99 { 100 num1 = rand() % numRand + 1; 101 } 102 // 记录题目 103 sprintf_s(caTitle, "%d÷%d", num1, num2); 104 szTitle += caTitle; 105 recordTitle(szTitle); 106 // 记录答案 107 sprintf_s(caAnswer, "%d", num1 / num2); 108 szAnswer += caAnswer; 109 recordAnswer(szAnswer); 110 } 111 // 有余数的除法 112 else 113 { 114 // 记录题目 115 sprintf_s(caTitle, "%d÷%d", num1, num2); 116 szTitle += caTitle; 117 recordTitle(szTitle); 118 // 记录答案 119 sprintf_s(caAnswer, "%d...%d", num1 / num2, num1 % num2); 120 szAnswer += caAnswer; 121 recordAnswer(szAnswer); 122 } 123 break; 124 }126 }
接下来实现小数运算,在这里,小数的位数是可以自主选定的,所以如果小数位数选择的是2位,那么在记录如文件时也要保证多余位数不会被记录。如double类型默认写入文件是2.510000,那我们只能保留2.51,并将保留后的结果写入文件。
1 // 新建小数运算公式 2 void CTitle::newDecimalFormula() 3 { 4 string szTitle = ""; 5 string szAnswer = ""; 6 string szOp = ""; 7 string szNum1 = ""; 8 string szNum2 = ""; 9 char caTitle[100] = { "" }; 10 char caAnswer[100] = { "" }; 11 char caNum1[100] = { "" }; 12 char caNum2[100] = { "" }; 13 double dAnswer = 0; 14 int num1 = rand() % numRand + 1;// 随机整数1,默认是正数 15 int num2 = rand() % numRand + 1;// 随机整数2,默认是正数 16 double dotNum1 = (rand() % (decimalLongPow) +1);// 随机小数1 17 double dotNum2 = (rand() % (decimalLongPow)+1);// 随机小数2 18 int op = rand() % 2 + 1; // 运算符,默认仅有加减运算 19 dotNum1 = dotNum1 / decimalLongPow; 20 dotNum2 = dotNum2 / decimalLongPow; 21 sprintf_s(caNum1, "%lf", num1 + dotNum1); 22 szNum1 += caNum1; 23 szNum1 = szNum1.substr(0, szNum1.length() - 6 + decimalLong); 24 sprintf_s(caNum2, "%lf", num2 + dotNum2); 25 szNum2 += caNum2; 26 szNum2 = szNum2.substr(0, szNum2.length() - 6 + decimalLong); 27 // 设置四则运算 28 if (hasMD == 1) 29 { 30 op = rand() % 4 + 1; 31 } 32 switch (op) 33 { 34 case 1: 35 // 加减运算中包含负数 36 if (hasMinus == 1) 37 { 38 num1 = rand() % (numRand * 2) - numRand; 39 num2 = rand() % (numRand * 2) - numRand; 40 szNum1 = ""; 41 szNum2 = ""; 42 sprintf_s(caNum1, "%lf", num1 + dotNum1); 43 szNum1 += caNum1; 44 szNum1 = szNum1.substr(0, szNum1.length() - 6 + decimalLong); 45 sprintf_s(caNum2, "%lf", num2 + dotNum2); 46 szNum2 += caNum2; 47 szNum2 = szNum2.substr(0, szNum2.length() - 6 + decimalLong); 48 } 49 if (num2 < 0) 50 { 51 // 记录题目 52 szOp = "+"; 53 szNum2 = "(" + szNum2 + ")"; 54 // 记录答案 55 dAnswer = num1 + dotNum1 + num2 + dotNum2; 56 } 57 else 58 { 59 // 记录题目 60 szOp = "+"; 61 // 记录答案 62 dAnswer = num1 + dotNum1 + num2 + dotNum2; 63 } 64 break; 65 case 2: 66 // 加减运算中包含负数 67 if (hasMinus == 1) 68 { 69 num1 = rand() % (numRand * 2) - numRand; 70 num2 = rand() % (numRand * 2) - numRand; 71 szNum1 = ""; 72 szNum2 = ""; 73 sprintf_s(caNum1, "%lf", num1 + dotNum1); 74 szNum1 += caNum1; 75 szNum1 = szNum1.substr(0, szNum1.length() - 6 + decimalLong); 76 sprintf_s(caNum2, "%lf", num2 + dotNum2); 77 szNum2 += caNum2; 78 szNum2 = szNum2.substr(0, szNum2.length() - 6 + decimalLong); 79 } 80 if (num2 < 0) 81 { 82 // 记录题目 83 szOp = "-"; 84 szNum2 = "(" + szNum2 + ")"; 85 // 记录答案 86 dAnswer = num1 + dotNum1 - num2 - dotNum2; 87 } 88 else 89 { 90 // 记录题目 91 szOp = "-"; 92 // 记录答案 93 dAnswer = num1 + dotNum1 - num2 - dotNum2; 94 } 95 break; 96 case 3: 97 // 记录题目 98 szOp = "×"; 99 // 记录答案 100 dAnswer = (num1 + dotNum1) * (num2 + dotNum2); 101 break; 102 case 4: 103 // 除数为0 104 while (num2 == 0 && dotNum2 == 0) 105 { 106 num2 = rand() % numRand + 1; 107 dotNum2 = (rand() % (decimalLong)+1); 108 } 109 // 记录题目 110 szOp = "÷"; 111 // 记录答案 112 dAnswer = (num1 + dotNum1) / (num2 + dotNum2); 113 break; 114 } 115 // 记录题目 116 szTitle = szNum1 + szOp + szNum2; 117 recordTitle(szTitle); 118 // 记录答案 119 sprintf_s(caAnswer, "%lf", dAnswer); 120 szAnswer += caAnswer; 121 szAnswer = szAnswer.substr(0, szAnswer.length() - 6 + decimalLong); 122 recordAnswer(szAnswer);124 }
最后需要进行的就是生成分数,在这里,我们需要注意:
(1)是否进行假分数的运算
(2)要对分数的运算结果进行约分,将分数化为最简。并且将假分数化简。如5/2要化简为2‘1/2(二又二分之一)
1 // 新建分数运算 2 void CTitle::newFractionFormula() 3 { 4 string szTitle = ""; 5 string szAnswer = ""; 6 char caTitle[100] = { "" }; 7 char caAnswer[100] = { "" }; 8 int deno1 = rand() % numRand + 1;// 随机分母1,默认是正数 9 int deno2 = rand() % numRand + 1;// 随机分母2,默认是正数 10 int nume1 = rand() % numRand + 1;// 随机分子1,默认是正数 11 int nume2 = rand() % numRand + 1;// 随机分子2,默认是正数 12 int nweNume = 0; 13 int nweDeno = 0; 14 // 支持假分数 15 if (fractionType == 1) 16 { 17 // 分母为0或1 18 while (deno1 == 0 || deno1 == 1) 19 { 20 deno1 = rand() % numRand + 1; 21 } 22 while (deno2 == 0 || deno1 == 1) 23 { 24 deno2 = rand() % numRand + 1; 25 } 26 } 27 // 不支持假分数 28 if (fractionType == 0) 29 { 30 // 分母为0或1 31 while (nume1 == 0 || nume1 == 1) 32 { 33 nume1 = rand() % numRand + 1; 34 } 35 while (nume2 == 0 || nume2 == 1) 36 { 37 nume2 = rand() % numRand + 1; 38 } 39 if (deno1 < nume1) 40 { 41 int temp = deno1; 42 deno1 = nume1; 43 nume1 = temp; 44 } 45 if (deno2 < nume2) 46 { 47 int temp = deno2; 48 deno2 = nume2; 49 nume2 = temp; 50 } 51 } 52 int op = rand() % 2 + 1; // 运算符,默认仅有加减运算 53 // 设置四则运算 54 if (hasMD == 1) 55 { 56 op = rand() % 4 + 1; 57 } 58 switch (op) 59 { 60 case 1: 61 // 记录题目 62 sprintf_s(caTitle, "%d/%d+%d/%d", nume1, deno1, nume2, deno2); 63 szTitle += caTitle; 64 recordTitle(szTitle); 65 // 记录答案 66 nweNume = nume1*deno2 + deno1*nume2; 67 nweDeno = deno1*deno2; 68 break; 69 case 2: 70 // 记录题目 71 sprintf_s(caTitle, "%d/%d-%d/%d", nume1, deno1, nume2, deno2); 72 szTitle += caTitle; 73 recordTitle(szTitle); 74 // 记录答案 75 nweNume = nume1*deno2 - deno1*nume2; 76 nweDeno = deno1*deno2; 77 reduceFraction(nweNume, nweDeno); 78 break; 79 case 3: 80 // 记录题目 81 sprintf_s(caTitle, "%d/%d×%d/%d", nume1, deno1, nume2, deno2); 82 szTitle += caTitle; 83 recordTitle(szTitle); 84 // 记录答案 85 nweNume = nume1*nume2; 86 nweDeno = deno1*deno2; 87 break; 88 case 4: 89 // 记录题目 90 sprintf_s(caTitle, "%d/%d÷%d/%d", nume1, deno1, nume2, deno2); 91 szTitle += caTitle; 92 recordTitle(szTitle); 93 // 记录答案 94 nweNume = nume1*deno2; 95 nweDeno = deno1*nume2; 96 break; 97 } 98 reduceFraction(nweNume, nweDeno); // 化简 99 if (nweNume == 0) 100 { 101 recordAnswer("0"); 102 } 103 else if (nweDeno == 1) 104 { 105 sprintf_s(caAnswer, "%d", nweNume); 106 szAnswer += caAnswer; 107 recordAnswer(szAnswer); 108 } 109 else if (nweNume < nweDeno) 110 { 111 sprintf_s(caAnswer, "%d/%d", nweNume, nweDeno); 112 szAnswer += caAnswer; 113 recordAnswer(szAnswer); 114 } 115 else if (nweNume > nweDeno) 116 { 117 if (nweNume%nweDeno != 0) 118 { 119 sprintf_s(caAnswer, "%d'%d/%d", nweNume / nweDeno, nweNume%nweDeno, nweDeno); 120 szAnswer += caAnswer; 121 recordAnswer(szAnswer); 122 } 123 else 124 { 125 sprintf_s(caAnswer, "%d", nweNume / nweDeno); 126 szAnswer += caAnswer; 127 recordAnswer(szAnswer); 128 } 129 } 130 else 131 { 132 recordAnswer("1"); 133 } 134 } 135 // 化简分数 136 void CTitle::reduceFraction(int &nume, int &deno) 137 { 138 int temp; 139 int a = nume; 140 int b = deno; 141 int gcd = 0; // 最大公约式 142 if (a < b) 143 { 144 temp = a; 145 a = b; 146 b = temp; 147 } 148 while (b != 0) 149 { 150 temp = a % b; 151 a = b; 152 b = temp; 153 } 154 gcd = a; 155 nume = nume / gcd; 156 deno = deno / gcd; 157 }
至此,我们的类就编写完成了。接下来只需要在主函数生成类,并进行类方法的调用就行了。
1 #include "iostream" 2 #include "time.h" 3 #include "CTitle.h" 4 #include "CTitle.h" 5 using namespace std; 6 void setting(); 7 CTitle title; 8 int lineSpacing = 0; 9 int main() 10 { 11 setting(); 12 title.newTitle(); 13 // 从文件打印题目 14 FILE *fp = NULL; 15 FILE *fp2 = NULL; 16 errno_t err; 17 char caTitle[100]; 18 char caAnsewr[100]; 19 char myansewr[100]; 20 int mark = 1; 21 if ((err = fopen_s(&fp, "Title.txt", "r")) == 0 && (err = fopen_s(&fp2, "Answer.txt", "r")) == 0) 22 { 23 // 读取题目 24 while ((fscanf_s(fp, "%s", caTitle, 100) != EOF)) 25 { 26 // 读取答案 27 fscanf_s(fp2, "%s", caAnsewr, 100); 28 do 29 { 30 cout << caTitle << " = "; 31 cin >> myansewr; 32 if (strcmp(myansewr, caAnsewr) == 0) 33 { 34 cout << "√" << endl; 35 mark = 1; 36 } 37 else 38 { 39 cout << "×" << endl; 40 mark = 0; 41 } 42 for (int i = 0; i < lineSpacing; i++) 43 { 44 cout << endl; 45 } 46 } while (mark == 0); 47 } 48 cout << "恭喜你完成今日作业" << endl; 49 } 50 return 0; 51 } 52 void setting() 53 { 54 int input = 0; 55 cout << "请完成如下设置" << endl; 56 cout << "设置今日题量:"; 57 cin >> input; 58 title.setTitleCount(input); 59 cout << endl; 60 cout << "设置数值范围绝对值:"; 61 cin >> input; 62 title.setNumRand(input); 63 cout << endl; 64 do 65 { 66 cout << "加减有无负数,1有0无:"; 67 cin >> input; 68 } 69 while (input != 0 && input != 1); 70 title.setMinus(input); 71 cout << endl; 72 do 73 { 74 cout << "是否有乘除法,1有0无:"; 75 cin >> input; 76 } 77 while (input != 0 && input != 1); 78 title.setMD(input); 79 cout << endl; 80 if (input == 1) 81 { 82 do 83 { 84 cout << "整数除法有无余数,1有0无:"; 85 cin >> input; 86 } 87 while (input != 0 && input != 1); 88 title.setResidue(input); 89 cout << endl; 90 } 91 92 do 93 { 94 cout << "是否支持小数,1有0无:"; 95 cin >> input; 96 } 97 while (input != 0 && input != 1); 98 title.setDecimal(input); 99 cout << endl; 100 if (input == 1) 101 { 102 cout << "设置小数位数(1~5):"; 103 cin >> input; 104 title.setDecimalLong(input); 105 cout << endl; 106 } 107 do 108 { 109 cout << "是否支持分数,1有0无:"; 110 cin >> input; 111 } 112 while (input != 0 && input != 1); 113 title.setFraction(input); 114 cout << endl; 115 if (input == 1) 116 { 117 cout << "设置是否支持假分数,1有0无:"; 118 cin >> input; 119 title.setFractionType(input); 120 cout << endl; 121 } 122 cout << "设置打印行间隔:"; 123 cin >> input; 124 lineSpacing = input; 125 cout << endl; 126 }
恩,对于这一次的作业,我只完成了二元式的生成和运算,没有涉及到多元式,所以没有写上括号生成与运算的问题。本来只是想简单写写,好久没有用C++写过程序了,没想到写着写着就写了这么多行,但好像也没有实现什么功能,只是保证了最基础的运行。还是要继续提升啊。